// Copyright 2024 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package liveness import ( "flag" "fmt" "math/rand" "os" "sort" "testing" ) func TestMain(m *testing.M) { flag.Parse() os.Exit(m.Run()) } func TestMakeAndPrint(t *testing.T) { testcases := []struct { inp []int exp string err bool }{ { inp: []int{0, 1, 2, 3}, exp: "[0,1) [2,3)", }, { // degenerate but legal inp: []int{0, 1, 1, 2}, exp: "[0,1) [1,2)", }, { // odd number of elems inp: []int{0}, err: true, exp: "odd number of elems 1", }, { // bad range element inp: []int{0, 0}, err: true, exp: "bad range elem 0:0, en<=st", }, { // overlap w/ previous inp: []int{0, 9, 3, 12}, err: true, exp: "bad range elem 3:12 overlaps prev 0:9", }, { // range starts not ordered inp: []int{10, 11, 3, 4}, err: true, exp: "range start not ordered 3:4 less than prev 10:11", }, } for k, tc := range testcases { is, err := makeIntervals(tc.inp...) want := tc.exp if err != nil { if !tc.err { t.Fatalf("unexpected error on tc:%d %+v -> %v", k, tc.inp, err) } else { got := fmt.Sprintf("%v", err) if got != want { t.Fatalf("bad error on tc:%d %+v got %q want %q", k, tc.inp, got, want) } } continue } else if tc.err { t.Fatalf("missing error on tc:%d %+v return was %q", k, tc.inp, is.String()) } got := is.String() if got != want { t.Fatalf("exp mismatch on tc:%d %+v got %q want %q", k, tc.inp, got, want) } } } func TestIntervalOverlap(t *testing.T) { testcases := []struct { i1, i2 Interval exp bool }{ { i1: Interval{st: 0, en: 1}, i2: Interval{st: 0, en: 1}, exp: true, }, { i1: Interval{st: 0, en: 1}, i2: Interval{st: 1, en: 2}, exp: false, }, { i1: Interval{st: 9, en: 10}, i2: Interval{st: 1, en: 2}, exp: false, }, { i1: Interval{st: 0, en: 10}, i2: Interval{st: 5, en: 6}, exp: true, }, } for _, tc := range testcases { want := tc.exp got := tc.i1.Overlaps(tc.i2) if want != got { t.Fatalf("Overlaps([%d,%d), [%d,%d)): got %v want %v", tc.i1.st, tc.i1.en, tc.i2.st, tc.i2.en, got, want) } } } func TestIntervalAdjacent(t *testing.T) { testcases := []struct { i1, i2 Interval exp bool }{ { i1: Interval{st: 0, en: 1}, i2: Interval{st: 0, en: 1}, exp: false, }, { i1: Interval{st: 0, en: 1}, i2: Interval{st: 1, en: 2}, exp: true, }, { i1: Interval{st: 1, en: 2}, i2: Interval{st: 0, en: 1}, exp: true, }, { i1: Interval{st: 0, en: 10}, i2: Interval{st: 0, en: 3}, exp: false, }, } for k, tc := range testcases { want := tc.exp got := tc.i1.adjacent(tc.i2) if want != got { t.Fatalf("tc=%d adjacent([%d,%d), [%d,%d)): got %v want %v", k, tc.i1.st, tc.i1.en, tc.i2.st, tc.i2.en, got, want) } } } func TestIntervalMerge(t *testing.T) { testcases := []struct { i1, i2 Interval exp Interval err bool }{ { // error case i1: Interval{st: 0, en: 1}, i2: Interval{st: 2, en: 3}, err: true, }, { // same i1: Interval{st: 0, en: 1}, i2: Interval{st: 0, en: 1}, exp: Interval{st: 0, en: 1}, err: false, }, { // adjacent i1: Interval{st: 0, en: 1}, i2: Interval{st: 1, en: 2}, exp: Interval{st: 0, en: 2}, err: false, }, { // overlapping 1 i1: Interval{st: 0, en: 5}, i2: Interval{st: 3, en: 10}, exp: Interval{st: 0, en: 10}, err: false, }, { // overlapping 2 i1: Interval{st: 9, en: 15}, i2: Interval{st: 3, en: 11}, exp: Interval{st: 3, en: 15}, err: false, }, } for k, tc := range testcases { var dst Interval dstp := &dst dst = tc.i1 err := dstp.MergeInto(tc.i2) if (err != nil) != tc.err { t.Fatalf("tc=%d MergeInto([%d,%d) <= [%d,%d)): got err=%v want err=%v", k, tc.i1.st, tc.i1.en, tc.i2.st, tc.i2.en, err, tc.err) } if err != nil { continue } want := tc.exp.String() got := dst.String() if want != got { t.Fatalf("tc=%d MergeInto([%d,%d) <= [%d,%d)): got %v want %v", k, tc.i1.st, tc.i1.en, tc.i2.st, tc.i2.en, got, want) } } } func TestIntervalsOverlap(t *testing.T) { testcases := []struct { inp1, inp2 []int exp bool }{ { // first empty inp1: []int{}, inp2: []int{1, 2}, exp: false, }, { // second empty inp1: []int{9, 10}, inp2: []int{}, exp: false, }, { // disjoint 1 inp1: []int{1, 2}, inp2: []int{2, 3}, exp: false, }, { // disjoint 2 inp1: []int{2, 3}, inp2: []int{1, 2}, exp: false, }, { // interleaved 1 inp1: []int{1, 2, 3, 4}, inp2: []int{2, 3, 5, 6}, exp: false, }, { // interleaved 2 inp1: []int{2, 3, 5, 6}, inp2: []int{1, 2, 3, 4}, exp: false, }, { // overlap 1 inp1: []int{1, 3}, inp2: []int{2, 9, 10, 11}, exp: true, }, { // overlap 2 inp1: []int{18, 29}, inp2: []int{2, 9, 10, 19}, exp: true, }, } for k, tc := range testcases { is1, err1 := makeIntervals(tc.inp1...) if err1 != nil { t.Fatalf("unexpected error on tc:%d %+v: %v", k, tc.inp1, err1) } is2, err2 := makeIntervals(tc.inp2...) if err2 != nil { t.Fatalf("unexpected error on tc:%d %+v: %v", k, tc.inp2, err2) } got := is1.Overlaps(is2) want := tc.exp if got != want { t.Fatalf("overlaps mismatch on tc:%d %+v %+v got %v want %v", k, tc.inp1, tc.inp2, got, want) } } } var seedflag = flag.Int64("seed", 101, "Random seed") var trialsflag = flag.Int64("trials", 10000, "Number of trials") var segsflag = flag.Int64("segs", 4, "Max segments within interval") var limitflag = flag.Int64("limit", 20, "Limit of interval max end") // NB: consider turning this into a fuzz test if the interval data // structures or code get any more complicated. func TestRandomIntervalsOverlap(t *testing.T) { rand.Seed(*seedflag) // Return a pseudo-random intervals object with 0-3 segments within // the range of 0 to limit mk := func() Intervals { vals := rand.Perm(int(*limitflag)) // decide how many segments segs := rand.Intn(int(*segsflag)) picked := vals[:(segs * 2)] sort.Ints(picked) ii, err := makeIntervals(picked...) if err != nil { t.Fatalf("makeIntervals(%+v) returns err %v", picked, err) } return ii } brute := func(i1, i2 Intervals) bool { for i := range i1 { for j := range i2 { if i1[i].Overlaps(i2[j]) { return true } } } return false } for k := range *trialsflag { // Create two interval ranges and test if they overlap. Then // compare the overlap with a brute-force overlap calculation. i1, i2 := mk(), mk() got := i1.Overlaps(i2) want := brute(i1, i2) if got != want { t.Fatalf("overlap mismatch on t:%d %v %v got %v want %v", k, i1, i2, got, want) } } } func TestIntervalsMerge(t *testing.T) { testcases := []struct { inp1, inp2 []int exp []int }{ { // first empty inp1: []int{}, inp2: []int{1, 2}, exp: []int{1, 2}, }, { // second empty inp1: []int{1, 2}, inp2: []int{}, exp: []int{1, 2}, }, { // overlap 1 inp1: []int{1, 2}, inp2: []int{2, 3}, exp: []int{1, 3}, }, { // overlap 2 inp1: []int{1, 5}, inp2: []int{2, 10}, exp: []int{1, 10}, }, { // non-overlap 1 inp1: []int{1, 2}, inp2: []int{11, 12}, exp: []int{1, 2, 11, 12}, }, { // non-overlap 2 inp1: []int{1, 2, 3, 4, 5, 6}, inp2: []int{2, 3, 4, 5, 6, 7}, exp: []int{1, 7}, }, } for k, tc := range testcases { is1, err1 := makeIntervals(tc.inp1...) if err1 != nil { t.Fatalf("unexpected error on tc:%d %+v: %v", k, tc.inp1, err1) } is2, err2 := makeIntervals(tc.inp2...) if err2 != nil { t.Fatalf("unexpected error on tc:%d %+v: %v", k, tc.inp2, err2) } m := is1.Merge(is2) wis, werr := makeIntervals(tc.exp...) if werr != nil { t.Fatalf("unexpected error on tc:%d %+v: %v", k, tc.exp, werr) } want := wis.String() got := m.String() if want != got { t.Fatalf("k=%d Merge(%s, %s): got %v want %v", k, is1, is2, m, want) } } } func TestBuilder(t *testing.T) { type posLiveKill struct { pos int becomesLive, isKill bool // what to pass to IntervalsBuilder } testcases := []struct { inp []posLiveKill exp []int aerr, ferr bool }{ // error case, position non-decreasing { inp: []posLiveKill{ posLiveKill{pos: 10, becomesLive: true}, posLiveKill{pos: 18, isKill: true}, }, aerr: true, }, // error case, position negative { inp: []posLiveKill{ posLiveKill{pos: -1, becomesLive: true}, }, aerr: true, }, // empty { exp: nil, }, // single BB { inp: []posLiveKill{ posLiveKill{pos: 10, becomesLive: true}, posLiveKill{pos: 9, isKill: true}, }, exp: []int{10, 11}, }, // couple of BBs { inp: []posLiveKill{ posLiveKill{pos: 11, becomesLive: true}, posLiveKill{pos: 10, becomesLive: true}, posLiveKill{pos: 9, isKill: true}, posLiveKill{pos: 4, becomesLive: true}, posLiveKill{pos: 1, isKill: true}, }, exp: []int{2, 5, 10, 12}, }, // couple of BBs { inp: []posLiveKill{ posLiveKill{pos: 20, isKill: true}, posLiveKill{pos: 19, isKill: true}, posLiveKill{pos: 17, becomesLive: true}, posLiveKill{pos: 14, becomesLive: true}, posLiveKill{pos: 10, isKill: true}, posLiveKill{pos: 4, becomesLive: true}, posLiveKill{pos: 0, isKill: true}, }, exp: []int{1, 5, 11, 18}, }, } for k, tc := range testcases { var c IntervalsBuilder var aerr error for _, event := range tc.inp { if event.becomesLive { if err := c.Live(event.pos); err != nil { aerr = err break } } if event.isKill { if err := c.Kill(event.pos); err != nil { aerr = err break } } } if (aerr != nil) != tc.aerr { t.Fatalf("k=%d add err mismatch: tc.aerr:%v aerr!=nil:%v", k, tc.aerr, (aerr != nil)) } if tc.aerr { continue } ii, ferr := c.Finish() if ferr != nil { if tc.ferr { continue } t.Fatalf("h=%d finish err mismatch: tc.ferr:%v ferr!=nil:%v", k, tc.ferr, ferr != nil) } got := ii.String() wis, werr := makeIntervals(tc.exp...) if werr != nil { t.Fatalf("unexpected error on tc:%d %+v: %v", k, tc.exp, werr) } want := wis.String() if want != got { t.Fatalf("k=%d Ctor test: got %v want %v", k, got, want) } } } // makeIntervals constructs an Intervals object from the start/end // sequence in nums, expected to be of the form // s1,en1,st2,en2,...,stk,enk. Used only for unit testing. func makeIntervals(nums ...int) (Intervals, error) { var r Intervals if len(nums)&1 != 0 { return r, fmt.Errorf("odd number of elems %d", len(nums)) } for i := 0; i < len(nums); i += 2 { st := nums[i] en := nums[i+1] r = append(r, Interval{st: st, en: en}) } return r, check(r) }