Fix cache keys for Python version ranges (#937)

## Summary
- URL-encode the Python version component before adding it to the cache
key
- URL-encode the user-provided cache suffix for the same reason
- Add cache key tests for Python ranges, comma-containing suffixes, and
unchanged simple inputs

Fixes #914

Refs: pi-session 019f3164-85e7-7817-bffd-501d89b3a1fd

## Tests
- npm run all
This commit is contained in:
Kevin Stillhammer
2026-07-05 10:46:44 +02:00
committed by GitHub
co-authored by GitHub
parent 3cc3c11fdf
commit 17c398959b
4 changed files with 116 additions and 4 deletions
+74
View File
@@ -0,0 +1,74 @@
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
import { createSetupInputs } from "../helpers/setup-inputs";
const mockRestoreCache = jest.fn();
const mockSaveState = jest.fn();
const mockSetOutput = jest.fn();
jest.unstable_mockModule("@actions/cache", () => ({
restoreCache: mockRestoreCache,
}));
jest.unstable_mockModule("@actions/core", () => ({
saveState: mockSaveState,
setOutput: mockSetOutput,
}));
jest.unstable_mockModule("../../src/hash/hash-files", () => ({
hashFiles: jest.fn(async () => "dependencyhash"),
}));
jest.unstable_mockModule("../../src/utils/logging", () => ({
info: jest.fn(),
warning: jest.fn(),
}));
jest.unstable_mockModule("../../src/utils/platforms", () => ({
getArch: jest.fn(() => "x86_64"),
getOSNameVersion: jest.fn(() => "ubuntu-24.04"),
getPlatform: jest.fn(async () => "unknown-linux-gnu"),
}));
const { restoreCache } = await import("../../src/cache/restore-cache");
function cacheKeyOutput(): string {
const call = mockSetOutput.mock.calls.find(([name]) => name === "cache-key");
expect(call).toBeDefined();
return call?.[1] as string;
}
beforeEach(() => {
jest.clearAllMocks();
});
describe("restoreCache", () => {
it("encodes Python version ranges before adding them to the cache key", async () => {
await restoreCache(createSetupInputs(), ">3.10.11,<3.11");
const cacheKey = cacheKeyOutput();
expect(cacheKey).not.toContain(",");
expect(cacheKey).toContain("-%3E3.10.11%2C%3C3.11-");
});
it("encodes cache suffixes before adding them to the cache key", async () => {
const inputs = createSetupInputs({ cacheSuffix: "tests-3.10,3.11" });
await restoreCache(inputs, "3.11");
const cacheKey = cacheKeyOutput();
expect(cacheKey).not.toContain(",");
expect(cacheKey).toContain("-tests-3.10%2C3.11");
});
it("keeps cache keys unchanged for exact Python versions and simple suffixes", async () => {
const inputs = createSetupInputs({ cacheSuffix: "tests-3.11" });
await restoreCache(inputs, "3.11");
expect(cacheKeyOutput()).toBe(
"setup-uv-2-x86_64-unknown-linux-gnu-ubuntu-24.04-3.11-pruned-dependencyhash-tests-3.11",
);
});
});
+36
View File
@@ -0,0 +1,36 @@
import { CacheLocalSource, type SetupInputs } from "../../src/utils/inputs";
export function createSetupInputs(
overrides: Partial<SetupInputs> = {},
): SetupInputs {
return {
activateEnvironment: false,
addProblemMatchers: false,
cacheDependencyGlob: "uv.lock",
cacheLocalPath: {
path: "/tmp/setup-uv-cache",
source: CacheLocalSource.Input,
},
cachePython: false,
cacheSuffix: "",
checksum: "",
downloadFromAstralMirror: false,
enableCache: true,
githubToken: "",
ignoreEmptyWorkdir: false,
ignoreNothingToCache: false,
noProject: false,
pruneCache: true,
pythonDir: "/tmp/uv-python-dir",
pythonVersion: "",
quiet: false,
resolutionStrategy: "highest",
restoreCache: false,
saveCache: true,
venvPath: "/workspace/.venv",
version: "",
versionFile: "",
workingDirectory: "/workspace",
...overrides,
};
}
Generated Vendored
+2 -2
View File
@@ -90621,8 +90621,8 @@ async function computeKeys(inputs, pythonVersion) {
if (cacheDependencyPathHash === "-") {
cacheDependencyPathHash = "-no-dependency-glob";
}
const suffix = inputs.cacheSuffix ? `-${inputs.cacheSuffix}` : "";
const version3 = pythonVersion ?? "unknown";
const suffix = inputs.cacheSuffix ? `-${encodeURIComponent(inputs.cacheSuffix)}` : "";
const version3 = encodeURIComponent(pythonVersion ?? "unknown");
const platform2 = await getPlatform();
const osNameVersion = getOSNameVersion();
const pruned = inputs.pruneCache ? "-pruned" : "";
+4 -2
View File
@@ -94,8 +94,10 @@ async function computeKeys(
if (cacheDependencyPathHash === "-") {
cacheDependencyPathHash = "-no-dependency-glob";
}
const suffix = inputs.cacheSuffix ? `-${inputs.cacheSuffix}` : "";
const version = pythonVersion ?? "unknown";
const suffix = inputs.cacheSuffix
? `-${encodeURIComponent(inputs.cacheSuffix)}`
: "";
const version = encodeURIComponent(pythonVersion ?? "unknown");
const platform = await getPlatform();
const osNameVersion = getOSNameVersion();
const pruned = inputs.pruneCache ? "-pruned" : "";