mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
0fb38f1da2
* test: prune abandoned test-run tmp directories at run setup A run killed before its teardown (tool-timeout SIGKILL, OOM, cancelled job) left /tmp/agent-device-test-run-<pid>-* behind, and check:tmpdir-leaks — which runs after test:unit in check:unit — flagged every dead-pid directory it found. It could not tell this run's leak from a historical one, so one killed run made every later, otherwise-green gate on the host fail. Both TMPDIR redirection entry points (the Vitest global setup and the node --test wrapper) now prune dead-pid run directories before creating their own, printing one [tmpdir] line when they did; the post-run check keeps its semantics and can now only ever name the run that just finished. Live owners (a concurrent run in another worktree) are never touched. The root/prefix constants move into check-tmpdir-leaks-model.ts, next to the liveness classification, so the setup can import the prune without a cycle. * test(tmpdir): a run directory is live while any process still holds it as TMPDIR, not only while its owner runs Review (P1): owner-pid liveness alone would prune a directory out from under the orphaned children of a SIGKILLed run — the node --test chain, Vitest forks, or a daemon a test spawned all keep running with that TMPDIR. The liveness model now reads every process's TMPDIR (ps -E on macOS, /proc/<pid>/environ on Linux) and treats a run directory as live while its owner pid is alive OR any process's TMPDIR points into it; both the prune and the post-run leak check use it. Regression: a wrapped probe spawns a detached long-lived child, only the wrapper is SIGKILLed, the next prune preserves the directory; after every consumer exits, the next prune removes it. Planted red with owner-only liveness: the orphaned directory is pruned.
67 lines
2.9 KiB
TypeScript
67 lines
2.9 KiB
TypeScript
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import {
|
|
TEST_RUN_TMP_PREFIX,
|
|
TEST_RUN_TMP_ROOT,
|
|
pruneAbandonedRunDirectories,
|
|
reportPrunedRunDirectories,
|
|
} from './check-tmpdir-leaks-model.ts';
|
|
|
|
// os.tmpdir() reads TMPDIR on every call, so redirecting it here covers every
|
|
// mkdtemp call site — test and production — without touching any of them.
|
|
// globalSetup/globalTeardown run once per `vitest run` invocation, in the
|
|
// same process that spawns every worker, so this env mutation is inherited
|
|
// by all of them (confirmed: forked workers see it via normal env
|
|
// inheritance) and the removal below only ever runs once, after every worker
|
|
// across every project has finished. A per-file afterAll hook was tried
|
|
// first; it proved unreliable (some workers were torn down before running
|
|
// it), which is why this is a single run-level hook instead.
|
|
//
|
|
// The root/prefix live in check-tmpdir-leaks-model.ts, next to the liveness
|
|
// classification this setup and the post-run leak check both rely on.
|
|
|
|
let testRunTmpDir: string;
|
|
let previousTmpDir: string | undefined;
|
|
let previousSwiftCacheDir: string | undefined;
|
|
|
|
// setup/teardown are vitest's globalSetup contract: it imports this file by
|
|
// the path string in vitest.config.ts and calls these by name, so nothing in
|
|
// the source graph references them directly.
|
|
// fallow-ignore-next-line unused-export
|
|
export function setup(): void {
|
|
previousTmpDir = process.env.TMPDIR;
|
|
previousSwiftCacheDir = process.env.AGENT_DEVICE_SWIFT_CACHE_DIR;
|
|
const originalTmpDir = os.tmpdir();
|
|
if (!previousSwiftCacheDir?.trim()) {
|
|
// The Swift compiler cache is intentionally durable across test runs. If it
|
|
// followed the disposable TMPDIR, AVFoundation helpers would recompile on
|
|
// every invocation and push integration scenarios past their 5s budgets.
|
|
process.env.AGENT_DEVICE_SWIFT_CACHE_DIR = path.join(
|
|
originalTmpDir,
|
|
'agent-device-swift-cache',
|
|
);
|
|
}
|
|
reportPrunedRunDirectories(pruneAbandonedRunDirectories(TEST_RUN_TMP_ROOT));
|
|
// The pid is embedded so check-tmpdir-leaks.ts can tell a directory that's
|
|
// still in active use (its vitest process is alive — a concurrent run in
|
|
// another worktree, say) apart from one actually abandoned by a killed
|
|
// process; the trailing mkdtemp suffix still guards against same-pid reuse.
|
|
testRunTmpDir = fs.mkdtempSync(
|
|
path.join(TEST_RUN_TMP_ROOT, `${TEST_RUN_TMP_PREFIX}${process.pid}-`),
|
|
);
|
|
process.env.TMPDIR = testRunTmpDir;
|
|
}
|
|
|
|
// fallow-ignore-next-line unused-export
|
|
export function teardown(): void {
|
|
fs.rmSync(testRunTmpDir, { recursive: true, force: true });
|
|
restoreEnv('TMPDIR', previousTmpDir);
|
|
restoreEnv('AGENT_DEVICE_SWIFT_CACHE_DIR', previousSwiftCacheDir);
|
|
}
|
|
|
|
function restoreEnv(key: string, value: string | undefined): void {
|
|
if (value === undefined) delete process.env[key];
|
|
else process.env[key] = value;
|
|
}
|