So I developed a fairly simple class, called TestSimulator, to do "Error Injection". :-) Basically, at key parts in your code you insert one line of code
TestSimulator.doCommand(someUniqueStringRepresentingYourLocation);
e.g.
TestSimulator.doCommand("MyClassName.someMethodName-complete");
TestSimulator has a boolean enabled, plus a HashMap
It uses the unique string to look up a command, which is a simple DSL for what to do. It currently supports
throw:exceptionClass
throw:exceptionClass(message)
sleep:milliseconds
interrupt:
All of them do pretty much what you'd expect. For example, you could say throw:java.lang.ArithmeticException(Too many foobars). (no quotes) Note that interrupt: does not throw an Interrupted exception, use throw: for that, it calls Thread.currentThread().interrupt();
so you can test if you are properly checking that flag later.
Here's the source code. Error handling within the class is a bit primitive.
public class TestSimulator { public static boolean enabled = false; /** * Commands have the form command:value * Currently we support * throw:exceptionclass, e.g. throw:java.lang.ArithmeticException * throw:exceptionclass(message), e.g. throw:java.lang.ArithmeticException(Too many foobars) * sleep:milliseconds, e.g. sleep:1000 * interrupt: */ public static final HashMap<String, String> sCommandMap = new HashMap(); /** * Executes the command for the given key * * @param key * @throws Exception the most common case throws some form of Throwable */ public static void doCommand(String key) throws Exception { String command = sCommandMap.get(key); if (!enabled || command == null) return; System.out.println("testSimulator.doCommand " + command); if (command.startsWith("throw:")) { Throwable t = makeThrowable(command.substring(6)); if (t instanceof Exception) throw (Exception)t; else if (t instanceof Error) throw (Error)t; } else if (command.startsWith("sleep:")) { long milliseconds = Long.parseLong(command.substring(6)); Thread.sleep(milliseconds); } else if (command.startsWith("interrupt:")) { Thread.currentThread().interrupt(); } else { throw new IllegalArgumentException(key + " = " + command); } } // utilities static Throwable makeThrowable(String classNameAndMessage) { String message = null; String className = classNameAndMessage.trim(); int paren = classNameAndMessage.indexOf('('); if (paren > 0) { message = classNameAndMessage.substring(paren+1, classNameAndMessage.length()-1); className = classNameAndMessage.substring(0, paren).trim(); } try { Class<? extends Throwable> clazz = (Class<? extends Throwable>) Class.forName(className); if (message == null) return clazz.newInstance(); else return clazz.getConstructor(String.class).newInstance(message); } catch (Exception e) { e.printStackTrace(); return null; } }
To actually use this class, I've been writing small little mains in the classes I am primarily interested in testing. There's probably a better way, but this works for now. e.g. if I am testing a class called CalculatePI, it would have a main looking like:
public static void main(String[] args) throws Exception {
TestSimulator.enabled = true;
// modify the following as desired
TestSimulator.sCommandMap.put("CalculatePI.calculateThis1", null);
TestSimulator.sCommandMap.put("CalculatePI.longCalculationStep3", "interrupt:");
TestSimulator.sCommandMap.put("CalculatePI.longCalculation-complete", throw: java.lang.ArithmeticException(Failed to converge)");
// launch the big fancy app as appropriate here...
MainClass.main(new String[0]);
}
No comments:
Post a Comment