001/*
002 * Copyright (C) 2009 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.google.common.collect.testing;
018
019import static java.util.Arrays.asList;
020import static java.util.Collections.emptyMap;
021import static java.util.Collections.emptySet;
022import static java.util.Collections.singletonMap;
023import static java.util.Collections.unmodifiableMap;
024
025import com.google.common.annotations.GwtIncompatible;
026import com.google.common.collect.testing.features.CollectionFeature;
027import com.google.common.collect.testing.features.CollectionSize;
028import com.google.common.collect.testing.features.MapFeature;
029import com.google.common.collect.testing.testers.MapEntrySetTester;
030import com.google.errorprone.annotations.CanIgnoreReturnValue;
031import java.io.Serializable;
032import java.lang.reflect.Method;
033import java.util.Collection;
034import java.util.Collections;
035import java.util.Comparator;
036import java.util.EnumMap;
037import java.util.HashMap;
038import java.util.Hashtable;
039import java.util.LinkedHashMap;
040import java.util.Map;
041import java.util.Map.Entry;
042import java.util.NavigableMap;
043import java.util.SortedMap;
044import java.util.TreeMap;
045import java.util.concurrent.ConcurrentHashMap;
046import java.util.concurrent.ConcurrentSkipListMap;
047import junit.framework.Test;
048import junit.framework.TestSuite;
049
050/**
051 * Generates a test suite covering the {@link Map} implementations in the {@link java.util} package.
052 * Can be subclassed to specify tests that should be suppressed.
053 *
054 * @author Kevin Bourrillion
055 */
056@GwtIncompatible
057public class TestsForMapsInJavaUtil {
058
059  public static Test suite() {
060    return new TestsForMapsInJavaUtil().allTests();
061  }
062
063  public Test allTests() {
064    TestSuite suite = new TestSuite("java.util Maps");
065    suite.addTest(testsForCheckedMap());
066    suite.addTest(testsForCheckedNavigableMap());
067    suite.addTest(testsForCheckedSortedMap());
068    suite.addTest(testsForEmptyMap());
069    suite.addTest(testsForEmptyNavigableMap());
070    suite.addTest(testsForEmptySortedMap());
071    suite.addTest(testsForSingletonMap());
072    suite.addTest(testsForHashMap());
073    suite.addTest(testsForHashtable());
074    suite.addTest(testsForLinkedHashMap());
075    suite.addTest(testsForSynchronizedNavigableMap());
076    suite.addTest(testsForTreeMapNatural());
077    suite.addTest(testsForTreeMapWithComparator());
078    suite.addTest(testsForUnmodifiableMap());
079    suite.addTest(testsForUnmodifiableNavigableMap());
080    suite.addTest(testsForUnmodifiableSortedMap());
081    suite.addTest(testsForEnumMap());
082    suite.addTest(testsForConcurrentHashMap());
083    suite.addTest(testsForConcurrentSkipListMapNatural());
084    suite.addTest(testsForConcurrentSkipListMapWithComparator());
085    return suite;
086  }
087
088  protected Collection<Method> suppressForCheckedMap() {
089    return emptySet();
090  }
091
092  protected Collection<Method> suppressForCheckedNavigableMap() {
093    return emptySet();
094  }
095
096  protected Collection<Method> suppressForCheckedSortedMap() {
097    return emptySet();
098  }
099
100  protected Collection<Method> suppressForEmptyMap() {
101    return emptySet();
102  }
103
104  private static Collection<Method> suppressForEmptyNavigableMap() {
105    return emptySet();
106  }
107
108  private static Collection<Method> suppressForEmptySortedMap() {
109    return emptySet();
110  }
111
112  protected Collection<Method> suppressForSingletonMap() {
113    return emptySet();
114  }
115
116  protected Collection<Method> suppressForHashMap() {
117    return emptySet();
118  }
119
120  protected Collection<Method> suppressForHashtable() {
121    return emptySet();
122  }
123
124  protected Collection<Method> suppressForLinkedHashMap() {
125    return emptySet();
126  }
127
128  protected Collection<Method> suppressForSynchronizedNavigableMap() {
129    return emptySet();
130  }
131
132  protected Collection<Method> suppressForTreeMapNatural() {
133    return emptySet();
134  }
135
136  protected Collection<Method> suppressForTreeMapWithComparator() {
137    return emptySet();
138  }
139
140  protected Collection<Method> suppressForUnmodifiableMap() {
141    return emptySet();
142  }
143
144  protected Collection<Method> suppressForUnmodifiableNavigableMap() {
145    return emptySet();
146  }
147
148  protected Collection<Method> suppressForUnmodifiableSortedMap() {
149    return emptySet();
150  }
151
152  protected Collection<Method> suppressForEnumMap() {
153    return emptySet();
154  }
155
156  protected Collection<Method> suppressForConcurrentHashMap() {
157    return emptySet();
158  }
159
160  protected Collection<Method> suppressForConcurrentSkipListMap() {
161    return asList(
162        MapEntrySetTester.getSetValueMethod(),
163        MapEntrySetTester.getSetValueWithNullValuesAbsentMethod(),
164        MapEntrySetTester.getSetValueWithNullValuesPresentMethod(),
165        MapEntrySetTester.getIteratorSetValueAndRemove());
166  }
167
168  public Test testsForCheckedMap() {
169    return MapTestSuiteBuilder.using(
170            new TestStringMapGenerator() {
171              @Override
172              protected Map<String, String> create(Entry<String, String>[] entries) {
173                Map<String, String> map = populate(new HashMap<String, String>(), entries);
174                return Collections.checkedMap(map, String.class, String.class);
175              }
176            })
177        .named("checkedMap/HashMap")
178        .withFeatures(
179            MapFeature.GENERAL_PURPOSE,
180            MapFeature.ALLOWS_NULL_KEYS,
181            MapFeature.ALLOWS_NULL_VALUES,
182            MapFeature.ALLOWS_ANY_NULL_QUERIES,
183            MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION,
184            MapFeature.RESTRICTS_KEYS,
185            MapFeature.RESTRICTS_VALUES,
186            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
187            CollectionFeature.SERIALIZABLE,
188            CollectionSize.ANY)
189        .suppressing(suppressForCheckedMap())
190        .createTestSuite();
191  }
192
193  public Test testsForCheckedNavigableMap() {
194    return SortedMapTestSuiteBuilder.using(
195            new TestStringSortedMapGenerator() {
196              @Override
197              protected NavigableMap<String, String> create(Entry<String, String>[] entries) {
198                NavigableMap<String, String> map = populate(new TreeMap<String, String>(), entries);
199                return Collections.checkedNavigableMap(map, String.class, String.class);
200              }
201            })
202        .named("checkedNavigableMap/TreeMap, natural")
203        .withFeatures(
204            MapFeature.GENERAL_PURPOSE,
205            MapFeature.ALLOWS_NULL_VALUES,
206            MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION,
207            MapFeature.RESTRICTS_KEYS,
208            MapFeature.RESTRICTS_VALUES,
209            CollectionFeature.KNOWN_ORDER,
210            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
211            CollectionFeature.SERIALIZABLE,
212            CollectionSize.ANY)
213        .suppressing(suppressForCheckedNavigableMap())
214        .createTestSuite();
215  }
216
217  public Test testsForCheckedSortedMap() {
218    return SortedMapTestSuiteBuilder.using(
219            new TestStringSortedMapGenerator() {
220              @Override
221              protected SortedMap<String, String> create(Entry<String, String>[] entries) {
222                SortedMap<String, String> map = populate(new TreeMap<String, String>(), entries);
223                return Collections.checkedSortedMap(map, String.class, String.class);
224              }
225            })
226        .named("checkedSortedMap/TreeMap, natural")
227        .withFeatures(
228            MapFeature.GENERAL_PURPOSE,
229            MapFeature.ALLOWS_NULL_VALUES,
230            MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION,
231            MapFeature.RESTRICTS_KEYS,
232            MapFeature.RESTRICTS_VALUES,
233            CollectionFeature.KNOWN_ORDER,
234            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
235            CollectionFeature.SERIALIZABLE,
236            CollectionSize.ANY)
237        .suppressing(suppressForCheckedSortedMap())
238        .createTestSuite();
239  }
240
241  public Test testsForEmptyMap() {
242    return MapTestSuiteBuilder.using(
243            new TestStringMapGenerator() {
244              @Override
245              protected Map<String, String> create(Entry<String, String>[] entries) {
246                return emptyMap();
247              }
248            })
249        .named("emptyMap")
250        .withFeatures(CollectionFeature.SERIALIZABLE, CollectionSize.ZERO)
251        .suppressing(suppressForEmptyMap())
252        .createTestSuite();
253  }
254
255  public Test testsForEmptyNavigableMap() {
256    return MapTestSuiteBuilder.using(
257            new TestStringSortedMapGenerator() {
258              @Override
259              protected NavigableMap<String, String> create(Entry<String, String>[] entries) {
260                return Collections.emptyNavigableMap();
261              }
262            })
263        .named("emptyNavigableMap")
264        .withFeatures(CollectionFeature.SERIALIZABLE, CollectionSize.ZERO)
265        .suppressing(suppressForEmptyNavigableMap())
266        .createTestSuite();
267  }
268
269  public Test testsForEmptySortedMap() {
270    return MapTestSuiteBuilder.using(
271            new TestStringSortedMapGenerator() {
272              @Override
273              protected SortedMap<String, String> create(Entry<String, String>[] entries) {
274                return Collections.emptySortedMap();
275              }
276            })
277        .named("emptySortedMap")
278        .withFeatures(CollectionFeature.SERIALIZABLE, CollectionSize.ZERO)
279        .suppressing(suppressForEmptySortedMap())
280        .createTestSuite();
281  }
282
283  public Test testsForSingletonMap() {
284    return MapTestSuiteBuilder.using(
285            new TestStringMapGenerator() {
286              @Override
287              protected Map<String, String> create(Entry<String, String>[] entries) {
288                return singletonMap(entries[0].getKey(), entries[0].getValue());
289              }
290            })
291        .named("singletonMap")
292        .withFeatures(
293            MapFeature.ALLOWS_NULL_KEYS,
294            MapFeature.ALLOWS_NULL_VALUES,
295            MapFeature.ALLOWS_ANY_NULL_QUERIES,
296            CollectionFeature.SERIALIZABLE,
297            CollectionSize.ONE)
298        .suppressing(suppressForSingletonMap())
299        .createTestSuite();
300  }
301
302  public Test testsForHashMap() {
303    return MapTestSuiteBuilder.using(
304            new TestStringMapGenerator() {
305              @Override
306              protected Map<String, String> create(Entry<String, String>[] entries) {
307                return toHashMap(entries);
308              }
309            })
310        .named("HashMap")
311        .withFeatures(
312            MapFeature.GENERAL_PURPOSE,
313            MapFeature.ALLOWS_NULL_KEYS,
314            MapFeature.ALLOWS_NULL_VALUES,
315            MapFeature.ALLOWS_ANY_NULL_QUERIES,
316            MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION,
317            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
318            CollectionFeature.SERIALIZABLE,
319            CollectionSize.ANY)
320        .suppressing(suppressForHashMap())
321        .createTestSuite();
322  }
323
324  public Test testsForHashtable() {
325    return MapTestSuiteBuilder.using(
326            new TestStringMapGenerator() {
327              @Override
328              // We are testing Hashtable / testing our tests on Hashtable.
329              @SuppressWarnings("JdkObsolete")
330              protected Map<String, String> create(Entry<String, String>[] entries) {
331                return populate(new Hashtable<String, String>(), entries);
332              }
333            })
334        .withFeatures(
335            MapFeature.GENERAL_PURPOSE,
336            MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION,
337            MapFeature.RESTRICTS_KEYS,
338            MapFeature.SUPPORTS_REMOVE,
339            CollectionFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION,
340            CollectionFeature.SERIALIZABLE,
341            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
342            CollectionFeature.SUPPORTS_REMOVE,
343            CollectionSize.ANY)
344        .named("Hashtable")
345        .suppressing(suppressForHashtable())
346        .createTestSuite();
347  }
348
349  public Test testsForLinkedHashMap() {
350    return MapTestSuiteBuilder.using(
351            new TestStringMapGenerator() {
352              @Override
353              protected Map<String, String> create(Entry<String, String>[] entries) {
354                return populate(new LinkedHashMap<String, String>(), entries);
355              }
356            })
357        .named("LinkedHashMap")
358        .withFeatures(
359            MapFeature.GENERAL_PURPOSE,
360            MapFeature.ALLOWS_NULL_KEYS,
361            MapFeature.ALLOWS_NULL_VALUES,
362            MapFeature.ALLOWS_ANY_NULL_QUERIES,
363            MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION,
364            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
365            CollectionFeature.KNOWN_ORDER,
366            CollectionFeature.SERIALIZABLE,
367            CollectionSize.ANY)
368        .suppressing(suppressForLinkedHashMap())
369        .createTestSuite();
370  }
371
372  /**
373   * Tests regular NavigableMap behavior of synchronizedNavigableMap(treeMap); does not test the
374   * fact that it's synchronized.
375   */
376  public Test testsForSynchronizedNavigableMap() {
377    return NavigableMapTestSuiteBuilder.using(
378            new TestStringSortedMapGenerator() {
379              @Override
380              protected SortedMap<String, String> create(Entry<String, String>[] entries) {
381                NavigableMap<String, String> delegate = populate(new TreeMap<>(), entries);
382                return Collections.synchronizedNavigableMap(delegate);
383              }
384            })
385        .named("synchronizedNavigableMap/TreeMap, natural")
386        .withFeatures(
387            MapFeature.GENERAL_PURPOSE,
388            MapFeature.ALLOWS_NULL_VALUES,
389            MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION,
390            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
391            CollectionFeature.KNOWN_ORDER,
392            CollectionFeature.SERIALIZABLE,
393            CollectionSize.ANY)
394        .suppressing(suppressForSynchronizedNavigableMap())
395        .createTestSuite();
396  }
397
398  public Test testsForTreeMapNatural() {
399    return NavigableMapTestSuiteBuilder.using(
400            new TestStringSortedMapGenerator() {
401              @Override
402              protected SortedMap<String, String> create(Entry<String, String>[] entries) {
403                /*
404                 * TODO(cpovirk): it would be nice to create an input Map and use
405                 * the copy constructor here and in the other tests
406                 */
407                return populate(new TreeMap<String, String>(), entries);
408              }
409            })
410        .named("TreeMap, natural")
411        .withFeatures(
412            MapFeature.GENERAL_PURPOSE,
413            MapFeature.ALLOWS_NULL_VALUES,
414            MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION,
415            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
416            CollectionFeature.KNOWN_ORDER,
417            CollectionFeature.SERIALIZABLE,
418            CollectionSize.ANY)
419        .suppressing(suppressForTreeMapNatural())
420        .createTestSuite();
421  }
422
423  public Test testsForTreeMapWithComparator() {
424    return NavigableMapTestSuiteBuilder.using(
425            new TestStringSortedMapGenerator() {
426              @Override
427              protected SortedMap<String, String> create(Entry<String, String>[] entries) {
428                return populate(
429                    new TreeMap<String, String>(arbitraryNullFriendlyComparator()), entries);
430              }
431            })
432        .named("TreeMap, with comparator")
433        .withFeatures(
434            MapFeature.GENERAL_PURPOSE,
435            MapFeature.ALLOWS_NULL_KEYS,
436            MapFeature.ALLOWS_NULL_VALUES,
437            MapFeature.ALLOWS_ANY_NULL_QUERIES,
438            MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION,
439            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
440            CollectionFeature.KNOWN_ORDER,
441            CollectionFeature.SERIALIZABLE,
442            CollectionSize.ANY)
443        .suppressing(suppressForTreeMapWithComparator())
444        .createTestSuite();
445  }
446
447  public Test testsForUnmodifiableMap() {
448    return MapTestSuiteBuilder.using(
449            new TestStringMapGenerator() {
450              @Override
451              protected Map<String, String> create(Entry<String, String>[] entries) {
452                return unmodifiableMap(toHashMap(entries));
453              }
454            })
455        .named("unmodifiableMap/HashMap")
456        .withFeatures(
457            MapFeature.ALLOWS_NULL_KEYS,
458            MapFeature.ALLOWS_NULL_VALUES,
459            MapFeature.ALLOWS_ANY_NULL_QUERIES,
460            CollectionFeature.SERIALIZABLE,
461            CollectionSize.ANY)
462        .suppressing(suppressForUnmodifiableMap())
463        .createTestSuite();
464  }
465
466  public Test testsForUnmodifiableNavigableMap() {
467    return MapTestSuiteBuilder.using(
468            new TestStringSortedMapGenerator() {
469              @Override
470              protected NavigableMap<String, String> create(Entry<String, String>[] entries) {
471                return Collections.unmodifiableNavigableMap(populate(new TreeMap<>(), entries));
472              }
473            })
474        .named("unmodifiableNavigableMap/TreeMap, natural")
475        .withFeatures(
476            MapFeature.ALLOWS_NULL_VALUES,
477            CollectionFeature.KNOWN_ORDER,
478            CollectionFeature.SERIALIZABLE,
479            CollectionSize.ANY)
480        .suppressing(suppressForUnmodifiableNavigableMap())
481        .createTestSuite();
482  }
483
484  public Test testsForUnmodifiableSortedMap() {
485    return MapTestSuiteBuilder.using(
486            new TestStringSortedMapGenerator() {
487              @Override
488              protected SortedMap<String, String> create(Entry<String, String>[] entries) {
489                SortedMap<String, String> map = populate(new TreeMap<String, String>(), entries);
490                return Collections.unmodifiableSortedMap(map);
491              }
492            })
493        .named("unmodifiableSortedMap/TreeMap, natural")
494        .withFeatures(
495            MapFeature.ALLOWS_NULL_VALUES,
496            CollectionFeature.KNOWN_ORDER,
497            CollectionFeature.SERIALIZABLE,
498            CollectionSize.ANY)
499        .suppressing(suppressForUnmodifiableSortedMap())
500        .createTestSuite();
501  }
502
503  public Test testsForEnumMap() {
504    return MapTestSuiteBuilder.using(
505            new TestEnumMapGenerator() {
506              @Override
507              protected Map<AnEnum, String> create(Entry<AnEnum, String>[] entries) {
508                return populate(new EnumMap<AnEnum, String>(AnEnum.class), entries);
509              }
510            })
511        .named("EnumMap")
512        .withFeatures(
513            MapFeature.GENERAL_PURPOSE,
514            MapFeature.ALLOWS_NULL_VALUES,
515            MapFeature.RESTRICTS_KEYS,
516            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
517            CollectionFeature.KNOWN_ORDER,
518            CollectionFeature.SERIALIZABLE,
519            CollectionSize.ANY)
520        .suppressing(suppressForEnumMap())
521        .createTestSuite();
522  }
523
524  public Test testsForConcurrentHashMap() {
525    return MapTestSuiteBuilder.using(
526            new TestStringMapGenerator() {
527              @Override
528              protected Map<String, String> create(Entry<String, String>[] entries) {
529                return populate(new ConcurrentHashMap<String, String>(), entries);
530              }
531            })
532        .named("ConcurrentHashMap")
533        .withFeatures(
534            MapFeature.GENERAL_PURPOSE,
535            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
536            CollectionFeature.SERIALIZABLE,
537            CollectionSize.ANY)
538        .suppressing(suppressForConcurrentHashMap())
539        .createTestSuite();
540  }
541
542  public Test testsForConcurrentSkipListMapNatural() {
543    return NavigableMapTestSuiteBuilder.using(
544            new TestStringSortedMapGenerator() {
545              @Override
546              protected SortedMap<String, String> create(Entry<String, String>[] entries) {
547                return populate(new ConcurrentSkipListMap<String, String>(), entries);
548              }
549            })
550        .named("ConcurrentSkipListMap, natural")
551        .withFeatures(
552            MapFeature.GENERAL_PURPOSE,
553            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
554            CollectionFeature.KNOWN_ORDER,
555            CollectionFeature.SERIALIZABLE,
556            CollectionSize.ANY)
557        .suppressing(suppressForConcurrentSkipListMap())
558        .createTestSuite();
559  }
560
561  public Test testsForConcurrentSkipListMapWithComparator() {
562    return NavigableMapTestSuiteBuilder.using(
563            new TestStringSortedMapGenerator() {
564              @Override
565              protected SortedMap<String, String> create(Entry<String, String>[] entries) {
566                return populate(
567                    new ConcurrentSkipListMap<String, String>(arbitraryNullFriendlyComparator()),
568                    entries);
569              }
570            })
571        .named("ConcurrentSkipListMap, with comparator")
572        .withFeatures(
573            MapFeature.GENERAL_PURPOSE,
574            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
575            CollectionFeature.KNOWN_ORDER,
576            CollectionFeature.SERIALIZABLE,
577            CollectionSize.ANY)
578        .suppressing(suppressForConcurrentSkipListMap())
579        .createTestSuite();
580  }
581
582  // TODO: IdentityHashMap, AbstractMap
583
584  private static Map<String, String> toHashMap(Entry<String, String>[] entries) {
585    return populate(new HashMap<String, String>(), entries);
586  }
587
588  // TODO: call conversion constructors or factory methods instead of using
589  // populate() on an empty map
590  @CanIgnoreReturnValue
591  private static <T, M extends Map<T, String>> M populate(M map, Entry<T, String>[] entries) {
592    for (Entry<T, String> entry : entries) {
593      map.put(entry.getKey(), entry.getValue());
594    }
595    return map;
596  }
597
598  static <T> Comparator<T> arbitraryNullFriendlyComparator() {
599    return new NullFriendlyComparator<>();
600  }
601
602  private static final class NullFriendlyComparator<T> implements Comparator<T>, Serializable {
603    @Override
604    public int compare(T left, T right) {
605      return String.valueOf(left).compareTo(String.valueOf(right));
606    }
607  }
608}