blob: 9b9253b49e0d672eeb1c54f7d8769505450f1d23 [file] [log] [blame]
Colin Crossb6715442017-10-24 11:13:31 -07001// Copyright 2017 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package android
16
17import (
Colin Cross454c0872019-02-15 23:03:34 -080018 "fmt"
Colin Crossb6715442017-10-24 11:13:31 -070019 "reflect"
Colin Cross27027c72020-02-28 15:34:17 -080020 "strconv"
Martin Stjernholm1461c4d2021-03-27 19:04:05 +000021 "strings"
Colin Crossb6715442017-10-24 11:13:31 -070022 "testing"
23)
24
25var firstUniqueStringsTestCases = []struct {
26 in []string
27 out []string
28}{
29 {
30 in: []string{"a"},
31 out: []string{"a"},
32 },
33 {
34 in: []string{"a", "b"},
35 out: []string{"a", "b"},
36 },
37 {
38 in: []string{"a", "a"},
39 out: []string{"a"},
40 },
41 {
42 in: []string{"a", "b", "a"},
43 out: []string{"a", "b"},
44 },
45 {
46 in: []string{"b", "a", "a"},
47 out: []string{"b", "a"},
48 },
49 {
50 in: []string{"a", "a", "b"},
51 out: []string{"a", "b"},
52 },
53 {
54 in: []string{"a", "b", "a", "b"},
55 out: []string{"a", "b"},
56 },
57 {
58 in: []string{"liblog", "libdl", "libc++", "libdl", "libc", "libm"},
59 out: []string{"liblog", "libdl", "libc++", "libc", "libm"},
60 },
61}
62
63func TestFirstUniqueStrings(t *testing.T) {
Colin Cross27027c72020-02-28 15:34:17 -080064 f := func(t *testing.T, imp func([]string) []string, in, want []string) {
65 t.Helper()
66 out := imp(in)
67 if !reflect.DeepEqual(out, want) {
Colin Crossb6715442017-10-24 11:13:31 -070068 t.Errorf("incorrect output:")
Colin Cross27027c72020-02-28 15:34:17 -080069 t.Errorf(" input: %#v", in)
70 t.Errorf(" expected: %#v", want)
Colin Crossb6715442017-10-24 11:13:31 -070071 t.Errorf(" got: %#v", out)
72 }
73 }
Colin Cross27027c72020-02-28 15:34:17 -080074
75 for _, testCase := range firstUniqueStringsTestCases {
76 t.Run("list", func(t *testing.T) {
77 f(t, firstUniqueStringsList, testCase.in, testCase.out)
78 })
79 t.Run("map", func(t *testing.T) {
80 f(t, firstUniqueStringsMap, testCase.in, testCase.out)
81 })
82 }
Colin Crossb6715442017-10-24 11:13:31 -070083}
84
85var lastUniqueStringsTestCases = []struct {
86 in []string
87 out []string
88}{
89 {
90 in: []string{"a"},
91 out: []string{"a"},
92 },
93 {
94 in: []string{"a", "b"},
95 out: []string{"a", "b"},
96 },
97 {
98 in: []string{"a", "a"},
99 out: []string{"a"},
100 },
101 {
102 in: []string{"a", "b", "a"},
103 out: []string{"b", "a"},
104 },
105 {
106 in: []string{"b", "a", "a"},
107 out: []string{"b", "a"},
108 },
109 {
110 in: []string{"a", "a", "b"},
111 out: []string{"a", "b"},
112 },
113 {
114 in: []string{"a", "b", "a", "b"},
115 out: []string{"a", "b"},
116 },
117 {
118 in: []string{"liblog", "libdl", "libc++", "libdl", "libc", "libm"},
119 out: []string{"liblog", "libc++", "libdl", "libc", "libm"},
120 },
121}
122
123func TestLastUniqueStrings(t *testing.T) {
124 for _, testCase := range lastUniqueStringsTestCases {
125 out := LastUniqueStrings(testCase.in)
126 if !reflect.DeepEqual(out, testCase.out) {
127 t.Errorf("incorrect output:")
128 t.Errorf(" input: %#v", testCase.in)
129 t.Errorf(" expected: %#v", testCase.out)
130 t.Errorf(" got: %#v", out)
131 }
132 }
133}
Logan Chien5e877b12018-03-08 18:14:19 +0800134
135func TestJoinWithPrefix(t *testing.T) {
136 testcases := []struct {
137 name string
138 input []string
139 expected string
140 }{
141 {
142 name: "zero_inputs",
143 input: []string{},
144 expected: "",
145 },
146 {
147 name: "one_input",
148 input: []string{"a"},
149 expected: "prefix:a",
150 },
151 {
152 name: "two_inputs",
153 input: []string{"a", "b"},
154 expected: "prefix:a prefix:b",
155 },
156 }
157
158 prefix := "prefix:"
159
160 for _, testCase := range testcases {
161 t.Run(testCase.name, func(t *testing.T) {
162 out := JoinWithPrefix(testCase.input, prefix)
163 if out != testCase.expected {
164 t.Errorf("incorrect output:")
165 t.Errorf(" input: %#v", testCase.input)
166 t.Errorf(" prefix: %#v", prefix)
167 t.Errorf(" expected: %#v", testCase.expected)
168 t.Errorf(" got: %#v", out)
169 }
170 })
171 }
172}
173
174func TestIndexList(t *testing.T) {
175 input := []string{"a", "b", "c"}
176
177 testcases := []struct {
178 key string
179 expected int
180 }{
181 {
182 key: "a",
183 expected: 0,
184 },
185 {
186 key: "b",
187 expected: 1,
188 },
189 {
190 key: "c",
191 expected: 2,
192 },
193 {
194 key: "X",
195 expected: -1,
196 },
197 }
198
199 for _, testCase := range testcases {
200 t.Run(testCase.key, func(t *testing.T) {
201 out := IndexList(testCase.key, input)
202 if out != testCase.expected {
203 t.Errorf("incorrect output:")
204 t.Errorf(" key: %#v", testCase.key)
205 t.Errorf(" input: %#v", input)
206 t.Errorf(" expected: %#v", testCase.expected)
207 t.Errorf(" got: %#v", out)
208 }
209 })
210 }
211}
212
213func TestInList(t *testing.T) {
214 input := []string{"a"}
215
216 testcases := []struct {
217 key string
218 expected bool
219 }{
220 {
221 key: "a",
222 expected: true,
223 },
224 {
225 key: "X",
226 expected: false,
227 },
228 }
229
230 for _, testCase := range testcases {
231 t.Run(testCase.key, func(t *testing.T) {
232 out := InList(testCase.key, input)
233 if out != testCase.expected {
234 t.Errorf("incorrect output:")
235 t.Errorf(" key: %#v", testCase.key)
236 t.Errorf(" input: %#v", input)
237 t.Errorf(" expected: %#v", testCase.expected)
238 t.Errorf(" got: %#v", out)
239 }
240 })
241 }
242}
243
244func TestPrefixInList(t *testing.T) {
245 prefixes := []string{"a", "b"}
246
247 testcases := []struct {
248 str string
249 expected bool
250 }{
251 {
252 str: "a-example",
253 expected: true,
254 },
255 {
256 str: "b-example",
257 expected: true,
258 },
259 {
260 str: "X-example",
261 expected: false,
262 },
263 }
264
265 for _, testCase := range testcases {
266 t.Run(testCase.str, func(t *testing.T) {
Jaewoong Jung3aff5782020-02-11 07:54:35 -0800267 out := HasAnyPrefix(testCase.str, prefixes)
Logan Chien5e877b12018-03-08 18:14:19 +0800268 if out != testCase.expected {
269 t.Errorf("incorrect output:")
270 t.Errorf(" str: %#v", testCase.str)
271 t.Errorf(" prefixes: %#v", prefixes)
272 t.Errorf(" expected: %#v", testCase.expected)
273 t.Errorf(" got: %#v", out)
274 }
275 })
276 }
277}
278
279func TestFilterList(t *testing.T) {
280 input := []string{"a", "b", "c", "c", "b", "d", "a"}
281 filter := []string{"a", "c"}
282 remainder, filtered := FilterList(input, filter)
283
284 expected := []string{"b", "b", "d"}
285 if !reflect.DeepEqual(remainder, expected) {
286 t.Errorf("incorrect remainder output:")
287 t.Errorf(" input: %#v", input)
288 t.Errorf(" filter: %#v", filter)
289 t.Errorf(" expected: %#v", expected)
290 t.Errorf(" got: %#v", remainder)
291 }
292
293 expected = []string{"a", "c", "c", "a"}
294 if !reflect.DeepEqual(filtered, expected) {
295 t.Errorf("incorrect filtered output:")
296 t.Errorf(" input: %#v", input)
297 t.Errorf(" filter: %#v", filter)
298 t.Errorf(" expected: %#v", expected)
299 t.Errorf(" got: %#v", filtered)
300 }
301}
302
Martin Stjernholm1461c4d2021-03-27 19:04:05 +0000303func TestFilterListPred(t *testing.T) {
304 pred := func(s string) bool { return strings.HasPrefix(s, "a/") }
305 AssertArrayString(t, "filter", FilterListPred([]string{"a/c", "b/a", "a/b"}, pred), []string{"a/c", "a/b"})
306 AssertArrayString(t, "filter", FilterListPred([]string{"b/c", "a/a", "b/b"}, pred), []string{"a/a"})
307 AssertArrayString(t, "filter", FilterListPred([]string{"c/c", "b/a", "c/b"}, pred), []string{})
308 AssertArrayString(t, "filter", FilterListPred([]string{"a/c", "a/a", "a/b"}, pred), []string{"a/c", "a/a", "a/b"})
309}
310
Logan Chien5e877b12018-03-08 18:14:19 +0800311func TestRemoveListFromList(t *testing.T) {
312 input := []string{"a", "b", "c", "d", "a", "c", "d"}
313 filter := []string{"a", "c"}
314 expected := []string{"b", "d", "d"}
315 out := RemoveListFromList(input, filter)
316 if !reflect.DeepEqual(out, expected) {
317 t.Errorf("incorrect output:")
318 t.Errorf(" input: %#v", input)
319 t.Errorf(" filter: %#v", filter)
320 t.Errorf(" expected: %#v", expected)
321 t.Errorf(" got: %#v", out)
322 }
323}
Logan Chien7922ab82018-03-06 18:29:27 +0800324
325func TestRemoveFromList(t *testing.T) {
326 testcases := []struct {
327 name string
328 key string
329 input []string
330 expectedFound bool
331 expectedOut []string
332 }{
333 {
334 name: "remove_one_match",
335 key: "a",
336 input: []string{"a", "b", "c"},
337 expectedFound: true,
338 expectedOut: []string{"b", "c"},
339 },
340 {
341 name: "remove_three_matches",
342 key: "a",
343 input: []string{"a", "b", "a", "c", "a"},
344 expectedFound: true,
345 expectedOut: []string{"b", "c"},
346 },
347 {
348 name: "remove_zero_matches",
349 key: "X",
350 input: []string{"a", "b", "a", "c", "a"},
351 expectedFound: false,
352 expectedOut: []string{"a", "b", "a", "c", "a"},
353 },
354 {
355 name: "remove_all_matches",
356 key: "a",
357 input: []string{"a", "a", "a", "a"},
358 expectedFound: true,
359 expectedOut: []string{},
360 },
361 }
362
363 for _, testCase := range testcases {
364 t.Run(testCase.name, func(t *testing.T) {
365 found, out := RemoveFromList(testCase.key, testCase.input)
366 if found != testCase.expectedFound {
367 t.Errorf("incorrect output:")
368 t.Errorf(" key: %#v", testCase.key)
369 t.Errorf(" input: %#v", testCase.input)
370 t.Errorf(" expected: %#v", testCase.expectedFound)
371 t.Errorf(" got: %#v", found)
372 }
373 if !reflect.DeepEqual(out, testCase.expectedOut) {
374 t.Errorf("incorrect output:")
375 t.Errorf(" key: %#v", testCase.key)
376 t.Errorf(" input: %#v", testCase.input)
377 t.Errorf(" expected: %#v", testCase.expectedOut)
378 t.Errorf(" got: %#v", out)
379 }
380 })
381 }
382}
Colin Cross454c0872019-02-15 23:03:34 -0800383
384func ExampleCopyOf() {
385 a := []string{"1", "2", "3"}
386 b := CopyOf(a)
387 a[0] = "-1"
388 fmt.Printf("a = %q\n", a)
389 fmt.Printf("b = %q\n", b)
390
391 // Output:
392 // a = ["-1" "2" "3"]
393 // b = ["1" "2" "3"]
394}
395
396func ExampleCopyOf_append() {
397 a := make([]string, 1, 2)
398 a[0] = "foo"
399
400 fmt.Println("Without CopyOf:")
401 b := append(a, "bar")
402 c := append(a, "baz")
403 fmt.Printf("a = %q\n", a)
404 fmt.Printf("b = %q\n", b)
405 fmt.Printf("c = %q\n", c)
406
407 a = make([]string, 1, 2)
408 a[0] = "foo"
409
410 fmt.Println("With CopyOf:")
411 b = append(CopyOf(a), "bar")
412 c = append(CopyOf(a), "baz")
413 fmt.Printf("a = %q\n", a)
414 fmt.Printf("b = %q\n", b)
415 fmt.Printf("c = %q\n", c)
416
417 // Output:
418 // Without CopyOf:
419 // a = ["foo"]
420 // b = ["foo" "baz"]
421 // c = ["foo" "baz"]
422 // With CopyOf:
423 // a = ["foo"]
424 // b = ["foo" "bar"]
425 // c = ["foo" "baz"]
426}
Ivan Lozano022a73b2019-09-09 20:29:31 -0700427
428func TestSplitFileExt(t *testing.T) {
429 t.Run("soname with version", func(t *testing.T) {
430 root, suffix, ext := SplitFileExt("libtest.so.1.0.30")
431 expected := "libtest"
432 if root != expected {
433 t.Errorf("root should be %q but got %q", expected, root)
434 }
435 expected = ".so.1.0.30"
436 if suffix != expected {
437 t.Errorf("suffix should be %q but got %q", expected, suffix)
438 }
439 expected = ".so"
440 if ext != expected {
441 t.Errorf("ext should be %q but got %q", expected, ext)
442 }
443 })
444
445 t.Run("soname with svn version", func(t *testing.T) {
446 root, suffix, ext := SplitFileExt("libtest.so.1svn")
447 expected := "libtest"
448 if root != expected {
449 t.Errorf("root should be %q but got %q", expected, root)
450 }
451 expected = ".so.1svn"
452 if suffix != expected {
453 t.Errorf("suffix should be %q but got %q", expected, suffix)
454 }
455 expected = ".so"
456 if ext != expected {
457 t.Errorf("ext should be %q but got %q", expected, ext)
458 }
459 })
460
461 t.Run("version numbers in the middle should be ignored", func(t *testing.T) {
462 root, suffix, ext := SplitFileExt("libtest.1.0.30.so")
463 expected := "libtest.1.0.30"
464 if root != expected {
465 t.Errorf("root should be %q but got %q", expected, root)
466 }
467 expected = ".so"
468 if suffix != expected {
469 t.Errorf("suffix should be %q but got %q", expected, suffix)
470 }
471 expected = ".so"
472 if ext != expected {
473 t.Errorf("ext should be %q but got %q", expected, ext)
474 }
475 })
476
477 t.Run("no known file extension", func(t *testing.T) {
478 root, suffix, ext := SplitFileExt("test.exe")
479 expected := "test"
480 if root != expected {
481 t.Errorf("root should be %q but got %q", expected, root)
482 }
483 expected = ".exe"
484 if suffix != expected {
485 t.Errorf("suffix should be %q but got %q", expected, suffix)
486 }
487 if ext != expected {
488 t.Errorf("ext should be %q but got %q", expected, ext)
489 }
490 })
491}
Colin Cross0a2f7192019-09-23 14:33:09 -0700492
493func Test_Shard(t *testing.T) {
494 type args struct {
495 strings []string
496 shardSize int
497 }
498 tests := []struct {
499 name string
500 args args
501 want [][]string
502 }{
503 {
504 name: "empty",
505 args: args{
506 strings: nil,
507 shardSize: 1,
508 },
509 want: [][]string(nil),
510 },
511 {
512 name: "single shard",
513 args: args{
514 strings: []string{"a", "b"},
515 shardSize: 2,
516 },
517 want: [][]string{{"a", "b"}},
518 },
519 {
520 name: "single short shard",
521 args: args{
522 strings: []string{"a", "b"},
523 shardSize: 3,
524 },
525 want: [][]string{{"a", "b"}},
526 },
527 {
528 name: "shard per input",
529 args: args{
530 strings: []string{"a", "b", "c"},
531 shardSize: 1,
532 },
533 want: [][]string{{"a"}, {"b"}, {"c"}},
534 },
535 {
536 name: "balanced shards",
537 args: args{
538 strings: []string{"a", "b", "c", "d"},
539 shardSize: 2,
540 },
541 want: [][]string{{"a", "b"}, {"c", "d"}},
542 },
543 {
544 name: "unbalanced shards",
545 args: args{
546 strings: []string{"a", "b", "c"},
547 shardSize: 2,
548 },
549 want: [][]string{{"a", "b"}, {"c"}},
550 },
551 }
552 for _, tt := range tests {
553 t.Run(tt.name, func(t *testing.T) {
554 t.Run("strings", func(t *testing.T) {
555 if got := ShardStrings(tt.args.strings, tt.args.shardSize); !reflect.DeepEqual(got, tt.want) {
556 t.Errorf("ShardStrings(%v, %v) = %v, want %v",
557 tt.args.strings, tt.args.shardSize, got, tt.want)
558 }
559 })
560
561 t.Run("paths", func(t *testing.T) {
562 stringsToPaths := func(strings []string) Paths {
563 if strings == nil {
564 return nil
565 }
566 paths := make(Paths, len(strings))
567 for i, s := range strings {
568 paths[i] = PathForTesting(s)
569 }
570 return paths
571 }
572
573 paths := stringsToPaths(tt.args.strings)
574
575 var want []Paths
576 if sWant := tt.want; sWant != nil {
577 want = make([]Paths, len(sWant))
578 for i, w := range sWant {
579 want[i] = stringsToPaths(w)
580 }
581 }
582
583 if got := ShardPaths(paths, tt.args.shardSize); !reflect.DeepEqual(got, want) {
584 t.Errorf("ShardPaths(%v, %v) = %v, want %v",
585 paths, tt.args.shardSize, got, want)
586 }
587 })
588 })
589 }
590}
Colin Cross27027c72020-02-28 15:34:17 -0800591
592func BenchmarkFirstUniqueStrings(b *testing.B) {
593 implementations := []struct {
594 name string
595 f func([]string) []string
596 }{
597 {
598 name: "list",
599 f: firstUniqueStringsList,
600 },
601 {
602 name: "map",
603 f: firstUniqueStringsMap,
604 },
Colin Cross96c44122020-11-25 14:29:50 -0800605 {
606 name: "optimal",
607 f: FirstUniqueStrings,
608 },
Colin Cross27027c72020-02-28 15:34:17 -0800609 }
610 const maxSize = 1024
611 uniqueStrings := make([]string, maxSize)
612 for i := range uniqueStrings {
613 uniqueStrings[i] = strconv.Itoa(i)
614 }
615 sameString := make([]string, maxSize)
616 for i := range sameString {
617 sameString[i] = uniqueStrings[0]
618 }
619
620 f := func(b *testing.B, imp func([]string) []string, s []string) {
621 for i := 0; i < b.N; i++ {
622 b.ReportAllocs()
623 s = append([]string(nil), s...)
624 imp(s)
625 }
626 }
627
628 for n := 1; n <= maxSize; n <<= 1 {
629 b.Run(strconv.Itoa(n), func(b *testing.B) {
630 for _, implementation := range implementations {
631 b.Run(implementation.name, func(b *testing.B) {
632 b.Run("same", func(b *testing.B) {
633 f(b, implementation.f, sameString[:n])
634 })
635 b.Run("unique", func(b *testing.B) {
636 f(b, implementation.f, uniqueStrings[:n])
637 })
638 })
639 }
640 })
641 }
642}
Colin Cross9eb853b2022-02-17 11:13:37 -0800643
644func TestSortedStringKeys(t *testing.T) {
645 testCases := []struct {
646 name string
647 in interface{}
648 expected []string
649 }{
650 {
651 name: "nil",
652 in: map[string]string(nil),
653 expected: nil,
654 },
655 {
656 name: "empty",
657 in: map[string]string{},
658 expected: nil,
659 },
660 {
661 name: "simple",
662 in: map[string]string{"a": "foo", "b": "bar"},
663 expected: []string{"a", "b"},
664 },
665 {
666 name: "interface values",
667 in: map[string]interface{}{"a": nil, "b": nil},
668 expected: []string{"a", "b"},
669 },
670 }
671
672 for _, tt := range testCases {
673 t.Run(tt.name, func(t *testing.T) {
674 got := SortedStringKeys(tt.in)
675 if g, w := got, tt.expected; !reflect.DeepEqual(g, w) {
676 t.Errorf("wanted %q, got %q", w, g)
677 }
678 })
679 }
680}
681
682func TestSortedStringValues(t *testing.T) {
683 testCases := []struct {
684 name string
685 in interface{}
686 expected []string
687 }{
688 {
689 name: "nil",
690 in: map[string]string(nil),
691 expected: nil,
692 },
693 {
694 name: "empty",
695 in: map[string]string{},
696 expected: nil,
697 },
698 {
699 name: "simple",
700 in: map[string]string{"foo": "a", "bar": "b"},
701 expected: []string{"a", "b"},
702 },
703 {
704 name: "duplicates",
705 in: map[string]string{"foo": "a", "bar": "b", "baz": "b"},
706 expected: []string{"a", "b", "b"},
707 },
708 }
709
710 for _, tt := range testCases {
711 t.Run(tt.name, func(t *testing.T) {
712 got := SortedStringValues(tt.in)
713 if g, w := got, tt.expected; !reflect.DeepEqual(g, w) {
714 t.Errorf("wanted %q, got %q", w, g)
715 }
716 })
717 }
718}
719
720func TestSortedUniqueStringValues(t *testing.T) {
721 testCases := []struct {
722 name string
723 in interface{}
724 expected []string
725 }{
726 {
727 name: "nil",
728 in: map[string]string(nil),
729 expected: nil,
730 },
731 {
732 name: "empty",
733 in: map[string]string{},
734 expected: nil,
735 },
736 {
737 name: "simple",
738 in: map[string]string{"foo": "a", "bar": "b"},
739 expected: []string{"a", "b"},
740 },
741 {
742 name: "duplicates",
743 in: map[string]string{"foo": "a", "bar": "b", "baz": "b"},
744 expected: []string{"a", "b"},
745 },
746 }
747
748 for _, tt := range testCases {
749 t.Run(tt.name, func(t *testing.T) {
750 got := SortedUniqueStringValues(tt.in)
751 if g, w := got, tt.expected; !reflect.DeepEqual(g, w) {
752 t.Errorf("wanted %q, got %q", w, g)
753 }
754 })
755 }
756}