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