Skip to content

Commit cb34452

Browse files
Fix reporting of ignored JUnit 3 test classes (#3427)
Prior to this commit, `@Ignore`-annotated JUnit 3 test classes where reported as started and then as skipped due to their `Description` not including class-level annotations in some case. Co-authored-by: Marc Philipp <mail@marcphilipp.de>
1 parent a6a559b commit cb34452

File tree

9 files changed

+111
-13
lines changed

9 files changed

+111
-13
lines changed

documentation/src/docs/asciidoc/release-notes/release-notes-5.10.1.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ JUnit repository on GitHub.
4747

4848
==== Bug Fixes
4949

50-
*
50+
* Fix reporting of JUnit 3 test classes with `@Ignored` annotation
5151

5252
==== Deprecations and Breaking Changes
5353

junit-vintage-engine/src/main/java/org/junit/vintage/engine/descriptor/RunnerTestDescriptor.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,14 @@ public class RunnerTestDescriptor extends VintageTestDescriptor {
4343

4444
private final Set<Description> rejectedExclusions = new HashSet<>();
4545
private Runner runner;
46+
private final boolean ignored;
4647
private boolean wasFiltered;
4748
private List<Filter> filters = new ArrayList<>();
4849

49-
public RunnerTestDescriptor(UniqueId uniqueId, Class<?> testClass, Runner runner) {
50+
public RunnerTestDescriptor(UniqueId uniqueId, Class<?> testClass, Runner runner, boolean ignored) {
5051
super(uniqueId, runner.getDescription(), testClass.getSimpleName(), ClassSource.from(testClass));
5152
this.runner = runner;
53+
this.ignored = ignored;
5254
}
5355

5456
@Override
@@ -155,6 +157,10 @@ private Runner getRunnerToReport() {
155157
return (runner instanceof RunnerDecorator) ? ((RunnerDecorator) runner).getDecoratedRunner() : runner;
156158
}
157159

160+
public boolean isIgnored() {
161+
return ignored;
162+
}
163+
158164
private static class ExcludeDescriptionFilter extends Filter {
159165

160166
private final Description description;

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,14 @@
2626
import org.junit.platform.engine.discovery.UniqueIdSelector;
2727
import org.junit.platform.engine.support.discovery.SelectorResolver;
2828
import org.junit.runner.Runner;
29-
import org.junit.runners.model.RunnerBuilder;
3029
import org.junit.vintage.engine.descriptor.RunnerTestDescriptor;
3130

3231
/**
3332
* @since 4.12
3433
*/
3534
class ClassSelectorResolver implements SelectorResolver {
3635

37-
private static final RunnerBuilder RUNNER_BUILDER = new DefensiveAllDefaultPossibilitiesBuilder();
36+
private static final DefensiveAllDefaultPossibilitiesBuilder RUNNER_BUILDER = new DefensiveAllDefaultPossibilitiesBuilder();
3837

3938
private final ClassFilter classFilter;
4039

@@ -76,7 +75,7 @@ private Resolution resolveTestClass(Class<?> testClass, Context context) {
7675

7776
private RunnerTestDescriptor createRunnerTestDescriptor(TestDescriptor parent, Class<?> testClass, Runner runner) {
7877
UniqueId uniqueId = parent.getUniqueId().append(SEGMENT_TYPE_RUNNER, testClass.getName());
79-
return new RunnerTestDescriptor(uniqueId, testClass, runner);
78+
return new RunnerTestDescriptor(uniqueId, testClass, runner, RUNNER_BUILDER.isIgnored(runner));
8079
}
8180

8281
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ public Runner runnerForClass(Class<?> testClass) throws Throwable {
6464
return runner;
6565
}
6666

67+
boolean isIgnored(Runner runner) {
68+
return runner instanceof IgnoredClassRunner || runner instanceof IgnoringRunnerDecorator;
69+
}
70+
6771
/**
6872
* Instead of checking for the {@link Ignore} annotation and returning an
6973
* {@link IgnoredClassRunner} from {@link IgnoredBuilder}, we've let the
@@ -72,7 +76,7 @@ public Runner runnerForClass(Class<?> testClass) throws Throwable {
7276
* override its runtime behavior (i.e. skip execution) but return its
7377
* regular {@link org.junit.runner.Description}.
7478
*/
75-
private Runner decorateIgnoredTestClass(Runner runner) {
79+
private IgnoringRunnerDecorator decorateIgnoredTestClass(Runner runner) {
7680
if (runner instanceof Filterable) {
7781
return new FilterableIgnoringRunnerDecorator(runner);
7882
}

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

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.junit.platform.engine.TestDescriptor;
2121
import org.junit.platform.engine.TestExecutionResult;
2222
import org.junit.platform.engine.UniqueId;
23+
import org.junit.platform.engine.support.descriptor.ClassSource;
2324
import org.junit.runner.Description;
2425
import org.junit.runner.Result;
2526
import org.junit.runner.notification.Failure;
@@ -49,7 +50,7 @@ class RunListenerAdapter extends RunListener {
4950

5051
@Override
5152
public void testRunStarted(Description description) {
52-
if (description.isSuite() && description.getAnnotation(Ignore.class) == null) {
53+
if (description.isSuite() && !testRun.getRunnerTestDescriptor().isIgnored()) {
5354
fireExecutionStarted(testRun.getRunnerTestDescriptor(), EventType.REPORTED);
5455
}
5556
}
@@ -65,7 +66,9 @@ public void testSuiteStarted(Description description) {
6566

6667
@Override
6768
public void testIgnored(Description description) {
68-
testIgnored(lookupOrRegisterNextTestDescriptor(description), determineReasonForIgnoredTest(description));
69+
TestDescriptor testDescriptor = lookupOrRegisterNextTestDescriptor(description);
70+
String reason = determineReasonForIgnoredTest(testDescriptor, description).orElse("<unknown>");
71+
testIgnored(testDescriptor, reason);
6972
}
7073

7174
@Override
@@ -176,9 +179,21 @@ private void testIgnored(TestDescriptor testDescriptor, String reason) {
176179
fireExecutionSkipped(testDescriptor, reason);
177180
}
178181

179-
private String determineReasonForIgnoredTest(Description description) {
180-
Ignore ignoreAnnotation = description.getAnnotation(Ignore.class);
181-
return Optional.ofNullable(ignoreAnnotation).map(Ignore::value).orElse("<unknown>");
182+
private Optional<String> determineReasonForIgnoredTest(TestDescriptor testDescriptor, Description description) {
183+
Optional<String> reason = getReason(description.getAnnotation(Ignore.class));
184+
if (reason.isPresent()) {
185+
return reason;
186+
}
187+
// Workaround for some runners (e.g. JUnit38ClassRunner) don't include the @Ignore annotation
188+
// in the description, so we read it from the test class directly
189+
return testDescriptor.getSource() //
190+
.filter(ClassSource.class::isInstance) //
191+
.map(source -> ((ClassSource) source).getJavaClass()) //
192+
.flatMap(testClass -> getReason(testClass.getAnnotation(Ignore.class)));
193+
}
194+
195+
private static Optional<String> getReason(Ignore annotation) {
196+
return Optional.ofNullable(annotation).map(Ignore::value);
182197
}
183198

184199
private void dynamicTestRegistered(TestDescriptor testDescriptor) {

junit-vintage-engine/src/test/java/org/junit/vintage/engine/VintageTestEngineExecutionTests.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
package org.junit.vintage.engine;
1212

13+
import static java.util.function.Predicate.isEqual;
1314
import static org.assertj.core.api.Assertions.assertThat;
1415
import static org.junit.jupiter.api.Assumptions.assumeTrue;
1516
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
@@ -56,8 +57,10 @@
5657
import org.junit.runner.notification.RunNotifier;
5758
import org.junit.runners.Suite;
5859
import org.junit.runners.Suite.SuiteClasses;
60+
import org.junit.vintage.engine.samples.junit3.IgnoredJUnit3TestCase;
5961
import org.junit.vintage.engine.samples.junit3.JUnit3ParallelSuiteWithSubsuites;
6062
import org.junit.vintage.engine.samples.junit3.JUnit3SuiteWithSubsuites;
63+
import org.junit.vintage.engine.samples.junit3.JUnit4SuiteWithIgnoredJUnit3TestCase;
6164
import org.junit.vintage.engine.samples.junit3.PlainJUnit3TestCaseWithSingleTestWhichFails;
6265
import org.junit.vintage.engine.samples.junit4.CompletelyDynamicTestCase;
6366
import org.junit.vintage.engine.samples.junit4.EmptyIgnoredTestCase;
@@ -896,6 +899,27 @@ void executesRegularSpockFeatureMethod() {
896899
event(engine(), finishedSuccessfully()));
897900
}
898901

902+
@Test
903+
void executesIgnoredJUnit3TestCase() {
904+
var suiteClass = IgnoredJUnit3TestCase.class;
905+
execute(suiteClass).allEvents().assertEventsMatchExactly( //
906+
event(engine(), started()), //
907+
event(container(suiteClass), skippedWithReason(isEqual("testing"))), //
908+
event(engine(), finishedSuccessfully()));
909+
}
910+
911+
@Test
912+
void executesJUnit4SuiteWithIgnoredJUnit3TestCase() {
913+
var suiteClass = JUnit4SuiteWithIgnoredJUnit3TestCase.class;
914+
var testClass = IgnoredJUnit3TestCase.class;
915+
execute(suiteClass).allEvents().assertEventsMatchExactly( //
916+
event(engine(), started()), //
917+
event(container(suiteClass), started()), //
918+
event(container(testClass), skippedWithReason(isEqual("testing"))), //
919+
event(container(suiteClass), finishedSuccessfully()), //
920+
event(engine(), finishedSuccessfully()));
921+
}
922+
899923
private static EngineExecutionResults execute(Class<?> testClass) {
900924
return execute(request(testClass));
901925
}

junit-vintage-engine/src/test/java/org/junit/vintage/engine/execution/TestRunTests.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ class TestRunTests {
3232
void returnsEmptyOptionalForUnknownDescriptions() throws Exception {
3333
Class<?> testClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class;
3434
var runnerId = engineId().append(SEGMENT_TYPE_RUNNER, testClass.getName());
35-
var runnerTestDescriptor = new RunnerTestDescriptor(runnerId, testClass, new BlockJUnit4ClassRunner(testClass));
35+
var runnerTestDescriptor = new RunnerTestDescriptor(runnerId, testClass, new BlockJUnit4ClassRunner(testClass),
36+
false);
3637
var unknownDescription = createTestDescription(testClass, "dynamicTest");
3738

3839
var testRun = new TestRun(runnerTestDescriptor);
@@ -45,7 +46,8 @@ void returnsEmptyOptionalForUnknownDescriptions() throws Exception {
4546
void registersDynamicTestDescriptors() throws Exception {
4647
Class<?> testClass = PlainJUnit4TestCaseWithSingleTestWhichFails.class;
4748
var runnerId = engineId().append(SEGMENT_TYPE_RUNNER, testClass.getName());
48-
var runnerTestDescriptor = new RunnerTestDescriptor(runnerId, testClass, new BlockJUnit4ClassRunner(testClass));
49+
var runnerTestDescriptor = new RunnerTestDescriptor(runnerId, testClass, new BlockJUnit4ClassRunner(testClass),
50+
false);
4951
var dynamicTestId = runnerId.append(SEGMENT_TYPE_DYNAMIC, "dynamicTest");
5052
var dynamicDescription = createTestDescription(testClass, "dynamicTest");
5153
var dynamicTestDescriptor = new VintageTestDescriptor(dynamicTestId, dynamicDescription, null);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2015-2023 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+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.vintage.engine.samples.junit3;
12+
13+
import junit.framework.TestCase;
14+
15+
import org.junit.Assert;
16+
import org.junit.Ignore;
17+
18+
/**
19+
* @since 4.12
20+
*/
21+
@Ignore("testing")
22+
public class IgnoredJUnit3TestCase extends TestCase {
23+
24+
public void test() {
25+
Assert.fail("this test should be ignored");
26+
}
27+
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright 2015-2023 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+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.vintage.engine.samples.junit3;
12+
13+
import org.junit.runner.RunWith;
14+
import org.junit.runners.Suite;
15+
import org.junit.runners.Suite.SuiteClasses;
16+
17+
@RunWith(Suite.class)
18+
@SuiteClasses({ IgnoredJUnit3TestCase.class })
19+
public class JUnit4SuiteWithIgnoredJUnit3TestCase {
20+
}

0 commit comments

Comments
 (0)