test: close the wait-deadline review gaps

- doctorCancellation measured from the signal, after the probe had
  started, so a probe stopped by its own deadline could still pass. The
  fake probe now records its start, and the check runs from there.
- rea/project-wait-deadline missed literal computed `timeout` keys and
  spread options. It now reports both, as well as computed keys it
  cannot read.
- The testing guide notes that a long-running test's expect.poll keeps
  its project budget, while waitFor follows the tag's deadline.

Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
This commit is contained in:
gensokyo-devandgensokyo-dev authored and gensokyo-dev committed 2026-11-14 19:28:38 +08:00
1 parent 4baf1b6193
commit 55c9e8e546
4 files changed
+26 -8

No files matched your search

+2
View File
@@ -324,6 +324,8 @@ Waits follow the same policy; Vitest's one-second `expect.poll` and
test may run, less the reporting reserve: projects configure `expect.poll`
that way, and `waitFor` from `tests/support/testWait.ts` replaces `vi.waitFor`.
Fixture waits such as `awaitAcknowledgement` default to the same deadline.
`expect.poll` keeps its project's budget in a `long-running` test, while
`waitFor` follows the deadline the tag applies.
`rea/project-wait-deadline` rejects `vi.waitFor` and explicit poll timeouts. When promptness is the behavior under test, such as
cancellation that must preempt a slower production deadline, a wait timeout is
not its oracle: order the condition before releasing the gate that would
+15 -4
View File
@@ -47,15 +47,26 @@ const isVitestMethod = (sourceCode, callee, receiver, method) => {
);
};
/** A wait timeout written at the call: a number, or an options `timeout`. */
const keyName = (property) => {
if (!property.computed) return property.key.name ?? property.key.value;
return property.key.type === "Literal" ? property.key.value : undefined;
};
/**
* A wait timeout the call may set: a number or other non-literal options, an
* options `timeout` (including a literal computed key), or a spread whose
* properties the rule cannot see.
*/
const explicitTimeout = (options) => {
if (options === undefined) return undefined;
if (options.type !== "ObjectExpression") return options;
return options.properties.find(
(property) =>
property.type === "Property" &&
!property.computed &&
(property.key.name ?? property.key.value) === "timeout",
property.type === "SpreadElement" ||
(property.type === "Property" &&
(property.computed && property.key.type !== "Literal"
? true
: keyName(property) === "timeout")),
);
};
@@ -46,7 +46,7 @@ cliTest.skipIf(!["darwin", "linux"].includes(process.platform)).for(
executable,
`#!${process.execPath}
if (!process.argv.slice(2).includes(${JSON.stringify(probeArgument)})) process.exit(7);
require("node:fs").writeFileSync(${JSON.stringify(ready)}, JSON.stringify({pid: process.pid, runId: process.env.REA_PROCESS_RUN_ID}));
require("node:fs").writeFileSync(${JSON.stringify(ready)}, JSON.stringify({pid: process.pid, runId: process.env.REA_PROCESS_RUN_ID, at: Date.now()}));
process.on("SIGTERM", () => {});
setInterval(() => { if (require("node:fs").existsSync(${JSON.stringify(release)})) process.exit(0); }, 25).unref();
setTimeout(() => process.exit(99), 20000);
@@ -104,12 +104,14 @@ setTimeout(() => process.exit(99), 20000);
.strictObject({
pid: z.number().int().positive(),
runId: z.string().uuid(),
at: z.number(),
})
.parse(readyRecord)
: z
.strictObject({
pid: z.number().int().positive(),
runId: z.string().min(1),
at: z.number(),
})
.parse(readyRecord);
if (provider === "jadx") {
@@ -119,12 +121,11 @@ setTimeout(() => process.exit(99), 20000);
runId: observed.runId,
};
}
const signalled = performance.now();
child.kill(signal);
await expect.poll(() => child.exitCode).not.toBeNull();
// Without cancellation, doctor would end when the probe's own deadline
// (JadxJavaProbe and KunaInstallation: 10 s) stopped the probe.
expect(performance.now() - signalled).toBeLessThan(PROBE_DEADLINE_MS);
// (JadxJavaProbe and KunaInstallation: 10 s from its start) stopped it.
expect(Date.now() - observed.at).toBeLessThan(PROBE_DEADLINE_MS);
const result = await closed;
expect(JSON.parse(stdout)).toMatchObject({ code: "cancelled" });
if (ownership !== undefined) {
@@ -17,6 +17,7 @@ tester.run("project-wait-deadline", rule, {
`${vitest} await expect.poll(() => ready).toBe(true);`,
`${vitest} await expect.poll(() => ready, { interval: 10 }).toBe(true);`,
`${vitest} await expect.poll(() => ready, { message: "ready" }).toBe(true);`,
`${vitest} await expect.poll(() => ready, { ["interval"]: 10 }).toBe(true);`,
`import { waitFor } from "../support/testWait.js"; await waitFor(() => ready, { interval: 10 });`,
// Same names that are not Vitest's: a fixture method, or a shadowed local.
`${vitest} await fixture.vi.waitFor(() => ready, { timeout: 5_000 });`,
@@ -40,6 +41,9 @@ tester.run("project-wait-deadline", rule, {
`${vitest} await expect.poll(() => ready, options).toBe(true);`,
`import { expect as assert } from "vitest"; await assert.poll(() => ready, { timeout: 5 }).toBe(true);`,
`import * as vitest from "vitest"; await vitest.expect.poll(() => ready, { "timeout": 5 }).toBe(true);`,
`${vitest} await expect.poll(() => ready, { ["timeout"]: 5 }).toBe(true);`,
`${vitest} await expect.poll(() => ready, { ...{ timeout: 5 } }).toBe(true);`,
`${vitest} await expect.poll(() => ready, { [key]: 5 }).toBe(true);`,
].map((code) => ({
code,
filename,