From 6a71f5bd2c0c3a561a48ceee8f8e344f9da51fb5 Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Wed, 28 Mar 2018 17:29:22 +0300 Subject: [PATCH] Restore JUnit3RunnerWithInners version for JPS and delegate pill to it --- .../kotlin/test/JUnit3RunnerWithInners.java | 157 ++++-------------- .../test/JUnit3RunnerWithInnersForGradle.java | 140 ++++++++++++++++ .../test/JUnit3RunnerWithInnersForJPS.java | 150 +++++++++++++++++ 3 files changed, 326 insertions(+), 121 deletions(-) create mode 100644 compiler/tests-common/tests/org/jetbrains/kotlin/test/JUnit3RunnerWithInnersForGradle.java create mode 100644 compiler/tests-common/tests/org/jetbrains/kotlin/test/JUnit3RunnerWithInnersForJPS.java diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/test/JUnit3RunnerWithInners.java b/compiler/tests-common/tests/org/jetbrains/kotlin/test/JUnit3RunnerWithInners.java index 136eca16c5e..928d40ff367 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/test/JUnit3RunnerWithInners.java +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/test/JUnit3RunnerWithInners.java @@ -17,36 +17,54 @@ package org.jetbrains.kotlin.test; import junit.framework.Test; -import junit.framework.TestCase; import junit.framework.TestResult; -import junit.framework.TestSuite; -import org.junit.internal.MethodSorter; -import org.junit.internal.runners.JUnit38ClassRunner; import org.junit.runner.Description; import org.junit.runner.Runner; import org.junit.runner.manipulation.*; import org.junit.runner.notification.RunNotifier; -import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.regex.Pattern; /** - * This runner executes own tests and bypass Gradle filtering for inner classes execution. - * 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. + * Runner that is responsible for executing tests including test methods from all inner classes. + * Works differently for Gradle and JPS. Default is Gradle for now. */ public class JUnit3RunnerWithInners extends Runner implements Filterable, Sortable { - private final JUnit38ClassRunner delegateRunner; + private final Runner delegateRunner; - private static class FakeEmptyClassTest implements Test, Filterable { + public JUnit3RunnerWithInners(Class klass) { + super(); + + if ("true".equals(System.getProperty("use.pill"))) { + delegateRunner = new JUnit3RunnerWithInnersForJPS(klass); + } + else { + delegateRunner = new JUnit3RunnerWithInnersForGradle(klass); + } + } + + @Override + public void run(RunNotifier notifier) { + delegateRunner.run(notifier); + } + + @Override + public Description getDescription() { + return delegateRunner.getDescription(); + } + + @Override + public void filter(Filter filter) throws NoTestsRemainException { + ((Filterable)delegateRunner).filter(filter); + } + + @Override + public void sort(Sorter sorter) { + ((Sortable)delegateRunner).sort(sorter); + } + + static class FakeEmptyClassTest implements Test, Filterable { private final String className; FakeEmptyClassTest(Class klass) { @@ -75,113 +93,10 @@ public class JUnit3RunnerWithInners extends Runner implements Filterable, Sortab } } - public JUnit3RunnerWithInners(Class klass) { - super(); - - String className = klass.getName(); - - Test test = new TestSuite(klass.asSubclass(TestCase.class)); - if (!hasOwnTestMethods(klass)) { - for (Class declaredClass : klass.getDeclaredClasses()) { - if (TestCase.class.isAssignableFrom(declaredClass)) { - test = new FakeEmptyClassTest(klass); - break; - } - } - } - - delegateRunner = new JUnit38ClassRunner(test) { - @Override public void filter(Filter filter) throws NoTestsRemainException { - String classPatternString = getGradleClassPattern(filter); - - if (classPatternString != null) { - if (Pattern.compile(classPatternString + "\\$.*").matcher(className).matches()) { - return; - } - } - - super.filter(filter); - } - }; - } - - @Override - public void run(RunNotifier notifier) { - delegateRunner.run(notifier); - } - - @Override - public Description getDescription() { - return delegateRunner.getDescription(); - } - - @Override - public void filter(Filter filter) throws NoTestsRemainException { - delegateRunner.filter(filter); - } - - @Override - public void sort(Sorter sorter) { - delegateRunner.sort(sorter); - } - - private static boolean hasOwnTestMethods(Class klass) { - for (Method each : MethodSorter.getDeclaredMethods(klass)) { - if (isTestMethod(each)) return true; - } - - return false; - } - - private static boolean isTestMethod(Method method) { + static boolean isTestMethod(Method method) { return method.getParameterTypes().length == 0 && method.getName().startsWith("test") && method.getReturnType().equals(Void.TYPE) && Modifier.isPublic(method.getModifiers()); } - - private static String getGradleClassPattern(Filter filter) { - try { - Class filterClass = filter.getClass(); - if (!"org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor$MethodNameFilter".equals(filterClass.getName())) { - return null; - } - - Field matcherField = filterClass.getDeclaredField("matcher"); - matcherField.setAccessible(true); - Object testSelectionMatcher = matcherField.get(filter); - Class testSelectionMatcherClass = testSelectionMatcher.getClass(); - if (!"org.gradle.api.internal.tasks.testing.filter.TestSelectionMatcher".equals(testSelectionMatcherClass.getName())) { - return null; - } - - Field includePatternsField; - try { - includePatternsField = testSelectionMatcherClass.getDeclaredField("includePatterns"); - } - catch (NoSuchFieldException exception) { - includePatternsField = testSelectionMatcherClass.getDeclaredField("buildScriptIncludePatterns"); - } - - includePatternsField.setAccessible(true); - @SuppressWarnings("unchecked") ArrayList includePatterns = - (ArrayList) includePatternsField.get(testSelectionMatcher); - - if (includePatterns.size() != 1) { - return null; - } - - Pattern pattern = includePatterns.get(0); - String patternStr = pattern.pattern(); - - if (patternStr.endsWith("*")) { - return null; - } - - return patternStr; - } - catch (ReflectiveOperationException e) { - throw new IllegalStateException(e); - } - } } \ No newline at end of file diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/test/JUnit3RunnerWithInnersForGradle.java b/compiler/tests-common/tests/org/jetbrains/kotlin/test/JUnit3RunnerWithInnersForGradle.java new file mode 100644 index 00000000000..1d84c64b4c5 --- /dev/null +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/test/JUnit3RunnerWithInnersForGradle.java @@ -0,0 +1,140 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.test; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; +import org.junit.internal.MethodSorter; +import org.junit.internal.runners.JUnit38ClassRunner; +import org.junit.runner.Description; +import org.junit.runner.Runner; +import org.junit.runner.manipulation.*; +import org.junit.runner.notification.RunNotifier; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.regex.Pattern; + +import static org.jetbrains.kotlin.test.JUnit3RunnerWithInners.isTestMethod; + +/** + * This runner executes own tests and bypass Gradle filtering for inner classes execution. + * 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. + */ +class JUnit3RunnerWithInnersForGradle extends Runner implements Filterable, Sortable { + private final JUnit38ClassRunner delegateRunner; + + public JUnit3RunnerWithInnersForGradle(Class klass) { + super(); + + String className = klass.getName(); + + Test test = new TestSuite(klass.asSubclass(TestCase.class)); + if (!hasOwnTestMethods(klass)) { + for (Class declaredClass : klass.getDeclaredClasses()) { + if (TestCase.class.isAssignableFrom(declaredClass)) { + test = new JUnit3RunnerWithInners.FakeEmptyClassTest(klass); + break; + } + } + } + + delegateRunner = new JUnit38ClassRunner(test) { + @Override public void filter(Filter filter) throws NoTestsRemainException { + String classPatternString = getGradleClassPattern(filter); + + if (classPatternString != null) { + if (Pattern.compile(classPatternString + "\\$.*").matcher(className).matches()) { + return; + } + } + + super.filter(filter); + } + }; + } + + @Override + public void run(RunNotifier notifier) { + delegateRunner.run(notifier); + } + + @Override + public Description getDescription() { + return delegateRunner.getDescription(); + } + + @Override + public void filter(Filter filter) throws NoTestsRemainException { + delegateRunner.filter(filter); + } + + @Override + public void sort(Sorter sorter) { + delegateRunner.sort(sorter); + } + + private static boolean hasOwnTestMethods(Class klass) { + for (Method each : MethodSorter.getDeclaredMethods(klass)) { + if (isTestMethod(each)) return true; + } + + return false; + } + + private static String getGradleClassPattern(Filter filter) { + try { + Class filterClass = filter.getClass(); + if (!"org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor$MethodNameFilter".equals(filterClass.getName())) { + return null; + } + + Field matcherField = filterClass.getDeclaredField("matcher"); + matcherField.setAccessible(true); + Object testSelectionMatcher = matcherField.get(filter); + Class testSelectionMatcherClass = testSelectionMatcher.getClass(); + if (!"org.gradle.api.internal.tasks.testing.filter.TestSelectionMatcher".equals(testSelectionMatcherClass.getName())) { + return null; + } + + Field includePatternsField; + try { + includePatternsField = testSelectionMatcherClass.getDeclaredField("includePatterns"); + } + catch (NoSuchFieldException exception) { + includePatternsField = testSelectionMatcherClass.getDeclaredField("buildScriptIncludePatterns"); + } + + includePatternsField.setAccessible(true); + @SuppressWarnings("unchecked") ArrayList includePatterns = + (ArrayList) includePatternsField.get(testSelectionMatcher); + + if (includePatterns.size() != 1) { + return null; + } + + Pattern pattern = includePatterns.get(0); + String patternStr = pattern.pattern(); + + if (patternStr.endsWith("*")) { + return null; + } + + return patternStr; + } + catch (ReflectiveOperationException e) { + throw new IllegalStateException(e); + } + } +} \ No newline at end of file diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/test/JUnit3RunnerWithInnersForJPS.java b/compiler/tests-common/tests/org/jetbrains/kotlin/test/JUnit3RunnerWithInnersForJPS.java new file mode 100644 index 00000000000..7055edb28ef --- /dev/null +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/test/JUnit3RunnerWithInnersForJPS.java @@ -0,0 +1,150 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * 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.jetbrains.kotlin.test; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; +import org.junit.internal.MethodSorter; +import org.junit.internal.runners.JUnit38ClassRunner; +import org.junit.runner.Description; +import org.junit.runner.Runner; +import org.junit.runner.manipulation.*; +import org.junit.runner.notification.RunNotifier; + +import java.lang.reflect.Method; +import java.util.*; + +import static org.jetbrains.kotlin.test.JUnit3RunnerWithInners.isTestMethod; + +/** + * This runner runs class with all inners test classes, but monitors situation when those classes are planned to be executed + * with IDEA package test runner. + */ +public class JUnit3RunnerWithInnersForJPS extends Runner implements Filterable, Sortable { + private static final Set requestedRunners = new HashSet<>(); + + private JUnit38ClassRunner delegateRunner; + private final Class klass; + private boolean isFakeTest = false; + + public JUnit3RunnerWithInnersForJPS(Class klass) { + this.klass = klass; + requestedRunners.add(klass); + } + + @Override + public void run(RunNotifier notifier) { + initialize(); + delegateRunner.run(notifier); + } + + @Override + public Description getDescription() { + initialize(); + return isFakeTest ? Description.EMPTY : delegateRunner.getDescription(); + } + + @Override + public void filter(Filter filter) throws NoTestsRemainException { + delegateRunner = new JUnit38ClassRunner(klass); + delegateRunner.filter(filter); + } + + @Override + public void sort(Sorter sorter) { + initialize(); + delegateRunner.sort(sorter); + } + + protected void initialize() { + if (delegateRunner != null) return; + delegateRunner = new JUnit38ClassRunner(getCollectedTests()); + } + + private Test getCollectedTests() { + List innerClasses = collectDeclaredClasses(klass, false); + Set unprocessedInnerClasses = unprocessedClasses(innerClasses); + + if (unprocessedInnerClasses.isEmpty()) { + if (!innerClasses.isEmpty() && !hasTestMethods(klass)) { + isFakeTest = true; + return new JUnit3RunnerWithInners.FakeEmptyClassTest(klass); + } + else { + return new TestSuite(klass.asSubclass(TestCase.class)); + } + } + else if (unprocessedInnerClasses.size() == innerClasses.size()) { + return createTreeTestSuite(klass); + } + else { + return new TestSuite(klass.asSubclass(TestCase.class)); + } + } + + private static Test createTreeTestSuite(Class root) { + Set classes = new LinkedHashSet<>(collectDeclaredClasses(root, true)); + Map classSuites = new HashMap<>(); + + 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 unprocessedClasses(Collection classes) { + Set result = new LinkedHashSet<>(); + for (Class aClass : classes) { + if (!requestedRunners.contains(aClass)) { + result.add(aClass); + } + } + + return result; + } + + private static List collectDeclaredClasses(Class klass, boolean withItself) { + List result = new ArrayList<>(); + if (withItself) { + result.add(klass); + } + + for (Class aClass : klass.getDeclaredClasses()) { + result.addAll(collectDeclaredClasses(aClass, true)); + } + + return result; + } + + private static boolean hasTestMethods(Class klass) { + for (Class currentClass = klass; Test.class.isAssignableFrom(currentClass); currentClass = currentClass.getSuperclass()) { + for (Method each : MethodSorter.getDeclaredMethods(currentClass)) { + if (isTestMethod(each)) return true; + } + } + + return false; + } +} \ No newline at end of file