package xapi.gwt.junit.api; import xapi.collect.X_Collect; import xapi.collect.api.IntTo; import xapi.log.X_Log; import xapi.time.X_Time; import xapi.util.X_Util; import xapi.util.api.ProvidesValue; import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.Callable; import java.util.function.BiFunction; import java.util.function.BooleanSupplier; import java.util.function.Function; /** * Created by james on 18/10/15. */ public class JUnitExecution<Context> { private int[] timeouts; private int[] intervals; private int maxTimeoutsLeft; private int maxIntervalsLeft; private BooleanSupplier[] beforeFinished; private BiFunction<Method, Throwable, Callable<Boolean>>[] onFinishedMethod; private Function<Method, Callable<Boolean>>[] onStartMethod; private BiFunction<Class<?>, Object, Callable<Boolean>>[] onStartClass; private Function<Map<Method, Throwable>, Callable<Boolean>>[] onFinishedClass; private Throwable error; private Double deadline; private Context context; private Object instance; public JUnitExecution() { clear(); } public boolean isFinished() { return allDone(beforeFinished) || isErrorState(); } public boolean isTimeoutsClear() { return (timeouts.length <= maxTimeoutsLeft) && (intervals.length <= maxIntervalsLeft); } protected boolean allDone(BooleanSupplier[] beforeFinished) { for (BooleanSupplier before : beforeFinished) { if (!before.getAsBoolean()) { return false; } } return true; } public JUnitExecution<Context> onBeforeFinished(BooleanSupplier test) { beforeFinished = X_Util.pushOnto(beforeFinished, test); return this; } public JUnitExecution<Context> onFinished(BiFunction<Method, Throwable, Callable<Boolean>> callback) { onFinishedMethod = X_Util.pushOnto(onFinishedMethod, callback); return this; } public JUnitExecution<Context> onStartMethod(Function<Method, Callable<Boolean>> callback) { onStartMethod = X_Util.pushOnto(onStartMethod, callback); return this; } public JUnitExecution<Context> onStartClass(BiFunction<Class<?>, Object, Callable<Boolean>> callback) { onStartClass = X_Util.pushOnto(onStartClass, callback); return this; } public JUnitExecution<Context> onFinishedClass(Function<Map<Method,Throwable>, Callable<Boolean>> callback) { onFinishedClass = X_Util.pushOnto(onFinishedClass, callback); return this; } public void autoClean() { onFinishedClass(e -> { this.clear(); return null; }); } protected boolean isErrorState() { return error != null || (deadline != null && X_Time.isPast(deadline)); } public JUnitExecution<Context> setDeadline(double deadline) { this.deadline = deadline; return this; } public JUnitExecution<Context> clearDeadline() { this.deadline = null; return this; } /** * Completely destroys this execution and reseting it to a clean state. * <p> * If you want your context object to survive, stash it in between invocations. */ public JUnitExecution<Context> clear() { clearDeadline(); error = null; context = null; if (timeouts != null) { kill(timeouts, intervals); } timeouts = new int[0]; intervals = new int[0]; onStartClass = new BiFunction[0]; onStartMethod = new Function[0]; onFinishedMethod = new BiFunction[0]; onFinishedClass = new Function[0]; beforeFinished = new BooleanSupplier[]{ // The default test is that the timeouts and intervals are cleared. this::isTimeoutsClear }; return this; } private native void kill(int[] timeouts, int[] intervals) /*-{ timeouts.forEach(clearTimeout); intervals.forEach(clearInterval); }-*/; /** * Report an error of any kind. Does nothing if the Throwable parameter is null. * <p> * If the string message is expensive to construct, * and you are too lazy to do the null check yourself, * use {@link JUnitExecution#reportError(Throwable, ProvidesValue)}, * so you can instead send a lambda, and let the null check prevent potentially * wasteful .toString() invocations. */ public void reportError(Throwable error, String message) { if (error != null) { // Never overwrite an error if somebody sends null this.error = error; try{ X_Log.error(getClass(), "\n", message, "\n", error); } catch (Error rethrow){ throw rethrow; } catch (Throwable ignored){ } } } /** * Report an error of any kind. Does nothing if the Throwable parameter is null. * <p> * Prefer this method if the debugging string is relatively expensive to construct. * <p> * This method is final so you only have to override the method which accepts a string. */ public final void reportError(Throwable error, ProvidesValue<String> message) { if (error != null) { reportError(error, message.get()); } } public Context getContext() { return context; } public void setContext(Context context) { this.context = context; } public Iterable<Callable<Boolean>> finishClass(Map<Method, Throwable> results) { final Function<Map<Method, Throwable>, Callable<Boolean>>[] copy = onFinishedClass; onFinishedClass = new Function[0]; IntTo<Callable<Boolean>> delays = X_Collect.newList(Callable.class); for (Function<Map<Method, Throwable>, Callable<Boolean>> callback : copy) { final Callable<Boolean> delay = callback.apply(results); if (delay != null) { delays.add(delay); } } return delays.forEach(); } public Iterable<Callable<Boolean>> finishMethod(Method m, Throwable e) { IntTo<Callable<Boolean>> delays = X_Collect.newList(Callable.class); for (BiFunction<Method, Throwable, Callable<Boolean>> callback : onFinishedMethod) { final Callable<Boolean> delay = callback.apply(m, e); if (delay != null) { delays.add(delay); } } if (delays.isEmpty()) { error = null; } else { delays.add(()->{ error = null; return true; }); } return delays.forEach(); } public void normalizeLimits() { maxIntervalsLeft = intervals.length; maxTimeoutsLeft = timeouts.length; } public Iterable<Callable<Boolean>> startMethod(Method method) { IntTo<Callable<Boolean>> delays = X_Collect.newList(Callable.class); for (Function<Method, Callable<Boolean>> callback : onStartMethod) { final Callable<Boolean> delay = callback.apply(method); if (delay != null) { delays.add(delay); } } return delays.forEach(); } public Iterable<Callable<Boolean>> startClass(Class<?> testClass, Object inst) { IntTo<Callable<Boolean>> delays = X_Collect.newList(Callable.class); for (BiFunction<Class<?>, Object, Callable<Boolean>> callback : onStartClass) { final Callable<Boolean> delay = callback.apply(testClass, inst); if (delay != null) { delays.add(delay); } } return delays.forEach(); } public Object getInstance() { return instance; } public void setInstance(Object instance) { this.instance = instance; } public boolean hasError() { return error != null; } public Throwable getError() { return error; } }