Stop running tests from inner classes in JUnit3RunnerWithInners (KT-23234)

Running tests as part of parent class produced test names on
TeamCity that were different from test names obtained when inner classes
were executed first. And it looks like test order execution in Gradle
depends on file walker and file system.

 #KT-23234 Fixed
This commit is contained in:
Nikolay Krasko
2018-03-27 20:12:02 +03:00
parent bf8c7075bc
commit 8d4fa8f3e8
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.test; package org.jetbrains.kotlin.test;
import junit.framework.Test; import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestResult; import junit.framework.TestResult;
import junit.framework.TestSuite; import junit.framework.TestSuite;
import org.junit.internal.MethodSorter; import org.junit.internal.MethodSorter;
@@ -29,25 +30,27 @@ import org.junit.runner.notification.RunNotifier;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.*; import java.util.ArrayList;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
* This runner runs class with all inners test classes, but monitors situation when * This runner executes own tests and bypass Gradle filtering for inner classes execution.
* inner classes are already processed. * Together with the hack in `tasks.kt - fun Project.projectTest()` that adds inner class files to processing,
* this allows running tests in inner classes when parent class pattern is used.
*
* This class also suppress "No tests found in class" warning when inner test classes are present.
*
* Previous implementation that was building test suite with test cases for inner classes automatically, produced unstable test names
* on TeamCity. Names were different when inner test is executed as top class or part of the other parent class.
*/ */
public class JUnit3RunnerWithInners extends Runner implements Filterable, Sortable { public class JUnit3RunnerWithInners extends Runner implements Filterable, Sortable {
private static final Set<Class> processedClasses = new HashSet<>(); private final JUnit38ClassRunner delegateRunner;
private JUnit38ClassRunner delegateRunner;
private final Class<?> klass;
private boolean isFakeTest = false;
private static class FakeEmptyClassTest implements Test, Filterable { private static class FakeEmptyClassTest implements Test, Filterable {
private final String klassName; private final String className;
FakeEmptyClassTest(Class<?> klass) { FakeEmptyClassTest(Class<?> klass) {
this.klassName = klass.getName(); this.className = klass.getName();
} }
@Override @Override
@@ -63,7 +66,7 @@ public class JUnit3RunnerWithInners extends Runner implements Filterable, Sortab
@Override @Override
public String toString() { public String toString() {
return "Empty class with inners for " + klassName; return "Empty class with inners for " + className;
} }
@Override @Override
@@ -73,44 +76,26 @@ public class JUnit3RunnerWithInners extends Runner implements Filterable, Sortab
} }
public JUnit3RunnerWithInners(Class<?> klass) { public JUnit3RunnerWithInners(Class<?> klass) {
this.klass = klass; super();
}
@Override String className = klass.getName();
public void run(RunNotifier notifier) {
initialize();
delegateRunner.run(notifier);
}
@Override Test test = new TestSuite(klass.asSubclass(TestCase.class));
public Description getDescription() { if (!hasOwnTestMethods(klass)) {
initialize(); for (Class<?> declaredClass : klass.getDeclaredClasses()) {
return isFakeTest ? Description.EMPTY : delegateRunner.getDescription(); if (TestCase.class.isAssignableFrom(declaredClass)) {
} test = new FakeEmptyClassTest(klass);
break;
}
}
}
@Override delegateRunner = new JUnit38ClassRunner(test) {
public void filter(Filter filter) throws NoTestsRemainException {
initialize();
delegateRunner.filter(filter);
}
@Override
public void sort(Sorter sorter) {
initialize();
delegateRunner.sort(sorter);
}
private void initialize() {
if (delegateRunner != null) return;
Test collectedTests = getCollectedTests();
delegateRunner = new JUnit38ClassRunner(collectedTests) {
@Override public void filter(Filter filter) throws NoTestsRemainException { @Override public void filter(Filter filter) throws NoTestsRemainException {
String classDescription = collectedTests.toString();
String classPatternString = getGradleClassPattern(filter); String classPatternString = getGradleClassPattern(filter);
if (classPatternString != null) { if (classPatternString != null) {
if (Pattern.compile(classPatternString + "\\$.*").matcher(classDescription).matches()) { if (Pattern.compile(classPatternString + "\\$.*").matcher(className).matches()) {
return; return;
} }
} }
@@ -120,64 +105,29 @@ public class JUnit3RunnerWithInners extends Runner implements Filterable, Sortab
}; };
} }
private Test getCollectedTests() { @Override
if (processedClasses.contains(klass)) { public void run(RunNotifier notifier) {
isFakeTest = true; delegateRunner.run(notifier);
return new FakeEmptyClassTest(klass);
}
Set<Class> classes = collectDeclaredClasses(klass, true);
Set<Class> unprocessedClasses = unprocessedClasses(classes);
processedClasses.addAll(unprocessedClasses);
return createTreeTestSuite(klass, unprocessedClasses);
} }
private static Test createTreeTestSuite(Class root, Set<Class> classes) { @Override
Map<Class, TestSuite> classSuites = new HashMap<>(); public Description getDescription() {
return delegateRunner.getDescription();
for (Class aClass : classes) {
classSuites.put(aClass, hasTestMethods(aClass) ? new TestSuite(aClass) : new TestSuite(aClass.getCanonicalName()));
}
for (Class aClass : classes) {
if (aClass.getEnclosingClass() != null && classes.contains(aClass.getEnclosingClass())) {
classSuites.get(aClass.getEnclosingClass()).addTest(classSuites.get(aClass));
}
}
return classSuites.get(root);
} }
private static Set<Class> unprocessedClasses(Collection<Class> classes) { @Override
Set<Class> result = new LinkedHashSet<>(); public void filter(Filter filter) throws NoTestsRemainException {
for (Class aClass : classes) { delegateRunner.filter(filter);
if (!processedClasses.contains(aClass)) {
result.add(aClass);
}
}
return result;
} }
private static Set<Class> collectDeclaredClasses(Class klass, boolean withItself) { @Override
Set<Class> result = new HashSet<>(); public void sort(Sorter sorter) {
if (withItself) { delegateRunner.sort(sorter);
result.add(klass);
}
for (Class aClass : klass.getDeclaredClasses()) {
result.addAll(collectDeclaredClasses(aClass, true));
}
return result;
} }
private static boolean hasTestMethods(Class klass) { private static boolean hasOwnTestMethods(Class klass) {
for (Class currentClass = klass; Test.class.isAssignableFrom(currentClass); currentClass = currentClass.getSuperclass()) { for (Method each : MethodSorter.getDeclaredMethods(klass)) {
for (Method each : MethodSorter.getDeclaredMethods(currentClass)) { if (isTestMethod(each)) return true;
if (isTestMethod(each)) return true;
}
} }
return false; return false;
@@ -229,7 +179,8 @@ public class JUnit3RunnerWithInners extends Runner implements Filterable, Sortab
} }
return patternStr; return patternStr;
} catch (ReflectiveOperationException e) { }
catch (ReflectiveOperationException e) {
throw new IllegalStateException(e); throw new IllegalStateException(e);
} }
} }