返回 DeepSeek-Reasonix
range_test.go
根目录 / internal / readcoord / range_test.go
1 package readcoord
2
3 import (
4 "math/rand"
5 "slices"
6 "testing"
7
8 "reasonix/internal/tool"
9 )
10
11 func ranges(pairs ...int) []tool.ReadRange {
12 out := make([]tool.ReadRange, 0, len(pairs)/2)
13 for i := 0; i+1 < len(pairs); i += 2 {
14 out = append(out, tool.ReadRange{Start: pairs[i], End: pairs[i+1]})
15 }
16 return out
17 }
18
19 func sameRanges(a, b []tool.ReadRange) bool { return slices.Equal(Normalize(a), Normalize(b)) }
20
21 func TestNormalizeMergesOverlapsAndAdjacency(t *testing.T) {
22 cases := []struct {
23 name string
24 in []tool.ReadRange
25 want []tool.ReadRange
26 }{
27 {"empty dropped", ranges(5, 5, 7, 3), nil},
28 {"adjacent merge", ranges(0, 5, 5, 9), ranges(0, 9)},
29 {"overlap merge", ranges(0, 6, 4, 9), ranges(0, 9)},
30 {"disjoint kept", ranges(0, 2, 5, 7), ranges(0, 2, 5, 7)},
31 {"contained dropped", ranges(0, 10, 3, 5), ranges(0, 10)},
32 }
33 for _, tc := range cases {
34 t.Run(tc.name, func(t *testing.T) {
35 if got := Normalize(tc.in); !sameRanges(got, tc.want) {
36 t.Fatalf("Normalize(%+v) = %+v, want %+v", tc.in, got, tc.want)
37 }
38 })
39 }
40 }
41
42 func TestSubtractReturnsUncoveredParts(t *testing.T) {
43 cases := []struct {
44 name string
45 want []tool.ReadRange
46 have []tool.ReadRange
47 wantLeft []tool.ReadRange
48 wantCover bool
49 }{
50 {"nothing covered", ranges(0, 10), nil, ranges(0, 10), false},
51 {"prefix covered", ranges(0, 10), ranges(0, 4), ranges(4, 10), false},
52 {"middle hole", ranges(0, 10), ranges(0, 4, 6, 10), ranges(4, 6), false},
53 {"fully covered", ranges(2, 5), ranges(0, 10), nil, true},
54 {"tail covered", ranges(0, 10), ranges(4, 10), ranges(0, 4), false},
55 }
56 for _, tc := range cases {
57 t.Run(tc.name, func(t *testing.T) {
58 got := Subtract(tc.want, tc.have)
59 if !sameRanges(got, tc.wantLeft) {
60 t.Fatalf("Subtract(%+v, %+v) = %+v, want %+v", tc.want, tc.have, got, tc.wantLeft)
61 }
62 if Covers(tc.have, tc.want) != tc.wantCover {
63 t.Fatalf("Covers(%+v, %+v) = %v, want %v", tc.have, tc.want, !tc.wantCover, tc.wantCover)
64 }
65 })
66 }
67 }
68
69 func randomRanges(rng *rand.Rand, n int) []tool.ReadRange {
70 out := make([]tool.ReadRange, 0, n)
71 for range n {
72 start := rng.Intn(40)
73 out = append(out, tool.ReadRange{Start: start, End: start + rng.Intn(12)})
74 }
75 return out
76 }
77
78 // TestCoverageAlgebraHoldsOverRandomSplits checks the property every paging
79 // decision depends on: the uncovered parts of a requirement, added back to what
80 // was already covered, cover the requirement exactly, and share no line with it.
81 func TestCoverageAlgebraHoldsOverRandomSplits(t *testing.T) {
82 rng := rand.New(rand.NewSource(7))
83 for i := range 2000 {
84 want := randomRanges(rng, 1+rng.Intn(5))
85 have := randomRanges(rng, 1+rng.Intn(5))
86 gaps := Subtract(want, have)
87 if !Covers(append(append([]tool.ReadRange(nil), have...), gaps...), want) {
88 t.Fatalf("case %d: covered %+v plus gaps %+v does not cover %+v", i, have, gaps, want)
89 }
90 for _, gap := range gaps {
91 for _, h := range Normalize(have) {
92 if start, end := max(gap.Start, h.Start), min(gap.End, h.End); start < end {
93 t.Fatalf("case %d: gap %+v overlaps covered %+v", i, gap, h)
94 }
95 }
96 }
97 }
98 }
99
100 func TestNormalizeIsOrderIndependent(t *testing.T) {
101 rng := rand.New(rand.NewSource(11))
102 for i := range 500 {
103 in := randomRanges(rng, 1+rng.Intn(6))
104 want := Normalize(in)
105 shuffled := append([]tool.ReadRange(nil), in...)
106 rng.Shuffle(len(shuffled), func(a, b int) { shuffled[a], shuffled[b] = shuffled[b], shuffled[a] })
107 if got := Normalize(shuffled); !sameRanges(got, want) {
108 t.Fatalf("case %d: Normalize depends on order: %+v vs %+v", i, got, want)
109 }
110 if again := Normalize(want); !sameRanges(again, want) {
111 t.Fatalf("case %d: Normalize is not idempotent: %+v vs %+v", i, again, want)
112 }
113 }
114 }
115
116 func FuzzCoverageRecombination(f *testing.F) {
117 f.Add([]byte{0, 10, 3, 6}, []byte{0, 4, 8, 10})
118 f.Fuzz(func(t *testing.T, wantRaw, haveRaw []byte) {
119 want := decodeRanges(wantRaw)
120 have := decodeRanges(haveRaw)
121 gaps := Subtract(want, have)
122 if !Covers(append(append([]tool.ReadRange(nil), have...), gaps...), want) {
123 t.Fatalf("covered %+v plus gaps %+v does not cover %+v", have, gaps, want)
124 }
125 })
126 }
127
128 func decodeRanges(raw []byte) []tool.ReadRange {
129 out := make([]tool.ReadRange, 0, len(raw)/2)
130 for i := 0; i+1 < len(raw); i += 2 {
131 start := int(raw[i])
132 out = append(out, tool.ReadRange{Start: start, End: start + int(raw[i+1])})
133 }
134 return out
135 }
136
136 lines GO