// Copyright © 2011-2014, Esko Luontola <www.orfjackal.net> // This software is released under the Apache License 2.0. // The license text is at http://www.apache.org/licenses/LICENSE-2.0 package fi.jumi.core.util; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; import java.lang.reflect.Field; import java.util.*; public class TestableRandom implements TestRule { private final long seed; private final Random random; private final List<Object> log = new ArrayList<>(); public TestableRandom() { this(System.currentTimeMillis()); } public TestableRandom(long seed) { this.seed = seed; this.random = new Random(seed); } @Override public Statement apply(Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { try { base.evaluate(); } catch (Throwable t) { t.addSuppressed(new DebugInformation(getDebugInformation())); throw t; } } }; } public void resetSeed() { info("reset seed"); random.setSeed(seed); } public byte nextByte() { return log((byte) random.nextInt()); } public short nextShort() { return log((short) random.nextInt()); } public char nextChar() { return log((char) random.nextInt()); } public int nextInt() { return log(random.nextInt()); } public int nextInt(int exclusiveMax) { return log(random.nextInt(exclusiveMax)); } public long nextLong() { return log(random.nextLong()); } // debug information public void info(String message) { log.add(new Info(message)); } private <T> T log(T generatedValue) { log.add(generatedValue); return generatedValue; } private String getDebugInformation() { String message = TestableRandom.class.getSimpleName() + " seed was " + seed + "L"; for (Object obj : log) { if (obj instanceof Info) { message += "\n- " + obj; } else { message += "\n- (" + formatType(obj) + ") " + obj; } } return message; } private static String formatType(Object obj) { try { Field f = obj.getClass().getField("TYPE"); Class<?> primitiveType = (Class<?>) f.get(null); return primitiveType.toString(); } catch (NoSuchFieldException e) { return obj.getClass().getSimpleName(); } catch (Exception e) { throw new RuntimeException(e); } } private static class Info { private final String message; public Info(String message) { this.message = message; } @Override public String toString() { return message; } } private static class DebugInformation extends RuntimeException { public DebugInformation(String message) { super("\n" + message); } } }