diff --git a/README.md b/README.md index 5dfaf3a..3867990 100644 --- a/README.md +++ b/README.md @@ -339,7 +339,7 @@ internally. - `only` {boolean} If truthy, and the test context is configured to run `only` tests, then this test will be run. Otherwise, the test is skipped. **Default:** `false`. - * `signal` {AbortSignal} Allows aborting an in-progress test + * `signal` {AbortSignal} Allows aborting an in-progress test. - `skip` {boolean|string} If truthy, the test is skipped. If a string is provided, that string is displayed in the test results as the reason for skipping the test. **Default:** `false`. @@ -506,6 +506,7 @@ execution of the test function. This function does not return a value. - `skip` {boolean|string} If truthy, the test is skipped. If a string is provided, that string is displayed in the test results as the reason for skipping the test. **Default:** `false`. + - `signal` {AbortSignal} Allows aborting an in-progress test. - `todo` {boolean|string} If truthy, the test marked as `TODO`. If a string is provided, that string is displayed in the test results as the reason why the test is `TODO`. **Default:** `false`. diff --git a/lib/internal/test_runner/harness.js b/lib/internal/test_runner/harness.js index 8c5dcc9..1780178 100644 --- a/lib/internal/test_runner/harness.js +++ b/lib/internal/test_runner/harness.js @@ -1,4 +1,4 @@ -// https://github.com/nodejs/node/blob/1523a1817ed9b06fb51c0149451f9ea31bd2756e/lib/internal/test_runner/harness.js +// https://github.com/nodejs/node/blob/26e27424ad91c60a44d3d4c58b62a39b555ba75d/lib/internal/test_runner/harness.js 'use strict' const { ArrayPrototypeForEach, @@ -14,8 +14,10 @@ const { ERR_TEST_FAILURE } } = require('#internal/errors') +const { getOptionValue } = require('#internal/options') const { Test, ItTest, Suite } = require('#internal/test_runner/test') +const isTestRunner = getOptionValue('--test') const testResources = new SafeMap() const root = new Test({ __proto__: null, name: '' }) let wasRootSetup = false @@ -136,8 +138,11 @@ function setup (root) { process.on('uncaughtException', exceptionHandler) process.on('unhandledRejection', rejectionHandler) process.on('beforeExit', exitHandler) - process.on('SIGINT', terminationHandler) - process.on('SIGTERM', terminationHandler) + // TODO(MoLow): Make it configurable to hook when isTestRunner === false. + if (isTestRunner) { + process.on('SIGINT', terminationHandler) + process.on('SIGTERM', terminationHandler) + } root.reporter.pipe(process.stdout) root.reporter.version() diff --git a/lib/internal/test_runner/test.js b/lib/internal/test_runner/test.js index f6b9df5..e782e49 100644 --- a/lib/internal/test_runner/test.js +++ b/lib/internal/test_runner/test.js @@ -1,4 +1,4 @@ -// https://github.com/nodejs/node/blob/dab492f0444b0a6ae8a41dd1d9605e036c363655/lib/internal/test_runner/test.js +// https://github.com/nodejs/node/blob/a3e110820ff98702e1761831e7beaf0f5f1f75e7/lib/internal/test_runner/test.js 'use strict' @@ -36,9 +36,9 @@ const { } = require('#internal/util') const { isPromise } = require('#internal/util/types') const { - isUint32, validateAbortSignal, - validateNumber + validateNumber, + validateUint32 } = require('#internal/validators') const { setTimeout } = require('#timers/promises') const { TIMEOUT_MAX } = require('#internal/timers') @@ -152,14 +152,22 @@ class Test extends AsyncResource { this.timeout = parent.timeout } - if (isUint32(concurrency) && concurrency !== 0) { - this.concurrency = concurrency - } else if (typeof concurrency === 'boolean') { - if (concurrency) { - this.concurrency = isTestRunner ? MathMax(cpus().length - 1, 1) : Infinity - } else { - this.concurrency = 1 - } + switch (typeof concurrency) { + case 'number': + validateUint32(concurrency, 'options.concurrency', 1) + this.concurrency = concurrency + break + + case 'boolean': + if (concurrency) { + this.concurrency = isTestRunner ? MathMax(cpus().length - 1, 1) : Infinity + } else { + this.concurrency = 1 + } + break + + default: + if (concurrency != null) throw new TypeError(`Expected options.concurrency to be a number or boolean, got ${concurrency}`) } if (timeout != null && timeout !== Infinity) { @@ -554,7 +562,12 @@ class Suite extends Test { try { const context = { signal: this.signal } - this.buildSuite = this.runInAsyncScope(this.fn, context, [context]) + this.buildSuite = PromisePrototypeThen( + PromiseResolve(this.runInAsyncScope(this.fn, context, [context])), + undefined, + (err) => { + this.fail(new ERR_TEST_FAILURE(err, kTestCodeFailure)) + }) } catch (err) { this.fail(new ERR_TEST_FAILURE(err, kTestCodeFailure)) } @@ -562,17 +575,9 @@ class Suite extends Test { this.buildPhaseFinished = true } - start () { - return this.run() - } - async run () { - try { - await this.buildSuite - } catch (err) { - this.fail(new ERR_TEST_FAILURE(err, kTestCodeFailure)) - } this.parent.activeSubtests++ + await this.buildSuite this.startTime = hrtime() if (this[kShouldAbort]()) { diff --git a/lib/internal/validators.js b/lib/internal/validators.js index 34ed59d..eb261d8 100644 --- a/lib/internal/validators.js +++ b/lib/internal/validators.js @@ -1,4 +1,4 @@ -// https://github.com/nodejs/node/blob/d83446b4c4694322e12d2b7d22592f2be674e580/lib/internal/validators.js +// https://github.com/nodejs/node/blob/60da0a1b364efdd84870269d23b39faa12fb46d8/lib/internal/validators.js function isUint32 (value) { return value === (value >>> 0) } @@ -23,8 +23,24 @@ const validateAbortSignal = (signal, name) => { } } +const validateUint32 = (value, name, positive) => { + if (typeof value !== 'number') { + throw new TypeError(`Expected ${name} to be a number, got ${value}`) + } + if (!Number.isInteger(value)) { + throw new RangeError(`Expected ${name} to be an integer, got ${value}`) + } + const min = positive ? 1 : 0 + // 2 ** 32 === 4294967296 + const max = 4_294_967_295 + if (value < min || value > max) { + throw new RangeError(`Expected ${name} to be ${`>= ${min} && <= ${max}`}, got ${value}`) + } +} + module.exports = { isUint32, validateAbortSignal, - validateNumber + validateNumber, + validateUint32 } diff --git a/test/common/index.js b/test/common/index.js index d656ded..ae531e6 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -143,5 +143,7 @@ if (process.version.startsWith('v14.') || process.version.startsWith('v16.')) { } module.exports = { - expectsError + expectsError, + isWindow: process.platform === 'win32', + mustCall } diff --git a/test/fixtures/test-runner/never_ending_async.js b/test/fixtures/test-runner/never_ending_async.js new file mode 100644 index 0000000..a47e8b9 --- /dev/null +++ b/test/fixtures/test-runner/never_ending_async.js @@ -0,0 +1,7 @@ +// https://github.com/nodejs/node/blob/26e27424ad91c60a44d3d4c58b62a39b555ba75d/test/fixtures/test-runner/never_ending_async.js +const test = require('#node:test') +const { setTimeout } = require('#timers/promises') + +// We are using a very large timeout value to ensure that the parent process +// will have time to send a SIGINT signal to cancel the test. +test('never ending test', () => setTimeout(100_000_000)) diff --git a/test/fixtures/test-runner/never_ending_sync.js b/test/fixtures/test-runner/never_ending_sync.js new file mode 100644 index 0000000..2bc80a5 --- /dev/null +++ b/test/fixtures/test-runner/never_ending_sync.js @@ -0,0 +1,6 @@ +// https://github.com/nodejs/node/blob/26e27424ad91c60a44d3d4c58b62a39b555ba75d/test/fixtures/test-runner/never_ending_sync.js +const test = require('#node:test') + +test('never ending test', () => { + while (true); +}) diff --git a/test/message/test_runner_desctibe_it.js b/test/message/test_runner_desctibe_it.js index 51ad650..1690310 100644 --- a/test/message/test_runner_desctibe_it.js +++ b/test/message/test_runner_desctibe_it.js @@ -1,4 +1,4 @@ -// https://github.com/nodejs/node/blob/389b7e138e89a339fabe4ad628bf09cd9748f957/test/message/test_runner_desctibe_it.js +// https://github.com/nodejs/node/blob/a3e110820ff98702e1761831e7beaf0f5f1f75e7/test/message/test_runner_desctibe_it.js // Flags: --no-warnings 'use strict' require('../common') @@ -149,18 +149,6 @@ describe('level 0a', { concurrency: 4 }, () => { return p0a }) -describe('top level', { concurrency: 2 }, () => { - it('+long running', async () => { - return new Promise((resolve, reject) => { - setTimeout(resolve, 3000).unref() - }) - }) - - describe('+short running', async () => { - it('++short running', async () => {}) - }) -}) - describe('invalid subtest - pass but subtest fails', () => { setImmediate(() => { it('invalid subtest fail', () => { @@ -338,3 +326,47 @@ describe('timeouts', () => { setTimeout(done, 10) }) }) + +describe('successful thenable', () => { + it('successful thenable', () => { + let thenCalled = false + return { + get then () { + if (thenCalled) throw new Error() + thenCalled = true + return (successHandler) => successHandler() + } + } + }) + + it('rejected thenable', () => { + let thenCalled = false + return { + get then () { + if (thenCalled) throw new Error() + thenCalled = true + return (_, errorHandler) => errorHandler(new Error('custom error')) + } + } + }) + + let thenCalled = false + return { + get then () { + if (thenCalled) throw new Error() + thenCalled = true + return (successHandler) => successHandler() + } + } +}) + +describe('rejected thenable', () => { + let thenCalled = false + return { + get then () { + if (thenCalled) throw new Error() + thenCalled = true + return (_, errorHandler) => errorHandler(new Error('custom error')) + } + } +}) diff --git a/test/message/test_runner_desctibe_it.out b/test/message/test_runner_desctibe_it.out index 177dbff..2d58215 100644 --- a/test/message/test_runner_desctibe_it.out +++ b/test/message/test_runner_desctibe_it.out @@ -24,6 +24,7 @@ not ok 3 - sync fail todo # TODO * * * + * ... # Subtest: sync fail todo with message not ok 4 - sync fail todo with message # TODO this is a failing todo @@ -74,6 +75,7 @@ not ok 8 - sync throw fail * * * + * ... # Subtest: async skip pass ok 9 - async skip pass # SKIP @@ -100,6 +102,7 @@ not ok 11 - async throw fail * * * + * ... # Subtest: async skip fail not ok 12 - async skip fail @@ -128,6 +131,7 @@ not ok 13 - async assertion fail * * * + * ... # Subtest: resolve pass ok 14 - resolve pass @@ -149,6 +153,7 @@ not ok 15 - reject fail * * * + * ... # Subtest: unhandled rejection - passes but warns ok 16 - unhandled rejection - passes but warns @@ -239,45 +244,23 @@ ok 23 - level 0a --- duration_ms: * ... -# Subtest: top level - # Subtest: +long running - ok 1 - +long running - --- - duration_ms: * - ... - # Subtest: +short running - # Subtest: ++short running - ok 1 - ++short running - --- - duration_ms: * - ... - 1..1 - ok 2 - +short running - --- - duration_ms: * - ... - 1..2 -ok 24 - top level - --- - duration_ms: * - ... # Subtest: invalid subtest - pass but subtest fails -ok 25 - invalid subtest - pass but subtest fails +ok 24 - invalid subtest - pass but subtest fails --- duration_ms: * ... # Subtest: sync skip option -ok 26 - sync skip option # SKIP +ok 25 - sync skip option # SKIP --- duration_ms: * ... # Subtest: sync skip option with message -ok 27 - sync skip option with message # SKIP this is skipped +ok 26 - sync skip option with message # SKIP this is skipped --- duration_ms: * ... # Subtest: sync skip option is false fail -not ok 28 - sync skip option is false fail +not ok 27 - sync skip option is false fail --- duration_ms: * failureType: 'testCodeFailure' @@ -293,67 +276,67 @@ not ok 28 - sync skip option is false fail * ... # Subtest: -ok 29 - +ok 28 - --- duration_ms: * ... # Subtest: functionOnly -ok 30 - functionOnly +ok 29 - functionOnly --- duration_ms: * ... # Subtest: -ok 31 - +ok 30 - --- duration_ms: * ... # Subtest: test with only a name provided -ok 32 - test with only a name provided +ok 31 - test with only a name provided --- duration_ms: * ... # Subtest: -ok 33 - +ok 32 - --- duration_ms: * ... # Subtest: -ok 34 - # SKIP +ok 33 - # SKIP --- duration_ms: * ... # Subtest: test with a name and options provided -ok 35 - test with a name and options provided # SKIP +ok 34 - test with a name and options provided # SKIP --- duration_ms: * ... # Subtest: functionAndOptions -ok 36 - functionAndOptions # SKIP +ok 35 - functionAndOptions # SKIP --- duration_ms: * ... # Subtest: escaped description \\ \# \\\#\\ -ok 37 - escaped description \\ \# \\\#\\ +ok 36 - escaped description \\ \# \\\#\\ --- duration_ms: * ... # Subtest: escaped skip message -ok 38 - escaped skip message # SKIP \#skip +ok 37 - escaped skip message # SKIP \#skip --- duration_ms: * ... # Subtest: escaped todo message -ok 39 - escaped todo message # TODO \#todo +ok 38 - escaped todo message # TODO \#todo --- duration_ms: * ... # Subtest: callback pass -ok 40 - callback pass +ok 39 - callback pass --- duration_ms: * ... # Subtest: callback fail -not ok 41 - callback fail +not ok 40 - callback fail --- duration_ms: * failureType: 'testCodeFailure' @@ -364,22 +347,22 @@ not ok 41 - callback fail * ... # Subtest: sync t is this in test -ok 42 - sync t is this in test +ok 41 - sync t is this in test --- duration_ms: * ... # Subtest: async t is this in test -ok 43 - async t is this in test +ok 42 - async t is this in test --- duration_ms: * ... # Subtest: callback t is this in test -ok 44 - callback t is this in test +ok 43 - callback t is this in test --- duration_ms: * ... # Subtest: callback also returns a Promise -not ok 45 - callback also returns a Promise +not ok 44 - callback also returns a Promise --- duration_ms: * failureType: 'callbackAndPromisePresent' @@ -388,7 +371,7 @@ not ok 45 - callback also returns a Promise stack: |- ... # Subtest: callback throw -not ok 46 - callback throw +not ok 45 - callback throw --- duration_ms: * failureType: 'testCodeFailure' @@ -404,7 +387,7 @@ not ok 46 - callback throw * ... # Subtest: callback called twice -not ok 47 - callback called twice +not ok 46 - callback called twice --- duration_ms: * failureType: 'multipleCallbackInvocations' @@ -415,12 +398,12 @@ not ok 47 - callback called twice * ... # Subtest: callback called twice in different ticks -ok 48 - callback called twice in different ticks +ok 47 - callback called twice in different ticks --- duration_ms: * ... # Subtest: callback called twice in future tick -not ok 49 - callback called twice in future tick +not ok 48 - callback called twice in future tick --- duration_ms: * failureType: 'uncaughtException' @@ -430,7 +413,7 @@ not ok 49 - callback called twice in future tick * ... # Subtest: callback async throw -not ok 50 - callback async throw +not ok 49 - callback async throw --- duration_ms: * failureType: 'uncaughtException' @@ -440,12 +423,12 @@ not ok 50 - callback async throw * ... # Subtest: callback async throw after done -ok 51 - callback async throw after done +ok 50 - callback async throw after done --- duration_ms: * ... # Subtest: custom inspect symbol fail -not ok 52 - custom inspect symbol fail +not ok 51 - custom inspect symbol fail --- duration_ms: * failureType: 'testCodeFailure' @@ -454,7 +437,7 @@ not ok 52 - custom inspect symbol fail stack: |- ... # Subtest: custom inspect symbol that throws fail -not ok 53 - custom inspect symbol that throws fail +not ok 52 - custom inspect symbol that throws fail --- duration_ms: * failureType: 'testCodeFailure' @@ -506,7 +489,7 @@ not ok 53 - custom inspect symbol that throws fail * ... 1..2 -not ok 54 - subtest sync throw fails +not ok 53 - subtest sync throw fails --- duration_ms: * failureType: 'subtestsFailed' @@ -524,7 +507,7 @@ not ok 54 - subtest sync throw fails code: 'ERR_TEST_FAILURE' ... 1..1 -not ok 55 - describe sync throw fails +not ok 54 - describe sync throw fails --- duration_ms: * failureType: 'testCodeFailure' @@ -552,7 +535,7 @@ not ok 55 - describe sync throw fails code: 'ERR_TEST_FAILURE' ... 1..1 -not ok 56 - describe async throw fails +not ok 55 - describe async throw fails --- duration_ms: * failureType: 'testCodeFailure' @@ -579,7 +562,7 @@ not ok 56 - describe async throw fails error: 'test timed out after 5ms' code: 'ERR_TEST_FAILURE' stack: |- - * + async Promise.all (index 0) ... # Subtest: timed out callback test not ok 2 - timed out callback test @@ -600,15 +583,51 @@ not ok 56 - describe async throw fails duration_ms: * ... 1..4 -not ok 57 - timeouts +not ok 56 - timeouts --- duration_ms: * failureType: 'subtestsFailed' error: '2 subtests failed' code: 'ERR_TEST_FAILURE' ... +# Subtest: successful thenable + # Subtest: successful thenable + ok 1 - successful thenable + --- + duration_ms: * + ... + # Subtest: rejected thenable + not ok 2 - rejected thenable + --- + duration_ms: * + failureType: 'testCodeFailure' + error: 'custom error' + code: 'ERR_TEST_FAILURE' + stack: |- + * + * + ... + 1..2 +not ok 57 - successful thenable + --- + duration_ms: * + failureType: 'subtestsFailed' + error: '1 subtest failed' + code: 'ERR_TEST_FAILURE' + ... +# Subtest: rejected thenable +not ok 58 - rejected thenable + --- + duration_ms: * + failureType: 'testCodeFailure' + error: 'custom error' + code: 'ERR_TEST_FAILURE' + stack: |- + * + * + ... # Subtest: invalid subtest fail -not ok 58 - invalid subtest fail +not ok 59 - invalid subtest fail --- duration_ms: * failureType: 'parentAlreadyFinished' @@ -617,16 +636,16 @@ not ok 58 - invalid subtest fail stack: |- * ... -1..58 +1..59 # Warning: Test "unhandled rejection - passes but warns" generated asynchronous activity after the test ended. This activity created the error "Error: rejected from unhandled rejection fail" and would have caused the test to fail, but instead triggered an unhandledRejection event. # Warning: Test "async unhandled rejection - passes but warns" generated asynchronous activity after the test ended. This activity created the error "Error: rejected from async unhandled rejection fail" and would have caused the test to fail, but instead triggered an unhandledRejection event. # Warning: Test "immediate throw - passes but warns" generated asynchronous activity after the test ended. This activity created the error "Error: thrown from immediate throw fail" and would have caused the test to fail, but instead triggered an uncaughtException event. # 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. # 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. # 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. -# tests 58 -# pass 23 -# fail 21 +# tests 59 +# pass 22 +# fail 23 # cancelled 0 # skipped 9 # todo 5 diff --git a/test/message/test_runner_output.js b/test/message/test_runner_output.js index 583a650..e4edf0a 100644 --- a/test/message/test_runner_output.js +++ b/test/message/test_runner_output.js @@ -1,4 +1,4 @@ -// https://github.com/nodejs/node/blob/8cbc39068cd102e3bbdb6c5a1d14ce450e6ef093/test/message/test_runner_output.js +// https://github.com/nodejs/node/blob/a3e110820ff98702e1761831e7beaf0f5f1f75e7/test/message/test_runner_output.js // Flags: --no-warnings 'use strict' require('../common') @@ -349,3 +349,25 @@ test('large timeout async test is ok', { timeout: 30_000_000 }, async (t) => { test('large timeout callback test is ok', { timeout: 30_000_000 }, (t, done) => { setTimeout(done, 10) }) + +test('successful thenable', () => { + let thenCalled = false + return { + get then () { + if (thenCalled) throw new Error() + thenCalled = true + return (successHandler) => successHandler() + } + } +}) + +test('rejected thenable', () => { + let thenCalled = false + return { + get then () { + if (thenCalled) throw new Error() + thenCalled = true + return (_, errorHandler) => errorHandler('custom error') + } + } +}) diff --git a/test/message/test_runner_output.out b/test/message/test_runner_output.out index 4e987f8..49a19fe 100644 --- a/test/message/test_runner_output.out +++ b/test/message/test_runner_output.out @@ -588,8 +588,21 @@ ok 60 - large timeout callback test is ok --- duration_ms: * ... +# Subtest: successful thenable +ok 61 - successful thenable + --- + duration_ms: * + ... +# Subtest: rejected thenable +not ok 62 - rejected thenable + --- + duration_ms: * + failureType: 'testCodeFailure' + error: 'custom error' + code: 'ERR_TEST_FAILURE' + ... # Subtest: invalid subtest fail -not ok 61 - invalid subtest fail +not ok 63 - invalid subtest fail --- duration_ms: * failureType: 'parentAlreadyFinished' @@ -598,16 +611,16 @@ not ok 61 - invalid subtest fail stack: |- * ... -1..61 +1..63 # Warning: Test "unhandled rejection - passes but warns" generated asynchronous activity after the test ended. This activity created the error "Error: rejected from unhandled rejection fail" and would have caused the test to fail, but instead triggered an unhandledRejection event. # Warning: Test "async unhandled rejection - passes but warns" generated asynchronous activity after the test ended. This activity created the error "Error: rejected from async unhandled rejection fail" and would have caused the test to fail, but instead triggered an unhandledRejection event. # Warning: Test "immediate throw - passes but warns" generated asynchronous activity after the test ended. This activity created the error "Error: thrown from immediate throw fail" and would have caused the test to fail, but instead triggered an uncaughtException event. # 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. # 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. # 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. -# tests 61 -# pass 26 -# fail 18 +# tests 63 +# pass 27 +# fail 19 # cancelled 2 # skipped 10 # todo 5 diff --git a/test/parallel/test-runner-concurrency.js b/test/parallel/test-runner-concurrency.js index 7b811a0..763be40 100644 --- a/test/parallel/test-runner-concurrency.js +++ b/test/parallel/test-runner-concurrency.js @@ -1,8 +1,8 @@ -// https://github.com/nodejs/node/blob/dab492f0444b0a6ae8a41dd1d9605e036c363655/test/parallel/test-runner-concurrency.js +// https://github.com/nodejs/node/blob/a3e110820ff98702e1761831e7beaf0f5f1f75e7/test/parallel/test-runner-concurrency.js 'use strict' -require('../common') -const { describe, it } = require('node:test') +const common = require('../common') +const { describe, it, test } = require('#node:test') const assert = require('assert') describe('Concurrency option (boolean) = true ', { concurrency: true }, () => { @@ -29,3 +29,38 @@ describe( }) } ) + +{ + // Make sure tests run in order when root concurrency is 1 (default) + const tree = [] + const expectedTestTree = common.mustCall(() => { + assert.deepStrictEqual(tree, [ + 'suite 1', 'nested', 'suite 2', + '1', '2', 'nested 1', 'nested 2', + 'test', 'test 1', 'test 2' + ]) + }) + + describe('suite 1', () => { + tree.push('suite 1') + it('1', () => tree.push('1')) + it('2', () => tree.push('2')) + + describe('nested', () => { + tree.push('nested') + it('nested 1', () => tree.push('nested 1')) + it('nested 2', () => tree.push('nested 2')) + }) + }) + + test('test', async (t) => { + tree.push('test') + await t.test('test1', () => tree.push('test 1')) + await t.test('test 2', () => tree.push('test 2')) + }) + + describe('suite 2', () => { + tree.push('suite 2') + it('should run after other suites', expectedTestTree) + }) +} diff --git a/test/parallel/test-runner-exit-code.js b/test/parallel/test-runner-exit-code.js index 7e407ee..014d381 100644 --- a/test/parallel/test-runner-exit-code.js +++ b/test/parallel/test-runner-exit-code.js @@ -1,13 +1,31 @@ -// https://github.com/nodejs/node/blob/2fd4c013c221653da2a7921d08fe1aa96aaba504/test/parallel/test-runner-exit-code.js - +// https://github.com/nodejs/node/blob/26e27424ad91c60a44d3d4c58b62a39b555ba75d/test/parallel/test-runner-exit-code.js 'use strict' - const common = require('../common') const fixtures = require('../common/fixtures') const assert = require('assert') -const { spawnSync } = require('child_process') -const { promisify } = require('util') -const setTimeout = promisify(require('timers').setTimeout) +const { spawnSync, spawn } = require('child_process') +const { once } = require('events') +const finished = require('util').promisify(require('stream').finished) + +async function runAndKill (file) { + if (common.isWindows) { + common.printSkipMessage(`signals are not supported in windows, skipping ${file}`) + return + } + let stdout = '' + const child = spawn(process.execPath, ['--test', file]) + child.stdout.setEncoding('utf8') + child.stdout.on('data', (chunk) => { + if (!stdout.length) child.kill('SIGINT') + stdout += chunk + }) + const [code, signal] = await once(child, 'exit') + await finished(child.stdout) + assert.match(stdout, /not ok 1/) + assert.match(stdout, /# cancelled 1\n/) + assert.strictEqual(signal, null) + assert.strictEqual(code, 1) +} if (process.argv[2] === 'child') { const test = require('#node:test') @@ -21,12 +39,6 @@ if (process.argv[2] === 'child') { test('failing test', () => { assert.strictEqual(true, false) }) - } else if (process.argv[3] === 'never_ends') { - assert.strictEqual(process.argv[3], 'never_ends') - test('never ending test', () => { - return setTimeout(100_000_000) - }) - process.kill(process.pid, 'SIGINT') } else assert.fail('unreachable') } else { let child = spawnSync(process.execPath, [__filename, 'child', 'pass']) @@ -41,14 +53,6 @@ if (process.argv[2] === 'child') { assert.strictEqual(child.status, 1) assert.strictEqual(child.signal, null) - child = spawnSync(process.execPath, [__filename, 'child', 'never_ends']) - assert.strictEqual(child.status, 1) - assert.strictEqual(child.signal, null) - if (common.isWindows) { - common.printSkipMessage('signals are not supported in windows') - } else { - const stdout = child.stdout.toString() - assert.match(stdout, /not ok 1 - never ending test/) - assert.match(stdout, /# cancelled 1/) - } + runAndKill(fixtures.path('test-runner', 'never_ending_sync.js')).then(common.mustCall()) + runAndKill(fixtures.path('test-runner', 'never_ending_async.js')).then(common.mustCall()) } diff --git a/test/parallel/test-runner-option-validation.js b/test/parallel/test-runner-option-validation.js index 3f12a07..74123b6 100644 --- a/test/parallel/test-runner-option-validation.js +++ b/test/parallel/test-runner-option-validation.js @@ -1,4 +1,4 @@ -// https://github.com/nodejs/node/blob/d83446b4c4694322e12d2b7d22592f2be674e580/test/parallel/test-runner-option-validation.js +// https://github.com/nodejs/node/blob/60da0a1b364efdd84870269d23b39faa12fb46d8/test/parallel/test-runner-option-validation.js 'use strict' require('../common') @@ -15,4 +15,16 @@ const test = require('node:test'); [null, undefined, Infinity, 0, 1, 1.1].forEach((timeout) => { // Valid values should not throw. test({ timeout }) +}); + +// eslint-disable-next-line symbol-description +[Symbol(), {}, [], () => {}, 1n, '1'].forEach((concurrency) => { + assert.throws(() => test({ concurrency }), { code: 'ERR_INVALID_ARG_TYPE' }) +}); +[-1, 0, 1.1, -Infinity, NaN, 2 ** 33, Number.MAX_SAFE_INTEGER].forEach((concurrency) => { + assert.throws(() => test({ concurrency }), { code: 'ERR_OUT_OF_RANGE' }) +}); +[null, undefined, 1, 2 ** 31, true, false].forEach((concurrency) => { + // Valid values should not throw. + test({ concurrency }) })