Skip to content

Commit 2dc55df

Browse files
committed
Discover children of ignored Vintage test classes
Prior to this commit, the Vintage test engine only returned a childless TestDescriptor for test classes annotated with @ignore. However, build tools like Gradle need to show an accurate number of tests, i.e. they want to access the test methods of an ignored test class. The Jupiter engine already discovers skipped containers, e.g. test classes annotated with @disabled, including their children and descendants. The Vintage engine now adopts this approach and returns a full subtree of TestDescriptors for classes annotated with @ignore. During execution, it will only report the TestDescriptor of the test class as skipped which is consistent with how the Jupiter engine reports skipped containers. Fixes #1277.
1 parent f0236d0 commit 2dc55df

File tree

13 files changed

+295
-47
lines changed

13 files changed

+295
-47
lines changed

documentation/src/docs/asciidoc/release-notes/release-notes-5.1.0-RC1.adoc

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,13 @@ on GitHub.
7272

7373
==== New Features and Improvements
7474

75-
* ❓
75+
* Prior to this release, the Vintage test engine only returned a childless
76+
`TestDescriptor` for test classes annotated with `@Ignore`. However, build tools like
77+
Gradle need to show an accurate number of tests, i.e. they want to access and count the
78+
test methods of a test class regardless whether it's ignored.
79+
The Jupiter engine already discovers skipped containers, e.g. test classes annotated
80+
with `@Disabled`, including their children and descendants. The Vintage engine now
81+
adopts this approach and returns a full subtree of `TestDescriptors` for classes
82+
annotated with `@Ignore`. During execution, it will only report the `TestDescriptor`
83+
of the test class as skipped which is consistent with how the Jupiter engine reports
84+
skipped containers.

junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/DefensiveAllDefaultPossibilitiesBuilder.java

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,17 @@
1313
import java.lang.reflect.Method;
1414
import java.util.function.Predicate;
1515

16+
import org.junit.Ignore;
1617
import org.junit.internal.builders.AllDefaultPossibilitiesBuilder;
1718
import org.junit.internal.builders.AnnotatedBuilder;
19+
import org.junit.internal.builders.IgnoredBuilder;
20+
import org.junit.internal.builders.IgnoredClassRunner;
1821
import org.junit.internal.builders.JUnit4Builder;
1922
import org.junit.platform.commons.logging.Logger;
2023
import org.junit.platform.commons.logging.LoggerFactory;
2124
import org.junit.platform.commons.util.ReflectionUtils;
2225
import org.junit.runner.Runner;
26+
import org.junit.runner.manipulation.Filterable;
2327
import org.junit.runners.model.RunnerBuilder;
2428

