mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:30:42 +00:00
ci: split auto-reply shard timing
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { performance } from "node:perf_hooks";
|
||||
import { acquireLocalHeavyCheckLockSync } from "./lib/local-heavy-check-runtime.mjs";
|
||||
import {
|
||||
@@ -7,6 +6,11 @@ import {
|
||||
resolveLocalFullSuiteProfile,
|
||||
resolveLocalVitestEnv,
|
||||
} from "./lib/vitest-local-scheduling.mjs";
|
||||
import {
|
||||
createShardTimingSample,
|
||||
readShardTimings,
|
||||
writeShardTimings,
|
||||
} from "./lib/vitest-shard-timings.mjs";
|
||||
import {
|
||||
resolveVitestCliEntry,
|
||||
resolveVitestNodeArgs,
|
||||
@@ -94,8 +98,6 @@ const FULL_SUITE_CONFIG_WEIGHT = new Map([
|
||||
["test/vitest/vitest.extension-memory.config.ts", 6],
|
||||
["test/vitest/vitest.extension-msteams.config.ts", 4],
|
||||
]);
|
||||
const TIMINGS_FILE_ENV_KEY = "OPENCLAW_TEST_PROJECTS_TIMINGS_PATH";
|
||||
const TIMINGS_DISABLE_ENV_KEY = "OPENCLAW_TEST_PROJECTS_TIMINGS";
|
||||
const releaseLockOnce = () => {
|
||||
if (lockReleased) {
|
||||
return;
|
||||
@@ -104,81 +106,6 @@ const releaseLockOnce = () => {
|
||||
releaseLock();
|
||||
};
|
||||
|
||||
function shouldUseShardTimings(env = process.env) {
|
||||
return env[TIMINGS_DISABLE_ENV_KEY] !== "0";
|
||||
}
|
||||
|
||||
function resolveShardTimingsPath(cwd = process.cwd(), env = process.env) {
|
||||
return env[TIMINGS_FILE_ENV_KEY] || path.join(cwd, ".artifacts", "vitest-shard-timings.json");
|
||||
}
|
||||
|
||||
function readShardTimings(cwd = process.cwd(), env = process.env) {
|
||||
if (!shouldUseShardTimings(env)) {
|
||||
return new Map();
|
||||
}
|
||||
try {
|
||||
const raw = fs.readFileSync(resolveShardTimingsPath(cwd, env), "utf8");
|
||||
const parsed = JSON.parse(raw);
|
||||
const configs = parsed && typeof parsed === "object" ? parsed.configs : null;
|
||||
if (!configs || typeof configs !== "object") {
|
||||
return new Map();
|
||||
}
|
||||
return new Map(
|
||||
Object.entries(configs)
|
||||
.map(([config, value]) => {
|
||||
const durationMs = Number(value?.averageMs ?? value?.durationMs);
|
||||
return Number.isFinite(durationMs) && durationMs > 0 ? [config, durationMs] : null;
|
||||
})
|
||||
.filter(Boolean),
|
||||
);
|
||||
} catch {
|
||||
return new Map();
|
||||
}
|
||||
}
|
||||
|
||||
function writeShardTimings(samples, cwd = process.cwd(), env = process.env) {
|
||||
if (!shouldUseShardTimings(env) || samples.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const outputPath = resolveShardTimingsPath(cwd, env);
|
||||
let current = { version: 1, configs: {} };
|
||||
try {
|
||||
current = JSON.parse(fs.readFileSync(outputPath, "utf8"));
|
||||
} catch {
|
||||
// First run, or a corrupt local artifact. Rewrite below.
|
||||
}
|
||||
|
||||
const configs =
|
||||
current && typeof current === "object" && current.configs && typeof current.configs === "object"
|
||||
? { ...current.configs }
|
||||
: {};
|
||||
const updatedAt = new Date().toISOString();
|
||||
for (const sample of samples) {
|
||||
if (!sample.config || !Number.isFinite(sample.durationMs) || sample.durationMs <= 0) {
|
||||
continue;
|
||||
}
|
||||
const previous = configs[sample.config];
|
||||
const previousAverage = Number(previous?.averageMs ?? previous?.durationMs);
|
||||
const sampleCount = Math.max(0, Number(previous?.sampleCount) || 0) + 1;
|
||||
const averageMs =
|
||||
Number.isFinite(previousAverage) && previousAverage > 0
|
||||
? Math.round(previousAverage * 0.7 + sample.durationMs * 0.3)
|
||||
: Math.round(sample.durationMs);
|
||||
configs[sample.config] = {
|
||||
averageMs,
|
||||
lastMs: Math.round(sample.durationMs),
|
||||
sampleCount,
|
||||
updatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
||||
const tempPath = `${outputPath}.${process.pid}.tmp`;
|
||||
fs.writeFileSync(tempPath, `${JSON.stringify({ version: 1, configs }, null, 2)}\n`, "utf8");
|
||||
fs.renameSync(tempPath, outputPath);
|
||||
}
|
||||
|
||||
function cleanupVitestRunSpec(spec) {
|
||||
if (!spec.includeFilePath) {
|
||||
return;
|
||||
@@ -263,8 +190,7 @@ async function runLoggedVitestSpec(spec) {
|
||||
}
|
||||
return {
|
||||
...result,
|
||||
timing:
|
||||
!spec.watchMode && spec.includePatterns === null ? { config: spec.config, durationMs } : null,
|
||||
timing: createShardTimingSample(spec, durationMs),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -288,6 +214,7 @@ function interleaveSlowAndFastSpecs(sortedSpecs) {
|
||||
}
|
||||
|
||||
function orderFullSuiteSpecsForParallelRun(specs, shardTimings = new Map()) {
|
||||
const hasMatchingShardTiming = specs.some((spec) => shardTimings.has(spec.config));
|
||||
const sortedSpecs = specs.toSorted((a, b) => {
|
||||
const weightDelta =
|
||||
resolveConfigSortWeight(b.config, shardTimings) -
|
||||
@@ -297,7 +224,7 @@ function orderFullSuiteSpecsForParallelRun(specs, shardTimings = new Map()) {
|
||||
}
|
||||
return a.config.localeCompare(b.config);
|
||||
});
|
||||
return shardTimings.size > 0 ? interleaveSlowAndFastSpecs(sortedSpecs) : sortedSpecs;
|
||||
return hasMatchingShardTiming ? interleaveSlowAndFastSpecs(sortedSpecs) : sortedSpecs;
|
||||
}
|
||||
|
||||
function isFullExtensionsProjectRun(specs) {
|
||||
|
||||
Reference in New Issue
Block a user