Skip to content

Commit 48a56f6

Browse files
committed
Add some tests for downloadDependencyCaches related to feature prefixes
1 parent 4885eb2 commit 48a56f6

File tree

2 files changed

+169
-6
lines changed

2 files changed

+169
-6
lines changed

src/dependency-caching.test.ts

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import * as fs from "fs";
22
import path from "path";
33

4+
import * as actionsCache from "@actions/cache";
5+
import * as glob from "@actions/glob";
46
import test from "ava";
57
import * as sinon from "sinon";
68

@@ -15,6 +17,9 @@ import {
1517
internal,
1618
CSHARP_BASE_PATTERNS,
1719
CSHARP_EXTRA_PATTERNS,
20+
downloadDependencyCaches,
21+
CacheHitKind,
22+
cacheKey,
1823
} from "./dependency-caching";
1924
import { Feature } from "./feature-flags";
2025
import { KnownLanguage } from "./languages";
@@ -168,6 +173,164 @@ test("checkHashPatterns - returns patterns when patterns match", async (t) => {
168173
});
169174
});
170175

176+
type RestoreCacheFunc = (
177+
paths: string[],
178+
primaryKey: string,
179+
restoreKeys: string[] | undefined,
180+
) => Promise<string | undefined>;
181+
182+
/**
183+
* Constructs a function that `actionsCache.restoreCache` can be stubbed with.
184+
*
185+
* @param mockCacheKeys The keys of caches that we want to exist in the Actions cache.
186+
*
187+
* @returns Returns a function that `actionsCache.restoreCache` can be stubbed with.
188+
*/
189+
function makeMockCacheCheck(mockCacheKeys: string[]): RestoreCacheFunc {
190+
return async (
191+
_paths: string[],
192+
primaryKey: string,
193+
restoreKeys: string[] | undefined,
194+
) => {
195+
// The behaviour here mirrors what the real `restoreCache` would do:
196+
// - Starting with the primary restore key, check all caches for a match:
197+
// even for the primary restore key, this only has to be a prefix match.
198+
// - If the primary restore key doesn't prefix-match any cache, then proceed
199+
// in the same way for each restore key in turn.
200+
for (const restoreKey of [primaryKey, ...(restoreKeys || [])]) {
201+
for (const mockCacheKey of mockCacheKeys) {
202+
if (mockCacheKey.startsWith(restoreKey)) {
203+
return mockCacheKey;
204+
}
205+
}
206+
}
207+
// Only if no restore key matches any cache key prefix, there is no matching
208+
// cache and we return `undefined`.
209+
return undefined;
210+
};
211+
}
212+
213+
test("downloadDependencyCaches - does not restore caches with feature keys if no features are enabled", async (t) => {
214+
process.env["RUNNER_OS"] = "Linux";
215+
216+
const codeql = createStubCodeQL({});
217+
const messages: LoggedMessage[] = [];
218+
const logger = getRecordingLogger(messages);
219+
220+
sinon.stub(glob, "hashFiles").resolves("abcdef");
221+
222+
const keyWithFeature = await cacheKey(
223+
codeql,
224+
createFeatures([Feature.CsharpNewCacheKey]),
225+
KnownLanguage.csharp,
226+
// Patterns don't matter here because we have stubbed `hashFiles` to always return a specific hash above.
227+
[],
228+
);
229+
230+
const restoreCacheStub = sinon
231+
.stub(actionsCache, "restoreCache")
232+
.callsFake(makeMockCacheCheck([keyWithFeature]));
233+
234+
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
235+
makePatternCheckStub
236+
.withArgs(CSHARP_BASE_PATTERNS)
237+
.resolves(CSHARP_BASE_PATTERNS);
238+
makePatternCheckStub.withArgs(CSHARP_EXTRA_PATTERNS).resolves(undefined);
239+
240+
const results = await downloadDependencyCaches(
241+
codeql,
242+
createFeatures([]),
243+
[KnownLanguage.csharp],
244+
logger,
245+
);
246+
t.is(results.length, 1);
247+
t.is(results[0].language, KnownLanguage.csharp);
248+
t.is(results[0].hit_kind, CacheHitKind.Miss);
249+
t.assert(restoreCacheStub.calledOnce);
250+
});
251+
252+
test("downloadDependencyCaches - restores caches with feature keys if features are enabled", async (t) => {
253+
process.env["RUNNER_OS"] = "Linux";
254+
255+
const codeql = createStubCodeQL({});
256+
const messages: LoggedMessage[] = [];
257+
const logger = getRecordingLogger(messages);
258+
const features = createFeatures([Feature.CsharpNewCacheKey]);
259+
260+
sinon.stub(glob, "hashFiles").resolves("abcdef");
261+
262+
const keyWithFeature = await cacheKey(
263+
codeql,
264+
features,
265+
KnownLanguage.csharp,
266+
// Patterns don't matter here because we have stubbed `hashFiles` to always return a specific hash above.
267+
[],
268+
);
269+
270+
const restoreCacheStub = sinon
271+
.stub(actionsCache, "restoreCache")
272+
.callsFake(makeMockCacheCheck([keyWithFeature]));
273+
274+
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
275+
makePatternCheckStub
276+
.withArgs(CSHARP_BASE_PATTERNS)
277+
.resolves(CSHARP_BASE_PATTERNS);
278+
makePatternCheckStub.withArgs(CSHARP_EXTRA_PATTERNS).resolves(undefined);
279+
280+
const results = await downloadDependencyCaches(
281+
codeql,
282+
features,
283+
[KnownLanguage.csharp],
284+
logger,
285+
);
286+
t.is(results.length, 1);
287+
t.is(results[0].language, KnownLanguage.csharp);
288+
t.is(results[0].hit_kind, CacheHitKind.Exact);
289+
t.assert(restoreCacheStub.calledOnce);
290+
});
291+
292+
test("downloadDependencyCaches - restores caches with feature keys if features are enabled for partial matches", async (t) => {
293+
process.env["RUNNER_OS"] = "Linux";
294+
295+
const codeql = createStubCodeQL({});
296+
const messages: LoggedMessage[] = [];
297+
const logger = getRecordingLogger(messages);
298+
const features = createFeatures([Feature.CsharpNewCacheKey]);
299+
300+
const hashFilesStub = sinon.stub(glob, "hashFiles");
301+
hashFilesStub.onFirstCall().resolves("abcdef");
302+
hashFilesStub.onSecondCall().resolves("123456");
303+
304+
const keyWithFeature = await cacheKey(
305+
codeql,
306+
features,
307+
KnownLanguage.csharp,
308+
// Patterns don't matter here because we have stubbed `hashFiles` to always return a specific hash above.
309+
[],
310+
);
311+
312+
const restoreCacheStub = sinon
313+
.stub(actionsCache, "restoreCache")
314+
.callsFake(makeMockCacheCheck([keyWithFeature]));
315+
316+
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
317+
makePatternCheckStub
318+
.withArgs(CSHARP_BASE_PATTERNS)
319+
.resolves(CSHARP_BASE_PATTERNS);
320+
makePatternCheckStub.withArgs(CSHARP_EXTRA_PATTERNS).resolves(undefined);
321+
322+
const results = await downloadDependencyCaches(
323+
codeql,
324+
features,
325+
[KnownLanguage.csharp],
326+
logger,
327+
);
328+
t.is(results.length, 1);
329+
t.is(results[0].language, KnownLanguage.csharp);
330+
t.is(results[0].hit_kind, CacheHitKind.Partial);
331+
t.assert(restoreCacheStub.calledOnce);
332+
});
333+
171334
test("getFeaturePrefix - returns empty string if no features are enabled", async (t) => {
172335
const codeql = createStubCodeQL({});
173336
const features = createFeatures([]);

src/dependency-caching.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { createCacheKeyHash, getTotalCacheSize } from "./caching-utils";
1010
import { CodeQL } from "./codeql";
1111
import { Config } from "./config-utils";
1212
import { EnvVar } from "./environment";
13-
import { Feature, FeatureEnablement, Features } from "./feature-flags";
13+
import { Feature, FeatureEnablement } from "./feature-flags";
1414
import { KnownLanguage, Language } from "./languages";
1515
import { Logger } from "./logging";
1616
import { getErrorMessage, getRequiredEnvParam } from "./util";
@@ -236,7 +236,7 @@ export async function checkHashPatterns(
236236
*/
237237
export async function downloadDependencyCaches(
238238
codeql: CodeQL,
239-
features: Features,
239+
features: FeatureEnablement,
240240
languages: Language[],
241241
logger: Logger,
242242
): Promise<DependencyCacheRestoreStatusReport> {
@@ -335,7 +335,7 @@ export type DependencyCacheUploadStatusReport = DependencyCacheUploadStatus[];
335335
*/
336336
export async function uploadDependencyCaches(
337337
codeql: CodeQL,
338-
features: Features,
338+
features: FeatureEnablement,
339339
config: Config,
340340
logger: Logger,
341341
): Promise<DependencyCacheUploadStatusReport> {
@@ -438,9 +438,9 @@ export async function uploadDependencyCaches(
438438
*
439439
* @returns A cache key capturing information about the project(s) being analyzed in the specified language.
440440
*/
441-
async function cacheKey(
441+
export async function cacheKey(
442442
codeql: CodeQL,
443-
features: Features,
443+
features: FeatureEnablement,
444444
language: Language,
445445
patterns: string[],
446446
): Promise<string> {
@@ -509,7 +509,7 @@ export async function getFeaturePrefix(
509509
*/
510510
async function cachePrefix(
511511
codeql: CodeQL,
512-
features: Features,
512+
features: FeatureEnablement,
513513
language: Language,
514514
): Promise<string> {
515515
const runnerOs = getRequiredEnvParam("RUNNER_OS");

0 commit comments

Comments
 (0)