mirror of
https://github.com/openclaw/openclaw.git
synced 2026-09-07 08:39:14 +00:00
* fix(macos): isolate native test process resources * test(macos): include native launcher in workflow routing contract * fix(scripts): finalize strict child ownership before output closure * test(macos): isolate signing failure resources * test(ci): own checkout fixture processes and retained artifacts * ci(macos): capture bounded native test diagnostics * fix(macos): provision disposable native test keychains Complete the pre-bundle resource owner with an unlocked private Keychain and verified cleanup. Cover setup, shutdown, output drainage, migration, and failure retention without changing production preview behavior. Remove temporary native diagnostics before exact-head CI proof. * test(macos): honor Foundation path contracts Normalize both home-path operands with Foundation and verify inherited TMPDIR separately from Darwin's per-user temporary directory. Preserve actual fixture containment, cleanup, and Keychain acceptance; correct the matching docs and socket-fixture guidance. * test: preserve fixture cleanup failures Join the existing child-close promise within the cleanup budget, retain body and teardown errors through the shared fixture owner, and keep independent test-case dimensions explicit. Resolve fixture lint without weakening assertions or process cleanup.
139 lines
5.2 KiB
TypeScript
139 lines
5.2 KiB
TypeScript
// CI resource owner; the disposable credentialless runner is the isolation boundary.
|
|
import { randomUUID } from "node:crypto";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { runWithFailedTrailer } from "./lib/failed-trailer.mts";
|
|
import { runManagedCommand } from "./lib/managed-child-process.mts";
|
|
|
|
await runWithFailedTrailer("macos-native", async () => {
|
|
const env = process.env;
|
|
// Invocation checks prevent accidental local use; these markers are not a sandbox.
|
|
if (
|
|
env.CI !== "true" ||
|
|
env.GITHUB_ACTIONS !== "true" ||
|
|
env.RUNNER_OS !== "macOS" ||
|
|
!env.RUNNER_TEMP ||
|
|
!env.HOME ||
|
|
process.platform === "win32"
|
|
) {
|
|
throw new Error(
|
|
"Run native app tests in the disposable macos-swift GitHub CI job, never on an operator desktop.",
|
|
);
|
|
}
|
|
const [profileMode, ...args] = process.argv.slice(2);
|
|
if (profileMode !== "default" && profileMode !== "named") {
|
|
throw new Error("Select default or named profile semantics before the Swift test arguments.");
|
|
}
|
|
if (!args.includes("--skip-build")) {
|
|
throw new Error(
|
|
"Build tests first with swift build --build-tests; this launcher requires --skip-build.",
|
|
);
|
|
}
|
|
|
|
// Keep paths short for tools honoring TMPDIR, independently of RUNNER_TEMP's length.
|
|
// Foundation's Darwin temp directory belongs to the disposable OS worker instead.
|
|
const root = fs.realpathSync(fs.mkdtempSync("/tmp/oc-test-"));
|
|
let canRemove = true;
|
|
try {
|
|
const home = path.join(root, "home");
|
|
const state = path.join(root, "state");
|
|
const tmp = path.join(root, "tmp");
|
|
for (const dir of [home, state, tmp]) {
|
|
fs.mkdirSync(dir, { mode: 0o700 });
|
|
}
|
|
const childEnv: NodeJS.ProcessEnv = {};
|
|
for (const key of [
|
|
"PATH",
|
|
"DEVELOPER_DIR",
|
|
"SDKROOT",
|
|
"TOOLCHAINS",
|
|
"LANG",
|
|
"LC_ALL",
|
|
"TERM",
|
|
"DYLD_FRAMEWORK_PATH",
|
|
"DYLD_LIBRARY_PATH",
|
|
"LLVM_PROFILE_FILE",
|
|
"SWIFTPM_MODULECACHE_OVERRIDE",
|
|
"CLANG_MODULE_CACHE_PATH",
|
|
// Preserve Actions' orphan-cleanup correlation through the isolated child env.
|
|
"RUNNER_TRACKING_ID",
|
|
]) {
|
|
if (env[key] !== undefined) {
|
|
childEnv[key] = env[key];
|
|
}
|
|
}
|
|
Object.assign(childEnv, {
|
|
CI: "true",
|
|
HOME: home,
|
|
CFFIXED_USER_HOME: home,
|
|
TMPDIR: `${tmp}/`,
|
|
TMP: tmp,
|
|
TEMP: tmp,
|
|
// The full suite protects default-profile lifecycle behavior. Named-profile
|
|
// construction is exercised separately; both use the disposable runner's account.
|
|
OPENCLAW_PROFILE: profileMode === "named" ? `test-${randomUUID()}` : "default",
|
|
OPENCLAW_STATE_DIR: state,
|
|
OPENCLAW_CONFIG_PATH: path.join(state, "openclaw.json"),
|
|
});
|
|
|
|
// Keep SwiftPM's build cache available without inheriting the runner's app state.
|
|
const cache = path.join(home, "Library/Caches");
|
|
fs.mkdirSync(cache, { recursive: true, mode: 0o700 });
|
|
fs.symlinkSync(
|
|
path.join(env.HOME, "Library/Caches/org.swift.swiftpm"),
|
|
path.join(cache, "org.swift.swiftpm"),
|
|
);
|
|
const keychain = path.join(home, "Library/Keychains/native-tests.keychain-db");
|
|
// Security writes its user preferences beneath HOME but does not create the parent.
|
|
for (const dir of [path.dirname(keychain), path.join(home, "Library/Preferences")]) {
|
|
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
}
|
|
const run = async (bin: string, commandArgs: string[], timeoutMs?: number) => {
|
|
canRemove = false;
|
|
const code = await runManagedCommand({
|
|
bin,
|
|
args: commandArgs,
|
|
env: childEnv,
|
|
requireProcessTreeExit: true,
|
|
timeoutMs,
|
|
});
|
|
canRemove = true;
|
|
return code;
|
|
};
|
|
// Empty test-only password prevents prompts; no automatic locking while the suite runs.
|
|
// Only the user domain changes. Common/dynamic Keychains still require a disposable host.
|
|
try {
|
|
for (const command of [
|
|
["create-keychain", "-p", "", keychain],
|
|
["unlock-keychain", "-p", "", keychain],
|
|
["set-keychain-settings", keychain],
|
|
["list-keychains", "-d", "user", "-s", keychain],
|
|
["default-keychain", "-d", "user", "-s", keychain],
|
|
]) {
|
|
process.exitCode = await run("security", command, 30_000);
|
|
if (process.exitCode !== 0) {
|
|
console.error(`[macos-native] security ${command[0]} failed (exit ${process.exitCode})`);
|
|
return;
|
|
}
|
|
}
|
|
process.exitCode = await run("swift", ["test", ...args]);
|
|
} finally {
|
|
// A completed failed create may leave a database. Never delete it until every child closed.
|
|
if (canRemove && fs.existsSync(keychain)) {
|
|
const cleanupCode = await run("security", ["delete-keychain", keychain], 30_000);
|
|
if (cleanupCode !== 0) {
|
|
canRemove = false;
|
|
process.exitCode ||= cleanupCode;
|
|
console.error(`[macos-native] security delete-keychain failed (exit ${cleanupCode})`);
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
// Retain evidence/resources if process-tree completion could not be established.
|
|
if (canRemove) {
|
|
fs.rmSync(root, { recursive: true, force: true });
|
|
} else {
|
|
console.error(`[macos-native] retained resources after incomplete launch/cleanup: ${root}`);
|
|
}
|
|
}
|
|
});
|