mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-26 06:34:20 +08:00
Pass the SuiteContext and hook contexts (#1302)
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
- **Concurrent tests:** with `{ concurrency }` above 1, subtests run on their own fibers, and results are reported in declaration order, as in Node. Concurrent bodies resume in Node's order around `beforeEach` hooks and nested suites. Other runner steps, such as `afterEach` and `after` hooks, take no microtask turns between bodies, so code that observes the interleaving of concurrent bodies across those steps can see a different order than Node.
|
||||
- **Snapshots:** `t.assert.snapshot` and `t.assert.fileSnapshot` compile, with Node's default serializer, `JSON.stringify(value, null, 2)`, or an `options.serializers` array literal. A compiled test reads the snapshot file Node writes beside the entry file, `<entry file>.snapshot`, at the entry file's path when the program was compiled. Running the binary with Node's flag `--test-update-snapshots` as its first argument writes the snapshot files instead; as in Node, the flag is kept out of `process.argv` and appears in `process.execArgv`. Snapshot files are read in the form Node writes them, `` exports[`key`] = `value`; `` statements. The `snapshot.setDefaultSnapshotSerializers` and `snapshot.setResolveSnapshotPath` module functions throw `SC2020`.
|
||||
- **TestContext values:** `t.filePath` is the entry file's path when the program was compiled. `t.attempt` is `0`, since a test runs once. `t.passed` is `false` while the body runs and the test's outcome in its `afterEach` and `t.after` hooks. `t.workerId` is `Number(process.env.NODE_TEST_WORKER_ID) || undefined`, as in Node. `t.tags` is `[]`, since the `tags` test option throws `SC2020`; the array is not frozen as Node's is.
|
||||
- **Suite and hook contexts:** a `describe` body may take its `SuiteContext`, which has `name`, `fullName`, `filePath`, `attempt`, `passed` and `diagnostic`. Hooks may take their context, as in Node: the suite's for `before` and `after`, the test's for `beforeEach`, `afterEach`, `t.before` and `t.after`. The `SuiteContext` `signal` and `log` members, and hook `done` callbacks, throw `SC2020`.
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
"schemaVersion": 3,
|
||||
"nodeVersion": "24.22.0",
|
||||
"nodeCommit": "f14cd3aa7bf9ca8df2cc5b8bb14beb0f335b1d93",
|
||||
"artifactVersion": "622f48ed5c836aec4644",
|
||||
"artifactVersion": "a610bdd1ae56e1c4cd38",
|
||||
"rowCount": 3880
|
||||
}
|
||||
|
||||
@@ -689,9 +689,9 @@
|
||||
"apiEntries": 117,
|
||||
"static": {
|
||||
"supported": 0,
|
||||
"partial": 58,
|
||||
"partial": 64,
|
||||
"refused": 0,
|
||||
"not-implemented": 59,
|
||||
"not-implemented": 53,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
@@ -66677,9 +66677,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -66723,9 +66723,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -66746,9 +66746,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -66769,9 +66769,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -66815,9 +66815,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -66838,9 +66838,9 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Implemented for the documented scriptc module-loader subset.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
"tierItemCount": 3456,
|
||||
"tiers": {
|
||||
"static": {
|
||||
"audit-partial": 1149,
|
||||
"verify-gap": 2180,
|
||||
"audit-partial": 1155,
|
||||
"verify-gap": 2174,
|
||||
"replace-refusal": 40,
|
||||
"classify": 2,
|
||||
"implement": 85
|
||||
@@ -68013,12 +68013,15 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:test",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:test.context.diagnostic",
|
||||
"tests": [
|
||||
"tests/fixtures/node-test/cases/suite-context.ts",
|
||||
"tests/harness/node-test.test.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -68063,12 +68066,15 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:test",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:test.context.filePath",
|
||||
"tests": [
|
||||
"tests/fixtures/node-test/cases/suite-context.ts",
|
||||
"tests/harness/node-test.test.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -68088,12 +68094,15 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:test",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:test.context.fullName",
|
||||
"tests": [
|
||||
"tests/fixtures/node-test/cases/suite-context.ts",
|
||||
"tests/harness/node-test.test.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -68113,12 +68122,15 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:test",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:test.context.name",
|
||||
"tests": [
|
||||
"tests/fixtures/node-test/cases/suite-context.ts",
|
||||
"tests/harness/node-test.test.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -68163,12 +68175,15 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:test",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:test.passed",
|
||||
"tests": [
|
||||
"tests/fixtures/node-test/cases/suite-context.ts",
|
||||
"tests/harness/node-test.test.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -68188,12 +68203,15 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:test",
|
||||
"tests": []
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "compiler-feature:test.attempt",
|
||||
"tests": [
|
||||
"tests/fixtures/node-test/cases/suite-context.ts",
|
||||
"tests/harness/node-test.test.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -746,9 +746,9 @@
|
||||
"apiEntries": 117,
|
||||
"static": {
|
||||
"supported": 0,
|
||||
"partial": 58,
|
||||
"partial": 64,
|
||||
"refused": 0,
|
||||
"not-implemented": 59,
|
||||
"not-implemented": 53,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
@@ -90980,6 +90980,7 @@
|
||||
"tests/fixtures/node-test/cases/mock-timers.ts",
|
||||
"tests/fixtures/node-test/cases/only-nested.ts",
|
||||
"tests/fixtures/node-test/cases/snapshot.ts",
|
||||
"tests/fixtures/node-test/cases/suite-context.ts",
|
||||
"tests/fixtures/node-test/cases/suite-variants.ts",
|
||||
"tests/fixtures/node-test/cases/todo-suites.ts",
|
||||
"tests/harness/node-test.test.ts"
|
||||
@@ -93943,8 +93944,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:test"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:test.context.diagnostic",
|
||||
"tests": [
|
||||
"tests/fixtures/node-test/cases/suite-context.ts",
|
||||
"tests/harness/node-test.test.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -93989,8 +93994,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:test"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:test.context.filePath",
|
||||
"tests": [
|
||||
"tests/fixtures/node-test/cases/suite-context.ts",
|
||||
"tests/harness/node-test.test.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -94012,8 +94021,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:test"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:test.context.fullName",
|
||||
"tests": [
|
||||
"tests/fixtures/node-test/cases/suite-context.ts",
|
||||
"tests/harness/node-test.test.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -94035,8 +94048,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:test"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:test.context.name",
|
||||
"tests": [
|
||||
"tests/fixtures/node-test/cases/suite-context.ts",
|
||||
"tests/harness/node-test.test.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
},
|
||||
@@ -94081,8 +94098,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:test"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:test.passed",
|
||||
"tests": [
|
||||
"tests/fixtures/node-test/cases/suite-context.ts",
|
||||
"tests/harness/node-test.test.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
@@ -94104,8 +94125,12 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:test"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:test.attempt",
|
||||
"tests": [
|
||||
"tests/fixtures/node-test/cases/suite-context.ts",
|
||||
"tests/harness/node-test.test.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "ancestor"
|
||||
},
|
||||
|
||||
@@ -357,6 +357,7 @@
|
||||
{ "chapter": "url", "signatures": ["Legacy `urlObject`", "`urlObject.auth`", "`urlObject.hash`", "`urlObject.host`", "`urlObject.hostname`", "`urlObject.href`", "`urlObject.path`", "`urlObject.pathname`", "`urlObject.port`", "`urlObject.protocol`", "`urlObject.query`", "`urlObject.search`", "`urlObject.slashes`"], "status": "partial", "evidence": ["tests/corpus/5221-url-parse-shapes.ts", "tests/corpus/5222-url-parse-hosts.ts", "tests/corpus/5223-url-parse-query.ts", "tests/corpus/5224-url-format-objects.ts", "tests/fixtures/node-types/url-legacy.ts"] },
|
||||
{ "chapter": "test", "parent": "`context.assert`", "symbols": ["context.assert.snapshot", "context.assert.fileSnapshot"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/snapshot.ts", "tests/harness/node-test.test.ts"] },
|
||||
{ "chapter": "test", "parent": "Class: `TestContext`", "symbols": ["context.filePath", "passed", "attempt", "tags", "workerId"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/ctx-members.ts", "tests/harness/node-test.test.ts"] },
|
||||
{ "chapter": "test", "parent": "Class: `SuiteContext`", "symbols": ["context.name", "context.fullName", "context.filePath", "context.diagnostic", "passed", "attempt"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/suite-context.ts", "tests/harness/node-test.test.ts"] },
|
||||
{ "chapter": "test", "parent": "Class: `TestContext`", "symbols": ["context.diagnostic", "context.name"], "status": "partial", "evidence": ["tests/harness/node-test.test.ts"] },
|
||||
{ "chapter": "test", "parent": "Class: `MockTracker`", "symbols": ["mock.fn"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/mock-fn.ts", "tests/harness/node-test.test.ts"] },
|
||||
{ "chapter": "test", "parent": "Class: `MockTracker`", "symbols": ["mock.method", "mock.reset", "mock.restoreAll"], "status": "partial", "evidence": ["tests/fixtures/node-test/cases/mock-method.ts", "tests/corpus/5810-mock-method-records.ts", "tests/corpus/5811-mock-tracker-restore.ts", "tests/harness/node-test.test.ts"] },
|
||||
|
||||
+13
-2
@@ -3375,8 +3375,18 @@ declare module "node:util/types" {
|
||||
* suites typecheck but fence at their use sites (no lowering). */
|
||||
declare module "node:test" {
|
||||
type TestFn = (t: TestContext) => void | Promise<void>;
|
||||
type SuiteFn = () => void;
|
||||
type HookFn = () => void | Promise<void>;
|
||||
type SuiteFn = (s: SuiteContext) => void;
|
||||
type HookFn = (c: TestContext | SuiteContext) => void | Promise<void>;
|
||||
/* The describe/suite callback's context, and the before/after hook
|
||||
* context of a suite. */
|
||||
interface SuiteContext {
|
||||
readonly name: string;
|
||||
readonly fullName: string;
|
||||
readonly filePath: string | undefined;
|
||||
readonly attempt: number;
|
||||
readonly passed: boolean;
|
||||
diagnostic(message: string): void;
|
||||
}
|
||||
interface TestOptions {
|
||||
skip?: boolean | string;
|
||||
todo?: boolean | string;
|
||||
@@ -3537,6 +3547,7 @@ declare module "node:test" {
|
||||
run,
|
||||
snapshot,
|
||||
TestContext,
|
||||
SuiteContext,
|
||||
TestOptions,
|
||||
Mock,
|
||||
MockFunctionContext,
|
||||
|
||||
@@ -409,11 +409,16 @@ function lowerSuite(
|
||||
}
|
||||
if (methodMode === 1 || methodMode === 2) opts.mode = methodMode;
|
||||
const cb = lowerer.lowerExpr(fnNode);
|
||||
if (cb.type.kind !== "func" || cb.type.params.length !== 0) {
|
||||
// The body may take the SuiteContext (flag 2, as test bodies take theirs).
|
||||
if (
|
||||
cb.type.kind !== "func" ||
|
||||
cb.type.params.length > 1 ||
|
||||
(cb.type.params.length === 1 && cb.type.params[0]!.kind !== "testCtx")
|
||||
) {
|
||||
lowerer.noLowering(
|
||||
`${what} whose body is not a zero-parameter function`,
|
||||
`${what} whose body is not a function of the SuiteContext`,
|
||||
fnNode,
|
||||
"suite bodies take no parameters (they run at registration)",
|
||||
"suite bodies take no parameters or the SuiteContext (they run at registration)",
|
||||
);
|
||||
}
|
||||
if (cb.type.ret.kind !== "void" && !isUnitType(cb.type.ret)) {
|
||||
@@ -427,7 +432,10 @@ function lowerSuite(
|
||||
const mode = numLit(opts.mode, loc);
|
||||
const msg = opts.msg ?? strLit("", loc);
|
||||
// describe.only and { only: true } mark the suite (flag 4, as for tests).
|
||||
const flags = numLit(optionFlags(opts) | (methodMode === 3 ? 4 : 0), loc);
|
||||
const flags = numLit(
|
||||
optionFlags(opts) | (methodMode === 3 ? 4 : 0) | (cb.type.params.length === 1 ? 2 : 0),
|
||||
loc,
|
||||
);
|
||||
return withOptionValues(
|
||||
{
|
||||
kind: "libCall",
|
||||
@@ -456,10 +464,12 @@ function lowerHook(
|
||||
lowerer.noLowering(
|
||||
`${what} with ${args.length} arguments`,
|
||||
expr,
|
||||
"the supported form is one zero-parameter function (hook options have no lowering)",
|
||||
"the supported form is one function (hook options have no lowering)",
|
||||
);
|
||||
}
|
||||
const { cb, flags } = lowerBodyArg(lowerer, args[0]!, what, false);
|
||||
// A hook may take its context: the suite's for before/after, the test's
|
||||
// for beforeEach/afterEach.
|
||||
const { cb, flags } = lowerBodyArg(lowerer, args[0]!, what, true);
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "test.hook",
|
||||
@@ -919,7 +929,8 @@ export function lowerTestMethodCall(
|
||||
);
|
||||
}
|
||||
const receiver = lowerer.lowerExpr(access.expression);
|
||||
const { cb, flags } = lowerBodyArg(lowerer, args[0]!, what, false);
|
||||
// The hook may take the test's context.
|
||||
const { cb, flags } = lowerBodyArg(lowerer, args[0]!, what, true);
|
||||
return {
|
||||
kind: "libCall",
|
||||
fn: "test.ctxHook",
|
||||
|
||||
@@ -2952,11 +2952,13 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null {
|
||||
return { kind: "dgramSocket" };
|
||||
}
|
||||
// node:test's TestContext — the test-body parameter (`test('x', (t) =>
|
||||
// ...)`). @types/node's `class TestContext` and the fallback
|
||||
// declarations' interface both live inside `declare module "node:test"`
|
||||
// (isDeclaredInAmbientModule answers for both spellings).
|
||||
// ...)`) — and SuiteContext, the describe-body and suite-hook parameter:
|
||||
// both are the runner's node for the test or suite. @types/node's
|
||||
// classes and the fallback declarations' interfaces all live inside
|
||||
// `declare module "node:test"` (isDeclaredInAmbientModule answers for
|
||||
// both spellings).
|
||||
if (
|
||||
psym?.name === "TestContext" &&
|
||||
(psym?.name === "TestContext" || psym?.name === "SuiteContext") &&
|
||||
checker
|
||||
.declarationsOf(psym)
|
||||
.some(
|
||||
|
||||
@@ -1436,10 +1436,8 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
|
||||
argTypes: [STRING, F64, STRING, F64, STRING, F64, F64, F64],
|
||||
result: VOID,
|
||||
},
|
||||
"test.suite": {
|
||||
argTypes: [STRING, F64, STRING, { kind: "func", params: [], ret: VOID }, STRING, F64, F64],
|
||||
result: VOID,
|
||||
},
|
||||
// The suite body takes no parameters or its SuiteContext (flag 2).
|
||||
"test.suite": { argTypes: [STRING, F64, STRING, null, STRING, F64, F64], result: VOID },
|
||||
"test.hook": { argTypes: [F64, null, F64], result: VOID },
|
||||
"test.sub": {
|
||||
argTypes: [TESTCTX_T, STRING, F64, STRING, null, F64, STRING, F64, F64, F64],
|
||||
|
||||
@@ -406,9 +406,12 @@ static void scr_test_mocks_restore(ScrTestHooks *mocks, bool forget) {
|
||||
/* Run `which` hooks up (beforeEach: outermost suite first) or down
|
||||
* (afterEach: innermost first) the ancestor chain. A hook throw fails the
|
||||
* test with the hook's error (Node cancels harder — SEMANTICS.md). */
|
||||
static void scr_test_run_hooks_of(ScrTestCtx *owner, int which) {
|
||||
/* `ctx` is the context a hook that takes one receives: the suite or test
|
||||
* for its own before/after hooks, the test for the beforeEach/afterEach
|
||||
* chain. */
|
||||
static void scr_test_run_hooks_of(ScrTestCtx *owner, int which, ScrTestCtx *ctx) {
|
||||
for (size_t i = 0; i < owner->hooks[which].n && !scr_exc_pending(); i++) {
|
||||
scr_test_call(owner->hooks[which].v[i].cb, owner->hooks[which].v[i].flags, NULL, NULL);
|
||||
scr_test_call(owner->hooks[which].v[i].cb, owner->hooks[which].v[i].flags, ctx, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,9 +425,9 @@ static bool scr_test_run_each_hooks(ScrTestCtx *t, int which, bool outermost_fir
|
||||
if (p->hooks[which].n > 0) any = true;
|
||||
}
|
||||
if (outermost_first) {
|
||||
for (int i = n - 1; i >= 0; i--) scr_test_run_hooks_of(chain[i], which);
|
||||
for (int i = n - 1; i >= 0; i--) scr_test_run_hooks_of(chain[i], which, t);
|
||||
} else {
|
||||
for (int i = 0; i < n; i++) scr_test_run_hooks_of(chain[i], which);
|
||||
for (int i = 0; i < n; i++) scr_test_run_hooks_of(chain[i], which, t);
|
||||
}
|
||||
return any;
|
||||
}
|
||||
@@ -595,7 +598,7 @@ static void scr_test_run_node(ScrTestCtx *t) {
|
||||
/* The body already ran at registration (children collected). A hook
|
||||
* throw fails the SUITE with that error — before-failures skip the
|
||||
* children (Node cancels them; SEMANTICS.md numbers the difference). */
|
||||
scr_test_run_hooks_of(t, SCR_HOOK_BEFORE);
|
||||
scr_test_run_hooks_of(t, SCR_HOOK_BEFORE, t);
|
||||
if (scr_exc_pending()) {
|
||||
t->err = scr_exc_take();
|
||||
} else {
|
||||
@@ -606,7 +609,7 @@ static void scr_test_run_node(ScrTestCtx *t) {
|
||||
} else {
|
||||
scr_test_run_children(t);
|
||||
}
|
||||
scr_test_run_hooks_of(t, SCR_HOOK_AFTER);
|
||||
scr_test_run_hooks_of(t, SCR_HOOK_AFTER, t);
|
||||
if (scr_exc_pending()) t->err = scr_exc_take();
|
||||
}
|
||||
t->dur_ms = scr_now_ms() - start;
|
||||
@@ -619,6 +622,8 @@ static void scr_test_run_node(ScrTestCtx *t) {
|
||||
: scr_test_subtree_failed(t) ? SCR_TS_CHILDFAIL
|
||||
: SCR_TS_PASS;
|
||||
}
|
||||
/* Node's suite pass() comes after its after hooks. */
|
||||
t->passed = t->state == SCR_TS_PASS || t->state == SCR_TS_TODO_PASS;
|
||||
scr_test_report_result(t);
|
||||
return;
|
||||
}
|
||||
@@ -678,7 +683,7 @@ static void scr_test_run_node(ScrTestCtx *t) {
|
||||
}
|
||||
}
|
||||
/* t.after hooks run once the test and its afterEach chain finish. */
|
||||
scr_test_run_hooks_of(t, SCR_HOOK_AFTER);
|
||||
scr_test_run_hooks_of(t, SCR_HOOK_AFTER, t);
|
||||
if (scr_exc_pending()) {
|
||||
if (!failed && !t->err) {
|
||||
t->err = scr_exc_take();
|
||||
@@ -1519,7 +1524,11 @@ void scr_test_suite(ScrStr *name /*borrowed*/, double mode, ScrStr *msg /*borrow
|
||||
if (cb && (int)mode != SCR_TEST_SKIP) {
|
||||
ScrTestCtx *prev = scr_test_parent_get();
|
||||
scr_test_parent_set(t);
|
||||
((void (*)(ScrClosure *))cb->fn)(cb);
|
||||
/* flag 2: the body takes its SuiteContext (+1, as test bodies do). */
|
||||
if ((int)flags & SCR_TEST_F_CTX)
|
||||
((void (*)(ScrClosure *, ScrTestCtx *))cb->fn)(cb, scr_testctx_retain(t));
|
||||
else
|
||||
((void (*)(ScrClosure *))cb->fn)(cb);
|
||||
scr_test_parent_set(prev);
|
||||
}
|
||||
scr_closure_release(cb);
|
||||
@@ -1597,7 +1606,7 @@ void scr_test_ctx_todo(ScrTestCtx *t, ScrStr *msg /*borrowed*/) {
|
||||
* each descendant subtest, and after runs once the test finishes. */
|
||||
void scr_test_ctx_hook(ScrTestCtx *t, double which, ScrClosure *cb /*moves*/, double flags) {
|
||||
if ((int)which == SCR_HOOK_BEFORE) {
|
||||
scr_test_call(cb, (int)flags, NULL, NULL);
|
||||
scr_test_call(cb, (int)flags, t, NULL);
|
||||
scr_closure_release(cb);
|
||||
if (scr_exc_pending() && !t->before_err) t->before_err = scr_exc_take();
|
||||
else if (scr_exc_pending()) scr_exc_clear();
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
// The SuiteContext a describe body receives (name, fullName, filePath,
|
||||
// attempt, passed, diagnostic), and the context hooks receive: before and
|
||||
// after get the suite's, beforeEach and afterEach the test's, and a test's
|
||||
// t.before and t.after hooks the test's.
|
||||
import { after, afterEach, before, beforeEach, describe, it, test } from "node:test";
|
||||
import * as assert from "node:assert";
|
||||
|
||||
const seen: string[] = [];
|
||||
let outerPassed = (): boolean => false;
|
||||
|
||||
describe("outer", (s) => {
|
||||
const file = (s.filePath ?? "").endsWith("tests/fixtures/node-test/cases/suite-context.ts");
|
||||
seen.push(`body: ${s.name} | ${s.fullName} | ${s.passed} | ${s.attempt} | ${file}`);
|
||||
s.diagnostic("suite diagnostic");
|
||||
outerPassed = () => s.passed;
|
||||
before((c) => {
|
||||
seen.push(`before: ${c.name}`);
|
||||
});
|
||||
after((c) => {
|
||||
seen.push(`after: ${c.name} ${c.passed}`);
|
||||
});
|
||||
beforeEach((c) => {
|
||||
seen.push(`beforeEach: ${c.name}`);
|
||||
});
|
||||
afterEach((c) => {
|
||||
seen.push(`afterEach: ${c.name} ${c.passed}`);
|
||||
});
|
||||
describe("inner", (s2) => {
|
||||
seen.push(`inner: ${s2.fullName}`);
|
||||
it("x", () => {});
|
||||
});
|
||||
it("y", (t) => {
|
||||
t.before((c) => {
|
||||
seen.push(`t.before: ${c.name}`);
|
||||
});
|
||||
t.after((c) => {
|
||||
seen.push(`t.after: ${c.name} ${c.passed}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("report", () => {
|
||||
seen.push(`outer afterwards: ${outerPassed()}`);
|
||||
assert.deepStrictEqual(seen, [
|
||||
"body: outer | outer | false | 0 | true",
|
||||
"inner: outer > inner",
|
||||
"before: outer",
|
||||
"beforeEach: x",
|
||||
"afterEach: x true",
|
||||
"beforeEach: y",
|
||||
"t.before: y",
|
||||
"afterEach: y true",
|
||||
"t.after: y true",
|
||||
"after: outer false",
|
||||
"outer afterwards: true",
|
||||
]);
|
||||
});
|
||||
Reference in New Issue
Block a user