diff --git a/src/agents/auth-profiles/store-owner-publication.test.ts b/src/agents/auth-profiles/store-owner-publication.test.ts index c0054f91927e..5a80b71409f6 100644 --- a/src/agents/auth-profiles/store-owner-publication.test.ts +++ b/src/agents/auth-profiles/store-owner-publication.test.ts @@ -3,10 +3,7 @@ import path from "node:path"; import { fileURLToPath } from "node:url"; import { describe, expect, it } from "vitest"; import { runNodeScript } from "../../../test/helpers/run-node-script.js"; -import { - resolveRuntimeWorkerArgv, - resolveRuntimeWorkerUrl, -} from "../../infra/runtime-worker-url.js"; +import { resolveRuntimeWorkerUrl } from "../../infra/runtime-worker-url.js"; import { prepareSecretsRuntimeFastPathSnapshot } from "../../secrets/runtime-fast-path.js"; import { activateSecretsRuntimeSnapshotState } from "../../secrets/runtime-state.js"; import { openOpenClawStateDatabase } from "../../state/openclaw-state-db.js"; @@ -661,8 +658,8 @@ describe("auth publication owner receipts", () => { const laterCwd = path.join(tempDirs.make("openclaw-auth-scope-later-cwd-"), "nested"); fs.mkdirSync(laterCwd); const result = await runNodeScript( - [ - ...resolveRuntimeWorkerArgv(resolveRuntimeWorkerUrl(authProfileScopeCwdEntrypoint)), + (workerArgv) => [ + ...workerArgv(resolveRuntimeWorkerUrl(authProfileScopeCwdEntrypoint)), stateDir, agentDir, laterCwd, diff --git a/src/agents/code-mode-retention.test.ts b/src/agents/code-mode-retention.test.ts index 85f1fcbb1a1e..b197535121b2 100644 --- a/src/agents/code-mode-retention.test.ts +++ b/src/agents/code-mode-retention.test.ts @@ -1,8 +1,7 @@ import { fileURLToPath } from "node:url"; import { expect, it } from "vitest"; import { runNodeScript } from "../../test/helpers/run-node-script.js"; -import { resolveRuntimeWorkerArgv, resolveRuntimeWorkerUrl } from "../infra/runtime-worker-url.js"; -import { resolveTestNodeExecPath } from "../test-utils/node-process.js"; +import { resolveRuntimeWorkerUrl } from "../infra/runtime-worker-url.js"; import { codeModeDescriptionRetentionEntrypoint, codeModeRetentionEntrypoint, @@ -21,10 +20,7 @@ it.for([ }, ])("$name", { timeout: 30_000 }, async ({ entrypoint, expected }, { signal }) => { const result = await runNodeScript( - [ - "--expose-gc", - ...resolveRuntimeWorkerArgv(resolveRuntimeWorkerUrl(entrypoint), resolveTestNodeExecPath()), - ], + (workerArgv) => ["--expose-gc", ...workerArgv(resolveRuntimeWorkerUrl(entrypoint))], { ...process.env, NODE_OPTIONS: "", TSX_DISABLE_CACHE: "1" }, 15_000, { diff --git a/src/agents/embedded-agent-block-chunker.test.ts b/src/agents/embedded-agent-block-chunker.test.ts index f41b65da0e50..0e0ba501172e 100644 --- a/src/agents/embedded-agent-block-chunker.test.ts +++ b/src/agents/embedded-agent-block-chunker.test.ts @@ -3,8 +3,7 @@ import { describe, expect, it, vi } from "vitest"; import * as fences from "../../packages/markdown-core/src/fences.js"; import { markdownToIR } from "../../packages/markdown-core/src/ir.js"; import { runNodeScript } from "../../test/helpers/run-node-script.js"; -import { resolveRuntimeWorkerArgv, resolveRuntimeWorkerUrl } from "../infra/runtime-worker-url.js"; -import { resolveTestNodeExecPath } from "../test-utils/node-process.js"; +import { resolveRuntimeWorkerUrl } from "../infra/runtime-worker-url.js"; import { EmbeddedBlockChunker } from "./embedded-agent-block-chunker.js"; import { agentProcessTestEntrypoints } from "./process-runtime.test-support.js"; @@ -223,8 +222,8 @@ describe("EmbeddedBlockChunker", () => { // A synchronous stalled drain needs an external deadline, not Vitest's in-process timer. const chunkerUrl = resolveRuntimeWorkerUrl(agentProcessTestEntrypoints.blockChunker); const result = await runNodeScript( - [ - ...resolveRuntimeWorkerArgv(chunkerUrl, resolveTestNodeExecPath()).slice(0, -1), + (workerArgv) => [ + ...workerArgv(chunkerUrl).slice(0, -1), "--input-type=module", "--eval", ` diff --git a/src/agents/prepared-model-catalog-worker.reuse.integration.test.ts b/src/agents/prepared-model-catalog-worker.reuse.integration.test.ts index d8237011a23c..da94d8e544d6 100644 --- a/src/agents/prepared-model-catalog-worker.reuse.integration.test.ts +++ b/src/agents/prepared-model-catalog-worker.reuse.integration.test.ts @@ -55,10 +55,20 @@ it("prepares known provider owners once while keeping catalog execution and auth `provider: ${JSON.stringify(provider)}, filename: __filename`, ); const execution = (provider: string) => - `require("node:v8").queryObjects(WeakRef);` + + `require("node:v8").queryObjects(WeakRef); +const retainedPayloads = globalThis[Symbol.for("catalog.reuse.payloads")]; +const activePayload = retainedPayloads.at(-1); +const currentPayload = activePayload.ref.deref(); +` + record( "heap.jsonl", - `memory: process.memoryUsage(), payloads: globalThis[Symbol.for("catalog.reuse.payloads")].filter(ref => ref.deref()).length`, + `memory: process.memoryUsage(), + payloads: retainedPayloads.filter(({ ref }) => ref.deref()).length, + currentPayload: currentPayload && { + filename: activePayload.filename, + row: currentPayload.rows[99_999].index, + bufferBytes: currentPayload.buffer.byteLength, + }`, ) + record( "executions.jsonl", @@ -77,7 +87,7 @@ it("prepares known provider owners once while keeping catalog execution and auth fs.writeFileSync( baseEntry, `const { payload } = require("./payload.mjs"); -(globalThis[Symbol.for("catalog.reuse.payloads")] ??= []).push(new WeakRef(payload)); +(globalThis[Symbol.for("catalog.reuse.payloads")] ??= []).push({ ref: new WeakRef(payload), filename: __filename }); ` + fs .readFileSync(baseEntry, "utf8") @@ -242,7 +252,7 @@ module.exports = { id: ${JSON.stringify(provider)}, register(api) { ).toEqual(index === 0 ? [PROVIDER_ID, ...providerIds].toSorted() : []); const expanded = registrations(); const footprint = readCatalogCaptureFootprint(captureRoot); - // Retired capture files disappear even though Node retains their native ESM payloads. + // Retired capture files disappear even if native ESM payloads remain cached. expect(footprint.captures).toHaveLength(1); await refreshScope((index + 1) % 2, provider); expect(registrations()).toEqual(expanded); @@ -285,9 +295,28 @@ module.exports = { id: ${JSON.stringify(provider)}, register(api) { .readFileSync(path.join(fixture.root, "heap.jsonl"), "utf8") .trim() .split("\n") - .map((line) => JSON.parse(line) as { memory: NodeJS.MemoryUsage; payloads: number }); + .map( + (line) => + JSON.parse(line) as { + memory: NodeJS.MemoryUsage; + payloads: number; + currentPayload?: { filename: string; row: number; bufferBytes: number }; + }, + ); expect(heap).toHaveLength(providerIds.length * agentIds.length * 2); - expect(heap.map(({ payloads }) => payloads)).toEqual(heap.map(() => 2)); + const currentFilename = warmedRegistrations.findLast( + ({ provider }) => provider === PROVIDER_ID, + )!.filename; + for (const sample of heap) { + // Engines may collect the retired ESM payload; the current capture must remain usable. + expect(sample.payloads).toBeGreaterThanOrEqual(1); + expect(sample.payloads).toBeLessThanOrEqual(2); + expect(sample.currentPayload).toEqual({ + filename: currentFilename, + row: 99_999, + bufferBytes: 1024 * 1024, + }); + } expect(heap.at(-1)!.memory.heapUsed - heap[0]!.memory.heapUsed).toBeLessThan(16 * 1024 * 1024); expect(heap.at(-1)!.memory.arrayBuffers - heap[0]!.memory.arrayBuffers).toBeLessThan(1024 * 1024); console.log("Provider-scope heap", JSON.stringify({ first: heap[0], last: heap.at(-1) })); diff --git a/src/agents/sessions/settings-storage.test.ts b/src/agents/sessions/settings-storage.test.ts index 2b0338b18ef8..716877e30178 100644 --- a/src/agents/sessions/settings-storage.test.ts +++ b/src/agents/sessions/settings-storage.test.ts @@ -193,8 +193,8 @@ describe("FileSettingsStorage", () => { const root = fixtures.createTempDir("openclaw-settings-umask-"); const agentDir = join(root, "agent"); const result = await runNodeScript( - [ - ...resolveRuntimeWorkerArgv(storageUrl, resolveTestNodeExecPath()).slice(0, -1), + (workerArgv) => [ + ...workerArgv(storageUrl).slice(0, -1), "--input-type=module", "--eval", String.raw` diff --git a/src/agents/sessions/tools/read.retention.test.ts b/src/agents/sessions/tools/read.retention.test.ts index 2d01eaf5bda6..cc8887c9eaac 100644 --- a/src/agents/sessions/tools/read.retention.test.ts +++ b/src/agents/sessions/tools/read.retention.test.ts @@ -1,11 +1,7 @@ import { fileURLToPath } from "node:url"; import { beforeAll, expect, it } from "vitest"; import { runNodeScript } from "../../../../test/helpers/run-node-script.js"; -import { - resolveRuntimeWorkerArgv, - resolveRuntimeWorkerUrl, -} from "../../../infra/runtime-worker-url.js"; -import { resolveTestNodeExecPath } from "../../../test-utils/node-process.js"; +import { resolveRuntimeWorkerUrl } from "../../../infra/runtime-worker-url.js"; import { agentProcessTestEntrypoints } from "../../process-runtime.test-support.js"; type Observation = { @@ -19,12 +15,9 @@ let observations: Observation[] = []; beforeAll(async () => { const result = await runNodeScript( - [ + (workerArgv) => [ "--expose-gc", - ...resolveRuntimeWorkerArgv( - resolveRuntimeWorkerUrl(agentProcessTestEntrypoints.readRetention), - resolveTestNodeExecPath(), - ), + ...workerArgv(resolveRuntimeWorkerUrl(agentProcessTestEntrypoints.readRetention)), "all", ], { ...process.env, NODE_OPTIONS: "", TSX_DISABLE_CACHE: "1" }, diff --git a/src/agents/worktrees/service.gc.test.ts b/src/agents/worktrees/service.gc.test.ts index 67aec9eaf48d..91b8ab896d02 100644 --- a/src/agents/worktrees/service.gc.test.ts +++ b/src/agents/worktrees/service.gc.test.ts @@ -8,17 +8,13 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createDeferred } from "../../../test/helpers/promise.js"; import { runNodeScript } from "../../../test/helpers/run-node-script.js"; import * as backoff from "../../infra/backoff.js"; -import { - resolveRuntimeWorkerArgv, - resolveRuntimeWorkerUrl, -} from "../../infra/runtime-worker-url.js"; +import { resolveRuntimeWorkerUrl } from "../../infra/runtime-worker-url.js"; import { createWarnLogCapture } from "../../logging/test-helpers/warn-log-capture.js"; import * as pidAlive from "../../shared/pid-alive.js"; import { closeOpenClawStateDatabaseAsync, closeOpenClawStateDatabaseForTest, } from "../../state/openclaw-state-db.js"; -import { resolveTestNodeExecPath } from "../../test-utils/node-process.js"; import * as worktreeCapacity from "./capacity.js"; import * as worktreeGit from "./git.js"; import { requireGit } from "./git.js"; @@ -338,11 +334,8 @@ describe("ManagedWorktreeService garbage collection", () => { // Gateway cleanup runs on Node's main thread, whose stack limit differs from Vitest workers. const collected = await runNodeScript( - [ - ...resolveRuntimeWorkerArgv( - resolveRuntimeWorkerUrl(managedWorktreeGcEntrypoint), - resolveTestNodeExecPath(), - ), + (workerArgv) => [ + ...workerArgv(resolveRuntimeWorkerUrl(managedWorktreeGcEntrypoint)), String(now), ], env, diff --git a/src/cli/help-exit.process.test.ts b/src/cli/help-exit.process.test.ts index 84e604147f11..a10f49c89fc0 100644 --- a/src/cli/help-exit.process.test.ts +++ b/src/cli/help-exit.process.test.ts @@ -10,7 +10,7 @@ import { resolveVitestNodeArgs } from "../../scripts/lib/vitest-process-env.mts" import { createFixtureLifetime } from "../../test/helpers/fixture-lifetime.js"; import { runNodeScript } from "../../test/helpers/run-node-script.js"; import { useAutoCleanupTempDirTracker } from "../../test/helpers/temp-dir.js"; -import { resolveRuntimeWorkerArgv, resolveRuntimeWorkerUrl } from "../infra/runtime-worker-url.js"; +import { resolveRuntimeWorkerUrl } from "../infra/runtime-worker-url.js"; import { resolveTestNodeExecPath } from "../test-utils/node-process.js"; import { cliMessageExitEntrypoints, @@ -486,9 +486,9 @@ await runCliWithExitFinalization({ const spawned: { child?: ChildProcess } = {}; const child = await lifetime.track( runNodeScript( - [ + (workerArgv) => [ ...resolveVitestNodeArgs(), - ...resolveRuntimeWorkerArgv(helpersUrl, nodeExecutable).slice(0, -1), + ...workerArgv(helpersUrl).slice(0, -1), entryPath, ], { diff --git a/src/cli/update-cli/update-command-candidate-admission-fixture.test-support.ts b/src/cli/update-cli/update-command-candidate-admission-fixture.test-support.ts index 2125b1c35ae4..20f332e641ae 100644 --- a/src/cli/update-cli/update-command-candidate-admission-fixture.test-support.ts +++ b/src/cli/update-cli/update-command-candidate-admission-fixture.test-support.ts @@ -140,13 +140,10 @@ export function createCandidateAdmissionFixtures(f: CandidateAdmissionFixture) { ); const originalSpawn = expectDefined(spawn.getMockImplementation(), "default child transport"); spawn.mockImplementation((command: string, args: string[], options: SpawnOptions) => { - if (args[1] !== "update" || args[2] !== "admit") { + if (args.at(-4) !== "update" || args.at(-3) !== "admit" || args.at(-2) !== "--context") { return originalSpawn(command, args, options); } - const contextPath = expectDefined( - args[args.indexOf("--context") + 1], - "candidate admission context", - ); + const contextPath = expectDefined(args.at(-1), "candidate admission context"); contexts.push(JSON.parse(fsSync.readFileSync(contextPath, "utf8")) as UpdateAdmissionContext); events.push("admission"); databaseExistsAtAdmission.push( diff --git a/src/commands/daemon-install-plan.shared.test.ts b/src/commands/daemon-install-plan.shared.test.ts index a876c085539f..f632c390afb1 100644 --- a/src/commands/daemon-install-plan.shared.test.ts +++ b/src/commands/daemon-install-plan.shared.test.ts @@ -3,6 +3,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { describe, expect, it } from "vitest"; +import { resolveTestNodeExecPath } from "../test-utils/node-process.js"; import { resolveDaemonInstallRuntimeInputs, resolveDaemonServicePathDirs, @@ -15,7 +16,7 @@ describe("resolveDaemonInstallRuntimeInputs", () => { const root = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), "daemon-pin-"))); const pinned = path.join(root, "node"); try { - fs.symlinkSync(process.execPath, pinned); + fs.symlinkSync(resolveTestNodeExecPath(), pinned); await expect( resolveDaemonInstallRuntimeInputs({ env: {}, diff --git a/src/cron/session-lifecycle.worker.test-support.ts b/src/cron/session-lifecycle.worker.test-support.ts index d29be1dd45e9..8b1eccf5d37e 100644 --- a/src/cron/session-lifecycle.worker.test-support.ts +++ b/src/cron/session-lifecycle.worker.test-support.ts @@ -32,13 +32,22 @@ const postGrantPreload = ` const fixture = workerData.cronMutationProbe; let gate; let held = false; - parentPort.on("message", (request) => { + const observeRequest = (request) => { if (request.type === "reclaim" && ["entry", "lifecycle-projection-commit"].includes(request.plan.kind) && request.plan.descendantRunBasis?.sessionKeys.includes(fixture.sessionKey)) { gate = request.commitGate; } - }); + }; + const on = parentPort.on; + parentPort.on = function(event, listener) { + if (event === "message") { + // An eager preload listener consumes queued requests before the real worker imports finish. + parentPort.on = on; + on.call(this, event, observeRequest); + } + return on.call(this, event, listener); + }; const load = Atomics.load; Atomics.load = function(view, index) { const observed = load(view, index); diff --git a/src/gateway/session-utils.child-cache-retention.test.ts b/src/gateway/session-utils.child-cache-retention.test.ts index d678939325c3..342cef688516 100644 --- a/src/gateway/session-utils.child-cache-retention.test.ts +++ b/src/gateway/session-utils.child-cache-retention.test.ts @@ -1,14 +1,14 @@ import { fileURLToPath } from "node:url"; import { expect, it } from "vitest"; import { runNodeScript } from "../../test/helpers/run-node-script.js"; -import { resolveRuntimeWorkerArgv, resolveRuntimeWorkerUrl } from "../infra/runtime-worker-url.js"; +import { resolveRuntimeWorkerUrl } from "../infra/runtime-worker-url.js"; import { sessionChildCacheRetentionEntrypoint } from "./session-child-cache-retention-entrypoint.test-support.js"; it("keeps child links without retaining released session metadata", async ({ signal }) => { const result = await runNodeScript( - [ + (workerArgv) => [ "--expose-gc", - ...resolveRuntimeWorkerArgv(resolveRuntimeWorkerUrl(sessionChildCacheRetentionEntrypoint)), + ...workerArgv(resolveRuntimeWorkerUrl(sessionChildCacheRetentionEntrypoint)), ], { ...process.env, NODE_OPTIONS: "", TSX_DISABLE_CACHE: "1" }, 15_000, diff --git a/src/infra/gateway-state-owner.test.ts b/src/infra/gateway-state-owner.test.ts index 1bdcba5f221c..2eacc987ffe7 100644 --- a/src/infra/gateway-state-owner.test.ts +++ b/src/infra/gateway-state-owner.test.ts @@ -23,7 +23,7 @@ import { withStateDatabaseColdAdmission, } from "./gateway-state-owner.js"; import * as nodeSqlite from "./node-sqlite.js"; -import { resolveRuntimeWorkerArgv, resolveRuntimeWorkerUrl } from "./runtime-worker-url.js"; +import { resolveRuntimeWorkerUrl } from "./runtime-worker-url.js"; describe("Gateway state ownership", () => { it.skipIf(process.platform === "win32").each(["state", "explicit"] as const)( @@ -492,8 +492,8 @@ describe("Gateway state ownership", () => { ); const stateUrl = resolveRuntimeWorkerUrl(stateNativeProcessEntrypoints.stateDatabase); const child = await runNodeScript( - [ - ...resolveRuntimeWorkerArgv(stateUrl).slice(0, -1), + (workerArgv) => [ + ...workerArgv(stateUrl).slice(0, -1), "--input-type=module", "--eval", `import { runOpenClawStateWriteTransaction, closeOpenClawStateDatabaseForTest } from ${JSON.stringify(stateUrl.href)}; diff --git a/src/infra/sqlite-readonly-worker.compile-cache.process.test.ts b/src/infra/sqlite-readonly-worker.compile-cache.process.test.ts index 32f239484b51..9fd01ae42d6c 100644 --- a/src/infra/sqlite-readonly-worker.compile-cache.process.test.ts +++ b/src/infra/sqlite-readonly-worker.compile-cache.process.test.ts @@ -1,8 +1,7 @@ import { afterEach, expect, it } from "vitest"; import { createFixtureLifetime } from "../../test/helpers/fixture-lifetime.js"; import { runNodeScript } from "../../test/helpers/run-node-script.js"; -import { resolveTestNodeExecPath } from "../test-utils/node-process.js"; -import { resolveRuntimeWorkerArgv, resolveRuntimeWorkerUrl } from "./runtime-worker-url.js"; +import { resolveRuntimeWorkerUrl } from "./runtime-worker-url.js"; import { sqliteReadOnlyCompileCacheParentEntrypoint } from "./sqlite-readonly-worker.compile-cache-runtime.test-support.js"; const fixture = createFixtureLifetime(); @@ -22,11 +21,8 @@ it.for(["sync", "async", "scoped"] as const)( delete env.NODE_OPTIONS; const result = await fixture.track( runNodeScript( - [ - ...resolveRuntimeWorkerArgv( - resolveRuntimeWorkerUrl(sqliteReadOnlyCompileCacheParentEntrypoint), - resolveTestNodeExecPath(), - ), + (workerArgv) => [ + ...workerArgv(resolveRuntimeWorkerUrl(sqliteReadOnlyCompileCacheParentEntrypoint)), root, mode, ], diff --git a/src/infra/sqlite-worker-store.compile-cache.test.ts b/src/infra/sqlite-worker-store.compile-cache.test.ts index d78be3e0da06..7b41a0fd4b64 100644 --- a/src/infra/sqlite-worker-store.compile-cache.test.ts +++ b/src/infra/sqlite-worker-store.compile-cache.test.ts @@ -3,8 +3,7 @@ import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { runNodeScript } from "../../test/helpers/run-node-script.js"; import { useAutoCleanupTempDirTracker } from "../../test/helpers/temp-dir.js"; -import { resolveTestNodeExecPath } from "../test-utils/node-process.js"; -import { resolveRuntimeWorkerArgv, resolveRuntimeWorkerUrl } from "./runtime-worker-url.js"; +import { resolveRuntimeWorkerUrl } from "./runtime-worker-url.js"; import { sqliteWorkerStoreCompileCacheParentEntrypoint } from "./sqlite-worker-store.compile-cache-runtime.test-support.js"; const tempDirs = useAutoCleanupTempDirTracker(afterEach); @@ -58,11 +57,8 @@ describe("SQLite store worker compile cache", () => { delete env.NODE_DISABLE_COMPILE_CACHE; delete env.NODE_OPTIONS; const result = await runNodeScript( - [ - ...resolveRuntimeWorkerArgv( - resolveRuntimeWorkerUrl(sqliteWorkerStoreCompileCacheParentEntrypoint), - resolveTestNodeExecPath(), - ), + (workerArgv) => [ + ...workerArgv(resolveRuntimeWorkerUrl(sqliteWorkerStoreCompileCacheParentEntrypoint)), root, testCase.owner, testCase.cache ?? "unset", diff --git a/src/infra/update-managed-service-handoff-cross-process.test-support.ts b/src/infra/update-managed-service-handoff-cross-process.test-support.ts index 3dac42cc6fbb..fba1494b9f99 100644 --- a/src/infra/update-managed-service-handoff-cross-process.test-support.ts +++ b/src/infra/update-managed-service-handoff-cross-process.test-support.ts @@ -57,8 +57,9 @@ export function scopeWrapperSource(helperExitPath: string): string { return `#!${process.execPath} const fs = require("node:fs"); const { spawn } = require("node:child_process"); -const [command, scriptPath, paramsPath] = process.argv.slice(-3); -const helper = spawn(command, [scriptPath, paramsPath], { stdio: ["pipe", "pipe", "ignore"] }); +const args = process.argv.slice(2); +const [command, ...helperArgs] = args.slice(args.findIndex((arg) => !arg.startsWith("-"))); +const helper = spawn(command, helperArgs, { stdio: ["pipe", "pipe", "ignore"] }); let helperAlive = true; helper.stdin.on("error", () => {}); helper.stdout.pipe(process.stdout, { end: false }); diff --git a/src/logging/diagnostic-cpu-profile.test.ts b/src/logging/diagnostic-cpu-profile.test.ts index 425618c0a502..d53e5f2f7bb5 100644 --- a/src/logging/diagnostic-cpu-profile.test.ts +++ b/src/logging/diagnostic-cpu-profile.test.ts @@ -2,8 +2,7 @@ import type { Profiler } from "node:inspector"; import { fileURLToPath } from "node:url"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { runNodeScript } from "../../test/helpers/run-node-script.js"; -import { resolveRuntimeWorkerArgv, resolveRuntimeWorkerUrl } from "../infra/runtime-worker-url.js"; -import { resolveTestNodeExecPath } from "../test-utils/node-process.js"; +import { resolveRuntimeWorkerUrl } from "../infra/runtime-worker-url.js"; import { diagnosticProfileEntrypoints } from "./diagnostic-profile-runtime.test-support.js"; const native = vi.hoisted(() => ({ @@ -518,8 +517,8 @@ assert.equal(process.pid, pid); console.log(JSON.stringify({ node: process.version, platform: process.platform, arch: process.arch, actualDurationMs: result.actualDurationMs, samples: result.profile.samples.length, nodes: result.profile.nodes.length, listener: false })); `; const result = await runNodeScript( - [ - ...resolveRuntimeWorkerArgv(ownerUrl, resolveTestNodeExecPath()).slice(0, -1), + (workerArgv) => [ + ...workerArgv(ownerUrl).slice(0, -1), "--input-type=module", "--eval", source, diff --git a/src/logging/diagnostic-heap-profile.test.ts b/src/logging/diagnostic-heap-profile.test.ts index fcc6f16a0645..bb76ccddf7bb 100644 --- a/src/logging/diagnostic-heap-profile.test.ts +++ b/src/logging/diagnostic-heap-profile.test.ts @@ -3,8 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { DiagnosticsHeapProfileParams } from "../../packages/gateway-protocol/src/schema/diagnostics.js"; import { createDeferred } from "../../test/helpers/promise.js"; import { runNodeScript } from "../../test/helpers/run-node-script.js"; -import { resolveRuntimeWorkerArgv, resolveRuntimeWorkerUrl } from "../infra/runtime-worker-url.js"; -import { resolveTestNodeExecPath } from "../test-utils/node-process.js"; +import { resolveRuntimeWorkerUrl } from "../infra/runtime-worker-url.js"; import { diagnosticProfileEntrypoints } from "./diagnostic-profile-runtime.test-support.js"; const hostBunVersion = Object.getOwnPropertyDescriptor(process.versions, "bun"); @@ -378,8 +377,8 @@ for (const includeCollected of [false, true]) { assert.equal(url(), undefined); `; const result = await runNodeScript( - [ - ...resolveRuntimeWorkerArgv(ownerUrl, resolveTestNodeExecPath()).slice(0, -1), + (workerArgv) => [ + ...workerArgv(ownerUrl).slice(0, -1), "--expose-gc", "--input-type=module", "--eval", diff --git a/src/plugins/host-hook-cleanup.session-store.test.ts b/src/plugins/host-hook-cleanup.session-store.test.ts index 289354b3289e..2747118bc84e 100644 --- a/src/plugins/host-hook-cleanup.session-store.test.ts +++ b/src/plugins/host-hook-cleanup.session-store.test.ts @@ -17,7 +17,7 @@ import { completeAgentDeletionJournalInDatabase, } from "../state/agent-deletion-journal.js"; import { - closeOpenClawAgentDatabasesForTest, + closeOpenClawAgentDatabasesAsync, openOpenClawAgentDatabase, } from "../state/openclaw-agent-db.js"; import { SQLITE_SESSION_WRITER_QUEUES } from "../state/openclaw-agent-write-admission.js"; @@ -62,7 +62,7 @@ describe("plugin host cleanup session stores", () => { }, ); } - closeOpenClawAgentDatabasesForTest(); + await closeOpenClawAgentDatabasesAsync(); const retainedDatabase = path.join( fixtureStateDir, "agents", @@ -224,7 +224,7 @@ describe("plugin host cleanup session stores", () => { }, ); } - closeOpenClawAgentDatabasesForTest(); + await closeOpenClawAgentDatabasesAsync(); const retiredBefore = loadSessionEntry({ agentId: "retired", storePath: sharedStore, @@ -288,7 +288,7 @@ describe("plugin host cleanup session stores", () => { pluginExtensions: { fixture: { active: true } }, }, ); - closeOpenClawAgentDatabasesForTest(); + await closeOpenClawAgentDatabasesAsync(); const databasePath = path.join( fixtureStateDir, "agents", @@ -532,7 +532,7 @@ describe("plugin host cleanup session stores", () => { }); expect(result).toEqual({ cleanupCount: 1, failures: [] }); - closeOpenClawAgentDatabasesForTest(); + await closeOpenClawAgentDatabasesAsync(); const target = loadSessionEntry({ sessionKey: targetKey, storePath }); expect(target?.pluginExtensions).toEqual({ other: { state: { preserved: true } } }); expect(target?.pluginNextTurnInjections).toBeUndefined(); diff --git a/src/plugins/memory-runtime.owners.test.ts b/src/plugins/memory-runtime.owners.test.ts index 191181175577..c5228069f6bb 100644 --- a/src/plugins/memory-runtime.owners.test.ts +++ b/src/plugins/memory-runtime.owners.test.ts @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { expect, it, vi } from "vitest"; +import { afterEach, beforeEach, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { createDeferredCore } from "../shared/deferred.js"; import { createOpenClawTestState } from "../test-utils/openclaw-test-state.js"; @@ -15,12 +15,30 @@ import { createTestPluginRegistry } from "./registry-runtime.test-helpers.js"; import { disposePluginRegistryInstances } from "./runtime.js"; import { getPluginRuntimeGatewayRequestScope } from "./runtime/gateway-request-scope.js"; +const cleanupClock = vi.hoisted(() => ({ setTimeout: globalThis.setTimeout })); + +beforeEach(() => { + const nativeSetTimeout = globalThis.setTimeout; + vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"], shouldClearNativeTimers: true }); + cleanupClock.setTimeout = globalThis.setTimeout; + globalThis.setTimeout = nativeSetTimeout; +}); +afterEach(() => vi.useRealTimers()); + vi.mock("./host-hook-cleanup-timeout.js", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, - withPluginHostCleanupTimeout: (hookId: string, cleanup: () => T | Promise) => - actual.withPluginHostCleanupTimeout(hookId, cleanup, 500), + withPluginHostCleanupTimeout: (hookId: string, cleanup: () => T | Promise) => { + const nativeSetTimeout = globalThis.setTimeout; + // Only the cleanup deadline is virtual; asynchronous workers and watchers keep real timers. + globalThis.setTimeout = cleanupClock.setTimeout; + try { + return actual.withPluginHostCleanupTimeout(hookId, cleanup, 500); + } finally { + globalThis.setTimeout = nativeSetTimeout; + } + }, }; }); @@ -144,6 +162,7 @@ it.each([ const entered = createDeferredCore(); const releaseCreate = createDeferredCore(); const releaseClose = createDeferredCore(); + const closeEntered = createDeferredCore(); const probeEntered = createDeferredCore(); const releaseProbe = createDeferredCore(); let rejectClose = mode === "failed-close"; @@ -173,6 +192,7 @@ it.each([ }; }); const close = vi.fn(async () => { + closeEntered.resolve(); expect(getPluginRuntimeGatewayRequestScope()?.pluginId).toBe(targetId); if (rejectClose) { throw new Error("synthetic provider cleanup refused"); @@ -366,7 +386,10 @@ it.each([ activatePluginRegistry(next, null, "gateway-bindable", undefined, owner.registry); reload.commit(); } else if (mode === "timeout") { - await expect(drain).rejects.toThrow("plugin host cleanup timed out"); + const expired = expect(drain).rejects.toThrow("plugin host cleanup timed out"); + await closeEntered.promise; + await vi.advanceTimersByTimeAsync(500); + await expired; activatePluginRegistry(next, null, "gateway-bindable", undefined, owner.registry); reload.commit(); const fresh = await owner.runtime.getMemorySearchManager({ cfg: config, agentId: "main" }); @@ -383,7 +406,9 @@ it.each([ await finalClose; expect(close).toHaveBeenCalledOnce(); } else if (mode === "late-probe") { - await expect(drain).rejects.toThrow("plugin host cleanup timed out"); + const expired = expect(drain).rejects.toThrow("plugin host cleanup timed out"); + await vi.advanceTimersByTimeAsync(500); + await expired; activatePluginRegistry(next, null, "gateway-bindable", undefined, owner.registry); reload.commit(); const fresh = await owner.runtime.getMemorySearchManager({ cfg: config, agentId: "main" }); diff --git a/src/process/exec-output.retention.test.ts b/src/process/exec-output.retention.test.ts index ef04df10229a..4da55a604cda 100644 --- a/src/process/exec-output.retention.test.ts +++ b/src/process/exec-output.retention.test.ts @@ -1,20 +1,16 @@ import { fileURLToPath } from "node:url"; import { expect, it } from "vitest"; import { runNodeScript } from "../../test/helpers/run-node-script.js"; -import { resolveRuntimeWorkerArgv, resolveRuntimeWorkerUrl } from "../infra/runtime-worker-url.js"; -import { resolveTestNodeExecPath } from "../test-utils/node-process.js"; +import { resolveRuntimeWorkerUrl } from "../infra/runtime-worker-url.js"; import { execOutputRetentionEntrypoint } from "./retention-runtime.test-support.js"; it("releases discarded backing buffers while capped commands are still running", async ({ signal, }) => { const result = await runNodeScript( - [ + (workerArgv) => [ "--expose-gc", - ...resolveRuntimeWorkerArgv( - resolveRuntimeWorkerUrl(execOutputRetentionEntrypoint), - resolveTestNodeExecPath(), - ), + ...workerArgv(resolveRuntimeWorkerUrl(execOutputRetentionEntrypoint)), ], { ...process.env, NODE_OPTIONS: "", TSX_DISABLE_CACHE: "1" }, 15_000, diff --git a/src/state/claw-package-adoption.test.ts b/src/state/claw-package-adoption.test.ts index f47c97c37b55..84629824204f 100644 --- a/src/state/claw-package-adoption.test.ts +++ b/src/state/claw-package-adoption.test.ts @@ -10,8 +10,8 @@ import { readClawPackageRefs, } from "../claws/provenance.js"; import type { ClawAddPlan } from "../claws/types.js"; -import { resolveRuntimeWorkerArgv, resolveRuntimeWorkerUrl } from "../infra/runtime-worker-url.js"; -import { createNodeEvalArgs, resolveTestNodeExecPath } from "../test-utils/node-process.js"; +import { resolveRuntimeWorkerUrl } from "../infra/runtime-worker-url.js"; +import { createNodeEvalArgs } from "../test-utils/node-process.js"; import { markClawPackageIndependentlyOwned } from "./claw-package-adoption.js"; import { acquireClawPackageLifecycleLease } from "./claw-package-lifecycle-lease.js"; import { stateNativeProcessEntrypoints } from "./native-process-runtime.test-support.js"; @@ -286,8 +286,8 @@ describe("Claw package independent adoption", () => { stateNativeProcessEntrypoints.clawPackageLifecycleLease, ); const result = await runNodeScript( - [ - ...resolveRuntimeWorkerArgv(moduleUrl, resolveTestNodeExecPath()).slice(0, -1), + (workerArgv) => [ + ...workerArgv(moduleUrl).slice(0, -1), ...createNodeEvalArgs( ` import { withClawPackageLifecycleLease } from ${JSON.stringify(moduleUrl.href)}; diff --git a/src/state/openclaw-database-verify.test.ts b/src/state/openclaw-database-verify.test.ts index 0b1b461f29ff..73ecb01ea7ec 100644 --- a/src/state/openclaw-database-verify.test.ts +++ b/src/state/openclaw-database-verify.test.ts @@ -5,7 +5,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { runNodeScript } from "../../test/helpers/run-node-script.js"; import { useAutoCleanupTempDirTracker } from "../../test/helpers/temp-dir.js"; import { requireNodeSqlite } from "../infra/node-sqlite.js"; -import { resolveRuntimeWorkerArgv, resolveRuntimeWorkerUrl } from "../infra/runtime-worker-url.js"; +import { resolveRuntimeWorkerUrl } from "../infra/runtime-worker-url.js"; import { readStableSqliteFileGeneration } from "../infra/sqlite-file-generation.js"; import { readMainDatabasePosixLocks } from "../infra/sqlite-posix-locks.test-support.js"; import { readSqliteNumberPragma } from "../infra/sqlite-pragma.test-support.js"; @@ -234,7 +234,7 @@ describe("OpenClaw database integrity verifier", () => { it("relays a late restart-receipt Worker open to the parent verifier after native opening settles", async () => { const result = await runNodeScript( - resolveRuntimeWorkerArgv(resolveRuntimeWorkerUrl(databaseVerifyHostRuntimeEntrypoint)), + (workerArgv) => workerArgv(resolveRuntimeWorkerUrl(databaseVerifyHostRuntimeEntrypoint)), process.env, 30_000, { requireProcessTreeExit: true }, diff --git a/src/transcripts/library.test.ts b/src/transcripts/library.test.ts index 6cd47e124e78..8f1d5cfa2c32 100644 --- a/src/transcripts/library.test.ts +++ b/src/transcripts/library.test.ts @@ -1,4 +1,3 @@ -import { spawnSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; import { afterEach, describe, expect, it, vi } from "vitest"; @@ -7,14 +6,14 @@ import { TRANSCRIPTS_LEGACY_RESULT_MAX_BYTES, TRANSCRIPTS_RESULT_MAX_BYTES, } from "../../packages/gateway-protocol/src/schema/transcripts.js"; +import { runNodeScript } from "../../test/helpers/run-node-script.js"; import { useAutoCleanupTempDirTracker } from "../../test/helpers/temp-dir.js"; import { executeSqliteQuerySync } from "../infra/kysely-sync.js"; -import { resolveRuntimeWorkerArgv, resolveRuntimeWorkerUrl } from "../infra/runtime-worker-url.js"; +import { resolveRuntimeWorkerUrl } from "../infra/runtime-worker-url.js"; import { closeOpenClawStateDatabaseAsync, closeOpenClawStateDatabaseForTest, } from "../state/openclaw-state-db.js"; -import { resolveTestNodeExecPath } from "../test-utils/node-process.js"; import { activeSessions } from "./capture-startup.js"; import { transcriptLibraryTimezoneEntrypoint } from "./library-timezone-runtime.test-support.js"; import { exportTranscriptLibrary, getTranscriptLibrary, listTranscriptLibrary } from "./library.js"; @@ -146,24 +145,21 @@ describe("transcript library SQLite reads", () => { it( "uses the process timezone for unzoned stored dates and range bounds", { timeout: 45_000 }, - () => { + async ({ signal }) => { const stateDir = tempDirs.make("transcript-library-timezone-"); - const child = spawnSync( - resolveTestNodeExecPath(), - [ - ...resolveRuntimeWorkerArgv(resolveRuntimeWorkerUrl(transcriptLibraryTimezoneEntrypoint)), + const child = await runNodeScript( + (workerArgv) => [ + ...workerArgv(resolveRuntimeWorkerUrl(transcriptLibraryTimezoneEntrypoint)), stateDir, ], { - encoding: "utf8", - timeout: 30_000, - env: { - ...process.env, - TZ: "America/Los_Angeles", - OPENCLAW_STATE_DIR: stateDir, - OPENCLAW_CONFIG_PATH: path.join(stateDir, "openclaw.json"), - }, + ...process.env, + TZ: "America/Los_Angeles", + OPENCLAW_STATE_DIR: stateDir, + OPENCLAW_CONFIG_PATH: path.join(stateDir, "openclaw.json"), }, + 30_000, + { signal, maxBuffer: 1024 * 1024, requireProcessTreeExit: process.platform !== "win32" }, ); expect(child.error, child.stderr).toBeUndefined(); expect(child.status, child.stderr).toBe(0); diff --git a/src/worker/worker.runtime-imports.test.ts b/src/worker/worker.runtime-imports.test.ts index 29e53e7134af..1245e4144ba0 100644 --- a/src/worker/worker.runtime-imports.test.ts +++ b/src/worker/worker.runtime-imports.test.ts @@ -4,8 +4,7 @@ import { fileURLToPath } from "node:url"; import { afterEach, describe, expect, it } from "vitest"; import { runNodeScript } from "../../test/helpers/run-node-script.js"; import { useAutoCleanupTempDirTracker } from "../../test/helpers/temp-dir.js"; -import { resolveRuntimeWorkerArgv, resolveRuntimeWorkerUrl } from "../infra/runtime-worker-url.js"; -import { resolveTestNodeExecPath } from "../test-utils/node-process.js"; +import { resolveRuntimeWorkerUrl } from "../infra/runtime-worker-url.js"; import { workerImportRuntimeEntrypoints } from "./worker-import-runtime.test-support.js"; const tempDirs = useAutoCleanupTempDirTracker(afterEach); @@ -23,9 +22,9 @@ describe("worker runtime imports during admission", () => { const workspace = path.join(root, "workspace"); await mkdir(workspace); const result = await runNodeScript( - [ + (workerArgv) => [ "--unhandled-rejections=strict", - ...resolveRuntimeWorkerArgv(runtimeUrl, resolveTestNodeExecPath()).slice(0, -1), + ...workerArgv(runtimeUrl).slice(0, -1), fileURLToPath(new URL("./worker.runtime-imports.test-support.mjs", import.meta.url)), mode, workspace, diff --git a/test/helpers/run-node-script.test.ts b/test/helpers/run-node-script.test.ts index 87e53171a40a..bc6b3cd56cc4 100644 --- a/test/helpers/run-node-script.test.ts +++ b/test/helpers/run-node-script.test.ts @@ -1,6 +1,7 @@ import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { setTimeout as delay } from "node:timers/promises"; +import { pathToFileURL } from "node:url"; import { afterEach, expect, it, vi } from "vitest"; import { createVitestResourceOwner } from "../../scripts/lib/vitest-resource-ownership.mts"; import { createFixtureLifetime } from "./fixture-lifetime.js"; @@ -64,6 +65,32 @@ child.once("message", () => process.exit(17)); }); }); +it.for(["node", "current"] as const)( + "builds source worker arguments for the selected %s runtime", + async (runtime) => { + const script = join(tempDirs.make("openclaw-node-script-runtime-"), "worker.ts"); + writeFileSync( + script, + `enum Answer { value = 42 } +console.log(JSON.stringify({ answer: Answer.value, bun: Boolean(process.versions.bun), args: process.argv.slice(2) })); +`, + ); + const result = await runNodeScript( + (workerArgv) => [...workerArgv(pathToFileURL(script)), "worker-argument"], + process.env, + 5_000, + { executable: runtime === "current" ? process.execPath : undefined }, + ); + expect(result.error, result.stderr).toBeUndefined(); + expect(result.status, result.stderr).toBe(0); + expect(JSON.parse(result.stdout)).toEqual({ + answer: 42, + bun: runtime === "current" && Boolean(process.versions.bun), + args: ["worker-argument"], + }); + }, +); + it.for(["at limit", "stdout overflow", "stderr overflow"])( "preserves independent 2 MiB output failure boundaries: %s", async (mode) => { diff --git a/test/helpers/run-node-script.ts b/test/helpers/run-node-script.ts index 0a92e7fe5536..c0b602a3d276 100644 --- a/test/helpers/run-node-script.ts +++ b/test/helpers/run-node-script.ts @@ -1,10 +1,12 @@ import type { ChildProcess } from "node:child_process"; import { runManagedCommand } from "../../scripts/lib/managed-child-process.mts"; +import { resolveRuntimeWorkerArgv } from "../../src/infra/runtime-worker-url.js"; import { resolveTestNodeExecPath } from "../../src/test-utils/node-process.js"; import { createBoundedChildOutput } from "./bounded-child-output.js"; +/** Worker argv is resolved against the executable selected for this child. */ export async function runNodeScript( - scriptPathOrArgs: string | string[], + scriptPathOrArgs: string | string[] | ((workerArgv: (url: URL) => string[]) => string[]), env: NodeJS.ProcessEnv, timeoutMs: number | undefined, { @@ -31,9 +33,16 @@ export async function runNodeScript( let status: number | null = null; let error: unknown; try { + const execPath = executable ?? resolveTestNodeExecPath(); + const args = + typeof scriptPathOrArgs === "function" + ? scriptPathOrArgs((url) => resolveRuntimeWorkerArgv(url, execPath)) + : typeof scriptPathOrArgs === "string" + ? [scriptPathOrArgs] + : scriptPathOrArgs; status = await runManagedCommand({ - bin: executable ?? resolveTestNodeExecPath(), - args: typeof scriptPathOrArgs === "string" ? [scriptPathOrArgs] : scriptPathOrArgs, + bin: execPath, + args, cwd, env, timeoutMs, diff --git a/test/scripts/agent-plugin-gateway-e2e.test.ts b/test/scripts/agent-plugin-gateway-e2e.test.ts index 500f2b5a2ad6..013f9ef4b116 100644 --- a/test/scripts/agent-plugin-gateway-e2e.test.ts +++ b/test/scripts/agent-plugin-gateway-e2e.test.ts @@ -3,11 +3,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import path from "node:path"; import { afterEach, expect, it } from "vitest"; import { scriptProcessEntrypoints } from "../../scripts/script-process-runtime.test-support.js"; -import { - resolveRuntimeWorkerArgv, - resolveRuntimeWorkerUrl, -} from "../../src/infra/runtime-worker-url.js"; -import { resolveTestNodeExecPath } from "../../src/test-utils/node-process.js"; +import { resolveRuntimeWorkerUrl } from "../../src/infra/runtime-worker-url.js"; import { createFixtureLifetime } from "../helpers/fixture-lifetime.js"; import { createDeferred } from "../helpers/promise.js"; import { runNodeScript } from "../helpers/run-node-script.js"; @@ -40,15 +36,17 @@ it.skipIf(process.platform === "win32").for(cases)( const ready = createDeferred(); const receivedSignal = createDeferred(); let child: ChildProcess | undefined; - const scriptArgs = resolveRuntimeWorkerArgv(scriptUrl, resolveTestNodeExecPath()); const command = fixtures.track( runNodeScript( - [ - ...scriptArgs.slice(0, -1), - "--import", - new URL("./fixtures/agent-plugin-gateway-cancellation.mjs", import.meta.url).href, - ...scriptArgs.slice(-1), - ], + (workerArgv) => { + const scriptArgs = workerArgv(scriptUrl); + return [ + ...scriptArgs.slice(0, -1), + "--import", + new URL("./fixtures/agent-plugin-gateway-cancellation.mjs", import.meta.url).href, + ...scriptArgs.slice(-1), + ]; + }, { PATH: process.env.PATH, HOME: home, diff --git a/test/scripts/check-openclaw-package-tarball.test.ts b/test/scripts/check-openclaw-package-tarball.test.ts index 22eac6583461..ed7ac9239e65 100644 --- a/test/scripts/check-openclaw-package-tarball.test.ts +++ b/test/scripts/check-openclaw-package-tarball.test.ts @@ -13,6 +13,7 @@ import { PACKAGE_LIFECYCLE_PENDING_RELATIVE_PATH, } from "../../scripts/lib/package-lifecycle-marker.mjs"; import { WORKSPACE_TEMPLATE_PACK_PATHS } from "../../scripts/lib/workspace-bootstrap-smoke.mts"; +import { resolveTestNodeExecPath } from "../../src/test-utils/node-process.js"; import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js"; import { CODE_MODE_WORKER_PATH, @@ -220,7 +221,7 @@ childProcess.spawnSync = function (...callArgs) { syncBuiltinESMExports(); `, ); - const result = spawnSync(process.execPath, [resolve(CHECK_SCRIPT), tarball], { + const result = spawnSync(resolveTestNodeExecPath(), [resolve(CHECK_SCRIPT), tarball], { encoding: "utf8", env: { ...process.env, diff --git a/test/scripts/compiled-tooling.process.test.ts b/test/scripts/compiled-tooling.process.test.ts index d12c17c78ba4..c8359f673dda 100644 --- a/test/scripts/compiled-tooling.process.test.ts +++ b/test/scripts/compiled-tooling.process.test.ts @@ -1,10 +1,6 @@ import { afterEach, expect, it } from "vitest"; import { scriptModuleEntrypoints } from "../../scripts/script-module-runtime.test-support.mjs"; -import { - resolveRuntimeWorkerArgv, - resolveRuntimeWorkerUrl, -} from "../../src/infra/runtime-worker-url.js"; -import { resolveTestNodeExecPath } from "../../src/test-utils/node-process.js"; +import { resolveRuntimeWorkerUrl } from "../../src/infra/runtime-worker-url.js"; import { runNodeScript } from "../helpers/run-node-script.js"; import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js"; @@ -15,11 +11,8 @@ it("loads installed tooling when the prepared CLI validates native Vitest option }) => { const directory = tempDirs.make("openclaw-compiled-tooling-"); const result = await runNodeScript( - [ - ...resolveRuntimeWorkerArgv( - resolveRuntimeWorkerUrl(scriptModuleEntrypoints.testProjects), - resolveTestNodeExecPath(), - ), + (workerArgv) => [ + ...workerArgv(resolveRuntimeWorkerUrl(scriptModuleEntrypoints.testProjects)), "test/scripts/test-projects.test.ts", "--", "--invalid-native-vitest-option", diff --git a/test/scripts/vitest-worker-artifacts.test-support.ts b/test/scripts/vitest-worker-artifacts.test-support.ts index 245efabd34f8..de8cc3e1e464 100644 --- a/test/scripts/vitest-worker-artifacts.test-support.ts +++ b/test/scripts/vitest-worker-artifacts.test-support.ts @@ -67,7 +67,7 @@ function createWorkerArtifactFixtures( return joined; } - function node(args: string[], cwd = root, env = process.env) { + function node(args: Parameters[0], cwd = root, env = process.env) { const completion = fixtureLifetime.track( runNodeScript(args, compilerEnv(env, "node"), undefined, { cwd, @@ -401,7 +401,9 @@ export function workerProbe( const sourceLoader = sourceMode && !process.versions.bun; expect(args.includes('--import')).toBe(sourceLoader); if (sourceLoader) expect(args[1].startsWith('file:')).toBe(true); - expect(args[sourceLoader ? 2 : 0]).toMatch(sourceMode ? /\\.ts$/ : /\\.js$/); + const runtimeArgs = sourceLoader ? ['--import', expect.stringMatching(/^file:/)] : process.versions.bun ? ['--no-install'] : []; + expect(args.slice(0, runtimeArgs.length + 1)).toEqual([...runtimeArgs, fileURLToPath(generation)]); + expect(args[runtimeArgs.length]).toMatch(sourceMode ? /\\.ts$/ : /\\.js$/); fs.appendFileSync(${JSON.stringify(path.join(directory, "observations.jsonl"))}, JSON.stringify({args, tuiUrls, setupUrls, retentionUrl, value, configValue:inject('configValue'), knn:resolveRuntimeWorkerUrl(vectorKnnProcessEntrypoint).href})+'\\n'); fs.appendFileSync(${JSON.stringify(path.join(directory, "generations.jsonl"))}, JSON.stringify(generation)+'\\n'); ${receiptEndpoint ? `sendReceipt(${JSON.stringify(path.join(directory, "generations.jsonl"))}, 'written');` : ""} diff --git a/test/scripts/vitest-worker-artifacts.test.ts b/test/scripts/vitest-worker-artifacts.test.ts index 94d3ab051b72..f339ce8c02a7 100644 --- a/test/scripts/vitest-worker-artifacts.test.ts +++ b/test/scripts/vitest-worker-artifacts.test.ts @@ -15,8 +15,6 @@ import { } from "../../scripts/lib/vitest-worker-artifacts.mts"; import { resolveVitestSpawnParams, spawnWatchedVitestProcess } from "../../scripts/run-vitest.mts"; import { createVitestProcessCompletion } from "../../scripts/vitest-process-group.mts"; -import { resolveRuntimeWorkerArgv } from "../../src/infra/runtime-worker-url.js"; -import { resolveTestNodeExecPath } from "../../src/test-utils/node-process.js"; import { fixtureReceiptClientSource, openFixtureReceiptChannel, @@ -751,8 +749,8 @@ describe.concurrent("fresh compiled subprocess invocation", () => { : path.join(root, "src/agents/embedded-agent-runner/run/payloads.ts"), ); const result = await node( - [ - ...resolveRuntimeWorkerArgv(pathToFileURL(probe), resolveTestNodeExecPath()), + (workerArgv) => [ + ...workerArgv(pathToFileURL(probe)), url.href, pathToFileURL( owner