mirror of
https://github.com/aphrody-labs/vu.git
synced 2026-10-14 12:19:18 +00:00
154 lines
6.1 KiB
TypeScript
154 lines
6.1 KiB
TypeScript
// SPDX-License-Identifier: Apache-2.0
|
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
import { existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
import { homedir, tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
// End-to-end tests of the installed `vu` binary (the native subcommands). They run against
|
|
// $VU_TEST_ARTIFACT (default: the active runtime under ~/.vu) and skip when it predates the subcommands.
|
|
const prefix =
|
|
process.env["VU_TEST_ARTIFACT"] ??
|
|
join(homedir(), ".vu/runtime/x86_64-unknown-linux-gnu/current");
|
|
const vu = join(prefix, "bin/vu");
|
|
|
|
function run(args: string[], options: { cwd?: string; env?: Record<string, string> } = {}) {
|
|
const result = Bun.spawnSync([vu, ...args], {
|
|
cwd: options.cwd,
|
|
env: { ...process.env, ...options.env },
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
return { code: result.exitCode, out: result.stdout.toString(), err: result.stderr.toString() };
|
|
}
|
|
|
|
const current = existsSync(vu) && run(["--help"]).out.includes("python install");
|
|
let scratch = "";
|
|
beforeAll(() => {
|
|
scratch = mkdtempSync(join(tmpdir(), "vu-cli-"));
|
|
});
|
|
afterAll(() => rmSync(scratch, { recursive: true, force: true }));
|
|
|
|
describe.skipIf(!current)("vu native subcommands", () => {
|
|
test("help documents every command family", () => {
|
|
const { out } = run(["--help"]);
|
|
for (const word of [
|
|
"python install",
|
|
"lint",
|
|
"format",
|
|
"ffi",
|
|
"hf",
|
|
"compile",
|
|
"add remove sync",
|
|
])
|
|
expect(out).toContain(word);
|
|
});
|
|
|
|
test("unknown commands fail with a usage status", () => {
|
|
expect(run(["nope"]).code).toBe(64);
|
|
});
|
|
|
|
test("ffi reports the single shared libpython", () => {
|
|
const { code, out } = run(["ffi", "info", "--json"]);
|
|
expect(code).toBe(0);
|
|
const info = JSON.parse(out);
|
|
expect(info.schema).toBe("aphrody.vu-ffi/1");
|
|
expect(info.libraries).toBe(1);
|
|
expect(info.dlopen).toBe("RTLD_NOW|RTLD_GLOBAL");
|
|
expect(existsSync(info.libpython)).toBe(true);
|
|
expect(info.pythonhome).toBe(info.prefix);
|
|
expect(run(["ffi", "check"]).code).toBe(0);
|
|
});
|
|
|
|
test("hf status reads the standard variables and never prints the token", () => {
|
|
const home = join(scratch, "hf");
|
|
const { code, out } = run(["hf", "status", "--json"], {
|
|
env: { HF_HOME: home, HF_TOKEN: "hf_secret_value" },
|
|
});
|
|
expect(code).toBe(0);
|
|
expect(out).not.toContain("hf_secret_value");
|
|
const report = JSON.parse(out);
|
|
expect(report.hubCache).toBe(join(home, "hub"));
|
|
expect(report.token).toEqual({ present: true, source: "HF_TOKEN" });
|
|
});
|
|
|
|
test("compile byte-compiles a tree into unchecked-hash caches", () => {
|
|
const tree = join(scratch, "tree");
|
|
mkdirSync(tree, { recursive: true });
|
|
writeFileSync(join(tree, "m.py"), "VALUE = 1\n");
|
|
expect(run(["compile", tree]).code).toBe(0);
|
|
expect(readdirSync(join(tree, "__pycache__")).some((f) => f.endsWith(".pyc"))).toBe(true);
|
|
});
|
|
|
|
test("lint and format run the pinned ruff", () => {
|
|
const dir = join(scratch, "lint");
|
|
mkdirSync(dir, { recursive: true });
|
|
writeFileSync(join(dir, "ok.py"), "x = 1\n");
|
|
writeFileSync(join(dir, "bad.py"), "import os\n");
|
|
expect(run(["lint", "ok.py"], { cwd: dir }).code).toBe(0);
|
|
expect(run(["lint", "bad.py"], { cwd: dir }).code).toBe(1);
|
|
expect(run(["format", "--check", "ok.py"], { cwd: dir }).code).toBe(0);
|
|
});
|
|
|
|
test("python runs the embedded interpreter when the project pins its minor or nothing", () => {
|
|
const dir = join(scratch, "pin312");
|
|
mkdirSync(dir, { recursive: true });
|
|
const script = "import sys; print(sys.version_info[0], sys.version_info[1])";
|
|
const plain = run(["python", "-c", script], { cwd: dir });
|
|
expect(plain.out.trim()).toBe("3 12");
|
|
writeFileSync(join(dir, ".python-version"), "3.12\n");
|
|
expect(run(["python", "-c", script], { cwd: dir }).out.trim()).toBe("3 12");
|
|
});
|
|
|
|
test("python list names the runtime's interpreter first", () => {
|
|
const home = join(scratch, "home-list");
|
|
const { code, out } = run(["python", "list"], { env: { VU_HOME: home } });
|
|
expect(code).toBe(0);
|
|
expect(out.split("\n")[0]).toContain("runtime, embedded");
|
|
});
|
|
});
|
|
|
|
// Network: installs CPython 3.14 from the pinned python-build-standalone release metadata of uv.
|
|
describe.skipIf(!current || process.env["VU_TEST_NETWORK"] !== "1")(
|
|
"vu python version manager",
|
|
() => {
|
|
const home = () => join(scratch, "home-manager");
|
|
test("install, use and per-project selection of 3.14", () => {
|
|
const env = { VU_HOME: home() };
|
|
const install = run(["python", "install", "3.14"], { env });
|
|
expect(install.code).toBe(0);
|
|
expect(run(["python", "list"], { env }).out).toContain("cpython-3.14");
|
|
expect(existsSync(join(home(), "python"))).toBe(true);
|
|
|
|
const project = join(scratch, "project314");
|
|
mkdirSync(project, { recursive: true });
|
|
const used = run(["python", "use", "3.14"], { cwd: project, env });
|
|
expect(used.code).toBe(0);
|
|
expect(Bun.file(join(project, ".python-version")).size).toBeGreaterThan(0);
|
|
const script = "import sys; print(sys.version_info[0], sys.version_info[1])";
|
|
expect(run(["python", "-c", script], { cwd: project, env }).out.trim()).toBe("3 14");
|
|
expect(
|
|
run(["python", "-c", script], {
|
|
cwd: project,
|
|
env: { ...env, VU_PYTHON: "3.12" },
|
|
}).out.trim(),
|
|
).toBe("3 12");
|
|
expect(run(["python", "find", "3.14"], { cwd: project, env }).out.trim()).toContain(
|
|
"python3.14",
|
|
);
|
|
});
|
|
|
|
test("a project that asks for a missing version gets it installed on demand", () => {
|
|
const env = { VU_HOME: join(scratch, "home-auto") };
|
|
const project = join(scratch, "projectauto");
|
|
mkdirSync(project, { recursive: true });
|
|
writeFileSync(join(project, ".python-version"), "3.11\n");
|
|
const result = run(["python", "-c", "import sys; print(sys.version_info[1])"], {
|
|
cwd: project,
|
|
env,
|
|
});
|
|
expect(result.out.trim()).toBe("11");
|
|
expect(result.err).toContain("installing Python 3.11");
|
|
});
|
|
},
|
|
);
|