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