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;
021import static com.google.common.testing.SerializableTester.reserialize;
022import static java.util.Arrays.asList;
023
024import com.google.common.annotations.GwtIncompatible;
025import com.google.common.collect.testing.DerivedCollectionGenerators.MapEntrySetGenerator;
026import com.google.common.collect.testing.DerivedCollectionGenerators.MapValueCollectionGenerator;
027import com.google.common.collect.testing.features.CollectionFeature;
028import com.google.common.collect.testing.features.CollectionSize;
029import com.google.common.collect.testing.features.Feature;
030import com.google.common.collect.testing.features.MapFeature;
031import com.google.common.collect.testing.testers.MapClearTester;
032import com.google.common.collect.testing.testers.MapComputeIfAbsentTester;
033import com.google.common.collect.testing.testers.MapComputeIfPresentTester;
034import com.google.common.collect.testing.testers.MapComputeTester;
035import com.google.common.collect.testing.testers.MapContainsKeyTester;
036import com.google.common.collect.testing.testers.MapContainsValueTester;
037import com.google.common.collect.testing.testers.MapCreationTester;
038import com.google.common.collect.testing.testers.MapEntrySetTester;
039import com.google.common.collect.testing.testers.MapEqualsTester;
040import com.google.common.collect.testing.testers.MapForEachTester;
041import com.google.common.collect.testing.testers.MapGetOrDefaultTester;
042import com.google.common.collect.testing.testers.MapGetTester;
043import com.google.common.collect.testing.testers.MapHashCodeTester;
044import com.google.common.collect.testing.testers.MapIsEmptyTester;
045import com.google.common.collect.testing.testers.MapMergeTester;
046import com.google.common.collect.testing.testers.MapPutAllTester;
047import com.google.common.collect.testing.testers.MapPutIfAbsentTester;
048import com.google.common.collect.testing.testers.MapPutTester;
049import com.google.common.collect.testing.testers.MapRemoveEntryTester;
050import com.google.common.collect.testing.testers.MapRemoveTester;
051import com.google.common.collect.testing.testers.MapReplaceAllTester;
052import com.google.common.collect.testing.testers.MapReplaceEntryTester;
053import com.google.common.collect.testing.testers.MapReplaceTester;
054import com.google.common.collect.testing.testers.MapSerializationTester;
055import com.google.common.collect.testing.testers.MapSizeTester;
056import com.google.common.collect.testing.testers.MapToStringTester;
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 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          using(new ReserializedMapGenerator<K, V>(parentBuilder.getSubjectGenerator()))
123              .withFeatures(computeReserializedMapFeatures(parentBuilder.getFeatures()))
124              .named(parentBuilder.getName() + " reserialized")
125              .suppressing(parentBuilder.getSuppressedTests())
126              .withSetUp(parentBuilder.getSetUp())
127              .withTearDown(parentBuilder.getTearDown())
128              .createTestSuite());
129    }
130
131    derivedSuites.add(
132        createDerivedEntrySetSuite(
133                new MapEntrySetGenerator<K, V>(parentBuilder.getSubjectGenerator()))
134            .withFeatures(computeEntrySetFeatures(parentBuilder.getFeatures()))
135            .named(parentBuilder.getName() + " entrySet")
136            .suppressing(parentBuilder.getSuppressedTests())
137            .withSetUp(parentBuilder.getSetUp())
138            .withTearDown(parentBuilder.getTearDown())
139            .createTestSuite());
140
141    derivedSuites.add(
142        createDerivedKeySetSuite(keySetGenerator(parentBuilder.getSubjectGenerator()))
143            .withFeatures(computeKeySetFeatures(parentBuilder.getFeatures()))
144            .named(parentBuilder.getName() + " keys")
145            .suppressing(parentBuilder.getSuppressedTests())
146            .withSetUp(parentBuilder.getSetUp())
147            .withTearDown(parentBuilder.getTearDown())
148            .createTestSuite());
149
150    derivedSuites.add(
151        createDerivedValueCollectionSuite(
152                new MapValueCollectionGenerator<K, V>(parentBuilder.getSubjectGenerator()))
153            .named(parentBuilder.getName() + " values")
154            .withFeatures(computeValuesCollectionFeatures(parentBuilder.getFeatures()))
155            .suppressing(parentBuilder.getSuppressedTests())
156            .withSetUp(parentBuilder.getSetUp())
157            .withTearDown(parentBuilder.getTearDown())
158            .createTestSuite());
159
160    return derivedSuites;
161  }
162
163  protected SetTestSuiteBuilder<Entry<K, V>> createDerivedEntrySetSuite(
164      TestSetGenerator<Entry<K, V>> entrySetGenerator) {
165    return SetTestSuiteBuilder.using(entrySetGenerator);
166  }
167
168  protected SetTestSuiteBuilder<K> createDerivedKeySetSuite(TestSetGenerator<K> keySetGenerator) {
169    return SetTestSuiteBuilder.using(keySetGenerator);
170  }
171
172  protected CollectionTestSuiteBuilder<V> createDerivedValueCollectionSuite(
173      TestCollectionGenerator<V> valueCollectionGenerator) {
174    return CollectionTestSuiteBuilder.using(valueCollectionGenerator);
175  }
176
177  private static Set<Feature<?>> computeReserializedMapFeatures(Set<Feature<?>> mapFeatures) {
178    Set<Feature<?>> derivedFeatures = copyToSet(mapFeatures);
179    derivedFeatures.remove(CollectionFeature.SERIALIZABLE);
180    derivedFeatures.remove(CollectionFeature.SERIALIZABLE_INCLUDING_VIEWS);
181    return derivedFeatures;
182  }
183
184  private static Set<Feature<?>> computeEntrySetFeatures(Set<Feature<?>> mapFeatures) {
185    Set<Feature<?>> entrySetFeatures = computeCommonDerivedCollectionFeatures(mapFeatures);
186    if (mapFeatures.contains(MapFeature.ALLOWS_NULL_ENTRY_QUERIES)) {
187      entrySetFeatures.add(CollectionFeature.ALLOWS_NULL_QUERIES);
188    }
189    return entrySetFeatures;
190  }
191
192  private static Set<Feature<?>> computeKeySetFeatures(Set<Feature<?>> mapFeatures) {
193    Set<Feature<?>> keySetFeatures = computeCommonDerivedCollectionFeatures(mapFeatures);
194
195    // TODO(lowasser): make this trigger only if the map is a submap
196    // currently, the KeySetGenerator won't work properly for a subset of a keyset of a submap
197    keySetFeatures.add(CollectionFeature.SUBSET_VIEW);
198    if (mapFeatures.contains(MapFeature.ALLOWS_NULL_KEYS)) {
199      keySetFeatures.add(CollectionFeature.ALLOWS_NULL_VALUES);
200    } else if (mapFeatures.contains(MapFeature.ALLOWS_NULL_KEY_QUERIES)) {
201      keySetFeatures.add(CollectionFeature.ALLOWS_NULL_QUERIES);
202    }
203
204    return keySetFeatures;
205  }
206
207  private static Set<Feature<?>> computeValuesCollectionFeatures(Set<Feature<?>> mapFeatures) {
208    Set<Feature<?>> valuesCollectionFeatures = computeCommonDerivedCollectionFeatures(mapFeatures);
209    if (mapFeatures.contains(MapFeature.ALLOWS_NULL_VALUE_QUERIES)) {
210      valuesCollectionFeatures.add(CollectionFeature.ALLOWS_NULL_QUERIES);
211    }
212    if (mapFeatures.contains(MapFeature.ALLOWS_NULL_VALUES)) {
213      valuesCollectionFeatures.add(CollectionFeature.ALLOWS_NULL_VALUES);
214    }
215
216    return valuesCollectionFeatures;
217  }
218
219  public static Set<Feature<?>> computeCommonDerivedCollectionFeatures(
220      Set<Feature<?>> mapFeatures) {
221    mapFeatures = new HashSet<>(mapFeatures);
222    Set<Feature<?>> derivedFeatures = new HashSet<>();
223    mapFeatures.remove(CollectionFeature.SERIALIZABLE);
224    if (mapFeatures.remove(CollectionFeature.SERIALIZABLE_INCLUDING_VIEWS)) {
225      derivedFeatures.add(CollectionFeature.SERIALIZABLE);
226    }
227    if (mapFeatures.contains(MapFeature.SUPPORTS_REMOVE)) {
228      derivedFeatures.add(CollectionFeature.SUPPORTS_REMOVE);
229    }
230    if (mapFeatures.contains(MapFeature.REJECTS_DUPLICATES_AT_CREATION)) {
231      derivedFeatures.add(CollectionFeature.REJECTS_DUPLICATES_AT_CREATION);
232    }
233    if (mapFeatures.contains(MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION)) {
234      derivedFeatures.add(CollectionFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION);
235    }
236    // add the intersection of CollectionFeature.values() and mapFeatures
237    for (CollectionFeature feature : CollectionFeature.values()) {
238      if (mapFeatures.contains(feature)) {
239        derivedFeatures.add(feature);
240      }
241    }
242    // add the intersection of CollectionSize.values() and mapFeatures
243    for (CollectionSize size : CollectionSize.values()) {
244      if (mapFeatures.contains(size)) {
245        derivedFeatures.add(size);
246      }
247    }
248    return derivedFeatures;
249  }
250
251  private static final class ReserializedMapGenerator<K, V> implements TestMapGenerator<K, V> {
252    private final OneSizeTestContainerGenerator<Map<K, V>, Entry<K, V>> mapGenerator;
253
254    ReserializedMapGenerator(OneSizeTestContainerGenerator<Map<K, V>, Entry<K, V>> mapGenerator) {
255      this.mapGenerator = mapGenerator;
256    }
257
258    @Override
259    public SampleElements<Entry<K, V>> samples() {
260      return mapGenerator.samples();
261    }
262
263    @Override
264    public Entry<K, V>[] createArray(int length) {
265      return mapGenerator.createArray(length);
266    }
267
268    @Override
269    public Iterable<Entry<K, V>> order(List<Entry<K, V>> insertionOrder) {
270      return mapGenerator.order(insertionOrder);
271    }
272
273    @Override
274    public Map<K, V> create(Object... elements) {
275      return reserialize(mapGenerator.create(elements));
276    }
277
278    @Override
279    public K[] createKeyArray(int length) {
280      return ((TestMapGenerator<K, V>) mapGenerator.getInnerGenerator()).createKeyArray(length);
281    }
282
283    @Override
284    public V[] createValueArray(int length) {
285      return ((TestMapGenerator<K, V>) mapGenerator.getInnerGenerator()).createValueArray(length);
286    }
287  }
288}