test(hopper): align startup fixtures with cleanup and lease contracts

Inspect the typed primary startup failure retained by aggregate cleanup errors. Materialize isolated targets before real lease acquisition so launcher scenarios reach the behavior under test. Repairs the Linux shard and Windows curated test failures without weakening cleanup retention coverage.
This commit is contained in:
morluto committed 2026-10-12 15:02:43 +08:00
1 parent d4e8ef7499
commit b3dec005bc
3 files changed
+26 -9

No files matched your search

@@ -165,10 +165,12 @@ describe("Hopper bridge launcher selection", () => {
it("launches Hopper helpers with the caller-selected environment without restoring ambient values", async () => {
const directory = await createTestTempDirectory("rea-hopper-selected-env-");
const targetPath = join(directory, "target");
await writeFile(targetPath, "fixture target");
const launcher = new HopperApplicationLauncher({
environment: { REA_SELECTED_VALUE: "caller-value" },
launcherPath: process.execPath,
targetPath: join(directory, "target"),
targetPath,
targetKind: "executable",
loaderArgs: [
"-e",
@@ -107,7 +107,7 @@ const failedStartup = async (launcher: RetryCleanupLauncher) => {
error: {
_tag: "ProviderAdapterError",
cleanupIncomplete: true,
cause: { _tag: "HopperTimeoutError" },
cause: { cause: { _tag: "HopperTimeoutError" } },
},
});
const session = launcher.sessions[0];
@@ -233,7 +233,7 @@ it("blocks a fresh launch while cleanup remains unverified", async () => {
ok: false,
error: {
cleanupIncomplete: true,
cause: { _tag: "HopperTimeoutError" },
cause: { cause: { _tag: "HopperTimeoutError" } },
diagnostics: { primary_error: { code: "provider_timeout" } },
},
});
@@ -263,7 +263,7 @@ it("preserves failed startup state through a blocked retry until cleanup succeed
error: {
_tag: "ProviderAdapterError",
cleanupIncomplete: true,
cause: { _tag: "HopperTimeoutError" },
cause: { cause: { _tag: "HopperTimeoutError" } },
},
});
// ...but must not wipe the failed-startup state: the client still
@@ -1,8 +1,11 @@
import { spawn } from "node:child_process";
import { access } from "node:fs/promises";
import { access, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect, it, onTestFinished } from "vitest";
import { createTestTempDirectory } from "../../../fixtures/temporaryDirectory.js";
import { AnalysisError } from "../../../../src/domain/analysisErrorBase.js";
import { projectAnalysisError } from "../../../../src/domain/analysisErrorProjection.js";
import { ok } from "../../../../src/domain/result.js";
@@ -67,17 +70,23 @@ const trackFailedHelperClient = (
const projectPrimaryStartupFailure = (error: AnalysisError) => {
if (!error.cleanupIncomplete) return projectAnalysisError(error);
expect(projectAnalysisError(error).code).toBe("cleanup_incomplete");
const primary = error.cause;
const cause = error.cause;
if (!(cause instanceof AggregateError))
throw new Error("Cleanup failure did not retain its aggregate causes");
const primary = cause.cause;
if (!(primary instanceof AnalysisError))
throw new Error("Cleanup failure lost its typed startup cause");
return projectAnalysisError(primary);
};
it("does not equate an owned native helper exit with an external GUI exit", async () => {
const directory = await createTestTempDirectory("rea-hopper-helper-failure-");
const targetPath = join(directory, "target");
await writeFile(targetPath, "fixture target");
const launcher = new HopperApplicationLauncher({
environment: {},
launcherPath: process.execPath,
targetPath: "/target",
targetPath,
targetKind: "executable",
loaderArgs: [
"-e",
@@ -91,7 +100,10 @@ it("does not equate an owned native helper exit with an external GUI exit", asyn
const result = await client.start();
expect(result).toMatchObject({
ok: false,
error: { cleanupIncomplete: true, cause: { _tag: "HopperStartError" } },
error: {
cleanupIncomplete: true,
cause: { cause: { _tag: "HopperStartError" } },
},
});
if (result.ok) throw new Error("Expected the native helper to fail");
expect(projectPrimaryStartupFailure(result.error)).toMatchObject({
@@ -140,7 +152,10 @@ it.each(["exit", "signal", "exit75"] as const)(
const result = await client.start();
expect(result).toMatchObject({
ok: false,
error: { cleanupIncomplete: true, cause: { _tag: "HopperStartError" } },
error: {
cleanupIncomplete: true,
cause: { cause: { _tag: "HopperStartError" } },
},
});
if (result.ok) throw new Error("Expected the launcher to fail");
const projected = projectPrimaryStartupFailure(result.error);