Skip to content

Add timestamp attribute #45

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Nov 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- Add `timestamp` attribute ((#45)[https://github.com/cucumber/junit-xml-formatter/pull/45])

## [0.6.0] - 2024-11-15
### Added
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ JUnit XML Formatter

Writes Cucumber message into a JUnit XML report.

The JUnit XML report is a loose standard. We validate it against the
[Jenkins JUnit XML XSD](./jenkins-junit.xsd) so there should be a good
chance your CI will understand it.
The JUnit XML report is
[a de facto standard without an official specification](https://github.com/testmoapp/junitxml/tree/main).
But we validate it against the [Jenkins JUnit XML XSD](./jenkins-junit.xsd) so
there should be a good chance your CI will understand it.

If not, please let us know in the issues!

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package io.cucumber.junitxmlformatter;

import io.cucumber.messages.Convertor;
import io.cucumber.messages.types.Envelope;
import io.cucumber.messages.types.Feature;
import io.cucumber.messages.types.Pickle;
import io.cucumber.messages.types.PickleStep;
import io.cucumber.messages.types.Step;
import io.cucumber.messages.types.TestCaseStarted;
import io.cucumber.messages.types.TestRunStarted;
import io.cucumber.messages.types.TestStep;
import io.cucumber.messages.types.TestStepFinished;
import io.cucumber.messages.types.TestStepResult;
Expand All @@ -22,6 +24,7 @@
import java.util.Optional;

import static io.cucumber.messages.types.TestStepResultStatus.PASSED;
import static java.time.format.DateTimeFormatter.ISO_INSTANT;
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.stream.Collectors.toList;

Expand Down Expand Up @@ -125,4 +128,10 @@ TestStepResult getTestCaseStatus(TestCaseStarted testCaseStarted) {
.orElse(SCENARIO_WITH_NO_STEPS);
}

}
public Optional<String> getTestRunStartedAt() {
return query.findTestRunStarted()
.map(TestRunStarted::getTimestamp)
.map(Convertor::toInstant)
.map(ISO_INSTANT::format);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ private void writeSuiteAttributes(EscapingXmlStreamWriter writer) throws XMLStre
writer.writeAttribute("skipped", counts.get(SKIPPED).toString());
writer.writeAttribute("failures", String.valueOf(countFailures(counts)));
writer.writeAttribute("errors", "0");

Optional<String> testRunStartedAt = data.getTestRunStartedAt();
if (testRunStartedAt.isPresent()) {
writer.writeAttribute("timestamp", testRunStartedAt.get());
}
}

private static long countFailures(Map<TestStepResultStatus, Long> counts) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.xmlunit.assertj.XmlAssert;
import org.xmlunit.builder.Input;
import org.xmlunit.validation.JAXPValidator;
import org.xmlunit.validation.Languages;
Expand All @@ -20,6 +21,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
Expand Down Expand Up @@ -76,11 +78,16 @@ void validateAgainstSurefire(TestCase testCase) throws IOException {
ByteArrayOutputStream bytes = writeJunitXmlReport(testCase, new ByteArrayOutputStream());
Source actual = Input.fromByteArray(bytes.toByteArray()).build();
Source surefireSchema = Input.fromPath(Paths.get("../surefire-test-report-3.0.xsd")).build();
if (!testCasesWithMissingException.contains(testCase.name)) {
assertThat(actual).isValidAgainst(surefireSchema);
return;
}

JAXPValidator validator = new JAXPValidator(Languages.W3C_XML_SCHEMA_NS_URI);
validator.setSchemaSource(surefireSchema);
ValidationResult validationResult = validator.validateInstance(actual);

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

@ParameterizedTest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ void it_writes_two_messages_to_xml() throws IOException {

assertThat(html).isEqualTo("" +
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<testsuite name=\"Cucumber\" time=\"20\" tests=\"0\" skipped=\"0\" failures=\"0\" errors=\"0\">\n" +
"<testsuite name=\"Cucumber\" time=\"20\" tests=\"0\" skipped=\"0\" failures=\"0\" errors=\"0\" timestamp=\"1970-01-01T00:00:10Z\">\n" +
"</testsuite>\n"
);
}
Expand Down
18 changes: 18 additions & 0 deletions javascript/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions javascript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"dependencies": {
"@cucumber/query": "^13.0.2",
"@teppeis/multimaps": "^3.0.0",
"luxon": "^3.5.0",
"xmlbuilder": "^15.1.1"
},
"peerDependencies": {
Expand All @@ -35,6 +36,7 @@
"@types/chai": "^4.3.11",
"@types/chai-almost": "^1.0.3",
"@types/chai-xml": "^0.3.6",
"@types/luxon": "^3.4.2",
"@types/mocha": "^10.0.6",
"@types/node": "18.11.18",
"@typescript-eslint/eslint-plugin": "5.48.1",
Expand Down
12 changes: 12 additions & 0 deletions javascript/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import {
Duration,
PickleStep,
Step,
TestRunStarted,
TestStepResultStatus,
TimeConversion,
} from '@cucumber/messages'
import { DateTime } from 'luxon'

export function durationToSeconds(duration?: Duration) {
if (!duration) {
Expand All @@ -29,3 +31,13 @@ export function formatStep(step: Step, pickleStep: PickleStep, status: TestStepR
} while (text.length < 76)
return text + status.toLowerCase()
}

export function formatTimestamp(testRunStarted: TestRunStarted | undefined) {
if (!testRunStarted) {
return undefined
}
const millis = TimeConversion.timestampToMillisecondsSinceEpoch(testRunStarted.timestamp)
return DateTime.fromMillis(millis, { zone: 'UTC' }).toISO({
suppressMilliseconds: true,
}) as string
}
4 changes: 4 additions & 0 deletions javascript/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ export default {
builder.att('failures', testSuite.failures)
builder.att('errors', testSuite.errors)

if (testSuite.timestamp) {
builder.att('timestamp', testSuite.timestamp)
}

for (const testCase of testSuite.testCases) {
const testcaseElement = builder.ele('testcase', {
classname: testCase.classname,
Expand Down
4 changes: 3 additions & 1 deletion javascript/src/makeReport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
Query,
} from '@cucumber/query'

import { countStatuses, durationToSeconds, formatStep } from './helpers.js'
import { countStatuses, durationToSeconds, formatStep, formatTimestamp } from './helpers.js'

const NAMING_STRATEGY = namingStrategy(
NamingStrategyLength.LONG,
Expand All @@ -24,6 +24,7 @@ interface ReportSuite {
failures: number
errors: number
testCases: ReadonlyArray<ReportTestCase>
timestamp?: string
}

interface ReportTestCase {
Expand Down Expand Up @@ -53,6 +54,7 @@ export function makeReport(query: Query): ReportSuite {
),
errors: 0,
testCases: makeTestCases(query),
timestamp: formatTimestamp(query.findTestRunStarted())
}
}

Expand Down
2 changes: 1 addition & 1 deletion testdata/attachments.feature.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="Cucumber" time="0.045" tests="11" skipped="0" failures="0" errors="0">
<testsuite name="Cucumber" time="0.045" tests="11" skipped="0" failures="0" errors="0" timestamp="1970-01-01T00:00:00Z">
<testcase classname="Attachments" name="Strings can be attached with a media type" time="0.003">
<system-out><![CDATA[
When the string "hello" is attached as "application/octet-stream"...........passed
Expand Down
2 changes: 1 addition & 1 deletion testdata/cdata.feature.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="Cucumber" time="0.005" tests="1" skipped="0" failures="0" errors="0">
<testsuite name="Cucumber" time="0.005" tests="1" skipped="0" failures="0" errors="0" timestamp="1970-01-01T00:00:00Z">
<testcase classname="cdata" name="cdata" time="0.003">
<system-out><![CDATA[
Given I have 42 <![CDATA[cukes]]]]><![CDATA[> in my belly...............................passed
Expand Down
2 changes: 1 addition & 1 deletion testdata/data-tables.feature.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="Cucumber" time="0.007" tests="1" skipped="0" failures="0" errors="0">
<testsuite name="Cucumber" time="0.007" tests="1" skipped="0" failures="0" errors="0" timestamp="1970-01-01T00:00:00Z">
<testcase classname="Data Tables" name="transposed table" time="0.005">
<system-out><![CDATA[
When the following table is transposed:.....................................passed
Expand Down
2 changes: 1 addition & 1 deletion testdata/empty.feature.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="Cucumber" time="0.003" tests="1" skipped="0" failures="0" errors="0">
<testsuite name="Cucumber" time="0.003" tests="1" skipped="0" failures="0" errors="0" timestamp="1970-01-01T00:00:00Z">
<testcase classname="Empty Scenarios" name="Blank Scenario" time="0.001">
</testcase>
</testsuite>
2 changes: 1 addition & 1 deletion testdata/examples-tables.feature.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="Cucumber" time="0.073" tests="9" skipped="0" failures="4" errors="0">
<testsuite name="Cucumber" time="0.073" tests="9" skipped="0" failures="4" errors="0" timestamp="1970-01-01T00:00:00Z">
<testcase classname="Examples Tables" name="Eating cucumbers - These are passing - #1.1" time="0.007">
<system-out><![CDATA[
Given there are 12 cucumbers................................................passed
Expand Down
2 changes: 1 addition & 1 deletion testdata/hooks.feature.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="Cucumber" time="0.055" tests="5" skipped="0" failures="3" errors="0">
<testsuite name="Cucumber" time="0.055" tests="5" skipped="0" failures="3" errors="0" timestamp="1970-01-01T00:00:00Z">
<testcase classname="Hooks" name="No tags and a passed step" time="0.009">
<system-out><![CDATA[
When a step passes..........................................................passed
Expand Down
2 changes: 1 addition & 1 deletion testdata/markdown.feature.md.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="Cucumber" time="0.021" tests="2" skipped="0" failures="1" errors="0">
<testsuite name="Cucumber" time="0.021" tests="2" skipped="0" failures="1" errors="0" timestamp="1970-01-01T00:00:00Z">
<testcase classname="Cheese" name="Nom nom nom - Ylajali! - because we need more tables - #1.1" time="0.009">
<failure type="Error" message="You asked me to fail">
<![CDATA[You asked me to fail
Expand Down
2 changes: 1 addition & 1 deletion testdata/minimal.feature.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="Cucumber" time="0.005" tests="1" skipped="0" failures="0" errors="0">
<testsuite name="Cucumber" time="0.005" tests="1" skipped="0" failures="0" errors="0" timestamp="1970-01-01T00:00:00Z">
<testcase classname="minimal" name="cukes" time="0.003">
<system-out><![CDATA[
Given I have 42 cukes in my belly...........................................passed
Expand Down
2 changes: 1 addition & 1 deletion testdata/parameter-types.feature.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="Cucumber" time="0.005" tests="1" skipped="0" failures="0" errors="0">
<testsuite name="Cucumber" time="0.005" tests="1" skipped="0" failures="0" errors="0" timestamp="1970-01-01T00:00:00Z">
<testcase classname="Parameter Types" name="Flight transformer" time="0.003">
<system-out><![CDATA[
Given LHR-CDG has been delayed..............................................passed
Expand Down
2 changes: 1 addition & 1 deletion testdata/pending.feature.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="Cucumber" time="0.017" tests="3" skipped="0" failures="3" errors="0">
<testsuite name="Cucumber" time="0.017" tests="3" skipped="0" failures="3" errors="0" timestamp="1970-01-01T00:00:00Z">
<testcase classname="Pending steps" name="Unimplemented step signals pending status" time="0.003">
<failure>
<![CDATA[TODO]]>
Expand Down
2 changes: 1 addition & 1 deletion testdata/retry.feature.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="Cucumber" time="0.041" tests="5" skipped="0" failures="2" errors="0">
<testsuite name="Cucumber" time="0.041" tests="5" skipped="0" failures="2" errors="0" timestamp="1970-01-01T00:00:00Z">
<testcase classname="Retry" name="Test cases that pass aren't retried" time="0.003">
<system-out><![CDATA[
Given a step that always passes.............................................passed
Expand Down
2 changes: 1 addition & 1 deletion testdata/rules.feature.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="Cucumber" time="0.031" tests="3" skipped="0" failures="0" errors="0">
<testsuite name="Cucumber" time="0.031" tests="3" skipped="0" failures="0" errors="0" timestamp="1970-01-01T00:00:00Z">
<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">
<system-out><![CDATA[
Given the customer has 100 cents............................................passed
Expand Down
2 changes: 1 addition & 1 deletion testdata/skipped.feature.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="Cucumber" time="0.019" tests="3" skipped="3" failures="0" errors="0">
<testsuite name="Cucumber" time="0.019" tests="3" skipped="3" failures="0" errors="0" timestamp="1970-01-01T00:00:00Z">
<testcase classname="Skipping scenarios" name="Skipping from a Before hook" time="0.005">
<skipped/>
<system-out><![CDATA[
Expand Down
2 changes: 1 addition & 1 deletion testdata/stack-traces.feature.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="Cucumber" time="0.005" tests="1" skipped="0" failures="1" errors="0">
<testsuite name="Cucumber" time="0.005" tests="1" skipped="0" failures="1" errors="0" timestamp="1970-01-01T00:00:00Z">
<testcase classname="Stack traces" name="A failing step" time="0.003">
<failure type="Error" message="BOOM">
<![CDATA[BOOM
Expand Down
2 changes: 1 addition & 1 deletion testdata/undefined.feature.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="Cucumber" time="0.017" tests="3" skipped="0" failures="3" errors="0">
<testsuite name="Cucumber" time="0.017" tests="3" skipped="0" failures="3" errors="0" timestamp="1970-01-01T00:00:00Z">
<testcase classname="Undefined steps" name="An undefined step causes a failure" time="0.003">
<failure/>
<system-out><![CDATA[
Expand Down
2 changes: 1 addition & 1 deletion testdata/unknown-parameter-type.feature.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="Cucumber" time="0.005" tests="1" skipped="0" failures="1" errors="0">
<testsuite name="Cucumber" time="0.005" tests="1" skipped="0" failures="1" errors="0" timestamp="1970-01-01T00:00:00Z">
<testcase classname="Parameter Types" name="undefined parameter type" time="0.003">
<failure/>
<system-out><![CDATA[
Expand Down