diff --git a/doc/api/test.md b/doc/api/test.md
index e1639cab9ce585..504d3f60a5c2ae 100644
--- a/doc/api/test.md
+++ b/doc/api/test.md
@@ -105,11 +105,11 @@ top level test with two subtests.
```js
test('top level test', async (t) => {
- t.test('subtest 1', (t) => {
+ await t.test('subtest 1', (t) => {
assert.strictEqual(1, 1);
});
- t.test('subtest 2', (t) => {
+ await t.test('subtest 2', (t) => {
assert.strictEqual(2, 2);
});
});
@@ -118,7 +118,12 @@ test('top level test', async (t) => {
> **Note:** `beforeEach` and `afterEach` hooks are triggered
> between each subtest execution.
-Any subtest failures cause the parent test to fail.
+In this example, `await` is used to ensure that both subtests have completed.
+This is necessary because tests do not wait for their subtests to
+complete, unlike tests created within suites.
+Any subtests that are still outstanding when their parent finishes
+are cancelled and treated as failures. Any subtest failures cause the parent
+test to fail.
## Skipping tests
@@ -236,20 +241,20 @@ that are not executed are omitted from the test runner output.
// The suite's 'only' option is set, so these tests are run.
test('this test is run', { only: true }, async (t) => {
// Within this test, all subtests are run by default.
- t.test('running subtest');
+ await t.test('running subtest');
// The test context can be updated to run subtests with the 'only' option.
t.runOnly(true);
- t.test('this subtest is now skipped');
- t.test('this subtest is run', { only: true });
+ await t.test('this subtest is now skipped');
+ await t.test('this subtest is run', { only: true });
// Switch the context back to execute all tests.
t.runOnly(false);
- t.test('this subtest is now run');
+ await t.test('this subtest is now run');
// Explicitly do not run these tests.
- t.test('skipped subtest 3', { only: false });
- t.test('skipped subtest 4', { skip: true });
+ await t.test('skipped subtest 3', { only: false });
+ await t.test('skipped subtest 4', { skip: true });
});
// The 'only' option is not set, so this test is skipped.
@@ -304,13 +309,13 @@ multiple times (e.g. `--test-name-pattern="test 1"`,
```js
test('test 1', async (t) => {
- t.test('test 2');
- t.test('test 3');
+ await t.test('test 2');
+ await t.test('test 3');
});
test('Test 4', async (t) => {
- t.test('Test 5');
- t.test('test 6');
+ await t.test('Test 5');
+ await t.test('test 6');
});
```
@@ -1456,11 +1461,6 @@ run({ files: [path.resolve('./tests/test.js')] })
added:
- v22.0.0
- v20.13.0
-changes:
- - version:
- - v24.0.0
- pr-url: https://github.com/nodejs/node/pull/56664
- description: This function no longer returns a `Promise`.
-->
* `name` {string} The name of the suite, which is displayed when reporting test
@@ -1471,6 +1471,7 @@ changes:
* `fn` {Function|AsyncFunction} The suite function declaring nested tests and
suites. The first argument to this function is a [`SuiteContext`][] object.
**Default:** A no-op function.
+* Returns: {Promise} Immediately fulfilled with `undefined`.
The `suite()` function is imported from the `node:test` module.
@@ -1514,10 +1515,6 @@ added:
- v18.0.0
- v16.17.0
changes:
- - version:
- - v24.0.0
- pr-url: https://github.com/nodejs/node/pull/56664
- description: This function no longer returns a `Promise`.
- version:
- v20.2.0
- v18.17.0
@@ -1567,6 +1564,8 @@ changes:
to this function is a [`TestContext`][] object. If the test uses callbacks,
the callback function is passed as the second argument. **Default:** A no-op
function.
+* Returns: {Promise} Fulfilled with `undefined` once
+ the test completes, or immediately if the test runs within a suite.
The `test()` function is the value imported from the `test` module. Each
invocation of this function results in reporting the test to the {TestsStream}.
@@ -1575,6 +1574,26 @@ The `TestContext` object passed to the `fn` argument can be used to perform
actions related to the current test. Examples include skipping the test, adding
additional diagnostic information, or creating subtests.
+`test()` returns a `Promise` that fulfills once the test completes.
+if `test()` is called within a suite, it fulfills immediately.
+The return value can usually be discarded for top level tests.
+However, the return value from subtests should be used to prevent the parent
+test from finishing first and cancelling the subtest
+as shown in the following example.
+
+```js
+test('top level test', async (t) => {
+ // The setTimeout() in the following subtest would cause it to outlive its
+ // parent test if 'await' is removed on the next line. Once the parent test
+ // completes, it will cancel any outstanding subtests.
+ await t.test('longer running subtest', async (t) => {
+ return new Promise((resolve, reject) => {
+ setTimeout(resolve, 1000);
+ });
+ });
+});
+```
+
The `timeout` option can be used to fail the test if it takes longer than
`timeout` milliseconds to complete. However, it is not a reliable mechanism for
canceling tests because a running test might block the application thread and
@@ -3231,9 +3250,12 @@ before each subtest of the current test.
```js
test('top level test', async (t) => {
t.beforeEach((t) => t.diagnostic(`about to run ${t.name}`));
- t.test('This is a subtest', (t) => {
- assert.ok('some relevant assertion here');
- });
+ await t.test(
+ 'This is a subtest',
+ (t) => {
+ assert.ok('some relevant assertion here');
+ },
+ );
});
```
@@ -3291,9 +3313,12 @@ after each subtest of the current test.
```js
test('top level test', async (t) => {
t.afterEach((t) => t.diagnostic(`finished running ${t.name}`));
- t.test('This is a subtest', (t) => {
- assert.ok('some relevant assertion here');
- });
+ await t.test(
+ 'This is a subtest',
+ (t) => {
+ assert.ok('some relevant assertion here');
+ },
+ );
});
```
@@ -3545,8 +3570,10 @@ no-op.
test('top level test', (t) => {
// The test context can be set to run subtests with the 'only' option.
t.runOnly(true);
- t.test('this subtest is now skipped');
- t.test('this subtest is run', { only: true });
+ return Promise.all([
+ t.test('this subtest is now skipped'),
+ t.test('this subtest is run', { only: true }),
+ ]);
});
```
@@ -3618,10 +3645,6 @@ added:
- v18.0.0
- v16.17.0
changes:
- - version:
- - v24.0.0
- pr-url: https://github.com/nodejs/node/pull/56664
- description: This function no longer returns a `Promise`.
- version:
- v18.8.0
- v16.18.0
@@ -3666,13 +3689,14 @@ changes:
to this function is a [`TestContext`][] object. If the test uses callbacks,
the callback function is passed as the second argument. **Default:** A no-op
function.
+* Returns: {Promise} Fulfilled with `undefined` once the test completes.
This function is used to create subtests under the current test. This function
behaves in the same fashion as the top level [`test()`][] function.
```js
test('top level test', async (t) => {
- t.test(
+ await t.test(
'This is a subtest',
{ only: false, skip: false, concurrency: 1, todo: false, plan: 1 },
(t) => {
diff --git a/lib/internal/test_runner/harness.js b/lib/internal/test_runner/harness.js
index 9774326a4255c7..77791b7231d593 100644
--- a/lib/internal/test_runner/harness.js
+++ b/lib/internal/test_runner/harness.js
@@ -28,8 +28,6 @@ const {
setupGlobalSetupTeardownFunctions,
} = require('internal/test_runner/utils');
const { queueMicrotask } = require('internal/process/task_queues');
-const { TIMEOUT_MAX } = require('internal/timers');
-const { clearInterval, setInterval } = require('timers');
const { bigint: hrtime } = process.hrtime;
const testResources = new SafeMap();
let globalRoot;
@@ -230,20 +228,11 @@ function setupProcessState(root, globalOptions) {
const rejectionHandler =
createProcessEventHandler('unhandledRejection', root);
const coverage = configureCoverage(root, globalOptions);
- const exitHandler = async (kill) => {
+ const exitHandler = async () => {
if (root.subtests.length === 0 && (root.hooks.before.length > 0 || root.hooks.after.length > 0)) {
// Run global before/after hooks in case there are no tests
await root.run();
}
-
- if (kill !== true && root.subtestsPromise !== null) {
- // Wait for all subtests to finish, but keep the process alive in case
- // there are no ref'ed handles left.
- const keepAlive = setInterval(() => {}, TIMEOUT_MAX);
- await root.subtestsPromise.promise;
- clearInterval(keepAlive);
- }
-
root.postRun(new ERR_TEST_FAILURE(
'Promise resolution is still pending but the event loop has already resolved',
kCancelledByParent));
@@ -263,8 +252,8 @@ function setupProcessState(root, globalOptions) {
}
};
- const terminationHandler = async () => {
- await exitHandler(true);
+ const terminationHandler = () => {
+ exitHandler();
process.exit();
};
@@ -336,12 +325,11 @@ function runInParentContext(Factory) {
function run(name, options, fn, overrides) {
const parent = testResources.get(executionAsyncId()) || lazyBootstrapRoot();
const subtest = parent.createSubtest(Factory, name, options, fn, overrides);
-
if (parent instanceof Suite) {
- return;
+ return PromiseResolve();
}
- startSubtestAfterBootstrap(subtest);
+ return startSubtestAfterBootstrap(subtest);
}
const test = (name, options, fn) => {
@@ -350,7 +338,7 @@ function runInParentContext(Factory) {
loc: getCallerLocation(),
};
- run(name, options, fn, overrides);
+ return run(name, options, fn, overrides);
};
ArrayPrototypeForEach(['skip', 'todo', 'only'], (keyword) => {
test[keyword] = (name, options, fn) => {
@@ -360,7 +348,7 @@ function runInParentContext(Factory) {
loc: getCallerLocation(),
};
- run(name, options, fn, overrides);
+ return run(name, options, fn, overrides);
};
});
return test;
diff --git a/lib/internal/test_runner/test.js b/lib/internal/test_runner/test.js
index c8390586456db6..aa4606fb3822c1 100644
--- a/lib/internal/test_runner/test.js
+++ b/lib/internal/test_runner/test.js
@@ -370,7 +370,7 @@ class TestContext {
Test, name, options, fn, overrides,
);
- subtest.start();
+ return subtest.start();
}
before(fn, options) {
@@ -651,8 +651,6 @@ class Test extends AsyncResource {
this.activeSubtests = 0;
this.pendingSubtests = [];
this.readySubtests = new SafeMap();
- this.unfinishedSubtests = new SafeSet();
- this.subtestsPromise = null;
this.subtests = [];
this.waitingOn = 0;
this.finished = false;
@@ -756,11 +754,6 @@ class Test extends AsyncResource {
addReadySubtest(subtest) {
this.readySubtests.set(subtest.childNumber, subtest);
-
- if (this.unfinishedSubtests.delete(subtest) &&
- this.unfinishedSubtests.size === 0) {
- this.subtestsPromise.resolve();
- }
}
processReadySubtestRange(canSend) {
@@ -822,7 +815,6 @@ class Test extends AsyncResource {
if (parent.waitingOn === 0) {
parent.waitingOn = test.childNumber;
- parent.subtestsPromise = PromiseWithResolvers();
}
if (preventAddingSubtests) {
@@ -945,7 +937,6 @@ class Test extends AsyncResource {
// If there is enough available concurrency to run the test now, then do
// it. Otherwise, return a Promise to the caller and mark the test as
// pending for later execution.
- this.parent.unfinishedSubtests.add(this);
this.reporter.enqueue(this.nesting, this.loc, this.name, this.reportedType);
if (this.root.harness.buildPromise || !this.parent.hasConcurrency()) {
const deferred = PromiseWithResolvers();
@@ -1070,10 +1061,6 @@ class Test extends AsyncResource {
this[kShouldAbort]();
- if (this.subtestsPromise !== null) {
- await SafePromiseRace([this.subtestsPromise.promise, stopPromise]);
- }
-
if (this.plan !== null) {
const planPromise = this.plan?.check();
// If the plan returns a promise, it means that it is waiting for more assertions to be made before
diff --git a/test/fixtures/test-runner/output/default_output.snapshot b/test/fixtures/test-runner/output/default_output.snapshot
index a58e14346ec727..d0a83395733924 100644
--- a/test/fixtures/test-runner/output/default_output.snapshot
+++ b/test/fixtures/test-runner/output/default_output.snapshot
@@ -3,13 +3,13 @@
[90m﹣ should skip [90m(*ms)[39m # SKIP[39m
▶ parent
[31m✖ should fail [90m(*ms)[39m[39m
- [32m✔ should pass but parent fail [90m(*ms)[39m[39m
+ [31m✖ should pass but parent fail [90m(*ms)[39m[39m
[31m✖ parent [90m(*ms)[39m[39m
[34mℹ tests 6[39m
[34mℹ suites 0[39m
-[34mℹ pass 2[39m
+[34mℹ pass 1[39m
[34mℹ fail 3[39m
-[34mℹ cancelled 0[39m
+[34mℹ cancelled 1[39m
[34mℹ skipped 1[39m
[34mℹ todo 0[39m
[34mℹ duration_ms *[39m
@@ -40,3 +40,7 @@
*[39m
*[39m
*[39m
+
+*
+[31m✖ should pass but parent fail [90m(*ms)[39m[39m
+ [32m'test did not finish before its parent and was cancelled'[39m
diff --git a/test/fixtures/test-runner/output/dot_reporter.snapshot b/test/fixtures/test-runner/output/dot_reporter.snapshot
index 5abbb979667cfd..fc2b58cfef8428 100644
--- a/test/fixtures/test-runner/output/dot_reporter.snapshot
+++ b/test/fixtures/test-runner/output/dot_reporter.snapshot
@@ -1,5 +1,5 @@
..XX...X..XXX.X.....
-XXX............X....
+XXX.....X..X...X....
.....X...XXX.XX.....
XXXXXXX...XXXXX
@@ -93,6 +93,10 @@ Failed tests:
'1 subtest failed'
✖ sync throw non-error fail (*ms)
Symbol(thrown symbol from sync throw non-error fail)
+✖ +long running (*ms)
+ 'test did not finish before its parent and was cancelled'
+✖ top level (*ms)
+ '1 subtest failed'
✖ sync skip option is false fail (*ms)
Error: this should be executed
*
@@ -154,6 +158,8 @@ Failed tests:
*
*
*
+ *
+ *
✖ subtest sync throw fails (*ms)
'2 subtests failed'
✖ timed out async test (*ms)
diff --git a/test/fixtures/test-runner/output/hooks.snapshot b/test/fixtures/test-runner/output/hooks.snapshot
index f1f5b7573c9814..4ba957d539ce70 100644
--- a/test/fixtures/test-runner/output/hooks.snapshot
+++ b/test/fixtures/test-runner/output/hooks.snapshot
@@ -576,6 +576,7 @@ not ok 16 - t.after throws - no subtests
*
*
*
+ *
...
1..2
not ok 17 - t.beforeEach throws
@@ -606,6 +607,8 @@ not ok 17 - t.beforeEach throws
*
*
*
+ *
+ *
...
# Subtest: 2
not ok 2 - 2
@@ -626,6 +629,7 @@ not ok 17 - t.beforeEach throws
*
*
*
+ *
...
1..2
not ok 18 - t.afterEach throws
@@ -753,6 +757,7 @@ not ok 21 - afterEach context when test fails
*
*
*
+ *
...
1..2
not ok 22 - afterEach throws and test fails
diff --git a/test/fixtures/test-runner/output/hooks_spec_reporter.snapshot b/test/fixtures/test-runner/output/hooks_spec_reporter.snapshot
index 8ee710e845f39c..8c267672b9a951 100644
--- a/test/fixtures/test-runner/output/hooks_spec_reporter.snapshot
+++ b/test/fixtures/test-runner/output/hooks_spec_reporter.snapshot
@@ -363,6 +363,7 @@
*
*
*
+ *
*
1 (*ms)
@@ -375,6 +376,8 @@
*
*
*
+ *
+ *
*
2 (*ms)
@@ -388,6 +391,7 @@
*
*
*
+ *
*
1 (*ms)
@@ -435,6 +439,7 @@
*
*
*
+ *
*
t.after() is called if test body throws (*ms)
diff --git a/test/fixtures/test-runner/output/junit_reporter.snapshot b/test/fixtures/test-runner/output/junit_reporter.snapshot
index 3b1d15022af704..aaa5dcd6ff9963 100644
--- a/test/fixtures/test-runner/output/junit_reporter.snapshot
+++ b/test/fixtures/test-runner/output/junit_reporter.snapshot
@@ -186,8 +186,12 @@ Error [ERR_TEST_FAILURE]: thrown from subtest sync throw fail
-
-
+
+
+
+[Error [ERR_TEST_FAILURE]: test did not finish before its parent and was cancelled] { code: 'ERR_TEST_FAILURE', failureType: 'cancelledByParent', cause: 'test did not finish before its parent and was cancelled' }
+
+
@@ -351,7 +355,8 @@ Error [ERR_TEST_FAILURE]: thrown from subtest sync throw fails at first
-[Error [ERR_TEST_FAILURE]: thrown from subtest sync throw fails at second] {
+Error [ERR_TEST_FAILURE]: thrown from subtest sync throw fails at second
+ * {
code: 'ERR_TEST_FAILURE',
failureType: 'testCodeFailure',
cause: Error: thrown from subtest sync throw fails at second
@@ -361,6 +366,8 @@ Error [ERR_TEST_FAILURE]: thrown from subtest sync throw fails at first
*
*
*
+ *
+ *
}
@@ -512,9 +519,9 @@ Error [ERR_TEST_FAILURE]: test could not be started because its parent finished
-
-
-
+
+
+
diff --git a/test/fixtures/test-runner/output/no_refs.snapshot b/test/fixtures/test-runner/output/no_refs.snapshot
index 8014b0343892f7..310094947f9f96 100644
--- a/test/fixtures/test-runner/output/no_refs.snapshot
+++ b/test/fixtures/test-runner/output/no_refs.snapshot
@@ -1,23 +1,35 @@
TAP version 13
# Subtest: does not keep event loop alive
# Subtest: +does not keep event loop alive
- ok 1 - +does not keep event loop alive
+ not ok 1 - +does not keep event loop alive
---
duration_ms: *
type: 'test'
+ location: '/test/fixtures/test-runner/output/no_refs.js:(LINE):11'
+ failureType: 'cancelledByParent'
+ error: 'Promise resolution is still pending but the event loop has already resolved'
+ code: 'ERR_TEST_FAILURE'
+ stack: |-
+ *
...
1..1
-ok 1 - does not keep event loop alive
+not ok 1 - does not keep event loop alive
---
duration_ms: *
type: 'test'
+ location: '/test/fixtures/test-runner/output/no_refs.js:(LINE):1'
+ failureType: 'cancelledByParent'
+ error: 'Promise resolution is still pending but the event loop has already resolved'
+ code: 'ERR_TEST_FAILURE'
+ stack: |-
+ *
...
1..1
# tests 2
# suites 0
-# pass 2
+# pass 0
# fail 0
-# cancelled 0
+# cancelled 2
# skipped 0
# todo 0
# duration_ms *
diff --git a/test/fixtures/test-runner/output/output.snapshot b/test/fixtures/test-runner/output/output.snapshot
index ffbe91759bb859..36d9c289fa1615 100644
--- a/test/fixtures/test-runner/output/output.snapshot
+++ b/test/fixtures/test-runner/output/output.snapshot
@@ -288,10 +288,14 @@ ok 23 - level 0a
...
# Subtest: top level
# Subtest: +long running
- ok 1 - +long running
+ not ok 1 - +long running
---
duration_ms: *
type: 'test'
+ location: '/test/fixtures/test-runner/output/output.js:(LINE):5'
+ failureType: 'cancelledByParent'
+ error: 'test did not finish before its parent and was cancelled'
+ code: 'ERR_TEST_FAILURE'
...
# Subtest: +short running
# Subtest: ++short running
@@ -307,10 +311,14 @@ ok 23 - level 0a
type: 'test'
...
1..2
-ok 24 - top level
+not ok 24 - top level
---
duration_ms: *
type: 'test'
+ location: '/test/fixtures/test-runner/output/output.js:(LINE):1'
+ failureType: 'subtestsFailed'
+ error: '1 subtest failed'
+ code: 'ERR_TEST_FAILURE'
...
# Subtest: invalid subtest - pass but subtest fails
ok 25 - invalid subtest - pass but subtest fails
@@ -598,6 +606,8 @@ not ok 51 - custom inspect symbol that throws fail
*
*
*
+ *
+ *
...
1..2
not ok 52 - subtest sync throw fails
@@ -777,9 +787,9 @@ not ok 62 - invalid subtest fail
# Error: Test "callback async throw after done" at test/fixtures/test-runner/output/output.js:(LINE):1 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.
# tests 75
# suites 0
-# pass 36
-# fail 24
-# cancelled 2
+# pass 34
+# fail 25
+# cancelled 3
# skipped 9
# todo 4
# duration_ms *
diff --git a/test/fixtures/test-runner/output/output_cli.snapshot b/test/fixtures/test-runner/output/output_cli.snapshot
index 7f989f14c619cf..4546269836e9ca 100644
--- a/test/fixtures/test-runner/output/output_cli.snapshot
+++ b/test/fixtures/test-runner/output/output_cli.snapshot
@@ -288,10 +288,14 @@ ok 23 - level 0a
...
# Subtest: top level
# Subtest: +long running
- ok 1 - +long running
+ not ok 1 - +long running
---
duration_ms: *
type: 'test'
+ location: '/test/fixtures/test-runner/output/output.js:(LINE):5'
+ failureType: 'cancelledByParent'
+ error: 'test did not finish before its parent and was cancelled'
+ code: 'ERR_TEST_FAILURE'
...
# Subtest: +short running
# Subtest: ++short running
@@ -307,10 +311,14 @@ ok 23 - level 0a
type: 'test'
...
1..2
-ok 24 - top level
+not ok 24 - top level
---
duration_ms: *
type: 'test'
+ location: '/test/fixtures/test-runner/output/output.js:(LINE):1'
+ failureType: 'subtestsFailed'
+ error: '1 subtest failed'
+ code: 'ERR_TEST_FAILURE'
...
# Subtest: invalid subtest - pass but subtest fails
ok 25 - invalid subtest - pass but subtest fails
@@ -606,6 +614,8 @@ not ok 51 - custom inspect symbol that throws fail
*
*
*
+ *
+ *
...
1..2
not ok 52 - subtest sync throw fails
@@ -791,9 +801,9 @@ ok 63 - last test
1..63
# tests 77
# suites 0
-# pass 38
-# fail 24
-# cancelled 2
+# pass 36
+# fail 25
+# cancelled 3
# skipped 9
# todo 4
# duration_ms *
diff --git a/test/fixtures/test-runner/output/spec_reporter.snapshot b/test/fixtures/test-runner/output/spec_reporter.snapshot
index 6c11b9ba6d4a39..1892069327f92d 100644
--- a/test/fixtures/test-runner/output/spec_reporter.snapshot
+++ b/test/fixtures/test-runner/output/spec_reporter.snapshot
@@ -90,9 +90,9 @@
Error: Test "callback async throw after done" at test/fixtures/test-runner/output/output.js:269:1 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.
tests 75
suites 0
- pass 36
- fail 24
- cancelled 2
+ pass 34
+ fail 25
+ cancelled 3
skipped 9
todo 4
duration_ms *
@@ -203,6 +203,10 @@
sync throw non-error fail (*ms)
Symbol(thrown symbol from sync throw non-error fail)
+*
+ +long running (*ms)
+ 'test did not finish before its parent and was cancelled'
+
*
sync skip option is false fail (*ms)
Error: this should be executed
@@ -285,6 +289,8 @@
*
*
*
+ *
+ *
*
timed out async test (*ms)
diff --git a/test/fixtures/test-runner/output/spec_reporter_cli.snapshot b/test/fixtures/test-runner/output/spec_reporter_cli.snapshot
index a428b1140ac812..52dc40bb366e2c 100644
--- a/test/fixtures/test-runner/output/spec_reporter_cli.snapshot
+++ b/test/fixtures/test-runner/output/spec_reporter_cli.snapshot
@@ -93,9 +93,9 @@
Error: Test "callback async throw after done" at test/fixtures/test-runner/output/output.js:269:1 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.
tests 76
suites 0
- pass 37
- fail 24
- cancelled 2
+ pass 35
+ fail 25
+ cancelled 3
skipped 9
todo 4
duration_ms *
@@ -206,6 +206,10 @@
sync throw non-error fail (*ms)
Symbol(thrown symbol from sync throw non-error fail)
+*
+ +long running (*ms)
+ 'test did not finish before its parent and was cancelled'
+
*
sync skip option is false fail (*ms)
Error: this should be executed
@@ -288,6 +292,8 @@
*
*
*
+ *
+ *
*
timed out async test (*ms)
diff --git a/test/fixtures/test-runner/output/test-timeout-flag.js b/test/fixtures/test-runner/output/test-timeout-flag.js
index e3896a2966ff9b..11e294a1acbd0b 100644
--- a/test/fixtures/test-runner/output/test-timeout-flag.js
+++ b/test/fixtures/test-runner/output/test-timeout-flag.js
@@ -1,19 +1,39 @@
-// Flags: --test-timeout=20
'use strict';
-const { describe, test } = require('node:test');
-const { setTimeout } = require('node:timers/promises');
+const { describe, it, after } = require('node:test');
+const { setTimeout } = require('node:timers');
+
+const timeoutRefs = [];
describe('--test-timeout is set to 20ms', () => {
- test('should timeout after 20ms', async () => {
- await setTimeout(200000, undefined, { ref: false });
+ it('should timeout after 20ms', async () => {
+ const { promise, resolve } = Promise.withResolvers();
+ timeoutRefs.push(setTimeout(() => {
+ resolve();
+ }, 20000));
+ await promise;
});
- test('should timeout after 5ms', { timeout: 5 }, async () => {
- await setTimeout(200000, undefined, { ref: false });
+
+ it('should timeout after 5ms', { timeout: 5 }, async () => {
+ const { promise, resolve } = Promise.withResolvers();
+ timeoutRefs.push(setTimeout(() => {
+ resolve();
+ }, 20000));
+ await promise;
});
- test('should not timeout', { timeout: 50000 }, async () => {
- await setTimeout(1);
+ it('should not timeout', { timeout: 50000 }, async () => {
+ const { promise, resolve } = Promise.withResolvers();
+ timeoutRefs.push(setTimeout(() => {
+ resolve();
+ }, 1));
+ await promise;
});
- test('should pass', async () => {});
+ it('should pass', async () => {});
+
+ after(() => {
+ for (const timeoutRef of timeoutRefs) {
+ clearTimeout(timeoutRef);
+ }
+ });
});
diff --git a/test/parallel/test-runner-coverage-source-map.js b/test/parallel/test-runner-coverage-source-map.js
index 3b481f457dd3e6..e3b0676a557a9f 100644
--- a/test/parallel/test-runner-coverage-source-map.js
+++ b/test/parallel/test-runner-coverage-source-map.js
@@ -122,4 +122,4 @@ describe('Coverage with source maps', async () => {
t.assert.strictEqual(spawned.code, 1);
});
}
-});
+}).then(common.mustCall());
diff --git a/test/parallel/test-runner-misc.js b/test/parallel/test-runner-misc.js
index 28e626d182e899..cea115493249a1 100644
--- a/test/parallel/test-runner-misc.js
+++ b/test/parallel/test-runner-misc.js
@@ -18,8 +18,9 @@ if (process.argv[2] === 'child') {
assert.strictEqual(signal.aborted, false);
testSignal = signal;
await setTimeout(50);
+ })).finally(common.mustCall(() => {
+ test(() => assert.strictEqual(testSignal.aborted, true));
}));
- test(() => assert.strictEqual(testSignal.aborted, true));
// TODO(benjamingr) add more tests to describe + AbortSignal
// this just tests the parameter is passed
diff --git a/test/parallel/test-runner-module-mocking.js b/test/parallel/test-runner-module-mocking.js
index 1a1ef67632acd9..89e08a9e22a362 100644
--- a/test/parallel/test-runner-module-mocking.js
+++ b/test/parallel/test-runner-module-mocking.js
@@ -477,15 +477,13 @@ test('mocks are automatically restored', async (t) => {
assert.strictEqual(mocked.fn(), 43);
});
- t.test('checks original behavior', async () => {
- const cjsMock = require(cjsFixture);
- const esmMock = await import(esmFixture);
+ const cjsMock = require(cjsFixture);
+ const esmMock = await import(esmFixture);
- assert.strictEqual(cjsMock.string, 'original cjs string');
- assert.strictEqual(cjsMock.fn, undefined);
- assert.strictEqual(esmMock.string, 'original esm string');
- assert.strictEqual(esmMock.fn, undefined);
- });
+ assert.strictEqual(cjsMock.string, 'original cjs string');
+ assert.strictEqual(cjsMock.fn, undefined);
+ assert.strictEqual(esmMock.string, 'original esm string');
+ assert.strictEqual(esmMock.fn, undefined);
});
test('mocks can be restored independently', async (t) => {
diff --git a/test/parallel/test-runner-output.mjs b/test/parallel/test-runner-output.mjs
index 081d4673ed313d..489ac61ace87de 100644
--- a/test/parallel/test-runner-output.mjs
+++ b/test/parallel/test-runner-output.mjs
@@ -134,12 +134,19 @@ const tests = [
},
{
name: 'test-runner/output/test-timeout-flag.js',
- flags: ['--test-reporter=tap'],
+ flags: [
+ '--test-reporter=tap',
+ '--test-timeout=20',
+ ],
},
// --test-timeout should work with or without --test flag
{
name: 'test-runner/output/test-timeout-flag.js',
- flags: ['--test-reporter=tap', '--test'],
+ flags: [
+ '--test-reporter=tap',
+ '--test-timeout=20',
+ '--test',
+ ],
},
{
name: 'test-runner/output/hooks-with-no-global-test.js',
@@ -208,6 +215,10 @@ const tests = [
name: 'test-runner/output/unfinished-suite-async-error.js',
flags: ['--test-reporter=tap'],
},
+ {
+ name: 'test-runner/output/unresolved_promise.js',
+ flags: ['--test-reporter=tap'],
+ },
{ name: 'test-runner/output/default_output.js', transform: specTransform, tty: true },
{
name: 'test-runner/output/arbitrary-output.js',
diff --git a/test/parallel/test-runner-typechecking.js b/test/parallel/test-runner-typechecking.js
index 8568b4cb39218b..e96761b1a054bd 100644
--- a/test/parallel/test-runner-typechecking.js
+++ b/test/parallel/test-runner-typechecking.js
@@ -6,6 +6,7 @@ require('../common');
const assert = require('assert');
const { test, describe, it } = require('node:test');
+const { isPromise } = require('util/types');
const testOnly = test('only test', { only: true });
const testTodo = test('todo test', { todo: true });
@@ -15,12 +16,21 @@ const testTodoShorthand = test.todo('todo test shorthand');
const testSkipShorthand = test.skip('skip test shorthand');
describe('\'node:test\' and its shorthands should return the same', () => {
- it('should return undefined', () => {
- assert.strictEqual(testOnly, undefined);
- assert.strictEqual(testTodo, undefined);
- assert.strictEqual(testSkip, undefined);
- assert.strictEqual(testOnlyShorthand, undefined);
- assert.strictEqual(testTodoShorthand, undefined);
- assert.strictEqual(testSkipShorthand, undefined);
+ it('should return a Promise', () => {
+ assert(isPromise(testOnly));
+ assert(isPromise(testTodo));
+ assert(isPromise(testSkip));
+ assert(isPromise(testOnlyShorthand));
+ assert(isPromise(testTodoShorthand));
+ assert(isPromise(testSkipShorthand));
+ });
+
+ it('should resolve undefined', async () => {
+ assert.strictEqual(await testOnly, undefined);
+ assert.strictEqual(await testTodo, undefined);
+ assert.strictEqual(await testSkip, undefined);
+ assert.strictEqual(await testOnlyShorthand, undefined);
+ assert.strictEqual(await testTodoShorthand, undefined);
+ assert.strictEqual(await testSkipShorthand, undefined);
});
});