2529
/**
@@ -30,18 +34,48 @@
3034
* @since 4.12
3135
* @see DefensiveAnnotatedBuilder
3236
* @see DefensiveJUnit4Builder
37+
* @see IgnoredClassRunner
3338
*/
3439
class DefensiveAllDefaultPossibilitiesBuilder extends AllDefaultPossibilitiesBuilder {
3540

3641
private static final Logger logger = LoggerFactory.getLogger(DefensiveAllDefaultPossibilitiesBuilder.class);
3742

3843
private final AnnotatedBuilder annotatedBuilder;
39-
private final DefensiveJUnit4Builder defensiveJUnit4Builder;
44+
private final JUnit4Builder junit4Builder;
45+
private final IgnoredBuilder ignoredBuilder;
4046

4147
DefensiveAllDefaultPossibilitiesBuilder() {
4248
super(true);
4349
annotatedBuilder = new DefensiveAnnotatedBuilder(this);
44-
defensiveJUnit4Builder = new DefensiveJUnit4Builder();
50+
junit4Builder = new DefensiveJUnit4Builder();
51+
ignoredBuilder = new NullIgnoredBuilder();
52+
}
53+
54+
@Override
55+
public Runner runnerForClass(Class<?> testClass) throws Throwable {
56+
Runner runner = super.runnerForClass(testClass);
57+
if (testClass.getAnnotation(Ignore.class) != null) {
58+
if (runner == null) {
59+
return new IgnoredClassRunner(testClass);
60+
}
61+
return decorateIgnoredTestClass(runner);
62+
}
63+
return runner;
64+
}
65+
66+
/**
67+
* Instead of checking for the {@link Ignore} annotation and returning an
68+
* {@link IgnoredClassRunner} from {@link IgnoredBuilder}, we've let the
69+
* super class determine the regular runner that would have been used if
70+
* {@link Ignore} hadn't been present. Now, we decorate the runner to
71+
* override its runtime behavior (i.e. skip execution) but return its
72+
* regular {@link org.junit.runner.Description}.
73+
*/
74+
private Runner decorateIgnoredTestClass(Runner runner) {
75+
if (runner instanceof Filterable) {
76+
return new FilterableIgnoringRunnerDecorator(runner);
77+
}
78+
return new IgnoringRunnerDecorator(runner);
4579
}
4680

4781
@Override
@@ -51,7 +85,12 @@ protected AnnotatedBuilder annotatedBuilder() {
5185

5286
@Override
5387
protected JUnit4Builder junit4Builder() {
54-
return defensiveJUnit4Builder;
88+
return junit4Builder;
89+
}
90+
91+
@Override
92+
protected IgnoredBuilder ignoredBuilder() {
93+
return ignoredBuilder;
5594
}
5695

5796
/**
@@ -96,4 +135,16 @@ private boolean containsTestMethods(Class<?> testClass) {
96135
}
97136
}
98137

138+
/**
139+
* Customization of {@link IgnoredBuilder} that always returns {@code null}.
140+
*
141+
* @since 5.1
142+
*/
143+
private static class NullIgnoredBuilder extends IgnoredBuilder {
144+
@Override
145+
public Runner runnerForClass(Class<?> testClass) {
146+
// don't ignore entire test classes just yet
147+
return null;
148+
}
149+
}
99150
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2015-2018 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* http://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.vintage.engine.discovery;
12+
13+
import org.junit.platform.commons.util.Preconditions;
14+
import org.junit.runner.Runner;
15+
import org.junit.runner.manipulation.Filter;
16+
import org.junit.runner.manipulation.Filterable;
17+
import org.junit.runner.manipulation.NoTestsRemainException;
18+
19+
/**
20+
* {@link Filterable} {@link IgnoringRunnerDecorator}.
21+
*
22+
* @since 5.1
23+
*/
24+
class FilterableIgnoringRunnerDecorator extends IgnoringRunnerDecorator implements Filterable {
25+
26+
FilterableIgnoringRunnerDecorator(Runner runner) {
27+
super(runner);
28+
Preconditions.condition(runner instanceof Filterable,
29+
() -> "Runner must be an instance of Filterable: " + runner.getClass().getName());
30+
}
31+
32+
@Override
33+
public void filter(Filter filter) throws NoTestsRemainException {
34+
((Filterable) runner).filter(filter);
35+
}
36+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2015-2018 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* http://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.vintage.engine.discovery;
12+
13+
import org.junit.platform.commons.util.Preconditions;
14+
import org.junit.runner.Description;
15+
import org.junit.runner.Runner;
16+
import org.junit.runner.notification.RunNotifier;
17+
18+
/**
19+
* Decorator for Runners that will be ignored completely.
20+
*
21+
* <p>Contrary to {@link org.junit.internal.builders.IgnoredClassRunner}, this
22+
* runner returns a complete description including all children.
23+
*
24+
* @since 5.1
25+
*/
26+
class IgnoringRunnerDecorator extends Runner implements RunnerDecorator {
27+
28+
protected final Runner runner;
29+
30+
IgnoringRunnerDecorator(Runner runner) {
31+
this.runner = Preconditions.notNull(runner, "Runner must not be null");
32+
}
33+
34+
@Override
35+
public Description getDescription() {
36+
return runner.getDescription();
37+
}
38+
39+
@Override
40+
public void run(RunNotifier notifier) {
41+
notifier.fireTestIgnored(getDescription());
42+
}
43+
44+
@Override
45+
public Runner getDecoratedRunner() {
46+
return runner;
47+
}
48+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright 2015-2018 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* http://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.vintage.engine.discovery;
12+
13+
import org.junit.runner.Runner;
14+
15+
public interface RunnerDecorator {
16+
17+
Runner getDecoratedRunner();
18+
19+
}

junit-vintage-engine/src/main/java/org/junit/vintage/engine/discovery/TestClassRequestResolver.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,10 @@ private RunnerTestDescriptor determineRunnerTestDescriptor(Class<?> testClass, R
6969
runnerTestDescriptor = createCompleteRunnerTestDescriptor(testClass, filteredRunner, engineId);
7070
}
7171
else {
72-
logger.warn(() -> "Runner " + runner.getClass().getName() //
72+
Runner runnerToReport = (runner instanceof RunnerDecorator)
73+
? ((RunnerDecorator) runner).getDecoratedRunner()
74+
: runner;
75+
logger.warn(() -> "Runner " + runnerToReport.getClass().getName() //
7376
+ " (used on " + testClass.getName() + ") does not support filtering" //
7477
+ " and will therefore be run completely.");
7578
}

junit-vintage-engine/src/main/java/org/junit/vintage/engine/execution/RunListenerAdapter.java

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,6 @@ class RunListenerAdapter extends RunListener {
4444
this.uniqueIdExtractor = new UniqueIdReader().andThen(new UniqueIdStringifier());
4545
}
4646

47-
@Override
48-
public void testRunStarted(Description description) {
49-
// If it's not a suite it might be skipped entirely later on.
50-
if (description.isSuite()) {
51-
fireExecutionStarted(testRun.getRunnerTestDescriptor());
52-
}
53-
}
54-
5547
@Override
5648
public void testIgnored(Description description) {
5749
testIgnored(lookupOrRegisterTestDescriptor(description), determineReasonForIgnoredTest(description));

0 commit comments

Comments
 (0)