All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.tsl2.nano.autotest.ValueRandomizer Maven / Gradle / Ivy

There is a newer version: 2.5.3
Show newest version
/*
 * created by: Tom
 * created on: 31.03.2017
 * 
 * Copyright: (c) Thomas Schneider 2017, all rights reserved
 */
package de.tsl2.nano.autotest;

import static de.tsl2.nano.autotest.creator.AutoTest.PREFIX_FUNCTIONTEST;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.io.Closeable;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URL;
import java.nio.file.Path;
import java.text.Format;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Scanner;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.regex.Pattern;

import de.tsl2.nano.autotest.creator.AFunctionCaller;
import de.tsl2.nano.autotest.creator.AutoTest;
import de.tsl2.nano.core.ManagedException;
import de.tsl2.nano.core.cls.BeanAttribute;
import de.tsl2.nano.core.cls.BeanClass;
import de.tsl2.nano.core.cls.IAttribute;
import de.tsl2.nano.core.cls.PrimitiveUtil;
import de.tsl2.nano.core.cls.PrivateAccessor;
import de.tsl2.nano.core.util.AdapterProxy;
import de.tsl2.nano.core.util.ByteUtil;
import de.tsl2.nano.core.util.DateUtil;
import de.tsl2.nano.core.util.DefaultFormat;
import de.tsl2.nano.core.util.DependencyInjector;
import de.tsl2.nano.core.util.FieldUtil;
import de.tsl2.nano.core.util.FileUtil;
import de.tsl2.nano.core.util.ListSet;
import de.tsl2.nano.core.util.MapUtil;
import de.tsl2.nano.core.util.NumberUtil;
import de.tsl2.nano.core.util.ObjectUtil;
import de.tsl2.nano.core.util.StringUtil;
import de.tsl2.nano.core.util.Util;

