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.jumble.ui.JumbleListener;
006: import com.reeltwo.jumble.ui.NullListener;
007: import com.reeltwo.jumble.util.IOThread;
008: import com.reeltwo.jumble.util.JavaRunner;
009: import com.reeltwo.jumble.util.JumbleUtils;
010: import java.io.File;
011: import java.io.FileInputStream;
012: import java.io.FileOutputStream;
013: import java.io.IOException;
014: import java.io.ObjectInputStream;
015: import java.io.ObjectOutputStream;
016: import java.util.ArrayList;
017: import java.util.HashSet;
018: import java.util.Iterator;
019: import java.util.List;
020: import java.util.Set;
021: import java.util.StringTokenizer;
022:
023: /**
024: * A runner for the <CODE>FastJumbler</CODE>. Runs the FastJumbler in a new
025: * JVM and detects timeouts.
026: *
027: * @author Tin Pavlinic
028: * @version $Revision: 516 $
029: */
030: public class FastRunner {
031:
032: /** Filename for the cache */
033: public static final File CACHE_FILE = new File(System
034: .getProperty("user.home"), ".com.reeltwo.jumble-cache.dat");
035:
036: // Configuration properties
037:
038: /** Whether to mutate constants */
039: private boolean mInlineConstants = true;
040:
041: /** Whether to mutate return values */
042: private boolean mReturnVals = true;
043:
044: /** Whether to mutate increments */
045: private boolean mIncrements = true;
046:
047: /** Whether to mutate constant pool */
048: private boolean mCPool = true;
049:
050: /** Whether to mutate switches */
051: private boolean mSwitches = true;
052:
053: private boolean mOrdered = true;
054:
055: private boolean mVerbose = false;
056:
057: private boolean mLoadCache = true;
058:
059: private boolean mSaveCache = true;
060:
061: private boolean mUseCache = true;
062:
063: private String mClassPath = System.getProperty("java.class.path");
064:
065: /** Maximum number of mutations per JVM */
066: private int mMaxExternalMutations = -1;
067:
068: /**
069: * Index of the first mutation to attempt. Mainly useful for
070: * testing when there is a problematic mutation.
071: */
072: private int mFirstMutation = 0;
073:
074: private Set<String> mExcludeMethods = new HashSet<String>();
075:
076: private List<String> mJvmArgs = new ArrayList<String>();
077:
078: // State during run
079:
080: /** The class being tested */
081: private String mClassName;
082:
083: private File mCacheFile;
084:
085: private File mTestSuiteFile;
086:
087: private JavaRunner mRunner = null;
088:
089: private Process mChildProcess = null;
090:
091: private IOThread mIot = null;
092:
093: private IOThread mEot = null;
094:
095: private int mMutationCount;
096:
097: private long mTotalRuntime;
098:
099: /** The variable storing the failed tests - can get pretty big */
100: FailedTestMap mCache = null;
101:
102: public FastRunner() {
103: // Add a shutdown hook so that if this JVM is interrupted, any
104: // child process will be destroyed.
105: Runtime.getRuntime().addShutdownHook(new Thread() {
106: public void run() {
107: Process childProcess = mChildProcess;
108: if (childProcess != null) {
109: if (mVerbose) {
110: System.err
111: .println("Shutting down child process");
112: }
113: childProcess.destroy();
114: }
115: }
116: });
117: // This lets us run more mutation tests in each sub-JVM
118: // without running out of space for classes. Also consider
119: // setting setMaxExternalMutations.
120: mJvmArgs.add("-XX:PermSize=128m");
121: mJvmArgs.add("-cp");
122: mJvmArgs.add(System.getProperty("java.class.path"));
123: }
124:
125: private JavaRunner getJavaRunner() {
126: if (mRunner == null) {
127: mRunner = new JavaRunner(
128: "com.reeltwo.jumble.fast.FastJumbler");
129: mRunner.setJvmArguments(mJvmArgs
130: .toArray(new String[mJvmArgs.size()]));
131: }
132: return mRunner;
133: }
134:
135: /**
136: * Returns the classpath used to load test and source classes.
137: *
138: * @return the classpath string.
139: */
140: public String getClassPath() {
141: return mClassPath;
142: }
143:
144: /**
145: * Sets the classpath used to load test and source classes.
146: *
147: * @param classpath a <code>String</code> value
148: */
149: public void setClassPath(String classpath) {
150: mClassPath = classpath;
151: System.setProperty("java.class.path", mClassPath); // Make classpath available to code doing classpath scanning.
152: }
153:
154: /**
155: * Gets whether verbose mode is set.
156: *
157: * @return true if verbose mode is enabled.
158: */
159: public boolean isVerbose() {
160: return mVerbose;
161: }
162:
163: /**
164: * Sets whether verbose mode is enabled.
165: *
166: * @param newVerbose
167: * true if verbose mode should be enabled.
168: */
169: public void setVerbose(final boolean newVerbose) {
170: mVerbose = newVerbose;
171: }
172:
173: /**
174: * Gets whether inline constants will be mutated.
175: *
176: * @return true if inline constants will be mutated.
177: */
178: public boolean isInlineConstants() {
179: return mInlineConstants;
180: }
181:
182: /**
183: * Sets whether inline constants will be mutated.
184: *
185: * @param argInlineConstants
186: * true if inline constants should be mutated.
187: */
188: public void setInlineConstants(final boolean argInlineConstants) {
189: mInlineConstants = argInlineConstants;
190: }
191:
192: /**
193: * Gets whether return values will be mutated.
194: *
195: * @return true if return values will be mutated.
196: */
197: public boolean isReturnVals() {
198: return mReturnVals;
199: }
200:
201: /**
202: * Sets whether return values will be mutated.
203: *
204: * @param argReturnVals
205: * true return values should be mutated.
206: */
207: public void setReturnVals(final boolean argReturnVals) {
208: mReturnVals = argReturnVals;
209: }
210:
211: /**
212: * Gets whether increments will be mutated.
213: *
214: * @return true if increments will be mutated.
215: */
216: public boolean isIncrements() {
217: return mIncrements;
218: }
219:
220: /**
221: * Sets whether increments will be mutated.
222: *
223: * @param argIncrements
224: * true if increments will be mutated.
225: */
226: public void setIncrements(final boolean argIncrements) {
227: mIncrements = argIncrements;
228: }
229:
230: /**
231: * Gets whether constant pool will be mutated.
232: *
233: * @return true if constant pool will be mutated.
234: */
235: public boolean isCPool() {
236: return mCPool;
237: }
238:
239: /**
240: * Sets whether constant pool will be mutated.
241: *
242: * @param cpool true if constants will be mutated.
243: */
244: public void setCPool(final boolean cpool) {
245: mCPool = cpool;
246: }
247:
248: /**
249: * Sets whether switches will be mutated.
250: *
251: * @param switches true if switches will be mutated.
252: */
253: public void setSwitch(final boolean switches) {
254: mSwitches = switches;
255: }
256:
257: /**
258: * Gets whether switches will be mutated.
259: *
260: * @return true if switches will be mutated.
261: */
262: public boolean isSwitch() {
263: return mSwitches;
264: }
265:
266: /**
267: * Gets whether tests are ordered by their run time.
268: *
269: * @return true if tests are ordered by their run time.
270: */
271: public boolean isOrdered() {
272: return mOrdered;
273: }
274:
275: /**
276: * Sets whether tests are ordered by their run time.
277: *
278: * @param argOrdered true if tests should be ordered by their run time.
279: */
280: public void setOrdered(final boolean argOrdered) {
281: mOrdered = argOrdered;
282: }
283:
284: /**
285: * Gets the value of loadCache
286: *
287: * @return the value of loadCache
288: */
289: public boolean isLoadCache() {
290: return mLoadCache;
291: }
292:
293: /**
294: * Sets the value of loadCache
295: *
296: * @param argLoadCache
297: * Value to assign to loadCache
298: */
299: public void setLoadCache(final boolean argLoadCache) {
300: mLoadCache = argLoadCache;
301: }
302:
303: /**
304: * Gets the value of saveCache
305: *
306: * @return the value of saveCache
307: */
308: public boolean isSaveCache() {
309: return mSaveCache;
310: }
311:
312: /**
313: * Sets the value of saveCache
314: *
315: * @param argSaveCache
316: * Value to assign to saveCache
317: */
318: public void setSaveCache(final boolean argSaveCache) {
319: mSaveCache = argSaveCache;
320: }
321:
322: /**
323: * Gets the value of useCache
324: *
325: * @return the value of useCache
326: */
327: public boolean isUseCache() {
328: return mUseCache;
329: }
330:
331: /**
332: * Sets the value of useCache
333: *
334: * @param argUseCache
335: * Value to assign to useCache
336: */
337: public void setUseCache(final boolean argUseCache) {
338: mUseCache = argUseCache;
339: }
340:
341: /**
342: * Gets the set of excluded method names
343: *
344: * @return the set of excluded method names
345: */
346: public Set<String> getExcludeMethods() {
347: return mExcludeMethods;
348: }
349:
350: /**
351: * A function which computes the timeout for given that the original test took
352: * <CODE>runtime</CODE>
353: *
354: * @param runtime
355: * the original runtime
356: * @return the computed timeout
357: */
358: public static long computeTimeout(long runtime) {
359: return runtime * 10 + 2000;
360: }
361:
362: private void initCache() throws Exception {
363: boolean loaded = false;
364:
365: // Load the cache if it exists and is needed
366: if (mLoadCache) {
367: try {
368: ObjectInputStream ois = new ObjectInputStream(
369: new FileInputStream(CACHE_FILE));
370: mCache = (FailedTestMap) ois.readObject();
371: loaded = true;
372: } catch (IOException e) {
373: loaded = false;
374: }
375: }
376: if (!loaded) {
377: mCache = new FailedTestMap();
378: }
379: }
380:
381: private void updateCache(MutationResult mutation) {
382: if (mutation.isPassed()) {
383: StringTokenizer tokens = new StringTokenizer(mutation
384: .getTestDescription(), ":");
385: String clazzName = tokens.nextToken();
386: assert clazzName.equals(mutation.getClassName());
387: String methodName = tokens.nextToken();
388: int mutPoint = Integer.parseInt(tokens.nextToken());
389: String testName = tokens.nextToken();
390: mCache
391: .addFailure(clazzName, methodName, mutPoint,
392: testName);
393: }
394: }
395:
396: private boolean writeCache(File f) {
397: try {
398: if (f.exists()) {
399: f.delete();
400: }
401: ObjectOutputStream o = new ObjectOutputStream(
402: new FileOutputStream(f));
403: o.writeObject(mCache);
404: o.close();
405: return true;
406: } catch (IOException e) {
407: e.printStackTrace();
408: return false;
409: }
410: }
411:
412: public void addExcludeMethod(String methodName) {
413: mExcludeMethods.add(methodName);
414: }
415:
416: public void addJvmArg(String arg) {
417: if (arg == null) {
418: throw new NullPointerException();
419: }
420: mJvmArgs.add(arg);
421: }
422:
423: public void addSystemProperty(String property) {
424: if (property == null) {
425: throw new NullPointerException();
426: }
427: int pos = property.indexOf('=');
428: if (pos == -1) {
429: throw new IllegalArgumentException(
430: "Malformed property definition, expected name=value");
431: }
432: System.setProperty(property.substring(0, pos), property
433: .substring(pos + 1));
434: mJvmArgs.add("-D" + property);
435: }
436:
437: /** Constructs arguments to the FastJumbler */
438: private String[] createArgs(int currentMutation, int max) {
439: ArrayList<String> args = new ArrayList<String>();
440: args.add("--" + FastJumbler.FLAG_CLASSPATH);
441: args.add(mClassPath);
442:
443: // mutation point
444: args.add("--" + FastJumbler.FLAG_START);
445: args.add(String.valueOf(currentMutation));
446:
447: // class name
448: args.add(mClassName);
449: // test suite filename
450: args.add(mTestSuiteFile.toString());
451:
452: if (mUseCache) {
453: // Write a temp cache
454: if (writeCache(mCacheFile)) {
455: args.add(mCacheFile.toString());
456: }
457: }
458:
459: // exclude methods
460: if (!mExcludeMethods.isEmpty()) {
461: StringBuffer ex = new StringBuffer();
462: Iterator it = mExcludeMethods.iterator();
463: for (int i = 0; i < mExcludeMethods.size(); i++) {
464: if (i == 0) {
465: ex.append(it.next());
466: } else {
467: ex.append("," + it.next());
468: }
469: }
470: args.add("--" + FastJumbler.FLAG_EXCLUDE);
471: args.add(ex.toString());
472: }
473: // inline constants
474: if (mInlineConstants) {
475: args.add("--" + FastJumbler.FLAG_INLINE_CONSTS);
476: }
477: // return values
478: if (mReturnVals) {
479: args.add("--" + FastJumbler.FLAG_RETURN_VALS);
480: }
481: // increments
482: if (mIncrements) {
483: args.add("--" + FastJumbler.FLAG_INCREMENTS);
484: }
485: // constant pool
486: if (mCPool) {
487: args.add("--" + FastJumbler.FLAG_CPOOL);
488: }
489: // switches
490: if (mSwitches) {
491: args.add("--" + FastJumbler.FLAG_SWITCHES);
492: }
493: // verbose
494: if (mVerbose) {
495: args.add("--" + FastJumbler.FLAG_VERBOSE);
496: }
497:
498: if (max >= 0) {
499: args.add("--" + FastJumbler.FLAG_LENGTH);
500: args.add("" + max);
501: }
502: return args.toArray(new String[args.size()]);
503: }
504:
505: private Mutater createMutater(int mutationpoint) {
506: // Get the number of mutation points from the Jumbler
507: final Mutater m = new Mutater(mutationpoint);
508: m.setIgnoredMethods(mExcludeMethods);
509: m.setMutateIncrements(mIncrements);
510: m.setMutateCPool(mCPool);
511: m.setMutateSwitch(mSwitches);
512: m.setMutateInlineConstants(mInlineConstants);
513: m.setMutateReturnValues(mReturnVals);
514: return m;
515: }
516:
517: private int countMutationPoints(MutatingClassLoader loader,
518: String classname) throws ClassNotFoundException {
519: return loader.countMutationPoints(classname);
520: }
521:
522: private boolean debugOutput(String out, String err) {
523: if (err != null) {
524: System.err.println("Child.err->" + err);
525: }
526: if (out != null) {
527: System.err.println("Child.out->" + out);
528: }
529: return true; // So we can be enabled/disabled via assertion mechanism.
530: }
531:
532: private void waitForStart(IOThread iot, IOThread eot)
533: throws InterruptedException {
534: // read the "START" to let us know the JVM has started
535: // we don't want to time this.
536: // FIXME this looks dangerous. What if the test can't even get to the point
537: // of outputting START (e.g. class loading issues)
538: while (true) {
539: String out = iot.getNext();
540: String err = eot.getAvailable();
541: if (mVerbose) {
542: debugOutput(out, err);
543: }
544: if ((out == null) && (err == null)) {
545: Thread.sleep(10);
546: } else if (FastJumbler.SIGNAL_START.equals(out)) {
547: break;
548: } else {
549: throw new RuntimeException(
550: "com.reeltwo.jumble.fast.FastJumbler returned "
551: + ((out != null) ? out : err
552: + " on stderr")
553: + " instead of "
554: + FastJumbler.SIGNAL_START);
555: }
556: }
557: }
558:
559: private void startChildProcess(String[] args) throws IOException,
560: InterruptedException {
561: JavaRunner runner = getJavaRunner();
562: runner.setArguments(args);
563: mChildProcess = runner.start();
564: mIot = new IOThread(mChildProcess.getInputStream());
565: mIot.setDaemon(true);
566: mIot.start();
567: mEot = new IOThread(mChildProcess.getErrorStream());
568: mEot.setDaemon(true);
569: mEot.start();
570: waitForStart(mIot, mEot);
571: }
572:
573: /** Reads a mutation result from the child process */
574: private MutationResult readMutation(int currentMutation,
575: long timeout) throws InterruptedException {
576: long before = System.currentTimeMillis();
577: long after = before;
578: String modification = null;
579: // Run until we have a result or time out
580: while (true) {
581: String out = mIot.getNext();
582: if (mVerbose) {
583: debugOutput(out, mEot.getAvailable());
584: }
585: if (out == null) {
586: if (after - before > timeout) {
587: mChildProcess.destroy();
588: mChildProcess = null;
589: return new MutationResult(MutationResult.TIMEOUT,
590: mClassName, currentMutation, modification);
591: } else {
592: Thread.sleep(50);
593: after = System.currentTimeMillis();
594: }
595: } else {
596: if (out.startsWith(FastJumbler.SIGNAL_MAX_REACHED)) {
597: return null; // Child JVM requested continuing in a new JVM
598: } else if (out.startsWith(FastJumbler.INIT_PREFIX)) {
599: modification = out
600: .substring(FastJumbler.INIT_PREFIX.length());
601: } else {
602: MutationResult m = null;
603: if (out.startsWith(FastJumbler.PASS_PREFIX)) {
604: m = new MutationResult(MutationResult.PASS,
605: mClassName, currentMutation,
606: modification,
607: out.substring(FastJumbler.PASS_PREFIX
608: .length()));
609: } else if (out.startsWith(FastJumbler.FAIL_PREFIX)) {
610: m = new MutationResult(MutationResult.FAIL,
611: mClassName, currentMutation,
612: modification);
613: }
614: if (m != null) {
615: if (mUseCache) {
616: updateCache(m);
617: }
618: return m;
619: }
620: }
621: }
622: }
623: }
624:
625: /**
626: * Runs tests without mutating at all. If all OK, write out testsuitefile for
627: * later use, otherwise return a JumbleResult
628: */
629: private JumbleResult runInitialTests(List<String> testClassNames) {
630:
631: //System.err.println("Using classpath: " + mClassPath);
632: MutatingClassLoader jumbler = new MutatingClassLoader(
633: mClassName, createMutater(-1), mClassPath);
634: ClassLoader oldLoader = Thread.currentThread()
635: .getContextClassLoader();
636: Thread.currentThread().setContextClassLoader(jumbler);
637: try {
638:
639: mMutationCount = countMutationPoints(jumbler, mClassName);
640: if (mMutationCount == -1) {
641: return new InterfaceResult(mClassName);
642: }
643: TimingTestSuite suite = null;
644: try {
645: suite = new TimingTestSuite(jumbler, testClassNames
646: .toArray(new String[testClassNames.size()]));
647: } catch (ClassNotFoundException e) {
648: // test class did not exist
649: return new MissingTestsTestResult(mClassName,
650: testClassNames, mMutationCount);
651: }
652: //System.err.println("Parent. Starting initial run without mutating");
653:
654: JUnitTestResult result = new JUnitTestResult();
655: suite.run(result);
656: boolean successful = result.wasSuccessful();
657: //System.err.println("Parent. Finished");
658:
659: // Now, if the tests failed, can return straight away
660: if (!successful) {
661: if (mVerbose) {
662: System.err.println(result);
663: }
664: return new BrokenTestsTestResult(mClassName,
665: testClassNames, mMutationCount);
666: }
667:
668: // Set the test runtime so we can calculate timeouts when running the
669: // mutated tests
670: mTotalRuntime = suite.getTotalRuntime();
671:
672: // Store the test suite information serialized in a temporary file so
673: // FastJumbler can load it.
674: Object order = suite.getOrder(mOrdered);
675: ObjectOutputStream oos = new ObjectOutputStream(
676: new FileOutputStream(mTestSuiteFile));
677: oos.writeObject(order);
678: oos.close();
679:
680: // Now try the tests again in a separate JVM to detect if there
681: // are problems due to invocation within a separate JVM.
682: mChildProcess = null;
683: mIot = null;
684: mEot = null;
685: startChildProcess(createArgs(-1, 1));
686: MutationResult this Result = readMutation(-1,
687: computeTimeout(mTotalRuntime));
688: if (mChildProcess != null) {
689: mChildProcess = null;
690: }
691: if (this Result == null) {
692: // This is a problem due to unknown reasons
693: System.err
694: .println("WARNING: Child JVM requested restart before completing any mutations!!");
695: } else if (this Result.getStatus() == MutationResult.PASS) {
696: // This is a problem, we expect a run without any mutations to look like a fail (i.e. all tests pass)
697: if (mVerbose) {
698: System.err
699: .println("Problem jumbling: Tests failed when running unmutated in external JVM!");
700: }
701: return new BrokenTestsTestResult(mClassName,
702: testClassNames, mMutationCount);
703: }
704:
705: } catch (RuntimeException e) {
706: throw e;
707: } catch (Exception e) {
708: RuntimeException r = new IllegalStateException(
709: "Problem using reflection to set up run under another classloader");
710: r.initCause(e);
711: throw r;
712: } finally {
713: Thread.currentThread().setContextClassLoader(oldLoader);
714: }
715:
716: return null;
717: }
718:
719: /**
720: * Performs a Jumble run on the specified class with the specified tests.
721: *
722: * @param className
723: * the name of the class to Jumble
724: * @param testClassNames
725: * the names of the associated test classes
726: * @param listener
727: * the listener associated with this Jumble run.
728: * @throws Exception
729: * if something goes wrong
730: * @see JumbleResult
731: * @see JumbleListener
732: */
733: public void runJumble(final String className,
734: final List<String> testClassNames, JumbleListener listener)
735: throws Exception {
736: runJumbleProxy(className, testClassNames, listener);
737: }
738:
739: /**
740: * Goes through class names to find out if they can be resolved. Also checks
741: * that all declared test classes are actually test cases.
742: *
743: * @param out
744: * listener where the error is reported.
745: * @param className
746: * name of the class being jumbled.
747: * @param testClassNames
748: * list of test class names.
749: * @return
750: */
751: private boolean checkClasses(JumbleListener out, String className,
752: List<String> testClassNames) {
753: boolean ok = true;
754:
755: try {
756: MutatingClassLoader jumbler = new MutatingClassLoader(
757: mClassName, createMutater(-1), mClassPath);
758: Class clazz = jumbler.loadClass(className);
759: if (!clazz.isInterface()) {
760: for (int i = 0; i < testClassNames.size(); i++) {
761: String testName = testClassNames.get(i);
762: Class test = null;
763: try {
764: test = jumbler.loadClass(testName);
765: } catch (ClassNotFoundException e) {
766: ; // Do nothing. No test class is handled elswhere
767: }
768: if (test != null && !JumbleUtils.isTestClass(test)) {
769: out.error(testName + " is not a test class.");
770: ok = false;
771: }
772: }
773: }
774: } catch (ClassNotFoundException e) {
775: out.error("Class " + className + " not found.");
776: ok = false;
777: }
778:
779: return ok;
780: }
781:
782: /**
783: * Performs a Jumble run on the specified class with the specified tests.
784: *
785: * @param className
786: * the name of the class to Jumble
787: * @param testClassNames
788: * the names of the associated test classes
789: * @param listener
790: * the listener associated with this Jumble run.
791: * @return the results of the Jumble run
792: * @throws Exception
793: * if something goes wrong
794: * @see JumbleResult
795: * @see JumbleListener
796: */
797: private JumbleResult runJumbleProxy(final String className,
798: final List<String> testClassNames, JumbleListener listener)
799: throws Exception {
800: if (listener == null) {
801: listener = new NullListener();
802: }
803:
804: if (!checkClasses(listener, className, testClassNames)) {
805: return null;
806: }
807:
808: mClassName = className;
809: mCacheFile = File.createTempFile("cache", ".dat");
810: mTestSuiteFile = File.createTempFile("testSuite", ".dat");
811:
812: if (mUseCache) {
813: initCache();
814: }
815:
816: listener.jumbleRunStarted(mClassName, testClassNames);
817:
818: JumbleResult initialResult = runInitialTests(testClassNames);
819: if (initialResult != null) {
820: listener
821: .performedInitialTest(initialResult, mMutationCount);
822: // Jumbling will not happen here
823: listener.jumbleRunEnded();
824: return initialResult;
825: }
826:
827: // compute the timeout
828: long timeout = computeTimeout(mTotalRuntime);
829:
830: listener.performedInitialTest(new InitialOKJumbleResult(
831: className, testClassNames, timeout), mMutationCount);
832:
833: mChildProcess = null;
834: mIot = null;
835: mEot = null;
836:
837: final MutationResult[] allMutations = new MutationResult[mMutationCount];
838: int count = 0;
839: final int max = getMaxExternalMutations();
840: for (int currentMutation = getFirstMutation(); currentMutation < mMutationCount; currentMutation++) {
841: if (mChildProcess == null) {
842: startChildProcess(createArgs(currentMutation, max));
843: count = 0;
844: }
845: MutationResult this Result = readMutation(currentMutation,
846: timeout);
847: if (this Result == null) {
848: mChildProcess = null;
849: if (count == 0) {
850: System.err
851: .println("WARNING: Child JVM requested restart before completing any mutations!!");
852: } else {
853: // Restart current mutation in a new JVM
854: currentMutation--;
855: }
856: } else {
857: allMutations[currentMutation] = this Result;
858: count++;
859: if (max >= 0 && count >= max) {
860: mChildProcess = null;
861: }
862: listener.finishedMutation(this Result);
863: }
864: }
865: if (mChildProcess != null) {
866: mChildProcess = null;
867: }
868:
869: JumbleResult ret = new NormalJumbleResult(className,
870: testClassNames, allMutations, timeout);
871:
872: // finally, delete the test suite file
873: if (mTestSuiteFile.exists() && !mTestSuiteFile.delete()) {
874: System.err
875: .println("Error: could not delete temporary file");
876: }
877: // Also delete the temporary cache and save the cache if needed
878: if (mUseCache) {
879: if (mCacheFile.exists() && !mCacheFile.delete()) {
880: System.err
881: .println("Error: could not delete temporary cache file "
882: + mCacheFile);
883: }
884: if (mSaveCache) {
885: writeCache(CACHE_FILE);
886: }
887: }
888: listener.jumbleRunEnded();
889: mCache = null;
890: return ret;
891: }
892:
893: /**
894: * Gets the maximum number of mutations performed by the external JVM.
895: *
896: * @return the maximum number of external mutations. A negative value implies
897: * no maximum.
898: */
899: public int getMaxExternalMutations() {
900: return mMaxExternalMutations;
901: }
902:
903: /**
904: * Sets the maximum number of mutations performed by the external JVM.
905: *
906: * @param maxExternalMutations
907: * the maximum number of external mutations. A negative value implies
908: * no maximum.
909: */
910: public void setMaxExternalMutations(int maxExternalMutations) {
911: mMaxExternalMutations = maxExternalMutations;
912: }
913:
914: /**
915: * Get the index of the first mutation to attempt.
916: *
917: * @return the first mutation index.
918: */
919: public int getFirstMutation() {
920: return mFirstMutation;
921: }
922:
923: /**
924: * Set the index of the first mutation to attempt.
925: *
926: * @param newFirstMutation the new FirstMutation value.
927: */
928: public void setFirstMutation(final int newFirstMutation) {
929: mFirstMutation = newFirstMutation;
930: }
931: }
|