1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-11 08:33:43 -05:00

feat(node): add polyfill for node:test module (#20002)

This commit provides basic polyfill for "node:test" module. Currently
only top-level "test" function is polyfilled, all remaining functions from
that module throw not implemented errors.
This commit is contained in:
Bartek Iwańczuk 2023-08-02 01:17:38 +02:00 committed by GitHub
parent 00b5fe8ba3
commit 21f1b2f62b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 806 additions and 0 deletions

View file

@ -17,3 +17,9 @@ fn node_compat_tests() {
assert_eq!(Some(0), status.code());
assert!(status.success());
}
itest!(node_test_module {
args: "test node/test.js",
output: "node/test.out",
exit_code: 1,
});

383
cli/tests/testdata/node/test.js vendored Normal file
View file

@ -0,0 +1,383 @@
// Copyright Joyent, Inc. and other Node contributors.
// Ported from https://github.com/nodejs/node/blob/d396a041f71cc055ad60b0abc63ad81c0ee6a574/test/fixtures/test-runner/output/output.js
// deno-lint-ignore-file
import assert from "node:assert";
import test from "node:test";
import util from "node:util";
import { setImmediate } from "node:timers";
test("sync pass todo", (t) => {
t.todo();
});
test("sync pass todo with message", (t) => {
t.todo("this is a passing todo");
});
test("sync fail todo", (t) => {
t.todo();
throw new Error("thrown from sync fail todo");
});
test("sync fail todo with message", (t) => {
t.todo("this is a failing todo");
throw new Error("thrown from sync fail todo with message");
});
test("sync skip pass", (t) => {
t.skip();
});
test("sync skip pass with message", (t) => {
t.skip("this is skipped");
});
test("sync pass", (t) => {
t.diagnostic("this test should pass");
});
test("sync throw fail", () => {
throw new Error("thrown from sync throw fail");
});
test("async skip pass", async (t) => {
t.skip();
});
test("async pass", async () => {
});
test("async throw fail", async () => {
throw new Error("thrown from async throw fail");
});
test("async skip fail", async (t) => {
t.skip();
throw new Error("thrown from async throw fail");
});
test("async assertion fail", async () => {
// Make sure the assert module is handled.
assert.strictEqual(true, false);
});
test("resolve pass", () => {
return Promise.resolve();
});
test("reject fail", () => {
return Promise.reject(new Error("rejected from reject fail"));
});
test("unhandled rejection - passes but warns", () => {
Promise.reject(new Error("rejected from unhandled rejection fail"));
});
test("async unhandled rejection - passes but warns", async () => {
Promise.reject(new Error("rejected from async unhandled rejection fail"));
});
test("immediate throw - passes but warns", () => {
setImmediate(() => {
throw new Error("thrown from immediate throw fail");
});
});
test("immediate reject - passes but warns", () => {
setImmediate(() => {
Promise.reject(new Error("rejected from immediate reject fail"));
});
});
test("immediate resolve pass", () => {
return new Promise((resolve) => {
setImmediate(() => {
resolve();
});
});
});
test("subtest sync throw fail", async (t) => {
await t.test("+sync throw fail", (t) => {
t.diagnostic("this subtest should make its parent test fail");
throw new Error("thrown from subtest sync throw fail");
});
});
test("sync throw non-error fail", async (t) => {
throw Symbol("thrown symbol from sync throw non-error fail");
});
test("level 0a", { concurrency: 4 }, async (t) => {
t.test("level 1a", async (t) => {
const p1a = new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 100);
});
return p1a;
});
test("level 1b", async (t) => {
const p1b = new Promise((resolve) => {
resolve();
});
return p1b;
});
t.test("level 1c", async (t) => {
const p1c = new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 200);
});
return p1c;
});
t.test("level 1d", async (t) => {
const p1c = new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 150);
});
return p1c;
});
const p0a = new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 300);
});
return p0a;
});
test("top level", { concurrency: 2 }, async (t) => {
t.test("+long running", async (t) => {
return new Promise((resolve, reject) => {
setTimeout(resolve, 300).unref();
});
});
t.test("+short running", async (t) => {
t.test("++short running", async (t) => {});
});
});
test("invalid subtest - pass but subtest fails", (t) => {
setImmediate(() => {
t.test("invalid subtest fail", () => {
throw new Error("this should not be thrown");
});
});
});
test("sync skip option", { skip: true }, (t) => {
throw new Error("this should not be executed");
});
test("sync skip option with message", { skip: "this is skipped" }, (t) => {
throw new Error("this should not be executed");
});
test("sync skip option is false fail", { skip: false }, (t) => {
throw new Error("this should be executed");
});
// A test with no arguments provided.
test();
// A test with only a named function provided.
test(function functionOnly() {});
// A test with only an anonymous function provided.
test(() => {});
// A test with only a name provided.
test("test with only a name provided");
// A test with an empty string name.
test("");
// A test with only options provided.
test({ skip: true });
// A test with only a name and options provided.
test("test with a name and options provided", { skip: true });
// A test with only options and a function provided.
test({ skip: true }, function functionAndOptions() {});
// A test whose description needs to be escaped.
// test("escaped description \\ # \\#\\ \n \t \f \v \b \r");
// A test whose skip message needs to be escaped.
test("escaped skip message", { skip: "#skip" });
// A test whose todo message needs to be escaped.
test("escaped todo message", { todo: "#todo" });
// A test with a diagnostic message that needs to be escaped.
test("escaped diagnostic", (t) => {
t.diagnostic("#diagnostic");
});
test("callback pass", (t, done) => {
setImmediate(done);
});
test("callback fail", (t, done) => {
setImmediate(() => {
done(new Error("callback failure"));
});
});
test("sync t is this in test", function (t) {
assert.strictEqual(this, t);
});
test("async t is this in test", async function (t) {
assert.strictEqual(this, t);
});
test("callback t is this in test", function (t, done) {
assert.strictEqual(this, t);
done();
});
test("callback also returns a Promise", async (t, done) => {
throw new Error("thrown from callback also returns a Promise");
});
test("callback throw", (t, done) => {
throw new Error("thrown from callback throw");
});
test("callback called twice", (t, done) => {
done();
done();
});
test("callback called twice in different ticks", (t, done) => {
setImmediate(done);
done();
});
test("callback called twice in future tick", (t, done) => {
setImmediate(() => {
done();
done();
});
});
test("callback async throw", (t, done) => {
setImmediate(() => {
throw new Error("thrown from callback async throw");
});
});
test("callback async throw after done", (t, done) => {
setImmediate(() => {
throw new Error("thrown from callback async throw after done");
});
done();
});
test("custom inspect symbol fail", () => {
const obj = {
[util.inspect.custom]() {
return "customized";
},
foo: 1,
};
throw obj;
});
test("custom inspect symbol that throws fail", () => {
const obj = {
[util.inspect.custom]() {
throw new Error("bad-inspect");
},
foo: 1,
};
throw obj;
});
test("subtest sync throw fails", async (t) => {
await t.test("sync throw fails at first", (t) => {
throw new Error("thrown from subtest sync throw fails at first");
});
await t.test("sync throw fails at second", (t) => {
throw new Error("thrown from subtest sync throw fails at second");
});
});
test("timed out async test", { timeout: 5 }, async (t) => {
return new Promise((resolve) => {
setTimeout(resolve, 100);
});
});
test("timed out callback test", { timeout: 5 }, (t, done) => {
setTimeout(done, 100);
});
test("large timeout async test is ok", { timeout: 30_000_000 }, async (t) => {
return new Promise((resolve) => {
setTimeout(resolve, 10);
});
});
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");
},
};
});
test("unfinished test with uncaughtException", async () => {
await new Promise(() => {
setTimeout(() => {
throw new Error("foo");
});
});
});
test("unfinished test with unhandledRejection", async () => {
await new Promise(() => {
setTimeout(() => Promise.reject(new Error("bar")));
});
});

176
cli/tests/testdata/node/test.out vendored Normal file
View file

@ -0,0 +1,176 @@
[WILDCARD]
Warning: Not implemented: test.options.concurrency
Warning: Not implemented: test.options.concurrency
Warning: Not implemented: test.options.timeout
Warning: Not implemented: test.options.timeout
Warning: Not implemented: test.options.timeout
Warning: Not implemented: test.options.timeout
running 62 tests from ./node/test.js
sync pass todo ...
------- output -------
Warning: Not implemented: test.TestContext.todo
----- output end -----
sync pass todo ... ok [WILDCARD]
sync pass todo with message ...
------- output -------
Warning: Not implemented: test.TestContext.todo
----- output end -----
sync pass todo with message ... ok [WILDCARD]
sync fail todo ...
------- output -------
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 ...
------- output -------
DIAGNOSTIC: this test should pass
----- output end -----
sync pass ... ok [WILDCARD]
sync throw fail ... FAILED [WILDCARD]
async skip pass ...
------- output -------
Warning: Not implemented: test.TestContext.skip
----- output end -----
async skip pass ... ok [WILDCARD]
async pass ... ok [WILDCARD]
async throw fail ... FAILED [WILDCARD]
async skip fail ...
------- output -------
Warning: Not implemented: test.TestContext.skip
----- output end -----
async skip fail ... FAILED [WILDCARD]
async assertion fail ... FAILED [WILDCARD]
resolve pass ... ok [WILDCARD]
reject fail ... FAILED [WILDCARD]
unhandled rejection - passes but warns ...
Uncaught error from ./node/test.js FAILED
unhandled rejection - passes but warns ... cancelled ([WILDCARD])
async unhandled rejection - passes but warns ... cancelled ([WILDCARD])
immediate throw - passes but warns ... cancelled ([WILDCARD])
immediate reject - passes but warns ... cancelled ([WILDCARD])
immediate resolve pass ... cancelled ([WILDCARD])
subtest sync throw fail ... cancelled ([WILDCARD])
sync throw non-error fail ... cancelled ([WILDCARD])
level 0a ... cancelled ([WILDCARD])
top level ... cancelled ([WILDCARD])
invalid subtest - pass but subtest fails ... cancelled ([WILDCARD])
sync skip option ... ignored ([WILDCARD])
sync skip option with message ... cancelled ([WILDCARD])
sync skip option is false fail ... cancelled ([WILDCARD])
noop ... cancelled ([WILDCARD])
functionOnly ... cancelled ([WILDCARD])
<anonymous> ... cancelled ([WILDCARD])
test with only a name provided ... cancelled ([WILDCARD])
noop ... cancelled ([WILDCARD])
noop ... ignored ([WILDCARD])
test with a name and options provided ... ignored ([WILDCARD])
functionAndOptions ... ignored ([WILDCARD])
escaped skip message ... cancelled ([WILDCARD])
escaped todo message ... cancelled ([WILDCARD])
escaped diagnostic ... cancelled ([WILDCARD])
callback pass ... cancelled ([WILDCARD])
callback fail ... cancelled ([WILDCARD])
sync t is this in test ... cancelled ([WILDCARD])
async t is this in test ... cancelled ([WILDCARD])
callback t is this in test ... cancelled ([WILDCARD])
callback also returns a Promise ... cancelled ([WILDCARD])
callback throw ... cancelled ([WILDCARD])
callback called twice ... cancelled ([WILDCARD])
callback called twice in different ticks ... cancelled ([WILDCARD])
callback called twice in future tick ... cancelled ([WILDCARD])
callback async throw ... cancelled ([WILDCARD])
callback async throw after done ... cancelled ([WILDCARD])
custom inspect symbol fail ... cancelled ([WILDCARD])
custom inspect symbol that throws fail ... cancelled ([WILDCARD])
subtest sync throw fails ... cancelled ([WILDCARD])
timed out async test ... cancelled ([WILDCARD])
timed out callback test ... cancelled ([WILDCARD])
large timeout async test is ok ... cancelled ([WILDCARD])
large timeout callback test is ok ... cancelled ([WILDCARD])
successful thenable ... cancelled ([WILDCARD])
rejected thenable ... cancelled ([WILDCARD])
unfinished test with uncaughtException ... cancelled ([WILDCARD])
unfinished test with unhandledRejection ... cancelled ([WILDCARD])
ERRORS
sync fail todo => node:test:135:10
error: Error: thrown from sync fail todo
throw new Error("thrown from sync fail todo");
[WILDCARD]
sync fail todo with message => node:test:135:10
error: Error: thrown from sync fail todo with message
throw new Error("thrown from sync fail todo with message");
[WILDCARD]
sync throw fail => node:test:135:10
error: Error: thrown from sync throw fail
throw new Error("thrown from sync throw fail");
[WILDCARD]
async throw fail => node:test:135:10
error: Error: thrown from async throw fail
throw new Error("thrown from async throw fail");
[WILDCARD]
async skip fail => node:test:135:10
error: Error: thrown from async throw fail
throw new Error("thrown from async throw fail");
[WILDCARD]
async assertion fail => node:test:135:10
error: AssertionError: Values are not strictly equal:
[Diff] Actual / Expected
- true
+ false
at [WILDCARD]
reject fail => node:test:135:10
error: Error: rejected from reject fail
return Promise.reject(new Error("rejected from reject fail"));
^
at [WILDCARD]
./node/test.js (uncaught error)
error: Error: rejected from unhandled rejection fail
Promise.reject(new Error("rejected from unhandled rejection fail"));
^
at [WILDCARD]
This error was not caught from a test and caused the test runner to fail on the referenced module.
It most likely originated from a dangling promise, event/timeout handler or top-level code.
FAILURES
sync fail todo => node:test:135:10
sync fail todo with message => node:test:135:10
sync throw fail => node:test:135:10
async throw fail => node:test:135:10
async skip fail => node:test:135:10
async assertion fail => node:test:135:10
reject fail => node:test:135:10
./node/test.js (uncaught error)
FAILED | 8 passed | 51 failed | 4 ignored [WILDCARD]
error: Test failed

