/** * Copyright (c) Michael Steindorfer <Centrum Wiskunde & Informatica> and Contributors. * All rights reserved. * * This file is licensed under the BSD 2-Clause License, which accompanies this project * and is available under https://opensource.org/licenses/BSD-2-Clause. */ package io.usethesource.capsule; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Spliterator; import java.util.Spliterators; import java.util.stream.Stream; import java.util.stream.StreamSupport; import com.pholser.junit.quickcheck.Property; import com.pholser.junit.quickcheck.generator.Size; import io.usethesource.capsule.core.PersistentTrieSet; import org.junit.Ignore; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; public abstract class AbstractSetMultimapProperties<K, V, CT extends SetMultimap.Immutable<K, V>> { protected final int DEFAULT_TRIALS = 10; protected final int MAX_SIZE = 1_000; protected final Class<?> type; public AbstractSetMultimapProperties(Class<?> type) { this.type = type; } @Property(trials = DEFAULT_TRIALS) public void convertToJavaSetAndCheckSize(CT input) { final java.util.Set<java.util.Map.Entry<K, V>> javaSet = new HashSet<>(input.entrySet()); assertEquals(javaSet.size(), input.size()); } @Property(trials = DEFAULT_TRIALS) public void checkSizeOfEntrySetIterator(CT input) { final Iterator<?> iterator = input.entrySet().iterator(); int encounteredSize = 0; while (iterator.hasNext()) { iterator.next(); encounteredSize++; } assertEquals(input.size(), encounteredSize); } @Property(trials = DEFAULT_TRIALS) public void mapEqualsOtherMap(@Size(min = 0, max = 0) final CT emptyCollection, final SetMultimap.Immutable<K, V> thatMap) { final SetMultimap.Transient builder = emptyCollection.asTransient(); thatMap.entryIterator() .forEachRemaining(tuple -> builder.__insert(tuple.getKey(), tuple.getValue())); final CT thisMap = (CT) builder.freeze(); assertEquals(thisMap, thatMap); assertEquals(thatMap, thisMap); } @Property(trials = DEFAULT_TRIALS) public void keySetEqualsKeyIteratorElements(final CT multimap) { final java.util.Set<K> keySet = new HashSet<>(); multimap.keyIterator().forEachRemaining(keySet::add); // assertEquals(PersistentTrieSet.class, multimap.keySet().getClass()); assertEquals(keySet, multimap.keySet()); } // /** // * TODO: replace batch construction by sequence of 'insert' operations // */ // @Property // (trials = DEFAULT_TRIALS) // public void testInsertTuplesThatShareSameKey(final Integer key, // @Size(min = 1, max = 100) final java.util.HashSet<Integer> values) { // assertEquals(values.size(), toMultimap(key, values).size()); // assertTrue(toMultimap(key, values).containsKey(key)); // } // // /** // * TODO: replace batch construction by sequence of 'insert' operations followed by a 'remove' // */ // @Property(trials = DEFAULT_TRIALS) // public void testInsertTuplesWithOneRemoveThatShareSameKeyX(final Integer key, // @Size(min = 2, max = 100) final java.util.HashSet<Integer> values) { // // Integer value = sourceOfRandomness.choose(values); // SetMultimap.Immutable<Integer, Integer> multimap = toMultimap(key, values); // // if (multimap.__remove(key, value).size() + 1 == multimap.size()) { // // succeed // multimap = multimap.__remove(key, value); // } else { // // fail // assertTrue(multimap.containsEntry(key, value)); // multimap = multimap.__remove(key, value); // } // // // assertEquals(values.size() - 1, multimap.size()); // // assertTrue(multimap.containsKey(key)); // // values.forEach(currentValue -> { // // if (!currentValue.equals(value)) { // // assertTrue(multimap.containsEntry(key, currentValue)); // // } // // }); // } /** * Inserted tuple by tuple, starting from an empty multimap. Keeps track of all so far inserted * tuples and checks after each insertion if all inserted tuples are contained (quadratic * operation). */ @Property(trials = DEFAULT_TRIALS) public void stepwiseContainsAfterInsert(@Size(min = 0, max = 0) final CT emptyCollection, @Size(min = 1, max = MAX_SIZE) final java.util.HashSet<Map.Entry<K, V>> inputValues) { final HashSet<Map.Entry<K, V>> insertedValues = new HashSet<>(inputValues.size()); CT testCollection = emptyCollection; for (Map.Entry<K, V> newValueTuple : inputValues) { final CT tmpCollection = (CT) testCollection.__insert(newValueTuple.getKey(), newValueTuple.getValue()); insertedValues.add(newValueTuple); boolean containsInsertedValues = insertedValues.stream() .allMatch(tuple -> tmpCollection.containsEntry(tuple.getKey(), tuple.getValue())); assertTrue("All so far inserted values must be contained.", containsInsertedValues); // String.format("%s.insert(%s)", testSet, newValue); testCollection = tmpCollection; } } @Property(trials = DEFAULT_TRIALS) public void containsAfterInsert(@Size(min = 0, max = 0) final CT emptyCollection, @Size(min = 1, max = MAX_SIZE) final java.util.HashSet<Map.Entry<K, V>> inputValues) { CT testCollection = emptyCollection; for (Map.Entry<K, V> newValueTuple : inputValues) { final CT tmpCollection = (CT) testCollection.__insert(newValueTuple.getKey(), newValueTuple.getValue()); testCollection = tmpCollection; } final CT finalCollection = testCollection; boolean containsInsertedValues = inputValues.stream() .allMatch(tuple -> finalCollection.containsEntry(tuple.getKey(), tuple.getValue())); assertTrue("Must contain all inserted values.", containsInsertedValues); } @Property(trials = DEFAULT_TRIALS) public void notContainedAfterInsertRemove(CT input, K item0, V item1) { assertFalse(input.__insert(item0, item1).__remove(item0, item1).containsEntry(item0, item1)); } @Property(trials = DEFAULT_TRIALS) public void entryIteratorAfterInsert(@Size(min = 0, max = 0) final CT emptyCollection, @Size(min = 1, max = MAX_SIZE) final java.util.HashSet<Map.Entry<K, V>> inputValues) { CT testCollection = emptyCollection; for (Map.Entry<K, V> newValueTuple : inputValues) { final CT tmpCollection = (CT) testCollection.__insert(newValueTuple.getKey(), newValueTuple.getValue()); testCollection = tmpCollection; } final CT finalCollection = testCollection; final Spliterator<Map.Entry> entrySpliterator = Spliterators .spliterator(finalCollection.entryIterator(), finalCollection.size(), Spliterator.DISTINCT); final Stream<Map.Entry> entryStream = StreamSupport.stream(entrySpliterator, false); boolean containsInsertedValues = entryStream.allMatch(inputValues::contains); assertTrue("Must contain all inserted values.", containsInsertedValues); } @Property(trials = DEFAULT_TRIALS) public void sizeAfterInsertKeyValue(CT input, K key, V value) { int sizeDelta = Set.Immutable.of(value).__insertAll(input.get(key)).__removeAll(input.get(key)).size(); assertEquals(sizeDelta, input.__insert(key, value).size() - input.size()); } @Property(trials = DEFAULT_TRIALS) public void sizeAfterInsertKeyValues(CT input, K key, PersistentTrieSet<V> values) { int sizeDelta = values.__insertAll(input.get(key)).__removeAll(input.get(key)).size(); CT updatedInput = (CT) input.__insert(key, values); assertEquals(sizeDelta, updatedInput.size() - input.size()); // invoke other properties convertToJavaSetAndCheckSize(updatedInput); } /* * NOTE: tests transient insertion and variations of operations * TODO: make explicit sets of transient tests and tests for chained operations */ @Property(trials = DEFAULT_TRIALS) public void sizeAfterTransientInsertKeyValues(CT input, K key, PersistentTrieSet<V> values) { int sizeDelta = values.__insertAll(input.get(key)).__removeAll(input.get(key)).size(); SetMultimap.Transient<K, V> builder = input.asTransient(); builder.__remove(key); builder.__put(key, values); builder.__remove(key); builder.__insert(key, values); builder.__remove(key); builder.__put(key, values); builder.__insert(key, values); builder.__remove(key); builder.__insert(key, values); builder.__put(key, values); CT updatedInput = (CT) builder.freeze(); assertEquals(sizeDelta, updatedInput.size() - input.size()); // invoke other properties convertToJavaSetAndCheckSize(updatedInput); } @Property(trials = DEFAULT_TRIALS) public void getReturnsNonNull(CT input, K key) { assertNotNull("Must always return a set and not null.", input.get(key)); } @Property(trials = DEFAULT_TRIALS) public void transientGetReturnsNonNull(CT input, K key) { assertNotNull("Must always return a set and not null.", input.asTransient().get(key)); } @Property(trials = DEFAULT_TRIALS) public void serializationRoundtripIfSerializable(CT input) throws Exception { if (input instanceof java.io.Serializable) { assertEquals(input, deserialize(serialize((java.io.Serializable) input), input.getClass())); } } private static <T extends Serializable> byte[] serialize(T item) throws IOException { try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos)) { oos.writeObject(item); return baos.toByteArray(); } catch (IOException e) { throw e; } } private static <T> T deserialize(byte[] bytes, Class<T> itemClass) throws IOException, ClassNotFoundException { try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ObjectInputStream ois = new ObjectInputStream(bais)) { Object item = ois.readObject(); return itemClass.cast(item); } catch (IOException | ClassNotFoundException e) { throw e; } } }