Skip to content

Commit 17c3989

Browse files
authored
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
1 parent 3cc3c11 commit 17c3989

4 files changed

Lines changed: 116 additions & 4 deletions

File tree

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
2+
import { createSetupInputs } from "../helpers/setup-inputs";
3+
4+
const mockRestoreCache = jest.fn();
5+
const mockSaveState = jest.fn();
6+
const mockSetOutput = jest.fn();
7+
8+
jest.unstable_mockModule("@actions/cache", () => ({
9+
restoreCache: mockRestoreCache,
10+
}));
11+
12+
jest.unstable_mockModule("@actions/core", () => ({
13+
saveState: mockSaveState,
14+
setOutput: mockSetOutput,
15+
}));
16+
17+
jest.unstable_mockModule("../../src/hash/hash-files", () => ({
18+
hashFiles: jest.fn(async () => "dependencyhash"),
19+
}));
20+
21+
jest.unstable_mockModule("../../src/utils/logging", () => ({
22+
info: jest.fn(),
23+
warning: jest.fn(),
24+
}));
25+
26+
jest.unstable_mockModule("../../src/utils/platforms", () => ({
27+
getArch: jest.fn(() => "x86_64"),
28+
getOSNameVersion: jest.fn(() => "ubuntu-24.04"),
29+
getPlatform: jest.fn(async () => "unknown-linux-gnu"),
30+
}));
31+
32+
const { restoreCache } = await import("../../src/cache/restore-cache");
33+
34+
function cacheKeyOutput(): string {
35+
const call = mockSetOutput.mock.calls.find(([name]) => name === "cache-key");
36+
expect(call).toBeDefined();
37+
return call?.[1] as string;
38+
}
39+
40+
beforeEach(() => {
41+
jest.clearAllMocks();
42+
});
43+
44+
describe("restoreCache", () => {
45+
it("encodes Python version ranges before adding them to the cache key", async () => {
46+
await restoreCache(createSetupInputs(), ">3.10.11,<3.11");
47+
48+
const cacheKey = cacheKeyOutput();
49+
50+
expect(cacheKey).not.toContain(",");
51+
expect(cacheKey).toContain("-%3E3.10.11%2C%3C3.11-");
52+
});
53+
54+
it("encodes cache suffixes before adding them to the cache key", async () => {
55+
const inputs = createSetupInputs({ cacheSuffix: "tests-3.10,3.11" });
56+
57+
await restoreCache(inputs, "3.11");
58+
59+
const cacheKey = cacheKeyOutput();
60+
61+
expect(cacheKey).not.toContain(",");
62+
expect(cacheKey).toContain("-tests-3.10%2C3.11");
63+
});
64+
65+
it("keeps cache keys unchanged for exact Python versions and simple suffixes", async () => {
66+
const inputs = createSetupInputs({ cacheSuffix: "tests-3.11" });
67+
68+
await restoreCache(inputs, "3.11");
69+
70+
expect(cacheKeyOutput()).toBe(
71+
"setup-uv-2-x86_64-unknown-linux-gnu-ubuntu-24.04-3.11-pruned-dependencyhash-tests-3.11",
72+
);
73+
});
74+
});

__tests__/helpers/setup-inputs.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { CacheLocalSource, type SetupInputs } from "../../src/utils/inputs";
2+
3+
export function createSetupInputs(
4+
overrides: Partial<SetupInputs> = {},
5+
): SetupInputs {
6+
return {
7+
activateEnvironment: false,
8+
addProblemMatchers: false,
9+
cacheDependencyGlob: "uv.lock",
10+
cacheLocalPath: {
11+
path: "/tmp/setup-uv-cache",
12+
source: CacheLocalSource.Input,
13+
},
14+
cachePython: false,
15+
cacheSuffix: "",
16+
checksum: "",
17+
downloadFromAstralMirror: false,
18+
enableCache: true,
19+
githubToken: "",
20+
ignoreEmptyWorkdir: false,
21+
ignoreNothingToCache: false,
22+
noProject: false,
23+
pruneCache: true,
24+
pythonDir: "/tmp/uv-python-dir",
25+
pythonVersion: "",
26+
quiet: false,
27+
resolutionStrategy: "highest",
28+
restoreCache: false,
29+
saveCache: true,
30+
venvPath: "/workspace/.venv",
31+
version: "",
32+
versionFile: "",
33+
workingDirectory: "/workspace",
34+
...overrides,
35+
};
36+
}

dist/setup/index.cjs

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cache/restore-cache.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,10 @@ async function computeKeys(
9494
if (cacheDependencyPathHash === "-") {
9595
cacheDependencyPathHash = "-no-dependency-glob";
9696
}
97-
const suffix = inputs.cacheSuffix ? `-${inputs.cacheSuffix}` : "";
98-
const version = pythonVersion ?? "unknown";
97+
const suffix = inputs.cacheSuffix
98+
? `-${encodeURIComponent(inputs.cacheSuffix)}`
99+
: "";
100+
const version = encodeURIComponent(pythonVersion ?? "unknown");
99101
const platform = await getPlatform();
100102
const osNameVersion = getOSNameVersion();
101103
const pruned = inputs.pruneCache ? "-pruned" : "";

0 commit comments

Comments
 (0)