/* * Copyright (C) 2012 The Guava Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.common.collect.testing.google; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.testing.Helpers.mapEntry; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Multiset; import com.google.common.collect.testing.AbstractTester; import com.google.common.collect.testing.CollectionTestSuiteBuilder; import com.google.common.collect.testing.DerivedGenerator; import com.google.common.collect.testing.FeatureSpecificTestSuiteBuilder; import com.google.common.collect.testing.Helpers; import com.google.common.collect.testing.MapTestSuiteBuilder; import com.google.common.collect.testing.OneSizeTestContainerGenerator; import com.google.common.collect.testing.PerCollectionSizeTestSuiteBuilder; import com.google.common.collect.testing.SampleElements; import com.google.common.collect.testing.TestCollectionGenerator; import com.google.common.collect.testing.TestMapGenerator; import com.google.common.collect.testing.TestSubjectGenerator; import com.google.common.collect.testing.features.CollectionFeature; import com.google.common.collect.testing.features.Feature; import com.google.common.collect.testing.features.MapFeature; import com.google.common.testing.SerializableTester; import junit.framework.TestSuite; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; /** * Creates, based on your criteria, a JUnit test suite that exhaustively tests * a {@code Multimap} implementation. * * @author Louis Wasserman */ public class MultimapTestSuiteBuilder<K, V, M extends Multimap<K, V>> extends PerCollectionSizeTestSuiteBuilder< MultimapTestSuiteBuilder<K, V, M>, TestMultimapGenerator<K, V, M>, M, Map.Entry<K, V>> { public static <K, V, M extends Multimap<K, V>> MultimapTestSuiteBuilder<K, V, M> using( TestMultimapGenerator<K, V, M> generator) { return new MultimapTestSuiteBuilder<K, V, M>().usingGenerator(generator); } // Class parameters must be raw. @Override protected List<Class<? extends AbstractTester>> getTesters() { return ImmutableList.<Class<? extends AbstractTester>> of( MultimapSizeTester.class, MultimapContainsKeyTester.class, MultimapContainsValueTester.class, MultimapContainsEntryTester.class, MultimapGetTester.class, MultimapPutTester.class, MultimapPutIterableTester.class, MultimapRemoveEntryTester.class, MultimapRemoveAllTester.class); } @Override protected List<TestSuite> createDerivedSuites( FeatureSpecificTestSuiteBuilder< ?, ? extends OneSizeTestContainerGenerator<M, Map.Entry<K, V>>> parentBuilder) { // TODO: Once invariant support is added, supply invariants to each of the // derived suites, to check that mutations to the derived collections are // reflected in the underlying map. List<TestSuite> derivedSuites = super.createDerivedSuites(parentBuilder); if (parentBuilder.getFeatures().contains(CollectionFeature.SERIALIZABLE)) { derivedSuites.add(MultimapTestSuiteBuilder.using( new ReserializedMultimapGenerator<K, V, M>(parentBuilder.getSubjectGenerator())) .withFeatures(computeReserializedMultimapFeatures(parentBuilder.getFeatures())) .named(parentBuilder.getName() + " reserialized") .suppressing(parentBuilder.getSuppressedTests()) .createTestSuite()); } derivedSuites.add(MapTestSuiteBuilder.using( new AsMapGenerator<K, V, M>(parentBuilder.getSubjectGenerator())) .withFeatures(computeAsMapFeatures(parentBuilder.getFeatures())) .named(parentBuilder.getName() + ".asMap") .suppressing(parentBuilder.getSuppressedTests()) .createTestSuite()); derivedSuites.add(computeEntriesTestSuite(parentBuilder)); derivedSuites.add(computeMultimapGetTestSuite(parentBuilder)); derivedSuites.add(computeKeysTestSuite(parentBuilder)); return derivedSuites; } TestSuite computeValuesTestSuite( FeatureSpecificTestSuiteBuilder<?, ? extends OneSizeTestContainerGenerator<M, Map.Entry<K, V>>> parentBuilder) { return CollectionTestSuiteBuilder.using( new ValuesGenerator<K, V, M>(parentBuilder.getSubjectGenerator())) .withFeatures(computeValuesFeatures(parentBuilder.getFeatures())) .named(parentBuilder.getName() + ".entries") .suppressing(parentBuilder.getSuppressedTests()) .createTestSuite(); } TestSuite computeEntriesTestSuite( FeatureSpecificTestSuiteBuilder<?, ? extends OneSizeTestContainerGenerator<M, Map.Entry<K, V>>> parentBuilder) { return CollectionTestSuiteBuilder.using( new EntriesGenerator<K, V, M>(parentBuilder.getSubjectGenerator())) .withFeatures(computeEntriesFeatures(parentBuilder.getFeatures())) .named(parentBuilder.getName() + ".entries") .suppressing(parentBuilder.getSuppressedTests()) .createTestSuite(); } TestSuite computeMultimapGetTestSuite( FeatureSpecificTestSuiteBuilder<?, ? extends OneSizeTestContainerGenerator<M, Map.Entry<K, V>>> parentBuilder) { return CollectionTestSuiteBuilder.using( new MultimapGetGenerator<K, V, M>(parentBuilder.getSubjectGenerator())) .withFeatures(computeMultimapGetFeatures(parentBuilder.getFeatures())) .named(parentBuilder.getName() + ".get[key]") .suppressing(parentBuilder.getSuppressedTests()) .createTestSuite(); } TestSuite computeKeysTestSuite( FeatureSpecificTestSuiteBuilder<?, ? extends OneSizeTestContainerGenerator<M, Map.Entry<K, V>>> parentBuilder) { return MultisetTestSuiteBuilder.using( new KeysGenerator<K, V, M>(parentBuilder.getSubjectGenerator())) .withFeatures(computeKeysFeatures(parentBuilder.getFeatures())) .named(parentBuilder.getName() + ".keys") .suppressing(parentBuilder.getSuppressedTests()) .createTestSuite(); } static Set<Feature<?>> computeDerivedCollectionFeatures(Set<Feature<?>> multimapFeatures) { Set<Feature<?>> derivedFeatures = Helpers.copyToSet(multimapFeatures); if (!derivedFeatures.remove(CollectionFeature.SERIALIZABLE_INCLUDING_VIEWS)) { derivedFeatures.remove(CollectionFeature.SERIALIZABLE); } if (derivedFeatures.remove(MapFeature.ALLOWS_NULL_QUERIES)) { derivedFeatures.add(CollectionFeature.ALLOWS_NULL_QUERIES); } if (derivedFeatures.remove(MapFeature.SUPPORTS_REMOVE)) { derivedFeatures.add(CollectionFeature.SUPPORTS_REMOVE); } return derivedFeatures; } static Set<Feature<?>> computeEntriesFeatures( Set<Feature<?>> multimapFeatures) { return computeDerivedCollectionFeatures(multimapFeatures); } static Set<Feature<?>> computeValuesFeatures( Set<Feature<?>> multimapFeatures) { return computeDerivedCollectionFeatures(multimapFeatures); } static Set<Feature<?>> computeKeysFeatures( Set<Feature<?>> multimapFeatures) { Set<Feature<?>> result = computeDerivedCollectionFeatures(multimapFeatures); if (multimapFeatures.contains(MapFeature.ALLOWS_NULL_KEYS)) { result.add(CollectionFeature.ALLOWS_NULL_VALUES); } return result; } private static Set<Feature<?>> computeReserializedMultimapFeatures( Set<Feature<?>> multimapFeatures) { Set<Feature<?>> derivedFeatures = Helpers.copyToSet(multimapFeatures); derivedFeatures.remove(CollectionFeature.SERIALIZABLE); derivedFeatures.remove(CollectionFeature.SERIALIZABLE_INCLUDING_VIEWS); return derivedFeatures; } private static Set<Feature<?>> computeAsMapFeatures( Set<Feature<?>> multimapFeatures) { Set<Feature<?>> derivedFeatures = Helpers.copyToSet(multimapFeatures); derivedFeatures.remove(MapFeature.GENERAL_PURPOSE); derivedFeatures.remove(MapFeature.SUPPORTS_PUT); derivedFeatures.remove(MapFeature.ALLOWS_NULL_VALUES); derivedFeatures.add(MapFeature.REJECTS_DUPLICATES_AT_CREATION); if (!derivedFeatures.contains(CollectionFeature.SERIALIZABLE_INCLUDING_VIEWS)) { derivedFeatures.remove(CollectionFeature.SERIALIZABLE); } return derivedFeatures; } private static final Multimap<Feature<?>, Feature<?>> GET_FEATURE_MAP = ImmutableMultimap .<Feature<?>, Feature<?>> builder() .put( MapFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION, CollectionFeature.FAILS_FAST_ON_CONCURRENT_MODIFICATION) .put(MapFeature.GENERAL_PURPOSE, CollectionFeature.GENERAL_PURPOSE) .put(MapFeature.ALLOWS_NULL_QUERIES, CollectionFeature.ALLOWS_NULL_QUERIES) .put(MapFeature.ALLOWS_NULL_VALUES, CollectionFeature.ALLOWS_NULL_VALUES) .put(MapFeature.SUPPORTS_REMOVE, CollectionFeature.SUPPORTS_REMOVE) .put(MapFeature.SUPPORTS_PUT, CollectionFeature.SUPPORTS_ADD) .build(); Set<Feature<?>> computeMultimapGetFeatures( Set<Feature<?>> multimapFeatures) { Set<Feature<?>> derivedFeatures = Helpers.copyToSet(multimapFeatures); for (Map.Entry<Feature<?>, Feature<?>> entry : GET_FEATURE_MAP.entries()) { if (derivedFeatures.contains(entry.getKey())) { derivedFeatures.add(entry.getValue()); } } if (!derivedFeatures.contains(CollectionFeature.SERIALIZABLE_INCLUDING_VIEWS)) { derivedFeatures.remove(CollectionFeature.SERIALIZABLE); } derivedFeatures.removeAll(GET_FEATURE_MAP.keySet()); return derivedFeatures; } private static class AsMapGenerator<K, V, M extends Multimap<K, V>> implements TestMapGenerator<K, Collection<V>>, DerivedGenerator { private final OneSizeTestContainerGenerator<M, Map.Entry<K, V>> multimapGenerator; public AsMapGenerator( OneSizeTestContainerGenerator<M, Entry<K, V>> multimapGenerator) { this.multimapGenerator = multimapGenerator; } @Override public TestSubjectGenerator<?> getInnerGenerator() { return multimapGenerator; } private Collection<V> createCollection(V v) { return ((TestMultimapGenerator<K, V, M>) multimapGenerator.getInnerGenerator()) .createCollection(Collections.singleton(v)); } @Override public SampleElements<Entry<K, Collection<V>>> samples() { SampleElements<K> sampleKeys = ((TestMultimapGenerator<K, V, M>) multimapGenerator.getInnerGenerator()).sampleKeys(); SampleElements<V> sampleValues = ((TestMultimapGenerator<K, V, M>) multimapGenerator.getInnerGenerator()).sampleValues(); return new SampleElements<Entry<K, Collection<V>>>( mapEntry(sampleKeys.e0, createCollection(sampleValues.e0)), mapEntry(sampleKeys.e1, createCollection(sampleValues.e1)), mapEntry(sampleKeys.e2, createCollection(sampleValues.e2)), mapEntry(sampleKeys.e3, createCollection(sampleValues.e3)), mapEntry(sampleKeys.e4, createCollection(sampleValues.e4))); } @Override public Map<K, Collection<V>> create(Object... elements) { Set<K> keySet = new HashSet<K>(); List<Map.Entry<K, V>> builder = new ArrayList<Entry<K, V>>(); for (Object o : elements) { Map.Entry<K, Collection<V>> entry = (Entry<K, Collection<V>>) o; keySet.add(entry.getKey()); for (V v : entry.getValue()) { builder.add(mapEntry(entry.getKey(), v)); } } checkArgument(keySet.size() == elements.length, "Duplicate keys"); return multimapGenerator.create(builder.toArray()).asMap(); } @SuppressWarnings("unchecked") @Override public Entry<K, Collection<V>>[] createArray(int length) { return new Entry[length]; } @Override public Iterable<Entry<K, Collection<V>>> order(List<Entry<K, Collection<V>>> insertionOrder) { Map<K, Collection<V>> map = new HashMap<K, Collection<V>>(); List<Map.Entry<K, V>> builder = new ArrayList<Entry<K, V>>(); for (Entry<K, Collection<V>> entry : insertionOrder) { for (V v : entry.getValue()) { builder.add(mapEntry(entry.getKey(), v)); } map.put(entry.getKey(), entry.getValue()); } Iterable<Map.Entry<K, V>> ordered = multimapGenerator.order(builder); LinkedHashMap<K, Collection<V>> orderedMap = new LinkedHashMap<K, Collection<V>>(); for (Map.Entry<K, V> entry : ordered) { orderedMap.put(entry.getKey(), map.get(entry.getKey())); } return orderedMap.entrySet(); } @Override public K[] createKeyArray(int length) { return ((TestMultimapGenerator<K, V, M>) multimapGenerator.getInnerGenerator()) .createKeyArray(length); } @SuppressWarnings("unchecked") @Override public Collection<V>[] createValueArray(int length) { return new Collection[length]; } } static class EntriesGenerator<K, V, M extends Multimap<K, V>> implements TestCollectionGenerator<Entry<K, V>>, DerivedGenerator { private final OneSizeTestContainerGenerator<M, Map.Entry<K, V>> multimapGenerator; public EntriesGenerator( OneSizeTestContainerGenerator<M, Entry<K, V>> multimapGenerator) { this.multimapGenerator = multimapGenerator; } @Override public TestSubjectGenerator<?> getInnerGenerator() { return multimapGenerator; } @Override public SampleElements<Entry<K, V>> samples() { return multimapGenerator.samples(); } @Override public Collection<Entry<K, V>> create(Object... elements) { return multimapGenerator.create(elements).entries(); } @SuppressWarnings("unchecked") @Override public Entry<K, V>[] createArray(int length) { return new Entry[length]; } @Override public Iterable<Entry<K, V>> order(List<Entry<K, V>> insertionOrder) { return multimapGenerator.order(insertionOrder); } } static class ValuesGenerator<K, V, M extends Multimap<K, V>> implements TestCollectionGenerator<V> { private final OneSizeTestContainerGenerator<M, Map.Entry<K, V>> multimapGenerator; public ValuesGenerator( OneSizeTestContainerGenerator<M, Entry<K, V>> multimapGenerator) { this.multimapGenerator = multimapGenerator; } @Override public SampleElements<V> samples() { return ((TestMultimapGenerator<K, V, M>) multimapGenerator.getInnerGenerator()).sampleValues(); } @Override public Collection<V> create(Object... elements) { K k = ((TestMultimapGenerator<K, V, M>) multimapGenerator.getInnerGenerator()).sampleKeys().e0; Entry<K, V>[] entries = new Entry[elements.length]; for (int i = 0; i < elements.length; i++) { entries[i] = mapEntry(k, (V) elements[i]); } return multimapGenerator.create(entries).values(); } @SuppressWarnings("unchecked") @Override public V[] createArray(int length) { return ((TestMultimapGenerator<K, V, M>) multimapGenerator.getInnerGenerator()).createValueArray(length); } @Override public Iterable<V> order(List<V> insertionOrder) { K k = ((TestMultimapGenerator<K, V, M>) multimapGenerator.getInnerGenerator()).sampleKeys().e0; List<Entry<K, V>> entries = new ArrayList<Entry<K, V>>(); for (V v : insertionOrder) { entries.add(mapEntry(k, v)); } Iterable<Entry<K, V>> ordered = multimapGenerator.order(entries); List<V> orderedValues = new ArrayList<V>(); for (Entry<K, V> entry : ordered) { orderedValues.add(entry.getValue()); } return orderedValues; } } static class KeysGenerator<K, V, M extends Multimap<K, V>> implements TestMultisetGenerator<K>, DerivedGenerator { private final OneSizeTestContainerGenerator<M, Map.Entry<K, V>> multimapGenerator; public KeysGenerator( OneSizeTestContainerGenerator<M, Entry<K, V>> multimapGenerator) { this.multimapGenerator = multimapGenerator; } @Override public TestSubjectGenerator<?> getInnerGenerator() { return multimapGenerator; } @Override public SampleElements<K> samples() { return ((TestMultimapGenerator<K, V, M>) multimapGenerator.getInnerGenerator()).sampleKeys(); } @Override public Multiset<K> create(Object... elements) { Iterator<V> valueIter = ((TestMultimapGenerator<K, V, M>) multimapGenerator .getInnerGenerator()).sampleValues().iterator(); Entry<K, V>[] entries = new Entry[elements.length]; for (int i = 0; i < elements.length; i++) { entries[i] = mapEntry((K) elements[i], valueIter.next()); } return multimapGenerator.create(entries).keys(); } @SuppressWarnings("unchecked") @Override public K[] createArray(int length) { return ((TestMultimapGenerator<K, V, M>) multimapGenerator.getInnerGenerator()).createKeyArray(length); } @Override public Iterable<K> order(List<K> insertionOrder) { Iterator<V> valueIter = ((TestMultimapGenerator<K, V, M>) multimapGenerator .getInnerGenerator()).sampleValues().iterator(); List<Entry<K, V>> entries = new ArrayList<Entry<K, V>>(); for (K k : insertionOrder) { entries.add(mapEntry(k, valueIter.next())); } Iterable<Entry<K, V>> ordered = multimapGenerator.order(entries); List<K> orderedValues = new ArrayList<K>(); for (Entry<K, V> entry : ordered) { orderedValues.add(entry.getKey()); } return orderedValues; } } static class MultimapGetGenerator<K, V, M extends Multimap<K, V>> implements TestCollectionGenerator<V> { private final OneSizeTestContainerGenerator<M, Map.Entry<K, V>> multimapGenerator; public MultimapGetGenerator( OneSizeTestContainerGenerator< M, Map.Entry<K, V>> multimapGenerator) { this.multimapGenerator = multimapGenerator; } @Override public SampleElements<V> samples() { return ((TestMultimapGenerator<K, V, M>) multimapGenerator.getInnerGenerator()) .sampleValues(); } @Override public V[] createArray(int length) { return ((TestMultimapGenerator<K, V, M>) multimapGenerator.getInnerGenerator()) .createValueArray(length); } @Override public Iterable<V> order(List<V> insertionOrder) { K k = ((TestMultimapGenerator<K, V, M>) multimapGenerator.getInnerGenerator()) .sampleKeys().e0; List<Entry<K, V>> entries = new ArrayList<Entry<K, V>>(); for (V v : insertionOrder) { entries.add(mapEntry(k, v)); } Iterable<Entry<K, V>> orderedEntries = multimapGenerator.order(entries); List<V> values = new ArrayList<V>(); for (Entry<K, V> entry : orderedEntries) { values.add(entry.getValue()); } return values; } @Override public Collection<V> create(Object... elements) { Entry<K, V>[] array = multimapGenerator.createArray(elements.length); K k = ((TestMultimapGenerator<K, V, M>) multimapGenerator.getInnerGenerator()) .sampleKeys().e0; for (int i = 0; i < elements.length; i++) { array[i] = mapEntry(k, (V) elements[i]); } return multimapGenerator.create(array).get(k); } } private static class ReserializedMultimapGenerator<K, V, M extends Multimap<K, V>> implements TestMultimapGenerator<K, V, M> { private final OneSizeTestContainerGenerator<M, Map.Entry<K, V>> multimapGenerator; public ReserializedMultimapGenerator( OneSizeTestContainerGenerator< M, Map.Entry<K, V>> multimapGenerator) { this.multimapGenerator = multimapGenerator; } @Override public SampleElements<Map.Entry<K, V>> samples() { return multimapGenerator.samples(); } @Override public Map.Entry<K, V>[] createArray(int length) { return multimapGenerator.createArray(length); } @Override public Iterable<Map.Entry<K, V>> order( List<Map.Entry<K, V>> insertionOrder) { return multimapGenerator.order(insertionOrder); } @Override public M create(Object... elements) { return SerializableTester.reserialize(((TestMultimapGenerator<K, V, M>) multimapGenerator .getInnerGenerator()).create(elements)); } @Override public K[] createKeyArray(int length) { return ((TestMultimapGenerator<K, V, M>) multimapGenerator.getInnerGenerator()) .createKeyArray(length); } @Override public V[] createValueArray(int length) { return ((TestMultimapGenerator<K, V, M>) multimapGenerator.getInnerGenerator()) .createValueArray(length); } @Override public SampleElements<K> sampleKeys() { return ((TestMultimapGenerator<K, V, M>) multimapGenerator.getInnerGenerator()) .sampleKeys(); } @Override public SampleElements<V> sampleValues() { return ((TestMultimapGenerator<K, V, M>) multimapGenerator.getInnerGenerator()) .sampleValues(); } @Override public Collection<V> createCollection(Iterable<? extends V> values) { return ((TestMultimapGenerator<K, V, M>) multimapGenerator.getInnerGenerator()) .createCollection(values); } } }