ImmutableMapTest.java revision 1d580d0f6ee4f21eb309ba7b509d2c6d671c4044
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
17package com.google.common.collect;
18
19import static com.google.common.testing.SerializableTester.reserialize;
20
21import com.google.common.annotations.GwtCompatible;
22import com.google.common.annotations.GwtIncompatible;
23import com.google.common.base.Joiner;
24import com.google.common.collect.ImmutableMap.Builder;
25import com.google.common.collect.testing.CollectionTestSuiteBuilder;
26import com.google.common.collect.testing.ListTestSuiteBuilder;
27import com.google.common.collect.testing.MapInterfaceTest;
28import com.google.common.collect.testing.MinimalSet;
29import com.google.common.collect.testing.ReserializingTestCollectionGenerator;
30import com.google.common.collect.testing.ReserializingTestSetGenerator;
31import com.google.common.collect.testing.SampleElements.Colliders;
32import com.google.common.collect.testing.SampleElements.Unhashables;
33import com.google.common.collect.testing.SetTestSuiteBuilder;
34import com.google.common.collect.testing.UnhashableObject;
35import com.google.common.collect.testing.features.CollectionFeature;
36import com.google.common.collect.testing.features.CollectionSize;
37import com.google.common.collect.testing.google.MapGenerators.ImmutableMapEntrySetGenerator;
38import com.google.common.collect.testing.google.MapGenerators.ImmutableMapKeySetGenerator;
39import com.google.common.collect.testing.google.MapGenerators.ImmutableMapUnhashableValuesGenerator;
40import com.google.common.collect.testing.google.MapGenerators.ImmutableMapValueListGenerator;
41import com.google.common.collect.testing.google.MapGenerators.ImmutableMapValuesGenerator;
42import com.google.common.testing.NullPointerTester;
43import com.google.common.testing.SerializableTester;
44
45import junit.framework.Test;
46import junit.framework.TestCase;
47import junit.framework.TestSuite;
48
49import java.io.Serializable;
50import java.util.Collection;
51import java.util.Collections;
52import java.util.LinkedHashMap;
53import java.util.Map;
54import java.util.Map.Entry;
55
56/**
57 * Tests for {@link ImmutableMap}.
58 *
59 * @author Kevin Bourrillion
60 * @author Jesse Wilson
61 */
62@GwtCompatible(emulated = true)
63public class ImmutableMapTest extends TestCase {
64
65  @GwtIncompatible("suite")
66  public static Test suite() {
67    TestSuite suite = new TestSuite();
68    suite.addTestSuite(ImmutableMapTest.class);
69
70    suite.addTest(SetTestSuiteBuilder.using(new ImmutableMapKeySetGenerator())
71        .withFeatures(
72            CollectionSize.ANY,
73            CollectionFeature.KNOWN_ORDER,
74            CollectionFeature.REJECTS_DUPLICATES_AT_CREATION,
75            CollectionFeature.ALLOWS_NULL_QUERIES)
76        .named("ImmutableMap.keySet")
77        .createTestSuite());
78
79    suite.addTest(SetTestSuiteBuilder.using(new ImmutableMapEntrySetGenerator())
80        .withFeatures(
81            CollectionSize.ANY,
82            CollectionFeature.KNOWN_ORDER,
83            CollectionFeature.REJECTS_DUPLICATES_AT_CREATION,
84            CollectionFeature.ALLOWS_NULL_QUERIES)
85        .named("ImmutableMap.entrySet")
86        .createTestSuite());
87
88    suite.addTest(CollectionTestSuiteBuilder.using(
89        new ImmutableMapValuesGenerator())
90        .withFeatures(CollectionSize.ANY, CollectionFeature.KNOWN_ORDER,
91            CollectionFeature.ALLOWS_NULL_QUERIES)
92        .named("ImmutableMap.values")
93        .createTestSuite());
94
95    suite.addTest(SetTestSuiteBuilder.using(
96        ReserializingTestSetGenerator.newInstance(
97            new ImmutableMapKeySetGenerator()))
98        .withFeatures(
99            CollectionSize.ANY,
100            CollectionFeature.KNOWN_ORDER,
101            CollectionFeature.REJECTS_DUPLICATES_AT_CREATION,
102            CollectionFeature.ALLOWS_NULL_QUERIES)
103        .named("ImmutableMap.keySet, reserialized")
104        .createTestSuite());
105
106    suite.addTest(SetTestSuiteBuilder.using(
107        ReserializingTestSetGenerator.newInstance(
108            new ImmutableMapKeySetGenerator()))
109        .withFeatures(
110            CollectionSize.ANY,
111            CollectionFeature.KNOWN_ORDER,
112            CollectionFeature.REJECTS_DUPLICATES_AT_CREATION,
113            CollectionFeature.ALLOWS_NULL_QUERIES)
114        .named("ImmutableMap.entrySet, reserialized")
115        .createTestSuite());
116
117    suite.addTest(CollectionTestSuiteBuilder.using(
118        ReserializingTestCollectionGenerator.newInstance(
119            new ImmutableMapValuesGenerator()))
120        .withFeatures(CollectionSize.ANY, CollectionFeature.KNOWN_ORDER,
121            CollectionFeature.ALLOWS_NULL_QUERIES)
122        .named("ImmutableMap.values, reserialized")
123        .createTestSuite());
124
125    suite.addTest(CollectionTestSuiteBuilder.using(
126            new ImmutableMapUnhashableValuesGenerator())
127        .withFeatures(CollectionSize.ANY, CollectionFeature.KNOWN_ORDER,
128            CollectionFeature.ALLOWS_NULL_QUERIES)
129        .named("ImmutableMap.values, unhashable")
130        .createTestSuite());
131
132    suite.addTest(ListTestSuiteBuilder.using(
133        new ImmutableMapValueListGenerator())
134        .named("ImmutableMap.values.asList")
135        .withFeatures(CollectionSize.ANY,
136            CollectionFeature.ALLOWS_NULL_QUERIES)
137        .createTestSuite());
138
139    return suite;
140  }
141
142  public abstract static class AbstractMapTests<K, V>
143      extends MapInterfaceTest<K, V> {
144    public AbstractMapTests() {
145      super(false, false, false, false, false);
146    }
147
148    @Override protected Map<K, V> makeEmptyMap() {
149      throw new UnsupportedOperationException();
150    }
151
152    private static final Joiner joiner = Joiner.on(", ");
153
154    @Override protected void assertMoreInvariants(Map<K, V> map) {
155      // TODO: can these be moved to MapInterfaceTest?
156      for (Entry<K, V> entry : map.entrySet()) {
157        assertEquals(entry.getKey() + "=" + entry.getValue(),
158            entry.toString());
159      }
160
161      assertEquals("{" + joiner.join(map.entrySet()) + "}",
162          map.toString());
163      assertEquals("[" + joiner.join(map.entrySet()) + "]",
164          map.entrySet().toString());
165      assertEquals("[" + joiner.join(map.keySet()) + "]",
166          map.keySet().toString());
167      assertEquals("[" + joiner.join(map.values()) + "]",
168          map.values().toString());
169
170      assertEquals(MinimalSet.from(map.entrySet()), map.entrySet());
171      assertEquals(Sets.newHashSet(map.keySet()), map.keySet());
172    }
173  }
174
175  public static class MapTests extends AbstractMapTests<String, Integer> {
176    @Override protected Map<String, Integer> makeEmptyMap() {
177      return ImmutableMap.of();
178    }
179
180    @Override protected Map<String, Integer> makePopulatedMap() {
181      return ImmutableMap.of("one", 1, "two", 2, "three", 3);
182    }
183
184    @Override protected String getKeyNotInPopulatedMap() {
185      return "minus one";
186    }
187
188    @Override protected Integer getValueNotInPopulatedMap() {
189      return -1;
190    }
191  }
192
193  public static class SingletonMapTests
194      extends AbstractMapTests<String, Integer> {
195    @Override protected Map<String, Integer> makePopulatedMap() {
196      return ImmutableMap.of("one", 1);
197    }
198
199    @Override protected String getKeyNotInPopulatedMap() {
200      return "minus one";
201    }
202
203    @Override protected Integer getValueNotInPopulatedMap() {
204      return -1;
205    }
206  }
207
208  @GwtIncompatible("SerializableTester")
209  public static class ReserializedMapTests
210      extends AbstractMapTests<String, Integer> {
211    @Override protected Map<String, Integer> makePopulatedMap() {
212      return SerializableTester.reserialize(
213          ImmutableMap.of("one", 1, "two", 2, "three", 3));
214    }
215
216    @Override protected String getKeyNotInPopulatedMap() {
217      return "minus one";
218    }
219
220    @Override protected Integer getValueNotInPopulatedMap() {
221      return -1;
222    }
223  }
224
225  public static class MapTestsWithBadHashes
226      extends AbstractMapTests<Object, Integer> {
227
228    @Override protected Map<Object, Integer> makeEmptyMap() {
229      throw new UnsupportedOperationException();
230    }
231
232    @Override protected Map<Object, Integer> makePopulatedMap() {
233      Colliders colliders = new Colliders();
234      return ImmutableMap.of(
235          colliders.e0, 0,
236          colliders.e1, 1,
237          colliders.e2, 2,
238          colliders.e3, 3);
239    }
240
241    @Override protected Object getKeyNotInPopulatedMap() {
242      return new Colliders().e4;
243    }
244
245    @Override protected Integer getValueNotInPopulatedMap() {
246      return 4;
247    }
248  }
249
250  @GwtIncompatible("GWT's ImmutableMap emulation is backed by java.util.HashMap.")
251  public static class MapTestsWithUnhashableValues
252      extends AbstractMapTests<Integer, UnhashableObject> {
253    @Override protected Map<Integer, UnhashableObject> makeEmptyMap() {
254      return ImmutableMap.of();
255    }
256
257    @Override protected Map<Integer, UnhashableObject> makePopulatedMap() {
258      Unhashables unhashables = new Unhashables();
259      return ImmutableMap.of(
260          0, unhashables.e0, 1, unhashables.e1, 2, unhashables.e2);
261    }
262
263    @Override protected Integer getKeyNotInPopulatedMap() {
264      return 3;
265    }
266
267    @Override protected UnhashableObject getValueNotInPopulatedMap() {
268      return new Unhashables().e3;
269    }
270  }
271
272  @GwtIncompatible("GWT's ImmutableMap emulation is backed by java.util.HashMap.")
273  public static class MapTestsWithSingletonUnhashableValue
274      extends MapTestsWithUnhashableValues {
275    @Override protected Map<Integer, UnhashableObject> makePopulatedMap() {
276      Unhashables unhashables = new Unhashables();
277      return ImmutableMap.of(0, unhashables.e0);
278    }
279  }
280
281  public static class CreationTests extends TestCase {
282    public void testEmptyBuilder() {
283      ImmutableMap<String, Integer> map
284          = new Builder<String, Integer>().build();
285      assertEquals(Collections.<String, Integer>emptyMap(), map);
286    }
287
288    public void testSingletonBuilder() {
289      ImmutableMap<String, Integer> map = new Builder<String, Integer>()
290          .put("one", 1)
291          .build();
292      assertMapEquals(map, "one", 1);
293    }
294
295    public void testBuilder() {
296      ImmutableMap<String, Integer> map = new Builder<String, Integer>()
297          .put("one", 1)
298          .put("two", 2)
299          .put("three", 3)
300          .put("four", 4)
301          .put("five", 5)
302          .build();
303      assertMapEquals(map,
304          "one", 1, "two", 2, "three", 3, "four", 4, "five", 5);
305    }
306
307    public void testBuilder_withImmutableEntry() {
308      ImmutableMap<String, Integer> map = new Builder<String, Integer>()
309          .put(Maps.immutableEntry("one", 1))
310          .build();
311      assertMapEquals(map, "one", 1);
312    }
313
314    public void testBuilder_withImmutableEntryAndNullContents() {
315      Builder<String, Integer> builder = new Builder<String, Integer>();
316      try {
317        builder.put(Maps.immutableEntry("one", (Integer) null));
318        fail();
319      } catch (NullPointerException expected) {
320      }
321      try {
322        builder.put(Maps.immutableEntry((String) null, 1));
323        fail();
324      } catch (NullPointerException expected) {
325      }
326    }
327
328    private static class StringHolder {
329      String string;
330    }
331
332    public void testBuilder_withMutableEntry() {
333      ImmutableMap.Builder<String, Integer> builder =
334          new Builder<String, Integer>();
335      final StringHolder holder = new StringHolder();
336      holder.string = "one";
337      Entry<String, Integer> entry = new AbstractMapEntry<String, Integer>() {
338        @Override public String getKey() {
339          return holder.string;
340        }
341        @Override public Integer getValue() {
342          return 1;
343        }
344      };
345
346      builder.put(entry);
347      holder.string = "two";
348      assertMapEquals(builder.build(), "one", 1);
349    }
350
351    public void testBuilderPutAllWithEmptyMap() {
352      ImmutableMap<String, Integer> map = new Builder<String, Integer>()
353          .putAll(Collections.<String, Integer>emptyMap())
354          .build();
355      assertEquals(Collections.<String, Integer>emptyMap(), map);
356    }
357
358    public void testBuilderPutAll() {
359      Map<String, Integer> toPut = new LinkedHashMap<String, Integer>();
360      toPut.put("one", 1);
361      toPut.put("two", 2);
362      toPut.put("three", 3);
363      Map<String, Integer> moreToPut = new LinkedHashMap<String, Integer>();
364      moreToPut.put("four", 4);
365      moreToPut.put("five", 5);
366
367      ImmutableMap<String, Integer> map = new Builder<String, Integer>()
368          .putAll(toPut)
369          .putAll(moreToPut)
370          .build();
371      assertMapEquals(map,
372          "one", 1, "two", 2, "three", 3, "four", 4, "five", 5);
373    }
374
375    public void testBuilderReuse() {
376      Builder<String, Integer> builder = new Builder<String, Integer>();
377      ImmutableMap<String, Integer> mapOne = builder
378          .put("one", 1)
379          .put("two", 2)
380          .build();
381      ImmutableMap<String, Integer> mapTwo = builder
382          .put("three", 3)
383          .put("four", 4)
384          .build();
385
386      assertMapEquals(mapOne, "one", 1, "two", 2);
387      assertMapEquals(mapTwo, "one", 1, "two", 2, "three", 3, "four", 4);
388    }
389
390    public void testBuilderPutNullKey() {
391      Builder<String, Integer> builder = new Builder<String, Integer>();
392      try {
393        builder.put(null, 1);
394        fail();
395      } catch (NullPointerException expected) {
396      }
397    }
398
399    public void testBuilderPutNullValue() {
400      Builder<String, Integer> builder = new Builder<String, Integer>();
401      try {
402        builder.put("one", null);
403        fail();
404      } catch (NullPointerException expected) {
405      }
406    }
407
408    public void testBuilderPutNullKeyViaPutAll() {
409      Builder<String, Integer> builder = new Builder<String, Integer>();
410      try {
411        builder.putAll(Collections.<String, Integer>singletonMap(null, 1));
412        fail();
413      } catch (NullPointerException expected) {
414      }
415    }
416
417    public void testBuilderPutNullValueViaPutAll() {
418      Builder<String, Integer> builder = new Builder<String, Integer>();
419      try {
420        builder.putAll(Collections.<String, Integer>singletonMap("one", null));
421        fail();
422      } catch (NullPointerException expected) {
423      }
424    }
425
426    public void testPuttingTheSameKeyTwiceThrowsOnBuild() {
427      Builder<String, Integer> builder = new Builder<String, Integer>()
428          .put("one", 1)
429          .put("one", 1); // throwing on this line would be even better
430
431      try {
432        builder.build();
433        fail();
434      } catch (IllegalArgumentException expected) {
435        assertEquals("duplicate key: one", expected.getMessage());
436      }
437    }
438
439    public void testOf() {
440      assertMapEquals(
441          ImmutableMap.of("one", 1),
442          "one", 1);
443      assertMapEquals(
444          ImmutableMap.of("one", 1, "two", 2),
445          "one", 1, "two", 2);
446      assertMapEquals(
447          ImmutableMap.of("one", 1, "two", 2, "three", 3),
448          "one", 1, "two", 2, "three", 3);
449      assertMapEquals(
450          ImmutableMap.of("one", 1, "two", 2, "three", 3, "four", 4),
451          "one", 1, "two", 2, "three", 3, "four", 4);
452      assertMapEquals(
453          ImmutableMap.of("one", 1, "two", 2, "three", 3, "four", 4, "five", 5),
454          "one", 1, "two", 2, "three", 3, "four", 4, "five", 5);
455    }
456
457    public void testOfNullKey() {
458      try {
459        ImmutableMap.of(null, 1);
460        fail();
461      } catch (NullPointerException expected) {
462      }
463
464      try {
465        ImmutableMap.of("one", 1, null, 2);
466        fail();
467      } catch (NullPointerException expected) {
468      }
469    }
470
471    public void testOfNullValue() {
472      try {
473        ImmutableMap.of("one", null);
474        fail();
475      } catch (NullPointerException expected) {
476      }
477
478      try {
479        ImmutableMap.of("one", 1, "two", null);
480        fail();
481      } catch (NullPointerException expected) {
482      }
483    }
484
485    public void testOfWithDuplicateKey() {
486      try {
487        ImmutableMap.of("one", 1, "one", 1);
488        fail();
489      } catch (IllegalArgumentException expected) {
490        assertEquals("duplicate key: one", expected.getMessage());
491      }
492    }
493
494    public void testCopyOfEmptyMap() {
495      ImmutableMap<String, Integer> copy
496          = ImmutableMap.copyOf(Collections.<String, Integer>emptyMap());
497      assertEquals(Collections.<String, Integer>emptyMap(), copy);
498      assertSame(copy, ImmutableMap.copyOf(copy));
499    }
500
501    public void testCopyOfSingletonMap() {
502      ImmutableMap<String, Integer> copy
503          = ImmutableMap.copyOf(Collections.singletonMap("one", 1));
504      assertMapEquals(copy, "one", 1);
505      assertSame(copy, ImmutableMap.copyOf(copy));
506    }
507
508    public void testCopyOf() {
509      Map<String, Integer> original = new LinkedHashMap<String, Integer>();
510      original.put("one", 1);
511      original.put("two", 2);
512      original.put("three", 3);
513
514      ImmutableMap<String, Integer> copy = ImmutableMap.copyOf(original);
515      assertMapEquals(copy, "one", 1, "two", 2, "three", 3);
516      assertSame(copy, ImmutableMap.copyOf(copy));
517    }
518  }
519
520  public void testNullGet() {
521    ImmutableMap<String, Integer> map = ImmutableMap.of("one", 1);
522    assertNull(map.get(null));
523  }
524
525  @GwtIncompatible("NullPointerTester")
526  public void testNullPointers() throws Exception {
527    NullPointerTester tester = new NullPointerTester();
528    tester.testAllPublicStaticMethods(ImmutableMap.class);
529    tester.testAllPublicInstanceMethods(
530        new ImmutableMap.Builder<Object, Object>());
531    tester.testAllPublicInstanceMethods(ImmutableMap.of());
532    tester.testAllPublicInstanceMethods(ImmutableMap.of("one", 1));
533    tester.testAllPublicInstanceMethods(
534        ImmutableMap.of("one", 1, "two", 2, "three", 3));
535  }
536
537  private static <K, V> void assertMapEquals(Map<K, V> map,
538      Object... alternatingKeysAndValues) {
539    assertEquals(map.size(), alternatingKeysAndValues.length / 2);
540    int i = 0;
541    for (Entry<K, V> entry : map.entrySet()) {
542      assertEquals(alternatingKeysAndValues[i++], entry.getKey());
543      assertEquals(alternatingKeysAndValues[i++], entry.getValue());
544    }
545  }
546
547  private static class IntHolder implements Serializable {
548    public int value;
549
550    public IntHolder(int value) {
551      this.value = value;
552    }
553
554    @Override public boolean equals(Object o) {
555      return (o instanceof IntHolder) && ((IntHolder) o).value == value;
556    }
557
558    @Override public int hashCode() {
559      return value;
560    }
561
562    private static final long serialVersionUID = 5;
563  }
564
565  public void testMutableValues() {
566    IntHolder holderA = new IntHolder(1);
567    IntHolder holderB = new IntHolder(2);
568    Map<String, IntHolder> map = ImmutableMap.of("a", holderA, "b", holderB);
569    holderA.value = 3;
570    assertTrue(map.entrySet().contains(
571        Maps.immutableEntry("a", new IntHolder(3))));
572    Map<String, Integer> intMap = ImmutableMap.of("a", 3, "b", 2);
573    assertEquals(intMap.hashCode(), map.entrySet().hashCode());
574    assertEquals(intMap.hashCode(), map.hashCode());
575  }
576
577  @GwtIncompatible("SerializableTester")
578  public void testViewSerialization() {
579    Map<String, Integer> map = ImmutableMap.of("one", 1, "two", 2, "three", 3);
580    LenientSerializableTester.reserializeAndAssertLenient(map.entrySet());
581    LenientSerializableTester.reserializeAndAssertLenient(map.keySet());
582
583    Collection<Integer> reserializedValues = reserialize(map.values());
584    assertEquals(Lists.newArrayList(map.values()),
585        Lists.newArrayList(reserializedValues));
586    assertTrue(reserializedValues instanceof ImmutableCollection);
587  }
588}
589