001: package com.reeltwo.jumble.fast;
002:
003: import com.reeltwo.jumble.mutation.Mutater;
004: import com.reeltwo.jumble.mutation.MutatingClassLoader;
005: import com.reeltwo.util.CLIFlags.Flag;
006: import com.reeltwo.util.CLIFlags;
007: import java.io.FileInputStream;
008: import java.io.ObjectInputStream;
009: import java.lang.management.ManagementFactory;
010: import java.lang.management.MemoryMXBean;
011: import java.lang.management.MemoryUsage;
012: import java.util.HashSet;
013: import java.util.Set;
014:
015: /**
016: * A class that gives process separation when running unit tests. A parent
017: * virtual machine monitors the progress of the test runs and terminates this
018: * process in the event of infinite loops etc. This class communicates to the
019: * parent process via standard output.
020: *
021: * @author Tin Pavlinic
022: * @version $Revision: 496 $
023: *
024: */
025: public class FastJumbler {
026:
027: public static final String INIT_PREFIX = "INIT: ";
028:
029: public static final String PASS_PREFIX = "PASS: ";
030:
031: public static final String FAIL_PREFIX = "FAIL: ";
032:
033: public static final String SIGNAL_START = "START";
034:
035: public static final String SIGNAL_MAX_REACHED = "MAX_REACHED";
036:
037: // Private c'tor
038: private FastJumbler() {
039: }
040:
041: static final String FLAG_EXCLUDE = "exclude";
042: static final String FLAG_VERBOSE = "verbose";
043: static final String FLAG_RETURN_VALS = "return-vals";
044: static final String FLAG_INLINE_CONSTS = "inline-consts";
045: static final String FLAG_INCREMENTS = "increments";
046: static final String FLAG_CPOOL = "cpool";
047: static final String FLAG_SWITCHES = "switch";
048: static final String FLAG_START = "start";
049: static final String FLAG_LENGTH = "length";
050: static final String FLAG_CLASSPATH = "classpath";
051:
052: /**
053: * Main method. Supply --help to get help on the expected arguments.
054: *
055: * @param args
056: * command line arguments.
057: */
058: public static void main(String[] args) throws Exception {
059: final CLIFlags flags = new CLIFlags("FastJumbler");
060:
061: final Flag exFlag = flags.registerOptional('x', FLAG_EXCLUDE,
062: String.class, "METHOD",
063: "Comma-separated list of methods to exclude.");
064: final Flag verboseFlag = flags.registerOptional('v',
065: FLAG_VERBOSE, "Provide extra output during run.");
066: final Flag retFlag = flags.registerOptional('r',
067: FLAG_RETURN_VALS, "Mutate return values.");
068: final Flag inlFlag = flags.registerOptional('k',
069: FLAG_INLINE_CONSTS, "Mutate inline constants.");
070: final Flag cpoolFlag = flags.registerOptional('w', FLAG_CPOOL,
071: "Mutate constant pool entries.");
072: final Flag switchFlag = flags.registerOptional('j',
073: FLAG_SWITCHES, "Mutate switch instructions.");
074: final Flag incFlag = flags.registerOptional('i',
075: FLAG_INCREMENTS, "Mutate increments.");
076: final Flag startFlag = flags
077: .registerRequired('s', FLAG_START, Integer.class,
078: "NUM", "The mutation point to start at.");
079: final Flag lengthFlag = flags.registerOptional('l',
080: FLAG_LENGTH, Integer.class, "LEN",
081: "The number of mutation points to execute");
082: final Flag classpathFlag = flags.registerOptional('c',
083: FLAG_CLASSPATH, String.class, "CLASSPATH",
084: "The classpath to use for tests", System
085: .getProperty("java.class.path"));
086: final Flag classFlag = flags.registerRequired(String.class,
087: "CLASS", "Name of the class to mutate.");
088: final Flag testSuiteFlag = flags
089: .registerRequired(String.class, "TESTFILE",
090: "Name the test suite file containing serialized TestOrder objects.");
091: final Flag cacheFileFlag = flags.registerRequired(String.class,
092: "CACHEFILE", "Name the cache file file.");
093: cacheFileFlag.setMinCount(0);
094: flags.setFlags(args);
095:
096: // First, process all the command line options
097: final String className = ((String) classFlag.getValue())
098: .replace('/', '.');
099: // Process excludes
100: Set<String> ignore = new HashSet<String>();
101: if (exFlag.isSet()) {
102: String[] tokens = ((String) exFlag.getValue()).split(",");
103: for (int i = 0; i < tokens.length; i++) {
104: ignore.add(tokens[i]);
105: }
106: }
107:
108: final int startPoint = ((Integer) startFlag.getValue())
109: .intValue();
110: final int length = lengthFlag.isSet() ? ((Integer) lengthFlag
111: .getValue()).intValue() : -1;
112: final String classpath = (String) classpathFlag.getValue();
113: System.setProperty("java.class.path", classpath); // Make classpath available to code doing classpath scanning.
114: final Mutater mutater = new Mutater(-1);
115: mutater.setIgnoredMethods(ignore);
116: mutater.setMutateIncrements(incFlag.isSet());
117: mutater.setMutateCPool(cpoolFlag.isSet());
118: mutater.setMutateSwitch(switchFlag.isSet());
119: mutater.setMutateInlineConstants(inlFlag.isSet());
120: mutater.setMutateReturnValues(retFlag.isSet());
121: MutatingClassLoader jumbler = new MutatingClassLoader(
122: className, mutater, classpath);
123: final int mutationCount = jumbler
124: .countMutationPoints(className);
125: ObjectInputStream ois = new ObjectInputStream(
126: new FileInputStream((String) testSuiteFlag.getValue()));
127: final TestOrder order = (TestOrder) ois.readObject();
128: ois.close();
129:
130: FailedTestMap cache = null;
131: if (cacheFileFlag.isSet()) {
132: ois = new ObjectInputStream(new FileInputStream(
133: (String) cacheFileFlag.getValue()));
134: cache = (FailedTestMap) ois.readObject();
135: ois.close();
136: }
137:
138: MemoryMXBean mxbean = ManagementFactory.getMemoryMXBean();
139: MemoryUsage usage = mxbean.getNonHeapMemoryUsage();
140: long nonheapDelta;
141:
142: // Let the parent JVM know that we are ready to start
143: System.out.println(SIGNAL_START);
144: // Now run all the tests for each mutation point
145: int count = 0;
146: for (int i = startPoint; i < mutationCount; i++) {
147: if (count++ >= length && lengthFlag.isSet()) {
148: System.out.println(SIGNAL_MAX_REACHED);
149: break;
150: }
151: // if (verboseFlag.isSet()) {
152: // System.err.println("Attempting mutation point: " + i);
153: // }
154: mutater.setMutationPoint(i);
155: jumbler = new MutatingClassLoader(className, mutater,
156: classpath);
157: jumbler.loadClass(className);
158: String methodName = mutater.getMutatedMethodName(className);
159: int mutPoint = mutater
160: .getMethodRelativeMutationPoint(className);
161: assert (mutPoint != -1) : "Couldn't get method relative mutation point";
162: String modification = (i == -1) ? "No mutation made"
163: : mutater.getModification();
164:
165: // Communicate to parent the current mutation being attempted
166: System.out.println(INIT_PREFIX + modification);
167:
168: // Do the run
169: String out = JumbleTestSuite.run(jumbler, order, cache,
170: className, methodName, mutPoint, verboseFlag
171: .isSet());
172:
173: // Communicate the outcome to the parent JVM.
174: if (out.startsWith("FAIL")) {
175: // This is the magic line that the parent JVM is looking for.
176: System.out.println(FAIL_PREFIX + modification);
177: } else if (out.startsWith("PASS: ")) {
178: String testName = out.substring(6);
179: if (cache != null) {
180: cache.addFailure(className, methodName, mutPoint,
181: testName);
182: }
183: // This is the magic line that the parent JVM is looking for.
184: System.out.println(PASS_PREFIX + className + ":"
185: + methodName + ":" + mutPoint + ":" + testName);
186: } else {
187: throw new RuntimeException(
188: "Unexpected result from JumbleTestSuite: "
189: + out);
190: }
191:
192: long oldUsed = usage.getUsed() / 1024;
193: usage = mxbean.getNonHeapMemoryUsage();
194: nonheapDelta = usage.getUsed() / 1024 - oldUsed;
195: long available = (usage.getMax() - usage.getUsed()) / 1024;
196: if (verboseFlag.isSet()) {
197: System.err.println("Non-Heap used:" + usage.getUsed()
198: + "KB delta:" + nonheapDelta + "KB avail:"
199: + available + "KB");
200: }
201: // Check non-heap usage and possibly bail out.
202: if (nonheapDelta > 0
203: && available < ((nonheapDelta * 5) + 15000)) {
204: // Communicate to the parent JVM if there's not enough non-heap memory to continue.
205: System.out.println(SIGNAL_MAX_REACHED
206: + " Non-Heap used:" + usage.getUsed()
207: + "KB delta:" + nonheapDelta + "KB avail:"
208: + available + "KB");
209: break;
210: }
211:
212: }
213: }
214:
215: }
|