/* * Copyright (c) 2006-2011 Rogério Liesenfeld * This file is subject to the terms of the MIT license (see LICENSE.txt). */ package mockit.integration.junit4; import java.lang.reflect.*; import java.lang.annotation.*; import java.util.*; import java.io.*; import org.junit.internal.runners.*; import org.junit.runner.Description; import org.junit.runner.notification.*; import org.junit.runners.*; import org.junit.runners.model.*; import org.junit.*; import mockit.*; import mockit.incremental.*; import mockit.internal.util.*; /** * JUnit 4 test runner which ignores tests not affected by current local changes in production code. * <p/> * This allows incremental execution of tests according to the changes made to production code, * instead of running the full suite of tests covering such code every time. */ @MockClass(realClass = ParentRunner.class, instantiation = Instantiation.PerMockSetup) public final class IncrementalJUnit4Runner { private static final Method shouldRunMethod; static { try { shouldRunMethod = ParentRunner.class.getDeclaredMethod("shouldRun", Object.class); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } private final Properties coverageMap; private final Map<Method, Boolean> testMethods; private RunNotifier runNotifier; private Method testMethod; public ParentRunner<?> it; public IncrementalJUnit4Runner() { File testRunFile = new File("testRun.properties"); if (testRunFile.exists() && !testRunFile.canWrite()) { throw new IllegalStateException(); } coverageMap = new Properties(); testMethods = new HashMap<Method, Boolean>(); } @Mock(reentrant = true) public void run(RunNotifier notifier) { runNotifier = notifier; CoverageInfoFile coverageFile = new CoverageInfoFile(coverageMap); it.run(notifier); coverageFile.saveToFile(); } @Mock(reentrant = true) public boolean shouldRun(Object m) { testMethod = null; if (!coverageMap.isEmpty()) { if (m instanceof JUnit38ClassRunner) { boolean noTestsToRun = verifyTestMethodsInJUnit38TestClassThatShouldRun((JUnit38ClassRunner) m); if (noTestsToRun) { return false; } } else if (m instanceof FrameworkMethod) { testMethod = ((FrameworkMethod) m).getMethod(); Boolean shouldRun = shouldRunTestInCurrentTestRun(Test.class, testMethod); if (shouldRun != null) { return shouldRun; } } } Boolean shouldRun = Utilities.invoke(it, shouldRunMethod, m); if (testMethod != null) { testMethods.put(testMethod, shouldRun); } return shouldRun; } private boolean verifyTestMethodsInJUnit38TestClassThatShouldRun(JUnit38ClassRunner runner) { Description testClassDescription = runner.getDescription(); Class<?> testClass = testClassDescription.getTestClass(); Iterator<Description> itr = testClassDescription.getChildren().iterator(); while (itr.hasNext()) { Description testDescription = itr.next(); String testMethodName = testDescription.getMethodName(); testMethod = Utilities.findPublicVoidMethod(testClass, testMethodName); Boolean shouldRun = shouldRunTestInCurrentTestRun(null, testMethod); if (shouldRun != null && !shouldRun) { itr.remove(); } } return testClassDescription.getChildren().isEmpty(); } private Boolean shouldRunTestInCurrentTestRun(Class<? extends Annotation> testAnnotation, Method testMethod) { Boolean shouldRun = testMethods.get(testMethod); if (shouldRun != null) { return shouldRun; } if (isTestNotApplicableInCurrentTestRun(testAnnotation, testMethod)) { reportTestAsNotApplicableInCurrentTestRun(testMethod); testMethods.put(testMethod, false); return false; } return null; } private boolean isTestNotApplicableInCurrentTestRun(Class<? extends Annotation> testAnnotation, Method testMethod) { return (testAnnotation == null || testMethod.getAnnotation(testAnnotation) != null) && new TestFilter(coverageMap).shouldIgnoreTestInCurrentTestRun(testMethod); } private static final class TestNotApplicable extends RuntimeException { private TestNotApplicable() { super("unaffected by changes since last test run"); } @Override public void printStackTrace(PrintWriter s) {} } private static final Throwable NOT_APPLICABLE = new TestNotApplicable(); private void reportTestAsNotApplicableInCurrentTestRun(Method method) { Class<?> testClass = method.getDeclaringClass(); Description testDescription = Description.createTestDescription(testClass, method.getName()); runNotifier.fireTestAssumptionFailed(new Failure(testDescription, NOT_APPLICABLE)); } }