test(cli): CLI tests run with a home folder of their own, never the user's (#4587)

* test(cli): CLI tests run with a home folder of their own, never the user's

* style(cli): format the test home check

* test(cli): the skills test lane also runs with a home folder of its own

* style(cli): format the test home check

* ci: the test reachability map follows the skills lane's new command
This commit is contained in:
Miguel Ángel
2026-09-27 12:30:03 +00:00
committed by GitHub
parent c9b3d9c962
commit 2b1bb8b731
8 changed files with 83 additions and 5 deletions
+1 -1
View File
@@ -560,7 +560,7 @@ jobs:
SKILLS_TESTS+=(scripts/check-media-use-copy-parity.test.mjs scripts/creator-editing-capabilities.test.mjs)
printf 'Running %d skills test file(s):\n' "${#SKILLS_TESTS[@]}"
printf ' * %s\n' "${SKILLS_TESTS[@]}"
node --test "${SKILLS_TESTS[@]}"
node --import ./packages/cli/scripts/test-home.mjs --test "${SKILLS_TESTS[@]}"
test-skill-bootstrap-windows:
name: "Test: skill bootstrap (Windows, Node 24)"
+1 -1
View File
@@ -54,7 +54,7 @@
"knip": "knip",
"test:scripts": "node --import tsx --test scripts/animejs-v4-guidance.test.mjs scripts/check-tracked-artifacts.test.mjs scripts/check-registry-set-delta.test.mjs scripts/check-no-main-deletions.test.mjs scripts/check-pr-captures.test.mjs scripts/check-comment-citations.test.mjs scripts/comments-workflow.test.mjs scripts/comment-ratchet.test.mjs scripts/check-docs-snippet-motion.test.mjs scripts/registry-target-paths.test.mjs scripts/check-workspace-contracts.test.mjs scripts/check-media-use-copy-parity.test.mjs scripts/check-svg-sanitize-parity.test.mjs scripts/check-media-use-svg-sanitize-generated.test.mjs scripts/check-package-cycles.test.mjs scripts/check-cli-process-ownership.test.mjs scripts/check-large-files.test.mjs scripts/prune-fallow-audit-caches.test.mjs scripts/package-subpaths.test.mjs scripts/validate-release-channel.test.mjs scripts/publish-workflow.test.mjs scripts/pr-edit-concurrency.test.mjs scripts/merge-queue-workflows.test.mjs scripts/install-workspace-dependencies.test.mjs scripts/draft-changelog.test.ts scripts/set-version.test.ts scripts/release-prepare.test.ts scripts/cli-options.test.ts scripts/changelog-weekly.test.ts scripts/claude-plugin-compression.test.ts scripts/catalog-payload-assets.test.ts scripts/host-registry-assets.test.ts scripts/catalog-preview-temp.test.ts scripts/catalog-hosted-files.test.ts scripts/player-cdn-pin.test.ts scripts/studio-runtime-smoke.test.mjs scripts/verify-packed-manifests.test.mjs scripts/lint-skills.test.mjs scripts/creator-editing-recipes.test.mjs packages/gcp-cloud-run/check-dockerfile-workspaces.test.mjs packages/core/scripts/writeGeneratedFile.test.ts scripts/catalog-publication.test.mjs scripts/ci/resolve-workflow-pr.test.mjs scripts/check-catalog-source-pr.test.mjs scripts/generate-registry-items.test.ts scripts/catalog-drift.test.ts scripts/catalog-fetch-mirror.test.ts scripts/catalog-script-inlining.test.ts scripts/catalog-detail.test.ts scripts/generate-catalog-pages.test.ts scripts/verify-catalog-payloads.test.ts scripts/registry-skill-files.test.ts scripts/creator-editing-capabilities.test.mjs scripts/generate-catalog-previews.test.ts scripts/registry-primitive-payloads.test.ts registry/components/pan-stations/pan-stations.test.mjs && vitest run scripts/catalog/ scripts/contrastRatchet.test.ts scripts/generate-catalog-payloads.test.ts",
"typecheck:scripts": "tsc --noEmit -p scripts/tsconfig.json",
"test:skills": "HYPERFRAMES_MEDIA_HOME_REQUIRED=1 node --test 'skills/**/*.test.mjs' 'packages/cli/src/media-use/**/*.test.mjs'",
"test:skills": "HYPERFRAMES_MEDIA_HOME_REQUIRED=1 node --import ./packages/cli/scripts/test-home.mjs --test 'skills/**/*.test.mjs' 'packages/cli/src/media-use/**/*.test.mjs'",
"generate:previews": "tsx scripts/generate-template-previews.ts",
"generate:catalog-previews": "tsx scripts/generate-catalog-previews.ts",
"package:codex-plugin": "node scripts/package-codex-plugin.mjs",
+24
View File
@@ -0,0 +1,24 @@
// Loaded before any CLI test module: the cache, config and state paths the CLI builds from the home
// folder, or from these variables, land in a temp dir removed on exit, never in the user's own.
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
const home = mkdtempSync(join(tmpdir(), "hf-test-home-"));
process.once("exit", () => rmSync(home, { recursive: true, force: true }));
process.env.HOME = process.env.USERPROFILE = home;
for (const name of [
"XDG_CACHE_HOME",
"XDG_CONFIG_HOME",
"XDG_STATE_HOME",
"XDG_DATA_HOME",
"CODEX_HOME",
"CLAUDE_CONFIG_DIR",
"HEYGEN_CONFIG_DIR",
"HYPERFRAMES_CATALOG_ARTIFACT_DIR",
"HYPERFRAMES_MEDIA_HOME",
"HF_HOME",
"HUGGINGFACE_HUB_CACHE",
]) {
delete process.env[name];
}
@@ -0,0 +1,10 @@
import assert from "node:assert/strict";
import { realpathSync } from "node:fs";
import { homedir, tmpdir, userInfo } from "node:os";
import { sep } from "node:path";
import { test } from "node:test";
test("the skills test run has a home folder of its own, not the user's", () => {
assert.ok(realpathSync(homedir()).startsWith(realpathSync(tmpdir()) + sep));
assert.notEqual(homedir(), userInfo().homedir);
});
+3 -2
View File
@@ -1,13 +1,14 @@
import { describe, expect, it } from "vitest";
import { existsSync, readFileSync } from "node:fs";
import { homedir } from "node:os";
import { userInfo } from "node:os";
import { join } from "node:path";
import { configFromTokenizerJson, encode, normalize, preTokenize, wordPiece } from "./wordpiece.js";
const REFERENCE_PATH = join(import.meta.dirname, "__fixtures__", "wordpiece-reference.json");
// Read-only from the user's real home: tests run with a temp one, and the model is downloaded there.
const TOKENIZER_PATH = join(
homedir(),
userInfo().homedir,
".hyperframes",
"models",
"bge-small-en-v1.5.tokenizer.json",
+41
View File
@@ -0,0 +1,41 @@
import { existsSync, realpathSync, rmSync } from "node:fs";
import { homedir, tmpdir, userInfo } from "node:os";
import { join, sep } from "node:path";
import { afterEach, describe, expect, it, vi } from "vitest";
import { fetchRegistryManifest } from "./registry/remote.js";
afterEach(() => {
vi.unstubAllGlobals();
});
describe("the CLI test run", () => {
it("has a home folder of its own, not the user's", () => {
expect(realpathSync(homedir()).startsWith(realpathSync(tmpdir()) + sep)).toBe(true);
expect(homedir()).not.toBe(userInfo().homedir);
});
it("caches a registry read in that home", async () => {
const registry = `https://test.invalid/${crypto.randomUUID()}`;
const items: unknown[] = [];
const $schema = "https://hyperframes.heygen.com/schema/registry.json";
const manifest = { $schema, name: "t", homepage: "https://example.com", items };
vi.stubGlobal(
"fetch",
vi.fn(async (input: string | URL) =>
String(input) === `${registry}/registry.json`
? new Response(JSON.stringify(manifest))
: new Response("not found", { status: 404 }),
),
);
await fetchRegistryManifest(registry);
const slug = registry.replace(/[^a-zA-Z0-9]/g, "_");
const cacheFile = (home: string) =>
join(home, ".hyperframes", "cache", `${slug}__registry.json`);
const leaked = existsSync(cacheFile(userInfo().homedir));
rmSync(cacheFile(userInfo().homedir), { force: true });
expect(existsSync(cacheFile(homedir()))).toBe(true);
expect(leaked).toBe(false);
});
});
+2
View File
@@ -2,6 +2,8 @@ import { copyFileSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { defineConfig } from "vitest/config";
// Before workers fork, as below: the test run gets a home folder of its own.
import "./scripts/test-home.mjs";
// Windows: sharp's first text render builds Fontconfig's cache for every OS font (about 9 s on a
// fresh runner). Set here, before workers fork, because an in-process env write never reaches it.
+1 -1
View File
@@ -41,7 +41,7 @@
"runners": [
{
"cwd": ".",
"command": "set -euo pipefail\nmapfile -t SKILLS_TESTS < <(find skills packages/cli/src/media-use -type f -name \"*.test.mjs\" | sort)\nif [ \"${#SKILLS_TESTS[@]}\" -eq 0 ]; then\necho \"::error::No skills/**/*.test.mjs files found. Did the layout change?\"\nexit 1\nfi\nSKILLS_TESTS+=(scripts/check-media-use-copy-parity.test.mjs scripts/creator-editing-capabilities.test.mjs)\nprintf 'Running %d skills test file(s):\\n' \"${#SKILLS_TESTS[@]}\"\nprintf ' * %s\\n' \"${SKILLS_TESTS[@]}\"\nnode --test \"${SKILLS_TESTS[@]}\"",
"command": "set -euo pipefail\nmapfile -t SKILLS_TESTS < <(find skills packages/cli/src/media-use -type f -name \"*.test.mjs\" | sort)\nif [ \"${#SKILLS_TESTS[@]}\" -eq 0 ]; then\necho \"::error::No skills/**/*.test.mjs files found. Did the layout change?\"\nexit 1\nfi\nSKILLS_TESTS+=(scripts/check-media-use-copy-parity.test.mjs scripts/creator-editing-capabilities.test.mjs)\nprintf 'Running %d skills test file(s):\\n' \"${#SKILLS_TESTS[@]}\"\nprintf ' * %s\\n' \"${SKILLS_TESTS[@]}\"\nnode --import ./packages/cli/scripts/test-home.mjs --test \"${SKILLS_TESTS[@]}\"",
"tests": [
"skills/**/*.test.mjs",
"packages/cli/src/media-use/**/*.test.mjs",