Files
setup-uv/__tests__/version/tool-versions-file.test.ts
T
Kevin Stillhammer 46f427bd47 Read Python version from .tool-versions (#996)
## Summary
- read the Python version from an explicitly selected `.tool-versions`
file
- preserve `python-version` and existing `UV_PYTHON` precedence
- add parser, input, and workflow coverage and update documentation and
bundled action artifacts

## Validation
- `npm run all`
- `actionlint .github/workflows/test.yml`
- `uvx zizmor .github/workflows/test.yml`

Closes #983

Refs: pi-session 019ff01a-544c-79f3-8f73-a00132af39f5
2026-08-11 14:26:03 +02:00

194 lines
5.9 KiB
TypeScript

import { beforeEach, describe, expect, it, jest } from "@jest/globals";
const mockReadFileSync = jest.fn();
const mockWarning = jest.fn();
jest.unstable_mockModule("node:fs", () => ({
default: {
readFileSync: mockReadFileSync,
},
}));
jest.unstable_mockModule("@actions/core", () => ({
warning: mockWarning,
}));
async function getVersionFromToolVersions(filePath: string) {
const { getUvVersionFromToolVersions } = await import(
"../../src/version/tool-versions-file"
);
return getUvVersionFromToolVersions(filePath);
}
async function getPythonVersionFromToolVersions(filePath: string) {
const module = await import("../../src/version/tool-versions-file");
return module.getPythonVersionFromToolVersions(filePath);
}
describe("getUvVersionFromToolVersions", () => {
beforeEach(() => {
jest.resetModules();
jest.clearAllMocks();
});
it("should return undefined for non-.tool-versions files", async () => {
const result = await getVersionFromToolVersions("package.json");
expect(result).toBeUndefined();
expect(mockReadFileSync).not.toHaveBeenCalled();
});
it("should return version for valid uv entry", async () => {
const fileContent = "python 3.11.0\nuv 0.1.0\nnodejs 18.0.0";
mockReadFileSync.mockReturnValue(fileContent);
const result = await getVersionFromToolVersions(".tool-versions");
expect(result).toBe("0.1.0");
expect(mockReadFileSync).toHaveBeenCalledWith(".tool-versions", "utf8");
});
it("should return version for uv entry with v prefix", async () => {
const fileContent = "uv v0.2.0";
mockReadFileSync.mockReturnValue(fileContent);
const result = await getVersionFromToolVersions(".tool-versions");
expect(result).toBe("0.2.0");
});
it("should handle whitespace around uv entry", async () => {
const fileContent = " uv 0.3.0 ";
mockReadFileSync.mockReturnValue(fileContent);
const result = await getVersionFromToolVersions(".tool-versions");
expect(result).toBe("0.3.0");
});
it("should skip comments", async () => {
const fileContent = "# uv 0.1.0\npython 3.11.0\nuv 0.2.0 # inline comment";
mockReadFileSync.mockReturnValue(fileContent);
const result = await getVersionFromToolVersions(".tool-versions");
expect(result).toBe("0.2.0");
});
it("should return first matching uv version", async () => {
const fileContent = "uv 0.1.0\npython 3.11.0\nuv 0.2.0";
mockReadFileSync.mockReturnValue(fileContent);
const result = await getVersionFromToolVersions(".tool-versions");
expect(result).toBe("0.1.0");
});
it("should return undefined when no uv entry found", async () => {
const fileContent = "python 3.11.0\nnodejs 18.0.0";
mockReadFileSync.mockReturnValue(fileContent);
const result = await getVersionFromToolVersions(".tool-versions");
expect(result).toBeUndefined();
});
it("should return undefined for empty file", async () => {
mockReadFileSync.mockReturnValue("");
const result = await getVersionFromToolVersions(".tool-versions");
expect(result).toBeUndefined();
});
it("should warn and return undefined for ref syntax", async () => {
const fileContent = "uv ref:main";
mockReadFileSync.mockReturnValue(fileContent);
const result = await getVersionFromToolVersions(".tool-versions");
expect(result).toBeUndefined();
expect(mockWarning).toHaveBeenCalledWith(
"The ref syntax of .tool-versions is not supported. Please use a released version instead.",
);
});
it("should handle file path with .tool-versions extension", async () => {
const fileContent = "uv 0.1.0";
mockReadFileSync.mockReturnValue(fileContent);
const result = await getVersionFromToolVersions("path/to/.tool-versions");
expect(result).toBe("0.1.0");
expect(mockReadFileSync).toHaveBeenCalledWith(
"path/to/.tool-versions",
"utf8",
);
});
});
describe("getPythonVersionFromToolVersions", () => {
beforeEach(() => {
jest.resetModules();
jest.clearAllMocks();
});
it("should return version for a valid Python entry", async () => {
mockReadFileSync.mockReturnValue(
"nodejs 24.0.0\r\npython v3.13.1t # use free-threaded Python\r\nuv 0.12.3",
);
const result = await getPythonVersionFromToolVersions(".tool-versions");
expect(result).toBe("3.13.1t");
});
it("should return the first matching Python version", async () => {
mockReadFileSync.mockReturnValue("python 3.12\npython 3.13");
const result = await getPythonVersionFromToolVersions(".tool-versions");
expect(result).toBe("3.12");
});
it("should return undefined when no Python entry is found", async () => {
mockReadFileSync.mockReturnValue("uv 0.12.3\nnodejs 24.0.0");
const result = await getPythonVersionFromToolVersions(".tool-versions");
expect(result).toBeUndefined();
});
it("should warn and return undefined for multiple Python versions", async () => {
mockReadFileSync.mockReturnValue("python 3.13 3.12 system");
const result = await getPythonVersionFromToolVersions(".tool-versions");
expect(result).toBeUndefined();
expect(mockWarning).toHaveBeenCalledWith(
"Multiple Python versions in .tool-versions are not supported. The Python entry will be ignored.",
);
});
it.each(["ref:main", "path:~/src/python", "system"])(
"should warn and return undefined for %s",
async (version) => {
mockReadFileSync.mockReturnValue(`python ${version}`);
const result = await getPythonVersionFromToolVersions(".tool-versions");
expect(result).toBeUndefined();
expect(mockWarning).toHaveBeenCalledWith(
`The Python version ${version} in .tool-versions is not supported. The Python entry will be ignored.`,
);
},
);
it("should return undefined for non-.tool-versions files", async () => {
const result = await getPythonVersionFromToolVersions(".python-version");
expect(result).toBeUndefined();
expect(mockReadFileSync).not.toHaveBeenCalled();
});
});