| // 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) |
| } |