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