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

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

package de.tsl2.nano.autotest;

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.File;
import java.io.PrintWriter;
import java.io.Serializable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.math.BigDecimal;
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.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

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.BeanClass;
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.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 */ public class ValueRandomizer { private static final ValueSets valueSets = new ValueSets(); private ValueRandomizer() { } public static Object fillRandom(Object obj) { return fillRandom(obj, false, 0); } public static Object fillRandom(Object obj, boolean zeroNumber, final int depth) { PrivateAccessor acc = new PrivateAccessor<>(obj); Field[] fields = acc.findMembers(PrivateAccessor::notStaticAndNotFinal); Arrays.stream(fields).forEach(f -> acc.set(f.getName(), createRandomValue(f.getType(), zeroNumber, depth+1))); return obj; } public static Object createRandomProxy(Class interfaze, boolean zeroNumber) { return createRandomProxy(interfaze, zeroNumber, 0); } public static Object createRandomProxy(Class interfaze, boolean zeroNumber, final int depth) { Map types = AdapterProxy.getValueTypes(interfaze); Map values = new HashMap<>(); types.forEach( (n, t) -> values.put(n, createRandomValue(t, zeroNumber, depth+1))); 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); } @SuppressWarnings({ "unchecked" }) protected static V createRandomValue(Class typeOf, boolean zeroNumber, int depth) { Object n; 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); } if (NumberUtil.isNumber(n)) n = convert(n, typeOf, zeroNumber, depth); try { return ObjectUtil.wrap(n, typeOf); } catch (Exception e) { if (checkMaxDepth(depth) && ObjectUtil.isInstanceable(typeOf)) { return constructWithRandomParameters(typeOf, ++depth).instance; } else { ManagedException.forward(e); return null; } } } private static Object convert(Object n, Class typeOf, boolean zeroNumber, int depth) { 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; // TODO: create randomly } else if (typeOf.equals(ClassLoader.class)) n = Thread.currentThread().getContextClassLoader(); // TODO: create randomly else if (Collection.class.isAssignableFrom(typeOf)) 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)) 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) || !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, 0); } @SuppressWarnings({ "unchecked" }) static Construction constructWithRandomParameters(Class typeOf, int depth) { try { Constructor constructor; Object[] parameters; if (PrintWriter.class.isAssignableFrom(typeOf)) { // poor workaround to avoid PrintWriter writing to project directory (instead of target) constructor = (Constructor) Util.trY( () -> PrintWriter.class.getConstructor(File.class)); parameters = new Object[] {FileUtil.userDirFile(createRandomValue(String.class))}; } else { constructor = getBestConstructor(typeOf); if (constructor == null) throw new RuntimeException(typeOf + " is not constructable!"); 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, constructor.getParameterTypes()); } return new Construction(constructor.newInstance(parameters), constructor, parameters); } catch (Exception e) { ManagedException.forward(e); return null; } } private static Constructor getBestConstructor(Class typeOf) { if (BeanClass.hasDefaultConstructor(typeOf)) return Util.trY( () -> typeOf.getConstructor(new Class[0])); Constructor[] cs = (Constructor[]) typeOf.getConstructors(); for (int i = 0; i < cs.length; i++) { if (cs[i].getParameterTypes().length == 1) return cs[i]; } return cs.length > 0 ? cs[0] : null; } 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); } @SuppressWarnings({ "unchecked", "rawtypes" }) 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++) { if (ObjectUtil.isStandardType(types[j]) || types[j].isEnum() || ByteUtil.isByteStream(types[j]) || Serializable.class.isAssignableFrom(types[j])) randomObjects[i+j] = createRandomValue(types[j], zero || respectZero(countPerType, i), depth); else if (types[j].isInterface()) randomObjects[i+j] = createRandomProxy(types[j], zero, depth); else { Class type = types[j].equals(Object.class) ? TypeBean.class : types[j]; randomObjects[i+j] = fillRandom(constructWithRandomParameters(type, ++depth).instance, 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(); } } @SuppressWarnings({"serial", "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 + "' ..."); 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; 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