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.MapComputeIfAbsentTester;
030import com.google.common.collect.testing.testers.MapComputeIfPresentTester;
031import com.google.common.collect.testing.testers.MapComputeTester;
032import com.google.common.collect.testing.testers.MapContainsKeyTester;
033import com.google.common.collect.testing.testers.MapContainsValueTester;
034import com.google.common.collect.testing.testers.MapCreationTester;
035import com.google.common.collect.testing.testers.MapEntrySetTester;
036import com.google.common.collect.testing.testers.MapEqualsTester;
037import com.google.common.collect.testing.testers.MapForEachTester;
038import com.google.common.collect.testing.testers.MapGetOrDefaultTester;
039import com.google.common.collect.testing.testers.MapGetTester;
040import com.google.common.collect.testing.testers.MapHashCodeTester;
041import com.google.common.collect.testing.testers.MapIsEmptyTester;
042import com.google.common.collect.testing.testers.MapMergeTester;
043import com.google.common.collect.testing.testers.MapPutAllTester;
044import com.google.common.collect.testing.testers.MapPutIfAbsentTester;
045import com.google.common.collect.testing.testers.MapPutTester;
046import com.google.common.collect.testing.testers.MapRemoveEntryTester;
047import com.google.common.collect.testing.testers.MapRemoveTester;
048import com.google.common.collect.testing.testers.MapReplaceAllTester;
049import com.google.common.collect.testing.testers.MapReplaceEntryTester;
050import com.google.common.collect.testing.testers.MapReplaceTester;
051import com.google.common.collect.testing.testers.MapSerializationTester;
052import com.google.common.collect.testing.testers.MapSizeTester;
053import com.google.common.collect.testing.testers.MapToStringTester;
054import com.google.common.testing.SerializableTester;
055import java.util.Arrays;
056import java.util.HashSet;
057import java.util.List;
058import java.util.Map;
059import java.util.Map.Entry;
060import java.util.Set;
061import junit.framework.TestSuite;
062
063/**
064 * Creates, based on your criteria, a JUnit test suite that exhaustively tests a Map implementation.
065 *
066 * @author George van den Driessche
067 */
068@GwtIncompatible
069public class MapTestSuiteBuilder<K, V>
070    extends PerCollectionSizeTestSuiteBuilder<
071        MapTestSuiteBuilder<K, V>, TestMapGenerator<K, V>, Map<K, V>, Entry<K, V>> {
072  public static <K, V> MapTestSuiteBuilder<K, V> using(TestMapGenerator<K, V> generator) {
073    return new MapTestSuiteBuilder<K, V>().usingGenerator(generator);
074  }
075
076  @SuppressWarnings("rawtypes") // class literals
077  @Override
078  protected List<Class<? extends AbstractTester>> getTesters() {
079    return Arrays.<Class<? extends AbstractTester>>asList(
080        MapClearTester.class,
081        MapComputeTester.class,
082        MapComputeIfAbsentTester.class,
083        MapComputeIfPresentTester.class,
084        MapContainsKeyTester.class,
085        MapContainsValueTester.class,
086        MapCreationTester.class,
087        MapEntrySetTester.class,
088        MapEqualsTester.class,
089        MapForEachTester.class,
090        MapGetTester.class,
091        MapGetOrDefaultTester.class,
092        MapHashCodeTester.class,
093        MapIsEmptyTester.class,
094        MapMergeTester.class,
095        MapPutTester.class,
096        MapPutAllTester.class,
097        MapPutIfAbsentTester.class,
098        MapRemoveTester.class,
099        MapRemoveEntryTester.class,
100        MapReplaceTester.class,
101        MapReplaceAllTester.class,
102        MapReplaceEntryTester.class,
103        MapSerializationTester.class,
104        MapSizeTester.class,
105        MapToStringTester.class);
106  }
107
108  @Override
109  protected List<TestSuite> createDerivedSuites(
110      FeatureSpecificTestSuiteBuilder<
111              ?, ? extends OneSizeTestContainerGenerator<Map<K, V>, Entry<K, V>>>
112          parentBuilder) {
113    // TODO: Once invariant support is added, supply invariants to each of the
114    // derived suites, to check that mutations to the derived collections are
115    // reflected in the underlying map.
116
117    List<TestSuite> derivedSuites = super.createDerivedSuites(parentBuilder);
118
119    if (parentBuilder.getFeatures().contains(CollectionFeature.SERIALIZABLE)) {
120      derivedSuites.add(
121          MapTestSuiteBuilder.using(
122                  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 = Helpers.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 class ReserializedMapGenerator<K, V> implements TestMapGenerator<K, V> {
252    private final OneSizeTestContainerGenerator<Map<K, V>, Entry<K, V>> mapGenerator;
253
254    public ReserializedMapGenerator(
255        OneSizeTestContainerGenerator<Map<K, V>, Entry<K, V>> mapGenerator) {
256      this.mapGenerator = mapGenerator;
257    }
258
259    @Override
260    public SampleElements<Entry<K, V>> samples() {
261      return mapGenerator.samples();
262    }
263
264    @Override
265    public Entry<K, V>[] createArray(int length) {
266      return mapGenerator.createArray(length);
267    }
268
269    @Override
270    public Iterable<Entry<K, V>> order(List<Entry<K, V>> insertionOrder) {
271      return mapGenerator.order(insertionOrder);
272    }
273
274    @Override
275    public Map<K, V> create(Object... elements) {
276      return SerializableTester.reserialize(mapGenerator.create(elements));
277    }
278
279    @Override
280    public K[] createKeyArray(int length) {
281      return ((TestMapGenerator<K, V>) mapGenerator.getInnerGenerator()).createKeyArray(length);
282    }
283
284    @Override
285    public V[] createValueArray(int length) {
286      return ((TestMapGenerator<K, V>) mapGenerator.getInnerGenerator()).createValueArray(length);
287    }
288  }
289}