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 Collection<Method> suppressForEmptyNavigableMap() {
105    return emptySet();
106  }
107
108  private 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  }
166
167  public Test testsForCheckedMap() {
168    return MapTestSuiteBuilder.using(
169            new TestStringMapGenerator() {
170              @Override
171              protected Map<String, String> create(Entry<String, String>[] entries) {
172                Map<String, String> map = populate(new HashMap<String, String>(), entries);
173                return Collections.checkedMap(map, String.class, String.class);
174              }
175            })
176        .named("checkedMap/HashMap")
177        .withFeatures(
178            MapFeature.GENERAL_PURPOSE,
179            MapFeature.ALLOWS_NULL_KEYS,
180            MapFeature.ALLOWS_NULL_VALUES,
181            MapFeature.ALLOWS_ANY_NULL_QUERIES,
182            MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION,
183            MapFeature.RESTRICTS_KEYS,
184            MapFeature.RESTRICTS_VALUES,
185            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
186            CollectionFeature.SERIALIZABLE,
187            CollectionSize.ANY)
188        .suppressing(suppressForCheckedMap())
189        .createTestSuite();
190  }
191
192  public Test testsForCheckedNavigableMap() {
193    return SortedMapTestSuiteBuilder.using(
194            new TestStringSortedMapGenerator() {
195              @Override
196              protected NavigableMap<String, String> create(Entry<String, String>[] entries) {
197                NavigableMap<String, String> map = populate(new TreeMap<String, String>(), entries);
198                return Collections.checkedNavigableMap(map, String.class, String.class);
199              }
200            })
201        .named("checkedNavigableMap/TreeMap, natural")
202        .withFeatures(
203            MapFeature.GENERAL_PURPOSE,
204            MapFeature.ALLOWS_NULL_VALUES,
205            MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION,
206            MapFeature.RESTRICTS_KEYS,
207            MapFeature.RESTRICTS_VALUES,
208            CollectionFeature.KNOWN_ORDER,
209            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
210            CollectionFeature.SERIALIZABLE,
211            CollectionSize.ANY)
212        .suppressing(suppressForCheckedNavigableMap())
213        .createTestSuite();
214  }
215
216  public Test testsForCheckedSortedMap() {
217    return SortedMapTestSuiteBuilder.using(
218            new TestStringSortedMapGenerator() {
219              @Override
220              protected SortedMap<String, String> create(Entry<String, String>[] entries) {
221                SortedMap<String, String> map = populate(new TreeMap<String, String>(), entries);
222                return Collections.checkedSortedMap(map, String.class, String.class);
223              }
224            })
225        .named("checkedSortedMap/TreeMap, natural")
226        .withFeatures(
227            MapFeature.GENERAL_PURPOSE,
228            MapFeature.ALLOWS_NULL_VALUES,
229            MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION,
230            MapFeature.RESTRICTS_KEYS,
231            MapFeature.RESTRICTS_VALUES,
232            CollectionFeature.KNOWN_ORDER,
233            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
234            CollectionFeature.SERIALIZABLE,
235            CollectionSize.ANY)
236        .suppressing(suppressForCheckedSortedMap())
237        .createTestSuite();
238  }
239
240  public Test testsForEmptyMap() {
241    return MapTestSuiteBuilder.using(
242            new TestStringMapGenerator() {
243              @Override
244              protected Map<String, String> create(Entry<String, String>[] entries) {
245                return emptyMap();
246              }
247            })
248        .named("emptyMap")
249        .withFeatures(CollectionFeature.SERIALIZABLE, CollectionSize.ZERO)
250        .suppressing(suppressForEmptyMap())
251        .createTestSuite();
252  }
253
254  public Test testsForEmptyNavigableMap() {
255    return MapTestSuiteBuilder.using(
256            new TestStringSortedMapGenerator() {
257              @Override
258              protected NavigableMap<String, String> create(Entry<String, String>[] entries) {
259                return Collections.emptyNavigableMap();
260              }
261            })
262        .named("emptyNavigableMap")
263        .withFeatures(CollectionFeature.SERIALIZABLE, CollectionSize.ZERO)
264        .suppressing(suppressForEmptyNavigableMap())
265        .createTestSuite();
266  }
267
268  public Test testsForEmptySortedMap() {
269    return MapTestSuiteBuilder.using(
270            new TestStringSortedMapGenerator() {
271              @Override
272              protected SortedMap<String, String> create(Entry<String, String>[] entries) {
273                return Collections.emptySortedMap();
274              }
275            })
276        .named("emptySortedMap")
277        .withFeatures(CollectionFeature.SERIALIZABLE, CollectionSize.ZERO)
278        .suppressing(suppressForEmptySortedMap())
279        .createTestSuite();
280  }
281
282  public Test testsForSingletonMap() {
283    return MapTestSuiteBuilder.using(
284            new TestStringMapGenerator() {
285              @Override
286              protected Map<String, String> create(Entry<String, String>[] entries) {
287                return singletonMap(entries[0].getKey(), entries[0].getValue());
288              }
289            })
290        .named("singletonMap")
291        .withFeatures(
292            MapFeature.ALLOWS_NULL_KEYS,
293            MapFeature.ALLOWS_NULL_VALUES,
294            MapFeature.ALLOWS_ANY_NULL_QUERIES,
295            CollectionFeature.SERIALIZABLE,
296            CollectionSize.ONE)
297        .suppressing(suppressForSingletonMap())
298        .createTestSuite();
299  }
300
301  public Test testsForHashMap() {
302    return MapTestSuiteBuilder.using(
303            new TestStringMapGenerator() {
304              @Override
305              protected Map<String, String> create(Entry<String, String>[] entries) {
306                return toHashMap(entries);
307              }
308            })
309        .named("HashMap")
310        .withFeatures(
311            MapFeature.GENERAL_PURPOSE,
312            MapFeature.ALLOWS_NULL_KEYS,
313            MapFeature.ALLOWS_NULL_VALUES,
314            MapFeature.ALLOWS_ANY_NULL_QUERIES,
315            MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION,
316            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
317            CollectionFeature.SERIALIZABLE,
318            CollectionSize.ANY)
319        .suppressing(suppressForHashMap())
320        .createTestSuite();
321  }
322
323  public Test testsForHashtable() {
324    return MapTestSuiteBuilder.using(
325            new TestStringMapGenerator() {
326              @Override
327              // We are testing Hashtable / testing our tests on Hashtable.
328              @SuppressWarnings("JdkObsolete")
329              protected Map<String, String> create(Entry<String, String>[] entries) {
330                return populate(new Hashtable<String, String>(), entries);
331              }
332            })
333        .withFeatures(
334            MapFeature.GENERAL_PURPOSE,
335            MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION,
336            MapFeature.RESTRICTS_KEYS,
337            MapFeature.SUPPORTS_REMOVE,
338            CollectionFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION,
339            CollectionFeature.SERIALIZABLE,
340            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
341            CollectionFeature.SUPPORTS_REMOVE,
342            CollectionSize.ANY)
343        .named("Hashtable")
344        .suppressing(suppressForHashtable())
345        .createTestSuite();
346  }
347
348  public Test testsForLinkedHashMap() {
349    return MapTestSuiteBuilder.using(
350            new TestStringMapGenerator() {
351              @Override
352              protected Map<String, String> create(Entry<String, String>[] entries) {
353                return populate(new LinkedHashMap<String, String>(), entries);
354              }
355            })
356        .named("LinkedHashMap")
357        .withFeatures(
358            MapFeature.GENERAL_PURPOSE,
359            MapFeature.ALLOWS_NULL_KEYS,
360            MapFeature.ALLOWS_NULL_VALUES,
361            MapFeature.ALLOWS_ANY_NULL_QUERIES,
362            MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION,
363            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
364            CollectionFeature.KNOWN_ORDER,
365            CollectionFeature.SERIALIZABLE,
366            CollectionSize.ANY)
367        .suppressing(suppressForLinkedHashMap())
368        .createTestSuite();
369  }
370
371  /**
372   * Tests regular NavigableMap behavior of synchronizedNavigableMap(treeMap); does not test the
373   * fact that it's synchronized.
374   */
375  public Test testsForSynchronizedNavigableMap() {
376    return NavigableMapTestSuiteBuilder.using(
377            new TestStringSortedMapGenerator() {
378              @Override
379              protected SortedMap<String, String> create(Entry<String, String>[] entries) {
380                NavigableMap<String, String> delegate = populate(new TreeMap<>(), entries);
381                return Collections.synchronizedNavigableMap(delegate);
382              }
383            })
384        .named("synchronizedNavigableMap/TreeMap, natural")
385        .withFeatures(
386            MapFeature.GENERAL_PURPOSE,
387            MapFeature.ALLOWS_NULL_VALUES,
388            MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION,
389            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
390            CollectionFeature.KNOWN_ORDER,
391            CollectionFeature.SERIALIZABLE,
392            CollectionSize.ANY)
393        .suppressing(suppressForSynchronizedNavigableMap())
394        .createTestSuite();
395  }
396
397  public Test testsForTreeMapNatural() {
398    return NavigableMapTestSuiteBuilder.using(
399            new TestStringSortedMapGenerator() {
400              @Override
401              protected SortedMap<String, String> create(Entry<String, String>[] entries) {
402                /*
403                 * TODO(cpovirk): it would be nice to create an input Map and use
404                 * the copy constructor here and in the other tests
405                 */
406                return populate(new TreeMap<String, String>(), entries);
407              }
408            })
409        .named("TreeMap, natural")
410        .withFeatures(
411            MapFeature.GENERAL_PURPOSE,
412            MapFeature.ALLOWS_NULL_VALUES,
413            MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION,
414            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
415            CollectionFeature.KNOWN_ORDER,
416            CollectionFeature.SERIALIZABLE,
417            CollectionSize.ANY)
418        .suppressing(suppressForTreeMapNatural())
419        .createTestSuite();
420  }
421
422  public Test testsForTreeMapWithComparator() {
423    return NavigableMapTestSuiteBuilder.using(
424            new TestStringSortedMapGenerator() {
425              @Override
426              protected SortedMap<String, String> create(Entry<String, String>[] entries) {
427                return populate(
428                    new TreeMap<String, String>(arbitraryNullFriendlyComparator()), entries);
429              }
430            })
431        .named("TreeMap, with comparator")
432        .withFeatures(
433            MapFeature.GENERAL_PURPOSE,
434            MapFeature.ALLOWS_NULL_KEYS,
435            MapFeature.ALLOWS_NULL_VALUES,
436            MapFeature.ALLOWS_ANY_NULL_QUERIES,
437            MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION,
438            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
439            CollectionFeature.KNOWN_ORDER,
440            CollectionFeature.SERIALIZABLE,
441            CollectionSize.ANY)
442        .suppressing(suppressForTreeMapWithComparator())
443        .createTestSuite();
444  }
445
446  public Test testsForUnmodifiableMap() {
447    return MapTestSuiteBuilder.using(
448            new TestStringMapGenerator() {
449              @Override
450              protected Map<String, String> create(Entry<String, String>[] entries) {
451                return unmodifiableMap(toHashMap(entries));
452              }
453            })
454        .named("unmodifiableMap/HashMap")
455        .withFeatures(
456            MapFeature.ALLOWS_NULL_KEYS,
457            MapFeature.ALLOWS_NULL_VALUES,
458            MapFeature.ALLOWS_ANY_NULL_QUERIES,
459            CollectionFeature.SERIALIZABLE,
460            CollectionSize.ANY)
461        .suppressing(suppressForUnmodifiableMap())
462        .createTestSuite();
463  }
464
465  public Test testsForUnmodifiableNavigableMap() {
466    return MapTestSuiteBuilder.using(
467            new TestStringSortedMapGenerator() {
468              @Override
469              protected NavigableMap<String, String> create(Entry<String, String>[] entries) {
470                return Collections.unmodifiableNavigableMap(populate(new TreeMap<>(), entries));
471              }
472            })
473        .named("unmodifiableNavigableMap/TreeMap, natural")
474        .withFeatures(
475            MapFeature.ALLOWS_NULL_VALUES,
476            CollectionFeature.KNOWN_ORDER,
477            CollectionFeature.SERIALIZABLE,
478            CollectionSize.ANY)
479        .suppressing(suppressForUnmodifiableNavigableMap())
480        .createTestSuite();
481  }
482
483  public Test testsForUnmodifiableSortedMap() {
484    return MapTestSuiteBuilder.using(
485            new TestStringSortedMapGenerator() {
486              @Override
487              protected SortedMap<String, String> create(Entry<String, String>[] entries) {
488                SortedMap<String, String> map = populate(new TreeMap<String, String>(), entries);
489                return Collections.unmodifiableSortedMap(map);
490              }
491            })
492        .named("unmodifiableSortedMap/TreeMap, natural")
493        .withFeatures(
494            MapFeature.ALLOWS_NULL_VALUES,
495            CollectionFeature.KNOWN_ORDER,
496            CollectionFeature.SERIALIZABLE,
497            CollectionSize.ANY)
498        .suppressing(suppressForUnmodifiableSortedMap())
499        .createTestSuite();
500  }
501
502  public Test testsForEnumMap() {
503    return MapTestSuiteBuilder.using(
504            new TestEnumMapGenerator() {
505              @Override
506              protected Map<AnEnum, String> create(Entry<AnEnum, String>[] entries) {
507                return populate(new EnumMap<AnEnum, String>(AnEnum.class), entries);
508              }
509            })
510        .named("EnumMap")
511        .withFeatures(
512            MapFeature.GENERAL_PURPOSE,
513            MapFeature.ALLOWS_NULL_VALUES,
514            MapFeature.RESTRICTS_KEYS,
515            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
516            CollectionFeature.KNOWN_ORDER,
517            CollectionFeature.SERIALIZABLE,
518            CollectionSize.ANY)
519        .suppressing(suppressForEnumMap())
520        .createTestSuite();
521  }
522
523  public Test testsForConcurrentHashMap() {
524    return MapTestSuiteBuilder.using(
525            new TestStringMapGenerator() {
526              @Override
527              protected Map<String, String> create(Entry<String, String>[] entries) {
528                return populate(new ConcurrentHashMap<String, String>(), entries);
529              }
530            })
531        .named("ConcurrentHashMap")
532        .withFeatures(
533            MapFeature.GENERAL_PURPOSE,
534            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
535            CollectionFeature.SERIALIZABLE,
536            CollectionSize.ANY)
537        .suppressing(suppressForConcurrentHashMap())
538        .createTestSuite();
539  }
540
541  public Test testsForConcurrentSkipListMapNatural() {
542    return NavigableMapTestSuiteBuilder.using(
543            new TestStringSortedMapGenerator() {
544              @Override
545              protected SortedMap<String, String> create(Entry<String, String>[] entries) {
546                return populate(new ConcurrentSkipListMap<String, String>(), entries);
547              }
548            })
549        .named("ConcurrentSkipListMap, natural")
550        .withFeatures(
551            MapFeature.GENERAL_PURPOSE,
552            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
553            CollectionFeature.KNOWN_ORDER,
554            CollectionFeature.SERIALIZABLE,
555            CollectionSize.ANY)
556        .suppressing(suppressForConcurrentSkipListMap())
557        .createTestSuite();
558  }
559
560  public Test testsForConcurrentSkipListMapWithComparator() {
561    return NavigableMapTestSuiteBuilder.using(
562            new TestStringSortedMapGenerator() {
563              @Override
564              protected SortedMap<String, String> create(Entry<String, String>[] entries) {
565                return populate(
566                    new ConcurrentSkipListMap<String, String>(arbitraryNullFriendlyComparator()),
567                    entries);
568              }
569            })
570        .named("ConcurrentSkipListMap, with comparator")
571        .withFeatures(
572            MapFeature.GENERAL_PURPOSE,
573            CollectionFeature.SUPPORTS_ITERATOR_REMOVE,
574            CollectionFeature.KNOWN_ORDER,
575            CollectionFeature.SERIALIZABLE,
576            CollectionSize.ANY)
577        .suppressing(suppressForConcurrentSkipListMap())
578        .createTestSuite();
579  }
580
581  // TODO: IdentityHashMap, AbstractMap
582
583  private static Map<String, String> toHashMap(Entry<String, String>[] entries) {
584    return populate(new HashMap<String, String>(), entries);
585  }
586
587  // TODO: call conversion constructors or factory methods instead of using
588  // populate() on an empty map
589  @CanIgnoreReturnValue
590  private static <T, M extends Map<T, String>> M populate(M map, Entry<T, String>[] entries) {
591    for (Entry<T, String> entry : entries) {
592      map.put(entry.getKey(), entry.getValue());
593    }
594    return map;
595  }
596
597  static <T> Comparator<T> arbitraryNullFriendlyComparator() {
598    return new NullFriendlyComparator<>();
599  }
600
601  private static final class NullFriendlyComparator<T> implements Comparator<T>, Serializable {
602    @Override
603    public int compare(T left, T right) {
604      return String.valueOf(left).compareTo(String.valueOf(right));
605    }
606  }
607}