| // Copyright 2017 Google Inc. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package android |
| |
| import ( |
| "fmt" |
| "reflect" |
| "strconv" |
| "strings" |
| "testing" |
| ) |
| |
| var firstUniqueStringsTestCases = []struct { |
| in []string |
| out []string |
| }{ |
| { |
| in: []string{"a"}, |
| out: []string{"a"}, |
| }, |
| { |
| in: []string{"a", "b"}, |
| out: []string{"a", "b"}, |
| }, |
| { |
| in: []string{"a", "a"}, |
| out: []string{"a"}, |
| }, |
| { |
| in: []string{"a", "b", "a"}, |
| out: []string{"a", "b"}, |
| }, |
| { |
| in: []string{"b", "a", "a"}, |
| out: []string{"b", "a"}, |
| }, |
| { |
| in: []string{"a", "a", "b"}, |
| out: []string{"a", "b"}, |
| }, |
| { |
| in: []string{"a", "b", "a", "b"}, |
| out: []string{"a", "b"}, |
| }, |
| { |
| in: []string{"liblog", "libdl", "libc++", "libdl", "libc", "libm"}, |
| out: []string{"liblog", "libdl", "libc++", "libc", "libm"}, |
| }, |
| } |
| |
| func TestFirstUniqueStrings(t *testing.T) { |
| f := func(t *testing.T, imp func([]string) []string, in, want []string) { |
| t.Helper() |
| out := imp(in) |
| if !reflect.DeepEqual(out, want) { |
| t.Errorf("incorrect output:") |
| t.Errorf(" input: %#v", in) |
| t.Errorf(" expected: %#v", want) |
| t.Errorf(" got: %#v", out) |
| } |
| } |
| |
| for _, testCase := range firstUniqueStringsTestCases { |
| t.Run("list", func(t *testing.T) { |
| f(t, firstUniqueStringsList, testCase.in, testCase.out) |
| }) |
| t.Run("map", func(t *testing.T) { |
| f(t, firstUniqueStringsMap, testCase.in, testCase.out) |
| }) |
| } |
| } |
| |
| var lastUniqueStringsTestCases = []struct { |
| in []string |
| out []string |
| }{ |
| { |
| in: []string{"a"}, |
| out: []string{"a"}, |
| }, |
| { |
| in: []string{"a", "b"}, |
| out: []string{"a", "b"}, |
| }, |
| { |
| in: []string{"a", "a"}, |
| out: []string{"a"}, |
| }, |
| { |
| in: []string{"a", "b", "a"}, |
| out: []string{"b", "a"}, |
| }, |
| { |
| in: []string{"b", "a", "a"}, |
| out: []string{"b", "a"}, |
| }, |
| { |
| in: []string{"a", "a", "b"}, |
| out: []string{"a", "b"}, |
| }, |
| { |
| in: []string{"a", "b", "a", "b"}, |
| out: []string{"a", "b"}, |
| }, |
| { |
| in: []string{"liblog", "libdl", "libc++", "libdl", "libc", "libm"}, |
| out: []string{"liblog", "libc++", "libdl", "libc", "libm"}, |
| }, |
| } |
| |
| func TestLastUniqueStrings(t *testing.T) { |
| for _, testCase := range lastUniqueStringsTestCases { |
| out := LastUniqueStrings(testCase.in) |
| if !reflect.DeepEqual(out, testCase.out) { |
| t.Errorf("incorrect output:") |
| t.Errorf(" input: %#v", testCase.in) |
| t.Errorf(" expected: %#v", testCase.out) |
| t.Errorf(" got: %#v", out) |
| } |
| } |
| } |
| |
| func TestJoinWithPrefix(t *testing.T) { |
| testcases := []struct { |
| name string |
| input []string |
| expected string |
| }{ |
| { |
| name: "zero_inputs", |
| input: []string{}, |
| expected: "", |
| }, |
| { |
| name: "one_input", |
| input: []string{"a"}, |
| expected: "prefix:a", |
| }, |
| { |
| name: "two_inputs", |
| input: []string{"a", "b"}, |
| expected: "prefix:a prefix:b", |
| }, |
| } |
| |
| prefix := "prefix:" |
| |
| for _, testCase := range testcases { |
| t.Run(testCase.name, func(t *testing.T) { |
| out := JoinWithPrefix(testCase.input, prefix) |
| if out != testCase.expected { |
| t.Errorf("incorrect output:") |
| t.Errorf(" input: %#v", testCase.input) |
| t.Errorf(" prefix: %#v", prefix) |
| t.Errorf(" expected: %#v", testCase.expected) |
| t.Errorf(" got: %#v", out) |
| } |
| }) |
| } |
| } |
| |
| func TestIndexList(t *testing.T) { |
| input := []string{"a", "b", "c"} |
| |
| testcases := []struct { |
| key string |
| expected int |
| }{ |
| { |
| key: "a", |
| expected: 0, |
| }, |
| { |
| key: "b", |
| expected: 1, |
| }, |
| { |
| key: "c", |
| expected: 2, |
| }, |
| { |
| key: "X", |
| expected: -1, |
| }, |
| } |
| |
| for _, testCase := range testcases { |
| t.Run(testCase.key, func(t *testing.T) { |
| out := IndexList(testCase.key, input) |
| if out != testCase.expected { |
| t.Errorf("incorrect output:") |
| t.Errorf(" key: %#v", testCase.key) |
| t.Errorf(" input: %#v", input) |
| t.Errorf(" expected: %#v", testCase.expected) |
| t.Errorf(" got: %#v", out) |
| } |
| }) |
| } |
| } |
| |
| func TestInList(t *testing.T) { |
| input := []string{"a"} |
| |
| testcases := []struct { |
| key string |
| expected bool |
| }{ |
| { |
| key: "a", |
| expected: true, |
| }, |
| { |
| key: "X", |
| expected: false, |
| }, |
| } |
| |
| for _, testCase := range testcases { |
| t.Run(testCase.key, func(t *testing.T) { |
| out := InList(testCase.key, input) |
| if out != testCase.expected { |
| t.Errorf("incorrect output:") |
| t.Errorf(" key: %#v", testCase.key) |
| t.Errorf(" input: %#v", input) |
| t.Errorf(" expected: %#v", testCase.expected) |
| t.Errorf(" got: %#v", out) |
| } |
| }) |
| } |
| } |
| |
| func TestPrefixInList(t *testing.T) { |
| prefixes := []string{"a", "b"} |
| |
| testcases := []struct { |
| str string |
| expected bool |
| }{ |
| { |
| str: "a-example", |
| expected: true, |
| }, |
| { |
| str: "b-example", |
| expected: true, |
| }, |
| { |
| str: "X-example", |
| expected: false, |
| }, |
| } |
| |
| for _, testCase := range testcases { |
| t.Run(testCase.str, func(t *testing.T) { |
| out := HasAnyPrefix(testCase.str, prefixes) |
| if out != testCase.expected { |
| t.Errorf("incorrect output:") |
| t.Errorf(" str: %#v", testCase.str) |
| t.Errorf(" prefixes: %#v", prefixes) |
| t.Errorf(" expected: %#v", testCase.expected) |
| t.Errorf(" got: %#v", out) |
| } |
| }) |
| } |
| } |
| |
| func TestFilterList(t *testing.T) { |
| input := []string{"a", "b", "c", "c", "b", "d", "a"} |
| filter := []string{"a", "c"} |
| remainder, filtered := FilterList(input, filter) |
| |
| expected := []string{"b", "b", "d"} |
| if !reflect.DeepEqual(remainder, expected) { |
| t.Errorf("incorrect remainder output:") |
| t.Errorf(" input: %#v", input) |
| t.Errorf(" filter: %#v", filter) |
| t.Errorf(" expected: %#v", expected) |
| t.Errorf(" got: %#v", remainder) |
| } |
| |
| expected = []string{"a", "c", "c", "a"} |
| if !reflect.DeepEqual(filtered, expected) { |
| t.Errorf("incorrect filtered output:") |
| t.Errorf(" input: %#v", input) |
| t.Errorf(" filter: %#v", filter) |
| t.Errorf(" expected: %#v", expected) |
| t.Errorf(" got: %#v", filtered) |
| } |
| } |
| |
| func TestFilterListPred(t *testing.T) { |
| pred := func(s string) bool { return strings.HasPrefix(s, "a/") } |
| AssertArrayString(t, "filter", FilterListPred([]string{"a/c", "b/a", "a/b"}, pred), []string{"a/c", "a/b"}) |
| AssertArrayString(t, "filter", FilterListPred([]string{"b/c", "a/a", "b/b"}, pred), []string{"a/a"}) |
| AssertArrayString(t, "filter", FilterListPred([]string{"c/c", "b/a", "c/b"}, pred), []string{}) |
| AssertArrayString(t, "filter", FilterListPred([]string{"a/c", "a/a", "a/b"}, pred), []string{"a/c", "a/a", "a/b"}) |
| } |
| |
| func TestRemoveListFromList(t *testing.T) { |
| input := []string{"a", "b", "c", "d", "a", "c", "d"} |
| filter := []string{"a", "c"} |
| expected := []string{"b", "d", "d"} |
| out := RemoveListFromList(input, filter) |
| if !reflect.DeepEqual(out, expected) { |
| t.Errorf("incorrect output:") |
| t.Errorf(" input: %#v", input) |
| t.Errorf(" filter: %#v", filter) |
| t.Errorf(" expected: %#v", expected) |
| t.Errorf(" got: %#v", out) |
| } |
| } |
| |
| func TestRemoveFromList(t *testing.T) { |
| testcases := []struct { |
| name string |
| key string |
| input []string |
| expectedFound bool |
| expectedOut []string |
| }{ |
| { |
| name: "remove_one_match", |
| key: "a", |
| input: []string{"a", "b", "c"}, |
| expectedFound: true, |
| expectedOut: []string{"b", "c"}, |
| }, |
| { |
| name: "remove_three_matches", |
| key: "a", |
| input: []string{"a", "b", "a", "c", "a"}, |
| expectedFound: true, |
| expectedOut: []string{"b", "c"}, |
| }, |
| { |
| name: "remove_zero_matches", |
| key: "X", |
| input: []string{"a", "b", "a", "c", "a"}, |
| expectedFound: false, |
| expectedOut: []string{"a", "b", "a", "c", "a"}, |
| }, |
| { |
| name: "remove_all_matches", |
| key: "a", |
| input: []string{"a", "a", "a", "a"}, |
| expectedFound: true, |
| expectedOut: []string{}, |
| }, |
| } |
| |
| for _, testCase := range testcases { |
| t.Run(testCase.name, func(t *testing.T) { |
| found, out := RemoveFromList(testCase.key, testCase.input) |
| if found != testCase.expectedFound { |
| t.Errorf("incorrect output:") |
| t.Errorf(" key: %#v", testCase.key) |
| t.Errorf(" input: %#v", testCase.input) |
| t.Errorf(" expected: %#v", testCase.expectedFound) |
| t.Errorf(" got: %#v", found) |
| } |
| if !reflect.DeepEqual(out, testCase.expectedOut) { |
| t.Errorf("incorrect output:") |
| t.Errorf(" key: %#v", testCase.key) |
| t.Errorf(" input: %#v", testCase.input) |
| t.Errorf(" expected: %#v", testCase.expectedOut) |
| t.Errorf(" got: %#v", out) |
| } |
| }) |
| } |
| } |
| |
| func TestCopyOfEmptyAndNil(t *testing.T) { |
| emptyList := []string{} |
| copyOfEmptyList := CopyOf(emptyList) |
| AssertBoolEquals(t, "Copy of an empty list should be an empty list and not nil", true, copyOfEmptyList != nil) |
| copyOfNilList := CopyOf(nil) |
| AssertBoolEquals(t, "Copy of a nil list should be a nil list and not an empty list", true, copyOfNilList == nil) |
| } |
| |
| func ExampleCopyOf() { |
| a := []string{"1", "2", "3"} |
| b := CopyOf(a) |
| a[0] = "-1" |
| fmt.Printf("a = %q\n", a) |
| fmt.Printf("b = %q\n", b) |
| |
| // Output: |
| // a = ["-1" "2" "3"] |
| // b = ["1" "2" "3"] |
| } |
| |
| func ExampleCopyOf_append() { |
| a := make([]string, 1, 2) |
| a[0] = "foo" |
| |
| fmt.Println("Without CopyOf:") |
| b := append(a, "bar") |
| c := append(a, "baz") |
| fmt.Printf("a = %q\n", a) |
| fmt.Printf("b = %q\n", b) |
| fmt.Printf("c = %q\n", c) |
| |
| a = make([]string, 1, 2) |
| a[0] = "foo" |
| |
| fmt.Println("With CopyOf:") |
| b = append(CopyOf(a), "bar") |
| c = append(CopyOf(a), "baz") |
| fmt.Printf("a = %q\n", a) |
| fmt.Printf("b = %q\n", b) |
| fmt.Printf("c = %q\n", c) |
| |
| // Output: |
| // Without CopyOf: |
| // a = ["foo"] |
| // b = ["foo" "baz"] |
| // c = ["foo" "baz"] |
| // With CopyOf: |
| // a = ["foo"] |
| // b = ["foo" "bar"] |
| // c = ["foo" "baz"] |
| } |
| |
| func TestSplitFileExt(t *testing.T) { |
| t.Run("soname with version", func(t *testing.T) { |
| root, suffix, ext := SplitFileExt("libtest.so.1.0.30") |
| expected := "libtest" |
| if root != expected { |
| t.Errorf("root should be %q but got %q", expected, root) |
| } |
| expected = ".so.1.0.30" |
| if suffix != expected { |
| t.Errorf("suffix should be %q but got %q", expected, suffix) |
| } |
| expected = ".so" |
| if ext != expected { |
| t.Errorf("ext should be %q but got %q", expected, ext) |
| } |
| }) |
| |
| t.Run("soname with svn version", func(t *testing.T) { |
| root, suffix, ext := SplitFileExt("libtest.so.1svn") |
| expected := "libtest" |
| if root != expected { |
| t.Errorf("root should be %q but got %q", expected, root) |
| } |
| expected = ".so.1svn" |
| if suffix != expected { |
| t.Errorf("suffix should be %q but got %q", expected, suffix) |
| } |
| expected = ".so" |
| if ext != expected { |
| t.Errorf("ext should be %q but got %q", expected, ext) |
| } |
| }) |
| |
| t.Run("version numbers in the middle should be ignored", func(t *testing.T) { |
| root, suffix, ext := SplitFileExt("libtest.1.0.30.so") |
| expected := "libtest.1.0.30" |
| if root != expected { |
| t.Errorf("root should be %q but got %q", expected, root) |
| } |
| expected = ".so" |
| if suffix != expected { |
| t.Errorf("suffix should be %q but got %q", expected, suffix) |
| } |
| expected = ".so" |
| if ext != expected { |
| t.Errorf("ext should be %q but got %q", expected, ext) |
| } |
| }) |
| |
| t.Run("no known file extension", func(t *testing.T) { |
| root, suffix, ext := SplitFileExt("test.exe") |
| expected := "test" |
| if root != expected { |
| t.Errorf("root should be %q but got %q", expected, root) |
| } |
| expected = ".exe" |
| if suffix != expected { |
| t.Errorf("suffix should be %q but got %q", expected, suffix) |
| } |
| if ext != expected { |
| t.Errorf("ext should be %q but got %q", expected, ext) |
| } |
| }) |
| } |
| |
| func Test_Shard(t *testing.T) { |
| type args struct { |
| strings []string |
| shardSize int |
| } |
| tests := []struct { |
| name string |
| args args |
| want [][]string |
| }{ |
| { |
| name: "empty", |
| args: args{ |
| strings: nil, |
| shardSize: 1, |
| }, |
| want: [][]string(nil), |
| }, |
| { |
| name: "single shard", |
| args: args{ |
| strings: []string{"a", "b"}, |
| shardSize: 2, |
| }, |
| want: [][]string{{"a", "b"}}, |
| }, |
| { |
| name: "single short shard", |
| args: args{ |
| strings: []string{"a", "b"}, |
| shardSize: 3, |
| }, |
| want: [][]string{{"a", "b"}}, |
| }, |
| { |
| name: "shard per input", |
| args: args{ |
| strings: []string{"a", "b", "c"}, |
| shardSize: 1, |
| }, |
| want: [][]string{{"a"}, {"b"}, {"c"}}, |
| }, |
| { |
| name: "balanced shards", |
| args: args{ |
| strings: []string{"a", "b", "c", "d"}, |
| shardSize: 2, |
| }, |
| want: [][]string{{"a", "b"}, {"c", "d"}}, |
| }, |
| { |
| name: "unbalanced shards", |
| args: args{ |
| strings: []string{"a", "b", "c"}, |
| shardSize: 2, |
| }, |
| want: [][]string{{"a", "b"}, {"c"}}, |
| }, |
| } |
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| t.Run("strings", func(t *testing.T) { |
| if got := ShardStrings(tt.args.strings, tt.args.shardSize); !reflect.DeepEqual(got, tt.want) { |
| t.Errorf("ShardStrings(%v, %v) = %v, want %v", |
| tt.args.strings, tt.args.shardSize, got, tt.want) |
| } |
| }) |
| |
| t.Run("paths", func(t *testing.T) { |
| stringsToPaths := func(strings []string) Paths { |
| if strings == nil { |
| return nil |
| } |
| paths := make(Paths, len(strings)) |
| for i, s := range strings { |
| paths[i] = PathForTesting(s) |
| } |
| return paths |
| } |
| |
| paths := stringsToPaths(tt.args.strings) |
| |
| var want []Paths |
| if sWant := tt.want; sWant != nil { |
| want = make([]Paths, len(sWant)) |
| for i, w := range sWant { |
| want[i] = stringsToPaths(w) |
| } |
| } |
| |
| if got := ShardPaths(paths, tt.args.shardSize); !reflect.DeepEqual(got, want) { |
| t.Errorf("ShardPaths(%v, %v) = %v, want %v", |
| paths, tt.args.shardSize, got, want) |
| } |
| }) |
| }) |
| } |
| } |
| |
| func BenchmarkFirstUniqueStrings(b *testing.B) { |
| implementations := []struct { |
| name string |
| f func([]string) []string |
| }{ |
| { |
| name: "list", |
| f: firstUniqueStringsList, |
| }, |
| { |
| name: "map", |
| f: firstUniqueStringsMap, |
| }, |
| { |
| name: "optimal", |
| f: FirstUniqueStrings, |
| }, |
| } |
| const maxSize = 1024 |
| uniqueStrings := make([]string, maxSize) |
| for i := range uniqueStrings { |
| uniqueStrings[i] = strconv.Itoa(i) |
| } |
| sameString := make([]string, maxSize) |
| for i := range sameString { |
| sameString[i] = uniqueStrings[0] |
| } |
| |
| f := func(b *testing.B, imp func([]string) []string, s []string) { |
| for i := 0; i < b.N; i++ { |
| b.ReportAllocs() |
| s = append([]string(nil), s...) |
| imp(s) |
| } |
| } |
| |
| for n := 1; n <= maxSize; n <<= 1 { |
| b.Run(strconv.Itoa(n), func(b *testing.B) { |
| for _, implementation := range implementations { |
| b.Run(implementation.name, func(b *testing.B) { |
| b.Run("same", func(b *testing.B) { |
| f(b, implementation.f, sameString[:n]) |
| }) |
| b.Run("unique", func(b *testing.B) { |
| f(b, implementation.f, uniqueStrings[:n]) |
| }) |
| }) |
| } |
| }) |
| } |
| } |
| |
| func testSortedKeysHelper[K Ordered, V any](t *testing.T, name string, input map[K]V, expected []K) { |
| t.Helper() |
| t.Run(name, func(t *testing.T) { |
| actual := SortedKeys(input) |
| if !reflect.DeepEqual(actual, expected) { |
| t.Errorf("expected %v, got %v", expected, actual) |
| } |
| }) |
| } |
| |
| func TestSortedKeys(t *testing.T) { |
| testSortedKeysHelper(t, "simple", map[string]string{ |
| "b": "bar", |
| "a": "foo", |
| }, []string{ |
| "a", |
| "b", |
| }) |
| testSortedKeysHelper(t, "ints", map[int]interface{}{ |
| 10: nil, |
| 5: nil, |
| }, []int{ |
| 5, |
| 10, |
| }) |
| |
| testSortedKeysHelper(t, "nil", map[string]string(nil), nil) |
| testSortedKeysHelper(t, "empty", map[string]string{}, nil) |
| } |
| |
| func TestSortedStringValues(t *testing.T) { |
| testCases := []struct { |
| name string |
| in interface{} |
| expected []string |
| }{ |
| { |
| name: "nil", |
| in: map[string]string(nil), |
| expected: nil, |
| }, |
| { |
| name: "empty", |
| in: map[string]string{}, |
| expected: nil, |
| }, |
| { |
| name: "simple", |
| in: map[string]string{"foo": "a", "bar": "b"}, |
| expected: []string{"a", "b"}, |
| }, |
| { |
| name: "duplicates", |
| in: map[string]string{"foo": "a", "bar": "b", "baz": "b"}, |
| expected: []string{"a", "b", "b"}, |
| }, |
| } |
| |
| for _, tt := range testCases { |
| t.Run(tt.name, func(t *testing.T) { |
| got := SortedStringValues(tt.in) |
| if g, w := got, tt.expected; !reflect.DeepEqual(g, w) { |
| t.Errorf("wanted %q, got %q", w, g) |
| } |
| }) |
| } |
| } |
| |
| func TestSortedUniqueStringValues(t *testing.T) { |
| testCases := []struct { |
| name string |
| in interface{} |
| expected []string |
| }{ |
| { |
| name: "nil", |
| in: map[string]string(nil), |
| expected: nil, |
| }, |
| { |
| name: "empty", |
| in: map[string]string{}, |
| expected: nil, |
| }, |
| { |
| name: "simple", |
| in: map[string]string{"foo": "a", "bar": "b"}, |
| expected: []string{"a", "b"}, |
| }, |
| { |
| name: "duplicates", |
| in: map[string]string{"foo": "a", "bar": "b", "baz": "b"}, |
| expected: []string{"a", "b"}, |
| }, |
| } |
| |
| for _, tt := range testCases { |
| t.Run(tt.name, func(t *testing.T) { |
| got := SortedUniqueStringValues(tt.in) |
| if g, w := got, tt.expected; !reflect.DeepEqual(g, w) { |
| t.Errorf("wanted %q, got %q", w, g) |
| } |
| }) |
| } |
| } |