Skip to content

Commit 904bdf5

Browse files
MoLowsxa
authored andcommitted
test_runner: preserve run duration when using test-rerun
Signed-off-by: Moshe Atlow <moshe@atlow.co.il> PR-URL: #63429 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Chemi Atlow <chemi@atlow.co.il> Reviewed-By: Aviv Keller <me@aviv.sh>
1 parent 3f54c8b commit 904bdf5

4 files changed

Lines changed: 43 additions & 1 deletion

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ function reportReruns(previousRuns, globalOptions) {
6262
name: data.name,
6363
children,
6464
passed_on_attempt: data.details.passed_on_attempt??data.details.attempt,
65+
duration_ms: data.details.duration_ms,
6566
};
6667
}
6768
}

‎lib/internal/test_runner/test.js‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ const {
99
ArrayPrototypeSplice,
1010
ArrayPrototypeUnshift,
1111
ArrayPrototypeUnshiftApply,
12+
BigInt,
1213
Error,
1314
FunctionPrototype,
1415
MathFloor,
1516
MathMax,
17+
MathRound,
1618
Number,
1719
NumberPrototypeToFixed,
1820
ObjectKeys,
@@ -769,14 +771,25 @@ class Test extends AsyncResource {
769771
constpreviousAttempt=this.root.harness.previousRuns[this.attempt-1]?.[testIdentifier];
770772
if(previousAttempt!=null){
771773
this.passedAttempt=previousAttempt.passed_on_attempt;
774+
if(previousAttempt.duration_ms!==undefined){
775+
this.replayedDurationNs=BigInt(MathRound(previousAttempt.duration_ms*1_000_000));
776+
}
772777
this.fn=()=>{
778+
// Restore the original duration on the synthetic replay. Suites are
779+
// skipped here because Suite.run() unconditionally reassigns
780+
// startTime later; only non-suite tests benefit from setting it.
781+
if(this.reportedType!=='suite'){
782+
this.startTime=hrtime();
783+
this.endTime=this.startTime+(this.replayedDurationNs??0n);
784+
}
773785
for(leti=0;i<(previousAttempt.children?.length??0);i++){
774786
constchild=previousAttempt.children[i];
775787
constt=this.createSubtest(Test,child.name,{__proto__: null},noop,{
776788
__proto__: null,
777789
loc: [child.line,child.column,child.file],
778790
},noop);
779-
t.endTime=t.startTime=hrtime();
791+
t.startTime=hrtime();
792+
t.endTime=t.startTime+(t.replayedDurationNs??0n);
780793
// For suites, Suite.run() starts the subtests via SafePromiseAll.
781794
// Starting them here as well would run them twice, re-invoking the
782795
// synthetic children-creator against a now-incremented disambiguator
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
'use strict';
2+
// A passing test with a measurable duration alongside a failing test. The
3+
// failing test forces the rerun feature to retry on a second invocation,
4+
// causing the passing test to be replayed via the synthetic noop stub.
5+
const{ test }=require('node:test');
6+
7+
test('passing slow test',async()=>{
8+
awaitnewPromise((resolve)=>setTimeout(resolve,25));
9+
});
10+
11+
test('always failing',()=>{
12+
thrownewError('boom');
13+
});

‎test/parallel/test-runner-test-rerun-failures.js‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ const getStateFile = async () => {
8989
res.forEach((entry)=>{
9090
for(constiteminentry){
9191
deleteentry[item].children;
92+
deleteentry[item].duration_ms;
9293
}
9394
});
9495
returnres;
@@ -152,6 +153,20 @@ test('test should pass on third rerun with `--test`', async () => {
152153
assert.deepStrictEqual(awaitgetStateFile(),expectedStateFile);
153154
});
154155

156+
test('rerun preserves the original duration on the replayed pass',async()=>{
157+
constdurationFixture=fixtures.path('test-runner','rerun-duration.js');
158+
constargs=['--test-rerun-failures',stateFile,durationFixture];
159+
160+
awaitcommon.spawnPromisified(process.execPath,args);
161+
awaitcommon.spawnPromisified(process.execPath,args);
162+
163+
constraw=JSON.parse(awaitreadFile(stateFile,'utf8'));
164+
constpassKey=Object.keys(raw[0]).find((k)=>raw[0][k].name==='passing slow test');
165+
assert.ok(passKey,'expected the passing test to be recorded on attempt 0');
166+
assert.ok(raw[0][passKey].duration_ms>0,'expected a measurable duration on attempt 0');
167+
assert.strictEqual(raw[1][passKey].duration_ms,raw[0][passKey].duration_ms);
168+
});
169+
155170
test('using `run` api',async()=>{
156171
letstream=run({files: [fixture],rerunFailuresFilePath: stateFile});
157172
stream.on('test:pass',common.mustCall(19));

0 commit comments

Comments
 (0)