Skip to content

Commit d493a9b

Browse files
committed
test_runner: count nested tests
1 parent 334bb17 commit d493a9b

17 files changed

+142
-82
lines changed

lib/internal/test_runner/reporter/tap.js

+1
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ function reportDetails(nesting, data = kEmptyObject) {
113113
let details = `${_indent} ---\n`;
114114

115115
details += jsToYaml(_indent, 'duration_ms', duration_ms);
116+
details += jsToYaml(_indent, 'type', data.type);
116117
details += jsToYaml(_indent, null, error);
117118
details += `${_indent} ...\n`;
118119
return details;

lib/internal/test_runner/runner.js

+17-14
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ const {
1313
FunctionPrototypeCall,
1414
Number,
1515
ObjectAssign,
16-
ObjectKeys,
1716
PromisePrototypeThen,
1817
SafePromiseAll,
1918
SafePromiseAllReturnVoid,
@@ -151,10 +150,14 @@ function getRunArgs({ path, inspectPort }) {
151150

152151
class FileTest extends Test {
153152
#buffer = [];
154-
#counters = { __proto__: null, all: 0, failed: 0, passed: 0, cancelled: 0, skipped: 0, todo: 0, totalFailed: 0 };
153+
#reportedChildren = 0;
155154
failedSubtests = false;
155+
omitFromTopLevelCounters = true;
156156
#skipReporting() {
157-
return this.#counters.all > 0 && (!this.error || this.error.failureType === kSubtestsFailed);
157+
return this.#reportedChildren > 0 && (!this.error || this.error.failureType === kSubtestsFailed);
158+
}
159+
get omitFromCounters() {
160+
return this.#skipReporting();
158161
}
159162
#checkNestedComment({ comment }) {
160163
const firstSpaceIndex = StringPrototypeIndexOf(comment, ' ');
@@ -204,11 +207,19 @@ class FileTest extends Test {
204207
const method = pass ? 'ok' : 'fail';
205208
this.reporter[method](nesting, this.name, testNumber, node.description, diagnostics, directive);
206209
if (nesting === 0) {
207-
FunctionPrototypeCall(super.countSubtest,
208-
{ finished: true, skipped: skip, isTodo: todo, passed: pass, cancelled },
209-
this.#counters);
210210
this.failedSubtests ||= !pass;
211211
}
212+
this.#reportedChildren++;
213+
FunctionPrototypeCall(super.countCompleted, {
214+
name: node.description,
215+
finished: true,
216+
skipped: skip,
217+
isTodo: todo,
218+
passed: pass,
219+
cancelled,
220+
nesting,
221+
omitFromCounters: diagnostics.type === 'suite',
222+
});
212223
break;
213224

214225
}
@@ -233,14 +244,6 @@ class FileTest extends Test {
233244
this.reportStarted();
234245
this.#handleReportItem(ast);
235246
}
236-
countSubtest(counters) {
237-
if (this.#counters.all === 0) {
238-
return super.countSubtest(counters);
239-
}
240-
ArrayPrototypeForEach(ObjectKeys(counters), (key) => {
241-
counters[key] += this.#counters[key];
242-
});
243-
}
244247
reportStarted() {}
245248
report() {
246249
const skipReporting = this.#skipReporting();

lib/internal/test_runner/test.js

+52-27
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ const kUnwrapErrors = new SafeSet()
7171
.add('uncaughtException').add('unhandledRejection');
7272
const { testNamePatterns, testOnlyFlag } = parseCommandLine();
7373

74+
const completedTests = new SafeSet();
75+
7476
function stopTest(timeout, signal) {
7577
if (timeout === kDefaultTimeout) {
7678
return once(signal, 'abort');
@@ -575,31 +577,11 @@ class Test extends AsyncResource {
575577
this.postRun();
576578
}
577579

578-
countSubtest(counters) {
579-
// Check SKIP and TODO tests first, as those should not be counted as
580-
// failures.
581-
if (this.skipped) {
582-
counters.skipped++;
583-
} else if (this.isTodo) {
584-
counters.todo++;
585-
} else if (this.cancelled) {
586-
counters.cancelled++;
587-
} else if (!this.passed) {
588-
counters.failed++;
589-
} else {
590-
counters.passed++;
591-
}
592-
593-
if (!this.passed) {
594-
counters.totalFailed++;
595-
}
596-
counters.all++;
580+
countCompleted() {
581+
completedTests.add(this);
597582
}
598583

599584
postRun(pendingSubtestsError) {
600-
const counters = {
601-
__proto__: null, all: 0, failed: 0, passed: 0, cancelled: 0, skipped: 0, todo: 0, totalFailed: 0,
602-
};
603585
// If the test was failed before it even started, then the end time will
604586
// be earlier than the start time. Correct that here.
605587
if (this.endTime < this.startTime) {
@@ -610,19 +592,22 @@ class Test extends AsyncResource {
610592
// The test has run, so recursively cancel any outstanding subtests and
611593
// mark this test as failed if any subtests failed.
612594
this.pendingSubtests = [];
595+
let failed = 0;
613596
for (let i = 0; i < this.subtests.length; i++) {
614597
const subtest = this.subtests[i];
615598

616599
if (!subtest.finished) {
617600
subtest.#cancel(pendingSubtestsError);
618601
subtest.postRun(pendingSubtestsError);
619602
}
620-
subtest.countSubtest(counters);
603+
if (!subtest.passed) {
604+
failed++;
605+
}
621606
}
622607

623-
if ((this.passed || this.parent === null) && counters.totalFailed > 0) {
624-
const subtestString = `subtest${counters.totalFailed > 1 ? 's' : ''}`;
625-
const msg = `${counters.totalFailed} ${subtestString} failed`;
608+
if ((this.passed || this.parent === null) && failed > 0) {
609+
const subtestString = `subtest${failed > 1 ? 's' : ''}`;
610+
const msg = `${failed} ${subtestString} failed`;
626611

627612
this.fail(new ERR_TEST_FAILURE(msg, kSubtestsFailed));
628613
}
@@ -635,9 +620,43 @@ class Test extends AsyncResource {
635620
this.parent.addReadySubtest(this);
636621
this.parent.processReadySubtestRange(false);
637622
this.parent.processPendingSubtests();
623+
this.countCompleted();
638624
} else if (!this.reported) {
639625
this.reported = true;
640-
this.reporter.plan(this.nesting, kFilename, counters.all);
626+
627+
const counters = {
628+
__proto__: null,
629+
all: 0,
630+
failed: 0,
631+
passed: 0,
632+
cancelled: 0,
633+
skipped: 0,
634+
todo: 0,
635+
topLevel: 0,
636+
};
637+
completedTests.forEach((test) => {
638+
if (test.nesting === this.nesting && !test.omitFromTopLevelCounters) {
639+
counters.topLevel++;
640+
}
641+
if (test.omitFromCounters) {
642+
return;
643+
}
644+
// Check SKIP and TODO tests first, as those should not be counted as
645+
// failures.
646+
if (test.skipped) {
647+
counters.skipped++;
648+
} else if (test.isTodo) {
649+
counters.todo++;
650+
} else if (test.cancelled) {
651+
counters.cancelled++;
652+
} else if (!test.passed) {
653+
counters.failed++;
654+
} else {
655+
counters.passed++;
656+
}
657+
counters.all++;
658+
});
659+
this.reporter.plan(this.nesting, kFilename, counters.topLevel);
641660

642661
for (let i = 0; i < this.diagnostics.length; i++) {
643662
this.reporter.diagnostic(this.nesting, kFilename, this.diagnostics[i]);
@@ -703,6 +722,10 @@ class Test extends AsyncResource {
703722
directive = this.reporter.getTodo(this.message);
704723
}
705724

725+
if (this.reportedType) {
726+
details.type = this.reportedType;
727+
}
728+
706729
if (this.passed) {
707730
this.reporter.ok(this.nesting, kFilename, this.testNumber, this.name, details, directive);
708731
} else {
@@ -746,6 +769,8 @@ class TestHook extends Test {
746769
}
747770

748771
class Suite extends Test {
772+
omitFromCounters = true;
773+
reportedType = 'suite';
749774
constructor(options) {
750775
super(options);
751776

test/message/test_runner_abort.out

+3-3
Original file line numberDiff line numberDiff line change
@@ -260,10 +260,10 @@ not ok 4 - callback abort signal
260260
*
261261
...
262262
1..4
263-
# tests 4
264-
# pass 0
263+
# tests 22
264+
# pass 8
265265
# fail 0
266-
# cancelled 4
266+
# cancelled 14
267267
# skipped 0
268268
# todo 0
269269
# duration_ms *

test/message/test_runner_abort_suite.out

+5-3
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ TAP version 13
6464
not ok 1 - describe timeout signal
6565
---
6666
duration_ms: *
67+
type: 'suite'
6768
failureType: 'testAborted'
6869
error: 'The operation was aborted due to timeout'
6970
code: 23
@@ -78,6 +79,7 @@ not ok 1 - describe timeout signal
7879
not ok 2 - describe abort signal
7980
---
8081
duration_ms: *
82+
type: 'suite'
8183
failureType: 'testAborted'
8284
error: 'This operation was aborted'
8385
code: 20
@@ -94,10 +96,10 @@ not ok 2 - describe abort signal
9496
*
9597
...
9698
1..2
97-
# tests 2
98-
# pass 0
99+
# tests 9
100+
# pass 4
99101
# fail 0
100-
# cancelled 2
102+
# cancelled 5
101103
# skipped 0
102104
# todo 0
103105
# duration_ms *

test/message/test_runner_describe_it.out

+13-4
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ ok 21 - immediate resolve pass
210210
not ok 22 - subtest sync throw fail
211211
---
212212
duration_ms: *
213+
type: 'suite'
213214
failureType: 'subtestsFailed'
214215
error: '1 subtest failed'
215216
code: 'ERR_TEST_FAILURE'
@@ -247,11 +248,13 @@ not ok 23 - sync throw non-error fail
247248
ok 24 - level 0a
248249
---
249250
duration_ms: *
251+
type: 'suite'
250252
...
251253
# Subtest: invalid subtest - pass but subtest fails
252254
ok 25 - invalid subtest - pass but subtest fails
253255
---
254256
duration_ms: *
257+
type: 'suite'
255258
...
256259
# Subtest: sync skip option
257260
ok 26 - sync skip option # SKIP
@@ -494,6 +497,7 @@ not ok 53 - custom inspect symbol that throws fail
494497
not ok 54 - subtest sync throw fails
495498
---
496499
duration_ms: *
500+
type: 'suite'
497501
failureType: 'subtestsFailed'
498502
error: '2 subtests failed'
499503
code: 'ERR_TEST_FAILURE'
@@ -511,6 +515,7 @@ not ok 54 - subtest sync throw fails
511515
not ok 55 - describe sync throw fails
512516
---
513517
duration_ms: *
518+
type: 'suite'
514519
failureType: 'testCodeFailure'
515520
error: 'thrown from describe'
516521
code: 'ERR_TEST_FAILURE'
@@ -539,6 +544,7 @@ not ok 55 - describe sync throw fails
539544
not ok 56 - describe async throw fails
540545
---
541546
duration_ms: *
547+
type: 'suite'
542548
failureType: 'testCodeFailure'
543549
error: 'thrown from describe'
544550
code: 'ERR_TEST_FAILURE'
@@ -587,6 +593,7 @@ not ok 56 - describe async throw fails
587593
not ok 57 - timeouts
588594
---
589595
duration_ms: *
596+
type: 'suite'
590597
failureType: 'subtestsFailed'
591598
error: '2 subtests failed'
592599
code: 'ERR_TEST_FAILURE'
@@ -612,6 +619,7 @@ not ok 57 - timeouts
612619
not ok 58 - successful thenable
613620
---
614621
duration_ms: *
622+
type: 'suite'
615623
failureType: 'subtestsFailed'
616624
error: '1 subtest failed'
617625
code: 'ERR_TEST_FAILURE'
@@ -620,6 +628,7 @@ not ok 58 - successful thenable
620628
not ok 59 - rejected thenable
621629
---
622630
duration_ms: *
631+
type: 'suite'
623632
failureType: 'testCodeFailure'
624633
error: 'custom error'
625634
code: 'ERR_TEST_FAILURE'
@@ -643,10 +652,10 @@ not ok 60 - invalid subtest fail
643652
# Warning: Test "immediate reject - passes but warns" generated asynchronous activity after the test ended. This activity created the error "Error: rejected from immediate reject fail" and would have caused the test to fail, but instead triggered an unhandledRejection event.
644653
# Warning: Test "callback called twice in different ticks" generated asynchronous activity after the test ended. This activity created the error "Error [ERR_TEST_FAILURE]: callback invoked multiple times" and would have caused the test to fail, but instead triggered an uncaughtException event.
645654
# Warning: Test "callback async throw after done" generated asynchronous activity after the test ended. This activity created the error "Error: thrown from callback async throw after done" and would have caused the test to fail, but instead triggered an uncaughtException event.
646-
# tests 60
647-
# pass 23
648-
# fail 22
649-
# cancelled 0
655+
# tests 67
656+
# pass 29
657+
# fail 19
658+
# cancelled 4
650659
# skipped 10
651660
# todo 5
652661
# duration_ms *

test/message/test_runner_describe_nested.out

+2
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ TAP version 13
1010
ok 1 - nested
1111
---
1212
duration_ms: *
13+
type: 'suite'
1314
...
1415
1..1
1516
ok 1 - nested - no tests
1617
---
1718
duration_ms: *
19+
type: 'suite'
1820
...
1921
1..1
2022
# tests 1

0 commit comments

Comments
 (0)