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:
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user