blob: a2ef589588b6385927651a4727a69697a92a8551 [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
Spandan Dascc4da762023-04-27 19:34:08 +0000384func TestCopyOfEmptyAndNil(t *testing.T) {
385 emptyList := []string{}
386 copyOfEmptyList := CopyOf(emptyList)
387 AssertBoolEquals(t, "Copy of an empty list should be an empty list and not nil", true, copyOfEmptyList != nil)
388 copyOfNilList := CopyOf(nil)
389 AssertBoolEquals(t, "Copy of a nil list should be a nil list and not an empty list", true, copyOfNilList == nil)
390}
391
Colin Cross454c0872019-02-15 23:03:34 -0800392func ExampleCopyOf() {
393 a := []string{"1", "2", "3"}
394 b := CopyOf(a)
395 a[0] = "-1"
396 fmt.Printf("a = %q\n", a)
397 fmt.Printf("b = %q\n", b)
398
399 // Output:
400 // a = ["-1" "2" "3"]
401 // b = ["1" "2" "3"]
402}
403
404func ExampleCopyOf_append() {
405 a := make([]string, 1, 2)
406 a[0] = "foo"
407
408 fmt.Println("Without CopyOf:")
409 b := append(a, "bar")
410 c := append(a, "baz")
411 fmt.Printf("a = %q\n", a)
412 fmt.Printf("b = %q\n", b)
413 fmt.Printf("c = %q\n", c)
414
415 a = make([]string, 1, 2)
416 a[0] = "foo"
417
418 fmt.Println("With CopyOf:")
419 b = append(CopyOf(a), "bar")
420 c = append(CopyOf(a), "baz")
421 fmt.Printf("a = %q\n", a)
422 fmt.Printf("b = %q\n", b)
423 fmt.Printf("c = %q\n", c)
424
425 // Output:
426 // Without CopyOf:
427 // a = ["foo"]
428 // b = ["foo" "baz"]
429 // c = ["foo" "baz"]
430 // With CopyOf:
431 // a = ["foo"]
432 // b = ["foo" "bar"]
433 // c = ["foo" "baz"]
434}
Ivan Lozano022a73b2019-09-09 20:29:31 -0700435
436func TestSplitFileExt(t *testing.T) {
437 t.Run("soname with version", func(t *testing.T) {
438 root, suffix, ext := SplitFileExt("libtest.so.1.0.30")
439 expected := "libtest"
440 if root != expected {
441 t.Errorf("root should be %q but got %q", expected, root)
442 }
443 expected = ".so.1.0.30"
444 if suffix != expected {
445 t.Errorf("suffix should be %q but got %q", expected, suffix)
446 }
447 expected = ".so"
448 if ext != expected {
449 t.Errorf("ext should be %q but got %q", expected, ext)
450 }
451 })
452
453 t.Run("soname with svn version", func(t *testing.T) {
454 root, suffix, ext := SplitFileExt("libtest.so.1svn")
455 expected := "libtest"
456 if root != expected {
457 t.Errorf("root should be %q but got %q", expected, root)
458 }
459 expected = ".so.1svn"
460 if suffix != expected {
461 t.Errorf("suffix should be %q but got %q", expected, suffix)
462 }
463 expected = ".so"
464 if ext != expected {
465 t.Errorf("ext should be %q but got %q", expected, ext)
466 }
467 })
468
469 t.Run("version numbers in the middle should be ignored", func(t *testing.T) {
470 root, suffix, ext := SplitFileExt("libtest.1.0.30.so")
471 expected := "libtest.1.0.30"
472 if root != expected {
473 t.Errorf("root should be %q but got %q", expected, root)
474 }
475 expected = ".so"
476 if suffix != expected {
477 t.Errorf("suffix should be %q but got %q", expected, suffix)
478 }
479 expected = ".so"
480 if ext != expected {
481 t.Errorf("ext should be %q but got %q", expected, ext)
482 }
483 })
484
485 t.Run("no known file extension", func(t *testing.T) {
486 root, suffix, ext := SplitFileExt("test.exe")
487 expected := "test"
488 if root != expected {
489 t.Errorf("root should be %q but got %q", expected, root)
490 }
491 expected = ".exe"
492 if suffix != expected {
493 t.Errorf("suffix should be %q but got %q", expected, suffix)
494 }
495 if ext != expected {
496 t.Errorf("ext should be %q but got %q", expected, ext)
497 }
498 })
499}
Colin Cross0a2f7192019-09-23 14:33:09 -0700500
501func Test_Shard(t *testing.T) {
502 type args struct {
503 strings []string
504 shardSize int
505 }
506 tests := []struct {
507 name string
508 args args
509 want [][]string
510 }{
511 {
512 name: "empty",
513 args: args{
514 strings: nil,
515 shardSize: 1,
516 },
517 want: [][]string(nil),
518 },
519 {
520 name: "single shard",
521 args: args{
522 strings: []string{"a", "b"},
523 shardSize: 2,
524 },
525 want: [][]string{{"a", "b"}},
526 },
527 {
528 name: "single short shard",
529 args: args{
530 strings: []string{"a", "b"},
531 shardSize: 3,
532 },
533 want: [][]string{{"a", "b"}},
534 },
535 {
536 name: "shard per input",
537 args: args{
538 strings: []string{"a", "b", "c"},
539 shardSize: 1,
540 },
541 want: [][]string{{"a"}, {"b"}, {"c"}},
542 },
543 {
544 name: "balanced shards",
545 args: args{
546 strings: []string{"a", "b", "c", "d"},
547 shardSize: 2,
548 },
549 want: [][]string{{"a", "b"}, {"c", "d"}},
550 },
551 {
552 name: "unbalanced shards",
553 args: args{
554 strings: []string{"a", "b", "c"},
555 shardSize: 2,
556 },
557 want: [][]string{{"a", "b"}, {"c"}},
558 },
559 }
560 for _, tt := range tests {
561 t.Run(tt.name, func(t *testing.T) {
562 t.Run("strings", func(t *testing.T) {
563 if got := ShardStrings(tt.args.strings, tt.args.shardSize); !reflect.DeepEqual(got, tt.want) {
564 t.Errorf("ShardStrings(%v, %v) = %v, want %v",
565 tt.args.strings, tt.args.shardSize, got, tt.want)
566 }
567 })
568
569 t.Run("paths", func(t *testing.T) {
570 stringsToPaths := func(strings []string) Paths {
571 if strings == nil {
572 return nil
573 }
574 paths := make(Paths, len(strings))
575 for i, s := range strings {
576 paths[i] = PathForTesting(s)
577 }
578 return paths
579 }
580
581 paths := stringsToPaths(tt.args.strings)
582
583 var want []Paths
584 if sWant := tt.want; sWant != nil {
585 want = make([]Paths, len(sWant))
586 for i, w := range sWant {
587 want[i] = stringsToPaths(w)
588 }
589 }
590
591 if got := ShardPaths(paths, tt.args.shardSize); !reflect.DeepEqual(got, want) {
592 t.Errorf("ShardPaths(%v, %v) = %v, want %v",
593 paths, tt.args.shardSize, got, want)
594 }
595 })
596 })
597 }
598}
Colin Cross27027c72020-02-28 15:34:17 -0800599
600func BenchmarkFirstUniqueStrings(b *testing.B) {
601 implementations := []struct {
602 name string
603 f func([]string) []string
604 }{
605 {
606 name: "list",
607 f: firstUniqueStringsList,
608 },
609 {
610 name: "map",
611 f: firstUniqueStringsMap,
612 },
Colin Cross96c44122020-11-25 14:29:50 -0800613 {
614 name: "optimal",
615 f: FirstUniqueStrings,
616 },
Colin Cross27027c72020-02-28 15:34:17 -0800617 }
618 const maxSize = 1024
619 uniqueStrings := make([]string, maxSize)
620 for i := range uniqueStrings {
621 uniqueStrings[i] = strconv.Itoa(i)
622 }
623 sameString := make([]string, maxSize)
624 for i := range sameString {
625 sameString[i] = uniqueStrings[0]
626 }
627
628 f := func(b *testing.B, imp func([]string) []string, s []string) {
629 for i := 0; i < b.N; i++ {
630 b.ReportAllocs()
631 s = append([]string(nil), s...)
632 imp(s)
633 }
634 }
635
636 for n := 1; n <= maxSize; n <<= 1 {
637 b.Run(strconv.Itoa(n), func(b *testing.B) {
638 for _, implementation := range implementations {
639 b.Run(implementation.name, func(b *testing.B) {
640 b.Run("same", func(b *testing.B) {
641 f(b, implementation.f, sameString[:n])
642 })
643 b.Run("unique", func(b *testing.B) {
644 f(b, implementation.f, uniqueStrings[:n])
645 })
646 })
647 }
648 })
649 }
650}
Colin Cross9eb853b2022-02-17 11:13:37 -0800651
Cole Faust18994c72023-02-28 16:02:16 -0800652func testSortedKeysHelper[K Ordered, V any](t *testing.T, name string, input map[K]V, expected []K) {
653 t.Helper()
654 t.Run(name, func(t *testing.T) {
655 actual := SortedKeys(input)
656 if !reflect.DeepEqual(actual, expected) {
Spandan Dasc52e2c02023-03-02 23:45:10 +0000657 t.Errorf("expected %v, got %v", expected, actual)
Cole Faust18994c72023-02-28 16:02:16 -0800658 }
659 })
660}
661
662func TestSortedKeys(t *testing.T) {
663 testSortedKeysHelper(t, "simple", map[string]string{
664 "b": "bar",
665 "a": "foo",
666 }, []string{
667 "a",
668 "b",
669 })
670 testSortedKeysHelper(t, "ints", map[int]interface{}{
671 10: nil,
672 5: nil,
673 }, []int{
674 5,
675 10,
676 })
677
678 testSortedKeysHelper(t, "nil", map[string]string(nil), nil)
679 testSortedKeysHelper(t, "empty", map[string]string{}, nil)
680}
681
Colin Cross9eb853b2022-02-17 11:13:37 -0800682func 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}