Skip to content

Commit 685584e

Browse files
committed
Add timestamp attribute
While the timestamp attribute is not part of the JUnit or Surefire XSD in practice it seems to be a common enough property[1] that we can add it without expecting any of the popular tools to break. Closes: #44 1. https://github.com/testmoapp/junitxml
1 parent 4b5ddd5 commit 685584e

20 files changed

+50
-27
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Added
1111
- Update dependency io.cucumber:messages up to v26 ((#38)[https://github.com/cucumber/query/pull/38])
12+
- Add `timestamp` attribute ((#45)[https://github.com/cucumber/junit-xml-formatter/pull/45])
1213

1314
## [0.5.0] - 2024-06-22
1415
### Added

java/src/main/java/io/cucumber/junitxmlformatter/XmlReportData.java

+10
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package io.cucumber.junitxmlformatter;
22

3+
import io.cucumber.messages.Convertor;
34
import io.cucumber.messages.types.Envelope;
45
import io.cucumber.messages.types.Feature;
56
import io.cucumber.messages.types.Pickle;
67
import io.cucumber.messages.types.PickleStep;
78
import io.cucumber.messages.types.Step;
89
import io.cucumber.messages.types.TestCaseStarted;
10+
import io.cucumber.messages.types.TestRunStarted;
911
import io.cucumber.messages.types.TestStep;
1012
import io.cucumber.messages.types.TestStepFinished;
1113
import io.cucumber.messages.types.TestStepResult;
@@ -22,6 +24,7 @@
2224
import java.util.Optional;
2325

2426
import static io.cucumber.messages.types.TestStepResultStatus.PASSED;
27+
import static java.time.format.DateTimeFormatter.ISO_INSTANT;
2528
import static java.util.concurrent.TimeUnit.SECONDS;
2629
import static java.util.stream.Collectors.toList;
2730

@@ -123,4 +126,11 @@ TestStepResult getTestCaseStatus(TestCaseStarted testCaseStarted) {
123126
.orElse(SCENARIO_WITH_NO_STEPS);
124127
}
125128

129+
public String getTestRunStartedTimeStamp() {
130+
return query.findTestRunStarted()
131+
.map(TestRunStarted::getTimestamp)
132+
.map(Convertor::toInstant)
133+
.map(ISO_INSTANT::format)
134+
.orElse(null);
135+
}
126136
}

java/src/main/java/io/cucumber/junitxmlformatter/XmlReportWriter.java

+5
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ private void writeSuiteAttributes(EscapingXmlStreamWriter writer) throws XMLStre
5959
writer.writeAttribute("skipped", counts.getOrDefault(SKIPPED, 0L).toString());
6060
writer.writeAttribute("failures", String.valueOf(countFailures(counts)));
6161
writer.writeAttribute("errors", "0");
62+
63+
String testRunStartedTimeStamp = data.getTestRunStartedTimeStamp();
64+
if (testRunStartedTimeStamp != null) {
65+
writer.writeAttribute("timestamp", testRunStartedTimeStamp);
66+
}
6267
}
6368

6469
private static long countFailures(Map<TestStepResultStatus, Long> counts) {

java/src/test/java/io/cucumber/junitxmlformatter/MessagesToJunitXmlWriterAcceptanceTest.java

+18-11
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.junit.jupiter.api.Disabled;
77
import org.junit.jupiter.params.ParameterizedTest;
88
import org.junit.jupiter.params.provider.MethodSource;
9+
import org.xmlunit.assertj.XmlAssert;
910
import org.xmlunit.builder.Input;
1011
import org.xmlunit.validation.JAXPValidator;
1112
import org.xmlunit.validation.Languages;
@@ -20,6 +21,7 @@
2021
import java.nio.file.Files;
2122
import java.nio.file.Path;
2223
import java.nio.file.Paths;
24+
import java.util.ArrayList;
2325
import java.util.Arrays;
2426
import java.util.Comparator;
2527
import java.util.List;
@@ -28,6 +30,7 @@
2830
import java.util.stream.Stream;
2931

3032
import static io.cucumber.junitxmlformatter.Jackson.OBJECT_MAPPER;
33+
import static org.assertj.core.api.Assertions.assertThat;
3134
import static org.xmlunit.assertj.XmlAssert.assertThat;
3235

3336
class MessagesToJunitXmlWriterAcceptanceTest {
@@ -49,7 +52,7 @@ void test(TestCase testCase) throws IOException {
4952
ByteArrayOutputStream bytes = writeJunitXmlReport(testCase, new ByteArrayOutputStream());
5053
Source expected = Input.fromPath(testCase.expected).build();
5154
Source actual = Input.fromByteArray(bytes.toByteArray()).build();
52-
assertThat(actual).and(expected).ignoreWhitespace().areIdentical();
55+
XmlAssert.assertThat(actual).and(expected).ignoreWhitespace().areIdentical();
5356
}
5457

5558
@ParameterizedTest
@@ -58,7 +61,7 @@ void validateAgainstJenkins(TestCase testCase) throws IOException {
5861
ByteArrayOutputStream bytes = writeJunitXmlReport(testCase, new ByteArrayOutputStream());
5962
Source actual = Input.fromByteArray(bytes.toByteArray()).build();
6063
Source jenkinsSchema = Input.fromPath(Paths.get("../jenkins-junit.xsd")).build();
61-
assertThat(actual).isValidAgainst(jenkinsSchema);
64+
XmlAssert.assertThat(actual).isValidAgainst(jenkinsSchema);
6265
}
6366

6467
static final List<String> testCasesWithMissingException = Arrays.asList(
@@ -76,11 +79,16 @@ void validateAgainstSurefire(TestCase testCase) throws IOException {
7679
ByteArrayOutputStream bytes = writeJunitXmlReport(testCase, new ByteArrayOutputStream());
7780
Source actual = Input.fromByteArray(bytes.toByteArray()).build();
7881
Source surefireSchema = Input.fromPath(Paths.get("../surefire-test-report-3.0.xsd")).build();
79-
if (!testCasesWithMissingException.contains(testCase.name)) {
80-
assertThat(actual).isValidAgainst(surefireSchema);
81-
return;
82-
}
8382

83+
JAXPValidator validator = new JAXPValidator(Languages.W3C_XML_SCHEMA_NS_URI);
84+
validator.setSchemaSource(surefireSchema);
85+
ValidationResult validationResult = validator.validateInstance(actual);
86+
87+
List<String> expectedProblems = new ArrayList<>();
88+
/*
89+
* We add the timestamp attribute to all reports.
90+
*/
91+
expectedProblems.add("cvc-complex-type.3.2.2: Attribute 'timestamp' is not allowed to appear in element 'testsuite'.");
8492
/*
8593
This report tries to be compatible with the Jenkins XSD. The Surefire
8694
XSD is a bit stricter and generally assumes tests fail with an
@@ -94,12 +102,11 @@ void validateAgainstSurefire(TestCase testCase) throws IOException {
94102
Since the Surefire XSD is also relatively popular we do check it and
95103
exclude the cases that don't pass selectively.
96104
*/
97-
JAXPValidator validator = new JAXPValidator(Languages.W3C_XML_SCHEMA_NS_URI);
98-
validator.setSchemaSource(surefireSchema);
99-
ValidationResult validationResult = validator.validateInstance(actual);
105+
if (testCasesWithMissingException.contains(testCase.name)) {
106+
expectedProblems.add("cvc-complex-type.4: Attribute 'type' must appear on element 'failure'.");
107+
}
100108
Iterable<ValidationProblem> problems = validationResult.getProblems();
101-
Assertions.assertThat(problems).extracting(ValidationProblem::getMessage)
102-
.containsOnly("cvc-complex-type.4: Attribute 'type' must appear on element 'failure'.");
109+
assertThat(problems).extracting(ValidationProblem::getMessage).containsAll(expectedProblems);
103110
}
104111

105112
@ParameterizedTest

java/src/test/java/io/cucumber/junitxmlformatter/MessagesToJunitXmlWriterTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ void it_writes_two_messages_to_xml() throws IOException {
2828

2929
assertThat(html).isEqualTo("" +
3030
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
31-
"<testsuite name=\"Cucumber\" time=\"20\" tests=\"0\" skipped=\"0\" failures=\"0\" errors=\"0\">\n" +
31+
"<testsuite name=\"Cucumber\" time=\"20\" tests=\"0\" skipped=\"0\" failures=\"0\" errors=\"0\" timestamp=\"1970-01-01T00:00:10Z\">\n" +
3232
"</testsuite>\n"
3333
);
3434
}

testdata/attachments.feature.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<testsuite name="Cucumber" time="0.045" tests="11" skipped="0" failures="0" errors="0">
2+
<testsuite name="Cucumber" time="0.045" tests="11" skipped="0" failures="0" errors="0" timestamp="1970-01-01T00:00:00Z">
33
<testcase classname="Attachments" name="Strings can be attached with a media type" time="0.003">
44
<system-out><![CDATA[
55
When the string "hello" is attached as "application/octet-stream"...........passed

testdata/cdata.feature.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<testsuite name="Cucumber" time="0.005" tests="1" skipped="0" failures="0" errors="0">
2+
<testsuite name="Cucumber" time="0.005" tests="1" skipped="0" failures="0" errors="0" timestamp="1970-01-01T00:00:00Z">
33
<testcase classname="cdata" name="cdata" time="0.003">
44
<system-out><![CDATA[
55
Given I have 42 <![CDATA[cukes]]]]><![CDATA[> in my belly...............................passed

testdata/data-tables.feature.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<testsuite name="Cucumber" time="0.007" tests="1" skipped="0" failures="0" errors="0">
2+
<testsuite name="Cucumber" time="0.007" tests="1" skipped="0" failures="0" errors="0" timestamp="1970-01-01T00:00:00Z">
33
<testcase classname="Data Tables" name="transposed table" time="0.005">
44
<system-out><![CDATA[
55
When the following table is transposed:.....................................passed

testdata/examples-tables.feature.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<testsuite name="Cucumber" time="0.073" tests="9" skipped="0" failures="4" errors="0">
2+
<testsuite name="Cucumber" time="0.073" tests="9" skipped="0" failures="4" errors="0" timestamp="1970-01-01T00:00:00Z">
33
<testcase classname="Examples Tables" name="Eating cucumbers - These are passing - #1.1" time="0.007">
44
<system-out><![CDATA[
55
Given there are 12 cucumbers................................................passed

testdata/hooks.feature.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<testsuite name="Cucumber" time="0.055" tests="5" skipped="0" failures="3" errors="0">
2+
<testsuite name="Cucumber" time="0.055" tests="5" skipped="0" failures="3" errors="0" timestamp="1970-01-01T00:00:00Z">
33
<testcase classname="Hooks" name="No tags and a passed step" time="0.009">
44
<system-out><![CDATA[
55
When a step passes..........................................................passed

testdata/markdown.feature.md.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<testsuite name="Cucumber" time="0.021" tests="2" skipped="0" failures="1" errors="0">
2+
<testsuite name="Cucumber" time="0.021" tests="2" skipped="0" failures="1" errors="0" timestamp="1970-01-01T00:00:00Z">
33
<testcase classname="Cheese" name="Nom nom nom - Ylajali! - because we need more tables - #1.1" time="0.009">
44
<failure type="Error" message="You asked me to fail">
55
<![CDATA[You asked me to fail

testdata/minimal.feature.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<testsuite name="Cucumber" time="0.005" tests="1" skipped="0" failures="0" errors="0">
2+
<testsuite name="Cucumber" time="0.005" tests="1" skipped="0" failures="0" errors="0" timestamp="1970-01-01T00:00:00Z">
33
<testcase classname="minimal" name="cukes" time="0.003">
44
<system-out><![CDATA[
55
Given I have 42 cukes in my belly...........................................passed

testdata/parameter-types.feature.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<testsuite name="Cucumber" time="0.005" tests="1" skipped="0" failures="0" errors="0">
2+
<testsuite name="Cucumber" time="0.005" tests="1" skipped="0" failures="0" errors="0" timestamp="1970-01-01T00:00:00Z">
33
<testcase classname="Parameter Types" name="Flight transformer" time="0.003">
44
<system-out><![CDATA[
55
Given LHR-CDG has been delayed..............................................passed

testdata/pending.feature.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<testsuite name="Cucumber" time="0.017" tests="3" skipped="0" failures="3" errors="0">
2+
<testsuite name="Cucumber" time="0.017" tests="3" skipped="0" failures="3" errors="0" timestamp="1970-01-01T00:00:00Z">
33
<testcase classname="Pending steps" name="Unimplemented step signals pending status" time="0.003">
44
<failure>
55
<![CDATA[TODO]]>

testdata/retry.feature.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<testsuite name="Cucumber" time="0.041" tests="10" skipped="0" failures="7" errors="0">
2+
<testsuite name="Cucumber" time="0.041" tests="10" skipped="0" failures="7" errors="0" timestamp="1970-01-01T00:00:00Z">
33
<testcase classname="Retry" name="Test cases that pass aren't retried" time="0.003">
44
<system-out><![CDATA[
55
Given a step that always passes.............................................passed

testdata/rules.feature.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<testsuite name="Cucumber" time="0.031" tests="3" skipped="0" failures="0" errors="0">
2+
<testsuite name="Cucumber" time="0.031" tests="3" skipped="0" failures="0" errors="0" timestamp="1970-01-01T00:00:00Z">
33
<testcase classname="Usage of a `Rule`" name="A sale cannot happen if the customer does not have enough money - Not enough money" time="0.009">
44
<system-out><![CDATA[
55
Given the customer has 100 cents............................................passed

testdata/skipped.feature.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<testsuite name="Cucumber" time="0.019" tests="3" skipped="3" failures="0" errors="0">
2+
<testsuite name="Cucumber" time="0.019" tests="3" skipped="3" failures="0" errors="0" timestamp="1970-01-01T00:00:00Z">
33
<testcase classname="Skipping scenarios" name="Skipping from a Before hook" time="0.005">
44
<skipped/>
55
<system-out><![CDATA[

testdata/stack-traces.feature.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<testsuite name="Cucumber" time="0.005" tests="1" skipped="0" failures="1" errors="0">
2+
<testsuite name="Cucumber" time="0.005" tests="1" skipped="0" failures="1" errors="0" timestamp="1970-01-01T00:00:00Z">
33
<testcase classname="Stack traces" name="A failing step" time="0.003">
44
<failure type="Error" message="BOOM">
55
<![CDATA[BOOM

testdata/undefined.feature.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<testsuite name="Cucumber" time="0.017" tests="3" skipped="0" failures="3" errors="0">
2+
<testsuite name="Cucumber" time="0.017" tests="3" skipped="0" failures="3" errors="0" timestamp="1970-01-01T00:00:00Z">
33
<testcase classname="Undefined steps" name="An undefined step causes a failure" time="0.003">
44
<failure/>
55
<system-out><![CDATA[

testdata/unknown-parameter-type.feature.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<testsuite name="Cucumber" time="0.005" tests="1" skipped="0" failures="1" errors="0">
2+
<testsuite name="Cucumber" time="0.005" tests="1" skipped="0" failures="1" errors="0" timestamp="1970-01-01T00:00:00Z">
33
<testcase classname="Parameter Types" name="undefined parameter type" time="0.003">
44
<failure/>
55
<system-out><![CDATA[

0 commit comments

Comments
 (0)