001/*
002 * Copyright (C) 2008 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 com.google.common.collect.testing.DerivedCollectionGenerators.keySetGenerator;
020
021import com.google.common.annotations.GwtIncompatible;
022import com.google.common.collect.testing.DerivedCollectionGenerators.MapEntrySetGenerator;
023import com.google.common.collect.testing.DerivedCollectionGenerators.MapValueCollectionGenerator;
024import com.google.common.collect.testing.features.CollectionFeature;
025import com.google.common.collect.testing.features.CollectionSize;
026import com.google.common.collect.testing.features.Feature;
027import com.google.common.collect.testing.features.MapFeature;
028import com.google.common.collect.testing.testers.MapClearTester;
029import com.google.common.collect.testing.testers.MapContainsKeyTester;
030import com.google.common.collect.testing.testers.MapContainsValueTester;
031import com.google.common.collect.testing.testers.MapCreationTester;
032import com.google.common.collect.testing.testers.MapEntrySetTester;
033import com.google.common.collect.testing.testers.MapEqualsTester;
034import com.google.common.collect.testing.testers.MapGetTester;
035import com.google.common.collect.testing.testers.MapHashCodeTester;
036import com.google.common.collect.testing.testers.MapIsEmptyTester;
037import com.google.common.collect.testing.testers.MapPutAllTester;
038import com.google.common.collect.testing.testers.MapPutTester;
039import com.google.common.collect.testing.testers.MapRemoveTester;
040import com.google.common.collect.testing.testers.MapSerializationTester;
041import com.google.common.collect.testing.testers.MapSizeTester;
042import com.google.common.collect.testing.testers.MapToStringTester;
043import com.google.common.testing.SerializableTester;
044import java.util.Arrays;
045import java.util.HashSet;
046import java.util.List;
047import java.util.Map;
048import java.util.Map.Entry;
049import java.util.Set;
050import junit.framework.TestSuite;
051
052/**
053 * Creates, based on your criteria, a JUnit test suite that exhaustively tests a Map implementation.
054 *
055 * @author George van den Driessche
056 */
057@GwtIncompatible
058public class MapTestSuiteBuilder<K, V>
059    extends PerCollectionSizeTestSuiteBuilder<
060        MapTestSuiteBuilder<K, V>, TestMapGenerator<K, V>, Map<K, V>, Entry<K, V>> {
061  public static <K, V> MapTestSuiteBuilder<K, V> using(TestMapGenerator<K, V> generator) {
062    return new MapTestSuiteBuilder<K, V>().usingGenerator(generator);
063  }
064
065  @SuppressWarnings("unchecked") // Class parameters must be raw.
066  @Override
067  protected List<Class<? extends AbstractTester>> getTesters() {
068    return Arrays.<Class<? extends AbstractTester>>asList(
069        MapClearTester.class,
070        MapContainsKeyTester.class,
071        MapContainsValueTester.class,
072        MapCreationTester.class,
073        MapEntrySetTester.class,
074        MapEqualsTester.class,
075        MapGetTester.class,
076        MapHashCodeTester.class,
077        MapIsEmptyTester.class,
078        MapPutTester.class,
079        MapPutAllTester.class,
080        MapRemoveTester.class,
081        MapSerializationTester.class,
082        MapSizeTester.class,
083        MapToStringTester.class);
084  }
085
086  @Override
087  protected List<TestSuite> createDerivedSuites(
088      FeatureSpecificTestSuiteBuilder<
089              ?, ? extends OneSizeTestContainerGenerator<Map<K, V>, Entry<K, V>>>
090          parentBuilder) {
091    // TODO: Once invariant support is added, supply invariants to each of the
092    // derived suites, to check that mutations to the derived collections are
093    // reflected in the underlying map.
094
095    List<TestSuite> derivedSuites = super.createDerivedSuites(parentBuilder);
096
097    if (parentBuilder.getFeatures().contains(CollectionFeature.SERIALIZABLE)) {
098      derivedSuites.add(
099          MapTestSuiteBuilder.using(
100                  new ReserializedMapGenerator<K, V>(parentBuilder.getSubjectGenerator()))
101              .withFeatures(computeReserializedMapFeatures(parentBuilder.getFeatures()))
102              .named(parentBuilder.getName() + " reserialized")
103              .suppressing(parentBuilder.getSuppressedTests())
104              .withSetUp(parentBuilder.getSetUp())
105              .withTearDown(parentBuilder.getTearDown())
106              .createTestSuite());
107    }
108
109    derivedSuites.add(
110        createDerivedEntrySetSuite(
111                new MapEntrySetGenerator<K, V>(parentBuilder.getSubjectGenerator()))
112            .withFeatures(computeEntrySetFeatures(parentBuilder.getFeatures()))
113            .named(parentBuilder.getName() + " entrySet")
114            .suppressing(parentBuilder.getSuppressedTests())
115            .withSetUp(parentBuilder.getSetUp())
116            .withTearDown(parentBuilder.getTearDown())
117            .createTestSuite());
118
119    derivedSuites.add(
120        createDerivedKeySetSuite(keySetGenerator(parentBuilder.getSubjectGenerator()))
121            .withFeatures(computeKeySetFeatures(parentBuilder.getFeatures()))
122            .named(parentBuilder.getName() + " keys")
123            .suppressing(parentBuilder.getSuppressedTests())
124            .withSetUp(parentBuilder.getSetUp())
125            .withTearDown(parentBuilder.getTearDown())
126            .createTestSuite());
127
128    derivedSuites.add(
129        createDerivedValueCollectionSuite(
130                new MapValueCollectionGenerator<K, V>(parentBuilder.getSubjectGenerator()))
131            .named(parentBuilder.getName() + " values")
132            .withFeatures(computeValuesCollectionFeatures(parentBuilder.getFeatures()))
133            .suppressing(parentBuilder.getSuppressedTests())
134            .withSetUp(parentBuilder.getSetUp())
135            .withTearDown(parentBuilder.getTearDown())
136            .createTestSuite());
137
138    return derivedSuites;
139  }
140
141  protected SetTestSuiteBuilder<Entry<K, V>> createDerivedEntrySetSuite(
142      TestSetGenerator<Entry<K, V>> entrySetGenerator) {
143    return SetTestSuiteBuilder.using(entrySetGenerator);
144  }
145
146  protected SetTestSuiteBuilder<K> createDerivedKeySetSuite(TestSetGenerator<K> keySetGenerator) {
147    return SetTestSuiteBuilder.using(keySetGenerator);
148  }
149
150  protected CollectionTestSuiteBuilder<V> createDerivedValueCollectionSuite(
151      TestCollectionGenerator<V> valueCollectionGenerator) {
152    return CollectionTestSuiteBuilder.using(valueCollectionGenerator);
153  }
154
155  private static Set<Feature<?>> computeReserializedMapFeatures(Set<Feature<?>> mapFeatures) {
156    Set<Feature<?>> derivedFeatures = Helpers.copyToSet(mapFeatures);
157    derivedFeatures.remove(CollectionFeature.SERIALIZABLE);
158    derivedFeatures.remove(CollectionFeature.SERIALIZABLE_INCLUDING_VIEWS);
159    return derivedFeatures;
160  }
161
162  private static Set<Feature<?>> computeEntrySetFeatures(Set<Feature<?>> mapFeatures) {
163    Set<Feature<?>> entrySetFeatures = computeCommonDerivedCollectionFeatures(mapFeatures);
164    if (mapFeatures.contains(MapFeature.ALLOWS_NULL_ENTRY_QUERIES)) {
165      entrySetFeatures.add(CollectionFeature.ALLOWS_NULL_QUERIES);
166    }
167    return entrySetFeatures;
168  }
169
170  private static Set<Feature<?>> computeKeySetFeatures(Set<Feature<?>> mapFeatures) {
171    Set<Feature<?>> keySetFeatures = computeCommonDerivedCollectionFeatures(mapFeatures);
172
173    // TODO(lowasser): make this trigger only if the map is a submap
174    // currently, the KeySetGenerator won't work properly for a subset of a keyset of a submap
175    keySetFeatures.add(CollectionFeature.SUBSET_VIEW);
176    if (mapFeatures.contains(MapFeature.ALLOWS_NULL_KEYS)) {
177      keySetFeatures.add(CollectionFeature.ALLOWS_NULL_VALUES);
178    } else if (mapFeatures.contains(MapFeature.ALLOWS_NULL_KEY_QUERIES)) {
179      keySetFeatures.add(CollectionFeature.ALLOWS_NULL_QUERIES);
180    }
181
182    return keySetFeatures;
183  }
184
185  private static Set<Feature<?>> computeValuesCollectionFeatures(Set<Feature<?>> mapFeatures) {
186    Set<Feature<?>> valuesCollectionFeatures = computeCommonDerivedCollectionFeatures(mapFeatures);
187    if (mapFeatures.contains(MapFeature.ALLOWS_NULL_VALUE_QUERIES)) {
188      valuesCollectionFeatures.add(CollectionFeature.ALLOWS_NULL_QUERIES);
189    }
190    if (mapFeatures.contains(MapFeature.ALLOWS_NULL_VALUES)) {
191      valuesCollectionFeatures.add(CollectionFeature.ALLOWS_NULL_VALUES);
192    }
193
194    return valuesCollectionFeatures;
195  }
196
197  public static Set<Feature<?>> computeCommonDerivedCollectionFeatures(
198      Set<Feature<?>> mapFeatures) {
199    mapFeatures = new HashSet<>(mapFeatures);
200    Set<Feature<?>> derivedFeatures = new HashSet<>();
201    mapFeatures.remove(CollectionFeature.SERIALIZABLE);
202    if (mapFeatures.remove(CollectionFeature.SERIALIZABLE_INCLUDING_VIEWS)) {
203      derivedFeatures.add(CollectionFeature.SERIALIZABLE);
204    }
205    if (mapFeatures.contains(MapFeature.SUPPORTS_REMOVE)) {
206      derivedFeatures.add(CollectionFeature.SUPPORTS_REMOVE);
207    }
208    if (mapFeatures.contains(MapFeature.REJECTS_DUPLICATES_AT_CREATION)) {
209      derivedFeatures.add(CollectionFeature.REJECTS_DUPLICATES_AT_CREATION);
210    }
211    if (mapFeatures.contains(MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION)) {
212      derivedFeatures.add(CollectionFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION);
213    }
214    // add the intersection of CollectionFeature.values() and mapFeatures
215    for (CollectionFeature feature : CollectionFeature.values()) {
216      if (mapFeatures.contains(feature)) {
217        derivedFeatures.add(feature);
218      }
219    }
220    // add the intersection of CollectionSize.values() and mapFeatures
221    for (CollectionSize size : CollectionSize.values()) {
222      if (mapFeatures.contains(size)) {
223        derivedFeatures.add(size);
224      }
225    }
226    return derivedFeatures;
227  }
228
229  private static class ReserializedMapGenerator<K, V> implements TestMapGenerator<K, V> {
230    private final OneSizeTestContainerGenerator<Map<K, V>, Entry<K, V>> mapGenerator;
231
232    public ReserializedMapGenerator(
233        OneSizeTestContainerGenerator<Map<K, V>, Entry<K, V>> mapGenerator) {
234      this.mapGenerator = mapGenerator;
235    }
236
237    @Override
238    public SampleElements<Entry<K, V>> samples() {
239      return mapGenerator.samples();
240    }
241
242    @Override
243    public Entry<K, V>[] createArray(int length) {
244      return mapGenerator.createArray(length);
245    }
246
247    @Override
248    public Iterable<Entry<K, V>> order(List<Entry<K, V>> insertionOrder) {
249      return mapGenerator.order(insertionOrder);
250    }
251
252    @Override
253    public Map<K, V> create(Object... elements) {
254      return SerializableTester.reserialize(mapGenerator.create(elements));
255    }
256
257    @Override
258    public K[] createKeyArray(int length) {
259      return ((TestMapGenerator<K, V>) mapGenerator.getInnerGenerator()).createKeyArray(length);
260    }
261
262    @Override
263    public V[] createValueArray(int length) {
264      return ((TestMapGenerator<K, V>) mapGenerator.getInnerGenerator()).createValueArray(length);
265    }
266  }
267}