1
2
3
4
5
6
7 package bytealg_test
8
9 import (
10 "fmt"
11 "internal/bytealg"
12 "math/rand"
13 "strings"
14 "testing"
15 )
16
17
18
19 func indexRef(hay, needle string) int {
20 for i := 0; i+len(needle) <= len(hay); i++ {
21 if hay[i:i+len(needle)] == needle {
22 return i
23 }
24 }
25 return -1
26 }
27
28
29
30 func check(t *testing.T, hay, needle string) {
31 t.Helper()
32 want := indexRef(hay, needle)
33 if got := bytealg.IndexString(hay, needle); got != want {
34 t.Errorf("IndexString(%q, %q) = %d, want %d", hay, needle, got, want)
35 }
36 if got := bytealg.Index([]byte(hay), []byte(needle)); got != want {
37 t.Errorf("Index(%q, %q) = %d, want %d", hay, needle, got, want)
38 }
39 }
40
41
42
43
44
45
46 func TestIndexExhaustive(t *testing.T) {
47 if bytealg.MaxLen < 2 {
48 t.Skip("Index not enabled")
49 }
50 maxNeedle := bytealg.MaxLen
51 if maxNeedle > 64 {
52 maxNeedle = 64
53 }
54
55
56 content := func(n int) string {
57 s := make([]byte, n)
58 for i := range s {
59 s[i] = byte('A' + i%23)
60 }
61 return string(s)
62 }
63 for n := 2; n <= maxNeedle; n++ {
64 for _, extra := range []int{0, 1, 7, 13, 14, 15, 16, 17, 30, 31, 32, 33, 47, 48, 63, 64, 100} {
65 h := n + extra
66 hay := content(h)
67
68
69
70 for pos := 0; pos+n <= h; pos++ {
71 check(t, hay, hay[pos:pos+n])
72 }
73
74 check(t, hay, strings.Repeat("z", n))
75
76
77 corrupt := []byte(hay[h-n : h])
78 corrupt[n/2] ^= 0x80
79 check(t, hay, string(corrupt))
80 }
81 }
82 }
83
84
85
86
87 func TestIndexPathological(t *testing.T) {
88 if bytealg.MaxLen < 2 {
89 t.Skip("Index not enabled")
90 }
91 tests := []struct {
92 name string
93 a, b string
94 }{
95
96 {"anchor false positives", strings.Repeat("ab", 100), "a" + strings.Repeat("c", 10) + "b"},
97
98 {"all candidates no match", strings.Repeat("a", 200), "a" + strings.Repeat("a", 10) + "b"},
99 {"all candidates match at 0", strings.Repeat("a", 200), strings.Repeat("a", 12)},
100 {"all candidates match late", strings.Repeat("a", 100) + "b" + strings.Repeat("a", 20), strings.Repeat("a", 8) + "b"},
101
102 {"overlapping periodic", strings.Repeat("abcab", 40), "abcabcab"},
103
104
105
106
107
108
109 {"anchor collisions n=4 no match", strings.Repeat("a", 200), "aqqa"},
110 {"anchor collisions n=5 no match", strings.Repeat("a", 200), "aqqqa"},
111 {"anchor collisions n=6 no match", strings.Repeat("a", 200), "aqqqqa"},
112 {"anchor collisions n=7 no match", strings.Repeat("a", 200), "aqqqqqa"},
113 {"anchor collisions n=8 no match", strings.Repeat("a", 200), "aqqqqqqa"},
114 {"anchor collisions n=4 match late", strings.Repeat("a", 150) + "aqqa" + strings.Repeat("a", 46), "aqqa"},
115 {"anchor collisions n=5 match late", strings.Repeat("a", 150) + "aqqqa" + strings.Repeat("a", 45), "aqqqa"},
116 {"anchor collisions n=6 match late", strings.Repeat("a", 150) + "aqqqqa" + strings.Repeat("a", 44), "aqqqqa"},
117 {"anchor collisions n=7 match late", strings.Repeat("a", 150) + "aqqqqqa" + strings.Repeat("a", 43), "aqqqqqa"},
118 {"anchor collisions n=8 match late", strings.Repeat("a", 150) + "aqqqqqqa" + strings.Repeat("a", 42), "aqqqqqqa"},
119
120 {"match at end", strings.Repeat("x", 50) + "needle", "needle"},
121 {"match at start", "needle" + strings.Repeat("x", 50), "needle"},
122
123 {"near end miss", strings.Repeat("x", 53) + "needl!", "needle"},
124
125 {"last byte differs", strings.Repeat("x", 60) + "ne", "nz"},
126 {"last byte matches", strings.Repeat("x", 60) + "ne", "ne"},
127 }
128
129
130
131
132 for n := 5; n <= 32; n++ {
133 for _, mid := range []int{2, n / 2, n - 3} {
134 needle := []byte(strings.Repeat("a", n))
135 needle[mid] = 'b'
136 tests = append(tests,
137 struct{ name, a, b string }{
138 fmt.Sprintf("refine passes n=%d b[%d] no match", n, mid),
139 strings.Repeat("a", 200), string(needle),
140 },
141 struct{ name, a, b string }{
142 fmt.Sprintf("refine passes n=%d b[%d] match late", n, mid),
143 strings.Repeat("a", 150) + string(needle) + strings.Repeat("a", 30), string(needle),
144 })
145 }
146 }
147 for _, tt := range tests {
148 t.Run(tt.name, func(t *testing.T) {
149 if len(tt.b) > bytealg.MaxLen {
150 t.Skip("needle longer than MaxLen")
151 }
152 check(t, tt.a, tt.b)
153 })
154 }
155 }
156
157
158
159
160 func TestIndexRandom(t *testing.T) {
161 if bytealg.MaxLen < 2 {
162 t.Skip("Index not enabled")
163 }
164 r := rand.New(rand.NewSource(1))
165 for _, alphabet := range []string{"a", "ab", "abc", "abcdefgh"} {
166 for i := 0; i < 5000; i++ {
167 n := 2 + r.Intn(bytealg.MaxLen-1)
168 h := n + r.Intn(300)
169 hay := make([]byte, h)
170 for j := range hay {
171 hay[j] = alphabet[r.Intn(len(alphabet))]
172 }
173 var needle string
174 if r.Intn(2) == 0 && h > n {
175
176 pos := r.Intn(h - n)
177 needle = string(hay[pos : pos+n])
178 } else {
179 nb := make([]byte, n)
180 for j := range nb {
181 nb[j] = alphabet[r.Intn(len(alphabet))]
182 }
183 needle = string(nb)
184 }
185 check(t, string(hay), needle)
186 }
187 }
188 }
189
190
191
192
193
194
195
196
197
198
199
200 func BenchmarkIndexString(b *testing.B) {
201 r := rand.New(rand.NewSource(1))
202 base := make([]byte, 4096)
203 for i := range base {
204 base[i] = 'a' + byte(r.Intn(26))
205 }
206 sizes := []int{16, 24, 32, 64, 512, 4096}
207 for _, n := range []int{2, 4, 8, 16, 32} {
208 if n > bytealg.MaxLen {
209 continue
210 }
211 for _, h := range sizes {
212 if h < n {
213 continue
214 }
215 hay := make([]byte, h)
216 copy(hay, base)
217 needle := make([]byte, n)
218 for i := range needle {
219 needle[i] = 'A' + byte(r.Intn(26))
220 }
221 copy(hay[h-n:], needle)
222 s, sep := string(hay), string(needle)
223 b.Run(fmt.Sprintf("text/n=%d/h=%d", n, h), func(b *testing.B) {
224 b.SetBytes(int64(h))
225 for i := 0; i < b.N; i++ {
226 if bytealg.IndexString(s, sep) != h-n {
227 b.Fatal("wrong result")
228 }
229 }
230 })
231 }
232 }
233 for _, n := range []int{4, 8, 16, 32} {
234 if n > bytealg.MaxLen {
235 continue
236 }
237 for _, h := range sizes {
238 if h < n {
239 continue
240 }
241 s := strings.Repeat("ab", h/2)
242 sep := "a" + strings.Repeat("c", n-2) + "b"
243 b.Run(fmt.Sprintf("hard/n=%d/h=%d", n, h), func(b *testing.B) {
244 b.SetBytes(int64(h))
245 for i := 0; i < b.N; i++ {
246 if bytealg.IndexString(s, sep) != -1 {
247 b.Fatal("wrong result")
248 }
249 }
250 })
251 }
252 }
253 }
254
View as plain text