org.deephacks.tools4j.config.test.JUnitUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tools4j-config-tck Show documentation
Show all versions of tools4j-config-tck Show documentation
Functional Tests for Tools4j Config
package org.deephacks.tools4j.config.test;
import com.google.common.base.Charsets;
import com.google.common.io.CharStreams;
import org.deephacks.tools4j.config.model.Bean;
import org.deephacks.tools4j.config.model.Bean.BeanId;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
public class JUnitUtils {
/**
* Compute the root directory of this maven project. This will result in the
* same directory no matter if executed from Eclipse, this maven project root or
* any parent maven pom directory.
*
* @param anyTestClass Any test class *local* to the maven project, i.e that
* only exist in this maven project.
*
* @param anyTestClass The file that should be
* @return The root directory of this maven project.
*/
public static File computeMavenProjectRoot(Class> anyTestClass) {
final String clsUri = anyTestClass.getName().replace('.', '/') + ".class";
final URL url = anyTestClass.getClassLoader().getResource(clsUri);
final String clsPath = url.getPath();
// located in ./target/test-classes or ./eclipse-out/target
final File target_test_classes = new File(clsPath.substring(0,
clsPath.length() - clsUri.length()));
// get parent's parent
return target_test_classes.getParentFile().getParentFile();
}
/**
* Normalizes the root for reading a file to the maven project root directory.
*
* @param anyTestClass Any test class *local* to the maven project, i.e that
* only exist in this maven project.
*
* @param child A child path.
*
* @return A file relative to the maven root.
*/
public static File getMavenProjectChildFile(Class> anyTestClass, String child) {
return new File(computeMavenProjectRoot(anyTestClass), child);
}
public static File getMetaInfDir(Class> context) {
URL url = context.getResource("/META-INF/");
try {
return new File(url.toURI());
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
public static List readMetaInfResource(Class> context, String filepath) {
InputStream in = context.getResourceAsStream("/META-INF/" + filepath);
ArrayList list = new ArrayList();
try {
String content = CharStreams.toString(new InputStreamReader(in, Charsets.UTF_8));
list.addAll(Arrays.asList(content.split("\n")));
return list;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Mini-languge for generating classes and bean instances.
*
* Ex: A=1000, B=200$10, C=3$300; B=2000, C=200$10; C=300
*
* - creates 1000 A, 2000 B and 300 C instances.
* - Assign 200 random A instances to 10 random B references
* - Assign 3 random A instances to 300 random C references
* - Assign 200 random B instances to 10 random C references
*/
public static Set generate(String minilang, int numprops, int numvalues) {
String[] cfgtypes = minilang.split(";");
HashMap configs = new HashMap<>();
// declared config types
for (String cfgtype : cfgtypes) {
String[] stmts = cfgtype.split(",");
String stmt = stmts[0].trim();
String[] assign = stmt.split("=");
ConfigClass clazz = new ConfigClass(assign[0].trim(), ConfigClass.randomFieldName());
clazz.addBeans(Integer.parseInt(assign[1].trim()));
String[] refstmts = new String[cfgtype.split(",").length - 1];
System.arraycopy(stmts, 1, refstmts, 0, refstmts.length);
clazz.stmts = refstmts;
configs.put(assign[0].trim(), clazz);
}
// reference assignment for each config type
for (String parentname : configs.keySet()) {
ConfigClass parent = configs.get(parentname);
for (int i = 0; i < numprops; i++) {
parent.addField(numvalues);
}
for (String stmt : parent.stmts) {
String childname = stmt.split("=")[0].trim();
String refs = stmt.split("=")[1].trim();
ConfigClass child = configs.get(childname);
int parentnum = Integer.parseInt(refs.split("\\$")[0].trim());
int childnum = Integer.parseInt(refs.split("\\$")[1].trim());
parent.addReferences(parentnum, childnum, child);
}
}
return new HashSet<>(configs.values());
}
public static List generateBeans(int numBeans, int numProps) {
ArrayList beans = new ArrayList<>();
for (int i = 0; i < numBeans; i++) {
String id = "beanId" + i;
String type = "beanType" + i;
Bean bean = Bean.create(BeanId.create(id, type));
for (int j = 0; j < numProps; j++) {
String _name = "propName" + j;
String _value = "propFieldName" + j;
bean.addProperty(_name, _value);
List d = Arrays.asList("1", "2", "3");
bean.addProperty(_name, d);
}
beans.add(bean);
}
return beans;
}
/**
* Compile a generated config class to given directory.
*
* Classes will be loaded into current thread classloader.
*/
public static Set> compile(ConfigClass config, File dir) {
return compile(Arrays.asList(config), dir);
}
/**
* Compile generated config classes to given directory.
*
* Classes will be loaded into current thread classloader.
*/
public static Set> compile(Collection configs, File dir) {
HashMap sources = new HashMap<>();
for (ConfigClass config : configs) {
sources.put(config.classname, config.toString());
}
return CompilerUtils.compile(sources, ConfigClass.class.getPackage().getName(), dir);
}
/**
* A dynamically generated config class.
*/
public static class ConfigClass {
private final static Class>[] types = new Class[] { Long.class, String.class };
static final String CLASSNAME = "%classname%";
static final String ID = "%idfield%";
static final String ID_NAME = "%idname%";
static final String ID_TYPE = "%idtype%";
static final String ID_TEMPLATE = " @Id(desc=\"" + ID_NAME + "\") private " + ID_TYPE + " "
+ ID_NAME + ";";
static final String FIELDS = "%fields%";
static final String FIELD_NAME = "%fieldname%";
static final String FIELD_TYPE = "%fieldtype%";
static final String FIELD_TEMPLATE = " @Config(desc=\"" + FIELD_NAME + "\") private "
+ FIELD_TYPE + " " + FIELD_NAME + ";";
static final String REFERENCES = "%references%";
static final String REFERENCE_NAME = "%referencename%";
static final String REFERENCE_TYPE = "%referencetype%";
static final String REFERENCE_TEMPLATE = " @Config(desc=\"" + REFERENCE_NAME
+ "\") private " + REFERENCE_TYPE + " " + REFERENCE_NAME + ";";
static final String CLASS_TEMPLATE = "package " + ConfigClass.class.getPackage().getName()
+ ";\n" + "import org.deephacks.tools4j.config.*;\nimport java.util.*;\n"
+ "@Config(name=\"" + CLASSNAME + "\", desc=\"" + CLASSNAME + "\")\n"
+ "public class %classname% {\n" + ID + "\n" + FIELDS + "\n" + REFERENCES + "\n}";
private Map fields = new HashMap<>();
private Map references = new HashMap<>();
public String classname;
public String idName;
public String idType;
public ArrayList beans = new ArrayList<>();
String[] stmts = new String[0];
public ConfigClass(String classname, String idName) {
this.classname = classname;
this.idName = idName;
this.idType = "String";
}
public static Set generate(int numTypes, int minProps, int maxProps) {
numTypes = numTypes == 0 ? 1 : numTypes;
Set classes = new HashSet<>();
for (int i = 0; i < numTypes; i++) {
int numProps = randomInt(minProps, maxProps);
ConfigClass config = new ConfigClass(randomClassname(), randomFieldName());
for (int j = 0; j < numProps; j++) {
config.addField(10);
}
classes.add(config);
}
return classes;
}
public void addBeans(int num) {
for (int i = 0; i < num; i++) {
Bean b = Bean.create(BeanId.create(randomAlphanumeric(20), classname));
beans.add(b);
}
}
public List getBeans() {
return beans;
}
public void addField(int numvalues) {
Class> clazz = types[new Random().nextInt(types.length)];
boolean isList = new Random().nextBoolean();
String name = randomFieldName();
if (!isList) {
fields.put(name, clazz.getName());
for (Bean bean : beans) {
bean.addProperty(name, generateRandomValue(clazz));
}
} else {
fields.put(name, "List<" + clazz.getName() + ">");
for (Bean bean : beans) {
for (int i = 0; i < numvalues; i++) {
bean.addProperty(name, generateRandomValue(clazz));
}
}
}
}
public void addReferenceRandom(String type) {
boolean isList = new Random().nextBoolean();
if (!isList) {
addReference(type);
} else {
addReferenceList(randomFieldName(), type);
}
}
public void addReferences(int parentnum, int childnum, ConfigClass child) {
addReferenceList(child.classname, child.classname);
Bean[] b = beans.toArray(new Bean[0]);
for (int i = 0; i < parentnum; i++) {
List numbers = randomUniqueInt(0, beans.size() - 1);
while (parentnum-- > 0) {
if (parentnum > b.length) {
throw new IllegalArgumentException(
"MINILANG: too few instances available [" + b.length
+ "] to reference [" + parentnum + "] instances.");
}
Bean parent = b[numbers.get(parentnum)];
Set children = child.getInstances(childnum);
for (Bean bean : children) {
// circular references to self is not allowed.
if (!bean.equals(parent)) {
parent.addReference(bean.getId().getSchemaName(), bean.getId());
}
}
}
}
}
private Set getInstances(int num) {
Bean[] b = beans.toArray(new Bean[0]);
Set result = new HashSet<>();
List numbers = randomUniqueInt(0, num - 1);
while (num-- > 0) {
result.add(b[numbers.get(num)]);
}
return result;
}
public void addReference(String type) {
references.put(randomFieldName(), type);
}
public void addReferenceList(String name, String type) {
references.put(name, "List<" + type + ">");
}
public Collection getFields() {
return fields.keySet();
}
public Collection getReferences() {
return references.keySet();
}
/**
* Class names are assumed to have between 8 - 40 alphabetic characters.
*/
private static String randomClassname() {
return randomAlphabeth(8, 20);
}
/**
* Field names are assumed to have between 5 - 15 alphabetic characters.
*/
private static String randomFieldName() {
return randomAlphabeth(5, 15);
}
/**
* generate a random number between min and max
*/
private static int randomInt(int min, int max) {
return min + (new Random()).nextInt(max - min);
}
private static List randomUniqueInt(int min, int max) {
List numbers = new ArrayList<>();
for (int i = min; i <= max; i++) {
numbers.add(i);
}
Collections.shuffle(numbers);
return numbers;
}
/**
* generate a random number of alphabetic characters between min and max
*/
private static String randomAlphabeth(int min, int max) {
return randomAlphabetic(randomInt(min, max));
}
/**
* generate a random number of alphanumeric characters between
* min and max
*/
private static String randomAlphanum(int min, int max) {
return randomAlphanumeric(randomInt(min, max));
}
private final static List longValueCache = new ArrayList<>();
private static int numLongs = 0;
private final static List doubleValueCache = new ArrayList<>();
private static int numDoubles = 0;
private final static List stringValueCache = new ArrayList<>();
private static int numStrings = 0;
/**
* This method will generate at most 1 million random values for each type
* and then serve the same values again to save memory and time.
*/
private static final String generateRandomValue(Class> clazz) {
String value = null;
if (clazz.isAssignableFrom(Long.class)) {
if (numLongs > 1000000) {
return longValueCache.get(numLongs++ % 1000000);
}
numLongs++;
value = "" + new Random().nextLong();
longValueCache.add(value);
} else if (clazz.isAssignableFrom(Double.class)) {
if (numDoubles > 1000000) {
return doubleValueCache.get(numDoubles++ % 1000000);
}
numDoubles++;
value = "" + new Random().nextDouble();
doubleValueCache.add(value);
} else if (clazz.isAssignableFrom(String.class)) {
if (numStrings > 1000000) {
return stringValueCache.get(numStrings++ % 1000000);
}
numStrings++;
value = randomAlphanum(10, 40);
stringValueCache.add(value);
}
return value;
}
/**
* Add references between configurables with given probability.
* 2 = 50%, 3 = 33%, 4 = 25 % and so on.
*/
@SuppressWarnings("unused")
private static void addReferences(Set all, int probability) {
for (ConfigClass target : all) {
for (ConfigClass source : all) {
if (new Random().nextInt(probability) == 0) {
target.addReference(source.classname);
}
}
}
}
@Override
public String toString() {
String clazz = CLASS_TEMPLATE.replaceAll(CLASSNAME, classname);
String fieldStr = replace(fields, FIELD_TEMPLATE, FIELD_TYPE, FIELD_NAME);
String referenceStr = replace(references, REFERENCE_TEMPLATE, REFERENCE_TYPE,
REFERENCE_NAME);
String id = ID_TEMPLATE.replaceAll(ID_NAME, idName);
id = id.replaceAll(ID_TYPE, idType);
clazz = clazz.replaceAll(ID, id);
clazz = clazz.replaceAll(FIELDS, fieldStr);
clazz = clazz.replaceAll(REFERENCES, referenceStr);
return clazz;
}
private String replace(Map content, String template, String type,
String name) {
StringBuffer referenceStrs = new StringBuffer();
for (String key : content.keySet()) {
String reference = template.replaceAll(name, key);
reference = reference.replaceAll(type, content.get(key));
referenceStrs.append(reference).append("\n");
}
return referenceStrs.toString();
}
}
}