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("unchecked") // Class parameters must be raw.
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              .createTestSuite());
127    }
128
129    derivedSuites.add(
130        createDerivedEntrySetSuite(
131                new MapEntrySetGenerator<K, V>(parentBuilder.getSubjectGenerator()))
132            .withFeatures(computeEntrySetFeatures(parentBuilder.getFeatures()))
133            .named(parentBuilder.getName() + " entrySet")
134            .suppressing(parentBuilder.getSuppressedTests())
135            .createTestSuite());
136
137    derivedSuites.add(
138        createDerivedKeySetSuite(keySetGenerator(parentBuilder.getSubjectGenerator()))
139            .withFeatures(computeKeySetFeatures(parentBuilder.getFeatures()))
140            .named(parentBuilder.getName() + " keys")
141            .suppressing(parentBuilder.getSuppressedTests())
142            .createTestSuite());
143
144    derivedSuites.add(
145        createDerivedValueCollectionSuite(
146                new MapValueCollectionGenerator<K, V>(parentBuilder.getSubjectGenerator()))
147            .named(parentBuilder.getName() + " values")
148            .withFeatures(computeValuesCollectionFeatures(parentBuilder.getFeatures()))
149            .suppressing(parentBuilder.getSuppressedTests())
150            .createTestSuite());
151
152    return derivedSuites;
153  }
154
155  protected SetTestSuiteBuilder<Entry<K, V>> createDerivedEntrySetSuite(
156      TestSetGenerator<Entry<K, V>> entrySetGenerator) {
157    return SetTestSuiteBuilder.using(entrySetGenerator);
158  }
159
160  protected SetTestSuiteBuilder<K> createDerivedKeySetSuite(TestSetGenerator<K> keySetGenerator) {
161    return SetTestSuiteBuilder.using(keySetGenerator);
162  }
163
164  protected CollectionTestSuiteBuilder<V> createDerivedValueCollectionSuite(
165      TestCollectionGenerator<V> valueCollectionGenerator) {
166    return CollectionTestSuiteBuilder.using(valueCollectionGenerator);
167  }
168
169  private static Set<Feature<?>> computeReserializedMapFeatures(Set<Feature<?>> mapFeatures) {
170    Set<Feature<?>> derivedFeatures = Helpers.copyToSet(mapFeatures);
171    derivedFeatures.remove(CollectionFeature.SERIALIZABLE);
172    derivedFeatures.remove(CollectionFeature.SERIALIZABLE_INCLUDING_VIEWS);
173    return derivedFeatures;
174  }
175
176  private static Set<Feature<?>> computeEntrySetFeatures(Set<Feature<?>> mapFeatures) {
177    Set<Feature<?>> entrySetFeatures = computeCommonDerivedCollectionFeatures(mapFeatures);
178    if (mapFeatures.contains(MapFeature.ALLOWS_NULL_ENTRY_QUERIES)) {
179      entrySetFeatures.add(CollectionFeature.ALLOWS_NULL_QUERIES);
180    }
181    return entrySetFeatures;
182  }
183
184  private static Set<Feature<?>> computeKeySetFeatures(Set<Feature<?>> mapFeatures) {
185    Set<Feature<?>> keySetFeatures = computeCommonDerivedCollectionFeatures(mapFeatures);
186
187    // TODO(lowasser): make this trigger only if the map is a submap
188    // currently, the KeySetGenerator won't work properly for a subset of a keyset of a submap
189    keySetFeatures.add(CollectionFeature.SUBSET_VIEW);
190    if (mapFeatures.contains(MapFeature.ALLOWS_NULL_KEYS)) {
191      keySetFeatures.add(CollectionFeature.ALLOWS_NULL_VALUES);
192    } else if (mapFeatures.contains(MapFeature.ALLOWS_NULL_KEY_QUERIES)) {
193      keySetFeatures.add(CollectionFeature.ALLOWS_NULL_QUERIES);
194    }
195
196    return keySetFeatures;
197  }
198
199  private static Set<Feature<?>> computeValuesCollectionFeatures(Set<Feature<?>> mapFeatures) {
200    Set<Feature<?>> valuesCollectionFeatures = computeCommonDerivedCollectionFeatures(mapFeatures);
201    if (mapFeatures.contains(MapFeature.ALLOWS_NULL_VALUE_QUERIES)) {
202      valuesCollectionFeatures.add(CollectionFeature.ALLOWS_NULL_QUERIES);
203    }
204    if (mapFeatures.contains(MapFeature.ALLOWS_NULL_VALUES)) {
205      valuesCollectionFeatures.add(CollectionFeature.ALLOWS_NULL_VALUES);
206    }
207
208    return valuesCollectionFeatures;
209  }
210
211  public static Set<Feature<?>> computeCommonDerivedCollectionFeatures(
212      Set<Feature<?>> mapFeatures) {
213    mapFeatures = new HashSet<>(mapFeatures);
214    Set<Feature<?>> derivedFeatures = new HashSet<>();
215    mapFeatures.remove(CollectionFeature.SERIALIZABLE);
216    if (mapFeatures.remove(CollectionFeature.SERIALIZABLE_INCLUDING_VIEWS)) {
217      derivedFeatures.add(CollectionFeature.SERIALIZABLE);
218    }
219    if (mapFeatures.contains(MapFeature.SUPPORTS_REMOVE)) {
220      derivedFeatures.add(CollectionFeature.SUPPORTS_REMOVE);
221    }
222    if (mapFeatures.contains(MapFeature.REJECTS_DUPLICATES_AT_CREATION)) {
223      derivedFeatures.add(CollectionFeature.REJECTS_DUPLICATES_AT_CREATION);
224    }
225    if (mapFeatures.contains(MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION)) {
226      derivedFeatures.add(CollectionFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION);
227    }
228    // add the intersection of CollectionFeature.values() and mapFeatures
229    for (CollectionFeature feature : CollectionFeature.values()) {
230      if (mapFeatures.contains(feature)) {
231        derivedFeatures.add(feature);
232      }
233    }
234    // add the intersection of CollectionSize.values() and mapFeatures
235    for (CollectionSize size : CollectionSize.values()) {
236      if (mapFeatures.contains(size)) {
237        derivedFeatures.add(size);
238      }
239    }
240    return derivedFeatures;
241  }
242
243  private static class ReserializedMapGenerator<K, V> implements TestMapGenerator<K, V> {
244    private final OneSizeTestContainerGenerator<Map<K, V>, Entry<K, V>> mapGenerator;
245
246    public ReserializedMapGenerator(
247        OneSizeTestContainerGenerator<Map<K, V>, Entry<K, V>> mapGenerator) {
248      this.mapGenerator = mapGenerator;
249    }
250
251    @Override
252    public SampleElements<Entry<K, V>> samples() {
253      return mapGenerator.samples();
254    }
255
256    @Override
257    public Entry<K, V>[] createArray(int length) {
258      return mapGenerator.createArray(length);
259    }
260
261    @Override
262    public Iterable<Entry<K, V>> order(List<Entry<K, V>> insertionOrder) {
263      return mapGenerator.order(insertionOrder);
264    }
265
266    @Override
267    public Map<K, V> create(Object... elements) {
268      return SerializableTester.reserialize(mapGenerator.create(elements));
269    }
270
271    @Override
272    public K[] createKeyArray(int length) {
273      return ((TestMapGenerator<K, V>) mapGenerator.getInnerGenerator()).createKeyArray(length);
274    }
275
276    @Override
277    public V[] createValueArray(int length) {
278      return ((TestMapGenerator<K, V>) mapGenerator.getInnerGenerator()).createValueArray(length);
279    }
280  }
281}