mirror of
https://github.com/morluto/rea.git
synced 2026-11-10 17:30:32 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8247f72666 | ||
|
|
50eb50d26a | ||
|
|
61e8653bf6 | ||
|
|
c847a45b54 | ||
|
|
9a8733e528 | ||
|
|
48bb58524d | ||
|
|
f9160aa668 | ||
|
|
f2e05257f5 | ||
|
|
4d9021913c |
No files matched your search
@@ -817,6 +817,7 @@ jobs:
|
||||
tests/boundary/process/directAnalysisSignals.test.ts
|
||||
tests/boundary/process/processCaptureLifecycle.test.ts
|
||||
tests/boundary/process/processCli.test.ts
|
||||
tests/boundary/process/nativeUiVerifierFixtureOwnership.test.ts
|
||||
tests/boundary/mcp/sessionMcp.test.ts
|
||||
|
||||
reconstruction-readiness:
|
||||
|
||||
@@ -962,7 +962,11 @@ conformance/vtable fixture.
|
||||
allows a host-permission-boundary-only result and explicitly reports
|
||||
`positive_e2e: false`; it must not be reported as capture/action proof.
|
||||
Both commands reject a changed executable digest and clean up the fixture
|
||||
process and helper. These lanes require an interactive macOS desktop. See [native investigation](native-investigation.md).
|
||||
process and helper. LaunchServices starts the fixture outside `open`'s process
|
||||
tree, so its launch carries a run token that cleanup sweeps with or without a
|
||||
ready record. Incomplete cleanup fails the lane and retains the fixture root,
|
||||
and an interrupted lane exits only after that release settles.
|
||||
These lanes require an interactive macOS desktop. See [native investigation](native-investigation.md).
|
||||
|
||||
`npm run verify:native-calls` needs macOS with Command Line Tools (`clang`,
|
||||
`lldb`, `codesign`, `nm`) and actual debugger access to the owned fixtures.
|
||||
|
||||
@@ -173,6 +173,9 @@ const PATH_SCOPES = {
|
||||
"tests/boundary/process/directAnalysisSignals.test.ts",
|
||||
"tests/boundary/process/processCaptureLifecycle.test.ts",
|
||||
"tests/boundary/process/processCli.test.ts",
|
||||
"tests/boundary/process/nativeUiVerifierFixtureOwnership.test.ts",
|
||||
"scripts/verify-native-ui.mjs",
|
||||
"scripts/lib/native-ui-fixture-lifecycle.*",
|
||||
"tests/boundary/mcp/sessionMcp.test.ts",
|
||||
],
|
||||
// Four-host installed-package matrix and the Windows curated lane.
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/** Selected fields of one fixture's token-verified ready record. */
|
||||
export interface NativeUiFixtureScope {
|
||||
readonly pid: number;
|
||||
readonly window_id: number;
|
||||
}
|
||||
|
||||
/** Owns one LaunchServices fixture from before its launch through verified exit. */
|
||||
export interface NativeUiFixtureOwner {
|
||||
readonly root: string;
|
||||
/** Compile into the root; release stops this as swiftc requires and joins it. */
|
||||
compile(arguments_: readonly string[]): Promise<unknown>;
|
||||
/** Launch one fixture and return its token-verified ready scope. */
|
||||
launch(application: string): Promise<NativeUiFixtureScope>;
|
||||
/** Release every owned resource; failure retains them for another close. */
|
||||
close(): Promise<void>;
|
||||
}
|
||||
|
||||
/** Cleanup stays incomplete; the owner keeps its run token and root for retry. */
|
||||
export class NativeUiFixtureCleanupFailure extends Error {
|
||||
readonly _tag: "NativeUiFixtureCleanupFailure";
|
||||
readonly cleanupOwner: NativeUiFixtureOwner;
|
||||
readonly root: string;
|
||||
readonly runId: string;
|
||||
}
|
||||
|
||||
/** Allocate one private fixture root and the owner that alone may remove it. */
|
||||
export function createNativeUiFixtureOwner(options?: {
|
||||
readonly launcherCommand?: string;
|
||||
readonly readyTimeoutMs?: number;
|
||||
}): Promise<NativeUiFixtureOwner>;
|
||||
|
||||
/**
|
||||
* Run one lane around a fixture owner and settle its process outcome in one
|
||||
* place. Exits 130 or 143 when an interruption is the first failure.
|
||||
*/
|
||||
export function runNativeUiFixtureLane<Value>(
|
||||
operation: (fixture: NativeUiFixtureOwner) => Promise<Value>,
|
||||
options?: {
|
||||
readonly launcherCommand?: string;
|
||||
readonly readyTimeoutMs?: number;
|
||||
},
|
||||
): Promise<Value>;
|
||||
@@ -0,0 +1,314 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { setTimeout as delay } from "node:timers/promises";
|
||||
|
||||
import { SWIFTC_INTERRUPT } from "../../dist/process/DarwinProcessRunTokenReader.js";
|
||||
import { execFileOutput } from "../../dist/process/ExecFileOutput.js";
|
||||
import { cleanupOwnedProcessGroup } from "../../dist/process/ProcessOwnership.js";
|
||||
import {
|
||||
observeProcessStartIdentity,
|
||||
systemProcessOwnershipHost,
|
||||
} from "../../dist/process/ProcessOwnershipObservation.js";
|
||||
import {
|
||||
ProviderProcessSupervisor,
|
||||
spawnOwnedProviderProcess,
|
||||
} from "../../dist/process/ProviderProcess.js";
|
||||
import {
|
||||
closeVerifierResources,
|
||||
withVerifierCleanup,
|
||||
} from "./verifier-cleanup.mjs";
|
||||
|
||||
const READY_TIMEOUT_MS = 20_000;
|
||||
const READY_POLL_INTERVAL_MS = 100;
|
||||
const LAUNCHER_OUTPUT_CLOSE_GRACE_MS = 5_000;
|
||||
const INTERRUPT_EXIT_CODES = { SIGINT: 130, SIGTERM: 143 };
|
||||
|
||||
/** Cleanup stays incomplete; the owner keeps its run token and root for retry. */
|
||||
export class NativeUiFixtureCleanupFailure extends Error {
|
||||
_tag = "NativeUiFixtureCleanupFailure";
|
||||
|
||||
constructor(cleanupOwner, runId, options) {
|
||||
const failures =
|
||||
options.cause instanceof AggregateError
|
||||
? options.cause.errors
|
||||
: [options.cause];
|
||||
super(
|
||||
`Native UI fixture cleanup is incomplete; retained ${cleanupOwner.root} for run ${runId}: ${failures.map((failure) => failure.message).join("; ")}`,
|
||||
options,
|
||||
);
|
||||
this.name = "NativeUiFixtureCleanupFailure";
|
||||
this.cleanupOwner = cleanupOwner;
|
||||
this.root = cleanupOwner.root;
|
||||
this.runId = runId;
|
||||
}
|
||||
}
|
||||
|
||||
const positiveInteger = (value) => Number.isSafeInteger(value) && value > 0;
|
||||
|
||||
/** Parse the fixture's ready record once; only its selected fields leave here. */
|
||||
const parseReadyRecord = (line) => {
|
||||
let record;
|
||||
try {
|
||||
record = JSON.parse(line);
|
||||
} catch (cause) {
|
||||
throw new Error("Source-owned UI fixture wrote a malformed ready record", {
|
||||
cause,
|
||||
});
|
||||
}
|
||||
if (!positiveInteger(record?.pid) || !positiveInteger(record?.window_id))
|
||||
throw new Error(
|
||||
`Source-owned UI fixture ready record lacks a selected PID and window: ${line}`,
|
||||
);
|
||||
return { pid: record.pid, window_id: record.window_id };
|
||||
};
|
||||
|
||||
/**
|
||||
* Owns one LaunchServices fixture from before its launch through verified exit.
|
||||
*
|
||||
* `open` starts the app outside the launcher's process tree, so neither the
|
||||
* launcher handle nor a later PID report can establish ownership. The run token
|
||||
* placed in the launch environment can: the app carries it from its first
|
||||
* instruction, and the shared sweep finds it with or without a ready record.
|
||||
*/
|
||||
class NativeUiFixtureOwner {
|
||||
#runId = randomUUID();
|
||||
#lifetime = new AbortController();
|
||||
#launcherCommand;
|
||||
#readyTimeoutMs;
|
||||
#application;
|
||||
#compiling;
|
||||
#launching;
|
||||
#launcher;
|
||||
#ownership;
|
||||
#released = false;
|
||||
#closing;
|
||||
|
||||
constructor(root, launcherCommand, readyTimeoutMs) {
|
||||
this.root = root;
|
||||
this.#launcherCommand = launcherCommand;
|
||||
this.#readyTimeoutMs = readyTimeoutMs;
|
||||
}
|
||||
|
||||
/** Compile into the root; release stops this as swiftc requires and joins it. */
|
||||
compile(arguments_) {
|
||||
const compiling = execFileOutput("/usr/bin/xcrun", arguments_, {
|
||||
signal: this.#lifetime.signal,
|
||||
stopSignal: SWIFTC_INTERRUPT,
|
||||
lifetimePipe: true,
|
||||
});
|
||||
this.#compiling = Promise.allSettled([this.#compiling, compiling]);
|
||||
return compiling;
|
||||
}
|
||||
|
||||
/** Launch one fixture and return its token-verified ready scope. */
|
||||
launch(application) {
|
||||
if (this.#launching !== undefined)
|
||||
return Promise.reject(
|
||||
new Error("Native UI fixture owner launches one fixture"),
|
||||
);
|
||||
this.#launching = this.#launch(application);
|
||||
return this.#launching;
|
||||
}
|
||||
|
||||
/** Release every owned resource; failure retains them for another close. */
|
||||
close() {
|
||||
this.#lifetime.abort(new Error("Native UI fixture owner closed"));
|
||||
if (this.#released) return Promise.resolve();
|
||||
this.#closing ??= this.#release().finally(() => {
|
||||
this.#closing = undefined;
|
||||
});
|
||||
return this.#closing;
|
||||
}
|
||||
|
||||
async #launch(application) {
|
||||
const { signal } = this.#lifetime;
|
||||
signal.throwIfAborted();
|
||||
this.#application = application;
|
||||
const coordinates = join(this.root, "scope.json");
|
||||
await writeFile(coordinates, "");
|
||||
const owned = await spawnOwnedProviderProcess({
|
||||
command: this.#launcherCommand,
|
||||
// open(1) gives the app its own environment, which already holds the token.
|
||||
// `--env` would add an argv entry the shared reader cannot prove, and a
|
||||
// readable launcher exposing one fails every concurrent sweep on this host.
|
||||
arguments: ["-W", "-n", "-g", "--stdout", coordinates, application],
|
||||
runId: this.#runId,
|
||||
sweepTokenOwnedProcesses: true,
|
||||
signal,
|
||||
});
|
||||
// Retain both authorities before the first handshake await.
|
||||
this.#ownership = owned.ownership;
|
||||
this.#launcher = new ProviderProcessSupervisor(
|
||||
{ process: owned.process, ownsProcessLifetime: true },
|
||||
{ maxDiagnosticBytes: { stdout: 0, stderr: 64 * 1024 } },
|
||||
);
|
||||
const scope = parseReadyRecord(await this.#awaitReady(coordinates, signal));
|
||||
const identity = await observeProcessStartIdentity(
|
||||
scope.pid,
|
||||
systemProcessOwnershipHost,
|
||||
this.#runId,
|
||||
);
|
||||
if (identity?.state !== "readable")
|
||||
throw new Error(
|
||||
`Source-owned UI fixture ready record names PID ${String(scope.pid)}, which does not prove this run: ${identity?.reason ?? "process is absent"}`,
|
||||
);
|
||||
return scope;
|
||||
}
|
||||
|
||||
async #awaitReady(coordinates, signal) {
|
||||
const deadline = performance.now() + this.#readyTimeoutMs;
|
||||
for (;;) {
|
||||
signal.throwIfAborted();
|
||||
// Select exit first: a record written just before exit is still accepted.
|
||||
const exit = this.#launcher.observedExit();
|
||||
const output = await readFile(coordinates, "utf8");
|
||||
const end = output.indexOf("\n");
|
||||
if (end >= 0) return output.slice(0, end);
|
||||
if (exit !== undefined) {
|
||||
// Exit can precede the launcher's final diagnostics on its pipe.
|
||||
await this.#launcher.waitForOutputClose(LAUNCHER_OUTPUT_CLOSE_GRACE_MS);
|
||||
throw new Error(
|
||||
`Source-owned UI fixture launcher exited (code ${String(exit.code)}, signal ${String(exit.signal)}) before exposing one window: ${this.#launcher.snapshot().stderr.text.trim() || "no launcher diagnostics"}`,
|
||||
);
|
||||
}
|
||||
const remaining = deadline - performance.now();
|
||||
if (remaining <= 0)
|
||||
throw new Error(
|
||||
`Source-owned UI fixture failed to expose one window within ${String(this.#readyTimeoutMs / 1000)} seconds`,
|
||||
);
|
||||
// An abort rejects with a generic error; the loop reports its own reason.
|
||||
await delay(Math.min(READY_POLL_INTERVAL_MS, remaining), undefined, {
|
||||
signal,
|
||||
}).catch(() => undefined);
|
||||
}
|
||||
}
|
||||
|
||||
async #release() {
|
||||
// Join acquisition: a compile or launcher admitted before the abort stays owned.
|
||||
await Promise.allSettled([this.#compiling, this.#launching]);
|
||||
try {
|
||||
// Attempt each: a stuck launcher must not spare a token-proven fixture. A clean
|
||||
// sweep proves nothing while a launch can still start, so every attempt sweeps
|
||||
// again and only an attempt whose steps all succeeded removes the root.
|
||||
await closeVerifierResources([
|
||||
() => this.#stopLauncher(),
|
||||
() => this.#retireApplication(),
|
||||
() => this.#sweepFixture(),
|
||||
]);
|
||||
await rm(this.root, { recursive: true, force: true });
|
||||
} catch (cause) {
|
||||
throw new NativeUiFixtureCleanupFailure(this, this.#runId, { cause });
|
||||
}
|
||||
this.#released = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* /usr/bin/open is a platform binary, so macOS withholds its token and the
|
||||
* sweep cannot prove it. Its retained child handle is the authority instead,
|
||||
* and the launcher must be gone before the sweep can succeed.
|
||||
*/
|
||||
async #stopLauncher() {
|
||||
if (this.#launcher === undefined) return;
|
||||
const stopped = await this.#launcher.stop();
|
||||
if (stopped.status === "incomplete")
|
||||
throw new Error(`Native UI fixture launcher: ${stopped.reason}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* LaunchServices finishes a launch after `open` dies: on macOS 26 arm64 the
|
||||
* app appeared after an immediate sweep when `open` was killed 10-20 ms after
|
||||
* spawn. Retire the path it must exec, so the sweep that follows is not racing
|
||||
* a launch that can still start.
|
||||
*/
|
||||
async #retireApplication() {
|
||||
if (this.#launcher === undefined) return;
|
||||
try {
|
||||
await rename(this.#application, `${this.#application}.retired`);
|
||||
} catch (cause) {
|
||||
// Already retired by an earlier attempt, or never created.
|
||||
if (cause.code !== "ENOENT") throw cause;
|
||||
}
|
||||
}
|
||||
|
||||
async #sweepFixture() {
|
||||
if (this.#ownership === undefined) return;
|
||||
const result = await cleanupOwnedProcessGroup(this.#ownership);
|
||||
if (!result.cleaned)
|
||||
throw new Error(`Native UI fixture processes: ${result.reason}`, {
|
||||
cause: result,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Allocate one private fixture root and the owner that alone may remove it. */
|
||||
export const createNativeUiFixtureOwner = async ({
|
||||
launcherCommand = "/usr/bin/open",
|
||||
readyTimeoutMs = READY_TIMEOUT_MS,
|
||||
} = {}) =>
|
||||
new NativeUiFixtureOwner(
|
||||
await mkdtemp(join(tmpdir(), "rea-ui-fixture-")),
|
||||
launcherCommand,
|
||||
readyTimeoutMs,
|
||||
);
|
||||
|
||||
/**
|
||||
* Run one lane around a fixture owner and settle its process outcome in one place.
|
||||
*
|
||||
* A signal only records the interruption and ends the operation's race. The
|
||||
* release always runs to settlement, and every signal stays handled until then:
|
||||
* npm forwards Ctrl-C, so a default action on a repeated signal would kill the
|
||||
* release and orphan the fixture. SIGKILL remains the operator's override.
|
||||
*
|
||||
* The first failure selects the status and every failure is reported. A signal
|
||||
* after the operation failed keeps that failure and exit 1; a signal after it
|
||||
* succeeded returns its completed value with the interrupted exit status.
|
||||
*/
|
||||
export const runNativeUiFixtureLane = async (operation, options) => {
|
||||
const fixture = await createNativeUiFixtureOwner(options);
|
||||
let interruption;
|
||||
let operationFailure;
|
||||
let interrupt;
|
||||
const interrupted = new Promise((_, reject) => {
|
||||
interrupt = reject;
|
||||
});
|
||||
interrupted.catch(() => undefined);
|
||||
const listeners = Object.entries(INTERRUPT_EXIT_CODES).map(
|
||||
([signal, exitCode]) => {
|
||||
const listener = () => {
|
||||
if (interruption !== undefined) return;
|
||||
interruption = Object.assign(
|
||||
new Error(`Native UI verification interrupted by ${signal}`),
|
||||
{ exitCode },
|
||||
);
|
||||
interrupt(interruption);
|
||||
};
|
||||
process.on(signal, listener);
|
||||
return [signal, listener];
|
||||
},
|
||||
);
|
||||
try {
|
||||
const value = await withVerifierCleanup(
|
||||
() =>
|
||||
Promise.race([operation(fixture), interrupted]).catch((cause) => {
|
||||
operationFailure = cause;
|
||||
throw cause;
|
||||
}),
|
||||
[() => fixture.close()],
|
||||
);
|
||||
if (interruption !== undefined) process.exitCode = interruption.exitCode;
|
||||
return value;
|
||||
} catch (cause) {
|
||||
if (
|
||||
interruption === undefined ||
|
||||
(operationFailure !== undefined && operationFailure !== interruption)
|
||||
)
|
||||
throw cause;
|
||||
console.error(cause === interruption ? cause.message : cause);
|
||||
// An interrupted operation may still be running; nothing else ends it.
|
||||
process.exit(interruption.exitCode);
|
||||
} finally {
|
||||
for (const [signal, listener] of listeners) process.off(signal, listener);
|
||||
}
|
||||
};
|
||||
@@ -1,7 +1,5 @@
|
||||
import { execFile, spawn } from "node:child_process";
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtemp, mkdir, writeFile, readFile, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { execFile } from "node:child_process";
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
@@ -9,21 +7,16 @@ import { parseBinaryTarget } from "../dist/application/BinaryTargetResolver.js";
|
||||
import { observeNativeUi } from "../dist/native/NativeUiObservation.js";
|
||||
import { NATIVE_UI_HELPER_MAX_BUFFER } from "../dist/native/NativeUiOutputBudget.js";
|
||||
import { artifactMcpResult, withArtifactMcp } from "./lib/artifact-e2e.mjs";
|
||||
import { observeProcessStartIdentity } from "../dist/process/ProcessOwnershipObservation.js";
|
||||
import { runNativeUiFixtureLane } from "./lib/native-ui-fixture-lifecycle.mjs";
|
||||
|
||||
if (process.platform !== "darwin")
|
||||
throw new Error(
|
||||
"Native UI verification requires macOS, an interactive desktop and Xcode command-line tools",
|
||||
);
|
||||
const root = await mkdtemp(join(tmpdir(), "rea-ui-fixture-"));
|
||||
let child;
|
||||
let childExit;
|
||||
let fixturePid;
|
||||
let fixtureIdentity;
|
||||
let report;
|
||||
try {
|
||||
const report = await runNativeUiFixtureLane(async (fixture) => {
|
||||
const { root } = fixture;
|
||||
const childRetrievalTest = join(root, "child-retrieval-test");
|
||||
await promisify(execFile)("/usr/bin/xcrun", [
|
||||
await fixture.compile([
|
||||
"swiftc",
|
||||
fileURLToPath(
|
||||
new URL("../bridge/native/NativeUIChildren.swift", import.meta.url),
|
||||
@@ -80,7 +73,7 @@ try {
|
||||
`<?xml version="1.0"?><plist version="1.0"><dict><key>CFBundleExecutable</key><string>fixture</string><key>CFBundleIdentifier</key><string>io.rea.source-owned-ui-fixture</string><key>CFBundleName</key><string>REA UI Fixture</string><key>CFBundlePackageType</key><string>APPL</string></dict></plist>`,
|
||||
);
|
||||
const executable = join(contents, "MacOS", "fixture");
|
||||
await promisify(execFile)("/usr/bin/xcrun", [
|
||||
await fixture.compile([
|
||||
"swiftc",
|
||||
"-module-cache-path",
|
||||
join(root, "modules"),
|
||||
@@ -90,35 +83,7 @@ try {
|
||||
"-o",
|
||||
executable,
|
||||
]);
|
||||
const coordinates = join(root, "scope.json");
|
||||
await writeFile(coordinates, "");
|
||||
child = spawn(
|
||||
"/usr/bin/open",
|
||||
["-W", "-n", "-g", "--stdout", coordinates, join(root, "Fixture.app")],
|
||||
{ stdio: "ignore" },
|
||||
);
|
||||
childExit = new Promise((resolve) => child.once("exit", resolve));
|
||||
let scope;
|
||||
const deadline = Date.now() + 20000;
|
||||
while (Date.now() < deadline) {
|
||||
const output = await readFile(coordinates, "utf8");
|
||||
if (output.includes("\n")) {
|
||||
scope = JSON.parse(output.split("\n")[0]);
|
||||
break;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
if (scope === undefined)
|
||||
throw new Error(
|
||||
"Source-owned UI fixture failed to expose one window within 20 seconds",
|
||||
);
|
||||
fixturePid = scope.pid;
|
||||
fixtureIdentity = await observeProcessStartIdentity(fixturePid);
|
||||
assert.equal(
|
||||
fixtureIdentity?.state,
|
||||
"readable",
|
||||
"Fixture OS start identity is unavailable",
|
||||
);
|
||||
const scope = await fixture.launch(join(root, "Fixture.app"));
|
||||
const target = await parseBinaryTarget(executable);
|
||||
if (!target.ok) throw target.error;
|
||||
const observation = await observeNativeUi(
|
||||
@@ -247,7 +212,7 @@ try {
|
||||
);
|
||||
if (mismatch.ok || !mismatch.error.message.includes("target-mismatch"))
|
||||
throw new Error("Real helper did not reject changed target bytes");
|
||||
report = {
|
||||
return {
|
||||
ok: true,
|
||||
verification_status: observation.ok ? "passed" : "permission-boundary-only",
|
||||
positive_e2e: observation.ok,
|
||||
@@ -259,26 +224,5 @@ try {
|
||||
mismatch_rejected: true,
|
||||
target_owned: true,
|
||||
};
|
||||
} finally {
|
||||
if (fixturePid !== undefined && fixtureIdentity?.state === "readable") {
|
||||
const current = await observeProcessStartIdentity(fixturePid);
|
||||
if (current !== undefined) {
|
||||
assert.ok(
|
||||
current.state === "readable" &&
|
||||
current.identity === fixtureIdentity.identity,
|
||||
"Fixture PID identity changed; refusing to signal another process",
|
||||
);
|
||||
try {
|
||||
process.kill(fixturePid, "SIGTERM");
|
||||
} catch (cause) {
|
||||
if (cause.code !== "ESRCH") throw cause;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (child !== undefined && child.exitCode === null) {
|
||||
child.kill("SIGTERM");
|
||||
await childExit;
|
||||
}
|
||||
await rm(root, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
process.stdout.write(`${JSON.stringify(report)}\n`);
|
||||
@@ -892,6 +892,18 @@ it.each([
|
||||
"tests/boundary/process/darwinProcessRunTokenReader.test.ts",
|
||||
{ code: true, apple: true, package: false, runtime: false },
|
||||
],
|
||||
[
|
||||
"tests/boundary/process/nativeUiVerifierFixtureOwnership.test.ts",
|
||||
{ code: true, apple: true, package: false, runtime: false },
|
||||
],
|
||||
[
|
||||
"scripts/verify-native-ui.mjs",
|
||||
{ code: true, apple: true, package: false, runtime: false },
|
||||
],
|
||||
[
|
||||
"scripts/lib/native-ui-fixture-lifecycle.mjs",
|
||||
{ code: true, apple: true, package: false, runtime: false },
|
||||
],
|
||||
[
|
||||
"tests/boundary/cli/nativePlistCli.test.ts",
|
||||
{ code: true, apple: true, package: false, runtime: false },
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user