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); }); });