/**
 * For test purposes only!
 * 

* Simple logic to fill all types of fields of a given object with random * values. Together with a Bean class that provides all types of fields and * their getters and setters you are able to write generic framework unit tests. *

* NOTE: Additionally you should test your logic on testsets (e.g. maps of * fields with boundary values) describing the boundary conditions. To be more * generic, you may use lots of runs on random values instead. This may be done * with {@link #provideRandomizedObjects(int, Class...)} *

* Before creating any random number + value, the ValueRandomizer tries to read a file with * name e.g.: String -> string.set * If this file exists a random value of that will be used. The values can be separated by whitespaces or ';'. * If this file exists and has only one line with content like 'min<->max' then a minimum and maximum value * will be set for that type. * * @author Thomas Schneider */ @SuppressWarnings({"unchecked", "rawtypes"}) public class ValueRandomizer { private static final ValueSets valueSets = new ValueSets(); private static DependencyInjector di = new DependencyInjector(); private ValueRandomizer() { } public static Object fillRandom(Object obj) { return fillRandom(obj, false, 0); } public static T fillRandom(T obj, boolean zeroNumber, final int depth) { if (!checkMaxDepth(depth + 1)) { return obj; } PrivateAccessor acc = new PrivateAccessor<>(obj); Field[] fields = acc.findMembers(PrivateAccessor::notStaticAndNotFinal); Arrays.stream(fields) .filter(f -> acc.member(f.getName()) == null) .forEach(f -> Util.trY( () -> acc.set(f.getName(), createRandomValue(FieldUtil.getExplicitType(f), zeroNumber, depth)), false)); return obj; } public static Object createRandomProxy(Class interfaze, boolean zeroNumber) { return createRandomProxy(interfaze, zeroNumber, 0); } public static V createRandomProxy(Class interfaze, boolean zeroNumber, final int depth) { if (!checkMaxDepth(depth + 1)) { return null; } Map types = AdapterProxy.getValueTypes(interfaze); Map values = new HashMap<>(); types.forEach( (n, t) -> values.put(n, t.isAssignableFrom(interfaze) || interfaze.isAssignableFrom(t) ? null : createRandomValue(t, zeroNumber, depth))); return AdapterProxy.create(interfaze, values); } protected static V createRandomValue(Class typeOf) { return createRandomValue(typeOf, false); } protected static V createRandomValue(Class typeOf, boolean zeroNumber) { return createRandomValue(typeOf, zeroNumber, 0); } protected static V createRandomValue(Class typeOf, boolean zeroNumber, int depth) { Class genericType = (Class) ObjectUtil.getGenericType(typeOf); typeOf = !Iterable.class.isAssignableFrom(typeOf) && !Iterable.class.isAssignableFrom(typeOf) && genericType != null && !genericType.equals(Object.class) ? genericType : typeOf; if (!checkMaxDepth(++depth)) { System.out.print(">"); return null; } Object n; V value = null; try { if (!Util.isEmpty(AFunctionCaller.def(AutoTest.VALUESET_GROUP, ValueSets.DEFAULT)) && valueSets.hasValueSet(typeOf)) { n = valueSets.fromValueSet(typeOf); } else { n = zeroNumber && (typeOf.isPrimitive() || NumberUtil.isNumber(typeOf)) && (!PrimitiveUtil.isAssignableFrom(char.class, typeOf) || AFunctionCaller.def(AutoTest.ALLOW_SINGLE_CHAR_ZERO, false)) && (!PrimitiveUtil.isAssignableFrom(byte.class, typeOf) || AFunctionCaller.def(AutoTest.ALLOW_SINGLE_BYTE_ZERO, false)) ? 0d : createRandomNumber(typeOf); } // avoid writing files into the project folder (and not into target) if (File.class.isAssignableFrom(typeOf) || Path.class.isAssignableFrom(typeOf) || String.class.isAssignableFrom(typeOf) || Closeable.class.isAssignableFrom(typeOf)) { n = FileUtil.userDirFile(StringUtil.toBase64(n)).getAbsolutePath(); if (Reader.class.isAssignableFrom(typeOf)) { n = new StringReader(n.toString()); } else if (Writer.class.isAssignableFrom(typeOf)) { StringWriter writer = new StringWriter(); writer.write(n.toString()); n = writer; } } else if (NumberUtil.isNumber(n)) { n = convert(n, typeOf, zeroNumber, depth); } value = ObjectUtil.wrap(n, typeOf); value = constructDefaultWithoutSpecificValueOrNull(typeOf, value); } catch (Exception e) { // here we try it without randomized values but directly creating a default instance if (ObjectUtil.isInstanceable(typeOf)) { try { value = constructWithRandomParameters(typeOf, zeroNumber, depth).instance; } catch (Exception ex) { return null; } } else if (typeOf.isInterface()) { value = createRandomProxy(typeOf, zeroNumber, depth); } else { // ManagedException.forward(e); // Ok, we can't create an object, so we test with null value return null; } } if (Boolean.getBoolean(PREFIX_FUNCTIONTEST + "inject.beanattributes")) { try { di.inject(value); injectTestBeanAttributes(value, zeroNumber, depth); } catch (Exception e) { // Ok, we cannot inject - but the value is already created, so its not a problem.... } } return value; } private static V constructDefaultWithoutSpecificValueOrNull(Class typeOf, V value) { if (value != null && !PrimitiveUtil.isAssignableFrom(typeOf, value.getClass())) { if (!Util.isAbstract(typeOf) && BeanClass.getBeanClass(typeOf).hasDefaultConstructor(false)) value = BeanClass.getBeanClass(typeOf).createInstance(); else // TODO: perhaps we could search an implementation through ClassFinder value = null; } return value; } private static void injectTestBeanAttributes(V value, boolean zeroNumber, int depth) { if (!checkMaxDepth(depth + 7) || !Boolean.getBoolean(PREFIX_FUNCTIONTEST + "inject.beanattributes")) { return; } List attrs = BeanClass.getBeanClass(value.getClass()).getAttributes(true); attrs.stream().filter(a -> a.getValue(value) == null) .forEach(a -> a.setValue(value, createRandomValue(BeanAttribute.getExplicitType(a), zeroNumber, depth))); } private static Object convert(Object n, Class typeOf, boolean zeroNumber, int depth) { if (typeOf.isAssignableFrom(n.getClass())) { return n; } if (BeanClass.hasConstructor(typeOf, long.class)) n = ((Number) n).longValue(); // -> Date else if (typeOf.equals(Class.class)) { if (typeOf.isAnnotation()) n = ATestAnnotation.class; else if (typeOf.isInterface()) n = ITestInterface.class; else n = TypeBean.class; // must be equal to the object creating (see below) } else if (URI.class.isAssignableFrom(typeOf)) { n = URI.create("http://localhost"); } else if (URL.class.isAssignableFrom(typeOf)) { n = Util.trY(() -> URI.create("http://localhost").toURL()); } else if (InetAddress.class.isAssignableFrom(typeOf)) { n = Util.trY(() -> InetAddress.getLocalHost()); } else if (InetSocketAddress.class.isAssignableFrom(typeOf)) { n = Util.trY(() -> InetSocketAddress.createUnresolved("localhost", 0)); } else if (typeOf.isEnum()) { if (n instanceof Number) { n = typeOf.getEnumConstants()[((Number) n).intValue()]; } else { throw new IllegalArgumentException(typeOf + " -> " + n); } } else if (Format.class.isAssignableFrom(typeOf)) { n = new DefaultFormat(); } else if (Method.class.isAssignableFrom(typeOf)) { n = Util.trY( () -> TypeBean.class.getMethod("getString")); } else if (Field.class.isAssignableFrom(typeOf)) { n = Util.trY( () -> TypeBean.class.getField("string")); } else if (typeOf.equals(ClassLoader.class)) n = Thread.currentThread().getContextClassLoader(); // TODO: create randomly else if (Collection.class.isAssignableFrom(typeOf)) // if (ObjectUtil.getGenericType(typeOf) != null) // n = new ListSet<>(ObjectUtil.wrap(n, ObjectUtil.getGenericType(typeOf))); // else n = new ListSet<>(n); else if (Properties.class.isAssignableFrom(typeOf)) n = MapUtil.asProperties(StringUtil.toBase64(n).replace('=', 'X'), n.toString()); else if (Map.class.isAssignableFrom(typeOf)) // if (ObjectUtil.getGenericType(typeOf, 1) != null) // n = MapUtil.asMap(n.toString(), ObjectUtil.wrap(n, ObjectUtil.getGenericType(typeOf, 1))); // else n = MapUtil.asMap(StringUtil.toBase64(n).replace('=', 'X'), n); else if (ByteUtil.isByteStream(typeOf)) n = ByteUtil.toByteStream(new byte[] {((Number) n).byteValue()}, typeOf); else if (typeOf.isInterface() && !ObjectUtil.isStandardInterface(typeOf) && checkMaxDepth(depth)) n = createRandomProxy(typeOf, zeroNumber, ++depth); else if (typeOf.isArray()) { n = ObjectUtil.wrap(n, typeOf.getComponentType()); n = typeOf.getComponentType().isPrimitive() ? MapUtil.asArray(typeOf.getComponentType(), n) : MapUtil.asArray(MapUtil.asMap(n, n), typeOf.getComponentType()); } else if (typeOf.equals(Object.class)) { n = new TypeBean(n.toString()); //must be equals to the type of Class (see above) } else if (!ObjectUtil.isStandardType(typeOf) && !Util.isFrameworkClass(typeOf)) { n = typeOf.getSimpleName() + "(" + ByteUtil.hashCode(n) + ")"; } return n; } static boolean checkMaxDepth(int depth) { return depth < AFunctionCaller.def(AutoTest.CREATE_RANDDOM_MAX_DEPTH, 10); } public static Construction constructWithRandomParameters(Class typeOf) { return constructWithRandomParameters(typeOf, false, 0); } static Construction constructWithRandomParameters(Class typeOf, boolean zeroNumber, int depth) { try { Constructor constructor; Object[] parameters; if (typeOf.isArray()) { Object array = Array.newInstance(typeOf.getComponentType(), 1); Array.set(array, 0, createRandomValue(typeOf.getComponentType(), zeroNumber, depth)); return new Construction(array); } else if (hasFileConstructor(typeOf)) { constructor = (Constructor) Util.trY( () -> typeOf.getConstructor(File.class)); parameters = new Object[] { FileUtil.userDirFile(createRandomValue(String.class, zeroNumber, depth)) }; } else { constructor = getBestConstructor(typeOf); if (constructor == null) throw new RuntimeException(typeOf + " is not constructable!"); else if (constructor.getParameterCount() > 0 && !checkMaxDepth(depth)) throw new IllegalStateException("max depth reached on recursion. there is a cycle in parameter instantiation: " + typeOf); parameters = provideRandomizedObjects(depth, 1, getParameterTypes(constructor)); } constructor.setAccessible(true); V instance = (V) constructor.newInstance(parameters); try { if (Boolean.getBoolean(PREFIX_FUNCTIONTEST + "inject.beanattributes")) { di.inject(instance); } if (constructor.getParameterCount() == 0 && Boolean.getBoolean(AutoTest.PREFIX_FUNCTIONTEST + "fillinstance")) instance = fillRandom(instance, zeroNumber, depth); } catch (Exception ex) { //Ok, we do not inject in cause of problems } return new Construction(instance, constructor, parameters); } catch (Exception e) { ManagedException.forward(e); return null; } } private static Class[] getParameterTypes(Constructor c) { // TODO implement getting parameterTypes including Generic Type info // Class pars[] = new Class[c.getParameterCount()]; // for (int i = 0; i < c.getParameterCount(); i++) { // pars[i] = c.getParameters()[i].getType() // } return c.getParameterTypes(); } private static boolean hasFileConstructor(Class typeOf) { String fileConstructorNames = System.getProperty(AutoTest.PREFIX_FUNCTIONTEST + "fileconstructor.classes", FileWriter.class.getName() + ", " + PrintWriter.class.getName() + ", " + PrintStream.class.getName()); List fileConstructorClasses = Arrays.asList(fileConstructorNames.split("\\s*[,;]\\s*")); return fileConstructorClasses.contains(typeOf.getName()); } private static Constructor getBestConstructor(Class typeOf) { Constructor[] cs = (Constructor[]) typeOf.getDeclaredConstructors(); BiFunction lowerParLength = (c, i) -> 0 < c.getParameterTypes().length && c.getParameterTypes().length < i; int bestLength = Integer.MAX_VALUE; int selection = -1; for (int i = 0; i < cs.length; i++) { if (lowerParLength.apply(cs[i], bestLength) && hasSimpleTypes(cs[i].getParameterTypes())) { selection = i; bestLength = cs[i].getParameterTypes().length; } } if (selection == -1) { for (int i = 0; i < cs.length; i++) { if (lowerParLength.apply(cs[i], bestLength)) { selection = i; bestLength = cs[i].getParameterTypes().length; } } if (selection == -1) { if (BeanClass.hasDefaultConstructor(typeOf)) { return Util.trY(() -> typeOf.getDeclaredConstructor(new Class[0])); } } else { selection = 0; } } return cs.length > selection ? cs[selection] : null; } private static boolean hasSimpleTypes(Class[] parameterTypes) { return Arrays.stream(parameterTypes).allMatch(t -> Util.isSimpleType(t)); } protected static Object createRandomNumber(Class typeOf) { return createRandomNumber(typeOf, intervalOf(typeOf)); } protected static Object createRandomNumber(Class typeOf, long interval) { if (NumberUtil.isNumber(typeOf)) // negative only on number types interval = interval * (Math.random() < 0.5 ? -1 : 1); Object n = Math.random() * interval; return n; } private static long intervalOf(Class typeOf) { return NumberUtil.isNumber(typeOf) && !Number.class.equals(typeOf) ? BigDecimal.class.isAssignableFrom(typeOf) ? (long) Double.MAX_VALUE : ((Number) BeanClass.getStatic(PrimitiveUtil.getWrapper(typeOf), "MAX_VALUE")).longValue() : typeOf.isEnum() ? typeOf.getEnumConstants().length : Date.class.isAssignableFrom(typeOf) ? DateUtil.MAX_DATE.getTime() : Byte.MAX_VALUE; } public static Object[] provideRandomizedObjects(int countPerType, Class... types) { return provideRandomizedObjects(0, countPerType, types); } public static Object[] provideRandomizedObjects(int depth, int countPerType, Class... types) { boolean zero = countPerType == 0; countPerType = countPerType < 1 ? 1 : countPerType; Object[] randomObjects = new Object[countPerType * types.length]; for (int i = 0; i < countPerType; i++) { for (int j = 0; j < types.length; j++) { randomObjects[i+j] = createRandomValue(types[j], zero || respectZero(countPerType, i), depth); } } return randomObjects; } // @SuppressWarnings({ "unchecked", "rawtypes" }) // public static Map provideRandomizedObjectMap(int countPerType, Class... types) { // Map all = new LinkedHashMap<>(); // for (int i = 0; i < types.length; i++) { // Object[] randomObjects = new Object[countPerType]; // for (int j = 0; j < countPerType; j++) { // if (ObjectUtil.isStandardType(types[i]) || types[i].isEnum()) // randomObjects[j] = createRandomValue(types[i], respectZero(countPerType, j)); // else // randomObjects[j] = fillRandom(BeanClass.createInstance(types[i]), respectZero(countPerType, j)); // } // all.put(types[i], randomObjects); // } // return all; // } protected static boolean respectZero(int countPerType, int currentIndex) { return countPerType > 2 && currentIndex == 0; } public static final void reset() { valueSets.clear(); di.reset(); } public static DependencyInjector getDependencyInjector() { return di; } public static void setDependencyInjector(DependencyInjector di_) { di = di_; } public static String createFromRegEx(String regex, int minLength, int maxLength, int maxIterations) { if (!regex.matches(".*\\(\\?[= buf.append(createFromRegExPart(p.group(), 1, maxLength, maxIterations))); } return buf.toString(); } else { return createFromRegExPart(regex, minLength, maxLength, maxIterations); } } public static String createFromRegExPart(String regex, int minLength, int maxLength, int maxIterations) { regex = cleanUpUnClosedBrackets(regex); String slen = StringUtil.extract(regex, "(\\d+)\\}"); minLength = (int) (slen.length() > 0 ? Integer.parseInt(slen) : minLength); Pattern pattern = Pattern.compile(regex); CharSequence init = regex.replaceAll("[^\\\\][\\\\*+?\\}\\{\\]\\[]", ""); init = new StringBuilder(((String) init).replaceAll("[\\\\]([\\\\*+?\\}\\{\\]\\[])", "$1")); System.out .print(String.format("\tgenerating with (regex: '%s', init: %s, minlen: %d, maxlen: %d, maxiter: %d) ", regex, init, init.length(), maxLength, maxIterations)); return generateString((StringBuilder) init, minLength, maxLength, maxIterations, s -> pattern.matcher(s).find(s.length() - 1) || pattern.matcher(s).lookingAt()); } private static String cleanUpUnClosedBrackets(String regex) { long open = StringUtil.countChar(regex, '('); long close = StringUtil.countChar(regex, ')'); if (open != close) { if (open > close) { regex = regex.replaceFirst("[(]", ""); } else { int l = regex.lastIndexOf(")"); StringBuilder buf = new StringBuilder(regex); buf.replace(l, l+1, ""); regex = buf.toString(); } } else if (open == 1 && close == 1 && regex.indexOf('(') > regex.indexOf(')')) { regex = regex.replaceAll("[()]", ""); } return regex; } public static String generateString(StringBuilder init, int minLength, int maxLength, int maxIterations, Predicate checker) { StringBuilder match = new StringBuilder(StringUtil.fixString(init != null ? init : " ", minLength)); int i = 0; char c; boolean matched = false; while (!matched && i++ < maxIterations && match.length() <= maxLength) { if (!(matched = checker.test(match))) match.setLength(Math.max(minLength, match.length() - 1)); else break; c = (char) (int) (32 + Math.random() * 95); if (c == 127) c++; // -> EUR if (minLength > 0) { match.setCharAt((int) (Math.random() * minLength), c); } else { match.append(c); } } // if (match.length() == 0 || !checker.test(match)) // throw new IllegalStateException("couldn't generate a string for: " + checker); String s = match.toString(); System.out.println(" -> '" + s + "' checker match: " + checker.test(s)); return s; } } @SuppressWarnings({"rawtypes", "unchecked"}) class ValueSets extends HashMap> { private static final String MINMAX = "<->"; static final String DEFAULT = "default"; static AtomicInteger counter = new AtomicInteger(0); V fromValueSet(Class typeOf) { return (V) fromValueSet(Util.getSingleBaseType(typeOf), 0); } V fromValueSet(Class typeOf, int depth) { if (!containsKey(typeOf) && (FileUtil.userDirFile(valueSetFilename(typeOf)).exists() || FileUtil.hasResource(valueSetFilename(typeOf)))) { loadValueSet(typeOf); } List values = get(typeOf); if (isMinMax(values)) { return fromValueMinMax(values.get(0), typeOf); } else { Object result = values.get((int)(Math.random() * values.size())); result = avoidCollision(result, typeOf, depth); return ObjectUtil.wrap(result, typeOf); } } private boolean isMinMax(List values) { return values.size() == 1 && values.get(0).contains(MINMAX); } private void loadValueSet(Class typeOf) { loadValueSet(typeOf, null); } private void loadValueSet(Class typeOf, String prefix) { String file = valueSetFilename(typeOf); System.out.print("loading valueset '" + file + "' with prefix '" + prefix + "'..."); String content = new String(FileUtil.getFileBytes(file, null)); content = content.replaceFirst("[#].*\n", ""); String[] names = content.split("[\n]"); if (!Util.isEmpty(prefix)) Arrays.stream(names).map(n -> prefix + n); put(typeOf, Collections.synchronizedList(new ArrayList(Arrays.asList(names)))); System.out.print(names.length + " OK!\n"); } private Object avoidCollision(Object result, Class typeOf, int depth) { if (!AFunctionCaller.def(AutoTest.VALUESET_AVOID_COLLISION, boolean.class) || PrimitiveUtil.isPrimitiveOrWrapper(typeOf)) return result; // TODO: improve performance by avoiding reload List valueSet = get(typeOf); valueSet.remove(result); if (Util.isEmpty(valueSet)) { remove(typeOf); loadValueSet(typeOf, "" + counter.addAndGet(1)); } return result; } V fromValueMinMax(String minmax, Class typeOf) { String typ = StringUtil.substring(minmax, null, ":", 0, true); String min = StringUtil.substring(minmax, ":", MINMAX); String max = StringUtil.substring(minmax, MINMAX, null); double d = NumberUtil.random(Double.valueOf(min), Double.valueOf(max)); return ObjectUtil.wrap(typ != null ? PrimitiveUtil.convert(d, PrimitiveUtil.getPrimitiveClass(typ)): d, typeOf); } boolean hasValueSet(Class typeOf) { Class t = Util.getSingleBaseType(typeOf); return containsKey(t) || FileUtil.userDirFile(valueSetFilename(t)).exists() || FileUtil.hasResource(valueSetFilename(t)); } private static String valueSetFilename(Class typeOf) { String name = AFunctionCaller.def(AutoTest.VALUESET_GROUP, DEFAULT); return (name.equals(DEFAULT) ? "" : name + "-") + typeOf.getSimpleName().toLowerCase() + ".set"; } } @Retention(RUNTIME) @Target({TYPE, METHOD, PARAMETER}) @interface ATestAnnotation { int nix(); } interface ITestInterface { void nix(); }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy