Skip to content

Commit 260cf1a

Browse files
Han5991aduh95
authored andcommitted
test_runner: add timestamp to JUnit reporter testsuites
Emit the standard JUnit timestamp (ISO 8601) on <testsuite> elements, which the reporter was omitting. The suite start time is reconstructed as end-minus-duration, because the runner reports a suite's test:start lazily (when its first subtest reports), which would otherwise record a time close to the suite's end. Fixes: #64028 Signed-off-by: sangwook <rewq5991@gmail.com> PR-URL: #64029 Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Jacob Smith <jacob@frende.me>
1 parent 3a0549d commit 260cf1a

4 files changed

Lines changed: 21 additions & 6 deletions

File tree

‎lib/internal/test_runner/reporter/junit.js‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ const {
55
ArrayPrototypeMap,
66
ArrayPrototypePush,
77
ArrayPrototypeSome,
8+
Date,
9+
DateNow,
10+
DatePrototypeToISOString,
811
NumberPrototypeToFixed,
912
ObjectEntries,
1013
RegExpPrototypeSymbolReplace,
@@ -112,6 +115,11 @@ module.exports = async function* junitReporter(source) {
112115
currentTest.attrs.tests=nonCommentChildren.length;
113116
currentTest.attrs.failures=ArrayPrototypeFilter(currentTest.children,isFailure).length;
114117
currentTest.attrs.skipped=ArrayPrototypeFilter(currentTest.children,isSkipped).length;
118+
// A suite's `test:start` is emitted lazily (when its first subtest
119+
// reports), so derive the start time from the end minus the measured
120+
// duration rather than stamping the (late) test:start moment.
121+
currentTest.attrs.timestamp=
122+
DatePrototypeToISOString(newDate(DateNow()-event.data.details.duration_ms));
115123
currentTest.attrs.hostname=HOSTNAME;
116124
}else{
117125
currentTest.tag='testcase';

‎test/common/assertSnapshot.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ function replaceJunitDuration(str) {
226226
.replaceAll(/time="[0-9.]+"/g,'time="*"')
227227
.replaceAll(/duration_ms[0-9.]+/g,'duration_ms *')
228228
.replaceAll(`hostname="${hostname()}"`,'hostname="HOSTNAME"')
229+
.replaceAll(/timestamp="[^"]*"/g,'timestamp="*"')
229230
.replaceAll(/file="[^"]*"/g,'file="*"');
230231
}
231232

‎test/fixtures/test-runner/output/junit_reporter.snapshot‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ true !== false
128128
<testcasename="immediate throw - passes but warns"time="*"classname="test"file="*"/>
129129
<testcasename="immediate reject - passes but warns"time="*"classname="test"file="*"/>
130130
<testcasename="immediate resolve pass"time="*"classname="test"file="*"/>
131-
<testsuitename="subtest sync throw fail"time="*"disabled="0"errors="0"tests="1"failures="1"skipped="0"hostname="HOSTNAME">
131+
<testsuitename="subtest sync throw fail"time="*"disabled="0"errors="0"tests="1"failures="1"skipped="0"timestamp="*"hostname="HOSTNAME">
132132
<testcasename="+sync throw fail"time="*"classname="test"file="*"failure="thrown from subtest sync throw fail">
133133
<failuretype="testCodeFailure"message="thrown from subtest sync throw fail">
134134
Error [ERR_TEST_FAILURE]: thrown from subtest sync throw fail
@@ -151,15 +151,15 @@ Error [ERR_TEST_FAILURE]: thrown from subtest sync throw fail
151151
[Error [ERR_TEST_FAILURE]: Symbol(thrown symbol from sync throw non-error fail)] { code: 'ERR_TEST_FAILURE', failureType: 'testCodeFailure', cause: Symbol(thrown symbol from sync throw non-error fail) }
152152
</failure>
153153
</testcase>
154-
<testsuitename="level 0a"time="*"disabled="0"errors="0"tests="4"failures="0"skipped="0"hostname="HOSTNAME">
154+
<testsuitename="level 0a"time="*"disabled="0"errors="0"tests="4"failures="0"skipped="0"timestamp="*"hostname="HOSTNAME">
155155
<testcasename="level 1a"time="*"classname="test"file="*"/>
156156
<testcasename="level 1b"time="*"classname="test"file="*"/>
157157
<testcasename="level 1c"time="*"classname="test"file="*"/>
158158
<testcasename="level 1d"time="*"classname="test"file="*"/>
159159
</testsuite>
160-
<testsuitename="top level"time="*"disabled="0"errors="0"tests="2"failures="0"skipped="0"hostname="HOSTNAME">
160+
<testsuitename="top level"time="*"disabled="0"errors="0"tests="2"failures="0"skipped="0"timestamp="*"hostname="HOSTNAME">
161161
<testcasename="+long running"time="*"classname="test"file="*"/>
162-
<testsuitename="+short running"time="*"disabled="0"errors="0"tests="1"failures="0"skipped="0"hostname="HOSTNAME">
162+
<testsuitename="+short running"time="*"disabled="0"errors="0"tests="1"failures="0"skipped="0"timestamp="*"hostname="HOSTNAME">
163163
<testcasename="++short running"time="*"classname="test"file="*"/>
164164
</testsuite>
165165
</testsuite>
@@ -266,7 +266,7 @@ Error [ERR_TEST_FAILURE]: thrown from callback async throw
266266
</failure>
267267
</testcase>
268268
<testcasename="callback async throw after done"time="*"classname="test"file="*"/>
269-
<testsuitename="only is set on subtests but not in only mode"time="*"disabled="0"errors="0"tests="3"failures="0"skipped="0"hostname="HOSTNAME">
269+
<testsuitename="only is set on subtests but not in only mode"time="*"disabled="0"errors="0"tests="3"failures="0"skipped="0"timestamp="*"hostname="HOSTNAME">
270270
<testcasename="running subtest 1"time="*"classname="test"file="*"/>
271271
<testcasename="running subtest 3"time="*"classname="test"file="*"/>
272272
<testcasename="running subtest 4"time="*"classname="test"file="*"/>
@@ -288,7 +288,7 @@ Error [ERR_TEST_FAILURE]: thrown from callback async throw
288288
}
289289
</failure>
290290
</testcase>
291-
<testsuitename="subtest sync throw fails"time="*"disabled="0"errors="0"tests="2"failures="2"skipped="0"hostname="HOSTNAME">
291+
<testsuitename="subtest sync throw fails"time="*"disabled="0"errors="0"tests="2"failures="2"skipped="0"timestamp="*"hostname="HOSTNAME">
292292
<testcasename="sync throw fails at first"time="*"classname="test"file="*"failure="thrown from subtest sync throw fails at first">
293293
<failuretype="testCodeFailure"message="thrown from subtest sync throw fails at first">
294294
Error [ERR_TEST_FAILURE]: thrown from subtest sync throw fails at first

‎test/parallel/test-runner-reporters.js‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,12 @@ describe('node:test reporters', { concurrency: true }, () => {
200200
assert.strictEqual(child.stdout.toString(),'');
201201
constfileContents=fs.readFileSync(file,'utf8');
202202
assert.match(fileContents,/<testsuite.*name="nested".*tests="2".*failures="1".*skipped="0".*>/);
203+
// The exact timestamp format is intentionally not pinned here (still under
204+
// discussion); assert only that the value is present and a real date.
205+
const{1: timestamp}=fileContents.match(/<testsuite[^>]*timestamp="([^"]+)"/)??[];
206+
assert.ok(timestamp,'testsuite should have a timestamp attribute');
207+
assert.match(timestamp,/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
208+
assert.ok(!Number.isNaN(Date.parse(timestamp)),`expected a valid date, got ${timestamp}`);
203209
assert.match(fileContents,/<testcase.*name="failing".*>\s*<failure.*type="testCodeFailure".*message="error".*>/);
204210
assert.match(fileContents,/<testcase.*name="ok".*classname="test".*\/>/);
205211
assert.match(fileContents,/<testcase.*name="toplevel".*classname="test".*\/>/);

0 commit comments

Comments
 (0)