1010// left by a developer's earlier run on a tree without this isolation, or by
1111// a crashed run. The guard judges only what THIS run leaves, so an old
1212// leftover never reds a run that wrote nothing.
13- // 2. It creates ONE temporary root for the run and hands it to every worker
14- // through `provide` / `inject`. Each test file makes its own working
15- // directory under that root.
13+ // 2. It reserves a TAG for the run, `os-dogfood-run-XXXXXX`, as a directory
14+ // `mkdtempSync` creates under the system temp directory, and hands the tag
15+ // (a name, never a path) to every worker through `provide` / `inject`.
16+ // Each test file makes its own working directory directly under the system
17+ // temp directory, named `<tag>-file-XXXXXX`.
1618//
17- // At the END of the run it removes that root, and with it every per-file
18- // directory. The removal is run-level, not per-file: on the `shared-showcase`
19- // project (`isolate: false`) one memoized boot serves every file on a worker,
20- // and its SQLite handles stay open in the directory of the file that booted it.
19+ // At the END of the run it removes every directory whose name starts with this
20+ // run's `<tag>-file-`, then the reservation itself. Another run's directories
21+ // carry another tag, so a concurrent run on the same machine is never touched.
22+ // The removal is run-level, not per-file: on the `shared-showcase` project
23+ // (`isolate: false`) one memoized boot serves every file on a worker, and its
24+ // SQLite handles stay open in the directory of the file that booted it.
25+ //
26+ // Why a tag and not a shared parent path (#21924): every `mkdtempSync` base in
27+ // this tree must be one the tree's scratch-directory scan can read, so that an
28+ // in-tree fixture root can never hide behind an expression
29+ // (`scripts/pm/dispatch-gates.mjs`, "no mkdtempSync site in this tree takes a
30+ // base the scan cannot read"). A path handed over through `inject()` is such an
31+ // expression. `join(tmpdir(), ...)` is not: it is outside the tree by
32+ // construction, whatever name follows it.
2133//
2234// ⛔ This teardown never JUDGES anything. On vitest 4.1.11 an error thrown from
2335// a globalSetup teardown is printed as `error during close` and the run still
2436// exits 0 (measured), so a guard placed here would be a false green. The guard
2537// is a throwing `afterAll` in the per-file module, which fails a test file.
26- import { mkdtempSync , rmSync } from 'node:fs' ;
38+ import { mkdtempSync , readdirSync , rmSync } from 'node:fs' ;
2739import { tmpdir } from 'node:os' ;
28- import { join } from 'node:path' ;
40+ import { basename , join } from 'node:path' ;
2941import { fileURLToPath } from 'node:url' ;
3042import type { TestProject } from 'vitest/node' ;
3143
@@ -34,19 +46,29 @@ const PACKAGE_ROOT = fileURLToPath(new URL('..', import.meta.url));
3446
3547declare module 'vitest' {
3648 export interface ProvidedContext {
37- /** The run's temporary root ; each test file makes its working directory under it . */
38- dogfoodCwdRoot : string ;
49+ /** The run's tag ; each test file makes its working directory as `join(tmpdir(), '<tag>-file-')` . */
50+ dogfoodRunTag : string ;
3951 }
4052}
4153
42- let runRoot : string | undefined ;
54+ /** The prefix of every per-file directory a run tagged `tag` creates under the system temp directory. */
55+ export function perFileDirPrefix ( tag : string ) : string {
56+ return `${ tag } -file-` ;
57+ }
58+
59+ let reservation : string | undefined ;
4360
4461export function setup ( project : TestProject ) : void {
4562 rmSync ( join ( PACKAGE_ROOT , '.objectstack' ) , { recursive : true , force : true } ) ;
46- runRoot = mkdtempSync ( join ( tmpdir ( ) , 'os-dogfood-run-' ) ) ;
47- project . provide ( 'dogfoodCwdRoot ' , runRoot ) ;
63+ reservation = mkdtempSync ( join ( tmpdir ( ) , 'os-dogfood-run-' ) ) ;
64+ project . provide ( 'dogfoodRunTag ' , basename ( reservation ) ) ;
4865}
4966
5067export function teardown ( ) : void {
51- if ( runRoot ) rmSync ( runRoot , { recursive : true , force : true } ) ;
68+ if ( ! reservation ) return ;
69+ const prefix = perFileDirPrefix ( basename ( reservation ) ) ;
70+ for ( const name of readdirSync ( tmpdir ( ) ) ) {
71+ if ( name . startsWith ( prefix ) ) rmSync ( join ( tmpdir ( ) , name ) , { recursive : true , force : true } ) ;
72+ }
73+ rmSync ( reservation , { recursive : true , force : true } ) ;
5274}
0 commit comments