Files
setup-uv/__tests__/utils/python-runtime.test.ts
William Woodruff f634bf473a Expose a Python "identity" output (#1036)
https://github.com/pyca/cryptography/pull/15572#discussion_r3913508686
has the context for this: TL;DR our current `python-version` output
mirrors the "request" version exactly, which means that it's
insufficient for any downstream that needs to manage its own cache keys
(since caches shouldn't be shared across release candidates, but the `uv
python` request version doesn't include RC numbers).

The first commit here was my attempt to fix this by exposing the runtime
Python version, but this too is imprecise: the runtime version doesn't
indicate the interpreter variant (e.g. freethreading), which is also
important to capture in the cache identity.

My solution here is to expose `python-runtime-id`, which is just the
`key` of the active Python version from `uv python list
--output-format=json`.

---------

Signed-off-by: William Woodruff <william@yossarian.net>
2026-09-09 18:45:52 +02:00

87 lines
2.4 KiB
TypeScript

import { promisify } from "node:util";
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 inputs = createSetupInputs({
activateEnvironment: true,
pythonVersion: "3.15t",
});
jest.unstable_mockModule("node:child_process", () => ({
// execFile's custom promisifier returns both stdout and stderr.
execFile: Object.assign(mockExecFile, { [promisify.custom]: mockExecFile }),
}));
const { getPythonRuntimeId } = await import("../../src/utils/python-runtime");
beforeEach(() => {
mockExecFile.mockReset();
mockExecFile.mockResolvedValue({
stderr: "",
stdout: '[{"key":"cpython-3.13.1-linux-x86_64-gnu"}]\r\n',
});
});
it("does not query uv without environment activation", async () => {
expect(
await getPythonRuntimeId({ ...inputs, activateEnvironment: false }),
).toBe("");
expect(mockExecFile).not.toHaveBeenCalled();
});
it.each([
"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(key);
});
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("uv failed"),
"not JSON",
"null",
"{}",
"[]",
'[{"key":""}]',
'[{"key":123}]',
'[{"key":"first"},{"key":"second"}]',
])("rejects uv failure or invalid results: %s", async (result) => {
if (result instanceof Error) {
mockExecFile.mockRejectedValue(result);
} else {
mockExecFile.mockResolvedValue({ stderr: "", stdout: result });
}
await expect(getPythonRuntimeId(inputs)).rejects.toThrow(
"Failed to identify the activated environment's Python runtime:",
);
});