mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-30 11:21:07 +00:00
fix(test): harden planner artifact cleanup and profile env fallback
This commit is contained in:
@@ -166,6 +166,14 @@ export function createExecutionArtifacts(env = process.env) {
|
||||
return { ensureTempArtifactDir, writeTempJsonArtifact, cleanupTempArtifacts };
|
||||
}
|
||||
|
||||
export function createTempArtifactWriteStream(filePath) {
|
||||
const fd = fs.openSync(filePath, "w");
|
||||
return fs.createWriteStream(filePath, {
|
||||
fd,
|
||||
autoClose: true,
|
||||
});
|
||||
}
|
||||
|
||||
const ensureNodeOptionFlag = (nodeOptions, flagPrefix, nextValue) =>
|
||||
nodeOptions.includes(flagPrefix) ? nodeOptions : `${nodeOptions} ${nextValue}`.trim();
|
||||
|
||||
@@ -420,7 +428,7 @@ export async function executePlan(plan, options = {}) {
|
||||
.filter(Boolean)
|
||||
.join("-");
|
||||
const laneLogPath = path.join(artifacts.ensureTempArtifactDir(), `${artifactStem}.log`);
|
||||
const laneLogStream = fs.createWriteStream(laneLogPath, { flags: "w" });
|
||||
const laneLogStream = createTempArtifactWriteStream(laneLogPath);
|
||||
laneLogStream.write(`[test-parallel] entry=${unit.id}\n`);
|
||||
laneLogStream.write(`[test-parallel] cwd=${process.cwd()}\n`);
|
||||
laneLogStream.write(
|
||||
|
||||
@@ -3,6 +3,7 @@ import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
createExecutionArtifacts,
|
||||
createTempArtifactWriteStream,
|
||||
resolvePnpmCommandInvocation,
|
||||
resolveVitestFsModuleCachePath,
|
||||
} from "../../scripts/test-planner/executor.mjs";
|
||||
@@ -348,6 +349,24 @@ describe("test planner", () => {
|
||||
expect(fs.existsSync(artifactDir)).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps fd-backed artifact streams writable after temp cleanup", async () => {
|
||||
const artifacts = createExecutionArtifacts({});
|
||||
const artifactDir = artifacts.ensureTempArtifactDir();
|
||||
const logPath = path.join(artifactDir, "lane.log");
|
||||
const stream = createTempArtifactWriteStream(logPath);
|
||||
|
||||
stream.write("before cleanup\n");
|
||||
artifacts.cleanupTempArtifacts();
|
||||
|
||||
await expect(
|
||||
new Promise((resolve, reject) => {
|
||||
stream.on("error", reject);
|
||||
stream.end("after cleanup\n", resolve);
|
||||
}),
|
||||
).resolves.toBeNull();
|
||||
expect(fs.existsSync(artifactDir)).toBe(false);
|
||||
});
|
||||
|
||||
it("builds a CI manifest with planner-owned shard counts and matrices", () => {
|
||||
const manifest = buildCIExecutionManifest(
|
||||
{
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { importFreshModule } from "./helpers/import-fresh.js";
|
||||
import { installTestEnv } from "./test-env.js";
|
||||
|
||||
const ORIGINAL_ENV = { ...process.env };
|
||||
@@ -136,4 +137,30 @@ describe("installTestEnv", () => {
|
||||
expect(process.env.HOME).toBe(realHome);
|
||||
expect(process.env.TEST_PROFILE_ONLY).toBe("from-profile");
|
||||
});
|
||||
|
||||
it("falls back to parsing ~/.profile when bash is unavailable", async () => {
|
||||
const realHome = createTempHome();
|
||||
writeFile(path.join(realHome, ".profile"), "export TEST_PROFILE_ONLY=from-profile\n");
|
||||
|
||||
process.env.HOME = realHome;
|
||||
process.env.USERPROFILE = realHome;
|
||||
process.env.OPENCLAW_LIVE_TEST = "1";
|
||||
process.env.OPENCLAW_LIVE_USE_REAL_HOME = "1";
|
||||
process.env.OPENCLAW_LIVE_TEST_QUIET = "1";
|
||||
|
||||
vi.doMock("node:child_process", () => ({
|
||||
execFileSync: () => {
|
||||
throw Object.assign(new Error("bash missing"), { code: "ENOENT" });
|
||||
},
|
||||
}));
|
||||
|
||||
const { installTestEnv: installFreshTestEnv } = await importFreshModule<
|
||||
typeof import("./test-env.js")
|
||||
>(import.meta.url, "./test-env.js?scope=profile-fallback");
|
||||
|
||||
const testEnv = installFreshTestEnv();
|
||||
|
||||
expect(testEnv.tempHome).toBe(realHome);
|
||||
expect(process.env.TEST_PROFILE_ONLY).toBe("from-profile");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -50,34 +50,67 @@ function loadProfileEnv(homeDir = os.homedir()): void {
|
||||
if (!fs.existsSync(profilePath)) {
|
||||
return;
|
||||
}
|
||||
const applyEntry = (entry: string) => {
|
||||
const idx = entry.indexOf("=");
|
||||
if (idx <= 0) {
|
||||
return false;
|
||||
}
|
||||
const key = entry.slice(0, idx).trim();
|
||||
if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(key) || (process.env[key] ?? "") !== "") {
|
||||
return false;
|
||||
}
|
||||
process.env[key] = entry.slice(idx + 1);
|
||||
return true;
|
||||
};
|
||||
const countAppliedEntries = (entries: Iterable<string>) => {
|
||||
let applied = 0;
|
||||
for (const entry of entries) {
|
||||
if (applyEntry(entry)) {
|
||||
applied += 1;
|
||||
}
|
||||
}
|
||||
return applied;
|
||||
};
|
||||
try {
|
||||
const output = execFileSync(
|
||||
"/bin/bash",
|
||||
["-lc", `set -a; source "${profilePath}" >/dev/null 2>&1; env -0`],
|
||||
{ encoding: "utf8" },
|
||||
);
|
||||
const entries = output.split("\0");
|
||||
let applied = 0;
|
||||
for (const entry of entries) {
|
||||
if (!entry) {
|
||||
continue;
|
||||
}
|
||||
const idx = entry.indexOf("=");
|
||||
if (idx <= 0) {
|
||||
continue;
|
||||
}
|
||||
const key = entry.slice(0, idx);
|
||||
if (!key || (process.env[key] ?? "") !== "") {
|
||||
continue;
|
||||
}
|
||||
process.env[key] = entry.slice(idx + 1);
|
||||
applied += 1;
|
||||
}
|
||||
const applied = countAppliedEntries(output.split("\0").filter(Boolean));
|
||||
if (applied > 0 && !isTruthyEnvValue(process.env.OPENCLAW_LIVE_TEST_QUIET)) {
|
||||
console.log(`[live] loaded ${applied} env vars from ~/.profile`);
|
||||
}
|
||||
} catch {
|
||||
// ignore profile load failures
|
||||
try {
|
||||
const fallbackEntries = fs
|
||||
.readFileSync(profilePath, "utf8")
|
||||
.split(/\r?\n/u)
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line && !line.startsWith("#"))
|
||||
.map((line) => line.replace(/^export\s+/u, ""))
|
||||
.map((line) => {
|
||||
const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/u);
|
||||
if (!match) {
|
||||
return "";
|
||||
}
|
||||
let value = match[2].trim();
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
return `${match[1]}=${value}`;
|
||||
})
|
||||
.filter(Boolean);
|
||||
const applied = countAppliedEntries(fallbackEntries);
|
||||
if (applied > 0 && !isTruthyEnvValue(process.env.OPENCLAW_LIVE_TEST_QUIET)) {
|
||||
console.log(`[live] loaded ${applied} env vars from ~/.profile`);
|
||||
}
|
||||
} catch {
|
||||
// ignore profile load failures
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user