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