11import * as fs from "fs" ;
22import path from "path" ;
33
4+ import * as actionsCache from "@actions/cache" ;
5+ import * as glob from "@actions/glob" ;
46import test from "ava" ;
57import * 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" ;
1924import { Feature } from "./feature-flags" ;
2025import { 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+
171334test ( "getFeaturePrefix - returns empty string if no features are enabled" , async ( t ) => {
172335 const codeql = createStubCodeQL ( { } ) ;
173336 const features = createFeatures ( [ ] ) ;
0 commit comments