fix(node/test): basic support for t.skip and t.todo (#29222)

Adds basic support for `t.skip` and `t.todo`
This commit is contained in:
David Sherret 2025-05-08 18:40:16 -05:00 committed by GitHub
parent 9188c79ab4
commit cd877fd16f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 171 additions and 163 deletions

View file

@ -7,8 +7,9 @@ const {
ArrayPrototypeForEach, ArrayPrototypeForEach,
SafePromiseAll, SafePromiseAll,
SafePromisePrototypeFinally, SafePromisePrototypeFinally,
Symbol,
} = primordials; } = primordials;
import { notImplemented, warnNotImplemented } from "ext:deno_node/_utils.ts"; import { notImplemented } from "ext:deno_node/_utils.ts";
import assert from "node:assert"; import assert from "node:assert";
const methodsToCopy = [ const methodsToCopy = [
@ -49,11 +50,20 @@ export function run() {
function noop() {} function noop() {}
const skippedSymbol = Symbol("skipped");
class NodeTestContext { class NodeTestContext {
#denoContext: Deno.TestContext; #denoContext: Deno.TestContext;
#parent: NodeTestContext | undefined;
#skipped = false;
constructor(t: Deno.TestContext) { constructor(t: Deno.TestContext, parent: NodeTestContext | undefined) {
this.#denoContext = t; this.#denoContext = t;
this.#parent = parent;
}
get [skippedSymbol]() {
return this.#skipped || (this.#parent?.[skippedSymbol] ?? false);
} }
get assert() { get assert() {
@ -86,23 +96,34 @@ class NodeTestContext {
} }
skip() { skip() {
warnNotImplemented("test.TestContext.skip"); this.#skipped = true;
return null; return null;
} }
todo() { todo() {
warnNotImplemented("test.TestContext.todo"); this.#skipped = true;
return null; return null;
} }
test(name, options, fn) { test(name, options, fn) {
const prepared = prepareOptions(name, options, fn, {}); const prepared = prepareOptions(name, options, fn, {});
// deno-lint-ignore no-this-alias
const parentContext = this;
return PromisePrototypeThen( return PromisePrototypeThen(
this.#denoContext.step({ this.#denoContext.step({
name: prepared.name, name: prepared.name,
fn: async (denoTestContext) => { fn: async (denoTestContext) => {
const newNodeTextContext = new NodeTestContext(denoTestContext); const newNodeTextContext = new NodeTestContext(
await prepared.fn(newNodeTextContext); denoTestContext,
parentContext,
);
try {
await prepared.fn(newNodeTextContext);
} catch (err) {
if (!newNodeTextContext[skippedSymbol]) {
throw err;
}
}
}, },
ignore: prepared.options.todo || prepared.options.skip, ignore: prepared.options.todo || prepared.options.skip,
sanitizeExit: false, sanitizeExit: false,
@ -144,9 +165,20 @@ class TestSuite {
const prepared = prepareOptions(name, options, fn, overrides); const prepared = prepareOptions(name, options, fn, overrides);
const step = this.#denoTestContext.step({ const step = this.#denoTestContext.step({
name: prepared.name, name: prepared.name,
fn: (denoTestContext) => { fn: async (denoTestContext) => {
const newNodeTextContext = new NodeTestContext(denoTestContext); const newNodeTextContext = new NodeTestContext(
return prepared.fn(newNodeTextContext); denoTestContext,
undefined,
);
try {
return await prepared.fn(newNodeTextContext);
} catch (err) {
if (newNodeTextContext[skippedSymbol]) {
return undefined;
} else {
throw err;
}
}
}, },
ignore: prepared.options.todo || prepared.options.skip, ignore: prepared.options.todo || prepared.options.skip,
sanitizeExit: false, sanitizeExit: false,
@ -204,9 +236,13 @@ function prepareOptions(name, options, fn, overrides) {
function wrapTestFn(fn, resolve) { function wrapTestFn(fn, resolve) {
return async function (t) { return async function (t) {
const nodeTestContext = new NodeTestContext(t); const nodeTestContext = new NodeTestContext(t, undefined);
try { try {
await fn(nodeTestContext); await fn(nodeTestContext);
} catch (err) {
if (!nodeTestContext[skippedSymbol]) {
throw err;
}
} finally { } finally {
resolve(); resolve();
} }

View file

@ -22,6 +22,13 @@ test("sync fail todo", (t) => {
throw new Error("thrown from sync fail todo"); throw new Error("thrown from sync fail todo");
}); });
test("todo thrown sub test", async (t) => {
t.todo("this is a todo test and is not treated as a failure");
await t.test("test", () => {
throw new Error("this does not fail the test");
});
});
test("sync fail todo with message", (t) => { test("sync fail todo with message", (t) => {
t.todo("this is a failing todo"); t.todo("this is a failing todo");
throw new Error("thrown from sync fail todo with message"); throw new Error("thrown from sync fail todo with message");
@ -35,6 +42,13 @@ test("sync skip pass with message", (t) => {
t.skip("this is skipped"); t.skip("this is skipped");
}); });
test("skip thrown sub test", async (t) => {
t.skip("this is a skip test and is not treated as a failure");
await t.test("test", () => {
throw new Error("this does not fail the test");
});
});
test("sync pass", (t) => { test("sync pass", (t) => {
t.diagnostic("this test should pass"); t.diagnostic("this test should pass");
}); });

View file

@ -1,61 +1,35 @@
[WILDCARD] [WILDCARD]
running 67 tests from ./test.js running 69 tests from ./test.js
sync pass todo ... sync pass todo ... ok ([WILDLINE])
------- output ------- sync pass todo with message ... ok ([WILDLINE])
Warning: Not implemented: test.TestContext.todo sync fail todo ... ok ([WILDLINE])
----- output end ----- todo thrown sub test ...
sync pass todo ... ok [WILDCARD] test ... ok ([WILDLINE])
sync pass todo with message ... todo thrown sub test ... ok ([WILDLINE])
------- output ------- sync fail todo with message ... ok ([WILDLINE])
Warning: Not implemented: test.TestContext.todo sync skip pass ... ok ([WILDLINE])
----- output end ----- sync skip pass with message ... ok ([WILDLINE])
sync pass todo with message ... ok [WILDCARD] skip thrown sub test ...
sync fail todo ... test ... ok ([WILDLINE])
------- output ------- skip thrown sub test ... ok ([WILDLINE])
Warning: Not implemented: test.TestContext.todo
----- output end -----
sync fail todo ... FAILED [WILDCARD]
sync fail todo with message ...
------- output -------
Warning: Not implemented: test.TestContext.todo
----- output end -----
sync fail todo with message ... FAILED [WILDCARD]
sync skip pass ...
------- output -------
Warning: Not implemented: test.TestContext.skip
----- output end -----
sync skip pass ... ok [WILDCARD]
sync skip pass with message ...
------- output -------
Warning: Not implemented: test.TestContext.skip
----- output end -----
sync skip pass with message ... ok [WILDCARD]
sync pass ... sync pass ...
------- output ------- ------- output -------
DIAGNOSTIC: this test should pass DIAGNOSTIC: this test should pass
----- output end ----- ----- output end -----
sync pass ... ok [WILDCARD] sync pass ... ok ([WILDLINE])
sync throw fail ... FAILED [WILDCARD] sync throw fail ... FAILED ([WILDLINE])
async skip pass ... async skip pass ... ok ([WILDLINE])
------- output ------- async pass ... ok ([WILDLINE])
Warning: Not implemented: test.TestContext.skip async throw fail ... FAILED ([WILDLINE])
----- output end -----
async skip pass ... ok [WILDCARD]
async pass ... ok [WILDCARD]
async throw fail ... FAILED [WILDCARD]
nested test ... nested test ...
nested 1 ... nested 1 ...
nested 2 ... ok [WILDCARD] nested 2 ... ok ([WILDLINE])
nested 1 ... ok [WILDCARD] nested 1 ... ok ([WILDLINE])
nested test ... ok [WILDCARD] nested test ... ok ([WILDLINE])
async skip fail ... async skip fail ... ok ([WILDLINE])
------- output ------- async assertion fail ... FAILED ([WILDLINE])
Warning: Not implemented: test.TestContext.skip resolve pass ... ok ([WILDLINE])
----- output end ----- reject fail ... FAILED ([WILDLINE])
async skip fail ... FAILED [WILDCARD]
async assertion fail ... FAILED [WILDCARD]
resolve pass ... ok [WILDCARD]
reject fail ... FAILED [WILDCARD]
suite ... suite ...
test 1 ... ok ([WILDLINE]) test 1 ... ok ([WILDLINE])
test 2 ... ok ([WILDLINE]) test 2 ... ok ([WILDLINE])
@ -95,82 +69,69 @@ suite ... FAILED (due to 3 failed steps) ([WILDLINE])
assertions available via text context ... ok ([WILDLINE]) assertions available via text context ... ok ([WILDLINE])
unhandled rejection - passes but warns ... unhandled rejection - passes but warns ...
Uncaught error from ./test.js FAILED Uncaught error from ./test.js FAILED
unhandled rejection - passes but warns ... cancelled ([WILDCARD]) unhandled rejection - passes but warns ... cancelled ([WILDLINE])
async unhandled rejection - passes but warns ... cancelled ([WILDCARD]) async unhandled rejection - passes but warns ... cancelled ([WILDLINE])
immediate throw - passes but warns ... cancelled ([WILDCARD]) immediate throw - passes but warns ... cancelled ([WILDLINE])
immediate reject - passes but warns ... cancelled ([WILDCARD]) immediate reject - passes but warns ... cancelled ([WILDLINE])
immediate resolve pass ... cancelled ([WILDCARD]) immediate resolve pass ... cancelled ([WILDLINE])
subtest sync throw fail ... cancelled ([WILDCARD]) subtest sync throw fail ... cancelled ([WILDLINE])
sync throw non-error fail ... cancelled ([WILDCARD]) sync throw non-error fail ... cancelled ([WILDLINE])
level 0a ... cancelled ([WILDCARD]) level 0a ... cancelled ([WILDLINE])
top level ... cancelled ([WILDCARD]) top level ... cancelled ([WILDLINE])
invalid subtest - pass but subtest fails ... cancelled ([WILDCARD]) invalid subtest - pass but subtest fails ... cancelled ([WILDLINE])
sync skip option ... ignored ([WILDCARD]) sync skip option ... ignored ([WILDLINE])
sync skip option with message ... cancelled ([WILDCARD]) sync skip option with message ... cancelled ([WILDLINE])
sync skip option is false fail ... cancelled ([WILDCARD]) sync skip option is false fail ... cancelled ([WILDLINE])
noop ... cancelled ([WILDCARD]) noop ... cancelled ([WILDLINE])
functionOnly ... cancelled ([WILDCARD]) functionOnly ... cancelled ([WILDLINE])
<anonymous> ... cancelled ([WILDCARD]) <anonymous> ... cancelled ([WILDLINE])
test with only a name provided ... cancelled ([WILDCARD]) test with only a name provided ... cancelled ([WILDLINE])
noop ... cancelled ([WILDCARD]) noop ... cancelled ([WILDLINE])
noop ... ignored ([WILDCARD]) noop ... ignored ([WILDLINE])
test with a name and options provided ... ignored ([WILDCARD]) test with a name and options provided ... ignored ([WILDLINE])
functionAndOptions ... ignored ([WILDCARD]) functionAndOptions ... ignored ([WILDLINE])
escaped skip message ... cancelled ([WILDCARD]) escaped skip message ... cancelled ([WILDLINE])
escaped todo message ... cancelled ([WILDCARD]) escaped todo message ... cancelled ([WILDLINE])
escaped diagnostic ... cancelled ([WILDCARD]) escaped diagnostic ... cancelled ([WILDLINE])
callback pass ... cancelled ([WILDCARD]) callback pass ... cancelled ([WILDLINE])
callback fail ... cancelled ([WILDCARD]) callback fail ... cancelled ([WILDLINE])
sync t is this in test ... cancelled ([WILDCARD]) sync t is this in test ... cancelled ([WILDLINE])
async t is this in test ... cancelled ([WILDCARD]) async t is this in test ... cancelled ([WILDLINE])
callback t is this in test ... cancelled ([WILDCARD]) callback t is this in test ... cancelled ([WILDLINE])
callback also returns a Promise ... cancelled ([WILDCARD]) callback also returns a Promise ... cancelled ([WILDLINE])
callback throw ... cancelled ([WILDCARD]) callback throw ... cancelled ([WILDLINE])
callback called twice ... cancelled ([WILDCARD]) callback called twice ... cancelled ([WILDLINE])
callback called twice in different ticks ... cancelled ([WILDCARD]) callback called twice in different ticks ... cancelled ([WILDLINE])
callback called twice in future tick ... cancelled ([WILDCARD]) callback called twice in future tick ... cancelled ([WILDLINE])
callback async throw ... cancelled ([WILDCARD]) callback async throw ... cancelled ([WILDLINE])
callback async throw after done ... cancelled ([WILDCARD]) callback async throw after done ... cancelled ([WILDLINE])
custom inspect symbol fail ... cancelled ([WILDCARD]) custom inspect symbol fail ... cancelled ([WILDLINE])
custom inspect symbol that throws fail ... cancelled ([WILDCARD]) custom inspect symbol that throws fail ... cancelled ([WILDLINE])
subtest sync throw fails ... cancelled ([WILDCARD]) subtest sync throw fails ... cancelled ([WILDLINE])
timed out async test ... cancelled ([WILDCARD]) timed out async test ... cancelled ([WILDLINE])
timed out callback test ... cancelled ([WILDCARD]) timed out callback test ... cancelled ([WILDLINE])
large timeout async test is ok ... cancelled ([WILDCARD]) large timeout async test is ok ... cancelled ([WILDLINE])
large timeout callback test is ok ... cancelled ([WILDCARD]) large timeout callback test is ok ... cancelled ([WILDLINE])
successful thenable ... cancelled ([WILDCARD]) successful thenable ... cancelled ([WILDLINE])
rejected thenable ... cancelled ([WILDCARD]) rejected thenable ... cancelled ([WILDLINE])
unfinished test with uncaughtException ... cancelled ([WILDCARD]) unfinished test with uncaughtException ... cancelled ([WILDLINE])
unfinished test with unhandledRejection ... cancelled ([WILDCARD]) unfinished test with unhandledRejection ... cancelled ([WILDLINE])
ERRORS ERRORS
sync fail todo => ./test.js:20:1 sync throw fail => [WILDLINE]
error: Error: thrown from sync fail todo
throw new Error("thrown from sync fail todo");
[WILDCARD]
sync fail todo with message => ./test.js:25:1
error: Error: thrown from sync fail todo with message
throw new Error("thrown from sync fail todo with message");
[WILDCARD]
sync throw fail => ./test.js:42:1
error: Error: thrown from sync throw fail error: Error: thrown from sync throw fail
throw new Error("thrown from sync throw fail"); throw new Error("thrown from sync throw fail");
[WILDCARD] ^
at [WILDCARD]
async throw fail => ./test.js:53:1 async throw fail => [WILDLINE]
error: Error: thrown from async throw fail error: Error: thrown from async throw fail
throw new Error("thrown from async throw fail"); throw new Error("thrown from async throw fail");
[WILDCARD] ^
at [WILDCARD]
async skip fail => ./test.js:64:1 async assertion fail => [WILDLINE]
error: Error: thrown from async throw fail
throw new Error("thrown from async throw fail");
[WILDCARD]
async assertion fail => ./test.js:69:1
error: AssertionError: Values are not strictly equal: error: AssertionError: Values are not strictly equal:
@ -182,25 +143,25 @@ error: AssertionError: Values are not strictly equal:
at [WILDCARD] at [WILDCARD]
reject fail => ./test.js:78:1 reject fail => [WILDLINE]
error: Error: rejected from reject fail error: Error: rejected from reject fail
return Promise.reject(new Error("rejected from reject fail")); return Promise.reject(new Error("rejected from reject fail"));
^ ^
at [WILDCARD] at [WILDCARD]
suite ... test 2 => ./test.js:[WILDLINE] suite ... test 2 => [WILDLINE]
error: Error: thrown from test 2 error: Error: thrown from test 2
throw new Error("thrown from test 2"); throw new Error("thrown from test 2");
^ ^
at [WILDCARD] at [WILDCARD]
suite ... sub suite 1 ... nested test 2 => ./test.js:[WILDLINE] suite ... sub suite 1 ... nested test 2 => [WILDLINE]
error: Error: thrown from nested test 2 error: Error: thrown from nested test 2
throw new Error("thrown from nested test 2"); throw new Error("thrown from nested test 2");
^ ^
at [WILDCARD] at [WILDCARD]
suite ... sub suite 2 ... nested test 1 => ./test.js:[WILDLINE] suite ... sub suite 2 ... nested test 1 => [WILDLINE]
error: Error: thrown from nested test 1 error: Error: thrown from nested test 1
throw new Error("thrown from nested test 1"); throw new Error("thrown from nested test 1");
^ ^
@ -216,18 +177,15 @@ It most likely originated from a dangling promise, event/timeout handler or top-
FAILURES FAILURES
sync fail todo => ./test.js:20:1 sync throw fail => [WILDLINE]
sync fail todo with message => ./test.js:25:1 async throw fail => [WILDLINE]
sync throw fail => ./test.js:42:1 async assertion fail => [WILDLINE]
async throw fail => ./test.js:53:1 reject fail => [WILDLINE]
async skip fail => ./test.js:64:1 suite ... test 2 => [WILDLINE]
async assertion fail => ./test.js:69:1 suite ... sub suite 1 ... nested test 2 => [WILDLINE]
reject fail => ./test.js:78:1 suite ... sub suite 2 ... nested test 1 => [WILDLINE]
suite ... test 2 => ./test.js:[WILDLINE]
suite ... sub suite 1 ... nested test 2 => ./test.js:[WILDLINE]
suite ... sub suite 2 ... nested test 1 => ./test.js:[WILDLINE]
./test.js (uncaught error) ./test.js (uncaught error)
FAILED | 12 passed (21 steps) | 52 failed (5 steps) | 4 ignored [WILDCARD] FAILED | 17 passed (23 steps) | 49 failed (5 steps) | 4 ignored ([WILDLINE])
error: Test failed error: Test failed

View file

@ -1,40 +1,40 @@
running 6 tests from [WILDCARD]/main.ts running 6 tests from [WILDLINE]/main.ts
foo ... FAILED ([WILDCARD]) foo ... FAILED ([WILDLINE])
bar ... FAILED ([WILDCARD]) bar ... FAILED ([WILDLINE])
baz ... FAILED ([WILDCARD]) baz ... FAILED ([WILDLINE])
qux ... FAILED ([WILDCARD]) qux ... FAILED ([WILDLINE])
quux ... FAILED ([WILDCARD]) quux ... FAILED ([WILDLINE])
quuz ... FAILED ([WILDCARD]) quuz ... FAILED ([WILDLINE])
ERRORS ERRORS
foo => [WILDCARD]/main.ts:1:6 foo => [WILDLINE]/main.ts:1:6
error: undefined error: undefined
bar => [WILDCARD]/main.ts:5:6 bar => [WILDLINE]/main.ts:5:6
error: null error: null
baz => [WILDCARD]/main.ts:9:6 baz => [WILDLINE]/main.ts:9:6
error: 123 error: 123
qux => [WILDCARD]/main.ts:13:6 qux => [WILDLINE]/main.ts:13:6
error: "Hello, world!" error: "Hello, world!"
quux => [WILDCARD]/main.ts:17:6 quux => [WILDLINE]/main.ts:17:6
error: [ 1, 2, 3 ] error: [ 1, 2, 3 ]
quuz => [WILDCARD]/main.ts:21:6 quuz => [WILDLINE]/main.ts:21:6
error: { a: "Hello, world!", b: [ 1, 2, 3 ] } error: { a: "Hello, world!", b: [ 1, 2, 3 ] }
FAILURES FAILURES
foo => [WILDCARD]/main.ts:1:6 foo => [WILDLINE]/main.ts:1:6
bar => [WILDCARD]/main.ts:5:6 bar => [WILDLINE]/main.ts:5:6
baz => [WILDCARD]/main.ts:9:6 baz => [WILDLINE]/main.ts:9:6
qux => [WILDCARD]/main.ts:13:6 qux => [WILDLINE]/main.ts:13:6
quux => [WILDCARD]/main.ts:17:6 quux => [WILDLINE]/main.ts:17:6
quuz => [WILDCARD]/main.ts:21:6 quuz => [WILDLINE]/main.ts:21:6
FAILED | 0 passed | 6 failed ([WILDCARD]) FAILED | 0 passed | 6 failed ([WILDLINE])
error: Test failed error: Test failed