Skip to content

Commit 8d83528

Browse files
Farid Zakariavinnybod
andauthored
Add support for @afterall in XML report (#300)
Writing a simple test like ```java package com.library; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Test; public class AlwaysFailTest { @test public void doNothingTest() { System.out.println("Hi there!"); } @afterall public static void alwaysFail() { throw new RuntimeException("Always failing."); } } ``` Produces an XML that seems to look like it succeeds. ```xml <?xml version="1.0" encoding="UTF-8"?> <testsuites> <testsuite name="com.library.AlwaysFailTest" timestamp="2024-10-10T21:19:11.522176Z" hostname="KHK9NLVQGN" tests="2" failures="0" errors="0" disabled="0" skipped="0" package=""> <properties/> <testcase name="doNothingTest" classname="com.library.AlwaysFailTest" time="0.03"> <system-out><![CDATA[Hi there! ]]></system-out> </testcase> </testsuite> </testsuites> ``` Bazel itself correctly identifies it fails. The Junit4 reporter also included with Bazel natively correclty reports the failure in the XML output. Augment the Junit listener to add support for static methods and add them to the JUnit output. Doing so produces the following XML. ```xml <?xml version="1.0" encoding="UTF-8"?> <testsuites> <testsuite name="com.library.AlwaysFailTest" timestamp="2024-10-10T21:49:02.096648Z" hostname="KHK9NLVQGN" tests="3" failures="1" errors="0" disabled="0" skipped="0" package=""> <properties/> <testcase name="com.library.AlwaysFailTest" classname="com.library.AlwaysFailTest" time="0.05"> <failure message="Always failing." type="java.lang.RuntimeException"><![CDATA[java.lang.RuntimeException: Always failing. at com.library.AlwaysFailTest.alwaysFail(AlwaysFailTest.java:20) ... at com.github.bazel_contrib.contrib_rules_jvm.junit5.JUnit5Runner.main(JUnit5Runner.java:39) ]]></failure> </testcase> <testcase name="doNothingTest" classname="com.library.AlwaysFailTest" time="0.03"> <system-out><![CDATA[Hi there! ]]></system-out> </testcase> </testsuite> </testsuites> ``` Co-authored-by: Vince Rose <[email protected]>
1 parent 62bc8f3 commit 8d83528

File tree

6 files changed

+234
-5
lines changed

6 files changed

+234
-5
lines changed

MODULE.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ dev_maven.install(
222222
"org.junit.platform:junit-platform-suite:1.8.2",
223223
"org.junit.platform:junit-platform-suite-api:1.8.2",
224224
"org.junit.platform:junit-platform-suite-engine:1.8.2",
225+
"org.junit.platform:junit-platform-testkit:1.8.2",
225226
"org.junit.vintage:junit-vintage-engine:5.8.2",
226227
"org.mockito:mockito-core:4.8.1",
227228
],

contrib_rules_jvm_tests_install.json

100644100755
Lines changed: 134 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL",
3-
"__INPUT_ARTIFACTS_HASH": -579211453,
4-
"__RESOLVED_ARTIFACTS_HASH": -1476897549,
3+
"__INPUT_ARTIFACTS_HASH": -110331756,
4+
"__RESOLVED_ARTIFACTS_HASH": 1220653889,
55
"artifacts": {
66
"junit:junit": {
77
"shasums": {
@@ -31,6 +31,13 @@
3131
},
3232
"version": "1.1.2"
3333
},
34+
"org.assertj:assertj-core": {
35+
"shasums": {
36+
"jar": "d749db58c2bd353f1c03541d747b753931d4b84da8e48993ef51efe8694b4ed7",
37+
"sources": "d0384d378df4391392bbdf06691aa48b3ac3bc5f37c223e6466a75a03d54fee4"
38+
},
39+
"version": "3.20.2"
40+
},
3441
"org.hamcrest:hamcrest-core": {
3542
"shasums": {
3643
"jar": "66fdef91e9739348df7a096aa384a5685f4e875584cce89386a7a47251c4d8e9",
@@ -115,6 +122,13 @@
115122
},
116123
"version": "1.8.2"
117124
},
125+
"org.junit.platform:junit-platform-testkit": {
126+
"shasums": {
127+
"jar": "94d6fb9b1d4e7bb3d6a967b21f1cf4f7cf84455d602cccf614a186d936929b08",
128+
"sources": "485fac0cc55c14db0c10dff3e42351b10e0bfe5d337892efe8e5b5fcb672f752"
129+
},
130+
"version": "1.8.2"
131+
},
118132
"org.junit.vintage:junit-vintage-engine": {
119133
"shasums": {
120134
"jar": "ebd567b84e380d5373c47de3c9616d84f7bef91f9f8a8e7fc925be68240c1ba4",
@@ -198,6 +212,12 @@
198212
"org.junit.platform:junit-platform-suite-api",
199213
"org.junit.platform:junit-platform-suite-commons"
200214
],
215+
"org.junit.platform:junit-platform-testkit": [
216+
"org.apiguardian:apiguardian-api",
217+
"org.assertj:assertj-core",
218+
"org.junit.platform:junit-platform-launcher",
219+
"org.opentest4j:opentest4j"
220+
],
201221
"org.junit.vintage:junit-vintage-engine": [
202222
"junit:junit",
203223
"org.apiguardian:apiguardian-api",
@@ -294,6 +314,69 @@
294314
"org.apiguardian:apiguardian-api": [
295315
"org.apiguardian.api"
296316
],
317+
"org.assertj:assertj-core": [
318+
"org.assertj.core.annotations",
319+
"org.assertj.core.api",
320+
"org.assertj.core.api.exception",
321+
"org.assertj.core.api.filter",
322+
"org.assertj.core.api.iterable",
323+
"org.assertj.core.api.junit.jupiter",
324+
"org.assertj.core.api.recursive.comparison",
325+
"org.assertj.core.condition",
326+
"org.assertj.core.configuration",
327+
"org.assertj.core.data",
328+
"org.assertj.core.description",
329+
"org.assertj.core.error",
330+
"org.assertj.core.error.array2d",
331+
"org.assertj.core.error.future",
332+
"org.assertj.core.error.uri",
333+
"org.assertj.core.extractor",
334+
"org.assertj.core.groups",
335+
"org.assertj.core.internal",
336+
"org.assertj.core.internal.bytebuddy",
337+
"org.assertj.core.internal.bytebuddy.agent.builder",
338+
"org.assertj.core.internal.bytebuddy.asm",
339+
"org.assertj.core.internal.bytebuddy.build",
340+
"org.assertj.core.internal.bytebuddy.description",
341+
"org.assertj.core.internal.bytebuddy.description.annotation",
342+
"org.assertj.core.internal.bytebuddy.description.enumeration",
343+
"org.assertj.core.internal.bytebuddy.description.field",
344+
"org.assertj.core.internal.bytebuddy.description.method",
345+
"org.assertj.core.internal.bytebuddy.description.modifier",
346+
"org.assertj.core.internal.bytebuddy.description.type",
347+
"org.assertj.core.internal.bytebuddy.dynamic",
348+
"org.assertj.core.internal.bytebuddy.dynamic.loading",
349+
"org.assertj.core.internal.bytebuddy.dynamic.scaffold",
350+
"org.assertj.core.internal.bytebuddy.dynamic.scaffold.inline",
351+
"org.assertj.core.internal.bytebuddy.dynamic.scaffold.subclass",
352+
"org.assertj.core.internal.bytebuddy.implementation",
353+
"org.assertj.core.internal.bytebuddy.implementation.attribute",
354+
"org.assertj.core.internal.bytebuddy.implementation.auxiliary",
355+
"org.assertj.core.internal.bytebuddy.implementation.bind",
356+
"org.assertj.core.internal.bytebuddy.implementation.bind.annotation",
357+
"org.assertj.core.internal.bytebuddy.implementation.bytecode",
358+
"org.assertj.core.internal.bytebuddy.implementation.bytecode.assign",
359+
"org.assertj.core.internal.bytebuddy.implementation.bytecode.assign.primitive",
360+
"org.assertj.core.internal.bytebuddy.implementation.bytecode.assign.reference",
361+
"org.assertj.core.internal.bytebuddy.implementation.bytecode.collection",
362+
"org.assertj.core.internal.bytebuddy.implementation.bytecode.constant",
363+
"org.assertj.core.internal.bytebuddy.implementation.bytecode.member",
364+
"org.assertj.core.internal.bytebuddy.jar.asm",
365+
"org.assertj.core.internal.bytebuddy.jar.asm.commons",
366+
"org.assertj.core.internal.bytebuddy.jar.asm.signature",
367+
"org.assertj.core.internal.bytebuddy.matcher",
368+
"org.assertj.core.internal.bytebuddy.pool",
369+
"org.assertj.core.internal.bytebuddy.utility",
370+
"org.assertj.core.internal.bytebuddy.utility.privilege",
371+
"org.assertj.core.internal.bytebuddy.utility.visitor",
372+
"org.assertj.core.matcher",
373+
"org.assertj.core.presentation",
374+
"org.assertj.core.util",
375+
"org.assertj.core.util.diff",
376+
"org.assertj.core.util.diff.myers",
377+
"org.assertj.core.util.introspection",
378+
"org.assertj.core.util.xml"
379+
],
297380
"org.hamcrest:hamcrest-core": [
298381
"org.hamcrest",
299382
"org.hamcrest.core",
@@ -380,6 +463,9 @@
380463
"org.junit.platform:junit-platform-suite-engine": [
381464
"org.junit.platform.suite.engine"
382465
],
466+
"org.junit.platform:junit-platform-testkit": [
467+
"org.junit.platform.testkit.engine"
468+
],
383469
"org.junit.vintage:junit-vintage-engine": [
384470
"org.junit.vintage.engine",
385471
"org.junit.vintage.engine.descriptor",
@@ -479,6 +565,8 @@
479565
"net.bytebuddy:byte-buddy:jar:sources",
480566
"org.apiguardian:apiguardian-api",
481567
"org.apiguardian:apiguardian-api:jar:sources",
568+
"org.assertj:assertj-core",
569+
"org.assertj:assertj-core:jar:sources",
482570
"org.hamcrest:hamcrest-core",
483571
"org.hamcrest:hamcrest-core:jar:sources",
484572
"org.junit.jupiter:junit-jupiter-api",
@@ -503,6 +591,8 @@
503591
"org.junit.platform:junit-platform-suite-engine",
504592
"org.junit.platform:junit-platform-suite-engine:jar:sources",
505593
"org.junit.platform:junit-platform-suite:jar:sources",
594+
"org.junit.platform:junit-platform-testkit",
595+
"org.junit.platform:junit-platform-testkit:jar:sources",
506596
"org.junit.vintage:junit-vintage-engine",
507597
"org.junit.vintage:junit-vintage-engine:jar:sources",
508598
"org.mockito:mockito-core",
@@ -513,5 +603,47 @@
513603
"org.opentest4j:opentest4j:jar:sources"
514604
]
515605
},
606+
"services": {
607+
"org.junit.jupiter:junit-jupiter-engine": {
608+
"org.junit.platform.engine.TestEngine": [
609+
"org.junit.jupiter.engine.JupiterTestEngine"
610+
]
611+
},
612+
"org.junit.jupiter:junit-jupiter-engine:jar:sources": {
613+
"org.junit.platform.engine.TestEngine": [
614+
"org.junit.jupiter.engine.JupiterTestEngine"
615+
]
616+
},
617+
"org.junit.platform:junit-platform-launcher": {
618+
"org.junit.platform.launcher.TestExecutionListener": [
619+
"org.junit.platform.launcher.listeners.UniqueIdTrackingListener"
620+
]
621+
},
622+
"org.junit.platform:junit-platform-launcher:jar:sources": {
623+
"org.junit.platform.launcher.TestExecutionListener": [
624+
"org.junit.platform.launcher.listeners.UniqueIdTrackingListener"
625+
]
626+
},
627+
"org.junit.platform:junit-platform-suite-engine": {
628+
"org.junit.platform.engine.TestEngine": [
629+
"org.junit.platform.suite.engine.SuiteTestEngine"
630+
]
631+
},
632+
"org.junit.platform:junit-platform-suite-engine:jar:sources": {
633+
"org.junit.platform.engine.TestEngine": [
634+
"org.junit.platform.suite.engine.SuiteTestEngine"
635+
]
636+
},
637+
"org.junit.vintage:junit-vintage-engine": {
638+
"org.junit.platform.engine.TestEngine": [
639+
"org.junit.vintage.engine.VintageTestEngine"
640+
]
641+
},
642+
"org.junit.vintage:junit-vintage-engine:jar:sources": {
643+
"org.junit.platform.engine.TestEngine": [
644+
"org.junit.vintage.engine.VintageTestEngine"
645+
]
646+
}
647+
},
516648
"version": "2"
517649
}

java/src/com/github/bazel_contrib/contrib_rules_jvm/junit5/BazelJUnitOutputListener.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,9 @@ private Map<TestData, List<TestData>> matchTestCasesToSuites_locked(
109109
// results
110110
List<UniqueId.Segment> segments = testCase.getId().getUniqueIdObject().getSegments();
111111

112-
if (segments.size() == 3 || segments.size() == 5) {
112+
if (segments.size() == 2) {
113+
parent = results.get(testCase.getId().getUniqueIdObject());
114+
} else if (segments.size() == 3 || segments.size() == 5) {
113115
// get class / test data up one level
114116
parent =
115117
testCase
@@ -161,7 +163,7 @@ private List<TestData> findTestCases_locked() {
161163
// are identified by the fact that they have no child test cases in the
162164
// test plan, or they are marked as tests.
163165
TestIdentifier id = result.getId();
164-
return id.isTest() || testPlan.getChildren(id).isEmpty();
166+
return id.getSource() != null || id.isTest() || testPlan.getChildren(id).isEmpty();
165167
})
166168
.collect(Collectors.toList());
167169
}

java/src/com/github/bazel_contrib/contrib_rules_jvm/junit5/TestData.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,4 +146,25 @@ public boolean isDynamic() {
146146
public Instant getStarted() {
147147
return this.started;
148148
}
149+
150+
@Override
151+
public String toString() {
152+
return "TestData{"
153+
+ "id="
154+
+ id
155+
+ ", reportEntries="
156+
+ reportEntries
157+
+ ", started="
158+
+ started
159+
+ ", finished="
160+
+ finished
161+
+ ", reason='"
162+
+ reason
163+
+ '\''
164+
+ ", result="
165+
+ result
166+
+ ", dynamic="
167+
+ dynamic
168+
+ '}';
169+
}
149170
}

java/test/com/github/bazel_contrib/contrib_rules_jvm/junit5/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ java_test_suite(
3838
artifact("org.junit.jupiter:junit-jupiter-api", "contrib_rules_jvm_tests"),
3939
artifact("org.junit.jupiter:junit-jupiter-params", "contrib_rules_jvm_tests"),
4040
artifact("org.junit.platform:junit-platform-engine", "contrib_rules_jvm_tests"),
41+
artifact("org.junit.platform:junit-platform-testkit", "contrib_rules_jvm_tests"),
4142
artifact("org.mockito:mockito-core", "contrib_rules_jvm_tests"),
4243
artifact("org.opentest4j:opentest4j", "contrib_rules_jvm_tests"),
4344
] + junit5_vintage_deps("contrib_rules_jvm_tests"),
Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,40 @@
55
import static org.junit.jupiter.api.Assertions.assertNotNull;
66
import static org.junit.jupiter.api.Assertions.assertTrue;
77
import static org.junit.jupiter.api.Assertions.fail;
8+
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
89
import static org.junit.platform.launcher.LauncherConstants.STDERR_REPORT_ENTRY_KEY;
910
import static org.junit.platform.launcher.LauncherConstants.STDOUT_REPORT_ENTRY_KEY;
1011

12+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
13+
import java.io.File;
1114
import java.io.IOException;
1215
import java.io.Reader;
1316
import java.io.StringReader;
1417
import java.io.StringWriter;
1518
import java.io.Writer;
19+
import java.nio.file.Files;
1620
import java.util.Collection;
1721
import java.util.List;
22+
import java.util.concurrent.atomic.AtomicBoolean;
1823
import javax.xml.parsers.DocumentBuilder;
1924
import javax.xml.parsers.DocumentBuilderFactory;
2025
import javax.xml.parsers.ParserConfigurationException;
2126
import javax.xml.stream.XMLOutputFactory;
2227
import javax.xml.stream.XMLStreamException;
2328
import javax.xml.stream.XMLStreamWriter;
29+
import org.junit.jupiter.api.AfterAll;
2430
import org.junit.jupiter.api.Test;
2531
import org.junit.platform.engine.TestDescriptor;
2632
import org.junit.platform.engine.TestExecutionResult;
2733
import org.junit.platform.engine.UniqueId;
2834
import org.junit.platform.engine.reporting.ReportEntry;
35+
import org.junit.platform.launcher.Launcher;
2936
import org.junit.platform.launcher.TestIdentifier;
3037
import org.junit.platform.launcher.TestPlan;
38+
import org.junit.platform.launcher.core.LauncherConfig;
39+
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
40+
import org.junit.platform.launcher.core.LauncherFactory;
41+
import org.junit.platform.testkit.engine.EngineTestKit;
3142
import org.mockito.Mockito;
3243
import org.opentest4j.TestAbortedException;
3344
import org.w3c.dom.Document;
@@ -37,11 +48,72 @@
3748
import org.xml.sax.InputSource;
3849
import org.xml.sax.SAXException;
3950

40-
public class BazelJUnitOuputListenerTest {
51+
public class BazelJUnitOutputListenerTest {
4152

4253
private TestDescriptor testDescriptor = new StubbedTestDescriptor(createId("descriptors"));
4354
private TestIdentifier identifier = TestIdentifier.from(testDescriptor);
4455

56+
/** This latch is used in TestAfterAllFails for testAfterAllFailuresAreReported */
57+
private static final AtomicBoolean causeFailure = new AtomicBoolean(false);
58+
59+
static final class TestAfterAllFails {
60+
61+
@SuppressFBWarnings(value = "THROWS_METHOD_THROWS_RUNTIMEEXCEPTION")
62+
@AfterAll
63+
static void afterAll() {
64+
if (causeFailure.get()) {
65+
throw new RuntimeException("I always fail.");
66+
}
67+
}
68+
69+
@Test
70+
void test() {}
71+
}
72+
73+
@Test
74+
public void testAfterAllFailuresAreReported() throws IOException {
75+
causeFailure.set(true);
76+
77+
// First let's do a sanity test that we have the expected failures for the @AfterAll
78+
EngineTestKit.engine("junit-jupiter")
79+
.selectors(selectClass(TestAfterAllFails.class))
80+
.execute()
81+
.containerEvents()
82+
.assertStatistics(stats -> stats.skipped(0).started(2).succeeded(1).aborted(0).failed(1));
83+
84+
// Now let's run the same test. Unfortunately we cannot use EngineTestKit since it has no way
85+
// to register a listener.
86+
File xmlFile = File.createTempFile("junit-report", "xml");
87+
BazelJUnitOutputListener listener = new BazelJUnitOutputListener(xmlFile.toPath());
88+
LauncherConfig config = LauncherConfig.builder().addTestExecutionListeners(listener).build();
89+
90+
Launcher launcher = LauncherFactory.create(config);
91+
92+
LauncherDiscoveryRequestBuilder request =
93+
LauncherDiscoveryRequestBuilder.request().selectors(selectClass(TestAfterAllFails.class));
94+
95+
launcher.discover(request.build());
96+
97+
launcher.execute(request.build());
98+
listener.close();
99+
100+
// now write an assertion to validate the XML file has an error
101+
String[] expectedStrings = {
102+
"<failure message=\"I always fail.\" type=\"java.lang.RuntimeException\">", "failures=\"1\"",
103+
};
104+
105+
// Useful for debugging the expected output
106+
// System.out.println(Files.readString(xmlFile.toPath()));
107+
108+
for (String expected : expectedStrings) {
109+
assertTrue(
110+
Files.lines(xmlFile.toPath()).anyMatch(line -> line.contains(expected)),
111+
"Expected to find " + expected + " in " + xmlFile);
112+
}
113+
114+
causeFailure.set(false);
115+
}
116+
45117
@Test
46118
public void testResultCanBeDisabled() {
47119
// Note: we do not call `markFinished` so the TestResult is null. This is what happens when

0 commit comments

Comments
 (0)