View file

@ -491,6 +491,7 @@ deno_core::extension!(deno_node,
"stream/web.ts" with_specifier "node:stream/web",
"string_decoder.ts" with_specifier "node:string_decoder",
"sys.ts" with_specifier "node:sys",
"testing.ts" with_specifier "node:test",
"timers.ts" with_specifier "node:timers",
"timers/promises.ts" with_specifier "node:timers/promises",
"tls.ts" with_specifier "node:tls",

View file

@ -62,6 +62,7 @@ generate_builtin_node_module_lists! {
"stream/web",
"string_decoder",
"sys",
"test",
"timers",
"timers/promises",
"tls",

View file

@ -117,6 +117,7 @@ import streamPromises from "node:stream/promises";
import streamWeb from "node:stream/web";
import stringDecoder from "node:string_decoder";
import sys from "node:sys";
import test from "node:test";
import timers from "node:timers";
import timersPromises from "node:timers/promises";
import tls from "node:tls";
@ -219,6 +220,7 @@ function setupBuiltinModules() {
"stream/web": streamWeb,
string_decoder: stringDecoder,
sys,
test,
timers,
"timers/promises": timersPromises,
tls,

View file

@ -0,0 +1,237 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// TODO(petamoriken): enable prefer-primordials for node polyfills
// deno-lint-ignore-file prefer-primordials
import { notImplemented, warnNotImplemented } from "ext:deno_node/_utils.ts";
export function deferred() {
let methods;
const promise = new Promise((resolve, reject) => {
methods = {
async resolve(value) {
await value;
resolve(value);
},
// deno-lint-ignore no-explicit-any
reject(reason?: any) {
reject(reason);
},
};
});
return Object.assign(promise, methods);
}
export function run() {
notImplemented("test.run");
}
function noop() {}
class NodeTestContext {
#denoContext: Deno.TestContext;
constructor(t: Deno.TestContext) {
this.#denoContext = t;
}
get signal() {
notImplemented("test.TestContext.signal");
return null;
}
get name() {
notImplemented("test.TestContext.name");
return null;
}
diagnostic(message) {
console.log("DIAGNOSTIC:", message);
}
get mock() {
notImplemented("test.TestContext.mock");
return null;
}
runOnly() {
notImplemented("test.TestContext.runOnly");
return null;
}
skip() {
warnNotImplemented("test.TestContext.skip");
return null;
}
todo() {
warnNotImplemented("test.TestContext.todo");
return null;
}
test(name, options, fn) {
const prepared = prepareOptions(name, options, fn, {});
return this.#denoContext.step({
name: prepared.name,
fn: prepared.fn,
ignore: prepared.options.todo || prepared.options.skip,
}).then(() => undefined);
}
before(_fn, _options) {
notImplemented("test.TestContext.before");
}
after(_fn, _options) {
notImplemented("test.TestContext.after");
}
beforeEach(_fn, _options) {
notImplemented("test.TestContext.beforeEach");
}
afterEach(_fn, _options) {
notImplemented("test.TestContext.afterEach");
}
}
function prepareOptions(name, options, fn, overrides) {
if (typeof name === "function") {
fn = name;
} else if (name !== null && typeof name === "object") {
fn = options;
options = name;
} else if (typeof options === "function") {
fn = options;
}
if (options === null || typeof options !== "object") {
options = {};
}
const finalOptions = { ...options, ...overrides };
const { concurrency, timeout, signal } = finalOptions;
if (typeof concurrency !== "undefined") {
warnNotImplemented("test.options.concurrency");
}
if (typeof timeout !== "undefined") {
warnNotImplemented("test.options.timeout");
}
if (typeof signal !== "undefined") {
warnNotImplemented("test.options.signal");
}
if (typeof fn !== "function") {
fn = noop;
}
if (typeof name !== "string" || name === "") {
name = fn.name || "<anonymous>";
}
return { fn, options: finalOptions, name };
}
function wrapTestFn(fn, promise) {
return async function (t) {
const nodeTestContext = new NodeTestContext(t);
try {
await fn(nodeTestContext);
} finally {
promise.resolve(undefined);
}
};
}
function prepareDenoTest(name, options, fn, overrides) {
const prepared = prepareOptions(name, options, fn, overrides);
const promise = deferred();
const denoTestOptions = {
name: prepared.name,
fn: wrapTestFn(prepared.fn, promise),
only: prepared.options.only,
ignore: prepared.options.todo || prepared.options.skip,
};
Deno.test(denoTestOptions);
return promise;
}
export function test(name, options, fn) {
return prepareDenoTest(name, options, fn, {});
}
test.skip = function skip(name, options, fn) {
return prepareDenoTest(name, options, fn, { skip: true });
};
test.todo = function todo(name, options, fn) {
return prepareDenoTest(name, options, fn, { todo: true });
};
test.only = function only(name, options, fn) {
return prepareDenoTest(name, options, fn, { only: true });
};
export function describe() {
notImplemented("test.describe");
}
export function it() {
notImplemented("test.it");
}
export function before() {
notImplemented("test.before");
}
export function after() {
notImplemented("test.after");
}
export function beforeEach() {
notImplemented("test.beforeEach");
}
export function afterEach() {
notImplemented("test.afterEach");
}
export const mock = {
fn: () => {
notImplemented("test.mock.fn");
},
getter: () => {
notImplemented("test.mock.getter");
},
method: () => {
notImplemented("test.mock.method");
},
reset: () => {
notImplemented("test.mock.reset");
},
restoreAll: () => {
notImplemented("test.mock.restoreAll");
},
setter: () => {
notImplemented("test.mock.setter");
},
timers: {
enable: () => {
notImplemented("test.mock.timers.enable");
},
reset: () => {
notImplemented("test.mock.timers.reset");
},
tick: () => {
notImplemented("test.mock.timers.tick");
},
runAll: () => {
notImplemented("test.mock.timers.runAll");
},
},
};
export default test;