| Colin Cross | b671544 | 2017-10-24 11:13:31 -0700 | [diff] [blame] | 1 | // 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 |  | 
|  | 15 | package android | 
|  | 16 |  | 
|  | 17 | import ( | 
| Colin Cross | 454c087 | 2019-02-15 23:03:34 -0800 | [diff] [blame] | 18 | "fmt" | 
| Colin Cross | b671544 | 2017-10-24 11:13:31 -0700 | [diff] [blame] | 19 | "reflect" | 
| Colin Cross | 27027c7 | 2020-02-28 15:34:17 -0800 | [diff] [blame] | 20 | "strconv" | 
| Martin Stjernholm | 1461c4d | 2021-03-27 19:04:05 +0000 | [diff] [blame] | 21 | "strings" | 
| Colin Cross | b671544 | 2017-10-24 11:13:31 -0700 | [diff] [blame] | 22 | "testing" | 
| Colin Cross | b5e3f7d | 2023-07-06 15:37:53 -0700 | [diff] [blame] | 23 | "unsafe" | 
| Colin Cross | b671544 | 2017-10-24 11:13:31 -0700 | [diff] [blame] | 24 | ) | 
|  | 25 |  | 
|  | 26 | var firstUniqueStringsTestCases = []struct { | 
|  | 27 | in  []string | 
|  | 28 | out []string | 
|  | 29 | }{ | 
|  | 30 | { | 
|  | 31 | in:  []string{"a"}, | 
|  | 32 | out: []string{"a"}, | 
|  | 33 | }, | 
|  | 34 | { | 
|  | 35 | in:  []string{"a", "b"}, | 
|  | 36 | out: []string{"a", "b"}, | 
|  | 37 | }, | 
|  | 38 | { | 
|  | 39 | in:  []string{"a", "a"}, | 
|  | 40 | out: []string{"a"}, | 
|  | 41 | }, | 
|  | 42 | { | 
|  | 43 | in:  []string{"a", "b", "a"}, | 
|  | 44 | out: []string{"a", "b"}, | 
|  | 45 | }, | 
|  | 46 | { | 
|  | 47 | in:  []string{"b", "a", "a"}, | 
|  | 48 | out: []string{"b", "a"}, | 
|  | 49 | }, | 
|  | 50 | { | 
|  | 51 | in:  []string{"a", "a", "b"}, | 
|  | 52 | out: []string{"a", "b"}, | 
|  | 53 | }, | 
|  | 54 | { | 
|  | 55 | in:  []string{"a", "b", "a", "b"}, | 
|  | 56 | out: []string{"a", "b"}, | 
|  | 57 | }, | 
|  | 58 | { | 
|  | 59 | in:  []string{"liblog", "libdl", "libc++", "libdl", "libc", "libm"}, | 
|  | 60 | out: []string{"liblog", "libdl", "libc++", "libc", "libm"}, | 
|  | 61 | }, | 
|  | 62 | } | 
|  | 63 |  | 
|  | 64 | func TestFirstUniqueStrings(t *testing.T) { | 
| Colin Cross | 27027c7 | 2020-02-28 15:34:17 -0800 | [diff] [blame] | 65 | f := func(t *testing.T, imp func([]string) []string, in, want []string) { | 
|  | 66 | t.Helper() | 
|  | 67 | out := imp(in) | 
|  | 68 | if !reflect.DeepEqual(out, want) { | 
| Colin Cross | b671544 | 2017-10-24 11:13:31 -0700 | [diff] [blame] | 69 | t.Errorf("incorrect output:") | 
| Colin Cross | 27027c7 | 2020-02-28 15:34:17 -0800 | [diff] [blame] | 70 | t.Errorf("     input: %#v", in) | 
|  | 71 | t.Errorf("  expected: %#v", want) | 
| Colin Cross | b671544 | 2017-10-24 11:13:31 -0700 | [diff] [blame] | 72 | t.Errorf("       got: %#v", out) | 
|  | 73 | } | 
|  | 74 | } | 
| Colin Cross | 27027c7 | 2020-02-28 15:34:17 -0800 | [diff] [blame] | 75 |  | 
|  | 76 | for _, testCase := range firstUniqueStringsTestCases { | 
|  | 77 | t.Run("list", func(t *testing.T) { | 
| Colin Cross | c85750b | 2022-04-21 12:50:51 -0700 | [diff] [blame] | 78 | f(t, firstUniqueList[string], testCase.in, testCase.out) | 
| Colin Cross | 27027c7 | 2020-02-28 15:34:17 -0800 | [diff] [blame] | 79 | }) | 
|  | 80 | t.Run("map", func(t *testing.T) { | 
| Colin Cross | c85750b | 2022-04-21 12:50:51 -0700 | [diff] [blame] | 81 | f(t, firstUniqueMap[string], testCase.in, testCase.out) | 
| Colin Cross | 27027c7 | 2020-02-28 15:34:17 -0800 | [diff] [blame] | 82 | }) | 
|  | 83 | } | 
| Colin Cross | b671544 | 2017-10-24 11:13:31 -0700 | [diff] [blame] | 84 | } | 
|  | 85 |  | 
|  | 86 | var lastUniqueStringsTestCases = []struct { | 
|  | 87 | in  []string | 
|  | 88 | out []string | 
|  | 89 | }{ | 
|  | 90 | { | 
|  | 91 | in:  []string{"a"}, | 
|  | 92 | out: []string{"a"}, | 
|  | 93 | }, | 
|  | 94 | { | 
|  | 95 | in:  []string{"a", "b"}, | 
|  | 96 | out: []string{"a", "b"}, | 
|  | 97 | }, | 
|  | 98 | { | 
|  | 99 | in:  []string{"a", "a"}, | 
|  | 100 | out: []string{"a"}, | 
|  | 101 | }, | 
|  | 102 | { | 
|  | 103 | in:  []string{"a", "b", "a"}, | 
|  | 104 | out: []string{"b", "a"}, | 
|  | 105 | }, | 
|  | 106 | { | 
|  | 107 | in:  []string{"b", "a", "a"}, | 
|  | 108 | out: []string{"b", "a"}, | 
|  | 109 | }, | 
|  | 110 | { | 
|  | 111 | in:  []string{"a", "a", "b"}, | 
|  | 112 | out: []string{"a", "b"}, | 
|  | 113 | }, | 
|  | 114 | { | 
|  | 115 | in:  []string{"a", "b", "a", "b"}, | 
|  | 116 | out: []string{"a", "b"}, | 
|  | 117 | }, | 
|  | 118 | { | 
|  | 119 | in:  []string{"liblog", "libdl", "libc++", "libdl", "libc", "libm"}, | 
|  | 120 | out: []string{"liblog", "libc++", "libdl", "libc", "libm"}, | 
|  | 121 | }, | 
|  | 122 | } | 
|  | 123 |  | 
|  | 124 | func TestLastUniqueStrings(t *testing.T) { | 
|  | 125 | for _, testCase := range lastUniqueStringsTestCases { | 
|  | 126 | out := LastUniqueStrings(testCase.in) | 
|  | 127 | if !reflect.DeepEqual(out, testCase.out) { | 
|  | 128 | t.Errorf("incorrect output:") | 
|  | 129 | t.Errorf("     input: %#v", testCase.in) | 
|  | 130 | t.Errorf("  expected: %#v", testCase.out) | 
|  | 131 | t.Errorf("       got: %#v", out) | 
|  | 132 | } | 
|  | 133 | } | 
|  | 134 | } | 
| Logan Chien | 5e877b1 | 2018-03-08 18:14:19 +0800 | [diff] [blame] | 135 |  | 
|  | 136 | func TestJoinWithPrefix(t *testing.T) { | 
|  | 137 | testcases := []struct { | 
|  | 138 | name     string | 
|  | 139 | input    []string | 
|  | 140 | expected string | 
|  | 141 | }{ | 
|  | 142 | { | 
|  | 143 | name:     "zero_inputs", | 
|  | 144 | input:    []string{}, | 
|  | 145 | expected: "", | 
|  | 146 | }, | 
|  | 147 | { | 
|  | 148 | name:     "one_input", | 
|  | 149 | input:    []string{"a"}, | 
|  | 150 | expected: "prefix:a", | 
|  | 151 | }, | 
|  | 152 | { | 
|  | 153 | name:     "two_inputs", | 
|  | 154 | input:    []string{"a", "b"}, | 
|  | 155 | expected: "prefix:a prefix:b", | 
|  | 156 | }, | 
|  | 157 | } | 
|  | 158 |  | 
|  | 159 | prefix := "prefix:" | 
|  | 160 |  | 
|  | 161 | for _, testCase := range testcases { | 
|  | 162 | t.Run(testCase.name, func(t *testing.T) { | 
|  | 163 | out := JoinWithPrefix(testCase.input, prefix) | 
|  | 164 | if out != testCase.expected { | 
|  | 165 | t.Errorf("incorrect output:") | 
|  | 166 | t.Errorf("     input: %#v", testCase.input) | 
|  | 167 | t.Errorf("    prefix: %#v", prefix) | 
|  | 168 | t.Errorf("  expected: %#v", testCase.expected) | 
|  | 169 | t.Errorf("       got: %#v", out) | 
|  | 170 | } | 
|  | 171 | }) | 
|  | 172 | } | 
|  | 173 | } | 
|  | 174 |  | 
|  | 175 | func TestIndexList(t *testing.T) { | 
|  | 176 | input := []string{"a", "b", "c"} | 
|  | 177 |  | 
|  | 178 | testcases := []struct { | 
|  | 179 | key      string | 
|  | 180 | expected int | 
|  | 181 | }{ | 
|  | 182 | { | 
|  | 183 | key:      "a", | 
|  | 184 | expected: 0, | 
|  | 185 | }, | 
|  | 186 | { | 
|  | 187 | key:      "b", | 
|  | 188 | expected: 1, | 
|  | 189 | }, | 
|  | 190 | { | 
|  | 191 | key:      "c", | 
|  | 192 | expected: 2, | 
|  | 193 | }, | 
|  | 194 | { | 
|  | 195 | key:      "X", | 
|  | 196 | expected: -1, | 
|  | 197 | }, | 
|  | 198 | } | 
|  | 199 |  | 
|  | 200 | for _, testCase := range testcases { | 
|  | 201 | t.Run(testCase.key, func(t *testing.T) { | 
|  | 202 | out := IndexList(testCase.key, input) | 
|  | 203 | if out != testCase.expected { | 
|  | 204 | t.Errorf("incorrect output:") | 
|  | 205 | t.Errorf("       key: %#v", testCase.key) | 
|  | 206 | t.Errorf("     input: %#v", input) | 
|  | 207 | t.Errorf("  expected: %#v", testCase.expected) | 
|  | 208 | t.Errorf("       got: %#v", out) | 
|  | 209 | } | 
|  | 210 | }) | 
|  | 211 | } | 
|  | 212 | } | 
|  | 213 |  | 
|  | 214 | func TestInList(t *testing.T) { | 
|  | 215 | input := []string{"a"} | 
|  | 216 |  | 
|  | 217 | testcases := []struct { | 
|  | 218 | key      string | 
|  | 219 | expected bool | 
|  | 220 | }{ | 
|  | 221 | { | 
|  | 222 | key:      "a", | 
|  | 223 | expected: true, | 
|  | 224 | }, | 
|  | 225 | { | 
|  | 226 | key:      "X", | 
|  | 227 | expected: false, | 
|  | 228 | }, | 
|  | 229 | } | 
|  | 230 |  | 
|  | 231 | for _, testCase := range testcases { | 
|  | 232 | t.Run(testCase.key, func(t *testing.T) { | 
|  | 233 | out := InList(testCase.key, input) | 
|  | 234 | if out != testCase.expected { | 
|  | 235 | t.Errorf("incorrect output:") | 
|  | 236 | t.Errorf("       key: %#v", testCase.key) | 
|  | 237 | t.Errorf("     input: %#v", input) | 
|  | 238 | t.Errorf("  expected: %#v", testCase.expected) | 
|  | 239 | t.Errorf("       got: %#v", out) | 
|  | 240 | } | 
|  | 241 | }) | 
|  | 242 | } | 
|  | 243 | } | 
|  | 244 |  | 
|  | 245 | func TestPrefixInList(t *testing.T) { | 
|  | 246 | prefixes := []string{"a", "b"} | 
|  | 247 |  | 
|  | 248 | testcases := []struct { | 
|  | 249 | str      string | 
|  | 250 | expected bool | 
|  | 251 | }{ | 
|  | 252 | { | 
|  | 253 | str:      "a-example", | 
|  | 254 | expected: true, | 
|  | 255 | }, | 
|  | 256 | { | 
|  | 257 | str:      "b-example", | 
|  | 258 | expected: true, | 
|  | 259 | }, | 
|  | 260 | { | 
|  | 261 | str:      "X-example", | 
|  | 262 | expected: false, | 
|  | 263 | }, | 
|  | 264 | } | 
|  | 265 |  | 
|  | 266 | for _, testCase := range testcases { | 
|  | 267 | t.Run(testCase.str, func(t *testing.T) { | 
| Jaewoong Jung | 3aff578 | 2020-02-11 07:54:35 -0800 | [diff] [blame] | 268 | out := HasAnyPrefix(testCase.str, prefixes) | 
| Logan Chien | 5e877b1 | 2018-03-08 18:14:19 +0800 | [diff] [blame] | 269 | if out != testCase.expected { | 
|  | 270 | t.Errorf("incorrect output:") | 
|  | 271 | t.Errorf("       str: %#v", testCase.str) | 
|  | 272 | t.Errorf("  prefixes: %#v", prefixes) | 
|  | 273 | t.Errorf("  expected: %#v", testCase.expected) | 
|  | 274 | t.Errorf("       got: %#v", out) | 
|  | 275 | } | 
|  | 276 | }) | 
|  | 277 | } | 
|  | 278 | } | 
|  | 279 |  | 
|  | 280 | func TestFilterList(t *testing.T) { | 
|  | 281 | input := []string{"a", "b", "c", "c", "b", "d", "a"} | 
|  | 282 | filter := []string{"a", "c"} | 
|  | 283 | remainder, filtered := FilterList(input, filter) | 
|  | 284 |  | 
|  | 285 | expected := []string{"b", "b", "d"} | 
|  | 286 | if !reflect.DeepEqual(remainder, expected) { | 
|  | 287 | t.Errorf("incorrect remainder output:") | 
|  | 288 | t.Errorf("     input: %#v", input) | 
|  | 289 | t.Errorf("    filter: %#v", filter) | 
|  | 290 | t.Errorf("  expected: %#v", expected) | 
|  | 291 | t.Errorf("       got: %#v", remainder) | 
|  | 292 | } | 
|  | 293 |  | 
|  | 294 | expected = []string{"a", "c", "c", "a"} | 
|  | 295 | if !reflect.DeepEqual(filtered, expected) { | 
|  | 296 | t.Errorf("incorrect filtered output:") | 
|  | 297 | t.Errorf("     input: %#v", input) | 
|  | 298 | t.Errorf("    filter: %#v", filter) | 
|  | 299 | t.Errorf("  expected: %#v", expected) | 
|  | 300 | t.Errorf("       got: %#v", filtered) | 
|  | 301 | } | 
|  | 302 | } | 
|  | 303 |  | 
| Martin Stjernholm | 1461c4d | 2021-03-27 19:04:05 +0000 | [diff] [blame] | 304 | func TestFilterListPred(t *testing.T) { | 
|  | 305 | pred := func(s string) bool { return strings.HasPrefix(s, "a/") } | 
|  | 306 | AssertArrayString(t, "filter", FilterListPred([]string{"a/c", "b/a", "a/b"}, pred), []string{"a/c", "a/b"}) | 
|  | 307 | AssertArrayString(t, "filter", FilterListPred([]string{"b/c", "a/a", "b/b"}, pred), []string{"a/a"}) | 
|  | 308 | AssertArrayString(t, "filter", FilterListPred([]string{"c/c", "b/a", "c/b"}, pred), []string{}) | 
|  | 309 | AssertArrayString(t, "filter", FilterListPred([]string{"a/c", "a/a", "a/b"}, pred), []string{"a/c", "a/a", "a/b"}) | 
|  | 310 | } | 
|  | 311 |  | 
| Logan Chien | 5e877b1 | 2018-03-08 18:14:19 +0800 | [diff] [blame] | 312 | func TestRemoveListFromList(t *testing.T) { | 
|  | 313 | input := []string{"a", "b", "c", "d", "a", "c", "d"} | 
|  | 314 | filter := []string{"a", "c"} | 
|  | 315 | expected := []string{"b", "d", "d"} | 
|  | 316 | out := RemoveListFromList(input, filter) | 
|  | 317 | if !reflect.DeepEqual(out, expected) { | 
|  | 318 | t.Errorf("incorrect output:") | 
|  | 319 | t.Errorf("     input: %#v", input) | 
|  | 320 | t.Errorf("    filter: %#v", filter) | 
|  | 321 | t.Errorf("  expected: %#v", expected) | 
|  | 322 | t.Errorf("       got: %#v", out) | 
|  | 323 | } | 
|  | 324 | } | 
| Logan Chien | 7922ab8 | 2018-03-06 18:29:27 +0800 | [diff] [blame] | 325 |  | 
|  | 326 | func TestRemoveFromList(t *testing.T) { | 
|  | 327 | testcases := []struct { | 
|  | 328 | name          string | 
|  | 329 | key           string | 
|  | 330 | input         []string | 
|  | 331 | expectedFound bool | 
|  | 332 | expectedOut   []string | 
|  | 333 | }{ | 
|  | 334 | { | 
|  | 335 | name:          "remove_one_match", | 
|  | 336 | key:           "a", | 
|  | 337 | input:         []string{"a", "b", "c"}, | 
|  | 338 | expectedFound: true, | 
|  | 339 | expectedOut:   []string{"b", "c"}, | 
|  | 340 | }, | 
|  | 341 | { | 
|  | 342 | name:          "remove_three_matches", | 
|  | 343 | key:           "a", | 
|  | 344 | input:         []string{"a", "b", "a", "c", "a"}, | 
|  | 345 | expectedFound: true, | 
|  | 346 | expectedOut:   []string{"b", "c"}, | 
|  | 347 | }, | 
|  | 348 | { | 
|  | 349 | name:          "remove_zero_matches", | 
|  | 350 | key:           "X", | 
|  | 351 | input:         []string{"a", "b", "a", "c", "a"}, | 
|  | 352 | expectedFound: false, | 
|  | 353 | expectedOut:   []string{"a", "b", "a", "c", "a"}, | 
|  | 354 | }, | 
|  | 355 | { | 
|  | 356 | name:          "remove_all_matches", | 
|  | 357 | key:           "a", | 
|  | 358 | input:         []string{"a", "a", "a", "a"}, | 
|  | 359 | expectedFound: true, | 
|  | 360 | expectedOut:   []string{}, | 
|  | 361 | }, | 
|  | 362 | } | 
|  | 363 |  | 
|  | 364 | for _, testCase := range testcases { | 
|  | 365 | t.Run(testCase.name, func(t *testing.T) { | 
|  | 366 | found, out := RemoveFromList(testCase.key, testCase.input) | 
|  | 367 | if found != testCase.expectedFound { | 
|  | 368 | t.Errorf("incorrect output:") | 
|  | 369 | t.Errorf("       key: %#v", testCase.key) | 
|  | 370 | t.Errorf("     input: %#v", testCase.input) | 
|  | 371 | t.Errorf("  expected: %#v", testCase.expectedFound) | 
|  | 372 | t.Errorf("       got: %#v", found) | 
|  | 373 | } | 
|  | 374 | if !reflect.DeepEqual(out, testCase.expectedOut) { | 
|  | 375 | t.Errorf("incorrect output:") | 
|  | 376 | t.Errorf("       key: %#v", testCase.key) | 
|  | 377 | t.Errorf("     input: %#v", testCase.input) | 
|  | 378 | t.Errorf("  expected: %#v", testCase.expectedOut) | 
|  | 379 | t.Errorf("       got: %#v", out) | 
|  | 380 | } | 
|  | 381 | }) | 
|  | 382 | } | 
|  | 383 | } | 
| Colin Cross | 454c087 | 2019-02-15 23:03:34 -0800 | [diff] [blame] | 384 |  | 
| Spandan Das | cc4da76 | 2023-04-27 19:34:08 +0000 | [diff] [blame] | 385 | func TestCopyOfEmptyAndNil(t *testing.T) { | 
|  | 386 | emptyList := []string{} | 
|  | 387 | copyOfEmptyList := CopyOf(emptyList) | 
|  | 388 | AssertBoolEquals(t, "Copy of an empty list should be an empty list and not nil", true, copyOfEmptyList != nil) | 
| Colin Cross | 13aeb68 | 2023-07-06 15:02:56 -0700 | [diff] [blame] | 389 | copyOfNilList := CopyOf([]string(nil)) | 
| Spandan Das | cc4da76 | 2023-04-27 19:34:08 +0000 | [diff] [blame] | 390 | AssertBoolEquals(t, "Copy of a nil list should be a nil list and not an empty list", true, copyOfNilList == nil) | 
|  | 391 | } | 
|  | 392 |  | 
| Colin Cross | 454c087 | 2019-02-15 23:03:34 -0800 | [diff] [blame] | 393 | func ExampleCopyOf() { | 
|  | 394 | a := []string{"1", "2", "3"} | 
|  | 395 | b := CopyOf(a) | 
|  | 396 | a[0] = "-1" | 
|  | 397 | fmt.Printf("a = %q\n", a) | 
|  | 398 | fmt.Printf("b = %q\n", b) | 
|  | 399 |  | 
|  | 400 | // Output: | 
|  | 401 | // a = ["-1" "2" "3"] | 
|  | 402 | // b = ["1" "2" "3"] | 
|  | 403 | } | 
|  | 404 |  | 
|  | 405 | func ExampleCopyOf_append() { | 
|  | 406 | a := make([]string, 1, 2) | 
|  | 407 | a[0] = "foo" | 
|  | 408 |  | 
|  | 409 | fmt.Println("Without CopyOf:") | 
|  | 410 | b := append(a, "bar") | 
|  | 411 | c := append(a, "baz") | 
|  | 412 | fmt.Printf("a = %q\n", a) | 
|  | 413 | fmt.Printf("b = %q\n", b) | 
|  | 414 | fmt.Printf("c = %q\n", c) | 
|  | 415 |  | 
|  | 416 | a = make([]string, 1, 2) | 
|  | 417 | a[0] = "foo" | 
|  | 418 |  | 
|  | 419 | fmt.Println("With CopyOf:") | 
|  | 420 | b = append(CopyOf(a), "bar") | 
|  | 421 | c = append(CopyOf(a), "baz") | 
|  | 422 | fmt.Printf("a = %q\n", a) | 
|  | 423 | fmt.Printf("b = %q\n", b) | 
|  | 424 | fmt.Printf("c = %q\n", c) | 
|  | 425 |  | 
|  | 426 | // Output: | 
|  | 427 | // Without CopyOf: | 
|  | 428 | // a = ["foo"] | 
|  | 429 | // b = ["foo" "baz"] | 
|  | 430 | // c = ["foo" "baz"] | 
|  | 431 | // With CopyOf: | 
|  | 432 | // a = ["foo"] | 
|  | 433 | // b = ["foo" "bar"] | 
|  | 434 | // c = ["foo" "baz"] | 
|  | 435 | } | 
| Ivan Lozano | 022a73b | 2019-09-09 20:29:31 -0700 | [diff] [blame] | 436 |  | 
|  | 437 | func TestSplitFileExt(t *testing.T) { | 
|  | 438 | t.Run("soname with version", func(t *testing.T) { | 
|  | 439 | root, suffix, ext := SplitFileExt("libtest.so.1.0.30") | 
|  | 440 | expected := "libtest" | 
|  | 441 | if root != expected { | 
|  | 442 | t.Errorf("root should be %q but got %q", expected, root) | 
|  | 443 | } | 
|  | 444 | expected = ".so.1.0.30" | 
|  | 445 | if suffix != expected { | 
|  | 446 | t.Errorf("suffix should be %q but got %q", expected, suffix) | 
|  | 447 | } | 
|  | 448 | expected = ".so" | 
|  | 449 | if ext != expected { | 
|  | 450 | t.Errorf("ext should be %q but got %q", expected, ext) | 
|  | 451 | } | 
|  | 452 | }) | 
|  | 453 |  | 
|  | 454 | t.Run("soname with svn version", func(t *testing.T) { | 
|  | 455 | root, suffix, ext := SplitFileExt("libtest.so.1svn") | 
|  | 456 | expected := "libtest" | 
|  | 457 | if root != expected { | 
|  | 458 | t.Errorf("root should be %q but got %q", expected, root) | 
|  | 459 | } | 
|  | 460 | expected = ".so.1svn" | 
|  | 461 | if suffix != expected { | 
|  | 462 | t.Errorf("suffix should be %q but got %q", expected, suffix) | 
|  | 463 | } | 
|  | 464 | expected = ".so" | 
|  | 465 | if ext != expected { | 
|  | 466 | t.Errorf("ext should be %q but got %q", expected, ext) | 
|  | 467 | } | 
|  | 468 | }) | 
|  | 469 |  | 
|  | 470 | t.Run("version numbers in the middle should be ignored", func(t *testing.T) { | 
|  | 471 | root, suffix, ext := SplitFileExt("libtest.1.0.30.so") | 
|  | 472 | expected := "libtest.1.0.30" | 
|  | 473 | if root != expected { | 
|  | 474 | t.Errorf("root should be %q but got %q", expected, root) | 
|  | 475 | } | 
|  | 476 | expected = ".so" | 
|  | 477 | if suffix != expected { | 
|  | 478 | t.Errorf("suffix should be %q but got %q", expected, suffix) | 
|  | 479 | } | 
|  | 480 | expected = ".so" | 
|  | 481 | if ext != expected { | 
|  | 482 | t.Errorf("ext should be %q but got %q", expected, ext) | 
|  | 483 | } | 
|  | 484 | }) | 
|  | 485 |  | 
|  | 486 | t.Run("no known file extension", func(t *testing.T) { | 
|  | 487 | root, suffix, ext := SplitFileExt("test.exe") | 
|  | 488 | expected := "test" | 
|  | 489 | if root != expected { | 
|  | 490 | t.Errorf("root should be %q but got %q", expected, root) | 
|  | 491 | } | 
|  | 492 | expected = ".exe" | 
|  | 493 | if suffix != expected { | 
|  | 494 | t.Errorf("suffix should be %q but got %q", expected, suffix) | 
|  | 495 | } | 
|  | 496 | if ext != expected { | 
|  | 497 | t.Errorf("ext should be %q but got %q", expected, ext) | 
|  | 498 | } | 
|  | 499 | }) | 
|  | 500 | } | 
| Colin Cross | 0a2f719 | 2019-09-23 14:33:09 -0700 | [diff] [blame] | 501 |  | 
|  | 502 | func Test_Shard(t *testing.T) { | 
|  | 503 | type args struct { | 
|  | 504 | strings   []string | 
|  | 505 | shardSize int | 
|  | 506 | } | 
|  | 507 | tests := []struct { | 
|  | 508 | name string | 
|  | 509 | args args | 
|  | 510 | want [][]string | 
|  | 511 | }{ | 
|  | 512 | { | 
|  | 513 | name: "empty", | 
|  | 514 | args: args{ | 
|  | 515 | strings:   nil, | 
|  | 516 | shardSize: 1, | 
|  | 517 | }, | 
|  | 518 | want: [][]string(nil), | 
|  | 519 | }, | 
|  | 520 | { | 
|  | 521 | name: "single shard", | 
|  | 522 | args: args{ | 
|  | 523 | strings:   []string{"a", "b"}, | 
|  | 524 | shardSize: 2, | 
|  | 525 | }, | 
|  | 526 | want: [][]string{{"a", "b"}}, | 
|  | 527 | }, | 
|  | 528 | { | 
|  | 529 | name: "single short shard", | 
|  | 530 | args: args{ | 
|  | 531 | strings:   []string{"a", "b"}, | 
|  | 532 | shardSize: 3, | 
|  | 533 | }, | 
|  | 534 | want: [][]string{{"a", "b"}}, | 
|  | 535 | }, | 
|  | 536 | { | 
|  | 537 | name: "shard per input", | 
|  | 538 | args: args{ | 
|  | 539 | strings:   []string{"a", "b", "c"}, | 
|  | 540 | shardSize: 1, | 
|  | 541 | }, | 
|  | 542 | want: [][]string{{"a"}, {"b"}, {"c"}}, | 
|  | 543 | }, | 
|  | 544 | { | 
|  | 545 | name: "balanced shards", | 
|  | 546 | args: args{ | 
|  | 547 | strings:   []string{"a", "b", "c", "d"}, | 
|  | 548 | shardSize: 2, | 
|  | 549 | }, | 
|  | 550 | want: [][]string{{"a", "b"}, {"c", "d"}}, | 
|  | 551 | }, | 
|  | 552 | { | 
|  | 553 | name: "unbalanced shards", | 
|  | 554 | args: args{ | 
|  | 555 | strings:   []string{"a", "b", "c"}, | 
|  | 556 | shardSize: 2, | 
|  | 557 | }, | 
|  | 558 | want: [][]string{{"a", "b"}, {"c"}}, | 
|  | 559 | }, | 
|  | 560 | } | 
|  | 561 | for _, tt := range tests { | 
|  | 562 | t.Run(tt.name, func(t *testing.T) { | 
|  | 563 | t.Run("strings", func(t *testing.T) { | 
|  | 564 | if got := ShardStrings(tt.args.strings, tt.args.shardSize); !reflect.DeepEqual(got, tt.want) { | 
|  | 565 | t.Errorf("ShardStrings(%v, %v) = %v, want %v", | 
|  | 566 | tt.args.strings, tt.args.shardSize, got, tt.want) | 
|  | 567 | } | 
|  | 568 | }) | 
|  | 569 |  | 
|  | 570 | t.Run("paths", func(t *testing.T) { | 
|  | 571 | stringsToPaths := func(strings []string) Paths { | 
|  | 572 | if strings == nil { | 
|  | 573 | return nil | 
|  | 574 | } | 
|  | 575 | paths := make(Paths, len(strings)) | 
|  | 576 | for i, s := range strings { | 
|  | 577 | paths[i] = PathForTesting(s) | 
|  | 578 | } | 
|  | 579 | return paths | 
|  | 580 | } | 
|  | 581 |  | 
|  | 582 | paths := stringsToPaths(tt.args.strings) | 
|  | 583 |  | 
|  | 584 | var want []Paths | 
|  | 585 | if sWant := tt.want; sWant != nil { | 
|  | 586 | want = make([]Paths, len(sWant)) | 
|  | 587 | for i, w := range sWant { | 
|  | 588 | want[i] = stringsToPaths(w) | 
|  | 589 | } | 
|  | 590 | } | 
|  | 591 |  | 
|  | 592 | if got := ShardPaths(paths, tt.args.shardSize); !reflect.DeepEqual(got, want) { | 
|  | 593 | t.Errorf("ShardPaths(%v, %v) = %v, want %v", | 
|  | 594 | paths, tt.args.shardSize, got, want) | 
|  | 595 | } | 
|  | 596 | }) | 
|  | 597 | }) | 
|  | 598 | } | 
|  | 599 | } | 
| Colin Cross | 27027c7 | 2020-02-28 15:34:17 -0800 | [diff] [blame] | 600 |  | 
|  | 601 | func BenchmarkFirstUniqueStrings(b *testing.B) { | 
|  | 602 | implementations := []struct { | 
|  | 603 | name string | 
|  | 604 | f    func([]string) []string | 
|  | 605 | }{ | 
|  | 606 | { | 
|  | 607 | name: "list", | 
| Colin Cross | c85750b | 2022-04-21 12:50:51 -0700 | [diff] [blame] | 608 | f:    firstUniqueList[string], | 
| Colin Cross | 27027c7 | 2020-02-28 15:34:17 -0800 | [diff] [blame] | 609 | }, | 
|  | 610 | { | 
|  | 611 | name: "map", | 
| Colin Cross | c85750b | 2022-04-21 12:50:51 -0700 | [diff] [blame] | 612 | f:    firstUniqueMap[string], | 
| Colin Cross | 27027c7 | 2020-02-28 15:34:17 -0800 | [diff] [blame] | 613 | }, | 
| Colin Cross | 96c4412 | 2020-11-25 14:29:50 -0800 | [diff] [blame] | 614 | { | 
|  | 615 | name: "optimal", | 
|  | 616 | f:    FirstUniqueStrings, | 
|  | 617 | }, | 
| Colin Cross | 27027c7 | 2020-02-28 15:34:17 -0800 | [diff] [blame] | 618 | } | 
|  | 619 | const maxSize = 1024 | 
|  | 620 | uniqueStrings := make([]string, maxSize) | 
|  | 621 | for i := range uniqueStrings { | 
|  | 622 | uniqueStrings[i] = strconv.Itoa(i) | 
|  | 623 | } | 
|  | 624 | sameString := make([]string, maxSize) | 
|  | 625 | for i := range sameString { | 
|  | 626 | sameString[i] = uniqueStrings[0] | 
|  | 627 | } | 
|  | 628 |  | 
|  | 629 | f := func(b *testing.B, imp func([]string) []string, s []string) { | 
|  | 630 | for i := 0; i < b.N; i++ { | 
|  | 631 | b.ReportAllocs() | 
|  | 632 | s = append([]string(nil), s...) | 
|  | 633 | imp(s) | 
|  | 634 | } | 
|  | 635 | } | 
|  | 636 |  | 
|  | 637 | for n := 1; n <= maxSize; n <<= 1 { | 
|  | 638 | b.Run(strconv.Itoa(n), func(b *testing.B) { | 
|  | 639 | for _, implementation := range implementations { | 
|  | 640 | b.Run(implementation.name, func(b *testing.B) { | 
|  | 641 | b.Run("same", func(b *testing.B) { | 
|  | 642 | f(b, implementation.f, sameString[:n]) | 
|  | 643 | }) | 
|  | 644 | b.Run("unique", func(b *testing.B) { | 
|  | 645 | f(b, implementation.f, uniqueStrings[:n]) | 
|  | 646 | }) | 
|  | 647 | }) | 
|  | 648 | } | 
|  | 649 | }) | 
|  | 650 | } | 
|  | 651 | } | 
| Colin Cross | 9eb853b | 2022-02-17 11:13:37 -0800 | [diff] [blame] | 652 |  | 
| Cole Faust | 18994c7 | 2023-02-28 16:02:16 -0800 | [diff] [blame] | 653 | func testSortedKeysHelper[K Ordered, V any](t *testing.T, name string, input map[K]V, expected []K) { | 
|  | 654 | t.Helper() | 
|  | 655 | t.Run(name, func(t *testing.T) { | 
|  | 656 | actual := SortedKeys(input) | 
|  | 657 | if !reflect.DeepEqual(actual, expected) { | 
| Spandan Das | c52e2c0 | 2023-03-02 23:45:10 +0000 | [diff] [blame] | 658 | t.Errorf("expected %v, got %v", expected, actual) | 
| Cole Faust | 18994c7 | 2023-02-28 16:02:16 -0800 | [diff] [blame] | 659 | } | 
|  | 660 | }) | 
|  | 661 | } | 
|  | 662 |  | 
|  | 663 | func TestSortedKeys(t *testing.T) { | 
|  | 664 | testSortedKeysHelper(t, "simple", map[string]string{ | 
|  | 665 | "b": "bar", | 
|  | 666 | "a": "foo", | 
|  | 667 | }, []string{ | 
|  | 668 | "a", | 
|  | 669 | "b", | 
|  | 670 | }) | 
|  | 671 | testSortedKeysHelper(t, "ints", map[int]interface{}{ | 
|  | 672 | 10: nil, | 
|  | 673 | 5:  nil, | 
|  | 674 | }, []int{ | 
|  | 675 | 5, | 
|  | 676 | 10, | 
|  | 677 | }) | 
|  | 678 |  | 
|  | 679 | testSortedKeysHelper(t, "nil", map[string]string(nil), nil) | 
|  | 680 | testSortedKeysHelper(t, "empty", map[string]string{}, nil) | 
|  | 681 | } | 
|  | 682 |  | 
| Colin Cross | 9eb853b | 2022-02-17 11:13:37 -0800 | [diff] [blame] | 683 | func TestSortedStringValues(t *testing.T) { | 
|  | 684 | testCases := []struct { | 
|  | 685 | name     string | 
|  | 686 | in       interface{} | 
|  | 687 | expected []string | 
|  | 688 | }{ | 
|  | 689 | { | 
|  | 690 | name:     "nil", | 
|  | 691 | in:       map[string]string(nil), | 
|  | 692 | expected: nil, | 
|  | 693 | }, | 
|  | 694 | { | 
|  | 695 | name:     "empty", | 
|  | 696 | in:       map[string]string{}, | 
|  | 697 | expected: nil, | 
|  | 698 | }, | 
|  | 699 | { | 
|  | 700 | name:     "simple", | 
|  | 701 | in:       map[string]string{"foo": "a", "bar": "b"}, | 
|  | 702 | expected: []string{"a", "b"}, | 
|  | 703 | }, | 
|  | 704 | { | 
|  | 705 | name:     "duplicates", | 
|  | 706 | in:       map[string]string{"foo": "a", "bar": "b", "baz": "b"}, | 
|  | 707 | expected: []string{"a", "b", "b"}, | 
|  | 708 | }, | 
|  | 709 | } | 
|  | 710 |  | 
|  | 711 | for _, tt := range testCases { | 
|  | 712 | t.Run(tt.name, func(t *testing.T) { | 
|  | 713 | got := SortedStringValues(tt.in) | 
|  | 714 | if g, w := got, tt.expected; !reflect.DeepEqual(g, w) { | 
|  | 715 | t.Errorf("wanted %q, got %q", w, g) | 
|  | 716 | } | 
|  | 717 | }) | 
|  | 718 | } | 
|  | 719 | } | 
|  | 720 |  | 
|  | 721 | func TestSortedUniqueStringValues(t *testing.T) { | 
|  | 722 | testCases := []struct { | 
|  | 723 | name     string | 
|  | 724 | in       interface{} | 
|  | 725 | expected []string | 
|  | 726 | }{ | 
|  | 727 | { | 
|  | 728 | name:     "nil", | 
|  | 729 | in:       map[string]string(nil), | 
|  | 730 | expected: nil, | 
|  | 731 | }, | 
|  | 732 | { | 
|  | 733 | name:     "empty", | 
|  | 734 | in:       map[string]string{}, | 
|  | 735 | expected: nil, | 
|  | 736 | }, | 
|  | 737 | { | 
|  | 738 | name:     "simple", | 
|  | 739 | in:       map[string]string{"foo": "a", "bar": "b"}, | 
|  | 740 | expected: []string{"a", "b"}, | 
|  | 741 | }, | 
|  | 742 | { | 
|  | 743 | name:     "duplicates", | 
|  | 744 | in:       map[string]string{"foo": "a", "bar": "b", "baz": "b"}, | 
|  | 745 | expected: []string{"a", "b"}, | 
|  | 746 | }, | 
|  | 747 | } | 
|  | 748 |  | 
|  | 749 | for _, tt := range testCases { | 
|  | 750 | t.Run(tt.name, func(t *testing.T) { | 
|  | 751 | got := SortedUniqueStringValues(tt.in) | 
|  | 752 | if g, w := got, tt.expected; !reflect.DeepEqual(g, w) { | 
|  | 753 | t.Errorf("wanted %q, got %q", w, g) | 
|  | 754 | } | 
|  | 755 | }) | 
|  | 756 | } | 
|  | 757 | } | 
| Colin Cross | b5e3f7d | 2023-07-06 15:37:53 -0700 | [diff] [blame] | 758 |  | 
|  | 759 | var reverseTestCases = []struct { | 
|  | 760 | name     string | 
|  | 761 | in       []string | 
|  | 762 | expected []string | 
|  | 763 | }{ | 
|  | 764 | { | 
|  | 765 | name:     "nil", | 
|  | 766 | in:       nil, | 
|  | 767 | expected: nil, | 
|  | 768 | }, | 
|  | 769 | { | 
|  | 770 | name:     "empty", | 
|  | 771 | in:       []string{}, | 
|  | 772 | expected: []string{}, | 
|  | 773 | }, | 
|  | 774 | { | 
|  | 775 | name:     "one", | 
|  | 776 | in:       []string{"one"}, | 
|  | 777 | expected: []string{"one"}, | 
|  | 778 | }, | 
|  | 779 | { | 
|  | 780 | name:     "even", | 
|  | 781 | in:       []string{"one", "two"}, | 
|  | 782 | expected: []string{"two", "one"}, | 
|  | 783 | }, | 
|  | 784 | { | 
|  | 785 | name:     "odd", | 
|  | 786 | in:       []string{"one", "two", "three"}, | 
|  | 787 | expected: []string{"three", "two", "one"}, | 
|  | 788 | }, | 
|  | 789 | } | 
|  | 790 |  | 
|  | 791 | func TestReverseSliceInPlace(t *testing.T) { | 
|  | 792 | for _, testCase := range reverseTestCases { | 
|  | 793 | t.Run(testCase.name, func(t *testing.T) { | 
|  | 794 | slice := CopyOf(testCase.in) | 
|  | 795 | slice2 := slice | 
|  | 796 | ReverseSliceInPlace(slice) | 
|  | 797 | if !reflect.DeepEqual(slice, testCase.expected) { | 
|  | 798 | t.Errorf("expected %#v, got %#v", testCase.expected, slice) | 
|  | 799 | } | 
|  | 800 | if unsafe.SliceData(slice) != unsafe.SliceData(slice2) { | 
|  | 801 | t.Errorf("expected slices to share backing array") | 
|  | 802 | } | 
|  | 803 | }) | 
|  | 804 | } | 
|  | 805 | } | 
|  | 806 |  | 
|  | 807 | func TestReverseSlice(t *testing.T) { | 
|  | 808 | for _, testCase := range reverseTestCases { | 
|  | 809 | t.Run(testCase.name, func(t *testing.T) { | 
|  | 810 | slice := ReverseSlice(testCase.in) | 
|  | 811 | if !reflect.DeepEqual(slice, testCase.expected) { | 
|  | 812 | t.Errorf("expected %#v, got %#v", testCase.expected, slice) | 
|  | 813 | } | 
|  | 814 | if slice != nil && unsafe.SliceData(testCase.in) == unsafe.SliceData(slice) { | 
|  | 815 | t.Errorf("expected slices to have different backing arrays") | 
|  | 816 | } | 
|  | 817 | }) | 
|  | 818 | } | 
|  | 819 | } |