1 /*
2  * Copyright (C) 2008 The Guava Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.common.collect.testing;
18 
19 import static com.google.common.collect.testing.DerivedCollectionGenerators.keySetGenerator;
20 
21 import com.google.common.collect.testing.DerivedCollectionGenerators.MapEntrySetGenerator;
22 import com.google.common.collect.testing.DerivedCollectionGenerators.MapValueCollectionGenerator;
23 import com.google.common.collect.testing.features.CollectionFeature;
24 import com.google.common.collect.testing.features.CollectionSize;
25 import com.google.common.collect.testing.features.Feature;
26 import com.google.common.collect.testing.features.MapFeature;
27 import com.google.common.collect.testing.testers.MapClearTester;
28 import com.google.common.collect.testing.testers.MapContainsKeyTester;
29 import com.google.common.collect.testing.testers.MapContainsValueTester;
30 import com.google.common.collect.testing.testers.MapCreationTester;
31 import com.google.common.collect.testing.testers.MapEntrySetTester;
32 import com.google.common.collect.testing.testers.MapEqualsTester;
33 import com.google.common.collect.testing.testers.MapGetTester;
34 import com.google.common.collect.testing.testers.MapHashCodeTester;
35 import com.google.common.collect.testing.testers.MapIsEmptyTester;
36 import com.google.common.collect.testing.testers.MapPutAllTester;
37 import com.google.common.collect.testing.testers.MapPutTester;
38 import com.google.common.collect.testing.testers.MapRemoveTester;
39 import com.google.common.collect.testing.testers.MapSerializationTester;
40 import com.google.common.collect.testing.testers.MapSizeTester;
41 import com.google.common.collect.testing.testers.MapToStringTester;
42 import com.google.common.testing.SerializableTester;
43 
44 import junit.framework.TestSuite;
45 
46 import java.util.Arrays;
47 import java.util.HashSet;
48 import java.util.List;
49 import java.util.Map;
50 import java.util.Set;
51 
52 /**
53  * Creates, based on your criteria, a JUnit test suite that exhaustively tests
54  * a Map implementation.
55  *
56  * @author George van den Driessche
57  */
58 public class MapTestSuiteBuilder<K, V>
59     extends PerCollectionSizeTestSuiteBuilder<
60         MapTestSuiteBuilder<K, V>,
61         TestMapGenerator<K, V>, Map<K, V>, Map.Entry<K, V>> {
62   public static <K, V> MapTestSuiteBuilder<K, V> using(
63       TestMapGenerator<K, V> generator) {
64     return new MapTestSuiteBuilder<K, V>().usingGenerator(generator);
65   }
66 
67   @SuppressWarnings("unchecked") // Class parameters must be raw.
68   @Override protected List<Class<? extends AbstractTester>> getTesters() {
69     return Arrays.<Class<? extends AbstractTester>>asList(
70         MapClearTester.class,
71         MapContainsKeyTester.class,
72         MapContainsValueTester.class,
73         MapCreationTester.class,
74         MapEntrySetTester.class,
75         MapEqualsTester.class,
76         MapGetTester.class,
77         MapHashCodeTester.class,
78         MapIsEmptyTester.class,
79         MapPutTester.class,
80         MapPutAllTester.class,
81         MapRemoveTester.class,
82         MapSerializationTester.class,
83         MapSizeTester.class,
84         MapToStringTester.class
85     );
86   }
87 
88   @Override
89   protected List<TestSuite> createDerivedSuites(
90       FeatureSpecificTestSuiteBuilder<
91           ?,
92           ? extends OneSizeTestContainerGenerator<Map<K, V>, Map.Entry<K, V>>>
93       parentBuilder) {
94     // TODO: Once invariant support is added, supply invariants to each of the
95     // derived suites, to check that mutations to the derived collections are
96     // reflected in the underlying map.
97 
98     List<TestSuite> derivedSuites = super.createDerivedSuites(parentBuilder);
99 
100     if (parentBuilder.getFeatures().contains(CollectionFeature.SERIALIZABLE)) {
101       derivedSuites.add(MapTestSuiteBuilder.using(
102               new ReserializedMapGenerator<K, V>(parentBuilder.getSubjectGenerator()))
103           .withFeatures(computeReserializedMapFeatures(parentBuilder.getFeatures()))
104           .named(parentBuilder.getName() + " reserialized")
105           .suppressing(parentBuilder.getSuppressedTests())
106           .createTestSuite());
107     }
108 
109     derivedSuites.add(createDerivedEntrySetSuite(
110             new MapEntrySetGenerator<K, V>(parentBuilder.getSubjectGenerator()))
111         .withFeatures(computeEntrySetFeatures(parentBuilder.getFeatures()))
112         .named(parentBuilder.getName() + " entrySet")
113         .suppressing(parentBuilder.getSuppressedTests())
114         .createTestSuite());
115 
116     derivedSuites.add(createDerivedKeySetSuite(
117             keySetGenerator(parentBuilder.getSubjectGenerator()))
118         .withFeatures(computeKeySetFeatures(parentBuilder.getFeatures()))
119         .named(parentBuilder.getName() + " keys")
120         .suppressing(parentBuilder.getSuppressedTests())
121         .createTestSuite());
122 
123     derivedSuites.add(createDerivedValueCollectionSuite(
124             new MapValueCollectionGenerator<K, V>(
125                 parentBuilder.getSubjectGenerator()))
126         .named(parentBuilder.getName() + " values")
127         .withFeatures(computeValuesCollectionFeatures(
128             parentBuilder.getFeatures()))
129         .suppressing(parentBuilder.getSuppressedTests())
130         .createTestSuite());
131 
132     return derivedSuites;
133   }
134 
135   protected SetTestSuiteBuilder<Map.Entry<K, V>> createDerivedEntrySetSuite(
136       TestSetGenerator<Map.Entry<K, V>> entrySetGenerator) {
137     return SetTestSuiteBuilder.using(entrySetGenerator);
138   }
139 
140   protected SetTestSuiteBuilder<K> createDerivedKeySetSuite(TestSetGenerator<K> keySetGenerator) {
141     return SetTestSuiteBuilder.using(keySetGenerator);
142   }
143 
144   protected CollectionTestSuiteBuilder<V> createDerivedValueCollectionSuite(
145       TestCollectionGenerator<V> valueCollectionGenerator) {
146     return CollectionTestSuiteBuilder.using(valueCollectionGenerator);
147   }
148 
149   private static Set<Feature<?>> computeReserializedMapFeatures(
150       Set<Feature<?>> mapFeatures) {
151     Set<Feature<?>> derivedFeatures = Helpers.copyToSet(mapFeatures);
152     derivedFeatures.remove(CollectionFeature.SERIALIZABLE);
153     derivedFeatures.remove(CollectionFeature.SERIALIZABLE_INCLUDING_VIEWS);
154     return derivedFeatures;
155   }
156 
157   private static Set<Feature<?>> computeEntrySetFeatures(
158       Set<Feature<?>> mapFeatures) {
159     Set<Feature<?>> entrySetFeatures =
160         computeCommonDerivedCollectionFeatures(mapFeatures);
161     if (mapFeatures.contains(MapFeature.ALLOWS_NULL_ENTRY_QUERIES)) {
162       entrySetFeatures.add(CollectionFeature.ALLOWS_NULL_QUERIES);
163     }
164     return entrySetFeatures;
165   }
166 
167   private static Set<Feature<?>> computeKeySetFeatures(
168       Set<Feature<?>> mapFeatures) {
169     Set<Feature<?>> keySetFeatures =
170         computeCommonDerivedCollectionFeatures(mapFeatures);
171 
172     // TODO(user): make this trigger only if the map is a submap
173     // currently, the KeySetGenerator won't work properly for a subset of a keyset of a submap
174     keySetFeatures.add(CollectionFeature.SUBSET_VIEW);
175     if (mapFeatures.contains(MapFeature.ALLOWS_NULL_KEYS)) {
176       keySetFeatures.add(CollectionFeature.ALLOWS_NULL_VALUES);
177     } else if (mapFeatures.contains(MapFeature.ALLOWS_NULL_KEY_QUERIES)) {
178       keySetFeatures.add(CollectionFeature.ALLOWS_NULL_QUERIES);
179     }
180 
181     return keySetFeatures;
182   }
183 
184   private static Set<Feature<?>> computeValuesCollectionFeatures(
185       Set<Feature<?>> mapFeatures) {
186     Set<Feature<?>> valuesCollectionFeatures =
187         computeCommonDerivedCollectionFeatures(mapFeatures);
188     if (mapFeatures.contains(MapFeature.ALLOWS_NULL_VALUE_QUERIES)) {
189       valuesCollectionFeatures.add(CollectionFeature.ALLOWS_NULL_QUERIES);
190     }
191     if (mapFeatures.contains(MapFeature.ALLOWS_NULL_VALUES)) {
192       valuesCollectionFeatures.add(CollectionFeature.ALLOWS_NULL_VALUES);
193     }
194 
195     return valuesCollectionFeatures;
196   }
197 
198   public static Set<Feature<?>> computeCommonDerivedCollectionFeatures(
199       Set<Feature<?>> mapFeatures) {
200     mapFeatures = new HashSet<Feature<?>>(mapFeatures);
201     Set<Feature<?>> derivedFeatures = new HashSet<Feature<?>>();
202     mapFeatures.remove(CollectionFeature.SERIALIZABLE);
203     if (mapFeatures.remove(CollectionFeature.SERIALIZABLE_INCLUDING_VIEWS)) {
204       derivedFeatures.add(CollectionFeature.SERIALIZABLE);
205     }
206     if (mapFeatures.contains(MapFeature.SUPPORTS_REMOVE)) {
207       derivedFeatures.add(CollectionFeature.SUPPORTS_REMOVE);
208     }
209     if (mapFeatures.contains(MapFeature.REJECTS_DUPLICATES_AT_CREATION)) {
210       derivedFeatures.add(CollectionFeature.REJECTS_DUPLICATES_AT_CREATION);
211     }
212     if (mapFeatures.contains(MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION)) {
213       derivedFeatures.add(CollectionFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION);
214     }
215     // add the intersection of CollectionFeature.values() and mapFeatures
216     for (CollectionFeature feature : CollectionFeature.values()) {
217       if (mapFeatures.contains(feature)) {
218         derivedFeatures.add(feature);
219       }
220     }
221     // add the intersection of CollectionSize.values() and mapFeatures
222     for (CollectionSize size : CollectionSize.values()) {
223       if (mapFeatures.contains(size)) {
224         derivedFeatures.add(size);
225       }
226     }
227     return derivedFeatures;
228   }
229 
230   private static class ReserializedMapGenerator<K, V>
231       implements TestMapGenerator<K, V> {
232     private final OneSizeTestContainerGenerator<Map<K, V>, Map.Entry<K, V>>
233         mapGenerator;
234 
235     public ReserializedMapGenerator(
236         OneSizeTestContainerGenerator<
237             Map<K, V>, Map.Entry<K, V>> mapGenerator) {
238       this.mapGenerator = mapGenerator;
239     }
240 
241     @Override
242     public SampleElements<Map.Entry<K, V>> samples() {
243       return mapGenerator.samples();
244     }
245 
246     @Override
247     public Map.Entry<K, V>[] createArray(int length) {
248       return mapGenerator.createArray(length);
249     }
250 
251     @Override
252     public Iterable<Map.Entry<K, V>> order(
253         List<Map.Entry<K, V>> insertionOrder) {
254       return mapGenerator.order(insertionOrder);
255     }
256 
257     @Override
258     public Map<K, V> create(Object... elements) {
259       return SerializableTester.reserialize(mapGenerator.create(elements));
260     }
261 
262     @Override
263     public K[] createKeyArray(int length) {
264       return ((TestMapGenerator<K, V>) mapGenerator.getInnerGenerator())
265           .createKeyArray(length);
266     }
267 
268     @Override
269     public V[] createValueArray(int length) {
270       return ((TestMapGenerator<K, V>) mapGenerator.getInnerGenerator())
271         .createValueArray(length);
272     }
273   }
274 }
275