Skip to content

Commit d79755c

Browse files
committed
test_runner: add reporters
1 parent 2cad517 commit d79755c

File tree

10 files changed

+186
-65
lines changed

10 files changed

+186
-65
lines changed

lib/internal/main/test_runner.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const {
66
const { getOptionValue } = require('internal/options');
77
const { isUsingInspector } = require('internal/util/inspector');
88
const { run } = require('internal/test_runner/runner');
9+
const { getTestReporter } = require('internal/test_runner/utils');
910
const { exitCodes: { kGenericUserError } } = internalBinding('errors');
1011

1112
prepareMainThreadExecution(false);
@@ -21,7 +22,7 @@ if (isUsingInspector()) {
2122
inspectPort = process.debugPort;
2223
}
2324

24-
const tapStream = run({ concurrency, inspectPort, watch: getOptionValue('--watch') });
25+
const tapStream = getTestReporter(run({ concurrency, inspectPort, watch: getOptionValue('--watch') }));
2526
tapStream.pipe(process.stdout);
2627
tapStream.once('test:fail', () => {
2728
process.exitCode = kGenericUserError;

lib/internal/test_runner/harness.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const { exitCodes: { kGenericUserError } } = internalBinding('errors');
1818
const { kEmptyObject } = require('internal/util');
1919
const { getOptionValue } = require('internal/options');
2020
const { kCancelledByParent, Test, ItTest, Suite } = require('internal/test_runner/test');
21+
const { getTestReporter } = require('internal/test_runner/utils');
2122
const { bigint: hrtime } = process.hrtime;
2223

2324
const isTestRunnerCli = getOptionValue('--test');
@@ -119,8 +120,9 @@ let globalRoot;
119120
function getGlobalRoot() {
120121
if (!globalRoot) {
121122
globalRoot = createTestTree();
122-
globalRoot.reporter.pipe(process.stdout);
123-
globalRoot.reporter.once('test:fail', () => {
123+
const reporter = getTestReporter(globalRoot.reporter);
124+
reporter.pipe(process.stdout);
125+
reporter.once('test:fail', () => {
124126
process.exitCode = kGenericUserError;
125127
});
126128
}

lib/internal/test_runner/runner.js

+16-11
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ const { validateArray, validateBoolean } = require('internal/validators');
3333
const { getInspectPort, isUsingInspector, isInspectorMessage } = require('internal/util/inspector');
3434
const { kEmptyObject } = require('internal/util');
3535
const { createTestTree } = require('internal/test_runner/harness');
36-
const { kDefaultIndent, kSubtestsFailed, Test } = require('internal/test_runner/test');
36+
const { kSubtestsFailed, Test } = require('internal/test_runner/test');
3737
const { TapParser } = require('internal/test_runner/tap_parser');
38+
const { kDefaultIndent } = require('internal/test_runner/tap_stream');
3839
const { TokenKind } = require('internal/test_runner/tap_lexer');
3940

4041
const {
@@ -49,6 +50,7 @@ const {
4950
} = internalBinding('errors');
5051

5152
const kFilterArgs = ['--test', '--watch'];
53+
const kFilterArgValues = ['--test-reporter'];
5254

5355
// TODO(cjihrig): Replace this with recursive readdir once it lands.
5456
function processPath(path, testFiles, options) {
@@ -112,8 +114,10 @@ function createTestFileList() {
112114
return ArrayPrototypeSort(ArrayFrom(testFiles));
113115
}
114116

115-
function filterExecArgv(arg) {
116-
return !ArrayPrototypeIncludes(kFilterArgs, arg);
117+
function filterExecArgv(arg, i, arr) {
118+
return !ArrayPrototypeIncludes(kFilterArgs, arg) &&
119+
!ArrayPrototypeIncludes(kFilterArgValues, arg) &&
120+
!ArrayPrototypeIncludes(kFilterArgValues, arr[i - 1]);
117121
}
118122

119123
function getRunArgs({ path, inspectPort }) {
@@ -128,9 +132,10 @@ function getRunArgs({ path, inspectPort }) {
128132
class FileTest extends Test {
129133
#buffer = [];
130134
#handleReportItem({ kind, node, nesting = 0 }) {
131-
const indent = StringPrototypeRepeat(kDefaultIndent, nesting + 1);
135+
nesting += 1;
132136

133137
const details = (diagnostic) => {
138+
const indent = StringPrototypeRepeat(kDefaultIndent, nesting);
134139
return (
135140
diagnostic && {
136141
__proto__: null,
@@ -149,11 +154,11 @@ class FileTest extends Test {
149154
break;
150155

151156
case TokenKind.TAP_PLAN:
152-
this.reporter.plan(indent, node.end - node.start + 1);
157+
this.reporter.plan(nesting, node.end - node.start + 1);
153158
break;
154159

155160
case TokenKind.TAP_SUBTEST_POINT:
156-
this.reporter.subtest(indent, node.name);
161+
this.reporter.subtest(nesting, node.name);
157162
break;
158163

159164
case TokenKind.TAP_TEST_POINT:
@@ -172,15 +177,15 @@ class FileTest extends Test {
172177

173178
if (pass) {
174179
this.reporter.ok(
175-
indent,
180+
nesting,
176181
node.id,
177182
node.description,
178183
details(node.diagnostics),
179184
directive
180185
);
181186
} else {
182187
this.reporter.fail(
183-
indent,
188+
nesting,
184189
node.id,
185190
node.description,
186191
details(node.diagnostics),
@@ -190,15 +195,15 @@ class FileTest extends Test {
190195
break;
191196

192197
case TokenKind.COMMENT:
193-
if (indent === kDefaultIndent) {
198+
if (nesting === 1) {
194199
// Ignore file top level diagnostics
195200
break;
196201
}
197-
this.reporter.diagnostic(indent, node.comment);
202+
this.reporter.diagnostic(nesting, node.comment);
198203
break;
199204

200205
case TokenKind.UNKNOWN:
201-
this.reporter.diagnostic(indent, node.value);
206+
this.reporter.diagnostic(nesting, node.value);
202207
break;
203208
}
204209
}

lib/internal/test_runner/tap_stream.js

+54-30
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ const {
99
StringPrototypeReplaceAll,
1010
StringPrototypeToUpperCase,
1111
StringPrototypeSplit,
12+
StringPrototypeRepeat,
1213
RegExpPrototypeSymbolReplace,
1314
} = primordials;
1415
const { inspectWithNoCustomRetry } = require('internal/errors');
1516
const Readable = require('internal/streams/readable');
1617
const { isError, kEmptyObject } = require('internal/util');
18+
const kDefaultIndent = ' '; // 4 spaces
1719
const kFrameStartRegExp = /^ {4}at /;
1820
const kLineBreakRegExp = /\n|\r\n/;
1921
const kDefaultTAPVersion = 13;
@@ -30,8 +32,8 @@ class TapStream extends Readable {
3032
#buffer;
3133
#canPush;
3234

33-
constructor() {
34-
super();
35+
constructor(options = kEmptyObject) {
36+
super(options);
3537
this.#buffer = [];
3638
this.#canPush = true;
3739
}
@@ -40,34 +42,30 @@ class TapStream extends Readable {
4042
this.#canPush = true;
4143

4244
while (this.#buffer.length > 0) {
43-
const line = ArrayPrototypeShift(this.#buffer);
45+
const chunk = ArrayPrototypeShift(this.#buffer);
4446

45-
if (!this.#tryPush(line)) {
47+
if (!this.#tryPush(chunk)) {
4648
return;
4749
}
4850
}
4951
}
5052

51-
bail(message) {
52-
this.#tryPush(`Bail out!${message ? ` ${tapEscape(message)}` : ''}\n`);
53+
fail(nesting, testNumber, name, details, directive) {
54+
this.#emit('test:fail', { __proto__: null, name, nesting, testNumber, details, ...directive });
55+
this.#test(nesting, testNumber, 'not ok', name, directive);
56+
this.#details(nesting, details);
5357
}
5458

55-
fail(indent, testNumber, name, details, directive) {
56-
this.emit('test:fail', { __proto__: null, name, testNumber, details, ...directive });
57-
this.#test(indent, testNumber, 'not ok', name, directive);
58-
this.#details(indent, details);
59+
ok(nesting, testNumber, name, details, directive) {
60+
this.#emit('test:pass', { __proto__: null, name, nesting, testNumber, details, ...directive });
61+
this.#test(nesting, testNumber, 'ok', name, directive);
62+
this.#details(nesting, details);
5963
}
6064

61-
ok(indent, testNumber, name, details, directive) {
62-
this.emit('test:pass', { __proto__: null, name, testNumber, details, ...directive });
63-
this.#test(indent, testNumber, 'ok', name, directive);
64-
this.#details(indent, details);
65-
}
66-
67-
plan(indent, count, explanation) {
65+
plan(nesting, count, explanation) {
6866
const exp = `${explanation ? ` # ${tapEscape(explanation)}` : ''}`;
6967

70-
this.#tryPush(`${indent}1..${count}${exp}\n`);
68+
this.#tryPushString(`${this.#indent(nesting)}1..${count}${exp}\n`);
7169
}
7270

7371
getSkip(reason) {
@@ -78,32 +76,42 @@ class TapStream extends Readable {
7876
return { __proto__: null, todo: reason };
7977
}
8078

81-
subtest(indent, name) {
82-
this.#tryPush(`${indent}# Subtest: ${tapEscape(name)}\n`);
79+
subtest(nesting, name) {
80+
this.#emit('test:subtest', { nesting, name });
81+
this.#tryPushString(`${this.#indent(nesting)}# Subtest: ${tapEscape(name)}\n`);
8382
}
8483

85-
#details(indent, data = kEmptyObject) {
84+
#details(nesting, data = kEmptyObject) {
8685
const { error, duration, yaml } = data;
86+
const indent = this.#indent(nesting);
8787
let details = `${indent} ---\n`;
8888

8989
details += `${yaml ? yaml : ''}`;
9090
details += jsToYaml(indent, 'duration_ms', duration);
9191
details += jsToYaml(indent, null, error);
9292
details += `${indent} ...\n`;
93-
this.#tryPush(details);
93+
this.#tryPushString(details);
9494
}
9595

96-
diagnostic(indent, message) {
97-
this.emit('test:diagnostic', message);
98-
this.#tryPush(`${indent}# ${tapEscape(message)}\n`);
96+
diagnostic(nesting, message) {
97+
this.#emit('test:diagnostic', message);
98+
this.#tryPushString(`${this.#indent(nesting)}# ${tapEscape(message)}\n`);
9999
}
100100

101101
version(spec = kDefaultTAPVersion) {
102-
this.#tryPush(`TAP version ${spec}\n`);
102+
this.#tryPushString(`TAP version ${spec}\n`);
103+
}
104+
105+
#indent(nesting) {
106+
return StringPrototypeRepeat(kDefaultIndent, nesting);
103107
}
104108

105-
#test(indent, testNumber, status, name, directive = kEmptyObject) {
106-
let line = `${indent}${status} ${testNumber}`;
109+
#test(nesting, testNumber, status, name, directive = kEmptyObject) {
110+
if (this._readableState.objectMode) {
111+
// early return
112+
return;
113+
}
114+
let line = `${this.#indent(nesting)}${status} ${testNumber}`;
107115

108116
if (name) {
109117
line += ` ${tapEscape(`- ${name}`)}`;
@@ -115,7 +123,23 @@ class TapStream extends Readable {
115123

116124
line += '\n';
117125

118-
this.#tryPush(line);
126+
this.#tryPushString(line);
127+
}
128+
129+
#emit(type, data) {
130+
this.emit(type, data);
131+
this.#tryPushObject({ type, data });
132+
}
133+
134+
#tryPushString(str) {
135+
if (!this._readableState.objectMode) {
136+
this.#tryPush(str);
137+
}
138+
}
139+
#tryPushObject(obj) {
140+
if (this._readableState.objectMode) {
141+
this.#tryPush(obj);
142+
}
119143
}
120144

121145
#tryPush(message) {
@@ -261,4 +285,4 @@ function isAssertionLike(value) {
261285
return value && typeof value === 'object' && 'expected' in value && 'actual' in value;
262286
}
263287

264-
module.exports = { TapStream };
288+
module.exports = { TapStream, kDefaultIndent };

lib/internal/test_runner/test.js

+20-21
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ const kTestCodeFailure = 'testCodeFailure';
6363
const kTestTimeoutFailure = 'testTimeoutFailure';
6464
const kHookFailure = 'hookFailed';
6565
const kDefaultTimeout = null;
66-
const kDefaultIndent = ' '; // 4 spaces
6766
const noop = FunctionPrototype;
6867
const isTestRunner = getOptionValue('--test');
68+
const hasReporters = Boolean(getOptionValue('--test-reporter'));
6969
const testOnlyFlag = !isTestRunner && getOptionValue('--test-only');
7070
const testNamePatternFlag = isTestRunner ? null :
7171
getOptionValue('--test-name-pattern');
@@ -171,18 +171,18 @@ class Test extends AsyncResource {
171171

172172
if (parent === null) {
173173
this.concurrency = 1;
174-
this.indent = '';
174+
this.nesting = 0;
175175
this.only = testOnlyFlag;
176-
this.reporter = new TapStream();
176+
this.reporter = new TapStream({ objectMode: hasReporters });
177177
this.runOnlySubtests = this.only;
178178
this.testNumber = 0;
179179
this.timeout = kDefaultTimeout;
180180
} else {
181-
const indent = parent.parent === null ? parent.indent :
182-
parent.indent + kDefaultIndent;
181+
const nesting = parent.parent === null ? parent.nesting :
182+
parent.nesting + 1;
183183

184184
this.concurrency = parent.concurrency;
185-
this.indent = indent;
185+
this.nesting = nesting;
186186
this.only = only ?? !parent.runOnlySubtests;
187187
this.reporter = parent.reporter;
188188
this.runOnlySubtests = !this.only;
@@ -607,19 +607,19 @@ class Test extends AsyncResource {
607607
this.parent.processPendingSubtests();
608608
} else if (!this.reported) {
609609
this.reported = true;
610-
this.reporter.plan(this.indent, this.subtests.length);
610+
this.reporter.plan(this.nesting, this.subtests.length);
611611

612612
for (let i = 0; i < this.diagnostics.length; i++) {
613-
this.reporter.diagnostic(this.indent, this.diagnostics[i]);
613+
this.reporter.diagnostic(this.nesting, this.diagnostics[i]);
614614
}
615615

616-
this.reporter.diagnostic(this.indent, `tests ${this.subtests.length}`);
617-
this.reporter.diagnostic(this.indent, `pass ${counters.passed}`);
618-
this.reporter.diagnostic(this.indent, `fail ${counters.failed}`);
619-
this.reporter.diagnostic(this.indent, `cancelled ${counters.cancelled}`);
620-
this.reporter.diagnostic(this.indent, `skipped ${counters.skipped}`);
621-
this.reporter.diagnostic(this.indent, `todo ${counters.todo}`);
622-
this.reporter.diagnostic(this.indent, `duration_ms ${this.#duration()}`);
616+
this.reporter.diagnostic(this.nesting, `tests ${this.subtests.length}`);
617+
this.reporter.diagnostic(this.nesting, `pass ${counters.passed}`);
618+
this.reporter.diagnostic(this.nesting, `fail ${counters.failed}`);
619+
this.reporter.diagnostic(this.nesting, `cancelled ${counters.cancelled}`);
620+
this.reporter.diagnostic(this.nesting, `skipped ${counters.skipped}`);
621+
this.reporter.diagnostic(this.nesting, `todo ${counters.todo}`);
622+
this.reporter.diagnostic(this.nesting, `duration_ms ${this.#duration()}`);
623623
this.reporter.push(null);
624624
}
625625
}
@@ -655,7 +655,7 @@ class Test extends AsyncResource {
655655

656656
report() {
657657
if (this.subtests.length > 0) {
658-
this.reporter.plan(this.subtests[0].indent, this.subtests.length);
658+
this.reporter.plan(this.subtests[0].nesting, this.subtests.length);
659659
} else {
660660
this.reportSubtest();
661661
}
@@ -669,14 +669,14 @@ class Test extends AsyncResource {
669669
}
670670

671671
if (this.passed) {
672-
this.reporter.ok(this.indent, this.testNumber, this.name, details, directive);
672+
this.reporter.ok(this.nesting, this.testNumber, this.name, details, directive);
673673
} else {
674674
details.error = this.error;
675-
this.reporter.fail(this.indent, this.testNumber, this.name, details, directive);
675+
this.reporter.fail(this.nesting, this.testNumber, this.name, details, directive);
676676
}
677677

678678
for (let i = 0; i < this.diagnostics.length; i++) {
679-
this.reporter.diagnostic(this.indent, this.diagnostics[i]);
679+
this.reporter.diagnostic(this.nesting, this.diagnostics[i]);
680680
}
681681
}
682682

@@ -686,7 +686,7 @@ class Test extends AsyncResource {
686686
}
687687
this.#reportedSubtest = true;
688688
this.parent.reportSubtest();
689-
this.reporter.subtest(this.indent, this.name);
689+
this.reporter.subtest(this.nesting, this.name);
690690
}
691691
}
692692

@@ -790,7 +790,6 @@ class Suite extends Test {
790790
module.exports = {
791791
ItTest,
792792
kCancelledByParent,
793-
kDefaultIndent,
794793
kSubtestsFailed,
795794
kTestCodeFailure,
796795
kUnwrapErrors,

0 commit comments

Comments
 (0)