Skip to content

Commit e061cd4

Browse files
committed
test_runner: expose describe and it
1 parent 486dcd7 commit e061cd4

File tree

3 files changed

+89
-41
lines changed

3 files changed

+89
-41
lines changed

lib/internal/test_runner/harness.js

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,18 @@ const {
44
createHook,
55
executionAsyncId,
66
} = require('async_hooks');
7+
const console = require('console');
78
const {
89
codes: {
910
ERR_TEST_FAILURE,
1011
},
1112
} = require('internal/errors');
1213
const { Test } = require('internal/test_runner/test');
1314

14-
function createProcessEventHandler(eventName, rootTest, testResources) {
15+
16+
const testResources = new SafeMap();
17+
18+
function createProcessEventHandler(eventName, rootTest) {
1519
return (err) => {
1620
// Check if this error is coming from a test. If it is, fail the test.
1721
const test = testResources.get(executionAsyncId());
@@ -31,12 +35,13 @@ function createProcessEventHandler(eventName, rootTest, testResources) {
3135

3236
test.fail(new ERR_TEST_FAILURE(err, eventName));
3337
test.postRun();
38+
} else {
39+
console.error(err);
3440
}
3541
};
3642
}
3743

3844
function setup(root) {
39-
const testResources = new SafeMap();
4045
const hook = createHook({
4146
init(asyncId, type, triggerAsyncId, resource) {
4247
if (resource instanceof Test) {
@@ -58,9 +63,9 @@ function setup(root) {
5863
hook.enable();
5964

6065
const exceptionHandler =
61-
createProcessEventHandler('uncaughtException', root, testResources);
66+
createProcessEventHandler('uncaughtException', root);
6267
const rejectionHandler =
63-
createProcessEventHandler('unhandledRejection', root, testResources);
68+
createProcessEventHandler('unhandledRejection', root);
6469

6570
process.on('uncaughtException', exceptionHandler);
6671
process.on('unhandledRejection', rejectionHandler);
@@ -113,19 +118,31 @@ function setup(root) {
113118

114119
root.reporter.pipe(process.stdout);
115120
root.reporter.version();
121+
return root;
116122
}
117123

118-
function test(name, options, fn) {
119-
// If this is the first test encountered, bootstrap the test harness.
120-
if (this.subtests.length === 0) {
121-
setup(this);
122-
}
124+
const root = setup(new Test({ name: '<root>' }));
123125

126+
function test(name, options, fn) {
124127
const subtest = this.createSubtest(name, options, fn);
125-
126128
return subtest.start();
127129
}
128130

129-
const root = new Test({ name: '<root>' });
131+
function describe(name, options, fn) {
132+
const parent = testResources.get(executionAsyncId()) || root;
133+
const suite = parent.createSubSuite(name, options, fn);
134+
if (parent === root) {
135+
suite.run();
136+
}
137+
}
138+
139+
function it(name, options, fn) {
140+
const parent = testResources.get(executionAsyncId()) || root;
141+
parent.createSubtest(name, options, fn);
142+
}
130143

131-
module.exports = FunctionPrototypeBind(test, root);
144+
module.exports = {
145+
test: FunctionPrototypeBind(test, root),
146+
describe,
147+
it,
148+
};

lib/internal/test_runner/test.js

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,32 @@ const testOnlyFlag = !isTestRunner && getOptionValue('--test-only');
3636
// TODO(cjihrig): Use uv_available_parallelism() once it lands.
3737
const rootConcurrency = isTestRunner ? cpus().length : 1;
3838

39+
40+
function parseTestProps(parent, name, options, fn, overrides) {
41+
if (typeof name === 'function') {
42+
fn = name;
43+
} else if (name !== null && typeof name === 'object') {
44+
fn = options;
45+
options = name;
46+
} else if (typeof options === 'function') {
47+
fn = options;
48+
}
49+
50+
if (options === null || typeof options !== 'object') {
51+
options = kEmptyObject;
52+
}
53+
54+
// If this test has already ended, attach this test to the root test so
55+
// that the error can be properly reported.
56+
if (parent.finished) {
57+
while (parent.parent !== null) {
58+
parent = parent.parent;
59+
}
60+
}
61+
62+
return { fn, name, parent, ...options, ...overrides };
63+
}
64+
3965
class TestContext {
4066
#test;
4167

@@ -189,34 +215,11 @@ class Test extends AsyncResource {
189215
}
190216
}
191217

192-
createSubtest(name, options, fn) {
193-
if (typeof name === 'function') {
194-
fn = name;
195-
} else if (name !== null && typeof name === 'object') {
196-
fn = options;
197-
options = name;
198-
} else if (typeof options === 'function') {
199-
fn = options;
200-
}
218+
createSubtest(...props) {
219+
const test = new Test(parseTestProps(this, ...props));
201220

202-
if (options === null || typeof options !== 'object') {
203-
options = kEmptyObject;
204-
}
205-
206-
let parent = this;
207-
208-
// If this test has already ended, attach this test to the root test so
209-
// that the error can be properly reported.
210-
if (this.finished) {
211-
while (parent.parent !== null) {
212-
parent = parent.parent;
213-
}
214-
}
215-
216-
const test = new Test({ fn, name, parent, ...options });
217-
218-
if (parent.waitingOn === 0) {
219-
parent.waitingOn = test.testNumber;
221+
if (test.parent.waitingOn === 0) {
222+
test.parent.waitingOn = test.testNumber;
220223
}
221224

222225
if (this.finished) {
@@ -228,10 +231,22 @@ class Test extends AsyncResource {
228231
);
229232
}
230233

231-
ArrayPrototypePush(parent.subtests, test);
234+
ArrayPrototypePush(test.parent.subtests, test);
232235
return test;
233236
}
234237

238+
createSubSuite(...props) {
239+
// eslint-disable-next-line no-use-before-define
240+
const suite = new Suite(parseTestProps(this, ...props));
241+
242+
if (suite.parent.waitingOn === 0) {
243+
suite.parent.waitingOn = suite.testNumber;
244+
}
245+
246+
ArrayPrototypePush(suite.parent.subtests, suite);
247+
return suite;
248+
}
249+
235250
cancel() {
236251
if (this.endTime !== null) {
237252
return;
@@ -448,5 +463,19 @@ class Test extends AsyncResource {
448463
}
449464
}
450465
}
466+
class Suite extends Test {
467+
constructor(options) {
468+
super(options);
469+
470+
this.runInAsyncScope(this.fn);
471+
this.fn = () => {};
472+
}
473+
async run() {
474+
for (const subtest of this.subtests) {
475+
await subtest.run();
476+
}
477+
await super.run();
478+
}
479+
}
451480

452481
module.exports = { kDefaultIndent, kSubtestsFailed, kTestCodeFailure, Test };

lib/test.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
'use strict';
2-
const test = require('internal/test_runner/harness');
2+
const { test, describe, it } = require('internal/test_runner/harness');
33
const { emitExperimentalWarning } = require('internal/util');
44

55
emitExperimentalWarning('The test runner');
66

77
module.exports = test;
88
module.exports.test = test;
9+
module.exports.describe = describe;
10+
module.exports.it = it;

0 commit comments

Comments
 (0)