1 /* 2 * Copyright (C) 2008 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.common.collect; 18 19 import static com.google.common.base.Preconditions.checkArgument; 20 import static com.google.common.truth.Truth.assertThat; 21 import static java.util.Arrays.asList; 22 23 import com.google.common.annotations.GwtCompatible; 24 import com.google.common.annotations.GwtIncompatible; 25 import com.google.common.collect.testing.ListTestSuiteBuilder; 26 import com.google.common.collect.testing.MinimalCollection; 27 import com.google.common.collect.testing.SetTestSuiteBuilder; 28 import com.google.common.collect.testing.TestStringListGenerator; 29 import com.google.common.collect.testing.TestStringSetGenerator; 30 import com.google.common.collect.testing.features.CollectionFeature; 31 import com.google.common.collect.testing.features.CollectionSize; 32 import com.google.common.collect.testing.google.MultisetTestSuiteBuilder; 33 import com.google.common.collect.testing.google.TestStringMultisetGenerator; 34 import com.google.common.collect.testing.google.UnmodifiableCollectionTests; 35 import com.google.common.testing.CollectorTester; 36 import com.google.common.testing.EqualsTester; 37 import com.google.common.testing.NullPointerTester; 38 import com.google.common.testing.SerializableTester; 39 import com.google.errorprone.annotations.CanIgnoreReturnValue; 40 import java.util.ArrayList; 41 import java.util.Arrays; 42 import java.util.Collection; 43 import java.util.Collections; 44 import java.util.HashSet; 45 import java.util.Iterator; 46 import java.util.List; 47 import java.util.Set; 48 import java.util.function.BiPredicate; 49 import java.util.stream.Collector; 50 import junit.framework.Test; 51 import junit.framework.TestCase; 52 import junit.framework.TestSuite; 53 import org.checkerframework.checker.nullness.qual.Nullable; 54 55 /** 56 * Tests for {@link ImmutableMultiset}. 57 * 58 * @author Jared Levy 59 */ 60 @GwtCompatible(emulated = true) 61 public class ImmutableMultisetTest extends TestCase { 62 63 @GwtIncompatible // suite // TODO(cpovirk): add to collect/gwt/suites suite()64 public static Test suite() { 65 TestSuite suite = new TestSuite(); 66 suite.addTestSuite(ImmutableMultisetTest.class); 67 68 suite.addTest( 69 MultisetTestSuiteBuilder.using( 70 new TestStringMultisetGenerator() { 71 @Override 72 protected Multiset<String> create(String[] elements) { 73 return ImmutableMultiset.copyOf(elements); 74 } 75 }) 76 .named("ImmutableMultiset") 77 .withFeatures( 78 CollectionSize.ANY, 79 CollectionFeature.SERIALIZABLE_INCLUDING_VIEWS, 80 CollectionFeature.ALLOWS_NULL_QUERIES) 81 .createTestSuite()); 82 83 suite.addTest( 84 MultisetTestSuiteBuilder.using( 85 new TestStringMultisetGenerator() { 86 @Override 87 protected Multiset<String> create(String[] elements) { 88 return ImmutableMultiset.<String>builder().add(elements).buildJdkBacked(); 89 } 90 }) 91 .named("ImmutableMultiset [JDK backed]") 92 .withFeatures( 93 CollectionSize.ANY, 94 CollectionFeature.SERIALIZABLE_INCLUDING_VIEWS, 95 CollectionFeature.ALLOWS_NULL_QUERIES) 96 .createTestSuite()); 97 98 suite.addTest( 99 SetTestSuiteBuilder.using( 100 new TestStringSetGenerator() { 101 @Override 102 protected Set<String> create(String[] elements) { 103 return ImmutableMultiset.copyOf(elements).elementSet(); 104 } 105 }) 106 .named("ImmutableMultiset, element set") 107 .withFeatures( 108 CollectionSize.ANY, 109 CollectionFeature.SERIALIZABLE, 110 CollectionFeature.ALLOWS_NULL_QUERIES) 111 .createTestSuite()); 112 113 suite.addTest( 114 ListTestSuiteBuilder.using( 115 new TestStringListGenerator() { 116 @Override 117 protected List<String> create(String[] elements) { 118 return ImmutableMultiset.copyOf(elements).asList(); 119 } 120 121 @Override 122 public List<String> order(List<String> insertionOrder) { 123 List<String> order = new ArrayList<>(); 124 for (String s : insertionOrder) { 125 int index = order.indexOf(s); 126 if (index == -1) { 127 order.add(s); 128 } else { 129 order.add(index, s); 130 } 131 } 132 return order; 133 } 134 }) 135 .named("ImmutableMultiset.asList") 136 .withFeatures( 137 CollectionSize.ANY, 138 CollectionFeature.SERIALIZABLE, 139 CollectionFeature.ALLOWS_NULL_QUERIES) 140 .createTestSuite()); 141 142 suite.addTest( 143 ListTestSuiteBuilder.using( 144 new TestStringListGenerator() { 145 @Override 146 protected List<String> create(String[] elements) { 147 Set<String> set = new HashSet<>(); 148 ImmutableMultiset.Builder<String> builder = ImmutableMultiset.builder(); 149 for (String s : elements) { 150 checkArgument(set.add(s)); 151 builder.addCopies(s, 2); 152 } 153 ImmutableSet<String> elementSet = 154 (ImmutableSet<String>) builder.build().elementSet(); 155 return elementSet.asList(); 156 } 157 }) 158 .named("ImmutableMultiset.elementSet.asList") 159 .withFeatures( 160 CollectionSize.ANY, 161 CollectionFeature.REJECTS_DUPLICATES_AT_CREATION, 162 CollectionFeature.SERIALIZABLE, 163 CollectionFeature.ALLOWS_NULL_QUERIES) 164 .createTestSuite()); 165 166 return suite; 167 } 168 testCreation_noArgs()169 public void testCreation_noArgs() { 170 Multiset<String> multiset = ImmutableMultiset.of(); 171 assertTrue(multiset.isEmpty()); 172 } 173 testCreation_oneElement()174 public void testCreation_oneElement() { 175 Multiset<String> multiset = ImmutableMultiset.of("a"); 176 assertEquals(HashMultiset.create(asList("a")), multiset); 177 } 178 testCreation_twoElements()179 public void testCreation_twoElements() { 180 Multiset<String> multiset = ImmutableMultiset.of("a", "b"); 181 assertEquals(HashMultiset.create(asList("a", "b")), multiset); 182 } 183 testCreation_threeElements()184 public void testCreation_threeElements() { 185 Multiset<String> multiset = ImmutableMultiset.of("a", "b", "c"); 186 assertEquals(HashMultiset.create(asList("a", "b", "c")), multiset); 187 } 188 testCreation_fourElements()189 public void testCreation_fourElements() { 190 Multiset<String> multiset = ImmutableMultiset.of("a", "b", "c", "d"); 191 assertEquals(HashMultiset.create(asList("a", "b", "c", "d")), multiset); 192 } 193 testCreation_fiveElements()194 public void testCreation_fiveElements() { 195 Multiset<String> multiset = ImmutableMultiset.of("a", "b", "c", "d", "e"); 196 assertEquals(HashMultiset.create(asList("a", "b", "c", "d", "e")), multiset); 197 } 198 testCreation_sixElements()199 public void testCreation_sixElements() { 200 Multiset<String> multiset = ImmutableMultiset.of("a", "b", "c", "d", "e", "f"); 201 assertEquals(HashMultiset.create(asList("a", "b", "c", "d", "e", "f")), multiset); 202 } 203 testCreation_sevenElements()204 public void testCreation_sevenElements() { 205 Multiset<String> multiset = ImmutableMultiset.of("a", "b", "c", "d", "e", "f", "g"); 206 assertEquals(HashMultiset.create(asList("a", "b", "c", "d", "e", "f", "g")), multiset); 207 } 208 testCreation_emptyArray()209 public void testCreation_emptyArray() { 210 String[] array = new String[0]; 211 Multiset<String> multiset = ImmutableMultiset.copyOf(array); 212 assertTrue(multiset.isEmpty()); 213 } 214 testCreation_arrayOfOneElement()215 public void testCreation_arrayOfOneElement() { 216 String[] array = new String[] {"a"}; 217 Multiset<String> multiset = ImmutableMultiset.copyOf(array); 218 assertEquals(HashMultiset.create(asList("a")), multiset); 219 } 220 testCreation_arrayOfArray()221 public void testCreation_arrayOfArray() { 222 String[] array = new String[] {"a"}; 223 Multiset<String[]> multiset = ImmutableMultiset.<String[]>of(array); 224 Multiset<String[]> expected = HashMultiset.create(); 225 expected.add(array); 226 assertEquals(expected, multiset); 227 } 228 testCreation_arrayContainingOnlyNull()229 public void testCreation_arrayContainingOnlyNull() { 230 String[] array = new String[] {null}; 231 try { 232 ImmutableMultiset.copyOf(array); 233 fail(); 234 } catch (NullPointerException expected) { 235 } 236 } 237 testCopyOf_collection_empty()238 public void testCopyOf_collection_empty() { 239 // "<String>" is required to work around a javac 1.5 bug. 240 Collection<String> c = MinimalCollection.<String>of(); 241 Multiset<String> multiset = ImmutableMultiset.copyOf(c); 242 assertTrue(multiset.isEmpty()); 243 } 244 testCopyOf_collection_oneElement()245 public void testCopyOf_collection_oneElement() { 246 Collection<String> c = MinimalCollection.of("a"); 247 Multiset<String> multiset = ImmutableMultiset.copyOf(c); 248 assertEquals(HashMultiset.create(asList("a")), multiset); 249 } 250 testCopyOf_collection_general()251 public void testCopyOf_collection_general() { 252 Collection<String> c = MinimalCollection.of("a", "b", "a"); 253 Multiset<String> multiset = ImmutableMultiset.copyOf(c); 254 assertEquals(HashMultiset.create(asList("a", "b", "a")), multiset); 255 } 256 testCopyOf_collectionContainingNull()257 public void testCopyOf_collectionContainingNull() { 258 Collection<String> c = MinimalCollection.of("a", null, "b"); 259 try { 260 ImmutableMultiset.copyOf(c); 261 fail(); 262 } catch (NullPointerException expected) { 263 } 264 } 265 testCopyOf_multiset_empty()266 public void testCopyOf_multiset_empty() { 267 Multiset<String> c = HashMultiset.create(); 268 Multiset<String> multiset = ImmutableMultiset.copyOf(c); 269 assertTrue(multiset.isEmpty()); 270 } 271 testCopyOf_multiset_oneElement()272 public void testCopyOf_multiset_oneElement() { 273 Multiset<String> c = HashMultiset.create(asList("a")); 274 Multiset<String> multiset = ImmutableMultiset.copyOf(c); 275 assertEquals(HashMultiset.create(asList("a")), multiset); 276 } 277 testCopyOf_multiset_general()278 public void testCopyOf_multiset_general() { 279 Multiset<String> c = HashMultiset.create(asList("a", "b", "a")); 280 Multiset<String> multiset = ImmutableMultiset.copyOf(c); 281 assertEquals(HashMultiset.create(asList("a", "b", "a")), multiset); 282 } 283 testCopyOf_multisetContainingNull()284 public void testCopyOf_multisetContainingNull() { 285 Multiset<String> c = HashMultiset.create(asList("a", null, "b")); 286 try { 287 ImmutableMultiset.copyOf(c); 288 fail(); 289 } catch (NullPointerException expected) { 290 } 291 } 292 testCopyOf_iterator_empty()293 public void testCopyOf_iterator_empty() { 294 Iterator<String> iterator = Iterators.emptyIterator(); 295 Multiset<String> multiset = ImmutableMultiset.copyOf(iterator); 296 assertTrue(multiset.isEmpty()); 297 } 298 testCopyOf_iterator_oneElement()299 public void testCopyOf_iterator_oneElement() { 300 Iterator<String> iterator = Iterators.singletonIterator("a"); 301 Multiset<String> multiset = ImmutableMultiset.copyOf(iterator); 302 assertEquals(HashMultiset.create(asList("a")), multiset); 303 } 304 testCopyOf_iterator_general()305 public void testCopyOf_iterator_general() { 306 Iterator<String> iterator = asList("a", "b", "a").iterator(); 307 Multiset<String> multiset = ImmutableMultiset.copyOf(iterator); 308 assertEquals(HashMultiset.create(asList("a", "b", "a")), multiset); 309 } 310 testCopyOf_iteratorContainingNull()311 public void testCopyOf_iteratorContainingNull() { 312 Iterator<String> iterator = asList("a", null, "b").iterator(); 313 try { 314 ImmutableMultiset.copyOf(iterator); 315 fail(); 316 } catch (NullPointerException expected) { 317 } 318 } 319 testToImmutableMultiset()320 public void testToImmutableMultiset() { 321 BiPredicate<ImmutableMultiset<String>, ImmutableMultiset<String>> equivalence = 322 (ms1, ms2) -> ms1.equals(ms2) && ms1.entrySet().asList().equals(ms2.entrySet().asList()); 323 CollectorTester.of(ImmutableMultiset.<String>toImmutableMultiset(), equivalence) 324 .expectCollects(ImmutableMultiset.of()) 325 .expectCollects( 326 ImmutableMultiset.of("a", "a", "b", "c", "c", "c"), "a", "a", "b", "c", "c", "c"); 327 } 328 testToImmutableMultisetCountFunction()329 public void testToImmutableMultisetCountFunction() { 330 BiPredicate<ImmutableMultiset<String>, ImmutableMultiset<String>> equivalence = 331 (ms1, ms2) -> ms1.equals(ms2) && ms1.entrySet().asList().equals(ms2.entrySet().asList()); 332 CollectorTester.of( 333 ImmutableMultiset.<Multiset.Entry<String>, String>toImmutableMultiset( 334 Multiset.Entry::getElement, Multiset.Entry::getCount), 335 equivalence) 336 .expectCollects(ImmutableMultiset.of()) 337 .expectCollects( 338 ImmutableMultiset.of("a", "a", "b", "c", "c", "c"), 339 Multisets.immutableEntry("a", 1), 340 Multisets.immutableEntry("b", 1), 341 Multisets.immutableEntry("a", 1), 342 Multisets.immutableEntry("c", 3)); 343 } 344 testToImmutableMultiset_duplicates()345 public void testToImmutableMultiset_duplicates() { 346 class TypeWithDuplicates { 347 final int a; 348 final int b; 349 350 TypeWithDuplicates(int a, int b) { 351 this.a = a; 352 this.b = b; 353 } 354 355 @Override 356 public int hashCode() { 357 return a; 358 } 359 360 @Override 361 public boolean equals(Object obj) { 362 return obj instanceof TypeWithDuplicates && ((TypeWithDuplicates) obj).a == a; 363 } 364 365 public boolean fullEquals(TypeWithDuplicates other) { 366 return other != null && a == other.a && b == other.b; 367 } 368 } 369 370 Collector<TypeWithDuplicates, ?, ImmutableMultiset<TypeWithDuplicates>> collector = 371 ImmutableMultiset.toImmutableMultiset(); 372 BiPredicate<ImmutableMultiset<TypeWithDuplicates>, ImmutableMultiset<TypeWithDuplicates>> 373 equivalence = 374 (ms1, ms2) -> { 375 if (!ms1.equals(ms2)) { 376 return false; 377 } 378 List<TypeWithDuplicates> elements1 = ImmutableList.copyOf(ms1.elementSet()); 379 List<TypeWithDuplicates> elements2 = ImmutableList.copyOf(ms2.elementSet()); 380 for (int i = 0; i < ms1.elementSet().size(); i++) { 381 if (!elements1.get(i).fullEquals(elements2.get(i))) { 382 return false; 383 } 384 } 385 return true; 386 }; 387 TypeWithDuplicates a = new TypeWithDuplicates(1, 1); 388 TypeWithDuplicates b1 = new TypeWithDuplicates(2, 1); 389 TypeWithDuplicates b2 = new TypeWithDuplicates(2, 2); 390 TypeWithDuplicates c = new TypeWithDuplicates(3, 1); 391 CollectorTester.of(collector, equivalence) 392 .expectCollects( 393 ImmutableMultiset.<TypeWithDuplicates>builder().add(a).addCopies(b1, 2).add(c).build(), 394 a, 395 b1, 396 c, 397 b2); 398 collector = ImmutableMultiset.toImmutableMultiset(e -> e, e -> 1); 399 CollectorTester.of(collector, equivalence) 400 .expectCollects( 401 ImmutableMultiset.<TypeWithDuplicates>builder().add(a).addCopies(b1, 2).add(c).build(), 402 a, 403 b1, 404 c, 405 b2); 406 } 407 408 private static class CountingIterable implements Iterable<String> { 409 int count = 0; 410 411 @Override iterator()412 public Iterator<String> iterator() { 413 count++; 414 return asList("a", "b", "a").iterator(); 415 } 416 } 417 testCopyOf_plainIterable()418 public void testCopyOf_plainIterable() { 419 CountingIterable iterable = new CountingIterable(); 420 Multiset<String> multiset = ImmutableMultiset.copyOf(iterable); 421 assertEquals(HashMultiset.create(asList("a", "b", "a")), multiset); 422 assertEquals(1, iterable.count); 423 } 424 testCopyOf_hashMultiset()425 public void testCopyOf_hashMultiset() { 426 Multiset<String> iterable = HashMultiset.create(asList("a", "b", "a")); 427 Multiset<String> multiset = ImmutableMultiset.copyOf(iterable); 428 assertEquals(HashMultiset.create(asList("a", "b", "a")), multiset); 429 } 430 testCopyOf_treeMultiset()431 public void testCopyOf_treeMultiset() { 432 Multiset<String> iterable = TreeMultiset.create(asList("a", "b", "a")); 433 Multiset<String> multiset = ImmutableMultiset.copyOf(iterable); 434 assertEquals(HashMultiset.create(asList("a", "b", "a")), multiset); 435 } 436 testCopyOf_shortcut_empty()437 public void testCopyOf_shortcut_empty() { 438 Collection<String> c = ImmutableMultiset.of(); 439 assertSame(c, ImmutableMultiset.copyOf(c)); 440 } 441 testCopyOf_shortcut_singleton()442 public void testCopyOf_shortcut_singleton() { 443 Collection<String> c = ImmutableMultiset.of("a"); 444 assertSame(c, ImmutableMultiset.copyOf(c)); 445 } 446 testCopyOf_shortcut_immutableMultiset()447 public void testCopyOf_shortcut_immutableMultiset() { 448 Collection<String> c = ImmutableMultiset.of("a", "b", "c"); 449 assertSame(c, ImmutableMultiset.copyOf(c)); 450 } 451 testBuilderAdd()452 public void testBuilderAdd() { 453 ImmutableMultiset<String> multiset = 454 new ImmutableMultiset.Builder<String>().add("a").add("b").add("a").add("c").build(); 455 assertEquals(HashMultiset.create(asList("a", "b", "a", "c")), multiset); 456 } 457 testBuilderAddAll()458 public void testBuilderAddAll() { 459 List<String> a = asList("a", "b"); 460 List<String> b = asList("c", "d"); 461 ImmutableMultiset<String> multiset = 462 new ImmutableMultiset.Builder<String>().addAll(a).addAll(b).build(); 463 assertEquals(HashMultiset.create(asList("a", "b", "c", "d")), multiset); 464 } 465 testBuilderAddAllHashMultiset()466 public void testBuilderAddAllHashMultiset() { 467 Multiset<String> a = HashMultiset.create(asList("a", "b", "b")); 468 Multiset<String> b = HashMultiset.create(asList("c", "b")); 469 ImmutableMultiset<String> multiset = 470 new ImmutableMultiset.Builder<String>().addAll(a).addAll(b).build(); 471 assertEquals(HashMultiset.create(asList("a", "b", "b", "b", "c")), multiset); 472 } 473 testBuilderAddAllImmutableMultiset()474 public void testBuilderAddAllImmutableMultiset() { 475 Multiset<String> a = ImmutableMultiset.of("a", "b", "b"); 476 Multiset<String> b = ImmutableMultiset.of("c", "b"); 477 ImmutableMultiset<String> multiset = 478 new ImmutableMultiset.Builder<String>().addAll(a).addAll(b).build(); 479 assertEquals(HashMultiset.create(asList("a", "b", "b", "b", "c")), multiset); 480 } 481 testBuilderAddAllTreeMultiset()482 public void testBuilderAddAllTreeMultiset() { 483 Multiset<String> a = TreeMultiset.create(asList("a", "b", "b")); 484 Multiset<String> b = TreeMultiset.create(asList("c", "b")); 485 ImmutableMultiset<String> multiset = 486 new ImmutableMultiset.Builder<String>().addAll(a).addAll(b).build(); 487 assertEquals(HashMultiset.create(asList("a", "b", "b", "b", "c")), multiset); 488 } 489 testBuilderAddAllIterator()490 public void testBuilderAddAllIterator() { 491 Iterator<String> iterator = asList("a", "b", "a", "c").iterator(); 492 ImmutableMultiset<String> multiset = 493 new ImmutableMultiset.Builder<String>().addAll(iterator).build(); 494 assertEquals(HashMultiset.create(asList("a", "b", "a", "c")), multiset); 495 } 496 testBuilderAddCopies()497 public void testBuilderAddCopies() { 498 ImmutableMultiset<String> multiset = 499 new ImmutableMultiset.Builder<String>() 500 .addCopies("a", 2) 501 .addCopies("b", 3) 502 .addCopies("c", 0) 503 .build(); 504 assertEquals(HashMultiset.create(asList("a", "a", "b", "b", "b")), multiset); 505 } 506 testBuilderSetCount()507 public void testBuilderSetCount() { 508 ImmutableMultiset<String> multiset = 509 new ImmutableMultiset.Builder<String>().add("a").setCount("a", 2).setCount("b", 3).build(); 510 assertEquals(HashMultiset.create(asList("a", "a", "b", "b", "b")), multiset); 511 } 512 testBuilderAddHandlesNullsCorrectly()513 public void testBuilderAddHandlesNullsCorrectly() { 514 ImmutableMultiset.Builder<String> builder = ImmutableMultiset.builder(); 515 try { 516 builder.add((String) null); 517 fail("expected NullPointerException"); 518 } catch (NullPointerException expected) { 519 } 520 } 521 testBuilderAddAllHandlesNullsCorrectly()522 public void testBuilderAddAllHandlesNullsCorrectly() { 523 ImmutableMultiset.Builder<String> builder = ImmutableMultiset.builder(); 524 try { 525 builder.addAll((Collection<String>) null); 526 fail("expected NullPointerException"); 527 } catch (NullPointerException expected) { 528 } 529 530 builder = ImmutableMultiset.builder(); 531 List<String> listWithNulls = asList("a", null, "b"); 532 try { 533 builder.addAll(listWithNulls); 534 fail("expected NullPointerException"); 535 } catch (NullPointerException expected) { 536 } 537 538 builder = ImmutableMultiset.builder(); 539 Multiset<String> multisetWithNull = LinkedHashMultiset.create(asList("a", null, "b")); 540 try { 541 builder.addAll(multisetWithNull); 542 fail("expected NullPointerException"); 543 } catch (NullPointerException expected) { 544 } 545 } 546 testBuilderAddCopiesHandlesNullsCorrectly()547 public void testBuilderAddCopiesHandlesNullsCorrectly() { 548 ImmutableMultiset.Builder<String> builder = ImmutableMultiset.builder(); 549 try { 550 builder.addCopies(null, 2); 551 fail("expected NullPointerException"); 552 } catch (NullPointerException expected) { 553 } 554 } 555 testBuilderAddCopiesIllegal()556 public void testBuilderAddCopiesIllegal() { 557 ImmutableMultiset.Builder<String> builder = ImmutableMultiset.builder(); 558 try { 559 builder.addCopies("a", -2); 560 fail("expected IllegalArgumentException"); 561 } catch (IllegalArgumentException expected) { 562 } 563 } 564 testBuilderSetCountHandlesNullsCorrectly()565 public void testBuilderSetCountHandlesNullsCorrectly() { 566 ImmutableMultiset.Builder<String> builder = ImmutableMultiset.builder(); 567 try { 568 builder.setCount(null, 2); 569 fail("expected NullPointerException"); 570 } catch (NullPointerException expected) { 571 } 572 } 573 testBuilderSetCountIllegal()574 public void testBuilderSetCountIllegal() { 575 ImmutableMultiset.Builder<String> builder = ImmutableMultiset.builder(); 576 try { 577 builder.setCount("a", -2); 578 fail("expected IllegalArgumentException"); 579 } catch (IllegalArgumentException expected) { 580 } 581 } 582 583 @GwtIncompatible // NullPointerTester testNullPointers()584 public void testNullPointers() { 585 NullPointerTester tester = new NullPointerTester(); 586 tester.testAllPublicStaticMethods(ImmutableMultiset.class); 587 } 588 589 @GwtIncompatible // SerializableTester testSerialization_empty()590 public void testSerialization_empty() { 591 Collection<String> c = ImmutableMultiset.of(); 592 assertSame(c, SerializableTester.reserialize(c)); 593 } 594 595 @GwtIncompatible // SerializableTester testSerialization_multiple()596 public void testSerialization_multiple() { 597 Collection<String> c = ImmutableMultiset.of("a", "b", "a"); 598 Collection<String> copy = SerializableTester.reserializeAndAssert(c); 599 assertThat(copy).containsExactly("a", "a", "b").inOrder(); 600 } 601 602 @GwtIncompatible // SerializableTester testSerialization_elementSet()603 public void testSerialization_elementSet() { 604 Multiset<String> c = ImmutableMultiset.of("a", "b", "a"); 605 Collection<String> copy = LenientSerializableTester.reserializeAndAssertLenient(c.elementSet()); 606 assertThat(copy).containsExactly("a", "b").inOrder(); 607 } 608 609 @GwtIncompatible // SerializableTester testSerialization_entrySet()610 public void testSerialization_entrySet() { 611 Multiset<String> c = ImmutableMultiset.of("a", "b", "c"); 612 SerializableTester.reserializeAndAssert(c.entrySet()); 613 } 614 testEquals_immutableMultiset()615 public void testEquals_immutableMultiset() { 616 Collection<String> c = ImmutableMultiset.of("a", "b", "a"); 617 assertEquals(c, ImmutableMultiset.of("a", "b", "a")); 618 assertEquals(c, ImmutableMultiset.of("a", "a", "b")); 619 assertThat(c).isNotEqualTo(ImmutableMultiset.of("a", "b")); 620 assertThat(c).isNotEqualTo(ImmutableMultiset.of("a", "b", "c", "d")); 621 } 622 testIterationOrder()623 public void testIterationOrder() { 624 Collection<String> c = ImmutableMultiset.of("a", "b", "a"); 625 assertThat(c).containsExactly("a", "a", "b").inOrder(); 626 assertThat(ImmutableMultiset.of("c", "b", "a", "c").elementSet()) 627 .containsExactly("c", "b", "a") 628 .inOrder(); 629 } 630 testMultisetWrites()631 public void testMultisetWrites() { 632 Multiset<String> multiset = ImmutableMultiset.of("a", "b", "a"); 633 UnmodifiableCollectionTests.assertMultisetIsUnmodifiable(multiset, "test"); 634 } 635 testAsList()636 public void testAsList() { 637 ImmutableMultiset<String> multiset = ImmutableMultiset.of("a", "a", "b", "b", "b"); 638 ImmutableList<String> list = multiset.asList(); 639 assertEquals(ImmutableList.of("a", "a", "b", "b", "b"), list); 640 assertEquals(2, list.indexOf("b")); 641 assertEquals(4, list.lastIndexOf("b")); 642 } 643 644 @GwtIncompatible // SerializableTester testSerialization_asList()645 public void testSerialization_asList() { 646 ImmutableMultiset<String> multiset = ImmutableMultiset.of("a", "a", "b", "b", "b"); 647 SerializableTester.reserializeAndAssert(multiset.asList()); 648 } 649 testEquals()650 public void testEquals() { 651 new EqualsTester() 652 .addEqualityGroup(ImmutableMultiset.of(), ImmutableMultiset.of()) 653 .addEqualityGroup(ImmutableMultiset.of(1), ImmutableMultiset.of(1)) 654 .addEqualityGroup(ImmutableMultiset.of(1, 1), ImmutableMultiset.of(1, 1)) 655 .addEqualityGroup(ImmutableMultiset.of(1, 2, 1), ImmutableMultiset.of(2, 1, 1)) 656 .testEquals(); 657 } 658 testIterationOrderThroughBuilderRemovals()659 public void testIterationOrderThroughBuilderRemovals() { 660 ImmutableMultiset.Builder<String> builder = ImmutableMultiset.builder(); 661 builder.addCopies("a", 2); 662 builder.add("b"); 663 builder.add("c"); 664 builder.setCount("b", 0); 665 ImmutableMultiset<String> multiset = builder.build(); 666 assertThat(multiset.elementSet()).containsExactly("a", "c").inOrder(); 667 builder.add("b"); 668 assertThat(builder.build().elementSet()).containsExactly("a", "c", "b").inOrder(); 669 assertThat(multiset.elementSet()).containsExactly("a", "c").inOrder(); 670 } 671 672 /** 673 * A Comparable wrapper around a String which executes callbacks on calls to hashCode, equals, and 674 * compareTo. 675 */ 676 private static class CountsHashCodeAndEquals implements Comparable<CountsHashCodeAndEquals> { 677 private final String delegateString; 678 private final Runnable onHashCode; 679 private final Runnable onEquals; 680 private final Runnable onCompareTo; 681 CountsHashCodeAndEquals( String delegateString, Runnable onHashCode, Runnable onEquals, Runnable onCompareTo)682 CountsHashCodeAndEquals( 683 String delegateString, Runnable onHashCode, Runnable onEquals, Runnable onCompareTo) { 684 this.delegateString = delegateString; 685 this.onHashCode = onHashCode; 686 this.onEquals = onEquals; 687 this.onCompareTo = onCompareTo; 688 } 689 690 @Override hashCode()691 public int hashCode() { 692 onHashCode.run(); 693 return delegateString.hashCode(); 694 } 695 696 @Override equals(@ullable Object other)697 public boolean equals(@Nullable Object other) { 698 onEquals.run(); 699 return other instanceof CountsHashCodeAndEquals 700 && delegateString.equals(((CountsHashCodeAndEquals) other).delegateString); 701 } 702 703 @Override compareTo(CountsHashCodeAndEquals o)704 public int compareTo(CountsHashCodeAndEquals o) { 705 onCompareTo.run(); 706 return delegateString.compareTo(o.delegateString); 707 } 708 } 709 710 /** A holder of counters for calls to hashCode, equals, and compareTo. */ 711 private static final class CallsCounter { 712 long hashCode; 713 long equals; 714 long compareTo; 715 total()716 long total() { 717 return hashCode + equals + compareTo; 718 } 719 zero()720 void zero() { 721 hashCode = 0; 722 equals = 0; 723 compareTo = 0; 724 } 725 } 726 727 /** All the ways to create an ImmutableMultiset. */ 728 enum ConstructionPathway { 729 COPY_OF_COLLECTION { 730 @Override create(List<?> keys)731 ImmutableMultiset<?> create(List<?> keys) { 732 return ImmutableMultiset.copyOf(keys); 733 } 734 }, 735 COPY_OF_ITERATOR { 736 @Override create(List<?> keys)737 ImmutableMultiset<?> create(List<?> keys) { 738 return ImmutableMultiset.copyOf(keys.iterator()); 739 } 740 }, 741 BUILDER_ADD_ENTRY_BY_ENTRY { 742 @Override create(List<?> keys)743 ImmutableMultiset<?> create(List<?> keys) { 744 ImmutableMultiset.Builder<Object> builder = ImmutableMultiset.builder(); 745 for (Object o : keys) { 746 builder.add(o); 747 } 748 return builder.build(); 749 } 750 }, 751 BUILDER_ADD_ALL_COLLECTION { 752 @Override create(List<?> keys)753 ImmutableMultiset<?> create(List<?> keys) { 754 ImmutableMultiset.Builder<Object> builder = ImmutableMultiset.builder(); 755 builder.addAll(keys); 756 return builder.build(); 757 } 758 }; 759 760 @CanIgnoreReturnValue create(List<?> keys)761 abstract ImmutableMultiset<?> create(List<?> keys); 762 } 763 764 /** 765 * Returns a list of objects with the same hash code, of size 2^power, counting calls to equals, 766 * hashCode, and compareTo in counter. 767 */ createAdversarialInput(int power, CallsCounter counter)768 static List<CountsHashCodeAndEquals> createAdversarialInput(int power, CallsCounter counter) { 769 String str1 = "Aa"; 770 String str2 = "BB"; 771 assertEquals(str1.hashCode(), str2.hashCode()); 772 List<String> haveSameHashes2 = Arrays.asList(str1, str2); 773 List<CountsHashCodeAndEquals> result = 774 Lists.newArrayList( 775 Lists.transform( 776 Lists.cartesianProduct(Collections.nCopies(power, haveSameHashes2)), 777 strs -> 778 new CountsHashCodeAndEquals( 779 String.join("", strs), 780 () -> counter.hashCode++, 781 () -> counter.equals++, 782 () -> counter.compareTo++))); 783 assertEquals( 784 result.get(0).delegateString.hashCode(), 785 result.get(result.size() - 1).delegateString.hashCode()); 786 return result; 787 } 788 789 @GwtIncompatible testResistsHashFloodingInConstruction()790 public void testResistsHashFloodingInConstruction() { 791 CallsCounter smallCounter = new CallsCounter(); 792 List<CountsHashCodeAndEquals> haveSameHashesSmall = createAdversarialInput(10, smallCounter); 793 int smallSize = haveSameHashesSmall.size(); 794 795 CallsCounter largeCounter = new CallsCounter(); 796 List<CountsHashCodeAndEquals> haveSameHashesLarge = createAdversarialInput(15, largeCounter); 797 int largeSize = haveSameHashesLarge.size(); 798 799 for (ConstructionPathway pathway : ConstructionPathway.values()) { 800 smallCounter.zero(); 801 pathway.create(haveSameHashesSmall); 802 long smallOps = smallCounter.total(); 803 804 largeCounter.zero(); 805 pathway.create(haveSameHashesLarge); 806 long largeOps = largeCounter.total(); 807 808 double ratio = (double) largeOps / smallOps; 809 assertThat(ratio) 810 .named( 811 "ratio of equals/hashCode/compareTo operations to build an ImmutableMultiset via %s" 812 + " with %s entries versus %s entries", 813 pathway, largeSize, smallSize) 814 .isAtMost(2 * (largeSize * Math.log(largeSize)) / (smallSize * Math.log(smallSize))); 815 // allow up to 2x wobble in the constant factors 816 } 817 } 818 819 @GwtIncompatible testResistsHashFloodingOnCount()820 public void testResistsHashFloodingOnCount() { 821 CallsCounter smallCounter = new CallsCounter(); 822 List<CountsHashCodeAndEquals> haveSameHashesSmall = createAdversarialInput(10, smallCounter); 823 int smallSize = haveSameHashesSmall.size(); 824 ImmutableMultiset<?> smallMap = 825 ConstructionPathway.COPY_OF_COLLECTION.create(haveSameHashesSmall); 826 long worstCaseQuerySmall = worstCaseQueryOperations(smallMap, smallCounter); 827 828 CallsCounter largeCounter = new CallsCounter(); 829 List<CountsHashCodeAndEquals> haveSameHashesLarge = createAdversarialInput(15, largeCounter); 830 int largeSize = haveSameHashesLarge.size(); 831 ImmutableMultiset<?> largeMap = 832 ConstructionPathway.COPY_OF_COLLECTION.create(haveSameHashesLarge); 833 long worstCaseQueryLarge = worstCaseQueryOperations(largeMap, largeCounter); 834 835 double ratio = (double) worstCaseQueryLarge / worstCaseQuerySmall; 836 assertThat(ratio) 837 .named( 838 "Ratio of worst case query operations for an ImmutableMultiset of size %s versus %s", 839 largeSize, smallSize) 840 .isAtMost(2 * Math.log(largeSize) / Math.log(smallSize)); 841 // allow up to 2x wobble in the constant factors 842 } 843 worstCaseQueryOperations(Multiset<?> multiset, CallsCounter counter)844 private static long worstCaseQueryOperations(Multiset<?> multiset, CallsCounter counter) { 845 long worstCalls = 0; 846 for (Object k : multiset.elementSet()) { 847 counter.zero(); 848 int unused = multiset.count(k); 849 worstCalls = Math.max(worstCalls, counter.total()); 850 } 851 return worstCalls; 852 } 853 } 854