Use uv Python keys for runtime IDs

This commit is contained in:
William Woodruff
2026-09-03 16:15:42 -04:00
parent 358b824344
commit 22b0154feb
7 changed files with 88 additions and 194 deletions
+39 -69
View File
@@ -1,13 +1,11 @@
import { join } from "node:path";
import { promisify } from "node:util";
import { afterEach, beforeEach, expect, it, jest } from "@jest/globals";
import { beforeEach, expect, it, jest } from "@jest/globals";
import { createSetupInputs } from "../helpers/setup-inputs";
const mockExecFile =
jest.fn<
(...args: unknown[]) => Promise<{ stdout: string; stderr: string }>
>();
const originalPlatform = process.platform;
const inputs = createSetupInputs({
activateEnvironment: true,
pythonVersion: "3.15t",
@@ -20,28 +18,15 @@ jest.unstable_mockModule("node:child_process", () => ({
const { getPythonRuntimeId } = await import("../../src/utils/python-runtime");
function mockRuntime(overrides: Record<string, unknown> = {}) {
mockExecFile.mockResolvedValue({
stderr: "",
stdout: `${JSON.stringify({
freethreaded: false,
implementation: "cpython",
implementationVersion: [3, 13, 1, "final", 0],
pythonVersion: "3.13.1",
...overrides,
})}\r\n`,
});
}
beforeEach(() => {
mockExecFile.mockReset();
mockRuntime();
});
afterEach(() => {
Object.defineProperty(process, "platform", { value: originalPlatform });
mockExecFile.mockResolvedValue({
stderr: "",
stdout: '[{"key":"cpython-3.13.1-linux-x86_64-gnu"}]\r\n',
});
});
it("does not query Python without environment activation", async () => {
it("does not query uv without environment activation", async () => {
expect(
await getPythonRuntimeId({ ...inputs, activateEnvironment: false }),
).toBe("");
@@ -49,62 +34,47 @@ it("does not query Python without environment activation", async () => {
});
it.each([
["3.13.1", false, "cpython-3.13.1"],
["3.15.0a1", false, "cpython-3.15.0a1"],
["3.15.0b2", false, "cpython-3.15.0b2"],
["3.15.0rc1", false, "cpython-3.15.0rc1"],
["3.15.0rc1", true, "cpython-3.15.0rc1-freethreaded"],
["3.15.0rc2", true, "cpython-3.15.0rc2-freethreaded"],
["3.15.0", true, "cpython-3.15.0-freethreaded"],
])("formats CPython %s, free-threading=%s", async (version, free, expected) => {
mockRuntime({ freethreaded: free, pythonVersion: version });
expect(await getPythonRuntimeId(inputs)).toBe(expected);
});
it.each([
["pypy", [7, 3, 23, "final", 0], "pypy-7.3.23"],
["pypy", [7, 3, 24, "final", 0], "pypy-7.3.24"],
["pypy", [7, 3, 24, "alpha", 1], "pypy-7.3.24a1"],
["pypy", [7, 3, 24, "beta", 2], "pypy-7.3.24b2"],
["pypy", [7, 3, 24, "candidate", 3], "pypy-7.3.24rc3"],
["graalpy", [25, 0, 0, "final", 0], "graalpy-25.0.0"],
])("formats %s implementation version %j", async (name, version, expected) => {
mockRuntime({
implementation: name,
implementationVersion: version,
pythonVersion: "3.11.15",
"cpython-3.13.1-linux-x86_64-gnu",
"cpython-3.15.0rc1+freethreaded-macos-aarch64-none",
"cpython-3.15.0rc2+freethreaded-windows-x86_64-none",
"pypy-3.11.15-linux-x86_64-gnu",
])("returns uv's opaque runtime key unchanged: %s", async (key) => {
mockExecFile.mockResolvedValue({
stderr: "",
stdout: `${JSON.stringify([{ key }])}\r\n`,
});
expect(await getPythonRuntimeId(inputs)).toBe(`${expected}-python-3.11.15`);
expect(await getPythonRuntimeId(inputs)).toBe(key);
});
it.each([
["linux", 'a "quoted" venv', "bin/python"],
["darwin", "custom venv", "bin/python"],
["win32", "custom venv", "Scripts/python.exe"],
])("passes the venv executable directly on %s", async (platform, name, exe) => {
Object.defineProperty(process, "platform", { value: platform });
const venvPath = join("/runner temp", name);
await getPythonRuntimeId({ ...inputs, venvPath });
expect(mockExecFile).toHaveBeenCalledWith(
join(venvPath, exe),
["-I", "-c", expect.any(String)],
{ encoding: "utf8" },
);
});
it.each(['/runner temp/a "quoted" venv', "C:\\runner temp\\custom venv"])(
"queries the exact venv directory: %s",
async (venvPath) => {
await getPythonRuntimeId({ ...inputs, venvPath });
expect(mockExecFile).toHaveBeenCalledWith(
"uv",
[
"python",
"list",
venvPath,
"--only-installed",
"--output-format",
"json",
],
{ encoding: "utf8" },
);
},
);
it.each([
new Error("interpreter missing"),
"",
new Error("uv failed"),
"not JSON",
"null",
"{}",
JSON.stringify({
freethreaded: false,
implementation: "pypy",
implementationVersion: [7, 3, 24, "unknown", 0],
pythonVersion: "3.11.15",
}),
])("rejects interpreter failure or invalid metadata: %s", async (result) => {
"[]",
'[{"key":""}]',
'[{"key":123}]',
'[{"key":"first"},{"key":"second"}]',
])("rejects uv failure or invalid results: %s", async (result) => {
if (result instanceof Error) {
mockExecFile.mockRejectedValue(result);
} else {