/*
* Copyright 2010 Henry Coles
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the License.
*/
package org.pitest.junit;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.internal.runners.ErrorReportingRunner;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.manipulation.Filter;
import org.junit.runner.manipulation.Filterable;
import org.junit.runners.Parameterized;
import org.pitest.functional.F;
import org.pitest.functional.FCollection;
import org.pitest.functional.Option;
import org.pitest.junit.adapter.AdaptedJUnitTestUnit;
import org.pitest.reflection.IsAnnotatedWith;
import org.pitest.reflection.Reflection;
import org.pitest.testapi.TestUnit;
import org.pitest.testapi.TestUnitFinder;
import org.pitest.util.IsolationUtils;
public class JUnitCustomRunnerTestUnitFinder implements TestUnitFinder {
@SuppressWarnings("rawtypes")
private static final Option<Class> CLASS_RULE = findClassRuleClass();
@Override
public List<TestUnit> findTestUnits(final Class<?> clazz) {
final Runner runner = AdaptedJUnitTestUnit.createRunner(clazz);
if (isNotARunnableTest(runner, clazz.getName())) {
return Collections.emptyList();
}
if (Filterable.class.isAssignableFrom(runner.getClass())
&& !shouldTreatAsOneUnit(clazz, runner)) {
return splitIntoFilteredUnits(runner.getDescription());
} else {
return Collections.<TestUnit> singletonList(new AdaptedJUnitTestUnit(
clazz, Option.<Filter> none()));
}
}
private boolean isNotARunnableTest(final Runner runner,
final String className) {
try {
return (runner == null)
|| runner.getClass().isAssignableFrom(ErrorReportingRunner.class)
|| isParameterizedTest(runner)
|| isAJUnitThreeErrorOrWarning(runner)
|| isJUnitThreeSuiteMethodNotForOwnClass(runner, className);
} catch (RuntimeException ex) {
// some runners (looking at you spock) can throw a runtime exception
// when the getDescription method is called
return true;
}
}
private boolean isAJUnitThreeErrorOrWarning(final Runner runner) {
return !runner.getDescription().getChildren().isEmpty()
&& runner.getDescription().getChildren().get(0).getClassName()
.startsWith("junit.framework.TestSuite");
}
private boolean shouldTreatAsOneUnit(final Class<?> clazz, final Runner runner) {
final Set<Method> methods = Reflection.allMethods(clazz);
return runnerCannotBeSplit(runner)
|| hasAnnotation(methods, BeforeClass.class)
|| hasAnnotation(methods, AfterClass.class)
|| hasClassRuleAnnotations(clazz, methods);
}
@SuppressWarnings("unchecked")
private boolean hasClassRuleAnnotations(final Class<?> clazz,
final Set<Method> methods) {
if (CLASS_RULE.hasNone()) {
return false;
}
return hasAnnotation(methods, CLASS_RULE.value())
|| hasAnnotation(Reflection.publicFields(clazz), CLASS_RULE.value());
}
private boolean hasAnnotation(final Set<? extends AccessibleObject> methods,
final Class<? extends Annotation> annotation) {
return FCollection.contains(methods, IsAnnotatedWith.instance(annotation));
}
private boolean isParameterizedTest(final Runner runner) {
return Parameterized.class.isAssignableFrom(runner.getClass());
}
private boolean runnerCannotBeSplit(final Runner runner) {
final String runnerName = runner.getClass().getName();
return runnerName.equals("junitparams.JUnitParamsRunner")
|| runnerName.startsWith("org.spockframework.runtime.Sputnik")
|| runnerName.startsWith("com.insightfullogic.lambdabehave")
|| runnerName.startsWith("com.googlecode.yatspec")
|| runnerName.startsWith("com.google.gwtmockito.GwtMockitoTestRunner");
}
private boolean isJUnitThreeSuiteMethodNotForOwnClass(final Runner runner,
final String className) {
// use strings in case this hack blows up due to internal junit change
return runner.getClass().getName()
.equals("org.junit.internal.runners.SuiteMethod")
&& !runner.getDescription().getClassName().equals(className);
}
private List<TestUnit> splitIntoFilteredUnits(final Description description) {
return FCollection.filter(description.getChildren(), isTest()).map(
descriptionToTestUnit());
}
private F<Description, TestUnit> descriptionToTestUnit() {
return new F<Description, TestUnit>() {
@Override
public TestUnit apply(final Description a) {
return descriptionToTest(a);
}
};
}
private F<Description, Boolean> isTest() {
return new F<Description, Boolean>() {
@Override
public Boolean apply(final Description a) {
return a.isTest();
}
};
}
private TestUnit descriptionToTest(final Description description) {
Class<?> clazz = description.getTestClass();
if (clazz == null) {
clazz = IsolationUtils.convertForClassLoader(
IsolationUtils.getContextClassLoader(), description.getClassName());
}
return new AdaptedJUnitTestUnit(clazz,
Option.some(createFilterFor(description)));
}
private Filter createFilterFor(final Description description) {
return new DescriptionFilter(description.toString());
}
@SuppressWarnings("rawtypes")
private static Option<Class> findClassRuleClass() {
try {
return Option.<Class> some(Class.forName("org.junit.ClassRule"));
} catch (final ClassNotFoundException ex) {
return Option.none();
}
}
}