package com.sandwich.koan;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.io.Serializable;
import java.lang.Thread.State;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Random;
import com.sandwich.koan.constant.KoanConstants;
public class TestUtils {
private static Random random = new Random(System.currentTimeMillis());
public static final String EOL = System.getProperty("line.separator");
public static final int MAX_UNIQUE_CHARS = 1000;
public static long sizeInBytes(Serializable... objects) {
byte[] ba = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(objects);
oos.close();
ba = baos.toByteArray();
baos.close();
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
return ba.length;
}
public static void chewUpVM(){
chewUpVM(100);
}
public static void chewUpVM(long forApproxHowLongInMs){
long start = System.currentTimeMillis();
while(System.currentTimeMillis() - start > forApproxHowLongInMs){new Object();}
}
public static String getRandomText(int i) {
return getRandomText(i,false);
}
public static String getRandomText(int i, boolean enforceUnique) {
if(enforceUnique && i > MAX_UNIQUE_CHARS){
throw new RuntimeException("call getRandomText w/ a value under 1000 if u wish to enforce unique");
}
StringBuilder sb = new StringBuilder();
int j = 0;
while(j < i){
char nextChar = (char)random.nextInt();
if(enforceUnique && sb.indexOf(String.valueOf(nextChar)) != -1){
continue;
}
sb.append(nextChar);
j++;
}
return sb.toString();
}
public static long time(Runnable runnable) {
long start = System.currentTimeMillis();
runnable.run();
return System.currentTimeMillis() - start;
}
public static void forEachLine(String text, ArgRunner<String> runnable){
String[] strings = text.split(KoanConstants.EOLS, -1);
for(String line : strings){
runnable.run(line);
}
}
@SuppressWarnings("unchecked")
public static <T extends Serializable> T serialize(T t) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(t);
oos.close();
final byte[] ba = baos.toByteArray();
baos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(ba);
ObjectInputStream ois = new ObjectInputStream(bais);
Object returnValue = ois.readObject();
ois.close();
return (T) returnValue;
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("unchecked")
public static <T> T getValue(String fieldName, Object target) {
Class<?> clazz = target instanceof Class<?> ? (Class<?>)target : target.getClass();
Field field = getField(clazz, fieldName);
try {
boolean wasAccessible = field.isAccessible();
field.setAccessible(true);
Object value = field.get(target);
field.setAccessible(wasAccessible);
return (T)value;
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public static void setValue(String fieldName, Object value, Object target) {
Class<?> clazz = target instanceof Class<?> ? (Class<?>)target : target.getClass();
Field field = getField(clazz, fieldName);
try {
boolean wasAccessible = field.isAccessible();
field.setAccessible(true);
field.set(target, value);
field.setAccessible(wasAccessible);
return;
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public static Field getField(Class<?> clazz, String fieldName){
String className = clazz.getName();
do{
for(Field field : clazz.getDeclaredFields()){
if(field.getName().equals(fieldName)){
return field;
}
}
clazz = (Class<?>)clazz.getSuperclass();
}while(clazz != null);
throw new IllegalArgumentException(fieldName+" was not found in class: "+className);
}
/**
* Used to simulate multiple threads accessing the same method whilst busy.
* Will fail in insufficiently concurrent scenarios.
*
* @param assertion
* @param rs
* @throws InterruptedException
*/
public static void doSimultaneouslyAndRepetitively(TwoObjectAssertion assertion, final Runnable... rs) throws InterruptedException{
assertTrue("Whats the point of testing for synchronization w/ less than two threads?", rs.length > 1);
final int[] c = new int[]{0};
int startRuns = rs.length + 9;
final Thread[] threads = new Thread[rs.length];
int totalRuns = 0;
for(int i = 0; i < rs.length; i++){
final int it = i;
final int startRunsTemp = startRuns;
totalRuns += startRunsTemp;
startRuns--;
threads[i] = new Thread(new Runnable(){
public void run() {
Runnable runnable = rs[it];
for(int j = 0; j < startRunsTemp; j++){
runnable.run();
c[0]++;
}
}
});
threads[i].start();
}
allowThreadsToFinish(threads);
assertion.assertOn("Threads died before they could finish.", totalRuns, c[0]);
}
public static void doSimultaneouslyAndRepetitively(TwoObjectAssertion assertion,
Class<? extends Throwable> anticipatedExceptionClass,
final Runnable... rs) throws InterruptedException{
PrintStream temp = System.err;
try {
ByteArrayOutputStream sysErr = new ByteArrayOutputStream();
System.setErr(new PrintStream(sysErr));
doSimultaneouslyAndRepetitively(assertion, rs);
String errors = sysErr.toString();
int i = rs.length;
assertFalse(errors.contains("Thread-"+(i+1)));
for(;i > 0; i--){
assertTrue(errors.contains("Thread-"+i+"\" "+anticipatedExceptionClass.getName()));
}
} finally {
System.setErr(new PrintStream(temp));
}
}
/**
* Will lock Thread accessible when called from Thread via repetitive sleep
* calls until the passed in threads are terminated. Generally used to keep
* the test exec thread alive while the other threads are still working.
*
* @param threads
* @throws InterruptedException
*/
public static void allowThreadsToFinish(final Thread[] threads)
throws InterruptedException {
boolean threadsDone = false;
while(!threadsDone){
threadsDone = true;
for(Thread thread : threads){
if(thread.getState() != State.TERMINATED){
Thread.sleep(10);
threadsDone = false;
}
}
}
}
public static void assertEqualsContractEnforcement(Object obj0, Object obj1, Object obj2){
assertFalse(obj0.equals(null));
assertFalse(obj1.equals(null));
assertFalse(obj2.equals(null));
assertEquals(obj0, obj0);
assertEquals(obj1, obj1);
assertEquals(obj2, obj2);
assertEquals(obj0, obj1);
assertEquals(obj1, obj0);
assertEquals(obj1, obj2);
assertEquals(obj2, obj1);
assertEquals(obj2, obj0);
assertEquals(obj0, obj2);
}
public static void assertHashCodeContractEnforcement(Object obj0, Object obj1, Object obj2){
assertEquals(obj0.hashCode(), obj0.hashCode());
assertEquals(obj1.hashCode(), obj1.hashCode());
assertEquals(obj2.hashCode(), obj2.hashCode());
assertEquals(obj0.hashCode(), obj1.hashCode());
assertEquals(obj1.hashCode(), obj0.hashCode());
assertEquals(obj1.hashCode(), obj2.hashCode());
assertEquals(obj2.hashCode(), obj1.hashCode());
assertEquals(obj2.hashCode(), obj0.hashCode());
assertEquals(obj0.hashCode(), obj2.hashCode());
}
public static void assertHashCodeEqualsContracts(Object obj0, Object obj1, Object obj2){
assertEqualsContractEnforcement(obj0, obj1, obj2);
assertHashCodeContractEnforcement(obj0, obj1, obj2);
}
public static Object invokePrivate(
String name,
final Object obj,
Object[] objects) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
Method method = null;
Class<?> objClass = obj instanceof Class ? (Class<?>)obj : obj.getClass();
while(objClass != null && method == null){
for(Method m : objClass.getDeclaredMethods()){
if (name.equals(m.getName()) &&
(objects == null && m.getParameterTypes().length == 0) ||
(objects != null && objects.length == m.getParameterTypes().length)){
method = m;
break;
}
}
objClass = objClass.getSuperclass();
}
if(method == null){
throw new IllegalArgumentException("method with name "+name+" was not found, check class definition.");
}
final boolean wasAccessible = method.isAccessible();
try{
method.setAccessible(true);
return method.invoke(obj, objects);
}finally{
if(wasAccessible != method.isAccessible()){
method.setAccessible(wasAccessible);
}
}
}
public static interface ArgRunner<T> {
public void run(T t);
}
public static interface TwoObjectAssertion {
/**
* Message to display when assertion fails. Followed by 2 objects to
* assertOn. Useful for reusing in object composition tests or a
* chain of command pattern based assertion chain.
*
* @param msg
* @param o0
* @param o1
*/
void assertOn(String msg, Object o0, Object o1);
}
}