From 3d4cf20ef40bbd37804529edd03faee2838f8659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Tue, 15 Sep 2026 12:23:16 +0200 Subject: [PATCH 1/3] test: create test scratch via mkdtempForTest instead of os.tmpdir() Route every product-test scratch directory through the per-package mkdtempForTest/mkdtempForTestSync helpers so it lands under the run's redirected TMPDIR and is removed once per run, instead of path.join(os.tmpdir(), name), which reuses a fixed path across the whole suite. Adds the missing tmp-dir helpers to platform-harmonyos, provider-webdriver, and replay-test and rounds out platform-apple's. --- .../src/audio-probe-runtime.test.ts | 5 ++- .../internal/archive-extraction.fixtures.ts | 5 ++- .../src/internal/durable-file.test.ts | 5 ++- .../src/internal/owner-identity.test.ts | 8 +++- .../src/internal/verified-file.test.ts | 5 ++- .../src/__tests__/app-deployment.test.ts | 20 ++++++--- .../src/__tests__/snapshot.test.ts | 35 +++++++-------- .../__tests__/test-utils/android-file-host.ts | 6 +-- .../platform-apple/src/__tests__/tmp-dir.ts | 1 + .../runner/__tests__/runner-transport.test.ts | 7 +-- .../src/snapshot-source/adapter.test.ts | 14 +++--- .../src/snapshot-source/cache.test.ts | 11 ++--- .../src/__tests__/tmp-dir.ts | 14 ++++++ .../src/recording/runtime.test.ts | 5 ++- .../src/recording/runtime.test.ts | 6 +-- .../src/browserstack.test.ts | 5 ++- .../src/tmp-dir.fixtures.ts | 21 +++++++++ .../__tests__/session-test-artifacts.test.ts | 14 +++--- .../__tests__/session-test-attempt.test.ts | 6 +-- .../__tests__/session-test-runtime.test.ts | 5 ++- packages/replay-test/src/tmp-dir.fixtures.ts | 21 +++++++++ src/__tests__/cli-exit-paths.test.ts | 5 ++- src/__tests__/client-metro.test.ts | 24 +++++----- src/__tests__/metro-session-hints.test.ts | 8 +++- ...ssion-test-reporter-values-maestro.test.ts | 6 +-- .../session-test-reporter-values.test.ts | 6 +-- src/commands/management/app.test.ts | 8 +++- .../daemon-client-timeout-route.test.ts | 8 +++- .../__tests__/device-claim-prune.test.ts | 5 ++- .../http-server-limrun-uploaded-apps.test.ts | 8 +++- .../__tests__/internal-observation.test.ts | 7 ++- .../managed-session-artifact-path.test.ts | 5 ++- .../request-router-android-modal.test.ts | 9 ++-- .../request-router-apps-admission.test.ts | 5 ++- .../request-router-artifacts-web.test.ts | 7 +-- .../__tests__/request-router-cost.test.ts | 5 ++- ...request-router-custom-action-flags.test.ts | 5 ++- .../__tests__/request-router-events.test.ts | 17 +++---- .../request-router-lock-policy.test.ts | 20 ++++----- .../request-router-open-claim.test.ts | 6 +-- .../__tests__/request-router-open.test.ts | 9 ++-- .../request-router-record-flags.test.ts | 5 ++- .../request-router-recording-health.test.ts | 7 +-- .../request-router-repair-expired.test.ts | 6 +-- .../request-router-replay-scope.test.ts | 12 ++--- .../request-router-response-level.test.ts | 5 ++- .../request-router-screenshot.test.ts | 45 ++++++++++++++----- .../request-router-session-address.test.ts | 6 +-- .../request-router-typed-error.test.ts | 7 +-- src/daemon/__tests__/screenshot-crop.test.ts | 12 ++--- .../__tests__/screenshot-runtime.test.ts | 25 ++++++++--- src/daemon/__tests__/session-store.test.ts | 11 ++++- ...shot-custom-actions-platform-guard.test.ts | 5 ++- .../__tests__/snapshot-quality-latch.test.ts | 7 ++- .../__tests__/session-appstate-input.test.ts | 16 +++---- .../__tests__/session-boot-shutdown.test.ts | 30 ++++++------- .../__tests__/session-command-replay.test.ts | 16 +++---- .../__tests__/session-relaunch-close.test.ts | 4 +- .../__tests__/session-relaunch-guards.test.ts | 20 ++++----- .../__tests__/session-runtime-command.test.ts | 20 +++++---- .../session-selector-dispatch.test.ts | 10 ++--- .../interaction-ios-tap-outcome.test.ts | 5 ++- .../__tests__/session-test-fail-fast.test.ts | 5 ++- .../__tests__/session-test-runner.test.ts | 20 ++++----- .../session-test-suite-infrastructure.test.ts | 9 ++-- .../__tests__/session-test-suite.test.ts | 38 ++++++++-------- ...on-capabilities-install-projection.test.ts | 5 ++- .../__tests__/session-capabilities.test.ts | 21 ++++----- .../session-close-error-precedence.test.ts | 11 +++-- .../session-close-lifecycle-runtime.test.ts | 6 +-- .../session-close-resource-cleanup.test.ts | 15 ++++--- .../session-close-save-script.test.ts | 13 +++--- .../session-close-shutdown-platform.test.ts | 16 +++---- .../session-close-shutdown.fixtures.ts | 6 +-- .../session-devices-batch-runtime.test.ts | 6 +-- .../session-open-execution-runtime.test.ts | 15 ++++--- .../__tests__/session-open-existing.test.ts | 20 ++++----- .../__tests__/session-open-runtime.test.ts | 19 ++++---- .../session-open-url-prewarm.test.ts | 4 +- .../session-teardown-resources.test.ts | 6 +-- src/platform-runtime-app-log-output.test.ts | 13 +++--- src/platform-runtime-app-log-process.test.ts | 9 ++-- src/platform-runtime-network-host.test.ts | 5 ++- 83 files changed, 546 insertions(+), 382 deletions(-) create mode 100644 packages/platform-harmonyos/src/__tests__/tmp-dir.ts create mode 100644 packages/provider-webdriver/src/tmp-dir.fixtures.ts create mode 100644 packages/replay-test/src/tmp-dir.fixtures.ts diff --git a/packages/capture-kit/src/audio-probe-runtime.test.ts b/packages/capture-kit/src/audio-probe-runtime.test.ts index dfe47eef9f..dc121959aa 100644 --- a/packages/capture-kit/src/audio-probe-runtime.test.ts +++ b/packages/capture-kit/src/audio-probe-runtime.test.ts @@ -1,6 +1,6 @@ import assert from 'node:assert/strict'; import { promises as fs } from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import { test } from 'vitest'; import type { @@ -19,6 +19,7 @@ import { createDurableResourceEnvelope, encodeDurableDescriptor, } from './durable-resource-envelope.ts'; +import { mkdtempForTest } from './tmp-dir.fixtures.ts'; const device: DeviceInfo = { id: 'macos-host', @@ -56,7 +57,7 @@ function envelopeWith(body: object) { } async function withStatusDir(run: (dir: string) => Promise): Promise { - const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'audio-probe-kit-')); + const dir = await mkdtempForTest('audio-probe-kit-'); try { return await run(dir); } finally { diff --git a/packages/host-kit/src/internal/archive-extraction.fixtures.ts b/packages/host-kit/src/internal/archive-extraction.fixtures.ts index 1471882fca..dbcfa63e13 100644 --- a/packages/host-kit/src/internal/archive-extraction.fixtures.ts +++ b/packages/host-kit/src/internal/archive-extraction.fixtures.ts @@ -1,16 +1,17 @@ import { promises as fs } from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import { gzipSync } from 'node:zlib'; import * as tar from 'tar-stream'; import { runCmdSync } from './exec.ts'; +import { mkdtempForTest } from './tmp-dir.fixtures.ts'; export async function createArchiveWorkspace(): Promise<{ archivePath: string; outputRoot: string; root: string; }> { - const root = await fs.mkdtemp(path.join(os.tmpdir(), 'agent-device-archive-')); + const root = await mkdtempForTest('agent-device-archive-'); const outputRoot = path.join(root, 'output'); await fs.mkdir(outputRoot); return { archivePath: path.join(root, 'fixture.archive'), outputRoot, root }; diff --git a/packages/host-kit/src/internal/durable-file.test.ts b/packages/host-kit/src/internal/durable-file.test.ts index 778bb46b42..cde045fb2c 100644 --- a/packages/host-kit/src/internal/durable-file.test.ts +++ b/packages/host-kit/src/internal/durable-file.test.ts @@ -1,9 +1,10 @@ import assert from 'node:assert/strict'; import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import { afterEach, expect, test, vi } from 'vitest'; import { isAtomicPublishTemporaryPath, publishDurableFileSync } from './atomic-file.ts'; +import { mkdtempForTestSync } from './tmp-dir.fixtures.ts'; const roots: string[] = []; @@ -109,7 +110,7 @@ test('preserves a file fsync error when descriptor cleanup also fails', () => { }); function fixtureRoot(label: string): string { - const root = fs.mkdtempSync(path.join(os.tmpdir(), `agent-device-durable-file-${label}-`)); + const root = mkdtempForTestSync(`agent-device-durable-file-${label}-`); roots.push(root); return root; } diff --git a/packages/host-kit/src/internal/owner-identity.test.ts b/packages/host-kit/src/internal/owner-identity.test.ts index 6b6c867d8d..36da646601 100644 --- a/packages/host-kit/src/internal/owner-identity.test.ts +++ b/packages/host-kit/src/internal/owner-identity.test.ts @@ -1,6 +1,6 @@ import assert from 'node:assert/strict'; import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import { afterEach, test, vi } from 'vitest'; import { @@ -10,6 +10,7 @@ import { type OwnerIdentity, } from './owner-identity.ts'; import { readProcessStartTime } from './host-process.ts'; +import { mkdtempForTestSync } from './tmp-dir.fixtures.ts'; afterEach(() => { vi.restoreAllMocks(); @@ -28,7 +29,10 @@ test('distinguishes dead and PID-reused owners', () => { test('distinguishes a gone state directory from permission and transient I/O failures', () => { const startTime = readProcessStartTime(process.pid); - const missing = path.join(os.tmpdir(), `agent-device-missing-owner-${Date.now()}`); + const missing = path.join( + mkdtempForTestSync('agent-device-missing-owner'), + `agent-device-missing-owner-${Date.now()}`, + ); assert.equal( classifyOwnerLiveness({ owner: { pid: process.pid, startTime }, stateDir: missing }), 'owner-state-dir-gone', diff --git a/packages/host-kit/src/internal/verified-file.test.ts b/packages/host-kit/src/internal/verified-file.test.ts index 8678346127..18018ec83f 100644 --- a/packages/host-kit/src/internal/verified-file.test.ts +++ b/packages/host-kit/src/internal/verified-file.test.ts @@ -1,6 +1,6 @@ import assert from 'node:assert/strict'; import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import { afterEach, expect, test, vi } from 'vitest'; import { AppError, normalizeError } from '@agent-device/kernel/errors'; @@ -17,6 +17,7 @@ import { openVerifiedFileForRead, openVerifiedFileForTruncate, } from './verified-file.ts'; +import { mkdtempForTestSync } from './tmp-dir.fixtures.ts'; const roots: string[] = []; @@ -111,7 +112,7 @@ test('returns absent for a missing read without creating the file', () => { }); function fixturePath(label: string): string { - const root = fs.mkdtempSync(path.join(os.tmpdir(), `agent-device-verified-${label}-`)); + const root = mkdtempForTestSync(`agent-device-verified-${label}-`); roots.push(root); return path.join(root, 'artifact'); } diff --git a/packages/platform-android/src/__tests__/app-deployment.test.ts b/packages/platform-android/src/__tests__/app-deployment.test.ts index c9e36222cf..999888d3b1 100644 --- a/packages/platform-android/src/__tests__/app-deployment.test.ts +++ b/packages/platform-android/src/__tests__/app-deployment.test.ts @@ -1,7 +1,7 @@ import { test } from 'vitest'; import assert from 'node:assert/strict'; import { promises as fs } from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import { AppError } from '@agent-device/kernel/errors'; import { installAndroidInstallablePath } from '../app-deployment.ts'; @@ -14,7 +14,8 @@ import { withAndroidAdbProvider } from '../adb-executor.ts'; import type { DeviceInfo } from '@agent-device/kernel/device'; import { assertRejectsAppError } from './test-utils/app-error.ts'; import { withFakeAdb } from './test-utils/fake-adb.ts'; -import { mkdtempForTest } from './test-utils/tmp-dir.ts'; + +import { mkdtempForTest, mkdtempForTestSync } from './test-utils/tmp-dir.ts'; // The fake adb provider installs through the production withAndroidAdbProvider // scope, so `calls` records device-scoped args without a leading `-s `. @@ -32,7 +33,10 @@ test('inferAndroidAppName derives readable names from package ids', () => { }); test('installAndroidInstallablePath installs .apk via adb install -r', async () => { - const apkPath = path.join(os.tmpdir(), `agent-device-test-${Date.now()}.apk`); + const apkPath = path.join( + mkdtempForTestSync('agent-device-test'), + `agent-device-test-${Date.now()}.apk`, + ); await fs.writeFile(apkPath, 'placeholder', 'utf8'); await withFakeAdb( () => undefined, @@ -49,7 +53,10 @@ test('installAndroidInstallablePath installs .apk via adb install -r', async () }); test('installAndroidInstallablePath uses provider install capability when available', async () => { - const apkPath = path.join(os.tmpdir(), `agent-device-provider-install-${Date.now()}.apk`); + const apkPath = path.join( + mkdtempForTestSync('agent-device-provider-install'), + `agent-device-provider-install-${Date.now()}.apk`, + ); await fs.writeFile(apkPath, 'placeholder', 'utf8'); const installCalls: Array<{ source: string; replace: boolean | undefined }> = []; const device: DeviceInfo = { @@ -84,7 +91,10 @@ test('installAndroidInstallablePath uses provider install capability when availa test('an app install timeout keeps the generic adb-server advice', async () => { // The OEM install dialog can hold an app install too, but the helper-specific advice names an // agent-device helper package, so it must not leak onto the app-under-test install path. - const apkPath = path.join(os.tmpdir(), `agent-device-app-install-timeout-${Date.now()}.apk`); + const apkPath = path.join( + mkdtempForTestSync('agent-device-app-install-timeout'), + `agent-device-app-install-timeout-${Date.now()}.apk`, + ); await fs.writeFile(apkPath, 'placeholder', 'utf8'); const device: DeviceInfo = { platform: 'android', diff --git a/packages/platform-android/src/__tests__/snapshot.test.ts b/packages/platform-android/src/__tests__/snapshot.test.ts index 7cbd372406..510e17379e 100644 --- a/packages/platform-android/src/__tests__/snapshot.test.ts +++ b/packages/platform-android/src/__tests__/snapshot.test.ts @@ -1,7 +1,6 @@ import { afterEach, beforeEach, test, vi } from 'vitest'; import assert from 'node:assert/strict'; import { promises as fs } from 'node:fs'; -import os from 'node:os'; import path from 'node:path'; vi.mock('@agent-device/host-kit/command', async (importOriginal) => { @@ -114,31 +113,29 @@ afterEach(async () => { test('screenshotAndroid waits for transient UI to settle before capture', async () => { const events: string[] = []; - const outPath = path.join(os.tmpdir(), `agent-device-android-screenshot-${Date.now()}.png`); - - mockScreenshotEvents(events); - - await screenshotAndroid(device, outPath); + await withTempScreenshot('screenshot-settle-', async (outPath) => { + mockScreenshotEvents(events); + await screenshotAndroid(device, outPath); - const relevantEvents = events.filter((event, index) => { - if (event !== 'enable') { - return true; - } - return index === 0; + const relevantEvents = events.filter((event, index) => { + if (event !== 'enable') { + return true; + } + return index === 0; + }); + assert.deepEqual(relevantEvents, ['enable', 'settle:1000', 'capture', 'disable']); }); - assert.deepEqual(relevantEvents, ['enable', 'settle:1000', 'capture', 'disable']); }); test('screenshotAndroid skips stabilization when requested', async () => { const events: string[] = []; - const outPath = path.join(os.tmpdir(), `agent-device-android-screenshot-${Date.now()}.png`); - - mockScreenshotEvents(events); + await withTempScreenshot('screenshot-stabilize-', async (outPath) => { + mockScreenshotEvents(events); + await screenshotAndroid(device, outPath, { stabilize: false }); - await screenshotAndroid(device, outPath, { stabilize: false }); - - assert.deepEqual(events, ['capture']); - assert.equal(mockSleep.mock.calls.length, 0); + assert.deepEqual(events, ['capture']); + assert.equal(mockSleep.mock.calls.length, 0); + }); }); test('screenshotAndroid writes a valid PNG when output is clean', async () => { diff --git a/packages/platform-android/src/__tests__/test-utils/android-file-host.ts b/packages/platform-android/src/__tests__/test-utils/android-file-host.ts index 7d6dc41a13..422d4bb37a 100644 --- a/packages/platform-android/src/__tests__/test-utils/android-file-host.ts +++ b/packages/platform-android/src/__tests__/test-utils/android-file-host.ts @@ -2,7 +2,6 @@ import { constants } from 'node:fs'; import { access, mkdir, - mkdtemp, open, readFile, readdir, @@ -12,9 +11,10 @@ import { writeFile, } from 'node:fs/promises'; import { createHash, randomUUID } from 'node:crypto'; -import os from 'node:os'; + import path from 'node:path'; import type { AndroidAdbFileHost } from '../../adb-host.ts'; +import { mkdtempForTest } from './tmp-dir.ts'; export function createAndroidFileHost(): AndroidAdbFileHost { return { @@ -30,7 +30,7 @@ export function createAndroidFileHost(): AndroidAdbFileHost { return false; } }, - makeTempDirectory: async (prefix) => await mkdtemp(path.join(os.tmpdir(), prefix)), + makeTempDirectory: async (prefix) => await mkdtempForTest(prefix), readBytes: async (filePath) => await readFile(filePath), readDirectory: async (directory) => await readdir(directory), readText: async (filePath) => await readFile(filePath, 'utf8'), diff --git a/packages/platform-apple/src/__tests__/tmp-dir.ts b/packages/platform-apple/src/__tests__/tmp-dir.ts index 0a8a60c897..67812892d4 100644 --- a/packages/platform-apple/src/__tests__/tmp-dir.ts +++ b/packages/platform-apple/src/__tests__/tmp-dir.ts @@ -2,6 +2,7 @@ import fsPromises from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; +// fallow-ignore-next-line code-duplication export async function mkdtempForTest(prefix: string): Promise { return fsPromises.mkdtemp(path.join(os.tmpdir(), prefix)); } diff --git a/packages/platform-apple/src/runner/__tests__/runner-transport.test.ts b/packages/platform-apple/src/runner/__tests__/runner-transport.test.ts index edd7f7bc16..62518ddfec 100644 --- a/packages/platform-apple/src/runner/__tests__/runner-transport.test.ts +++ b/packages/platform-apple/src/runner/__tests__/runner-transport.test.ts @@ -1,7 +1,6 @@ import { afterEach, beforeEach, test, vi } from 'vitest'; import assert from 'node:assert/strict'; import fs from 'node:fs'; -import os from 'node:os'; import path from 'node:path'; import { AppError } from '@agent-device/kernel/errors'; import { @@ -33,6 +32,7 @@ vi.mock('../runner-usbmux.ts', async (importOriginal) => { import { clearDeviceTunnelIpCache } from '../runner-command-route.ts'; import { fetchWithTimeout, sendRunnerCommandOnce } from '../runner-transport.ts'; +import { mkdtempForTestSync } from './tmp-dir.ts'; // The real `resolveIosPhysicalDeviceControl` resolves the CoreDevice tunnel IP // through root-level tooling this package cannot reach; a fake control backed @@ -54,10 +54,7 @@ function fakeResolveIosPhysicalDeviceControl(device: { return { backend: device.iosPhysicalDeviceBackend === 'xctest' ? 'xctest' : 'coredevice', resolveTunnel: async (resolvedDevice) => { - const jsonPath = path.join( - os.tmpdir(), - `runner-transport-test-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}.json`, - ); + const jsonPath = path.join(mkdtempForTestSync('runner-transport-test'), 'tunnel.json'); try { await mockRunCmd('xcrun', [ 'devicectl', diff --git a/packages/platform-apple/src/snapshot-source/adapter.test.ts b/packages/platform-apple/src/snapshot-source/adapter.test.ts index 23c48e8298..bdb6e1970b 100644 --- a/packages/platform-apple/src/snapshot-source/adapter.test.ts +++ b/packages/platform-apple/src/snapshot-source/adapter.test.ts @@ -1,8 +1,7 @@ import assert from 'node:assert/strict'; import { EventEmitter } from 'node:events'; import { readFileSync } from 'node:fs'; -import { mkdtemp, rm, writeFile } from 'node:fs/promises'; -import os from 'node:os'; +import { rm, writeFile } from 'node:fs/promises'; import path from 'node:path'; import { test } from 'vitest'; import { @@ -18,9 +17,10 @@ import { SNAPSHOT_SOURCE_VERSION, } from './protocol.ts'; import type { SnapshotSourceHost, SnapshotSourceProcess, SnapshotSourceSocket } from './types.ts'; +import { mkdtempForTest } from '../__tests__/tmp-dir.ts'; test('the Simulator AX source returns raw acquisition facts and discloses unsupported facets', async () => { - const root = await mkdtemp(path.join(os.tmpdir(), 'agent-device-snapshot-adapter-')); + const root = await mkdtempForTest('agent-device-snapshot-adapter-'); const sourceRoot = path.join(root, 'source'); const cacheRoot = path.join(root, 'cache'); await (await import('@agent-device/host-kit/host-file')).ensureHostDirectory(sourceRoot); @@ -95,7 +95,7 @@ test('the Simulator AX source returns raw acquisition facts and discloses unsupp }); test('the Simulator AX source refuses a tree that ends at content another process owns', async () => { - const root = await mkdtemp(path.join(os.tmpdir(), 'agent-device-snapshot-adapter-remote-')); + const root = await mkdtempForTest('agent-device-snapshot-adapter-remote-'); const sourceRoot = path.join(root, 'source'); await (await import('@agent-device/host-kit/host-file')).ensureHostDirectory(sourceRoot); for (const name of [ @@ -145,7 +145,7 @@ test('the Simulator AX source refuses a tree that ends at content another proces }); test('preparation consumes the same acquisition deadline as bridge I/O', async () => { - const root = await mkdtemp(path.join(os.tmpdir(), 'agent-device-snapshot-adapter-deadline-')); + const root = await mkdtempForTest('agent-device-snapshot-adapter-deadline-'); const sourceRoot = path.join(root, 'source'); const cacheRoot = path.join(root, 'cache'); await (await import('@agent-device/host-kit/host-file')).ensureHostDirectory(sourceRoot); @@ -175,7 +175,7 @@ test('preparation consumes the same acquisition deadline as bridge I/O', async ( }); test('the Simulator AX source learns a hint only from a validated acquisition', async () => { - const root = await mkdtemp(path.join(os.tmpdir(), 'agent-device-snapshot-adapter-hints-')); + const root = await mkdtempForTest('agent-device-snapshot-adapter-hints-'); const sourceRoot = path.join(root, 'source'); await (await import('@agent-device/host-kit/host-file')).ensureHostDirectory(sourceRoot); for (const name of [ @@ -376,7 +376,7 @@ function expectedHint(hints: Readonly> | undefined } test('the Simulator AX source follows the shared hint contract', async () => { - const root = await mkdtemp(path.join(os.tmpdir(), 'agent-device-snapshot-adapter-contract-')); + const root = await mkdtempForTest('agent-device-snapshot-adapter-contract-'); const sourceRoot = path.join(root, 'source'); await (await import('@agent-device/host-kit/host-file')).ensureHostDirectory(sourceRoot); for (const name of [ diff --git a/packages/platform-apple/src/snapshot-source/cache.test.ts b/packages/platform-apple/src/snapshot-source/cache.test.ts index 4ac7331930..8f4ee65d67 100644 --- a/packages/platform-apple/src/snapshot-source/cache.test.ts +++ b/packages/platform-apple/src/snapshot-source/cache.test.ts @@ -1,6 +1,6 @@ import assert from 'node:assert/strict'; -import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; -import os from 'node:os'; +import { readFile, rm, writeFile } from 'node:fs/promises'; + import path from 'node:path'; import { test } from 'vitest'; import { createSnapshotSourceHost } from './host.ts'; @@ -8,9 +8,10 @@ import { ensureSnapshotBridgeBinary } from './cache.ts'; import { createSnapshotSourceDeadline } from './deadline.ts'; import { DEFAULT_SNAPSHOT_SOURCE_LIMITS } from './limits.ts'; import type { SnapshotSourceHost } from './types.ts'; +import { mkdtempForTest } from '../__tests__/tmp-dir.ts'; test('snapshot bridge preparation is cold-once, atomic, and invalidates corrupt or stale entries', async () => { - const root = await mkdtemp(path.join(os.tmpdir(), 'agent-device-snapshot-source-')); + const root = await mkdtempForTest('agent-device-snapshot-source-'); const sourceRoot = path.join(root, 'source'); const cacheRoot = path.join(root, 'cache'); await writeFile(path.join(root, 'placeholder'), 'unused'); @@ -122,7 +123,7 @@ test('snapshot bridge preparation is cold-once, atomic, and invalidates corrupt }); test('concurrent snapshot bridge preparation publishes one cache entry', async () => { - const root = await mkdtemp(path.join(os.tmpdir(), 'agent-device-snapshot-source-concurrent-')); + const root = await mkdtempForTest('agent-device-snapshot-source-concurrent-'); const sourceRoot = path.join(root, 'source'); const cacheRoot = path.join(root, 'cache'); await (await import('@agent-device/host-kit/host-file')).ensureHostDirectory(sourceRoot); @@ -160,7 +161,7 @@ test('concurrent snapshot bridge preparation publishes one cache entry', async ( }); test('an aborted cache waiter does not cancel an independent preparation', async () => { - const root = await mkdtemp(path.join(os.tmpdir(), 'agent-device-snapshot-source-abort-')); + const root = await mkdtempForTest('agent-device-snapshot-source-abort-'); const sourceRoot = path.join(root, 'source'); const cacheRoot = path.join(root, 'cache'); await (await import('@agent-device/host-kit/host-file')).ensureHostDirectory(sourceRoot); diff --git a/packages/platform-harmonyos/src/__tests__/tmp-dir.ts b/packages/platform-harmonyos/src/__tests__/tmp-dir.ts new file mode 100644 index 0000000000..cc7cb65b07 --- /dev/null +++ b/packages/platform-harmonyos/src/__tests__/tmp-dir.ts @@ -0,0 +1,14 @@ +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; + +/** + * Creates a fresh scratch directory for one test. Cleanup is automatic: the + * unit suite redirects TMPDIR to a per-run directory (scripts/vitest-tmpdir-global-setup.ts) + * that gets removed in one recursive rm after every worker finishes, so + * individual tests never need their own afterEach/afterAll for this. + */ +// fallow-ignore-next-line code-duplication +export function mkdtempForTestSync(prefix: string): string { + return fs.mkdtempSync(path.join(os.tmpdir(), prefix)); +} diff --git a/packages/platform-harmonyos/src/recording/runtime.test.ts b/packages/platform-harmonyos/src/recording/runtime.test.ts index c69bee6d48..be476ff266 100644 --- a/packages/platform-harmonyos/src/recording/runtime.test.ts +++ b/packages/platform-harmonyos/src/recording/runtime.test.ts @@ -1,6 +1,6 @@ import assert from 'node:assert/strict'; import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import { expect, test, vi } from 'vitest'; import { localRuntimeOwner } from '@agent-device/contracts/platform-runtime'; @@ -11,6 +11,7 @@ import { harmonyRecordingHost as harmonyHost, harmonyRecordingInput as input, } from './runtime.fixtures.ts'; +import { mkdtempForTestSync } from '../__tests__/tmp-dir.ts'; test('runs whole-screen capture and finalizes through the closed Harmony host', async () => { const operations = createHarmonyScreenRecordingOperations({ @@ -271,7 +272,7 @@ test('Harmony start rejection after cancellation preserves the exact reason', as }); test('invalid Harmony options leave an existing output untouched', async () => { - const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-harmony-output-')); + const root = mkdtempForTestSync('agent-device-harmony-output-'); const outputPath = path.join(root, 'capture.mp4'); fs.writeFileSync(outputPath, 'keep me'); const prepare = vi.fn(async (pathname: string) => fs.rmSync(pathname, { force: true })); diff --git a/packages/platform-web/src/recording/runtime.test.ts b/packages/platform-web/src/recording/runtime.test.ts index de614bc253..6736bd69bf 100644 --- a/packages/platform-web/src/recording/runtime.test.ts +++ b/packages/platform-web/src/recording/runtime.test.ts @@ -1,11 +1,11 @@ -import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; +import { readFile, rm, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; -import { tmpdir } from 'node:os'; import { expect, test, vi } from 'vitest'; import type { ScreenRecordingRuntimeHost } from '@agent-device/contracts/screen-recording-runtime-host'; import { localRuntimeOwner } from '@agent-device/contracts/platform-runtime'; import { recordingFileStore } from '@agent-device/capture-kit/recording-artifact-fixtures'; import { bindWebScreenRecordingRuntime } from './runtime.ts'; +import { mkdtempForTestSync } from '../__tests__/test-utils.ts'; const device = { platform: 'web' as const, @@ -106,7 +106,7 @@ test('does not stop a browser recorder that failed before acquisition', async () }); test('validates options before destructive output preparation', async () => { - const directory = await mkdtemp(join(tmpdir(), 'agent-device-web-recording-')); + const directory = mkdtempForTestSync('agent-device-web-recording-'); const outputPath = join(directory, 'capture.webm'); await writeFile(outputPath, 'existing recording'); let preparations = 0; diff --git a/packages/provider-webdriver/src/browserstack.test.ts b/packages/provider-webdriver/src/browserstack.test.ts index c1339cfa86..125bc8ea8c 100644 --- a/packages/provider-webdriver/src/browserstack.test.ts +++ b/packages/provider-webdriver/src/browserstack.test.ts @@ -1,9 +1,10 @@ import assert from 'node:assert/strict'; import { promises as fs } from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import { afterEach, test } from 'vitest'; import { uploadBrowserStackApp } from './browserstack.ts'; +import { mkdtempForTest } from './tmp-dir.fixtures.ts'; const realFetch = globalThis.fetch; @@ -12,7 +13,7 @@ afterEach(() => { }); test('BrowserStack upload aborts while the provider request is in flight', async () => { - const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'agent-device-browserstack-upload-')); + const tempDir = await mkdtempForTest('agent-device-browserstack-upload-'); const appPath = path.join(tempDir, 'App.apk'); const controller = new AbortController(); const abortReason = new Error('request cancelled during BrowserStack upload'); diff --git a/packages/provider-webdriver/src/tmp-dir.fixtures.ts b/packages/provider-webdriver/src/tmp-dir.fixtures.ts new file mode 100644 index 0000000000..dbc5c2724c --- /dev/null +++ b/packages/provider-webdriver/src/tmp-dir.fixtures.ts @@ -0,0 +1,21 @@ +import fs from 'node:fs'; +import fsPromises from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; + +/** + * Creates a fresh scratch directory for one test. Cleanup is automatic: the + * unit suite redirects TMPDIR to a per-run directory (scripts/vitest-tmpdir-global-setup.ts) + * that gets removed in one recursive rm after every worker finishes, so + * individual tests never need their own afterEach/afterAll for this. + */ +// fallow-ignore-next-line code-duplication +export async function mkdtempForTest(prefix: string): Promise { + return fsPromises.mkdtemp(path.join(os.tmpdir(), prefix)); +} + +/** Sync counterpart of {@link mkdtempForTest}, for setup code that can't await. */ +// fallow-ignore-next-line code-duplication +export function mkdtempForTestSync(prefix: string): string { + return fs.mkdtempSync(path.join(os.tmpdir(), prefix)); +} diff --git a/packages/replay-test/src/internal/__tests__/session-test-artifacts.test.ts b/packages/replay-test/src/internal/__tests__/session-test-artifacts.test.ts index 7182ecc664..ef16fc0b92 100644 --- a/packages/replay-test/src/internal/__tests__/session-test-artifacts.test.ts +++ b/packages/replay-test/src/internal/__tests__/session-test-artifacts.test.ts @@ -1,7 +1,7 @@ import { test } from 'vitest'; import assert from 'node:assert/strict'; import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import { DEFAULT_TEST_ARTIFACTS_ROOT, @@ -10,11 +10,7 @@ import { resolveReplayTestArtifactsDir, } from '../session-test-artifacts.ts'; import type { ReplayTestAttemptOutcome } from '../session-test-types.ts'; - -// Package tests compile within their own rootDir and cannot import the root test utility. -function mkdtempForTestSync(prefix: string): string { - return fs.mkdtempSync(path.join(os.tmpdir(), prefix)); -} +import { mkdtempForTestSync } from '../../tmp-dir.fixtures.ts'; test('resolveReplayTestArtifactsDir falls back to the default root when artifactsDir is omitted', () => { const dir = resolveReplayTestArtifactsDir({ cwd: '/repo', suiteInvocationId: 'abc123' }); @@ -44,7 +40,7 @@ const passedOutcome = ( }); test('materializeReplayTestAttemptArtifacts writes replay and result manifests for passing attempts', () => { - const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-test-artifacts-pass-')); + const root = mkdtempForTestSync('agent-device-test-artifacts-pass-'); const replayPath = path.join(root, 'flow.ad'); const screenshotPath = path.join(root, 'capture.png'); const attemptDir = path.join(root, 'attempt-1'); @@ -73,7 +69,7 @@ test('materializeReplayTestAttemptArtifacts writes replay and result manifests f }); test('prepareReplayTestAttemptArtifacts preserves original Maestro flow filename', () => { - const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-test-artifacts-maestro-')); + const root = mkdtempForTestSync('agent-device-test-artifacts-maestro-'); const replayPath = path.join(root, 'auth-flow.yml'); const attemptDir = path.join(root, 'attempt-1'); fs.writeFileSync(replayPath, 'appId: demo.app\n---\n- assertVisible: Welcome\n'); @@ -85,7 +81,7 @@ test('prepareReplayTestAttemptArtifacts preserves original Maestro flow filename }); test('materializeReplayTestAttemptArtifacts writes failure manifest and copies log artifacts', () => { - const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-test-artifacts-fail-')); + const root = mkdtempForTestSync('agent-device-test-artifacts-fail-'); const replayPath = path.join(root, 'flow.ad'); const screenshotPath = path.join(root, 'capture.png'); const logPath = path.join(root, 'daemon.log'); diff --git a/packages/replay-test/src/internal/__tests__/session-test-attempt.test.ts b/packages/replay-test/src/internal/__tests__/session-test-attempt.test.ts index a9c4fefbc5..e42b3788a5 100644 --- a/packages/replay-test/src/internal/__tests__/session-test-attempt.test.ts +++ b/packages/replay-test/src/internal/__tests__/session-test-attempt.test.ts @@ -1,11 +1,11 @@ import fs from 'node:fs'; -import os from 'node:os'; import path from 'node:path'; import { expect, test } from 'vitest'; import { runReplayTestCase } from '../session-test-attempt.ts'; import type { ReplayTestRunEntry } from '../session-test-discovery.ts'; import type { ReplayTestAttemptOutcome } from '../session-test-types.ts'; import type { ReplaySuiteTestFailed } from '@agent-device/contracts/replay'; +import { mkdtempForTestSync } from '../../tmp-dir.fixtures.ts'; const FAILED_WITH_WARNINGS: ReplayTestAttemptOutcome = { status: 'failed', @@ -27,7 +27,7 @@ const FAILED_WITHOUT_WARNINGS: ReplayTestAttemptOutcome = { }; function makeEntry(): ReplayTestRunEntry { - const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-test-attempt-')); + const root = mkdtempForTestSync('agent-device-test-attempt-'); const filePath = path.join(root, '01-flow.ad'); fs.writeFileSync(filePath, 'context platform=ios\nopen "Demo"\n'); return { @@ -45,7 +45,7 @@ async function runFailedCase(outcome: ReplayTestAttemptOutcome): Promise outcome, diff --git a/packages/replay-test/src/internal/__tests__/session-test-runtime.test.ts b/packages/replay-test/src/internal/__tests__/session-test-runtime.test.ts index fd88a91b33..0a59ba7f1e 100644 --- a/packages/replay-test/src/internal/__tests__/session-test-runtime.test.ts +++ b/packages/replay-test/src/internal/__tests__/session-test-runtime.test.ts @@ -1,11 +1,12 @@ import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import { afterEach, expect, test, vi } from 'vitest'; import { runReplayTestAttempt } from '../session-test-runtime.ts'; import type { ReplayTestAttemptOutcome } from '../session-test-types.ts'; +import { mkdtempForTestSync } from '../../tmp-dir.fixtures.ts'; // What the scheduler owes its host around cancellation (#1478 P3b): cancel exactly once when // an attempt times out, and always release when it settles. How the daemon then maps that onto @@ -45,7 +46,7 @@ afterEach(() => { }); function makeArtifactsDir(label: string): string { - return fs.mkdtempSync(path.join(os.tmpdir(), `agent-device-test-runtime-${label}-`)); + return mkdtempForTestSync(`agent-device-test-runtime-${label}-`); } function readTimingEventTypes(artifactsDir: string): string[] { diff --git a/packages/replay-test/src/tmp-dir.fixtures.ts b/packages/replay-test/src/tmp-dir.fixtures.ts new file mode 100644 index 0000000000..dbc5c2724c --- /dev/null +++ b/packages/replay-test/src/tmp-dir.fixtures.ts @@ -0,0 +1,21 @@ +import fs from 'node:fs'; +import fsPromises from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; + +/** + * Creates a fresh scratch directory for one test. Cleanup is automatic: the + * unit suite redirects TMPDIR to a per-run directory (scripts/vitest-tmpdir-global-setup.ts) + * that gets removed in one recursive rm after every worker finishes, so + * individual tests never need their own afterEach/afterAll for this. + */ +// fallow-ignore-next-line code-duplication +export async function mkdtempForTest(prefix: string): Promise { + return fsPromises.mkdtemp(path.join(os.tmpdir(), prefix)); +} + +/** Sync counterpart of {@link mkdtempForTest}, for setup code that can't await. */ +// fallow-ignore-next-line code-duplication +export function mkdtempForTestSync(prefix: string): string { + return fs.mkdtempSync(path.join(os.tmpdir(), prefix)); +} diff --git a/src/__tests__/cli-exit-paths.test.ts b/src/__tests__/cli-exit-paths.test.ts index a716fbfe4a..91bae3a036 100644 --- a/src/__tests__/cli-exit-paths.test.ts +++ b/src/__tests__/cli-exit-paths.test.ts @@ -1,5 +1,5 @@ import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import { afterEach, test, vi } from 'vitest'; import assert from 'node:assert/strict'; @@ -14,6 +14,7 @@ import { runWebCommand } from '../cli/commands/web.ts'; import { installIsolatedCliTestEnv } from './cli-test-env.ts'; import { resolveDaemonPaths } from '../daemon-resolution.ts'; import type { DaemonResponse } from '../daemon-client/daemon-client.ts'; +import { mkdtempForTestSync } from './test-utils/tmp-dir.ts'; afterEach(() => { vi.clearAllMocks(); @@ -179,7 +180,7 @@ test("web command exits with runWebCommand's status code", async () => { // the daemon log itself is large — otherwise the dump risks the same // process.exit()-truncates-a-pipe-write failure exitAfterFlush exists to fix. test('a --debug failure caps the daemon-log-tail dump instead of printing it unbounded', async () => { - const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-cli-log-tail-')); + const tempRoot = mkdtempForTestSync('agent-device-cli-log-tail-'); const stateDir = path.join(tempRoot, 'state'); fs.mkdirSync(stateDir, { recursive: true }); const { logPath } = resolveDaemonPaths(stateDir); diff --git a/src/__tests__/client-metro.test.ts b/src/__tests__/client-metro.test.ts index 24f148e199..ad67cba8cb 100644 --- a/src/__tests__/client-metro.test.ts +++ b/src/__tests__/client-metro.test.ts @@ -1,12 +1,11 @@ import { test } from 'vitest'; import assert from 'node:assert/strict'; import { once } from 'node:events'; -import { createHash, randomUUID } from 'node:crypto'; +import { createHash } from 'node:crypto'; import { chmodSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { createServer } from 'node:http'; import type { Socket } from 'node:net'; import net from 'node:net'; -import os from 'node:os'; import path from 'node:path'; import { prepareMetroRuntime, reloadMetro } from '../metro/client-metro.ts'; import { resolveMetroReloadEndpoints } from '../metro/metro-reload-endpoints.ts'; @@ -15,11 +14,12 @@ import { readMetroSessionHints } from '../metro/metro-session-hints.ts'; import { resolveDaemonPaths } from '../daemon-resolution.ts'; import { AppError } from '@agent-device/kernel/errors'; import { isProcessAlive, waitForProcessExit } from '@agent-device/host-kit/process'; +import { mkdtempForTestSync } from './test-utils/tmp-dir.ts'; const TEST_TOKEN = 'agent-device-proxy-test-token'; test('prepareMetroRuntime starts Metro, bridges through proxy, and writes runtime file when requested', async () => { - const tempRoot = path.join(os.tmpdir(), `agent-device-metro-${randomUUID()}`); + const tempRoot = mkdtempForTestSync('agent-device-metro'); const projectRoot = path.join(tempRoot, 'project'); const binDir = path.join(tempRoot, 'bin'); const runtimeFilePath = path.join(projectRoot, '.agent-device', 'metro-runtime.json'); @@ -178,7 +178,7 @@ for (const { configFileName, commandName } of [ { configFileName: 'webpack.config.js', commandName: 'webpack-start' }, ]) { test(`prepareMetroRuntime starts Re.Pack with ${commandName} for ${configFileName}`, async () => { - const tempRoot = path.join(os.tmpdir(), `agent-device-repack-${randomUUID()}`); + const tempRoot = mkdtempForTestSync('agent-device-repack'); const projectRoot = path.join(tempRoot, 'project'); const binDir = path.join(tempRoot, 'bin'); const argsFile = path.join(tempRoot, 'npx-args.json'); @@ -240,7 +240,7 @@ for (const { configFileName, commandName } of [ } test('prepareMetroRuntime maps kind=expo to the virtual-metro-entry bundle URL', async () => { - const tempRoot = path.join(os.tmpdir(), `agent-device-expo-kind-${randomUUID()}`); + const tempRoot = mkdtempForTestSync('agent-device-expo-kind'); const projectRoot = path.join(tempRoot, 'project'); const binDir = path.join(tempRoot, 'bin'); const metroPort = await findFreePort(); @@ -290,7 +290,7 @@ test('prepareMetroRuntime maps kind=expo to the virtual-metro-entry bundle URL', }); test('prepareMetroRuntime keeps index.bundle for non-expo kinds', async () => { - const tempRoot = path.join(os.tmpdir(), `agent-device-rn-kind-${randomUUID()}`); + const tempRoot = mkdtempForTestSync('agent-device-rn-kind'); const projectRoot = path.join(tempRoot, 'project'); const binDir = path.join(tempRoot, 'bin'); const metroPort = await findFreePort(); @@ -334,7 +334,7 @@ test('prepareMetroRuntime keeps index.bundle for non-expo kinds', async () => { }); test('prepareMetroRuntime detects the package manager from an ancestor lockfile in a monorepo', async () => { - const tempRoot = path.join(os.tmpdir(), `agent-device-pm-detect-${randomUUID()}`); + const tempRoot = mkdtempForTestSync('agent-device-pm-detect'); const monorepoRoot = path.join(tempRoot, 'monorepo'); const projectRoot = path.join(monorepoRoot, 'example'); const binDir = path.join(tempRoot, 'bin'); @@ -383,7 +383,7 @@ test('prepareMetroRuntime detects the package manager from an ancestor lockfile }); test('prepareMetroRuntime install failure hints at --no-install-deps and the detected package manager', async () => { - const tempRoot = path.join(os.tmpdir(), `agent-device-pm-fail-${randomUUID()}`); + const tempRoot = mkdtempForTestSync('agent-device-pm-fail'); const projectRoot = path.join(tempRoot, 'project'); const binDir = path.join(tempRoot, 'bin'); @@ -425,7 +425,7 @@ test('prepareMetroRuntime install failure hints at --no-install-deps and the det }); test('prepareMetroRuntime detects bun from the text bun.lock lockfile', async () => { - const tempRoot = path.join(os.tmpdir(), `agent-device-pm-bun-${randomUUID()}`); + const tempRoot = mkdtempForTestSync('agent-device-pm-bun'); const projectRoot = path.join(tempRoot, 'project'); const binDir = path.join(tempRoot, 'bin'); @@ -464,7 +464,7 @@ test('prepareMetroRuntime detects bun from the text bun.lock lockfile', async () }); test('prepareMetroRuntime lockfile walk-up stops at the repo root instead of adopting an outside lockfile', async () => { - const tempRoot = path.join(os.tmpdir(), `agent-device-pm-bound-${randomUUID()}`); + const tempRoot = mkdtempForTestSync('agent-device-pm-bound'); const repoRoot = path.join(tempRoot, 'repo'); const projectRoot = path.join(repoRoot, 'example'); const binDir = path.join(tempRoot, 'bin'); @@ -758,7 +758,7 @@ test('resolveMetroReloadEndpoints keeps the bundle URL mount prefix instead of c }); test('metro reload targets the dev server bound by metro prepare in the same session', async () => { - const tempRoot = path.join(os.tmpdir(), `agent-device-metro-session-${randomUUID()}`); + const tempRoot = mkdtempForTestSync('agent-device-metro-session'); const projectRoot = path.join(tempRoot, 'project'); const binDir = path.join(tempRoot, 'bin'); const stateDir = path.join(tempRoot, 'state'); @@ -837,7 +837,7 @@ test('metro reload targets the dev server bound by metro prepare in the same ses }); test('metro prepare --kind expo keeps a prefixed public base URL for session reload', async () => { - const tempRoot = path.join(os.tmpdir(), `agent-device-metro-expo-session-${randomUUID()}`); + const tempRoot = mkdtempForTestSync('agent-device-metro-expo-session'); const projectRoot = path.join(tempRoot, 'project'); const binDir = path.join(tempRoot, 'bin'); const stateDir = path.join(tempRoot, 'state'); diff --git a/src/__tests__/metro-session-hints.test.ts b/src/__tests__/metro-session-hints.test.ts index 3717a30287..4ad53e6a1e 100644 --- a/src/__tests__/metro-session-hints.test.ts +++ b/src/__tests__/metro-session-hints.test.ts @@ -2,16 +2,20 @@ import { test } from 'vitest'; import assert from 'node:assert/strict'; import { randomUUID } from 'node:crypto'; import { mkdirSync, rmSync, writeFileSync } from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import { clearMetroSessionHints, readMetroSessionHints, writeMetroSessionHints, } from '../metro/metro-session-hints.ts'; +import { mkdtempForTestSync } from './test-utils/tmp-dir.ts'; function tempStateDir(): string { - const dir = path.join(os.tmpdir(), `agent-device-metro-session-hints-${randomUUID()}`); + const dir = path.join( + mkdtempForTestSync('agent-device-metro-session-hints'), + `agent-device-metro-session-hints-${randomUUID()}`, + ); mkdirSync(dir, { recursive: true }); return dir; } diff --git a/src/cli/replay-test/__tests__/session-test-reporter-values-maestro.test.ts b/src/cli/replay-test/__tests__/session-test-reporter-values-maestro.test.ts index 5fc643656e..a282e01176 100644 --- a/src/cli/replay-test/__tests__/session-test-reporter-values-maestro.test.ts +++ b/src/cli/replay-test/__tests__/session-test-reporter-values-maestro.test.ts @@ -19,7 +19,6 @@ // Maestro replay resolves a target device through core/dispatch. Gesture viewport reads re-enter // the admitted internal runtime command; the invoke fixture below returns its typed viewport. import { expect, test, vi } from 'vitest'; -import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; vi.mock('@agent-device/device-selection/dispatch-resolve', async (importOriginal) => { const actual = @@ -37,7 +36,7 @@ vi.mock('@agent-device/device-selection/dispatch-resolve', async (importOriginal }); import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import type { ReplaySuiteResult } from '@agent-device/contracts/replay'; import { handleSessionCommands } from '../../../daemon/handlers/__tests__/session-command-harness.ts'; @@ -50,6 +49,7 @@ import { runReplayTestReporters, } from '../reporters/registry.ts'; import type { ReplayTestReporter, ReplayTestReporterContext } from '../reporters/types.ts'; +import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; type RecordedHook = { hook: 'onSuiteStart' | 'onTestStart' | 'onTestStep' | 'onTestResult' | 'onSuiteEnd'; @@ -121,7 +121,7 @@ async function runMaestroSuiteThroughReporter(params: { flags: { replayBackend: 'maestro', platform: 'android', ...params.flags }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore: makeSessionStore(params.root), invoke: params.invoke, }), diff --git a/src/cli/replay-test/__tests__/session-test-reporter-values.test.ts b/src/cli/replay-test/__tests__/session-test-reporter-values.test.ts index b570ae62cb..e6916bc326 100644 --- a/src/cli/replay-test/__tests__/session-test-reporter-values.test.ts +++ b/src/cli/replay-test/__tests__/session-test-reporter-values.test.ts @@ -11,7 +11,6 @@ // ADR 0012 attempts a post-failure screen digest through the narrow snapshot interactor seam; // these fixtures model no runner, so reject that leaf capture deterministically. import { expect, test, vi } from 'vitest'; -import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; vi.mock('../../../daemon/snapshot-interactor-capture.ts', () => ({ captureSnapshotWithInteractor: vi.fn(async () => { @@ -20,7 +19,7 @@ vi.mock('../../../daemon/snapshot-interactor-capture.ts', () => ({ })); import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import type { ReplaySuiteResult } from '@agent-device/contracts/replay'; import { handleSessionCommands } from '../../../daemon/handlers/__tests__/session-command-harness.ts'; @@ -35,6 +34,7 @@ import { runReplayTestReporters, } from '../reporters/registry.ts'; import type { ReplayTestReporter, ReplayTestReporterContext } from '../reporters/types.ts'; +import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; type RecordedHook = { hook: 'onSuiteStart' | 'onTestStart' | 'onTestStep' | 'onTestResult' | 'onSuiteEnd'; @@ -111,7 +111,7 @@ async function runSuiteThroughReporter(params: { flags: params.flags, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore: makeSessionStore(), invoke: params.invoke, }), diff --git a/src/commands/management/app.test.ts b/src/commands/management/app.test.ts index 26a29e275d..31e7655fcf 100644 --- a/src/commands/management/app.test.ts +++ b/src/commands/management/app.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from 'vitest'; import { randomUUID } from 'node:crypto'; import { mkdirSync, rmSync } from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import type { CliFlags } from '@agent-device/contracts/command'; import { parseArgs } from '../../cli/parser/args.ts'; @@ -9,13 +9,17 @@ import { createAgentDeviceClient } from '../../agent-device-client.ts'; import type { DaemonRequest, DaemonResponse } from '@agent-device/kernel/contracts'; import { readMetroSessionHints, writeMetroSessionHints } from '../../metro/metro-session-hints.ts'; import { openCommandFacet } from './app.ts'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; function flags(overrides: Partial = {}): CliFlags { return overrides as CliFlags; } function tempStateDir(): string { - const dir = path.join(os.tmpdir(), `agent-device-app-test-${randomUUID()}`); + const dir = path.join( + mkdtempForTestSync('agent-device-app-test'), + `agent-device-app-test-${randomUUID()}`, + ); mkdirSync(dir, { recursive: true }); return dir; } diff --git a/src/daemon-client/__tests__/daemon-client-timeout-route.test.ts b/src/daemon-client/__tests__/daemon-client-timeout-route.test.ts index 0272b993e8..9b2fe5b6d1 100644 --- a/src/daemon-client/__tests__/daemon-client-timeout-route.test.ts +++ b/src/daemon-client/__tests__/daemon-client-timeout-route.test.ts @@ -24,7 +24,7 @@ import net from 'node:net'; import http from 'node:http'; -import os from 'node:os'; + import path from 'node:path'; import assert from 'node:assert/strict'; import { beforeEach, test, vi } from 'vitest'; @@ -43,6 +43,7 @@ import { sendRequest } from '../daemon-client-transport.ts'; import type { DaemonRequest } from '../../daemon/daemon-request.ts'; import type { DaemonInfo } from '../daemon-client-metadata.ts'; import type { DaemonPaths } from '../../daemon-resolution.ts'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; const TIMEOUT_MS = 120; @@ -53,7 +54,10 @@ const TIMEOUT_MS = 120; const SNAPSHOT_COMMAND = 'snapshot'; function dummyStatePaths(): DaemonPaths { - const baseDir = path.join(os.tmpdir(), 'agent-device-timeout-route-test'); + const baseDir = path.join( + mkdtempForTestSync('agent-device-timeout-route-test'), + 'agent-device-timeout-route-test', + ); return { baseDir, infoPath: path.join(baseDir, 'daemon.json'), diff --git a/src/daemon/__tests__/device-claim-prune.test.ts b/src/daemon/__tests__/device-claim-prune.test.ts index 1e2291cd9e..6c9569cb2b 100644 --- a/src/daemon/__tests__/device-claim-prune.test.ts +++ b/src/daemon/__tests__/device-claim-prune.test.ts @@ -1,6 +1,6 @@ import assert from 'node:assert/strict'; import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import { afterEach, test, vi } from 'vitest'; import { deviceClaimIdentity, reconcileOrphanedDeviceClaims } from '../device-claims.ts'; @@ -10,6 +10,7 @@ import { acquireProcessLock } from '@agent-device/host-kit/file'; import { readCurrentOwnerIdentity } from '@agent-device/host-kit/process'; import { ANDROID_EMULATOR } from '../../__tests__/test-utils/device-fixtures.ts'; import { publishDaemonRegistration } from '../../__tests__/test-utils/device-claim-store.ts'; + import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; vi.mock('@agent-device/host-kit/process', async (importOriginal) => @@ -84,7 +85,7 @@ test('reconciles claims whose owner is gone and keeps every other claim', async test('reconciles nothing when the claim store does not exist', async () => { process.env.AGENT_DEVICE_CLAIMS_DIR = path.join( - os.tmpdir(), + mkdtempForTestSync('agent-device-reconciliation-absent-store'), 'agent-device-reconciliation-absent-store', ); assert.deepEqual( diff --git a/src/daemon/__tests__/http-server-limrun-uploaded-apps.test.ts b/src/daemon/__tests__/http-server-limrun-uploaded-apps.test.ts index ecf5ef0dff..9d9effa909 100644 --- a/src/daemon/__tests__/http-server-limrun-uploaded-apps.test.ts +++ b/src/daemon/__tests__/http-server-limrun-uploaded-apps.test.ts @@ -1,5 +1,5 @@ import assert from 'node:assert/strict'; -import os from 'node:os'; + import path from 'node:path'; import { test, vi } from 'vitest'; import { createLimrunRuntime } from '@agent-device/provider-limrun'; @@ -19,6 +19,7 @@ import { import { LeaseRegistry } from '../lease-registry.ts'; import { createDaemonHttpServer } from '../server/http-server.ts'; import { createRequestHandler } from './test-device-runtime-gateway.ts'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; const limrunIo = vi.hoisted(() => ({ listAssets: vi.fn(), @@ -51,7 +52,10 @@ test('public HTTP rejects Limrun uploaded-app listing and allocation before prov const token = 'limrun-http-test-token'; const leaseRegistry = new LeaseRegistry(); const handleRequest = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'agent-device-limrun-http-access.log'), + logPath: path.join( + mkdtempForTestSync('agent-device-limrun-http-access'), + 'agent-device-limrun-http-access.log', + ), token, sessionStore: makeSessionStore('agent-device-limrun-http-access-'), leaseRegistry, diff --git a/src/daemon/__tests__/internal-observation.test.ts b/src/daemon/__tests__/internal-observation.test.ts index 0066b72753..29167cfafc 100644 --- a/src/daemon/__tests__/internal-observation.test.ts +++ b/src/daemon/__tests__/internal-observation.test.ts @@ -1,4 +1,3 @@ -import os from 'node:os'; import path from 'node:path'; import { expect, test } from 'vitest'; import { makeIosSession } from '../../__tests__/test-utils/session-factories.ts'; @@ -13,6 +12,7 @@ import { } from '../ref-frame.ts'; import { markSessionPartialRefsIssued, setSessionSnapshot } from '../session-snapshot.ts'; import { SessionStore } from '../session-store.ts'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; function snapshot(ref: string, label = ref): SnapshotState { return { @@ -32,7 +32,10 @@ function snapshot(ref: string, label = ref): SnapshotState { } function scenario() { - const root = path.join(os.tmpdir(), `agent-device-internal-observation-${crypto.randomUUID()}`); + const root = path.join( + mkdtempForTestSync('agent-device-internal-observation'), + `agent-device-internal-observation-${crypto.randomUUID()}`, + ); const sessionStore = new SessionStore(path.join(root, 'sessions')); const sessionName = 'default'; const session = makeIosSession(sessionName, { appBundleId: 'com.example.app' }); diff --git a/src/daemon/__tests__/managed-session-artifact-path.test.ts b/src/daemon/__tests__/managed-session-artifact-path.test.ts index f166867310..914d09a770 100644 --- a/src/daemon/__tests__/managed-session-artifact-path.test.ts +++ b/src/daemon/__tests__/managed-session-artifact-path.test.ts @@ -1,8 +1,9 @@ import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import { afterEach, expect, test } from 'vitest'; import { requireManagedSessionArtifactPath } from '../managed-session-artifact-path.ts'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; const roots: string[] = []; @@ -81,7 +82,7 @@ test('accepts different lexical aliases for the same verified sessions root', () }); function temporaryRoot(): string { - const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-session-artifact-')); + const root = mkdtempForTestSync('agent-device-session-artifact-'); roots.push(root); return root; } diff --git a/src/daemon/__tests__/request-router-android-modal.test.ts b/src/daemon/__tests__/request-router-android-modal.test.ts index 2ab2565c54..4cd127c611 100644 --- a/src/daemon/__tests__/request-router-android-modal.test.ts +++ b/src/daemon/__tests__/request-router-android-modal.test.ts @@ -1,6 +1,6 @@ import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; import { test, expect, vi } from 'vitest'; -import os from 'node:os'; + import path from 'node:path'; import { AppError } from '@agent-device/kernel/errors'; import type { RawSnapshotNode } from '@agent-device/kernel/snapshot'; @@ -29,6 +29,7 @@ import type { ProviderDeviceRuntime } from '@agent-device/contracts/device'; import { makeTestScreenRecordingResource } from '../../__tests__/test-utils/screen-recording-live-handle.ts'; import { androidObservation } from '../../platform-runtime.ts'; import type { AndroidObservationAdapter } from '@agent-device/contracts/android-observation'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; vi.mock('@agent-device/platform-android/mechanics', async (importOriginal) => { const actual = await importOriginal(); @@ -155,7 +156,7 @@ test('generic Android gesture commands dismiss blocking system dialogs during re const { openAndroidApp } = await import('@agent-device/platform-android/mechanics'); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), @@ -207,7 +208,7 @@ test('generic Android gesture commands continue when recording dialog inspection vi.mocked(openAndroidApp).mockClear(); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), @@ -266,7 +267,7 @@ test('generic Android gesture commands skip local dialog recovery for provider d const providers = createProviderDeviceRuntimeRequestProviders([runtime]); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), diff --git a/src/daemon/__tests__/request-router-apps-admission.test.ts b/src/daemon/__tests__/request-router-apps-admission.test.ts index 7c45d7b0d4..4602b1ed69 100644 --- a/src/daemon/__tests__/request-router-apps-admission.test.ts +++ b/src/daemon/__tests__/request-router-apps-admission.test.ts @@ -1,5 +1,5 @@ import { expect, test, vi } from 'vitest'; -import os from 'node:os'; + import path from 'node:path'; import type { DeviceRuntimeGateway } from '@agent-device/contracts/platform-runtime'; import type { PlatformRuntimeOperations } from '@agent-device/contracts/platform-runtime-operations'; @@ -8,6 +8,7 @@ import { makeSessionStore } from '../../__tests__/test-utils/store-factory.ts'; import { LeaseRegistry } from '../lease-registry.ts'; import type { DaemonRequest } from '../daemon-request.ts'; import { createRequestHandler } from './test-device-runtime-gateway.ts'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; function createAppsAdmissionHarness(apps: readonly string[] = []) { const listProviderApps = vi.fn(async () => apps); @@ -22,7 +23,7 @@ function createAppsAdmissionHarness(apps: readonly string[] = []) { throw new Error('apps catalog must not bind a device'); }); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore: makeSessionStore('agent-device-apps-admission-'), leaseRegistry: new LeaseRegistry(), diff --git a/src/daemon/__tests__/request-router-artifacts-web.test.ts b/src/daemon/__tests__/request-router-artifacts-web.test.ts index ec8b6b5da0..14e74b3dc2 100644 --- a/src/daemon/__tests__/request-router-artifacts-web.test.ts +++ b/src/daemon/__tests__/request-router-artifacts-web.test.ts @@ -1,7 +1,7 @@ import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; import { test, expect } from 'vitest'; import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import { createRequestHandler } from './test-device-runtime-gateway.ts'; import { LeaseRegistry } from '../lease-registry.ts'; @@ -9,8 +9,9 @@ import type { DaemonArtifactInventoryEntry } from '@agent-device/contracts/obser import { makeSessionStore } from '../../__tests__/test-utils/store-factory.ts'; import { makeSession } from '../../__tests__/test-utils/session-factories.ts'; import { WEB_DESKTOP_DEVICE } from '../../__tests__/test-utils/device-fixtures.ts'; -import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; + import { cleanupDownloadableArtifact, trackDownloadableArtifact } from '../artifact-tracking.ts'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; // #1900: `artifacts` (`handlers/lease.ts`) lists daemon-tracked artifacts by tenant scope with no // device or platform involvement at all — `listArtifactsForRequest` never reads `device.platform`. @@ -30,7 +31,7 @@ test('artifacts lists a daemon-tracked artifact produced during a web session', try { const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), diff --git a/src/daemon/__tests__/request-router-cost.test.ts b/src/daemon/__tests__/request-router-cost.test.ts index c3d4bd732b..340c32a589 100644 --- a/src/daemon/__tests__/request-router-cost.test.ts +++ b/src/daemon/__tests__/request-router-cost.test.ts @@ -1,6 +1,6 @@ import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; import { test, expect, vi, beforeEach } from 'vitest'; -import os from 'node:os'; + import path from 'node:path'; vi.mock('@agent-device/platform-apple/runner/operations', async (importOriginal) => { @@ -22,6 +22,7 @@ import type { SessionState } from '../session-state.ts'; import { LeaseRegistry } from '../lease-registry.ts'; import { makeSessionStore } from '../../__tests__/test-utils/store-factory.ts'; import { commandRpcParamsSchema } from '@agent-device/kernel/contracts'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; // A representative, structurally rich owner payload so the parity assertions exercise nested // objects/arrays rather than a trivial flat record. `scroll` is the subject because it reaches a @@ -53,7 +54,7 @@ function makeHandler(sessionStore = makeSessionStore('agent-device-router-cost-' return { sessionStore, handler: createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), diff --git a/src/daemon/__tests__/request-router-custom-action-flags.test.ts b/src/daemon/__tests__/request-router-custom-action-flags.test.ts index 5e092e1ea6..40e94baece 100644 --- a/src/daemon/__tests__/request-router-custom-action-flags.test.ts +++ b/src/daemon/__tests__/request-router-custom-action-flags.test.ts @@ -8,14 +8,15 @@ import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/de */ import { test, expect } from 'vitest'; import path from 'node:path'; -import os from 'node:os'; + import { createRequestHandler } from './test-device-runtime-gateway.ts'; import { LeaseRegistry } from '../lease-registry.ts'; import { makeSessionStore } from '../../__tests__/test-utils/store-factory.ts'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; function createHandler() { return createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore: makeSessionStore('agent-device-router-custom-action-flags-'), leaseRegistry: new LeaseRegistry(), diff --git a/src/daemon/__tests__/request-router-events.test.ts b/src/daemon/__tests__/request-router-events.test.ts index 28a73ad628..6db32722a2 100644 --- a/src/daemon/__tests__/request-router-events.test.ts +++ b/src/daemon/__tests__/request-router-events.test.ts @@ -1,13 +1,14 @@ import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; import { test, expect } from 'vitest'; import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import { createRequestHandler } from './test-device-runtime-gateway.ts'; import { LeaseRegistry } from '../lease-registry.ts'; import { makeSessionStore } from '../../__tests__/test-utils/store-factory.ts'; import { makeIosSession, makeSession } from '../../__tests__/test-utils/session-factories.ts'; import { WEB_DESKTOP_DEVICE } from '../../__tests__/test-utils/device-fixtures.ts'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; test('events reads the daemon-owned session timeline without appending poll noise', async () => { const sessionStore = makeSessionStore('agent-device-router-events-'); @@ -20,7 +21,7 @@ test('events reads the daemon-owned session timeline without appending poll nois const eventLogPath = sessionStore.resolveEventLogPath('events-session'); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), @@ -64,7 +65,7 @@ test('events accepts a blank limit placeholder for cursor-only reads', async () }); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), @@ -103,7 +104,7 @@ test('events returns structured errors for invalid limit and cursor', async () = }); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), @@ -150,7 +151,7 @@ test('events flushes pending event writes before reading', async () => { }); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), @@ -187,7 +188,7 @@ test('events reads the daemon-owned session timeline for a web-backed session', }); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), @@ -220,7 +221,7 @@ test('request timeline records thrown request failures after scope creation', as sessionStore.set('events-session', makeIosSession('events-session')); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), @@ -259,7 +260,7 @@ test('request timeline records setup failures after start is appended', async () const sessionStore = makeSessionStore('agent-device-router-events-setup-failure-'); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), diff --git a/src/daemon/__tests__/request-router-lock-policy.test.ts b/src/daemon/__tests__/request-router-lock-policy.test.ts index 25487db97a..4a1479e81f 100644 --- a/src/daemon/__tests__/request-router-lock-policy.test.ts +++ b/src/daemon/__tests__/request-router-lock-policy.test.ts @@ -4,7 +4,6 @@ import { } from '../../__tests__/test-utils/device-inventory-gateways.ts'; import { test, expect, vi, beforeEach } from 'vitest'; import { legacyDispatchCapture } from './legacy-snapshot-capture-fixture.ts'; -import os from 'node:os'; import path from 'node:path'; vi.mock('@agent-device/platform-apple/runner/operations', async (importOriginal) => { @@ -29,6 +28,7 @@ import { type PlatformRuntimeOperations, snapshotRuntimePlanUses, } from '@agent-device/contracts/platform-runtime-operations'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; function snapshotDeviceRuntimeGateway(): DeviceRuntimeGateway { const runtime = snapshotRuntimeFixture(); @@ -125,7 +125,7 @@ test('direct daemon requests cannot bypass reject lock policy for existing sessi sessionStore.set('qa-ios', makeIosSession('qa-ios')); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), @@ -161,7 +161,7 @@ test('fresh named sessions with matching explicit serial bind and serialize on t const dispatchGate = installGatedDispatch(); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), @@ -239,7 +239,7 @@ test('fresh named sessions with the same name serialize first binding before rej const dispatchGate = installGatedDispatch(); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), @@ -312,7 +312,7 @@ test('fresh named sessions with only lock platform default serialize on the sele const dispatchGate = installGatedDispatch(); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), @@ -420,7 +420,7 @@ test('fresh named sessions reject incompatible selector combinations before bind for (const testCase of cases) { const sessionStore = makeSessionStore('agent-device-router-lock-'); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), @@ -458,7 +458,7 @@ test('batch steps cannot bypass reject lock policy on nested direct requests', a sessionStore.set('qa-ios', makeIosSession('qa-ios')); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), @@ -503,7 +503,7 @@ test('direct daemon requests apply strip lock policy for existing sessions befor systemRuntimeSpies.appSwitcher.mockClear(); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), @@ -546,7 +546,7 @@ test('strip lock policy still refuses a request naming a different device, befor }); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), @@ -584,7 +584,7 @@ test('batch preserves tenant-scoped session names across nested requests', async systemRuntimeSpies.appSwitcher.mockClear(); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry, diff --git a/src/daemon/__tests__/request-router-open-claim.test.ts b/src/daemon/__tests__/request-router-open-claim.test.ts index 43c070e8b6..c2f0de2d8e 100644 --- a/src/daemon/__tests__/request-router-open-claim.test.ts +++ b/src/daemon/__tests__/request-router-open-claim.test.ts @@ -1,11 +1,10 @@ import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; import { test, expect, vi, beforeEach } from 'vitest'; import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import crypto from 'node:crypto'; import { getResolveTargetDeviceMock } from './request-router-dispatch-mocks.ts'; -import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; vi.mock('../device-ready.ts', () => ({ ensureDeviceReady: vi.fn(async () => {}) })); vi.mock('@agent-device/host-kit/process', async (importOriginal) => { @@ -71,6 +70,7 @@ import type { DeviceBootObservation } from '@agent-device/contracts/device-boot' import { makeSessionStore } from '../../__tests__/test-utils/store-factory.ts'; import { inspectDeviceClaims } from '../device-claim-inspection.ts'; import { makeIosDevice, openRequest, storedClaimUpdatedAt } from './request-router-open-harness.ts'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; const mockResolveTargetDevice = vi.mocked(getResolveTargetDeviceMock()); const mockEnsureDeviceReady = vi.mocked(ensureDeviceReady); @@ -82,7 +82,7 @@ function createOpenHandler( leaseRegistry = new LeaseRegistry(), ) { return createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry, diff --git a/src/daemon/__tests__/request-router-open.test.ts b/src/daemon/__tests__/request-router-open.test.ts index affcd7eda3..7721b2fe28 100644 --- a/src/daemon/__tests__/request-router-open.test.ts +++ b/src/daemon/__tests__/request-router-open.test.ts @@ -2,10 +2,10 @@ import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/de import { legacyDispatchCapture } from './legacy-snapshot-capture-fixture.ts'; import { test, expect, vi, beforeEach } from 'vitest'; import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import { getResolveTargetDeviceMock } from './request-router-dispatch-mocks.ts'; -import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; + import { replayScriptSourceBundleFor } from '../../__tests__/test-utils/replay-script-source.ts'; vi.mock('../device-ready.ts', () => ({ ensureDeviceReady: vi.fn(async () => {}) })); @@ -101,6 +101,7 @@ import { openRequest, storedClaimUpdatedAt, } from './request-router-open-harness.ts'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; const mockResolveTargetDevice = vi.mocked(getResolveTargetDeviceMock()); const mockEnsureDeviceReady = vi.mocked(ensureDeviceReady); @@ -114,7 +115,7 @@ function createOpenHandler( leaseRegistry = new LeaseRegistry(), ) { return createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry, @@ -438,7 +439,7 @@ test('close fails synchronously when root composition omits platform resource cl actions: [], }); const handler = createProductionRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), diff --git a/src/daemon/__tests__/request-router-record-flags.test.ts b/src/daemon/__tests__/request-router-record-flags.test.ts index c6fe7eeb8a..d21de850a0 100644 --- a/src/daemon/__tests__/request-router-record-flags.test.ts +++ b/src/daemon/__tests__/request-router-record-flags.test.ts @@ -8,14 +8,15 @@ import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/de */ import { test, expect } from 'vitest'; import path from 'node:path'; -import os from 'node:os'; + import { createRequestHandler } from './test-device-runtime-gateway.ts'; import { LeaseRegistry } from '../lease-registry.ts'; import { makeSessionStore } from '../../__tests__/test-utils/store-factory.ts'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; function createHandler() { return createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore: makeSessionStore('agent-device-router-record-flags-'), leaseRegistry: new LeaseRegistry(), diff --git a/src/daemon/__tests__/request-router-recording-health.test.ts b/src/daemon/__tests__/request-router-recording-health.test.ts index 5baebdeb80..1b6be6aa8f 100644 --- a/src/daemon/__tests__/request-router-recording-health.test.ts +++ b/src/daemon/__tests__/request-router-recording-health.test.ts @@ -1,7 +1,7 @@ import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; import { legacyDispatchCapture } from './legacy-snapshot-capture-fixture.ts'; import { test, expect, vi, beforeEach } from 'vitest'; -import os from 'node:os'; + import path from 'node:path'; vi.mock('../../platform-runtime-apple-resources.ts', async (importOriginal) => ({ @@ -19,6 +19,7 @@ import type { SessionState } from '../session-state.ts'; import { LeaseRegistry } from '../lease-registry.ts'; import { makeSessionStore } from '../../__tests__/test-utils/store-factory.ts'; import { makeTestScreenRecordingResource } from '../../__tests__/test-utils/screen-recording-live-handle.ts'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; const mockObserveRunnerSession = vi.mocked(appleSessionObservation.observeRunnerSession); @@ -55,7 +56,7 @@ test('router blocks non-record commands when recording was invalidated', async ( sessionStore.set('default', session); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), @@ -109,7 +110,7 @@ test('router allows canonical iOS simulator gestures during overlay recording af sessionId: 'runner-after', }); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), diff --git a/src/daemon/__tests__/request-router-repair-expired.test.ts b/src/daemon/__tests__/request-router-repair-expired.test.ts index 65f71db864..b3d0ad2666 100644 --- a/src/daemon/__tests__/request-router-repair-expired.test.ts +++ b/src/daemon/__tests__/request-router-repair-expired.test.ts @@ -8,10 +8,9 @@ import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/de */ import { test, expect, vi } from 'vitest'; import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import { getResolveTargetDeviceMock } from './request-router-dispatch-mocks.ts'; -import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; vi.mock('../device-ready.ts', () => ({ ensureDeviceReady: vi.fn(async () => {}) })); @@ -22,13 +21,14 @@ import type { DeviceInfo } from '@agent-device/kernel/device'; import { LeaseRegistry } from '../lease-registry.ts'; import { makeSessionStore } from '../../__tests__/test-utils/store-factory.ts'; import { inspectAdReplay } from '@agent-device/ad-replay'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; const mockResolveTargetDevice = vi.mocked(getResolveTargetDeviceMock()); function makeHandler(prefix: string) { const sessionStore = makeSessionStore(prefix); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), diff --git a/src/daemon/__tests__/request-router-replay-scope.test.ts b/src/daemon/__tests__/request-router-replay-scope.test.ts index 2f013e242b..e9850fe882 100644 --- a/src/daemon/__tests__/request-router-replay-scope.test.ts +++ b/src/daemon/__tests__/request-router-replay-scope.test.ts @@ -2,10 +2,9 @@ import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/de import { legacyDispatchCapture } from './legacy-snapshot-capture-fixture.ts'; import { beforeEach, expect, test, vi } from 'vitest'; import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import { getResolveTargetDeviceMock } from './request-router-dispatch-mocks.ts'; -import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; vi.mock('../device-ready.ts', () => ({ ensureDeviceReady: vi.fn(async () => {}) })); @@ -44,6 +43,7 @@ import { ensureDeviceReady } from '../device-ready.ts'; // Readiness is package-owned; hold the open at the fixture's platform-neutral readiness gate. import { awaitFixtureReadiness } from './application-lifecycle-runtime-fixture.ts'; import { createRequestPlatformProviders } from '../../platform-runtime.ts'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; const mockResolveTargetDevice = vi.mocked(getResolveTargetDeviceMock()); const mockEnsureDeviceReady = vi.mocked(ensureDeviceReady); @@ -71,7 +71,7 @@ test('replay runs active-session actions inside the parent request provider scop const appleRunnerProvider = vi.fn(() => undefined); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), @@ -109,7 +109,7 @@ test('replay routes session-changing actions through the full request path', asy const appleRunnerProvider = vi.fn(() => undefined); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), @@ -144,7 +144,7 @@ test('session list includes a cwd-scoped session opened by replay', async () => const sessionStore = makeSessionStore('agent-device-replay-open-scope-'); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), @@ -210,7 +210,7 @@ test('fresh replay retains a dynamically selected device through finalization', }); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry, diff --git a/src/daemon/__tests__/request-router-response-level.test.ts b/src/daemon/__tests__/request-router-response-level.test.ts index 1d49b173a4..17a3cc5a72 100644 --- a/src/daemon/__tests__/request-router-response-level.test.ts +++ b/src/daemon/__tests__/request-router-response-level.test.ts @@ -1,6 +1,6 @@ import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; import { test, expect, vi, beforeEach } from 'vitest'; -import os from 'node:os'; + import path from 'node:path'; vi.mock('@agent-device/platform-apple/runner/operations', async (importOriginal) => { @@ -39,6 +39,7 @@ import type { SessionState } from '../session-state.ts'; import { LeaseRegistry } from '../lease-registry.ts'; import { makeSessionStore } from '../../__tests__/test-utils/store-factory.ts'; import { commandRpcParamsSchema } from '@agent-device/kernel/contracts'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; const REPRESENTATIVE_PAYLOAD = { message: 'scroll-ok', items: [1, 2, 3] } as const; /** What the bound `app-switcher` leaf answers; this file's registered view digests it. */ @@ -67,7 +68,7 @@ function makeHandler() { return { sessionStore, handler: createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), diff --git a/src/daemon/__tests__/request-router-screenshot.test.ts b/src/daemon/__tests__/request-router-screenshot.test.ts index c340463026..2e6a8229be 100644 --- a/src/daemon/__tests__/request-router-screenshot.test.ts +++ b/src/daemon/__tests__/request-router-screenshot.test.ts @@ -2,9 +2,8 @@ import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/de import { legacyDispatchCapture } from './legacy-snapshot-capture-fixture.ts'; import { test, expect, vi, beforeEach } from 'vitest'; import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; -import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; // `scroll` still executes through legacy platform dispatch; screenshot and click bind their fake // at the facts/bind seam below instead (ADR 0019). @@ -31,6 +30,7 @@ import { PNG } from '@agent-device/capture-kit/png'; import { ANDROID_EMULATOR, IOS_SIMULATOR } from '../../__tests__/test-utils/device-fixtures.ts'; import { makeSessionStore } from '../../__tests__/test-utils/store-factory.ts'; import { makeSession as makeBaseSession } from '../../__tests__/test-utils/session-factories.ts'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; function makeSession(name: string): SessionState { return makeBaseSession(name, { device: ANDROID_EMULATOR }); @@ -78,7 +78,7 @@ function screenshotRouter( sessionStore.set(session.name, session); const runtime = screenshotRuntimeFixture(options); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), @@ -112,7 +112,7 @@ test('screenshot resolves relative positional path against request cwd', async ( }); test('screenshot keeps absolute positional path unchanged', async () => { - const absolutePath = path.join(os.tmpdir(), 'evidence/test.png'); + const absolutePath = path.join(mkdtempForTestSync('evidence-test'), 'evidence/test.png'); const { handler, sessionStore, runtime } = screenshotRouter(makeSession('default')); await handler({ @@ -236,7 +236,7 @@ test('router serializes concurrent commands for the same device across sessions' }); const handler = createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), @@ -285,7 +285,10 @@ test('router serializes concurrent commands for the same device across sessions' }, 15_000); test('iOS simulator screenshot response includes output dimensions and logical density metadata', async () => { - const screenshotPath = path.join(os.tmpdir(), `agent-device-ios-meta-${Date.now()}.png`); + const screenshotPath = path.join( + mkdtempForTestSync('agent-device-ios-meta'), + `agent-device-ios-meta-${Date.now()}.png`, + ); const { handler } = screenshotRouter(makeIosSession('default'), { onCapture: (input) => writeSolidPng(input.outPath, 402, 874), }); @@ -312,7 +315,10 @@ test('iOS simulator screenshot response includes output dimensions and logical d }); test('non-iOS screenshot response tolerates malformed PNG metadata', async () => { - const screenshotPath = path.join(os.tmpdir(), `agent-device-android-truncated-${Date.now()}.png`); + const screenshotPath = path.join( + mkdtempForTestSync('agent-device-android-truncated'), + `agent-device-android-truncated-${Date.now()}.png`, + ); const { handler } = screenshotRouter(makeSession('default'), { onCapture: (input) => fs.writeFileSync(input.outPath, Buffer.alloc(0)), }); @@ -334,7 +340,10 @@ test('non-iOS screenshot response tolerates malformed PNG metadata', async () => }); test('iOS simulator screenshot omits logical density metadata after --scale downscale', async () => { - const screenshotPath = path.join(os.tmpdir(), `agent-device-ios-scale-${Date.now()}.png`); + const screenshotPath = path.join( + mkdtempForTestSync('agent-device-ios-scale'), + `agent-device-ios-scale-${Date.now()}.png`, + ); const { handler } = screenshotRouter(makeIosSession('default'), { onCapture: (input) => writeSolidPng(input.outPath, 804, 1748), }); @@ -397,7 +406,10 @@ test('screenshot --pixel-density is rejected outside iOS-family simulators', asy }); test('screenshot --overlay-refs captures a fresh snapshot when the session has none', async () => { - const screenshotPath = path.join(os.tmpdir(), `agent-device-overlay-${Date.now()}.png`); + const screenshotPath = path.join( + mkdtempForTestSync('agent-device-overlay'), + `agent-device-overlay-${Date.now()}.png`, + ); const order: string[] = []; const { handler, runtime } = screenshotRouter(makeSession('default'), { onCapture: (input) => { @@ -449,7 +461,10 @@ test('screenshot --overlay-refs captures a fresh snapshot when the session has n }); test('screenshot --overlay-refs uses presented iOS runner rows for overlay refs', async () => { - const screenshotPath = path.join(os.tmpdir(), `agent-device-overlay-ios-${Date.now()}.png`); + const screenshotPath = path.join( + mkdtempForTestSync('agent-device-overlay-ios'), + `agent-device-overlay-ios-${Date.now()}.png`, + ); const { handler, sessionStore, runtime } = screenshotRouter(makeIosSession('default'), { onCapture: (input) => writeSolidPng(input.outPath, 402, 874), snapshotResult: () => ({ @@ -546,7 +561,10 @@ test('screenshot --overlay-refs uses a fresh snapshot instead of stale session s ]), createdAt: Date.now(), }; - const screenshotPath = path.join(os.tmpdir(), `agent-device-overlay-${Date.now()}.png`); + const screenshotPath = path.join( + mkdtempForTestSync('agent-device-overlay'), + `agent-device-overlay-${Date.now()}.png`, + ); const { handler, sessionStore } = screenshotRouter(session, { snapshotResult: () => ({ backend: 'android', @@ -592,7 +610,10 @@ test('screenshot --overlay-refs uses a fresh snapshot instead of stale session s }); test('screenshot --pixel-density keeps overlay refs aligned to scaled iOS simulator output', async () => { - const screenshotPath = path.join(os.tmpdir(), `agent-device-overlay-2x-${Date.now()}.png`); + const screenshotPath = path.join( + mkdtempForTestSync('agent-device-overlay-2x'), + `agent-device-overlay-2x-${Date.now()}.png`, + ); const { handler } = screenshotRouter(makeIosSession('default'), { onCapture: (input) => writeSolidPng(input.outPath, 804, 1748), snapshotResult: () => ({ diff --git a/src/daemon/__tests__/request-router-session-address.test.ts b/src/daemon/__tests__/request-router-session-address.test.ts index 6a1b8bf2d6..16bbece3f1 100644 --- a/src/daemon/__tests__/request-router-session-address.test.ts +++ b/src/daemon/__tests__/request-router-session-address.test.ts @@ -1,10 +1,9 @@ import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/device-inventory-gateways.ts'; import { legacyDispatchCapture } from './legacy-snapshot-capture-fixture.ts'; import { test, expect, vi, beforeEach } from 'vitest'; -import os from 'node:os'; + import path from 'node:path'; import { getResolveTargetDeviceMock } from './request-router-dispatch-mocks.ts'; -import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; vi.mock('../device-ready.ts', () => ({ ensureDeviceReady: vi.fn(async () => {}) })); vi.mock('@agent-device/host-kit/process', async (importOriginal) => { @@ -32,6 +31,7 @@ import { awaitFixtureReadiness } from './application-lifecycle-runtime-fixture.t import { makeSessionStore } from '../../__tests__/test-utils/store-factory.ts'; import type { DeviceInfo } from '@agent-device/kernel/device'; import type { DaemonRequest, DaemonResponse } from '../daemon-request.ts'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; /** * The production route for the #2031/#1394 defect: a request without `--session` resolves the @@ -61,7 +61,7 @@ function makeIosDevice(id: string): DeviceInfo { function createHandler(sessionStore: ReturnType) { return createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), diff --git a/src/daemon/__tests__/request-router-typed-error.test.ts b/src/daemon/__tests__/request-router-typed-error.test.ts index 9ca4e583d2..2c048f282b 100644 --- a/src/daemon/__tests__/request-router-typed-error.test.ts +++ b/src/daemon/__tests__/request-router-typed-error.test.ts @@ -2,7 +2,7 @@ import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/de import { legacyDispatchCapture } from './legacy-snapshot-capture-fixture.ts'; import { test, expect, vi, beforeEach } from 'vitest'; import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; vi.mock('@agent-device/platform-apple/runner/operations', async (importOriginal) => { @@ -30,6 +30,7 @@ import { } from '../../__tests__/test-utils/session-factories.ts'; import type { DeviceInfo } from '@agent-device/kernel/device'; import { AppError, retriableForErrorCode } from '@agent-device/kernel/errors'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; const mockLifecycleEffect = vi.mocked(dispatchApplicationLifecycleEffect); @@ -58,7 +59,7 @@ function makeHandler(sessionStore = makeSessionStore('agent-device-router-typed- return { sessionStore, handler: createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), @@ -206,7 +207,7 @@ test('#1391: an ordinary close-time script-save failure surfaces details.reason/ const { sessionStore, handler } = makeHandler(); const session = makeAuthoringSession('typed-error', TENANT_SESSION_DEFAULTS); const targetPath = path.join( - os.tmpdir(), + mkdtempForTestSync('agent-device-router-typed-error'), `agent-device-router-typed-error-${Date.now()}-${Math.random().toString(36).slice(2)}.ad`, ); fs.writeFileSync(targetPath, 'pre-existing\n'); diff --git a/src/daemon/__tests__/screenshot-crop.test.ts b/src/daemon/__tests__/screenshot-crop.test.ts index 4406b33d65..40df8f9547 100644 --- a/src/daemon/__tests__/screenshot-crop.test.ts +++ b/src/daemon/__tests__/screenshot-crop.test.ts @@ -12,12 +12,12 @@ import type { SnapshotQualityVerdict, } from '@agent-device/kernel/snapshot'; import fs from 'node:fs'; -import os from 'node:os'; import path from 'node:path'; import { expect, test, vi } from 'vitest'; import type { SessionState } from '../session-state.ts'; import { buildScreenshotCropWarnings, cropScreenshotToSelector } from '../screenshot-crop.ts'; import { writeSolidPng } from './screenshot-runtime-fixture.ts'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; test('the warning composition is the single owner: partial intersection and only', () => { expect(buildScreenshotCropWarnings(undefined)).toEqual([]); @@ -88,10 +88,7 @@ type CropSeam = Readonly<{ function cropSeam(params: CropSeamParams): CropSeam { const session = makeSession('default', { device: params.device, surface: params.surface }); - const screenshotPath = path.join( - os.tmpdir(), - `agent-device-crop-on-${Date.now()}-${Math.random().toString(36).slice(2)}.png`, - ); + const screenshotPath = path.join(mkdtempForTestSync('agent-device-crop-on'), 'screenshot.png'); writeSolidPng(screenshotPath, params.png.width, params.png.height); const captureSnapshot = vi.fn(async (): Promise => ({ nodes: params.nodes, @@ -110,7 +107,10 @@ function cropSeam(params: CropSeamParams): CropSeam { surface: params.surface, cropOn: params.cropOn ?? 'label="Save"', screenshotPath, - logPath: path.join(os.tmpdir(), 'agent-device-crop-on-daemon.log'), + logPath: path.join( + mkdtempForTestSync('agent-device-crop-on-daemon'), + 'agent-device-crop-on-daemon.log', + ), dispatchContext: {}, captureSnapshot, }), diff --git a/src/daemon/__tests__/screenshot-runtime.test.ts b/src/daemon/__tests__/screenshot-runtime.test.ts index ea019f94c5..57b833bc8f 100644 --- a/src/daemon/__tests__/screenshot-runtime.test.ts +++ b/src/daemon/__tests__/screenshot-runtime.test.ts @@ -1,5 +1,5 @@ import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import { expect, test } from 'vitest'; import { SCREENSHOT_CROP_REASONS } from '@agent-device/contracts/capture'; @@ -11,6 +11,7 @@ import { resolveScreenshotGenericExecution } from '../screenshot-runtime.ts'; import { screenshotRuntimeFixture } from './screenshot-runtime-fixture.ts'; import type { DaemonRequest } from '../daemon-request.ts'; import type { SessionState } from '../session-state.ts'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; const unavailableCapture = Object.freeze({ available: false, @@ -36,7 +37,7 @@ function executionParams( return { session, sessionName: session.name, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), command: 'screenshot', request: req, positionals: req.positionals ?? [], @@ -60,7 +61,10 @@ async function executeResult( test('admits one capture plan, binds once, and hands the runtime the resolved destination', async () => { const fixture = screenshotRuntimeFixture(); const session = makeSession('default', { device: ANDROID_EMULATOR }); - const outPath = path.join(os.tmpdir(), `agent-device-bound-capture-${Date.now()}.png`); + const outPath = path.join( + mkdtempForTestSync('agent-device-bound-capture'), + `agent-device-bound-capture-${Date.now()}.png`, + ); const req = screenshotRequest({ positionals: [outPath] }); const resolved = await resolveScreenshotGenericExecution({ @@ -82,7 +86,10 @@ test('admits one capture plan, binds once, and hands the runtime the resolved de test('an iOS simulator session capture skips the redundant boot probe', async () => { const fixture = screenshotRuntimeFixture(); const session = makeSession('ios', { device: IOS_SIMULATOR }); - const outPath = path.join(os.tmpdir(), `agent-device-ios-boot-probe-${Date.now()}.png`); + const outPath = path.join( + mkdtempForTestSync('agent-device-ios-boot-probe'), + `agent-device-ios-boot-probe-${Date.now()}.png`, + ); const req = screenshotRequest({ positionals: [outPath] }); const resolved = await resolveScreenshotGenericExecution({ @@ -241,7 +248,10 @@ test('the crop runs after the platform write and before the shared scale', async }), }); const session = makeSession('default', { device: ANDROID_EMULATOR }); - const outPath = path.join(os.tmpdir(), `agent-device-crop-order-${Date.now()}.png`); + const outPath = path.join( + mkdtempForTestSync('agent-device-crop-order'), + `agent-device-crop-order-${Date.now()}.png`, + ); const req = screenshotRequest({ positionals: [outPath], flags: { screenshotCropOn: 'label="Save"', screenshotScale: 0.5 }, @@ -286,7 +296,10 @@ test('a partial crop surfaces its warning once in the result record and annotate }), }); const session = makeSession('default', { device: ANDROID_EMULATOR }); - const outPath = path.join(os.tmpdir(), `agent-device-crop-warning-${Date.now()}.png`); + const outPath = path.join( + mkdtempForTestSync('agent-device-crop-warning'), + `agent-device-crop-warning-${Date.now()}.png`, + ); const req = screenshotRequest({ positionals: [outPath], flags: { screenshotCropOn: 'label="Save"' }, diff --git a/src/daemon/__tests__/session-store.test.ts b/src/daemon/__tests__/session-store.test.ts index 1e02647ff3..a16ef9c89f 100644 --- a/src/daemon/__tests__/session-store.test.ts +++ b/src/daemon/__tests__/session-store.test.ts @@ -11,6 +11,7 @@ import { HEAL_COMPLETE_SENTINEL } from '../session-script-writer.ts'; import { parseReplayScriptDetailed } from '@agent-device/ad-script'; import type { TargetAnnotationV1 } from '@agent-device/contracts/replay'; import { repairPublication } from '../../__tests__/test-utils/session-factories.ts'; + import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; type RecordActionEntry = Parameters[1]; @@ -108,7 +109,9 @@ test('expandHome resolves tilde, relative-with-cwd, and absolute paths', () => { }); test('defaultTracePath sanitizes session name', () => { - const store = new SessionStore(path.join(os.tmpdir(), 'agent-device-tests')); + const store = new SessionStore( + path.join(mkdtempForTestSync('agent-device-tests'), 'agent-device-tests'), + ); const session = makeSession('session with spaces'); const tracePath = store.defaultTracePath(session); assert.match(tracePath, /session_with_spaces/); @@ -116,7 +119,11 @@ test('defaultTracePath sanitizes session name', () => { }); test('resolveSessionDir keeps every session dir beneath the sessions dir', () => { - const sessionsDir = path.join(os.tmpdir(), 'agent-device-tests', 'sessions'); + const sessionsDir = path.join( + mkdtempForTestSync('agent-device-tests'), + 'agent-device-tests', + 'sessions', + ); const store = new SessionStore(sessionsDir); assert.equal(store.resolveSessionDir('a/b:c d'), path.join(sessionsDir, 'a_b_c_d')); // `.` and `..` survive `safeSessionName` unchanged, so without an explicit diff --git a/src/daemon/__tests__/snapshot-custom-actions-platform-guard.test.ts b/src/daemon/__tests__/snapshot-custom-actions-platform-guard.test.ts index f8da105434..07e2da8d76 100644 --- a/src/daemon/__tests__/snapshot-custom-actions-platform-guard.test.ts +++ b/src/daemon/__tests__/snapshot-custom-actions-platform-guard.test.ts @@ -9,7 +9,7 @@ import { createTestDeviceInventoryGateways } from '../../__tests__/test-utils/de */ import { test, expect } from 'vitest'; import path from 'node:path'; -import os from 'node:os'; + import type { DeviceInfo } from '@agent-device/kernel/device'; import { createRequestHandler } from './test-device-runtime-gateway.ts'; import { LeaseRegistry } from '../lease-registry.ts'; @@ -21,6 +21,7 @@ import { captureSnapshotUse, } from '@agent-device/contracts/platform-runtime-operations'; import { snapshotRuntimeFixture } from './snapshot-runtime-fixture.ts'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; function snapshotDeviceRuntimeGateway(): DeviceRuntimeGateway { const runtime = snapshotRuntimeFixture(); @@ -49,7 +50,7 @@ function handlerForDevice(device: DeviceInfo) { appBundleId: 'com.example.app', } as SessionState); return createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), diff --git a/src/daemon/__tests__/snapshot-quality-latch.test.ts b/src/daemon/__tests__/snapshot-quality-latch.test.ts index 9f0335edce..337bf2e031 100644 --- a/src/daemon/__tests__/snapshot-quality-latch.test.ts +++ b/src/daemon/__tests__/snapshot-quality-latch.test.ts @@ -1,4 +1,3 @@ -import os from 'node:os'; import path from 'node:path'; import { expect, test, vi } from 'vitest'; import type { SnapshotQualityVerdict } from '@agent-device/kernel/snapshot'; @@ -17,6 +16,7 @@ import { SessionStore } from '../session-store.ts'; import type { SessionState } from '../session-state.ts'; import { legacyDispatchCapture } from './legacy-snapshot-capture-fixture.ts'; import { snapshotRuntimeFixture } from './snapshot-runtime-fixture.ts'; +import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts'; vi.mock('../snapshot-interactor-capture.ts', async () => { const fixture = await import('./legacy-snapshot-capture-fixture.ts'); @@ -183,7 +183,10 @@ test('the recovered warning rides the shared warnings channel and foreign entrie }); function scenario() { - const root = path.join(os.tmpdir(), `agent-device-quality-latch-${crypto.randomUUID()}`); + const root = path.join( + mkdtempForTestSync('agent-device-quality-latch'), + `agent-device-quality-latch-${crypto.randomUUID()}`, + ); const sessionStore = new SessionStore(path.join(root, 'sessions')); const sessionName = 'default'; const session = makeIosSession(sessionName, { appBundleId: 'com.example.app' }); diff --git a/src/daemon/handlers/__tests__/session-appstate-input.test.ts b/src/daemon/handlers/__tests__/session-appstate-input.test.ts index 9f9becd5ec..855694ffac 100644 --- a/src/daemon/handlers/__tests__/session-appstate-input.test.ts +++ b/src/daemon/handlers/__tests__/session-appstate-input.test.ts @@ -1,5 +1,4 @@ import { test, expect } from 'vitest'; -import * as os from 'node:os'; import * as path from 'node:path'; import { mockDispatch, @@ -10,6 +9,7 @@ import { } from './session-test-harness.ts'; import type { SessionState } from '../../session-state.ts'; import { handleSessionCommands } from './session-command-harness.ts'; +import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; test('appstate on iOS requires active session on selected device', async () => { const sessionStore = makeSessionStore(); @@ -44,7 +44,7 @@ test('appstate on iOS requires active session on selected device', async () => { flags: { platform: 'ios', device: 'iPhone 17 Pro' }, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -90,7 +90,7 @@ test('appstate returns session appName when bundle id is unavailable', async () flags: { platform: 'ios', device: 'iPhone 17 Pro' }, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -139,7 +139,7 @@ test('appstate fails when iOS session has no tracked app', async () => { flags: { platform: 'ios', device: 'iPhone 17 Pro' }, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -172,7 +172,7 @@ test('appstate without session on iOS selector returns SESSION_NOT_FOUND', async flags: { platform: 'ios', device: 'iPhone 17 Pro' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -195,7 +195,7 @@ test('appstate with explicit missing session returns SESSION_NOT_FOUND', async ( flags: { session: 'sim', platform: 'ios', device: 'iPhone 17 Pro' }, }, sessionName: 'sim', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -220,7 +220,7 @@ test('clipboard requires an active session or explicit device selector', async ( flags: {}, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -260,7 +260,7 @@ test('clipboard rejects unsupported iOS physical devices', async () => { flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); diff --git a/src/daemon/handlers/__tests__/session-boot-shutdown.test.ts b/src/daemon/handlers/__tests__/session-boot-shutdown.test.ts index 331247d2f0..54dfb1146d 100644 --- a/src/daemon/handlers/__tests__/session-boot-shutdown.test.ts +++ b/src/daemon/handlers/__tests__/session-boot-shutdown.test.ts @@ -1,5 +1,4 @@ import { test, expect } from 'vitest'; -import * as os from 'node:os'; import * as path from 'node:path'; import { AppError } from '@agent-device/kernel/errors'; import { @@ -18,6 +17,7 @@ import { mockInspectDeviceRuntimeFacts, mockShutdownTargetRuntime, } from './session-command-harness.ts'; +import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; test('boot requires session or explicit selector', async () => { const sessionStore = makeSessionStore(); @@ -30,7 +30,7 @@ test('boot requires session or explicit selector', async () => { flags: {}, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -72,7 +72,7 @@ test('boot prefers explicit device selector over active session device', async ( flags: { platform: 'ios', device: 'iPhone 17 Pro' }, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -120,7 +120,7 @@ test('boot --headless admits a stopped Android emulator through facts and binds flags: { platform: 'android', device: 'Pixel_9_Pro_XL', headless: true }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -164,7 +164,7 @@ test('boot rejects the macOS host boot cell after one facts inspection and befor flags: { platform: 'macos' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -200,7 +200,7 @@ test('boot admits a stopped Android emulator through normal readiness', async () flags: { platform: 'android', device: 'Pixel_9_Pro_XL' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -241,7 +241,7 @@ test('boot forwards Android serial admission policy to readiness', async () => { }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -271,7 +271,7 @@ test('boot --headless requires avd selector when device cannot be resolved', asy flags: { platform: 'android', serial: 'emulator-5554', headless: true }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -306,7 +306,7 @@ test('boot --headless rejects non-Android selectors', async () => { flags: { platform: 'ios', device: 'iPhone 17 Pro', headless: true }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -339,7 +339,7 @@ test('boot keeps --target validation before facts inspection', async () => { flags: { platform: 'android', target: 'tv', device: 'Pixel_9_Pro_XL' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -375,7 +375,7 @@ test('shutdown turns off selected iOS simulator', async () => { flags: { platform: 'ios', device: 'iPhone 17 Pro' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -421,7 +421,7 @@ test('shutdown rejects active session device and points to close --shutdown', as flags: { platform: 'ios', device: 'iPhone 17 Pro' }, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -460,7 +460,7 @@ test('shutdown turns off selected Android emulator', async () => { flags: { platform: 'android', device: 'Pixel_9_Pro_XL' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -507,7 +507,7 @@ test('shutdown rejects unsupported physical devices', async () => { flags: { platform: 'ios', udid: 'device-1' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -550,7 +550,7 @@ test('shutdown returns an error response when selected target shutdown fails', a flags: { platform: 'ios', device: 'iPhone 17 Pro' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); diff --git a/src/daemon/handlers/__tests__/session-command-replay.test.ts b/src/daemon/handlers/__tests__/session-command-replay.test.ts index 0565535069..56d2929ae4 100644 --- a/src/daemon/handlers/__tests__/session-command-replay.test.ts +++ b/src/daemon/handlers/__tests__/session-command-replay.test.ts @@ -1,12 +1,12 @@ import { test, expect } from 'vitest'; import * as fs from 'node:fs'; -import * as os from 'node:os'; import * as path from 'node:path'; import { makeSessionStore } from './session-test-harness.ts'; import { handleSessionCommands } from './session-command-harness.ts'; import type { DaemonRequest } from '../../daemon-request.ts'; -import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; + import { replayScriptSourceBundleFor } from '../../../__tests__/test-utils/replay-script-source.ts'; +import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; test('replay parses open --relaunch flag and replays open with relaunch semantics', async () => { const sessionStore = makeSessionStore(); @@ -24,7 +24,7 @@ test('replay parses open --relaunch flag and replays open with relaunch semantic flags: { replayScriptSource: replayScriptSourceBundleFor(replayPath) }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async (req) => { invoked.push(req); @@ -63,7 +63,7 @@ test('replay parses runtime set flags and replays runtime command', async () => meta: { cwd: replayRoot }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async (request) => { invoked.push(request); @@ -102,7 +102,7 @@ test('replay parses inline open runtime flags and replays open with runtime payl meta: { cwd: replayRoot }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async (request) => { invoked.push(request); @@ -143,7 +143,7 @@ test('replay inherits parent device selectors for each invoked step', async () = }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async (req) => { invoked.push(req); @@ -180,7 +180,7 @@ test('replay inherits the parent web platform selector for each invoked step', a }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async (req) => { invoked.push(req); @@ -221,7 +221,7 @@ test('test --platform web reports no matching scripts, typed or untyped, because meta: { cwd: root, requestId: 'suite-web-excluded' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async () => { throw new Error('test must not invoke any step when --platform web matches nothing'); diff --git a/src/daemon/handlers/__tests__/session-relaunch-close.test.ts b/src/daemon/handlers/__tests__/session-relaunch-close.test.ts index 55e44fecec..573ed6f3cf 100644 --- a/src/daemon/handlers/__tests__/session-relaunch-close.test.ts +++ b/src/daemon/handlers/__tests__/session-relaunch-close.test.ts @@ -1,5 +1,4 @@ import { test, expect, vi, beforeEach } from 'vitest'; -import * as os from 'node:os'; import * as path from 'node:path'; import { LeaseRegistry } from '../../lease-registry.ts'; import { @@ -77,6 +76,7 @@ import { } from '@agent-device/platform-apple/runner/operations'; import { runMacOsAlertAction } from '@agent-device/platform-apple/macos'; import { refFrameState } from '../../ref-frame.ts'; +import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; const mockResolveTargetDevice = vi.mocked(getResolveTargetDeviceMock()); const mockEnsureDeviceReady = vi.mocked(ensureDeviceReady); @@ -121,7 +121,7 @@ function createHandler( leaseRegistry: LeaseRegistry = new LeaseRegistry(), ) { return createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry, diff --git a/src/daemon/handlers/__tests__/session-relaunch-guards.test.ts b/src/daemon/handlers/__tests__/session-relaunch-guards.test.ts index b0dad6bf4b..3909cf06d6 100644 --- a/src/daemon/handlers/__tests__/session-relaunch-guards.test.ts +++ b/src/daemon/handlers/__tests__/session-relaunch-guards.test.ts @@ -1,5 +1,4 @@ import { test, expect } from 'vitest'; -import * as os from 'node:os'; import * as path from 'node:path'; import { mockResolveTargetDevice, @@ -11,6 +10,7 @@ import { } from './session-test-harness.ts'; import { handleSessionCommands } from './session-command-harness.ts'; import { makeTestScreenRecordingResource } from '../../../__tests__/test-utils/screen-recording-live-handle.ts'; +import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; test('open --relaunch rejects URL targets', async () => { const sessionStore = makeSessionStore(); @@ -23,7 +23,7 @@ test('open --relaunch rejects URL targets', async () => { flags: { relaunch: true }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -47,7 +47,7 @@ test('open --relaunch fails without app when no session exists', async () => { flags: { relaunch: true }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -71,7 +71,7 @@ test('open --relaunch rejects Android app binary paths', async () => { flags: { relaunch: true, platform: 'android' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -93,7 +93,7 @@ test('open --relaunch rejects bare Android app binary filenames', async () => { flags: { relaunch: true, platform: 'android' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -126,7 +126,7 @@ test('open --relaunch rejects Android app binary paths for active sessions', asy flags: { relaunch: true, platform: 'android' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -159,7 +159,7 @@ test('open --relaunch rejects Android app binary paths for active sessions befor flags: { relaunch: true, platform: 'android' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -181,7 +181,7 @@ test('open --relaunch rejects Android app binary paths before resolving a new de flags: { relaunch: true, platform: 'android' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -223,7 +223,7 @@ test('open on in-use device returns DEVICE_IN_USE before readiness checks', asyn flags: { platform: 'ios' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -277,7 +277,7 @@ test('open on device owned by recording session returns recording recovery hint' flags: { platform: 'ios' }, }, sessionName: 'test-attempt', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); diff --git a/src/daemon/handlers/__tests__/session-runtime-command.test.ts b/src/daemon/handlers/__tests__/session-runtime-command.test.ts index a1e170d13c..83690a8b3a 100644 --- a/src/daemon/handlers/__tests__/session-runtime-command.test.ts +++ b/src/daemon/handlers/__tests__/session-runtime-command.test.ts @@ -1,5 +1,5 @@ import { expect, test } from 'vitest'; -import os from 'node:os'; + import path from 'node:path'; import type { DaemonRequest } from '../../daemon-request.ts'; import { @@ -20,6 +20,7 @@ import { gestureRuntimeSpies, } from '../../__tests__/test-device-runtime-gateway.ts'; import { refFrameState } from '../../ref-frame.ts'; +import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; test('runtime set/show/clear manages session-scoped runtime hints before open', async () => { const sessionStore = makeSessionStore(); @@ -41,7 +42,7 @@ test('runtime set/show/clear manages session-scoped runtime hints before open', }, }, sessionName: 'remote-runtime', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -55,7 +56,7 @@ test('runtime set/show/clear manages session-scoped runtime hints before open', flags: {}, }, sessionName: 'remote-runtime', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -79,7 +80,7 @@ test('runtime set/show/clear manages session-scoped runtime hints before open', flags: {}, }, sessionName: 'remote-runtime', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -115,7 +116,7 @@ test('runtime clear removes applied transport hints for the active app', async ( flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -164,7 +165,7 @@ test('runtime clear expires the ref frame at the admitted hint mutation boundary flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -217,7 +218,7 @@ test('runtime clear rejects a false runtime-hints fact before its one implementa flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -242,7 +243,10 @@ test('runtime clear rejects a false runtime-hints fact before its one implementa test('runtime gesture-viewport admits and binds the exact viewport operation once', async () => { const sessionStore = makeSessionStore(); const sessionName = 'runtime-gesture-viewport'; - const logPath = path.join(os.tmpdir(), 'runtime-gesture-viewport.log'); + const logPath = path.join( + mkdtempForTestSync('runtime-gesture-viewport'), + 'runtime-gesture-viewport.log', + ); sessionStore.set( sessionName, makeSession(sessionName, { diff --git a/src/daemon/handlers/__tests__/session-selector-dispatch.test.ts b/src/daemon/handlers/__tests__/session-selector-dispatch.test.ts index 915f047091..8f9d32fa61 100644 --- a/src/daemon/handlers/__tests__/session-selector-dispatch.test.ts +++ b/src/daemon/handlers/__tests__/session-selector-dispatch.test.ts @@ -1,5 +1,4 @@ import { test, expect, vi } from 'vitest'; -import * as os from 'node:os'; import * as path from 'node:path'; import { AppError } from '@agent-device/kernel/errors'; import { @@ -28,6 +27,7 @@ import { import type { SessionState } from '../../session-state.ts'; import { handleSessionCommands } from './session-command-harness.ts'; import { refFrameState } from '../../ref-frame.ts'; +import { mkdtempForTestSync } from '../../../__tests__/test-utils/tmp-dir.ts'; const available = Object.freeze({ available: true } as const); const keyboardFamilyDenial = Object.freeze({ @@ -83,7 +83,7 @@ test('keyboard dismiss crosses the ADR 0014 seam while keyboard status preserves }; mockResolveTargetDevice.mockResolvedValue(device); mockDispatch.mockResolvedValue({}); - const logPath = path.join(os.tmpdir(), 'daemon.log'); + const logPath = path.join(mkdtempForTestSync('daemon'), 'daemon.log'); const { inspectFacts, bindDevice } = keyboardCapableRuntime(device); // dismiss mutates the device → frame expires. @@ -140,7 +140,7 @@ test('keyboard dismiss expires the frame before the invocation runs, even when i booted: true, }; mockResolveTargetDevice.mockResolvedValue(device); - const logPath = path.join(os.tmpdir(), 'daemon.log'); + const logPath = path.join(mkdtempForTestSync('daemon'), 'daemon.log'); const { inspectFacts, bindDevice } = keyboardCapableRuntime(device, { keyboardDismiss: () => { expect(refFrameState(sessionStore.get(sessionName)!)).toBe('expired'); @@ -180,7 +180,7 @@ test('keyboard requires an active session or explicit device selector', async () flags: {}, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -207,7 +207,7 @@ test('keyboard dismiss requires active iOS session for explicit selectors', asyn flags: { platform: 'ios', device: 'iPhone 17 Pro' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); diff --git a/src/daemon/interaction/internal/__tests__/interaction-ios-tap-outcome.test.ts b/src/daemon/interaction/internal/__tests__/interaction-ios-tap-outcome.test.ts index 18aabb5798..05d6f77977 100644 --- a/src/daemon/interaction/internal/__tests__/interaction-ios-tap-outcome.test.ts +++ b/src/daemon/interaction/internal/__tests__/interaction-ios-tap-outcome.test.ts @@ -2,7 +2,7 @@ import type { CommandFlags } from '@agent-device/contracts/command'; import { legacyDispatchCapture } from '../../../__tests__/legacy-snapshot-capture-fixture.ts'; import { beforeEach, expect, test, vi } from 'vitest'; import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import { AppError } from '@agent-device/kernel/errors'; import { buildSnapshotPresentationKey } from '@agent-device/kernel/snapshot'; @@ -35,6 +35,7 @@ import { } from '../../../__tests__/interaction-get-runtime-fixture.ts'; import { captureSnapshotWithInteractor } from '../../../snapshot-interactor-capture.ts'; import { corroborateIosTapFailure } from '../interaction-ios-tap-outcome.ts'; +import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; vi.mock('../../../snapshot-interactor-capture.ts', async () => { const fixture = await import('../../../__tests__/legacy-snapshot-capture-fixture.ts'); @@ -675,7 +676,7 @@ test('a corroborated runtime coordinate tap does not schedule a no-change retry' }); test('corroborated runtime taps retain target evidence through save and replay', async () => { - const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-ios-tap-replay-')); + const root = mkdtempForTestSync('agent-device-ios-tap-replay-'); const sessionName = 'ios-recorded-tap'; const sessionStore = makeSessionStore(); sessionStore.set( diff --git a/src/daemon/replay/internal/__tests__/session-test-fail-fast.test.ts b/src/daemon/replay/internal/__tests__/session-test-fail-fast.test.ts index 466ad95887..7d17a031ff 100644 --- a/src/daemon/replay/internal/__tests__/session-test-fail-fast.test.ts +++ b/src/daemon/replay/internal/__tests__/session-test-fail-fast.test.ts @@ -1,9 +1,10 @@ import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import { expect, test } from 'vitest'; import { makeSessionStore } from '../../../../__tests__/test-utils/store-factory.ts'; import { handleSessionCommands } from '../../../handlers/__tests__/session-command-harness.ts'; + import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; test('test --fail-fast continues after passing scripts', async () => { @@ -22,7 +23,7 @@ test('test --fail-fast continues after passing scripts', async () => { flags: { failFast: true }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore: makeSessionStore('agent-device-test-fail-fast-pass-store-'), invoke: async (request) => { invokedPaths.push(String(request.positionals?.[0])); diff --git a/src/daemon/replay/internal/__tests__/session-test-runner.test.ts b/src/daemon/replay/internal/__tests__/session-test-runner.test.ts index d3ad9ff984..44af3870f7 100644 --- a/src/daemon/replay/internal/__tests__/session-test-runner.test.ts +++ b/src/daemon/replay/internal/__tests__/session-test-runner.test.ts @@ -1,6 +1,5 @@ import { test, expect, vi } from 'vitest'; import fs from 'node:fs'; -import * as os from 'node:os'; import * as path from 'node:path'; import { clearRequestCanceled, markRequestCanceled } from '@agent-device/host-kit/request'; import { @@ -12,6 +11,7 @@ import { } from '../../../handlers/__tests__/session-test-harness.ts'; import type { DaemonRequest } from '../../../daemon-request.ts'; import { handleSessionCommands } from '../../../handlers/__tests__/session-command-harness.ts'; + import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; // Opening a Simulator schedules a best-effort runner prewarm that outlives the request; a real one @@ -68,7 +68,7 @@ test('session_list includes device_udid and ios_simulator_device_set for iOS ses const response = await handleSessionCommands({ req: { token: 't', session: 'default', command: 'session_list', positionals: [] }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -110,7 +110,7 @@ test('test filters replay scripts by context platform and skips untyped files', meta: { cwd: root, requestId: 'suite-filter' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async (req) => { invoked.push(req); @@ -179,7 +179,7 @@ test('test binds each replay script to its declared platform metadata', async () meta: { cwd: root, requestId: 'suite-platforms' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async (req) => { invoked.push(req); @@ -217,7 +217,7 @@ test('test cleans up suite-owned sessions after each executed script', async () meta: { cwd: root, requestId: 'suite-cleanup' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async (req) => { sessionStore.set( @@ -256,7 +256,7 @@ test('test retries failed scripts with fresh suite-owned sessions', async () => meta: { cwd: root, requestId: 'suite-retries' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async (req) => { invoked.push(req); @@ -309,7 +309,7 @@ test('test applies per-script timeout and writes attempt artifacts', async () => meta: { cwd: root, requestId: 'suite-timeout' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async (_req) => { invocationCount += 1; @@ -377,7 +377,7 @@ test('open does not retain a session when the request was canceled before comple meta: { requestId }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -408,7 +408,7 @@ test('test returns invalid args when no replay scripts match the platform filter meta: { cwd: root }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -433,7 +433,7 @@ test('test rejects duplicate replay test metadata in the context header', async meta: { cwd: root }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); diff --git a/src/daemon/replay/internal/__tests__/session-test-suite-infrastructure.test.ts b/src/daemon/replay/internal/__tests__/session-test-suite-infrastructure.test.ts index 289183222f..690e4bcfa1 100644 --- a/src/daemon/replay/internal/__tests__/session-test-suite-infrastructure.test.ts +++ b/src/daemon/replay/internal/__tests__/session-test-suite-infrastructure.test.ts @@ -1,11 +1,12 @@ import { expect, test, vi } from 'vitest'; import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; -import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; + import type { DaemonResponse } from '../../../daemon-request.ts'; import { handleSessionCommands } from '../../../handlers/__tests__/session-command-harness.ts'; import { expectOkData, makeSessionStore } from './session-test-suite.fixtures.ts'; +import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; vi.mock('../../../snapshot-interactor-capture.ts', () => ({ captureSnapshotWithInteractor: vi.fn(async () => { @@ -29,7 +30,7 @@ test('test --json marks a typed live device claim as infrastructure without retr flags: { retries: 3 }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async () => { attempts += 1; @@ -76,7 +77,7 @@ test('test --json retries DEVICE_IN_USE without typed device-claim provenance', flags: { retries: 3 }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async () => { attempts += 1; diff --git a/src/daemon/replay/internal/__tests__/session-test-suite.test.ts b/src/daemon/replay/internal/__tests__/session-test-suite.test.ts index 326add5485..46eea119c4 100644 --- a/src/daemon/replay/internal/__tests__/session-test-suite.test.ts +++ b/src/daemon/replay/internal/__tests__/session-test-suite.test.ts @@ -1,6 +1,5 @@ import type { RequestProgressEvent } from '@agent-device/contracts/progress'; import { test, expect, vi } from 'vitest'; -import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; // ADR 0012 migration step 2: every replay step failure now attempts a // post-failure screen digest capture + suggestion re-resolution through the @@ -15,7 +14,7 @@ vi.mock('../../../snapshot-interactor-capture.ts', () => ({ })); import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import { handleSessionCommands, @@ -37,6 +36,7 @@ import { makeAndroidSession, makeMacOsSession, } from '../../../../__tests__/test-utils/session-factories.ts'; +import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; const ANDROID_ONE: DeviceInfo = { platform: 'android', @@ -71,7 +71,7 @@ test('test does not retry infrastructure startup failures and stops the suite', flags: { retries: 3 }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async (req) => { invoked.push(req); @@ -120,7 +120,7 @@ test('test --fail-fast stops the suite after the first failure and leaves the re flags: { failFast: true }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async (req) => { invoked.push(req); @@ -164,7 +164,7 @@ test('test surfaces a suite-level failure when a source fails to parse', async ( flags: {}, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async (req) => { invoked.push(req); @@ -198,7 +198,7 @@ test('test discovers Maestro YAML suites when replay backend is set', async () = meta: { cwd: root, requestId: 'maestro-suite' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async (req) => { invoked.push(req); @@ -233,7 +233,7 @@ test('test emits progress when attempts retry and pass', async () => { flags: { retries: 1 }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async () => { attempts += 1; @@ -340,7 +340,7 @@ test('test stops before retrying when a rejected close leaves the prior macOS se flags: { retries: 2 }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, inspectFacts: async (device) => { const facts = await mockInspectDeviceRuntimeFacts(device); @@ -387,7 +387,7 @@ test('test stops retrying after maxAttempts when every attempt fails', async () flags: { retries: 2 }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async () => { attemptCount += 1; @@ -430,7 +430,7 @@ test('test emits skip progress without synthetic duration', async () => { flags: { platform: 'android' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async () => ({ ok: true, data: { replayed: 1, healed: 0 } }), }), @@ -482,7 +482,7 @@ test('test aggregates snapshot diagnostics from replay session samples', async ( flags: { platform: 'android' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async (req) => { const session = @@ -540,7 +540,7 @@ test('test aggregates snapshot diagnostics from failed replay session samples', flags: { platform: 'android' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async (req) => { const session = @@ -611,7 +611,7 @@ test('test stops the suite when the parent request is canceled during an active meta: { cwd: root, requestId: parentRequestId }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async (req) => { const nestedRequestId = req.meta?.requestId; @@ -681,7 +681,7 @@ test('test --shard-all runs each runnable entry on each selected device', async }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async (req) => { invoked.push(req); @@ -752,7 +752,7 @@ test('test --shard-split distributes runnable entries by modulo and keeps skips flags: { platform: 'android', shardSplit: 2 }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async (req) => { invoked.push(req); @@ -800,7 +800,7 @@ test('test sharding rejects mutually exclusive shard modes', async () => { flags: { platform: 'android', shardAll: 2, shardSplit: 2 }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async () => ({ ok: true, data: { replayed: 1, healed: 0 } }), }); @@ -826,7 +826,7 @@ test('test sharding rejects non-positive shard counts', async () => { flags: { platform: 'android', shardAll: 0 }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async () => ({ ok: true, data: { replayed: 1, healed: 0 } }), }); @@ -855,7 +855,7 @@ test('test sharding rejects fewer matched devices than requested shards', async flags: { platform: 'android', shardAll: 2 }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async () => ({ ok: true, data: { replayed: 1, healed: 0 } }), }), @@ -889,7 +889,7 @@ test('test sharding does not require devices when every entry is skipped', async flags: { platform: 'android', shardAll: 2 }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async () => ({ ok: true, data: { replayed: 1, healed: 0 } }), }), diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-capabilities-install-projection.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-capabilities-install-projection.test.ts index cd315879c4..07dd92244b 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-capabilities-install-projection.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-capabilities-install-projection.test.ts @@ -1,5 +1,5 @@ import { expect, test } from 'vitest'; -import os from 'node:os'; + import path from 'node:path'; import { PUBLIC_COMMANDS } from '@agent-device/command-registry/catalog'; import { makeAndroidSession } from '../../../../__tests__/test-utils/session-factories.ts'; @@ -10,6 +10,7 @@ import type { } from '../../../request-runtime-binding.ts'; import { createCapabilitiesAdmissionRuntime } from './session-capabilities.fixtures.ts'; import { handleSessionCommands } from '../../../handlers/__tests__/session-command-harness.ts'; +import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; test('capabilities projects the install family from exactly one facts inspection', async () => { const { sessionName, sessionStore } = createAndroidCapabilitiesSession('install-family'); @@ -160,7 +161,7 @@ async function dispatchCapabilities(params: { flags: {}, }, sessionName: params.sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore: params.sessionStore, bindDevice: params.bindDevice, inspectFacts: params.inspectFacts, diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-capabilities.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-capabilities.test.ts index 40929c4fd5..d6ff281cb3 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-capabilities.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-capabilities.test.ts @@ -1,6 +1,6 @@ import { test, expect, vi } from 'vitest'; import path from 'node:path'; -import os from 'node:os'; + import { PUBLIC_COMMANDS } from '@agent-device/command-registry/catalog'; import { LINUX_DEVICE, @@ -31,6 +31,7 @@ import type { InspectDeviceRuntimeFacts, } from '../../../request-runtime-binding.ts'; import { handleSessionCommands } from '../../../handlers/__tests__/session-command-harness.ts'; +import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; /** The system leaves this owner refuses: the retired fallback listed them unconditionally. */ const ANDROID_REFUSED_SYSTEM_COMMANDS = ['clipboard', 'alert', 'settings', 'app-switcher']; @@ -67,7 +68,7 @@ async function projectAndroidCapabilities(sessionName: string) { flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, inspectFacts: runtime.inspectFacts, bindDevice: runtime.bindDevice, @@ -149,7 +150,7 @@ test('capabilities excludes logs from an unavailable provider-mode XCTest runtim flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, inspectFacts: runtime.inspectFacts, bindDevice: runtime.bindDevice, @@ -192,7 +193,7 @@ test('capabilities excludes network when the runtime fact is unavailable', async flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, inspectFacts: runtime.inspectFacts, bindDevice: runtime.bindDevice, @@ -236,7 +237,7 @@ test('capabilities includes apps for the available HarmonyOS runtime fact', asyn flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, bindDevice: runtime.bindDevice, inspectFacts: runtime.inspectFacts, @@ -314,7 +315,7 @@ test.each(APPS_UNAVAILABLE_CAPABILITY_CASES)( flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, bindDevice: runtime.bindDevice, inspectFacts: runtime.inspectFacts, @@ -348,7 +349,7 @@ test('capabilities excludes appstate when its runtime fact is unavailable', asyn flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, bindDevice: runtime.bindDevice, inspectFacts: runtime.inspectFacts, @@ -381,7 +382,7 @@ test('capabilities excludes appstate when its readiness fact is unavailable', as flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, bindDevice: runtime.bindDevice, inspectFacts: runtime.inspectFacts, @@ -456,7 +457,7 @@ test.each([ flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, inspectFacts, invoke: async () => ({ ok: true, data: {} }), @@ -492,7 +493,7 @@ test('capabilities accepts a stopped Android AVD placeholder for explicit platfo flags: { platform: 'android' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: async () => ({ ok: true, data: {} }), }), diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-close-error-precedence.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-close-error-precedence.test.ts index 2b58308d07..3d2019eb61 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-close-error-precedence.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-close-error-precedence.test.ts @@ -19,7 +19,6 @@ const { mockReleaseRunnerOnClose, mockStopIosRunnerSession, noopInvoke, - os, path, resetSessionCloseShutdownMocks, withDiagnosticsScope, @@ -57,7 +56,7 @@ test('targeted close preserves the platform-close AppError and still runs later flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }), @@ -135,7 +134,7 @@ test('a failed platform close retains the device claim and reports it', async () flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -248,7 +247,7 @@ test('a failing best-effort cleanup also retains the device claim and reports it flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -333,7 +332,7 @@ test('a successful close clears the device claim', async () => { flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -385,7 +384,7 @@ test('targeted close skips platform dispatch and preserves the error when the re flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }), diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-close-lifecycle-runtime.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-close-lifecycle-runtime.test.ts index a7028898d2..7ca47b926c 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-close-lifecycle-runtime.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-close-lifecycle-runtime.test.ts @@ -1,5 +1,5 @@ import { beforeEach, expect, test, vi } from 'vitest'; -import os from 'node:os'; + import path from 'node:path'; import { SessionStore } from '../../../session-store.ts'; import type { DaemonRequest, DaemonResponse } from '../../../daemon-request.ts'; @@ -9,7 +9,6 @@ import { readSessionRuntimeRevision, refFrameState, } from '../../../ref-frame.ts'; -import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; const runtimeHintsModule = vi.hoisted(() => ({ evaluated: false, @@ -28,6 +27,7 @@ import { } from '../../../handlers/__tests__/session-command-harness.ts'; import { lifecycleRuntimeFacts } from '../../../__tests__/application-lifecycle-runtime-harness.ts'; import { dispatchApplicationLifecycleEffect } from '../../../__tests__/application-lifecycle-runtime-fixture.ts'; +import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; const mockDispatch = vi.mocked(dispatchApplicationLifecycleEffect); const noopInvoke = async (_req: DaemonRequest): Promise => ({ ok: true, data: {} }); @@ -65,7 +65,7 @@ async function close(params: { return await handleSessionCommands({ req: closeRequest(params.sessionName, params.positionals ?? [], params.internal), sessionName: params.sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore: params.sessionStore, invoke: noopInvoke, }); diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-close-resource-cleanup.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-close-resource-cleanup.test.ts index b9a96de5ac..d4709ea028 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-close-resource-cleanup.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-close-resource-cleanup.test.ts @@ -10,6 +10,7 @@ import { sessionCloseShutdownFixture, type SessionState, } from './session-close-shutdown.fixtures.ts'; +import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; const { AppError, @@ -21,7 +22,6 @@ const { mockDispatchCommand, mockStopAndroidSnapshotHelperSessionForDevice, noopInvoke, - os, path, resetSessionCloseShutdownMocks, WEB_DESKTOP_DEVICE, @@ -53,7 +53,7 @@ test('close stops Android snapshot helper session before deleting session', asyn flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -66,7 +66,10 @@ test('close stops Android snapshot helper session before deleting session', asyn test('close stops active host audio probe before deleting session', async () => { const sessionStore = makeSessionStore(); const sessionName = 'macos-active-audio-probe-session'; - const statusPath = path.join(os.tmpdir(), 'missing-audio-probe.json'); + const statusPath = path.join( + mkdtempForTestSync('missing-audio-probe'), + 'missing-audio-probe.json', + ); const startedAt = Date.now() - 2000; const stoppedResult = { audio: 'probe' as const, @@ -145,7 +148,7 @@ test('close stops active host audio probe before deleting session', async () => flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -169,7 +172,7 @@ test('close dispatches web session cleanup without a positional target', async ( flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -220,7 +223,7 @@ test('close preserves the session and lease when provider release fails so it ca flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, leaseRegistry, leaseLifecycleProvider: { diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-close-save-script.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-close-save-script.test.ts index da8fc5d400..6c4c5a933a 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-close-save-script.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-close-save-script.test.ts @@ -1,5 +1,6 @@ import { beforeEach, expect, test } from 'vitest'; import { sessionCloseShutdownFixture } from './session-close-shutdown.fixtures.ts'; +import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; const { fs, @@ -10,7 +11,6 @@ const { mockReleaseRunnerOnClose, mockStopIosRunnerSession, noopInvoke, - os, path, recordingFinishMock, resetSessionCloseShutdownMocks, @@ -21,7 +21,10 @@ beforeEach(resetSessionCloseShutdownMocks); test('close --save-script on a never-armed session is rejected before teardown, with no script written', async () => { const sessionStore = makeSessionStore(); const sessionName = 'ios-unarmed-close-save-script-session'; - const scriptPath = path.join(os.tmpdir(), `agent-device-unarmed-close-${Date.now()}.ad`); + const scriptPath = path.join( + mkdtempForTestSync('agent-device-unarmed-close'), + `agent-device-unarmed-close-${Date.now()}.ad`, + ); // The fixture must carry real cleanup-bearing state (here: an active recording, like // `makeIosSimulatorRecordingSession`'s other consumers) so this test can actually prove the // guard runs *before* `stopBestEffortSessionResources` — not just that the response rejects. @@ -41,7 +44,7 @@ test('close --save-script on a never-armed session is rejected before teardown, flags: { saveScript: scriptPath }, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }), @@ -75,7 +78,7 @@ test('close --save-script on a never-armed session is rejected before teardown, flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -114,7 +117,7 @@ test('close --save-script on a session with an active .ad repair transaction is flags: { saveScript: true }, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-close-shutdown-platform.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-close-shutdown-platform.test.ts index 4b0d145ec1..ae4e4ec1fa 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-close-shutdown-platform.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-close-shutdown-platform.test.ts @@ -4,6 +4,7 @@ import { type DeviceBinding, type PlatformRuntimeOperations, } from './session-close-shutdown.fixtures.ts'; +import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; const { handleSessionCommands, @@ -15,7 +16,6 @@ const { mockShutdownTargetRuntime, narrowDeviceBinding, noopInvoke, - os, path, providerRuntimeOwner, resetSessionCloseShutdownMocks, @@ -79,7 +79,7 @@ test('close --shutdown calls shutdownSimulator for iOS simulator and includes re flags: { shutdown: true }, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -144,7 +144,7 @@ test('close --shutdown keeps a selected provider-owned iOS simulator off local s flags: { shutdown: true }, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -186,7 +186,7 @@ test('close --shutdown calls shutdownAndroidEmulator for Android emulator and in flags: { shutdown: true }, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -229,7 +229,7 @@ test('close --shutdown is ignored for non-simulator iOS devices', async () => { flags: { shutdown: true }, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -267,7 +267,7 @@ test('close --shutdown is ignored for Android devices', async () => { flags: { shutdown: true }, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -313,7 +313,7 @@ test('close --shutdown returns success and failure payload when shutdownAndroidE flags: { shutdown: true }, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -353,7 +353,7 @@ test('close --shutdown returns success and failure payload when shutdownSimulato flags: { shutdown: true }, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-close-shutdown.fixtures.ts b/src/daemon/session-lifecycle/internal/__tests__/session-close-shutdown.fixtures.ts index e002541468..5ff3d967b8 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-close-shutdown.fixtures.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-close-shutdown.fixtures.ts @@ -1,12 +1,10 @@ import { vi } from 'vitest'; import fs from 'node:fs'; -import os from 'node:os'; import path from 'node:path'; import { SessionStore } from '../../../session-store.ts'; import type { DaemonRequest, DaemonResponse } from '../../../daemon-request.ts'; import type { SessionState } from '../../../session-state.ts'; import { AppError } from '@agent-device/kernel/errors'; -import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; vi.mock('@agent-device/platform-apple/runner/operations', async (importOriginal) => { const actual = @@ -86,6 +84,7 @@ import { createDurableResourceEnvelope } from '@agent-device/capture-kit'; import { screenRecordingResourceStore } from '../../../screen-recording-resource-store.ts'; import { lifecycleRuntimeFacts } from '../../../__tests__/application-lifecycle-runtime-harness.ts'; import { dispatchApplicationLifecycleEffect } from '../../../__tests__/application-lifecycle-runtime-fixture.ts'; +import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; export type { DeviceBinding, PlatformRuntimeOperations, SessionState }; @@ -146,7 +145,7 @@ function makeIosSimulatorRecordingSession( }, ); session.appBundleId = 'com.example.app'; - const outPath = path.join(os.tmpdir(), name + '.mp4'); + const outPath = path.join(mkdtempForTestSync('test'), name + '.mp4'); const finish = vi.fn(async () => options.recorderExitCode ? ({ @@ -262,7 +261,6 @@ export const sessionCloseShutdownFixture = Object.freeze({ mockStopIosRunnerSession, narrowDeviceBinding, noopInvoke, - os, path, providerRuntimeOwner, recordingCleanupMock, diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-devices-batch-runtime.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-devices-batch-runtime.test.ts index 51b72d0179..bbf3f549e7 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-devices-batch-runtime.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-devices-batch-runtime.test.ts @@ -1,6 +1,5 @@ import { test, expect, vi } from 'vitest'; import * as fs from 'node:fs'; -import * as os from 'node:os'; import * as path from 'node:path'; vi.mock('../../../materialized-path-registry.ts', async (importOriginal) => { @@ -19,10 +18,11 @@ import { handleSessionCommands } from '../../../handlers/__tests__/session-comma import { makeSessionStore } from '../../../../__tests__/test-utils/store-factory.ts'; import { makeSession } from '../../../../__tests__/test-utils/session-factories.ts'; import type { DaemonRequest, DaemonResponse } from '../../../daemon-request.ts'; -import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; + import { withTestDeviceInventory } from '../../../../__tests__/test-utils/device-inventory-gateways.ts'; import type { DeviceInfo } from '@agent-device/kernel/device'; import { readCurrentOwnerIdentity } from '@agent-device/host-kit/process'; +import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; const noopInvoke = async (_req: DaemonRequest): Promise => ({ ok: true, @@ -389,7 +389,7 @@ test('close clears retained materialized install paths bound to the session', as const response = await handleSessionCommands({ req: { token: 't', session: sessionName, command: 'close', positionals: [], flags: {} }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-open-execution-runtime.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-open-execution-runtime.test.ts index 43dcbe44ea..babda0afb4 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-open-execution-runtime.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-open-execution-runtime.test.ts @@ -1,5 +1,5 @@ import { test, expect, vi, beforeEach } from 'vitest'; -import os from 'node:os'; + import path from 'node:path'; import type { DaemonRequest } from '../../../daemon-request.ts'; import { AppError } from '@agent-device/kernel/errors'; @@ -71,6 +71,7 @@ import { makeSessionStore, noopInvoke, } from './session-open-runtime.fixtures.ts'; +import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; const mockDispatch = vi.mocked(dispatchApplicationLifecycleEffect); const mockApplyRuntimeHints = vi.mocked(applyRuntimeHintValues); @@ -129,7 +130,7 @@ test('open runtime payload replaces stored session runtime atomically', async () }, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -207,7 +208,7 @@ test('open runtime payload clears stale applied transport hints before launch', }, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -259,7 +260,7 @@ test('open runtime payload rejects invalid metro port before app launch', async }, }, sessionName: 'runtime-open-invalid-port', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -296,7 +297,7 @@ test('open runtime payload rejects malformed runtime objects without mutating se runtime: 'not-an-object' as unknown as DaemonRequest['runtime'], }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -342,7 +343,7 @@ test('open runtime payload does not persist replacement when launch fails', asyn }, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }), @@ -379,7 +380,7 @@ test('a first open keeps both positionals so the deep link still reaches the app flags: { platform: 'android' }, }, sessionName: 'deep-link-open', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-open-existing.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-open-existing.test.ts index 346ee7460c..446e8ebe46 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-open-existing.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-open-existing.test.ts @@ -1,5 +1,4 @@ import { test, expect } from 'vitest'; -import * as os from 'node:os'; import * as path from 'node:path'; import { buildSnapshotSignatures } from '@agent-device/capture-kit/snapshot-freshness'; import { AppError } from '@agent-device/kernel/errors'; @@ -16,6 +15,7 @@ import { } from '../../../handlers/__tests__/session-test-harness.ts'; import type { SessionState } from '../../../session-state.ts'; import { handleSessionCommands } from '../../../handlers/__tests__/session-command-harness.ts'; +import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; test('open web URL on iOS device session without active app falls back to Safari', async () => { const sessionStore = makeSessionStore(); @@ -46,7 +46,7 @@ test('open web URL on iOS device session without active app falls back to Safari flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -91,7 +91,7 @@ test('open app and URL on existing iOS device session keeps app context', async flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -138,7 +138,7 @@ test('open app on existing macOS session resolves and stores bundle id', async ( flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -173,7 +173,7 @@ test('open rejects --surface on non-macOS devices', async () => { }, }, sessionName: 'ios-surface', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -223,7 +223,7 @@ test('open on existing macOS frontmost-app session preserves surface without --s }, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -289,7 +289,7 @@ test('open on existing iOS session refreshes unavailable simulator by name', asy flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }), @@ -338,7 +338,7 @@ test('open app on existing Android session resolves and stores package id', asyn flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -382,7 +382,7 @@ test('open intent target on existing Android session clears stale package contex flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -435,7 +435,7 @@ test('open on existing Android session preserves a comparable freshness baseline flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-open-runtime.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-open-runtime.test.ts index 09eea8543c..a6d1ffa997 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-open-runtime.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-open-runtime.test.ts @@ -1,5 +1,5 @@ import { test, expect, vi, beforeEach } from 'vitest'; -import os from 'node:os'; + import path from 'node:path'; const mockResolveTargetDevice = vi.hoisted(() => vi.fn()); @@ -82,6 +82,7 @@ import { makeSessionStore, noopInvoke, } from './session-open-runtime.fixtures.ts'; +import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; const mockDispatch = vi.mocked(dispatchApplicationLifecycleEffect); const mockApplyRuntimeHints = vi.mocked(applyRuntimeHintValues); @@ -130,7 +131,7 @@ test('open applies stored runtime launchUrl and reports runtime hints', async () flags: { platform: 'android' }, }, sessionName: 'runtime-open', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -194,7 +195,7 @@ test('open rejects a false runtime-hints fact before its one implementation bind runtime: { metroHost: '10.0.0.10', metroPort: 8081 }, }, sessionName: 'runtime-open-false-fact', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -212,7 +213,7 @@ test('open rejects a false runtime-hints fact before its one implementation bind test('open applies launch-only flags only to the direct app launch before runtime launchUrl', async () => { const sessionStore = makeSessionStore(); - const launchConsolePath = path.join(os.tmpdir(), 'launch-console.log'); + const launchConsolePath = path.join(mkdtempForTestSync('launch-console'), 'launch-console.log'); const dispatchCalls: Array<{ command: string; positionals: string[]; @@ -244,7 +245,7 @@ test('open applies launch-only flags only to the direct app launch before runtim flags: { platform: 'ios', launchConsole: launchConsolePath, launchArgs: ['-Flag', 'YES'] }, }, sessionName: 'launch-console-runtime', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -287,7 +288,7 @@ test('open --metro-port alone defaults the host to 10.0.2.2 on an Android emulat runtime: { metroPort: 8084 }, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -318,7 +319,7 @@ test('open --metro-port alone defaults the host to 127.0.0.1 on an iOS simulator runtime: { metroPort: 8084 }, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -349,7 +350,7 @@ test('open --metro-port alone stays host-ambiguous on a physical Android device' runtime: { metroPort: 8084 }, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -379,7 +380,7 @@ test('open --relaunch allows Android package names ending with apk-like suffix', flags: { relaunch: true, platform: 'android' }, }, sessionName: 'default', - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-open-url-prewarm.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-open-url-prewarm.test.ts index 9e790a85d5..b2651327ba 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-open-url-prewarm.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-open-url-prewarm.test.ts @@ -1,5 +1,4 @@ import { test, expect, vi, beforeEach } from 'vitest'; -import * as os from 'node:os'; import * as path from 'node:path'; import { AppError } from '@agent-device/kernel/errors'; @@ -64,6 +63,7 @@ import { resolveIosApp, resolveIosSimulatorDeepLinkBundleId, } from '@agent-device/platform-apple/app-resolution'; +import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; const mockResolveTargetDevice = vi.mocked(getResolveTargetDeviceMock()); const mockDispatch = vi.mocked(dispatchApplicationLifecycleEffect); @@ -102,7 +102,7 @@ function createHandler( deviceRuntimeGateway = lifecycleDeviceRuntimeGateway, ) { return createRequestHandler({ - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), token: 'test-token', sessionStore, leaseRegistry: new LeaseRegistry(), diff --git a/src/daemon/session-lifecycle/internal/__tests__/session-teardown-resources.test.ts b/src/daemon/session-lifecycle/internal/__tests__/session-teardown-resources.test.ts index 4316abbfc2..d2ffe590f8 100644 --- a/src/daemon/session-lifecycle/internal/__tests__/session-teardown-resources.test.ts +++ b/src/daemon/session-lifecycle/internal/__tests__/session-teardown-resources.test.ts @@ -4,6 +4,7 @@ import { type SessionState, } from './session-close-shutdown.fixtures.ts'; import { installFakeManagedAgentBrowser } from '../../../../__tests__/test-utils/web-managed-agent-browser.ts'; +import { mkdtempForTestSync } from '../../../../__tests__/test-utils/tmp-dir.ts'; const { AppError, @@ -17,7 +18,6 @@ const { mockStopIosRunnerSession, mockStopIosRunnerSession: stopIosRunnerSession, noopInvoke, - os, path, recordingCleanupMock, recordingFinishMock, @@ -49,7 +49,7 @@ test('close finalizes an active iOS simulator recording before deleting the sess flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }); @@ -87,7 +87,7 @@ test('close surfaces a recording finalization failure through the cleanup-failur flags: {}, }, sessionName, - logPath: path.join(os.tmpdir(), 'daemon.log'), + logPath: path.join(mkdtempForTestSync('daemon'), 'daemon.log'), sessionStore, invoke: noopInvoke, }), diff --git a/src/platform-runtime-app-log-output.test.ts b/src/platform-runtime-app-log-output.test.ts index a4e37cb4ef..eb0acee0be 100644 --- a/src/platform-runtime-app-log-output.test.ts +++ b/src/platform-runtime-app-log-output.test.ts @@ -1,8 +1,9 @@ import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import { afterEach, expect, test } from 'vitest'; import { openAppLogOutput, readAppLogOutputTail } from './platform-runtime-app-log-output.ts'; +import { mkdtempForTestSync } from './__tests__/test-utils/tmp-dir.ts'; const roots: string[] = []; @@ -11,7 +12,7 @@ afterEach(() => { }); test('reads a bounded trusted app.log suffix and rejects paths outside sessions', async () => { - const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-app-log-output-')); + const root = mkdtempForTestSync('agent-device-app-log-output-'); roots.push(root); const sessionsDir = path.join(root, 'sessions'); const sessionDir = path.join(sessionsDir, 'one'); @@ -29,7 +30,7 @@ test('reads a bounded trusted app.log suffix and rejects paths outside sessions' }); test('rejects a symlinked session directory that escapes the sessions root', async () => { - const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-app-log-symlink-')); + const root = mkdtempForTestSync('agent-device-app-log-symlink-'); roots.push(root); const sessionsDir = path.join(root, 'sessions'); const outside = path.join(root, 'outside'); @@ -58,7 +59,7 @@ test('rejects a final app.log symlink before tail read and preserves the outside }); test('aligns a bounded UTF-8 suffix to the first complete line', async () => { - const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-app-log-tail-')); + const root = mkdtempForTestSync('agent-device-app-log-tail-'); roots.push(root); const sessionsDir = path.join(root, 'sessions'); const sessionDir = path.join(sessionsDir, 'one'); @@ -70,7 +71,7 @@ test('aligns a bounded UTF-8 suffix to the first complete line', async () => { }); test('rejects an asynchronous stream-open failure without an unhandled error', async () => { - const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-app-log-open-')); + const root = mkdtempForTestSync('agent-device-app-log-open-'); roots.push(root); const sessionsDir = path.join(root, 'sessions'); const outputPath = path.join(sessionsDir, 'one', 'app.log'); @@ -80,7 +81,7 @@ test('rejects an asynchronous stream-open failure without an unhandled error', a }); function finalSymlinkFixture(label: string) { - const root = fs.mkdtempSync(path.join(os.tmpdir(), `agent-device-app-log-${label}-`)); + const root = mkdtempForTestSync(`agent-device-app-log-${label}-`); roots.push(root); const sessionsDir = path.join(root, 'sessions'); const sessionDir = path.join(sessionsDir, 'one'); diff --git a/src/platform-runtime-app-log-process.test.ts b/src/platform-runtime-app-log-process.test.ts index 6393440532..13f306871c 100644 --- a/src/platform-runtime-app-log-process.test.ts +++ b/src/platform-runtime-app-log-process.test.ts @@ -1,6 +1,6 @@ import { EventEmitter } from 'node:events'; import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import { PassThrough } from 'node:stream'; import { afterEach, describe, expect, test, vi } from 'vitest'; @@ -28,6 +28,7 @@ import { createManagedAppLogProcesses, recoverLegacyAppLogMarkersAfterDaemonLock, } from './platform-runtime-app-log-process.ts'; +import { mkdtempForTestSync } from './__tests__/test-utils/tmp-dir.ts'; const roots: string[] = []; @@ -226,7 +227,7 @@ describe('managed app-log process host', () => { }); test('recovers only complete owned legacy markers and retains untrusted evidence', async () => { - const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-app-log-legacy-')); + const root = mkdtempForTestSync('agent-device-app-log-legacy-'); roots.push(root); const sessionsDir = path.join(root, 'sessions'); const recoveredPath = legacyMarker(sessionsDir, 'recovered', { @@ -263,7 +264,7 @@ describe('managed app-log process host', () => { }); test('scopes ownership-lost legacy markers to the device encoded by their command', async () => { - const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-app-log-legacy-identity-')); + const root = mkdtempForTestSync('agent-device-app-log-legacy-identity-'); roots.push(root); const sessionsDir = path.join(root, 'sessions'); const markerPath = legacyMarker(sessionsDir, 'android', { @@ -300,7 +301,7 @@ function legacyMarker(sessionsDir: string, sessionId: string, marker: unknown): } function processFixture(options: { settled?: boolean; rejectOutput?: boolean } = {}) { - const root = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-app-log-process-')); + const root = mkdtempForTestSync('agent-device-app-log-process-'); roots.push(root); const sessionsDir = path.join(root, 'sessions'); const sessionDir = path.join(sessionsDir, 'one'); diff --git a/src/platform-runtime-network-host.test.ts b/src/platform-runtime-network-host.test.ts index 7de96b09bc..19d051f5fd 100644 --- a/src/platform-runtime-network-host.test.ts +++ b/src/platform-runtime-network-host.test.ts @@ -1,10 +1,11 @@ import assert from 'node:assert/strict'; import fs from 'node:fs'; -import os from 'node:os'; + import path from 'node:path'; import { readRecentNetworkTrafficFromText } from '@agent-device/capture-kit'; import { afterEach, test } from 'vitest'; import { readRecentAppLogLines } from './platform-runtime-network-host.ts'; +import { mkdtempForTestSync } from './__tests__/test-utils/tmp-dir.ts'; const temporaryDirectories: string[] = []; @@ -69,7 +70,7 @@ test('preserves absolute source line numbers after selecting a bounded suffix', }); function createTemporaryDirectory(): string { - const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-device-network-host-')); + const directory = mkdtempForTestSync('agent-device-network-host-'); temporaryDirectories.push(directory); return directory; } From a60502905e2e41c6e3bae769baa08fcfa8afe373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Tue, 15 Sep 2026 12:23:16 +0200 Subject: [PATCH 2/3] chore(lint): forbid node:os in product tests Add a no-restricted-imports override so a product *.test.ts that imports node:os is a lint error pointing at mkdtempForTest, mirroring the existing node:child_process ban. A follow-up override clears it for the handful of tests that mock production's os.tmpdir()/os.homedir() or assert a real /tmp socket. --- oxlint.config.ts | 42 +++++++++++++++++++ .../host-kit/src/code-signature-cache.test.ts | 1 + .../install-artifact-cancellation.test.ts | 1 + .../runner/__tests__/runner-xctestrun.test.ts | 1 + .../platform-web/src/__tests__/test-utils.ts | 1 + src/__tests__/cli-diff.test.ts | 1 + src/__tests__/daemon-process-takeover.test.ts | 1 + src/__tests__/hermetic-env-setup.test.ts | 1 + src/__tests__/hermetic-env-setup.ts | 1 + src/__tests__/install-source.test.ts | 1 + .../test-utils/web-managed-agent-browser.ts | 1 + src/commands/replay/source-discovery.test.ts | 1 + src/daemon/__tests__/session-store.test.ts | 1 + 13 files changed, 54 insertions(+) diff --git a/oxlint.config.ts b/oxlint.config.ts index 9478b1e6b4..424203aa17 100644 --- a/oxlint.config.ts +++ b/oxlint.config.ts @@ -1,6 +1,20 @@ import nkzw from '@nkzw/oxlint-config'; import { defineConfig } from 'oxlint'; +// Product source trees, where unit tests get scratch from the package tmp-dir helpers. +// scripts/, test/, and package test harnesses manage TMPDIR for child processes on purpose, +// so keeping the roots explicit holds them out of scope that a bare `**` would sweep in. +const PRODUCT_TEST_ROOTS = ['src', 'packages/*/src']; +const PRODUCT_TEST_SHAPES = [ + '**/*.test.ts', + '**/*.fixtures.ts', + '**/__tests__/**/*.ts', + '**/test-utils/**/*.ts', +]; +const PRODUCT_TEST_FILES = PRODUCT_TEST_ROOTS.flatMap((root) => + PRODUCT_TEST_SHAPES.map((shape) => `${root}/${shape}`), +); + export default defineConfig({ env: { builtin: true, @@ -146,5 +160,33 @@ export default defineConfig({ output: 'writable', }, }, + { + // Product tests get scratch from the package tmp-dir helpers, which honor the run's + // redirected TMPDIR; reading node:os reuses a fixed path across the suite. A file with a + // justified read (mocking production, a real socket path, or the TMPDIR mechanism itself) + // oxlint-disables its one import line with a reason instead of widening this list. + files: PRODUCT_TEST_FILES, + rules: { + 'no-restricted-imports': [ + 'error', + { + paths: [ + { + name: 'node:os', + message: + 'Create test scratch with mkdtempForTest()/mkdtempForTestSync() from the package tmp-dir helper instead of reading node:os.', + }, + ], + }, + ], + }, + }, + { + // The tmp-dir helper modules are the sanctioned os.tmpdir() readers. + files: ['**/tmp-dir.ts', '**/tmp-dir.fixtures.ts'], + rules: { + 'no-restricted-imports': ['error', { paths: [] }], + }, + }, ], }); diff --git a/packages/host-kit/src/code-signature-cache.test.ts b/packages/host-kit/src/code-signature-cache.test.ts index a3002c332e..2c290deb7e 100644 --- a/packages/host-kit/src/code-signature-cache.test.ts +++ b/packages/host-kit/src/code-signature-cache.test.ts @@ -1,5 +1,6 @@ import assert from 'node:assert/strict'; import fs from 'node:fs'; +// oxlint-disable-next-line no-restricted-imports -- vi.spyOn(os,'tmpdir') aims production's cache home import os from 'node:os'; import path from 'node:path'; import { afterEach, test, vi } from 'vitest'; diff --git a/packages/platform-android/src/__tests__/install-artifact-cancellation.test.ts b/packages/platform-android/src/__tests__/install-artifact-cancellation.test.ts index 47e34dcba9..4241f7e867 100644 --- a/packages/platform-android/src/__tests__/install-artifact-cancellation.test.ts +++ b/packages/platform-android/src/__tests__/install-artifact-cancellation.test.ts @@ -1,6 +1,7 @@ import assert from 'node:assert/strict'; import dns from 'node:dns/promises'; import { promises as fs } from 'node:fs'; +// oxlint-disable-next-line no-restricted-imports -- vi.spyOn(os,'tmpdir') aims production's install scratch import os from 'node:os'; import path from 'node:path'; import { Readable } from 'node:stream'; diff --git a/packages/platform-apple/src/runner/__tests__/runner-xctestrun.test.ts b/packages/platform-apple/src/runner/__tests__/runner-xctestrun.test.ts index 1d5b5d67f2..ca2c02023f 100644 --- a/packages/platform-apple/src/runner/__tests__/runner-xctestrun.test.ts +++ b/packages/platform-apple/src/runner/__tests__/runner-xctestrun.test.ts @@ -1,6 +1,7 @@ import { test, vi, beforeEach } from 'vitest'; import assert from 'node:assert/strict'; import fs from 'node:fs'; +// oxlint-disable-next-line no-restricted-imports -- mirrors production's os.tmpdir xctestrun path import os from 'node:os'; import path from 'node:path'; import { pathToFileURL } from 'node:url'; diff --git a/packages/platform-web/src/__tests__/test-utils.ts b/packages/platform-web/src/__tests__/test-utils.ts index 6de09a6173..428fadc395 100644 --- a/packages/platform-web/src/__tests__/test-utils.ts +++ b/packages/platform-web/src/__tests__/test-utils.ts @@ -1,5 +1,6 @@ import crypto from 'node:crypto'; import fs from 'node:fs'; +// oxlint-disable-next-line no-restricted-imports -- real Unix socket path under TMPDIR (104-char limit) import os from 'node:os'; import path from 'node:path'; import type { CommandExecutorOverride, ExecOptions } from '@agent-device/host-kit/command'; diff --git a/src/__tests__/cli-diff.test.ts b/src/__tests__/cli-diff.test.ts index 52139c277e..84d2cba277 100644 --- a/src/__tests__/cli-diff.test.ts +++ b/src/__tests__/cli-diff.test.ts @@ -1,6 +1,7 @@ import { describe, test } from 'vitest'; import assert from 'node:assert/strict'; import fs from 'node:fs'; +// oxlint-disable-next-line no-restricted-imports -- asserts the default diff path under os.tmpdir import os from 'node:os'; import path from 'node:path'; import { PNG } from '@agent-device/capture-kit/png'; diff --git a/src/__tests__/daemon-process-takeover.test.ts b/src/__tests__/daemon-process-takeover.test.ts index f82bd9382e..ca39cc8496 100644 --- a/src/__tests__/daemon-process-takeover.test.ts +++ b/src/__tests__/daemon-process-takeover.test.ts @@ -1,6 +1,7 @@ import assert from 'node:assert/strict'; import { spawn } from 'node:child_process'; import fs from 'node:fs'; +// oxlint-disable-next-line no-restricted-imports -- real /tmp socket path within the 104-char limit import os from 'node:os'; import path from 'node:path'; import { afterEach, test } from 'vitest'; diff --git a/src/__tests__/hermetic-env-setup.test.ts b/src/__tests__/hermetic-env-setup.test.ts index eee9713cad..37eaadbe2f 100644 --- a/src/__tests__/hermetic-env-setup.test.ts +++ b/src/__tests__/hermetic-env-setup.test.ts @@ -1,3 +1,4 @@ +// oxlint-disable-next-line no-restricted-imports -- exercises the TMPDIR mechanism and os.availableParallelism import os from 'node:os'; import path from 'node:path'; import { afterEach, test, vi } from 'vitest'; diff --git a/src/__tests__/hermetic-env-setup.ts b/src/__tests__/hermetic-env-setup.ts index f784b4cb98..8cc114dac7 100644 --- a/src/__tests__/hermetic-env-setup.ts +++ b/src/__tests__/hermetic-env-setup.ts @@ -1,4 +1,5 @@ import fs from 'node:fs'; +// oxlint-disable-next-line no-restricted-imports -- sets the run's TMPDIR; must read the real tmpdir import os from 'node:os'; import path from 'node:path'; import { afterEach } from 'vitest'; diff --git a/src/__tests__/install-source.test.ts b/src/__tests__/install-source.test.ts index 0336b4bc66..58206934e5 100644 --- a/src/__tests__/install-source.test.ts +++ b/src/__tests__/install-source.test.ts @@ -3,6 +3,7 @@ import assert from 'node:assert/strict'; import dns from 'node:dns/promises'; import fsSync from 'node:fs'; import fs from 'node:fs/promises'; +// oxlint-disable-next-line no-restricted-imports -- vi.spyOn(os,'tmpdir') aims production's install source import os from 'node:os'; import path from 'node:path'; import { Readable } from 'node:stream'; diff --git a/src/__tests__/test-utils/web-managed-agent-browser.ts b/src/__tests__/test-utils/web-managed-agent-browser.ts index 0a235a8e2d..4e05dd63fd 100644 --- a/src/__tests__/test-utils/web-managed-agent-browser.ts +++ b/src/__tests__/test-utils/web-managed-agent-browser.ts @@ -1,5 +1,6 @@ import crypto from 'node:crypto'; import fs from 'node:fs'; +// oxlint-disable-next-line no-restricted-imports -- real browser install/socket dir under TMPDIR (socket length limit) import os from 'node:os'; import path from 'node:path'; diff --git a/src/commands/replay/source-discovery.test.ts b/src/commands/replay/source-discovery.test.ts index 96421c0d16..7d678cd3e8 100644 --- a/src/commands/replay/source-discovery.test.ts +++ b/src/commands/replay/source-discovery.test.ts @@ -1,6 +1,7 @@ import { test, vi } from 'vitest'; import assert from 'node:assert/strict'; import fs from 'node:fs'; +// oxlint-disable-next-line no-restricted-imports -- vi.spyOn(os,'homedir') for replay source discovery import os from 'node:os'; import path from 'node:path'; import { AppError } from '@agent-device/kernel/errors'; diff --git a/src/daemon/__tests__/session-store.test.ts b/src/daemon/__tests__/session-store.test.ts index a16ef9c89f..acc68faf14 100644 --- a/src/daemon/__tests__/session-store.test.ts +++ b/src/daemon/__tests__/session-store.test.ts @@ -1,6 +1,7 @@ import { test } from 'vitest'; import assert from 'node:assert/strict'; import fs from 'node:fs'; +// oxlint-disable-next-line no-restricted-imports -- asserts a path under os.homedir import os from 'node:os'; import path from 'node:path'; import { AppError } from '@agent-device/kernel/errors'; From a7dc0200356ee775a57e0d9d74cfb650e638dca3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Tue, 15 Sep 2026 18:46:59 +0200 Subject: [PATCH 3/3] fix(lint): keep the child_process and provider bans on files the node:os override matches oxlint doesn't merge no-restricted-imports options across overrides: when several overrides match a file, the last match's options replace the earlier ones instead of accumulating. The new PRODUCT_TEST_FILES override (node:os ban) and the tmp-dir exemption override both matched files that were already covered by the child_process/provider bans (fixtures.ts and test-utils files under src/**, packages/host-kit/src/**, and the host-kit tmp-dir helper), silently dropping those bans for those files. Compose every no-restricted-imports override from shared path/pattern constants so overlapping overrides restate the full union of bans that should apply, instead of one override's options clobbering another's. --- oxlint.config.ts | 113 ++++++++++++++++++++++++++++------------------- 1 file changed, 67 insertions(+), 46 deletions(-) diff --git a/oxlint.config.ts b/oxlint.config.ts index 424203aa17..c2b9e03fa8 100644 --- a/oxlint.config.ts +++ b/oxlint.config.ts @@ -15,6 +15,27 @@ const PRODUCT_TEST_FILES = PRODUCT_TEST_ROOTS.flatMap((root) => PRODUCT_TEST_SHAPES.map((shape) => `${root}/${shape}`), ); +// The last matching override replaces `no-restricted-imports` options instead of merging them, +// so each override restates every ban that applies to the files it matches. +const CHILD_PROCESS_PATH = { + message: + 'Use process helpers from @agent-device/host-kit/command instead of importing node:child_process directly.', + name: 'node:child_process', +}; +const PROVIDER_IMPORT_PATTERN = { + group: ['@agent-device/provider-*'], + message: + 'Command implementations must ask src/cli/connection/provider-policy.ts for provider capabilities.', +}; +const NODE_OS_PATH = { + message: + 'Create test scratch with mkdtempForTest()/mkdtempForTestSync() from the package tmp-dir helper instead of reading node:os.', + name: 'node:os', +}; + +const CHILD_PROCESS_BAN_ROOTS = ['src', 'packages/host-kit/src']; +const PROVIDER_BAN_ROOTS = ['src/commands', 'src/cli/commands']; + export default defineConfig({ env: { builtin: true, @@ -69,57 +90,23 @@ export default defineConfig({ }, }, { - files: ['src/**/*.ts', 'packages/host-kit/src/**/*.ts'], + files: CHILD_PROCESS_BAN_ROOTS.map((root) => `${root}/**/*.ts`), rules: { - 'no-restricted-imports': [ - 'error', - { - paths: [ - { - name: 'node:child_process', - message: - 'Use process helpers from @agent-device/host-kit/command instead of importing node:child_process directly.', - }, - ], - }, - ], + 'no-restricted-imports': ['error', { paths: [CHILD_PROCESS_PATH] }], }, }, { - files: ['src/commands/**/*.ts', 'src/cli/commands/**/*.ts'], + files: PROVIDER_BAN_ROOTS.map((root) => `${root}/**/*.ts`), rules: { 'no-restricted-imports': [ 'error', { - paths: [ - { - name: 'node:child_process', - message: - 'Use process helpers from @agent-device/host-kit/command instead of importing node:child_process directly.', - }, - ], - patterns: [ - { - group: ['@agent-device/provider-*'], - message: - 'Command implementations must ask src/cli/connection/provider-policy.ts for provider capabilities.', - }, - ], + paths: [CHILD_PROCESS_PATH], + patterns: [PROVIDER_IMPORT_PATTERN], }, ], }, }, - { - files: [ - 'packages/host-kit/src/internal/exec.ts', - 'packages/host-kit/src/**/*.test.ts', - 'src/**/*.test.ts', - 'src/**/__tests__/**/*.ts', - ], - rules: { - 'no-restricted-imports': ['error', { paths: [] }], - }, - }, { files: ['examples/test-app/src/**/*.tsx'], rules: { @@ -166,21 +153,39 @@ export default defineConfig({ // justified read (mocking production, a real socket path, or the TMPDIR mechanism itself) // oxlint-disables its one import line with a reason instead of widening this list. files: PRODUCT_TEST_FILES, + rules: { + 'no-restricted-imports': ['error', { paths: [NODE_OS_PATH] }], + }, + }, + { + files: CHILD_PROCESS_BAN_ROOTS.flatMap((root) => + PRODUCT_TEST_SHAPES.map((shape) => `${root}/${shape}`), + ), + rules: { + 'no-restricted-imports': ['error', { paths: [CHILD_PROCESS_PATH, NODE_OS_PATH] }], + }, + }, + { + files: PROVIDER_BAN_ROOTS.flatMap((root) => + PRODUCT_TEST_SHAPES.map((shape) => `${root}/${shape}`), + ), rules: { 'no-restricted-imports': [ 'error', { - paths: [ - { - name: 'node:os', - message: - 'Create test scratch with mkdtempForTest()/mkdtempForTestSync() from the package tmp-dir helper instead of reading node:os.', - }, - ], + paths: [CHILD_PROCESS_PATH, NODE_OS_PATH], + patterns: [PROVIDER_IMPORT_PATTERN], }, ], }, }, + { + // Tests may import node:child_process and provider packages directly. + files: ['packages/host-kit/src/**/*.test.ts', 'src/**/*.test.ts', 'src/**/__tests__/**/*.ts'], + rules: { + 'no-restricted-imports': ['error', { paths: [NODE_OS_PATH] }], + }, + }, { // The tmp-dir helper modules are the sanctioned os.tmpdir() readers. files: ['**/tmp-dir.ts', '**/tmp-dir.fixtures.ts'], @@ -188,5 +193,21 @@ export default defineConfig({ 'no-restricted-imports': ['error', { paths: [] }], }, }, + { + files: CHILD_PROCESS_BAN_ROOTS.flatMap((root) => [ + `${root}/**/tmp-dir.ts`, + `${root}/**/tmp-dir.fixtures.ts`, + ]), + rules: { + 'no-restricted-imports': ['error', { paths: [CHILD_PROCESS_PATH] }], + }, + }, + { + // The host-kit process helpers are the sanctioned node:child_process importer. + files: ['packages/host-kit/src/internal/exec.ts'], + rules: { + 'no-restricted-imports': ['error', { paths: [] }], + }, + }, ], });