mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 09:39:25 +00:00
improve(ci): explain Vitest transform cache reuse and invalidation (#155444)
* improve: distinguish Vitest archive restores from local cache invalidation * test(ci): model transform cache restore outputs * test(ci): follow upstream transform cache root contract * test(ci): require transform cache restore prefix
This commit is contained in:
parent
47ca954c51
commit
cd6e019ec2
3 changed files with 97 additions and 0 deletions
12
.github/actions/setup-node-env/action.yml
vendored
12
.github/actions/setup-node-env/action.yml
vendored
|
|
@ -269,6 +269,10 @@ runs:
|
||||||
env:
|
env:
|
||||||
CACHE_GENERATION: ${{ steps.vitest-cache-generation.outputs.value }}
|
CACHE_GENERATION: ${{ steps.vitest-cache-generation.outputs.value }}
|
||||||
CACHE_WRITER: "0"
|
CACHE_WRITER: "0"
|
||||||
|
# Prefix restores report cache-hit=false too; matched-key owns whether
|
||||||
|
# the archive was restored, independently of runner-local contents.
|
||||||
|
CACHE_RESTORED: ${{ steps.vitest-cache.outputs['cache-matched-key'] != '' && 'true' || 'false' }}
|
||||||
|
CACHE_RUNNER_ENVIRONMENT: ${{ runner.environment }}
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
@ -280,13 +284,21 @@ runs:
|
||||||
cache_generation="$(<"$generation_file")"
|
cache_generation="$(<"$generation_file")"
|
||||||
fi
|
fi
|
||||||
cache_entry="$(find "$cache_root" -mindepth 1 -maxdepth 1 -print -quit)"
|
cache_entry="$(find "$cache_root" -mindepth 1 -maxdepth 1 -print -quit)"
|
||||||
|
cache_local=empty
|
||||||
|
if [[ -n "$cache_entry" ]]; then
|
||||||
|
cache_local=retained
|
||||||
|
fi
|
||||||
# Every restore is runner-local. Never mix incompatible transform
|
# Every restore is runner-local. Never mix incompatible transform
|
||||||
# inputs even when an older archive was selected through a prefix.
|
# inputs even when an older archive was selected through a prefix.
|
||||||
if [[ -n "$cache_entry" ]] && [[ "$cache_generation" != "$CACHE_GENERATION" ]]; then
|
if [[ -n "$cache_entry" ]] && [[ "$cache_generation" != "$CACHE_GENERATION" ]]; then
|
||||||
echo "Vitest transform inputs changed; clearing incompatible cache generation"
|
echo "Vitest transform inputs changed; clearing incompatible cache generation"
|
||||||
find "$cache_root" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +
|
find "$cache_root" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +
|
||||||
|
cache_local=cleared
|
||||||
fi
|
fi
|
||||||
printf '%s\n' "$CACHE_GENERATION" > "$generation_file"
|
printf '%s\n' "$CACHE_GENERATION" > "$generation_file"
|
||||||
|
# This describes archive/local generation state, not per-file transform hits.
|
||||||
|
printf 'Vitest transform cache: restored=%s local=%s generation=%s runner=%s\n' \
|
||||||
|
"$CACHE_RESTORED" "$cache_local" "$CACHE_GENERATION" "$CACHE_RUNNER_ENVIRONMENT"
|
||||||
# The shard runner treats this as a persistent root and assigns one
|
# The shard runner treats this as a persistent root and assigns one
|
||||||
# isolated subdirectory per concurrent Vitest worker.
|
# isolated subdirectory per concurrent Vitest worker.
|
||||||
echo "OPENCLAW_VITEST_FS_MODULE_CACHE_ROOT=$cache_root" >> "$GITHUB_ENV"
|
echo "OPENCLAW_VITEST_FS_MODULE_CACHE_ROOT=$cache_root" >> "$GITHUB_ENV"
|
||||||
|
|
|
||||||
|
|
@ -5705,6 +5705,7 @@ server.listen(0, "127.0.0.1", () => {
|
||||||
});
|
});
|
||||||
let cacheInputs: Record<string, string> | undefined;
|
let cacheInputs: Record<string, string> | undefined;
|
||||||
let configuredGeneration: string | undefined;
|
let configuredGeneration: string | undefined;
|
||||||
|
let configuredRestored: string | undefined;
|
||||||
for (const step of transformSteps) {
|
for (const step of transformSteps) {
|
||||||
// Runner v2.336.0 evaluates embedded env before if; run/with inputs
|
// Runner v2.336.0 evaluates embedded env before if; run/with inputs
|
||||||
// are evaluated only after admission (CompositeActionHandler/ActionRunner).
|
// are evaluated only after admission (CompositeActionHandler/ActionRunner).
|
||||||
|
|
@ -5734,8 +5735,19 @@ server.listen(0, "127.0.0.1", () => {
|
||||||
cacheInputs = Object.fromEntries(
|
cacheInputs = Object.fromEntries(
|
||||||
Object.entries(step.with ?? {}).map(([key, value]) => [key, render(value)]),
|
Object.entries(step.with ?? {}).map(([key, value]) => [key, render(value)]),
|
||||||
);
|
);
|
||||||
|
// Exercise misses and prefix hits; neither reports cache-hit=true.
|
||||||
|
steps[expectDefined(step.id, "transform restore step id")] = {
|
||||||
|
outputs: {
|
||||||
|
"cache-hit": generation === "a".repeat(64) ? "" : "false",
|
||||||
|
"cache-matched-key":
|
||||||
|
generation === "a".repeat(64)
|
||||||
|
? ""
|
||||||
|
: `${expectDefined(cacheInputs["restore-keys"], "transform restore prefix").trim()}9-1`,
|
||||||
|
},
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
configuredGeneration = env.CACHE_GENERATION;
|
configuredGeneration = env.CACHE_GENERATION;
|
||||||
|
configuredRestored = env.CACHE_RESTORED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const enabled = os !== "Windows" && mode !== "off" && flags.includes("true");
|
const enabled = os !== "Windows" && mode !== "off" && flags.includes("true");
|
||||||
|
|
@ -5761,9 +5773,11 @@ server.listen(0, "127.0.0.1", () => {
|
||||||
"restore-keys": `${prefix}\n`,
|
"restore-keys": `${prefix}\n`,
|
||||||
});
|
});
|
||||||
expect(configuredGeneration).toBe(generation);
|
expect(configuredGeneration).toBe(generation);
|
||||||
|
expect(configuredRestored).toBe(generation === "a".repeat(64) ? "false" : "true");
|
||||||
} else {
|
} else {
|
||||||
expect(cacheInputs).toBeUndefined();
|
expect(cacheInputs).toBeUndefined();
|
||||||
expect(configuredGeneration).toBeUndefined();
|
expect(configuredGeneration).toBeUndefined();
|
||||||
|
expect(configuredRestored).toBeUndefined();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
71
test/scripts/setup-node-env-transform-cache.test.ts
Normal file
71
test/scripts/setup-node-env-transform-cache.test.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { spawnSync } from "node:child_process";
|
||||||
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { afterEach, expect, it } from "vitest";
|
||||||
|
import { parse } from "yaml";
|
||||||
|
import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js";
|
||||||
|
|
||||||
|
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
|
||||||
|
|
||||||
|
it.skipIf(process.platform === "win32")(
|
||||||
|
"reports archive restoration separately from local generation retention",
|
||||||
|
() => {
|
||||||
|
const action = parse(readFileSync(".github/actions/setup-node-env/action.yml", "utf8"));
|
||||||
|
const step = action.runs.steps.find(
|
||||||
|
(candidate: { name: string }) => candidate.name === "Configure Vitest transform cache",
|
||||||
|
);
|
||||||
|
expect(step.env.CACHE_RESTORED).toBe(
|
||||||
|
"${{ steps.vitest-cache.outputs['cache-matched-key'] != '' && 'true' || 'false' }}",
|
||||||
|
);
|
||||||
|
const generation = "a".repeat(64);
|
||||||
|
for (const scenario of [
|
||||||
|
{ restored: "true", marker: generation, entry: true, local: "retained" },
|
||||||
|
{ restored: "false", marker: generation, entry: true, local: "retained" },
|
||||||
|
{ restored: "true", marker: "b".repeat(64), entry: true, local: "cleared" },
|
||||||
|
{ restored: "true", marker: undefined, entry: true, local: "cleared" },
|
||||||
|
{ restored: "false", marker: undefined, entry: false, local: "empty" },
|
||||||
|
]) {
|
||||||
|
const root = tempDirs.make("openclaw-transform-cache-");
|
||||||
|
const cache = path.join(root, "cache");
|
||||||
|
const githubEnv = path.join(root, "env");
|
||||||
|
const marker = path.join(cache, ".openclaw-transform-generation");
|
||||||
|
const entry = path.join(cache, "entry");
|
||||||
|
mkdirSync(cache);
|
||||||
|
if (scenario.marker !== undefined) {
|
||||||
|
writeFileSync(marker, scenario.marker);
|
||||||
|
}
|
||||||
|
if (scenario.entry) {
|
||||||
|
writeFileSync(entry, "cached transform");
|
||||||
|
}
|
||||||
|
// Only relocate the fixed cache root; execute the shipped action body.
|
||||||
|
const script = step.run.replace(
|
||||||
|
"cache_root=/var/tmp/openclaw-vitest-fs-cache",
|
||||||
|
'cache_root="$TEST_CACHE_ROOT"',
|
||||||
|
);
|
||||||
|
const result = spawnSync("/bin/bash", ["-c", script], {
|
||||||
|
encoding: "utf8",
|
||||||
|
env: {
|
||||||
|
PATH: process.env.PATH,
|
||||||
|
TEST_CACHE_ROOT: cache,
|
||||||
|
GITHUB_ENV: githubEnv,
|
||||||
|
CACHE_GENERATION: generation,
|
||||||
|
CACHE_WRITER: step.env.CACHE_WRITER,
|
||||||
|
CACHE_RESTORED: scenario.restored,
|
||||||
|
CACHE_RUNNER_ENVIRONMENT: "github-hosted",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(result.status, result.stderr).toBe(0);
|
||||||
|
expect(result.stdout).toContain(
|
||||||
|
`Vitest transform cache: restored=${scenario.restored} local=${scenario.local} generation=${generation} runner=github-hosted`,
|
||||||
|
);
|
||||||
|
expect(readFileSync(marker, "utf8")).toBe(`${generation}\n`);
|
||||||
|
expect(existsSync(entry)).toBe(scenario.local === "retained");
|
||||||
|
if (scenario.local === "retained") {
|
||||||
|
expect(readFileSync(entry, "utf8")).toBe("cached transform");
|
||||||
|
}
|
||||||
|
expect(readFileSync(githubEnv, "utf8")).toBe(
|
||||||
|
`OPENCLAW_VITEST_FS_MODULE_CACHE_ROOT=${cache}\nOPENCLAW_VITEST_FS_MODULE_CACHE_WRITER=0\n`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
Loading…
Add table
Add a link
Reference in a new issue