/* * (c) Copyright 2007-2012 by Volker Bergmann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, is permitted under the terms of the * GNU General Public License. * * For redistributing this software or a derivative work under a license other * than the GPL-compatible Free Software License as defined by the Free * Software Foundation or approved by OSI, you must first obtain a commercial * license to this software product from Volker Bergmann. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * WITHOUT A WARRANTY OF ANY KIND. ALL EXPRESS OR IMPLIED CONDITIONS, * REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE * HEREBY EXCLUDED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package org.databene.benerator.test; import static org.junit.Assert.*; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.Collection; import java.util.concurrent.atomic.AtomicInteger; import org.databene.benerator.Generator; import org.databene.benerator.primitive.number.AbstractNonNullNumberGenerator; import org.databene.benerator.wrapper.ProductWrapper; import org.databene.commons.ArrayFormat; import org.databene.commons.ArrayUtil; import org.databene.commons.BeanUtil; import org.databene.commons.CollectionUtil; import org.databene.commons.Converter; import org.databene.commons.IOUtil; import org.databene.commons.Resettable; import org.databene.commons.Validator; import org.databene.commons.converter.ToStringConverter; import org.databene.commons.validator.UniqueValidator; import org.databene.measure.count.ObjectCounter; import org.databene.model.data.Entity; /** * Provides methods for testing generators.<br/> * <br/> * Created: 15.11.2007 14:46:31 * @author Volker Bergmann */ public abstract class GeneratorTest extends ModelTest { private Converter<Object, String> formatter = new ToStringConverter(); // helper methods for this and child classes ----------------------------------------------------------------------- public <T extends Generator<U>, U> T initialize(T generator) { generator.init(context); return generator; } public void close(Generator<?> generator) { IOUtil.close(generator); } public void setCurrentProduct(Object product) { context.setCurrentProduct(new ProductWrapper<Object>(product)); } @SuppressWarnings({ "unchecked", "rawtypes" }) public void printProducts(Generator<?> generator, int n) { ProductWrapper wrapper = new ProductWrapper(); for (int i = 0; i < n; i++) { ProductWrapper<?> tmp = generator.generate(wrapper); if (tmp == null) System.out.println("<>"); else System.out.println(formatter.convert(tmp.unwrap())); } } public static <T> Map<T, AtomicInteger> countProducts(Generator<T> generator, int n) { ObjectCounter<T> counter = new ObjectCounter<T>(Math.min(n, 1000)); ProductWrapper<T> wrapper = new ProductWrapper<T>(); for (int i = 0; i < n; i++) { wrapper = generator.generate(wrapper); if (wrapper == null) fail("Generator unavailable after " + i + " of " + n + " invocations"); else counter.count(wrapper.unwrap()); } return counter.getCounts(); } protected static <T> void assertEqualArrays(T expected, T actual) { ArrayFormat format = new ArrayFormat(); assertTrue("Expected " + format.format(expected) + ", found: " + format.format(actual), ArrayUtil.equals(expected, actual)); } protected static <T> Helper expectGeneratedSequence(Generator<T> generator, T ... products) { expectGeneratedSequenceOnce(generator, products); generator.reset(); expectGeneratedSequenceOnce(generator, products); return new Helper(generator); } protected <T> Helper expectGeneratedSet(Generator<T> generator, int invocations, T ... products) { expectGeneratedSetOnce(generator, invocations, products); generator.reset(); expectGeneratedSetOnce(generator, invocations, products); return new Helper(generator); } protected <T> Helper expectUniquelyGeneratedSet(Generator<T> generator, T ... products) { expectUniquelyGeneratedSetOnce(generator, products); generator.reset(); expectUniquelyGeneratedSetOnce(generator, products); return new Helper(generator); } protected <T> Helper expectUniqueProducts(Generator<T> generator, int n) { expectUniqueProductsOnce(generator, n); generator.reset(); expectUniqueProductsOnce(generator, n); return new Helper(generator); } @SuppressWarnings({ "unchecked", "rawtypes" }) protected <T> Helper expectGenerations(Generator<T> generator, int n, Validator ... validators) { expectGenerationsOnce(generator, n, validators); generator.reset(); for (Validator<?> validator : validators) if (validator instanceof Resettable) ((Resettable) validator).reset(); expectGenerationsOnce(generator, n, validators); return new Helper(generator); } protected <T> Helper expectUniqueGenerations(Generator<T> generator, int n) { expectUniqueGenerationsOnce(generator, n); generator.reset(); expectUniqueGenerationsOnce(generator, n); return new Helper(generator); } protected <T extends Comparable<T>> Helper expectRange(Generator<T> generator, int n, T min, T max) { expectRangeOnce(generator, n, min, max); generator.reset(); expectRangeOnce(generator, n, min, max); return new Helper(generator); } protected <T>String format(T product) { return ToStringConverter.convert(product, "[null]"); } @SuppressWarnings({ "rawtypes", "unchecked" }) public static void assertUnavailable(Generator<?> generator) { assertNull("Generator " + generator + " is expected to be unavailable", generator.generate(new ProductWrapper())); } public static void assertAvailable(Generator<?> generator) { assertAvailable("Generator " + generator + " is expected to be available", generator); } @SuppressWarnings({ "rawtypes", "unchecked" }) public static void assertAvailable(String message, Generator<?> generator) { assertNotNull(message, generator.generate(new ProductWrapper())); } // Number generator tests ------------------------------------------------------------------------------------------ public static <T extends Number> void checkEqualDistribution( Class<? extends AbstractNonNullNumberGenerator<T>> generatorClass, T min, T max, T granularity, int iterations, double tolerance, T ... expectedValues) { Set<T> expectedSet = CollectionUtil.toSet(expectedValues); checkDistribution(generatorClass, min, max, granularity, iterations, true, tolerance, expectedSet); } public static <T extends Number> void checkEqualDistribution( Class<? extends AbstractNonNullNumberGenerator<T>> generatorClass, T min, T max, T granularity, int iterations, double tolerance, Set<T> expectedSet) { checkDistribution(generatorClass, min, max, granularity, iterations, true, tolerance, expectedSet); } private static <T extends Number> void checkDistribution( Class<? extends AbstractNonNullNumberGenerator<T>> generatorClass, T min, T max, T granularity, int iterations, boolean equalDistribution, double tolerance, Set<T> expectedSet) { AbstractNonNullNumberGenerator<T> generator = BeanUtil.newInstance(generatorClass); generator.setMin(min); generator.setMax(max); generator.setGranularity(granularity); ObjectCounter<T> counter = new ObjectCounter<T>(expectedSet != null ? expectedSet.size() : 10); ProductWrapper<T> wrapper = new ProductWrapper<T>(); for (int i = 0; i < iterations; i++) counter.count(generator.generate(wrapper).unwrap()); checkDistribution(counter, equalDistribution, tolerance, expectedSet); } // unspecific generator tests -------------------------------------------------------------------------------------- public static <E> void checkEqualDistribution( Generator<E> generator, int iterations, double tolerance, Set<E> expectedSet) { checkDistribution(generator, iterations, true, tolerance, expectedSet); } public static <T> void checkProductSet(Generator<T> generator, int iterations, Set<T> expectedSet) { checkDistribution(generator, iterations, false, 0, expectedSet); } private static <T> void checkDistribution(Generator<T> generator, int iterations, boolean equalDistribution, double tolerance, Set<T> expectedSet) { ObjectCounter<T> counter = new ObjectCounter<T>(expectedSet != null ? expectedSet.size() : 10); ProductWrapper<T> wrapper = new ProductWrapper<T>(); for (int i = 0; i < iterations; i++) counter.count(generator.generate(wrapper).unwrap()); checkDistribution(counter, equalDistribution, tolerance, expectedSet); } @SuppressWarnings({ "rawtypes", "unchecked" }) protected static void expectRelativeWeights(Generator<?> generator, int iterations, Object... expectedValueWeightPairs) { ObjectCounter<Object> counter = new ObjectCounter<Object>(expectedValueWeightPairs.length / 2); ProductWrapper wrapper = new ProductWrapper(); for (int i = 0; i < iterations; i++) { wrapper = generator.generate(wrapper); if (wrapper == null) fail("Generator unavailable after " + i + " of " + iterations + " invocations"); else counter.count(wrapper.unwrap()); } Set<Object> productSet = counter.objectSet(); double totalExpectedWeight = 0; for (int i = 1; i < expectedValueWeightPairs.length; i += 2) totalExpectedWeight += ((Number) expectedValueWeightPairs[i]).doubleValue(); for (int i = 0; i < expectedValueWeightPairs.length; i += 2) { Object value = expectedValueWeightPairs[i]; double expectedWeight = ((Number) expectedValueWeightPairs[i + 1]).doubleValue() / totalExpectedWeight; if (expectedWeight > 0) { assertTrue("Generated set does not contain value " + value, productSet.contains(value)); double measuredWeight = counter.getRelativeCount(value); assertTrue("For value '" + value + "', weight " + expectedWeight + " is expected, but it is " + measuredWeight, Math.abs(measuredWeight - expectedWeight) / expectedWeight < 0.15); } else assertFalse("Generated contains value " + value + " though it has zero weight", productSet.contains(value)); } } // collection checks ----------------------------------------------------------------------------------------------- public static <E> void checkEqualDistribution(Collection<E> collection, double tolerance, Set<E> expectedSet) { checkDistribution(collection, true, tolerance, expectedSet); } private static <E> void checkDistribution(Collection<E> collection, boolean equalDistribution, double tolerance, Set<E> expectedSet) { ObjectCounter<E> counter = new ObjectCounter<E>(expectedSet != null ? expectedSet.size() : 10); for (E object : collection) counter.count(object); checkDistribution(counter, equalDistribution, tolerance, expectedSet); } // counter checks -------------------------------------------------------------------------------------------------- public static <E> void checkEqualDistribution( ObjectCounter<E> counter, double tolerance, Set<E> expectedSet) { checkDistribution(counter, true, tolerance, expectedSet); } private static <E> void checkDistribution( ObjectCounter<E> counter, boolean equalDistribution, double tolerance, Set<E> expectedSet) { if (equalDistribution) assertTrue("Distribution is not equal: " + counter, counter.equalDistribution(tolerance)); if (expectedSet != null) assertEquals(expectedSet, counter.objectSet()); } public static class Helper { private Generator<?> generator; public Helper(Generator<?> generator) { this.generator = generator; } public void withCeasedAvailability() { try { assertUnavailable(generator); } finally { generator.close(); } } public void withContinuedAvailability() { assertAvailable(generator); } } // private helpers ------------------------------------------------------------------------------------------------- protected static <T> void expectGeneratedSequenceOnce(Generator<T> generator, T... products) { int count = 0; ProductWrapper<T> wrapper = new ProductWrapper<T>(); for (T expectedProduct : products) { wrapper = generator.generate(wrapper); assertNotNull("Generator is unavailable after generating " + count + " of " + products.length + " products: " + generator, wrapper); T generatedProduct = wrapper.unwrap(); if (generatedProduct.getClass().isArray()) assertEqualArrays(expectedProduct, generatedProduct); else assertEquals(expectedProduct, generatedProduct); count++; } } private <T> void expectGeneratedSetOnce(Generator<T> generator, int invocations, T... expectedProducts) { Set<T> expectedSet = CollectionUtil.toSet(expectedProducts); Set<T> observedSet = new HashSet<T>(expectedProducts.length); ProductWrapper<T> wrapper = new ProductWrapper<T>(); for (int i = 0; i < invocations; i++) { wrapper = generator.generate(wrapper); assertNotNull("Generator has gone unavailable. " + "Generated only " + i + " of " + expectedProducts.length + " expected values: " + observedSet, wrapper); T generation = wrapper.unwrap(); logger.debug("created " + format(generation)); assertTrue("The generated value '" + format(generation) + "' was not in the expected set: " + expectedSet, expectedSet.contains(generation)); observedSet.add(generation); } assertEquals(expectedSet, observedSet); } private <T>void expectUniquelyGeneratedSetOnce(Generator<T> generator, T... expectedProducts) { Set<T> expectedSet = CollectionUtil.toSet(expectedProducts); UniqueValidator<Object> validator = new UniqueValidator<Object>(); ProductWrapper<T> wrapper = new ProductWrapper<T>(); for (int i = 0; i < expectedProducts.length; i++) { wrapper = generator.generate(wrapper); assertNotNull("Generator has gone unavailable after " + i + " products, " + "expected " + expectedProducts.length + " products. ", wrapper); T product = wrapper.unwrap(); logger.debug("created " + format(product)); assertTrue("Product is not unique: " + product, validator.valid(product)); assertTrue("The generated value '" + format(product) + "' was not in the expected set: " + format(expectedSet), expectedSet.contains(product)); } } private <T>void expectUniqueProductsOnce(Generator<T> generator, int n) { UniqueValidator<T> validator = new UniqueValidator<T>(); ProductWrapper<T> wrapper = new ProductWrapper<T>(); for (int i = 0; i < n; i++) { wrapper = generator.generate(wrapper); assertNotNull("Generator is not available: " + generator, wrapper); T product = wrapper.unwrap(); logger.debug("created: " + format(product)); assertTrue("Product is not unique: " + product, validator.valid(product)); } } private <T> void expectGenerationsOnce(Generator<T> generator, int n, Validator<T> ... validators) { ProductWrapper<T> wrapper = new ProductWrapper<T>(); for (int i = 0; i < n; i++) { wrapper = generator.generate(wrapper); assertNotNull("Generator has gone unavailable before creating the required number of products, " + "required " + n + " but was " + i, wrapper); T product = wrapper.unwrap(); logger.debug("created " + format(product)); for (Validator<T> validator : validators) { assertTrue("The generated value '" + format(product) + "' is not valid according to " + validator + ", failed after " + i + " generations", validator.valid(product)); } } } private <T> void expectUniqueGenerationsOnce(Generator<T> generator, int n, Validator<T> ... validators) { UniqueValidator<T> validator = new UniqueValidator<T>(); ProductWrapper<T> wrapper = new ProductWrapper<T>(); for (int i = 0; i < n; i++) { wrapper = generator.generate(wrapper); assertNotNull("Generator has gone unavailable before creating the required number of products ", wrapper); T product = wrapper.unwrap(); logger.debug("created " + format(product)); assertTrue("The generated value '" + format(product) + "' is not unique. Generator is " + generator, validator.valid(product)); } } protected <T extends Comparable<T>> void expectRangeOnce(Generator<T> generator, int n, T min, T max) { ProductWrapper<T> wrapper = new ProductWrapper<T>(); for (int i = 0; i < n; i++) { wrapper = generator.generate(wrapper); assertNotNull(wrapper); T product = wrapper.unwrap(); assertTrue("Generated value (" + product + ") is less than the configured minimum (" + min + ")", min.compareTo(product) <= 0); assertTrue("Generated value (" + product + ") is greater than the configured maximum (" + max + ")", max.compareTo(product) >= 0); } } public static void assertComponents(Entity entity, Object... nameValuePairs) { for (int i = 0; i < nameValuePairs.length; i+= 2) { String name = (String) nameValuePairs[i]; Object expected = nameValuePairs[i + 1]; Object actual = entity.getComponent(name); assertEquals("Unexpected value for component '" + name + "':", expected, actual); } } }