• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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