]> Cypherpunks.ru repositories - gostls13.git/blob - src/slices/slices_test.go
slices: optimize Index and Compact for large types
[gostls13.git] / src / slices / slices_test.go
1 // Copyright 2021 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 package slices
6
7 import (
8         "internal/race"
9         "internal/testenv"
10         "math"
11         "strings"
12         "testing"
13 )
14
15 var equalIntTests = []struct {
16         s1, s2 []int
17         want   bool
18 }{
19         {
20                 []int{1},
21                 nil,
22                 false,
23         },
24         {
25                 []int{},
26                 nil,
27                 true,
28         },
29         {
30                 []int{1, 2, 3},
31                 []int{1, 2, 3},
32                 true,
33         },
34         {
35                 []int{1, 2, 3},
36                 []int{1, 2, 3, 4},
37                 false,
38         },
39 }
40
41 var equalFloatTests = []struct {
42         s1, s2       []float64
43         wantEqual    bool
44         wantEqualNaN bool
45 }{
46         {
47                 []float64{1, 2},
48                 []float64{1, 2},
49                 true,
50                 true,
51         },
52         {
53                 []float64{1, 2, math.NaN()},
54                 []float64{1, 2, math.NaN()},
55                 false,
56                 true,
57         },
58 }
59
60 func TestEqual(t *testing.T) {
61         for _, test := range equalIntTests {
62                 if got := Equal(test.s1, test.s2); got != test.want {
63                         t.Errorf("Equal(%v, %v) = %t, want %t", test.s1, test.s2, got, test.want)
64                 }
65         }
66         for _, test := range equalFloatTests {
67                 if got := Equal(test.s1, test.s2); got != test.wantEqual {
68                         t.Errorf("Equal(%v, %v) = %t, want %t", test.s1, test.s2, got, test.wantEqual)
69                 }
70         }
71 }
72
73 // equal is simply ==.
74 func equal[T comparable](v1, v2 T) bool {
75         return v1 == v2
76 }
77
78 // equalNaN is like == except that all NaNs are equal.
79 func equalNaN[T comparable](v1, v2 T) bool {
80         isNaN := func(f T) bool { return f != f }
81         return v1 == v2 || (isNaN(v1) && isNaN(v2))
82 }
83
84 // offByOne returns true if integers v1 and v2 differ by 1.
85 func offByOne(v1, v2 int) bool {
86         return v1 == v2+1 || v1 == v2-1
87 }
88
89 func TestEqualFunc(t *testing.T) {
90         for _, test := range equalIntTests {
91                 if got := EqualFunc(test.s1, test.s2, equal[int]); got != test.want {
92                         t.Errorf("EqualFunc(%v, %v, equal[int]) = %t, want %t", test.s1, test.s2, got, test.want)
93                 }
94         }
95         for _, test := range equalFloatTests {
96                 if got := EqualFunc(test.s1, test.s2, equal[float64]); got != test.wantEqual {
97                         t.Errorf("Equal(%v, %v, equal[float64]) = %t, want %t", test.s1, test.s2, got, test.wantEqual)
98                 }
99                 if got := EqualFunc(test.s1, test.s2, equalNaN[float64]); got != test.wantEqualNaN {
100                         t.Errorf("Equal(%v, %v, equalNaN[float64]) = %t, want %t", test.s1, test.s2, got, test.wantEqualNaN)
101                 }
102         }
103
104         s1 := []int{1, 2, 3}
105         s2 := []int{2, 3, 4}
106         if EqualFunc(s1, s1, offByOne) {
107                 t.Errorf("EqualFunc(%v, %v, offByOne) = true, want false", s1, s1)
108         }
109         if !EqualFunc(s1, s2, offByOne) {
110                 t.Errorf("EqualFunc(%v, %v, offByOne) = false, want true", s1, s2)
111         }
112
113         s3 := []string{"a", "b", "c"}
114         s4 := []string{"A", "B", "C"}
115         if !EqualFunc(s3, s4, strings.EqualFold) {
116                 t.Errorf("EqualFunc(%v, %v, strings.EqualFold) = false, want true", s3, s4)
117         }
118
119         cmpIntString := func(v1 int, v2 string) bool {
120                 return string(rune(v1)-1+'a') == v2
121         }
122         if !EqualFunc(s1, s3, cmpIntString) {
123                 t.Errorf("EqualFunc(%v, %v, cmpIntString) = false, want true", s1, s3)
124         }
125 }
126
127 func BenchmarkEqualFunc_Large(b *testing.B) {
128         type Large [4 * 1024]byte
129
130         xs := make([]Large, 1024)
131         ys := make([]Large, 1024)
132         for i := 0; i < b.N; i++ {
133                 _ = EqualFunc(xs, ys, func(x, y Large) bool { return x == y })
134         }
135 }
136
137 var indexTests = []struct {
138         s    []int
139         v    int
140         want int
141 }{
142         {
143                 nil,
144                 0,
145                 -1,
146         },
147         {
148                 []int{},
149                 0,
150                 -1,
151         },
152         {
153                 []int{1, 2, 3},
154                 2,
155                 1,
156         },
157         {
158                 []int{1, 2, 2, 3},
159                 2,
160                 1,
161         },
162         {
163                 []int{1, 2, 3, 2},
164                 2,
165                 1,
166         },
167 }
168
169 func TestIndex(t *testing.T) {
170         for _, test := range indexTests {
171                 if got := Index(test.s, test.v); got != test.want {
172                         t.Errorf("Index(%v, %v) = %d, want %d", test.s, test.v, got, test.want)
173                 }
174         }
175 }
176
177 func equalToIndex[T any](f func(T, T) bool, v1 T) func(T) bool {
178         return func(v2 T) bool {
179                 return f(v1, v2)
180         }
181 }
182
183 func BenchmarkIndex_Large(b *testing.B) {
184         type Large [4 * 1024]byte
185
186         ss := make([]Large, 1024)
187         for i := 0; i < b.N; i++ {
188                 _ = Index(ss, Large{1})
189         }
190 }
191
192 func TestIndexFunc(t *testing.T) {
193         for _, test := range indexTests {
194                 if got := IndexFunc(test.s, equalToIndex(equal[int], test.v)); got != test.want {
195                         t.Errorf("IndexFunc(%v, equalToIndex(equal[int], %v)) = %d, want %d", test.s, test.v, got, test.want)
196                 }
197         }
198
199         s1 := []string{"hi", "HI"}
200         if got := IndexFunc(s1, equalToIndex(equal[string], "HI")); got != 1 {
201                 t.Errorf("IndexFunc(%v, equalToIndex(equal[string], %q)) = %d, want %d", s1, "HI", got, 1)
202         }
203         if got := IndexFunc(s1, equalToIndex(strings.EqualFold, "HI")); got != 0 {
204                 t.Errorf("IndexFunc(%v, equalToIndex(strings.EqualFold, %q)) = %d, want %d", s1, "HI", got, 0)
205         }
206 }
207
208 func BenchmarkIndexFunc_Large(b *testing.B) {
209         type Large [4 * 1024]byte
210
211         ss := make([]Large, 1024)
212         for i := 0; i < b.N; i++ {
213                 _ = IndexFunc(ss, func(e Large) bool {
214                         return e == Large{1}
215                 })
216         }
217 }
218
219 func TestContains(t *testing.T) {
220         for _, test := range indexTests {
221                 if got := Contains(test.s, test.v); got != (test.want != -1) {
222                         t.Errorf("Contains(%v, %v) = %t, want %t", test.s, test.v, got, test.want != -1)
223                 }
224         }
225 }
226
227 func TestContainsFunc(t *testing.T) {
228         for _, test := range indexTests {
229                 if got := ContainsFunc(test.s, equalToIndex(equal[int], test.v)); got != (test.want != -1) {
230                         t.Errorf("ContainsFunc(%v, equalToIndex(equal[int], %v)) = %t, want %t", test.s, test.v, got, test.want != -1)
231                 }
232         }
233
234         s1 := []string{"hi", "HI"}
235         if got := ContainsFunc(s1, equalToIndex(equal[string], "HI")); got != true {
236                 t.Errorf("ContainsFunc(%v, equalToContains(equal[string], %q)) = %t, want %t", s1, "HI", got, true)
237         }
238         if got := ContainsFunc(s1, equalToIndex(equal[string], "hI")); got != false {
239                 t.Errorf("ContainsFunc(%v, equalToContains(strings.EqualFold, %q)) = %t, want %t", s1, "hI", got, false)
240         }
241         if got := ContainsFunc(s1, equalToIndex(strings.EqualFold, "hI")); got != true {
242                 t.Errorf("ContainsFunc(%v, equalToContains(strings.EqualFold, %q)) = %t, want %t", s1, "hI", got, true)
243         }
244 }
245
246 var insertTests = []struct {
247         s    []int
248         i    int
249         add  []int
250         want []int
251 }{
252         {
253                 []int{1, 2, 3},
254                 0,
255                 []int{4},
256                 []int{4, 1, 2, 3},
257         },
258         {
259                 []int{1, 2, 3},
260                 1,
261                 []int{4},
262                 []int{1, 4, 2, 3},
263         },
264         {
265                 []int{1, 2, 3},
266                 3,
267                 []int{4},
268                 []int{1, 2, 3, 4},
269         },
270         {
271                 []int{1, 2, 3},
272                 2,
273                 []int{4, 5},
274                 []int{1, 2, 4, 5, 3},
275         },
276 }
277
278 func TestInsert(t *testing.T) {
279         s := []int{1, 2, 3}
280         if got := Insert(s, 0); !Equal(got, s) {
281                 t.Errorf("Insert(%v, 0) = %v, want %v", s, got, s)
282         }
283         for _, test := range insertTests {
284                 copy := Clone(test.s)
285                 if got := Insert(copy, test.i, test.add...); !Equal(got, test.want) {
286                         t.Errorf("Insert(%v, %d, %v...) = %v, want %v", test.s, test.i, test.add, got, test.want)
287                 }
288         }
289
290         if !testenv.OptimizationOff() && !race.Enabled {
291                 // Allocations should be amortized.
292                 const count = 50
293                 n := testing.AllocsPerRun(10, func() {
294                         s := []int{1, 2, 3}
295                         for i := 0; i < count; i++ {
296                                 s = Insert(s, 0, 1)
297                         }
298                 })
299                 if n > count/2 {
300                         t.Errorf("too many allocations inserting %d elements: got %v, want less than %d", count, n, count/2)
301                 }
302         }
303 }
304
305 var deleteTests = []struct {
306         s    []int
307         i, j int
308         want []int
309 }{
310         {
311                 []int{1, 2, 3},
312                 0,
313                 0,
314                 []int{1, 2, 3},
315         },
316         {
317                 []int{1, 2, 3},
318                 0,
319                 1,
320                 []int{2, 3},
321         },
322         {
323                 []int{1, 2, 3},
324                 3,
325                 3,
326                 []int{1, 2, 3},
327         },
328         {
329                 []int{1, 2, 3},
330                 0,
331                 2,
332                 []int{3},
333         },
334         {
335                 []int{1, 2, 3},
336                 0,
337                 3,
338                 []int{},
339         },
340 }
341
342 func TestDelete(t *testing.T) {
343         for _, test := range deleteTests {
344                 copy := Clone(test.s)
345                 if got := Delete(copy, test.i, test.j); !Equal(got, test.want) {
346                         t.Errorf("Delete(%v, %d, %d) = %v, want %v", test.s, test.i, test.j, got, test.want)
347                 }
348         }
349 }
350
351 var deleteFuncTests = []struct {
352         s    []int
353         fn   func(int) bool
354         want []int
355 }{
356         {
357                 nil,
358                 func(int) bool { return true },
359                 nil,
360         },
361         {
362                 []int{1, 2, 3},
363                 func(int) bool { return true },
364                 nil,
365         },
366         {
367                 []int{1, 2, 3},
368                 func(int) bool { return false },
369                 []int{1, 2, 3},
370         },
371         {
372                 []int{1, 2, 3},
373                 func(i int) bool { return i > 2 },
374                 []int{1, 2},
375         },
376         {
377                 []int{1, 2, 3},
378                 func(i int) bool { return i < 2 },
379                 []int{2, 3},
380         },
381         {
382                 []int{10, 2, 30},
383                 func(i int) bool { return i >= 10 },
384                 []int{2},
385         },
386 }
387
388 func TestDeleteFunc(t *testing.T) {
389         for i, test := range deleteFuncTests {
390                 copy := Clone(test.s)
391                 if got := DeleteFunc(copy, test.fn); !Equal(got, test.want) {
392                         t.Errorf("DeleteFunc case %d: got %v, want %v", i, got, test.want)
393                 }
394         }
395 }
396
397 func panics(f func()) (b bool) {
398         defer func() {
399                 if x := recover(); x != nil {
400                         b = true
401                 }
402         }()
403         f()
404         return false
405 }
406
407 func TestDeletePanics(t *testing.T) {
408         for _, test := range []struct {
409                 name string
410                 s    []int
411                 i, j int
412         }{
413                 {"with negative first index", []int{42}, -2, 1},
414                 {"with negative second index", []int{42}, 1, -1},
415                 {"with out-of-bounds first index", []int{42}, 2, 3},
416                 {"with out-of-bounds second index", []int{42}, 0, 2},
417                 {"with invalid i>j", []int{42}, 1, 0},
418         } {
419                 if !panics(func() { Delete(test.s, test.i, test.j) }) {
420                         t.Errorf("Delete %s: got no panic, want panic", test.name)
421                 }
422         }
423 }
424
425 func TestClone(t *testing.T) {
426         s1 := []int{1, 2, 3}
427         s2 := Clone(s1)
428         if !Equal(s1, s2) {
429                 t.Errorf("Clone(%v) = %v, want %v", s1, s2, s1)
430         }
431         s1[0] = 4
432         want := []int{1, 2, 3}
433         if !Equal(s2, want) {
434                 t.Errorf("Clone(%v) changed unexpectedly to %v", want, s2)
435         }
436         if got := Clone([]int(nil)); got != nil {
437                 t.Errorf("Clone(nil) = %#v, want nil", got)
438         }
439         if got := Clone(s1[:0]); got == nil || len(got) != 0 {
440                 t.Errorf("Clone(%v) = %#v, want %#v", s1[:0], got, s1[:0])
441         }
442 }
443
444 var compactTests = []struct {
445         name string
446         s    []int
447         want []int
448 }{
449         {
450                 "nil",
451                 nil,
452                 nil,
453         },
454         {
455                 "one",
456                 []int{1},
457                 []int{1},
458         },
459         {
460                 "sorted",
461                 []int{1, 2, 3},
462                 []int{1, 2, 3},
463         },
464         {
465                 "1 item",
466                 []int{1, 1, 2},
467                 []int{1, 2},
468         },
469         {
470                 "unsorted",
471                 []int{1, 2, 1},
472                 []int{1, 2, 1},
473         },
474         {
475                 "many",
476                 []int{1, 2, 2, 3, 3, 4},
477                 []int{1, 2, 3, 4},
478         },
479 }
480
481 func TestCompact(t *testing.T) {
482         for _, test := range compactTests {
483                 copy := Clone(test.s)
484                 if got := Compact(copy); !Equal(got, test.want) {
485                         t.Errorf("Compact(%v) = %v, want %v", test.s, got, test.want)
486                 }
487         }
488 }
489
490 func BenchmarkCompact(b *testing.B) {
491         for _, c := range compactTests {
492                 b.Run(c.name, func(b *testing.B) {
493                         ss := make([]int, 0, 64)
494                         for k := 0; k < b.N; k++ {
495                                 ss = ss[:0]
496                                 ss = append(ss, c.s...)
497                                 _ = Compact(ss)
498                         }
499                 })
500         }
501 }
502
503 func BenchmarkCompact_Large(b *testing.B) {
504         type Large [4 * 1024]byte
505
506         ss := make([]Large, 1024)
507         for i := 0; i < b.N; i++ {
508                 _ = Compact(ss)
509         }
510 }
511
512 func TestCompactFunc(t *testing.T) {
513         for _, test := range compactTests {
514                 copy := Clone(test.s)
515                 if got := CompactFunc(copy, equal[int]); !Equal(got, test.want) {
516                         t.Errorf("CompactFunc(%v, equal[int]) = %v, want %v", test.s, got, test.want)
517                 }
518         }
519
520         s1 := []string{"a", "a", "A", "B", "b"}
521         copy := Clone(s1)
522         want := []string{"a", "B"}
523         if got := CompactFunc(copy, strings.EqualFold); !Equal(got, want) {
524                 t.Errorf("CompactFunc(%v, strings.EqualFold) = %v, want %v", s1, got, want)
525         }
526 }
527
528 func BenchmarkCompactFunc_Large(b *testing.B) {
529         type Large [4 * 1024]byte
530
531         ss := make([]Large, 1024)
532         for i := 0; i < b.N; i++ {
533                 _ = CompactFunc(ss, func(a, b Large) bool { return a == b })
534         }
535 }
536
537 func TestGrow(t *testing.T) {
538         s1 := []int{1, 2, 3}
539
540         copy := Clone(s1)
541         s2 := Grow(copy, 1000)
542         if !Equal(s1, s2) {
543                 t.Errorf("Grow(%v) = %v, want %v", s1, s2, s1)
544         }
545         if cap(s2) < 1000+len(s1) {
546                 t.Errorf("after Grow(%v) cap = %d, want >= %d", s1, cap(s2), 1000+len(s1))
547         }
548
549         // Test mutation of elements between length and capacity.
550         copy = Clone(s1)
551         s3 := Grow(copy[:1], 2)[:3]
552         if !Equal(s1, s3) {
553                 t.Errorf("Grow should not mutate elements between length and capacity")
554         }
555         s3 = Grow(copy[:1], 1000)[:3]
556         if !Equal(s1, s3) {
557                 t.Errorf("Grow should not mutate elements between length and capacity")
558         }
559
560         // Test number of allocations.
561         if n := testing.AllocsPerRun(100, func() { Grow(s2, cap(s2)-len(s2)) }); n != 0 {
562                 t.Errorf("Grow should not allocate when given sufficient capacity; allocated %v times", n)
563         }
564         if n := testing.AllocsPerRun(100, func() { Grow(s2, cap(s2)-len(s2)+1) }); n != 1 {
565                 errorf := t.Errorf
566                 if race.Enabled || testenv.OptimizationOff() {
567                         errorf = t.Logf // this allocates multiple times in race detector mode
568                 }
569                 errorf("Grow should allocate once when given insufficient capacity; allocated %v times", n)
570         }
571
572         // Test for negative growth sizes.
573         var gotPanic bool
574         func() {
575                 defer func() { gotPanic = recover() != nil }()
576                 Grow(s1, -1)
577         }()
578         if !gotPanic {
579                 t.Errorf("Grow(-1) did not panic; expected a panic")
580         }
581 }
582
583 func TestClip(t *testing.T) {
584         s1 := []int{1, 2, 3, 4, 5, 6}[:3]
585         orig := Clone(s1)
586         if len(s1) != 3 {
587                 t.Errorf("len(%v) = %d, want 3", s1, len(s1))
588         }
589         if cap(s1) < 6 {
590                 t.Errorf("cap(%v[:3]) = %d, want >= 6", orig, cap(s1))
591         }
592         s2 := Clip(s1)
593         if !Equal(s1, s2) {
594                 t.Errorf("Clip(%v) = %v, want %v", s1, s2, s1)
595         }
596         if cap(s2) != 3 {
597                 t.Errorf("cap(Clip(%v)) = %d, want 3", orig, cap(s2))
598         }
599 }
600
601 // naiveReplace is a baseline implementation to the Replace function.
602 func naiveReplace[S ~[]E, E any](s S, i, j int, v ...E) S {
603         s = Delete(s, i, j)
604         s = Insert(s, i, v...)
605         return s
606 }
607
608 func TestReplace(t *testing.T) {
609         for _, test := range []struct {
610                 s, v []int
611                 i, j int
612         }{
613                 {}, // all zero value
614                 {
615                         s: []int{1, 2, 3, 4},
616                         v: []int{5},
617                         i: 1,
618                         j: 2,
619                 },
620                 {
621                         s: []int{1, 2, 3, 4},
622                         v: []int{5, 6, 7, 8},
623                         i: 1,
624                         j: 2,
625                 },
626                 {
627                         s: func() []int {
628                                 s := make([]int, 3, 20)
629                                 s[0] = 0
630                                 s[1] = 1
631                                 s[2] = 2
632                                 return s
633                         }(),
634                         v: []int{3, 4, 5, 6, 7},
635                         i: 0,
636                         j: 1,
637                 },
638         } {
639                 ss, vv := Clone(test.s), Clone(test.v)
640                 want := naiveReplace(ss, test.i, test.j, vv...)
641                 got := Replace(test.s, test.i, test.j, test.v...)
642                 if !Equal(got, want) {
643                         t.Errorf("Replace(%v, %v, %v, %v) = %v, want %v", test.s, test.i, test.j, test.v, got, want)
644                 }
645         }
646 }
647
648 func TestReplacePanics(t *testing.T) {
649         for _, test := range []struct {
650                 name string
651                 s, v []int
652                 i, j int
653         }{
654                 {"indexes out of order", []int{1, 2}, []int{3}, 2, 1},
655                 {"large index", []int{1, 2}, []int{3}, 1, 10},
656                 {"negative index", []int{1, 2}, []int{3}, -1, 2},
657         } {
658                 ss, vv := Clone(test.s), Clone(test.v)
659                 if !panics(func() { Replace(ss, test.i, test.j, vv...) }) {
660                         t.Errorf("Replace %s: should have panicked", test.name)
661                 }
662         }
663 }
664
665 func BenchmarkReplace(b *testing.B) {
666         cases := []struct {
667                 name string
668                 s, v func() []int
669                 i, j int
670         }{
671                 {
672                         name: "fast",
673                         s: func() []int {
674                                 return make([]int, 100)
675                         },
676                         v: func() []int {
677                                 return make([]int, 20)
678                         },
679                         i: 10,
680                         j: 40,
681                 },
682                 {
683                         name: "slow",
684                         s: func() []int {
685                                 return make([]int, 100)
686                         },
687                         v: func() []int {
688                                 return make([]int, 20)
689                         },
690                         i: 0,
691                         j: 2,
692                 },
693         }
694
695         for _, c := range cases {
696                 b.Run("naive-"+c.name, func(b *testing.B) {
697                         for k := 0; k < b.N; k++ {
698                                 s := c.s()
699                                 v := c.v()
700                                 _ = naiveReplace(s, c.i, c.j, v...)
701                         }
702                 })
703                 b.Run("optimized-"+c.name, func(b *testing.B) {
704                         for k := 0; k < b.N; k++ {
705                                 s := c.s()
706                                 v := c.v()
707                                 _ = Replace(s, c.i, c.j, v...)
708                         }
709                 })
710         }
711
712 }