@@ -59,47 +59,14 @@ test('save with missing input should fail', async () => {
5959 )
6060} )
6161
62- test ( 'save with large cache outputs should fail using' , async ( ) => {
63- const paths = 'node_modules'
64- const key = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
65- const cachePaths = [ path . resolve ( paths ) ]
66-
67- const createTarMock = jest . spyOn ( tar , 'createTar' )
68- const logWarningMock = jest . spyOn ( core , 'warning' )
69-
70- const cacheSize = 11 * 1024 * 1024 * 1024 //~11GB, over the 10GB limit
71- jest
72- . spyOn ( cacheUtils , 'getArchiveFileSizeInBytes' )
73- . mockReturnValueOnce ( cacheSize )
74- const compression = CompressionMethod . Gzip
75- const getCompressionMock = jest
76- . spyOn ( cacheUtils , 'getCompressionMethod' )
77- . mockReturnValueOnce ( Promise . resolve ( compression ) )
78-
79- const cacheId = await saveCache ( [ paths ] , key )
80- expect ( cacheId ) . toBe ( - 1 )
81- expect ( logWarningMock ) . toHaveBeenCalledWith (
82- 'Failed to save: Cache size of ~11264 MB (11811160064 B) is over the 10GB limit, not saving cache.'
83- )
84-
85- const archiveFolder = '/foo/bar'
86-
87- expect ( createTarMock ) . toHaveBeenCalledWith (
88- archiveFolder ,
89- cachePaths ,
90- compression
91- )
92- expect ( getCompressionMock ) . toHaveBeenCalledTimes ( 1 )
93- } )
94-
9562test ( 'create cache entry failure on non-ok response' , async ( ) => {
9663 const paths = [ 'node_modules' ]
9764 const key = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
9865 const infoLogMock = jest . spyOn ( core , 'info' )
9966
10067 const createCacheEntryMock = jest
10168 . spyOn ( CacheServiceClientJSON . prototype , 'CreateCacheEntry' )
102- . mockResolvedValue ( { ok : false , signedUploadUrl : '' } )
69+ . mockResolvedValue ( { ok : false , signedUploadUrl : '' , message : '' } )
10370
10471 const createTarMock = jest . spyOn ( tar , 'createTar' )
10572 const finalizeCacheEntryMock = jest . spyOn (
@@ -182,7 +149,7 @@ test('save cache fails if a signedUploadURL was not passed', async () => {
182149 const createCacheEntryMock = jest
183150 . spyOn ( CacheServiceClientJSON . prototype , 'CreateCacheEntry' )
184151 . mockReturnValue (
185- Promise . resolve ( { ok : true , signedUploadUrl : signedUploadURL } )
152+ Promise . resolve ( { ok : true , signedUploadUrl : signedUploadURL , message : '' } )
186153 )
187154
188155 const createTarMock = jest . spyOn ( tar , 'createTar' )
@@ -240,7 +207,7 @@ test('finalize save cache failure', async () => {
240207 const createCacheEntryMock = jest
241208 . spyOn ( CacheServiceClientJSON . prototype , 'CreateCacheEntry' )
242209 . mockReturnValue (
243- Promise . resolve ( { ok : true , signedUploadUrl : signedUploadURL } )
210+ Promise . resolve ( { ok : true , signedUploadUrl : signedUploadURL , message : '' } )
244211 )
245212
246213 const createTarMock = jest . spyOn ( tar , 'createTar' )
@@ -260,7 +227,7 @@ test('finalize save cache failure', async () => {
260227
261228 const finalizeCacheEntryMock = jest
262229 . spyOn ( CacheServiceClientJSON . prototype , 'FinalizeCacheEntryUpload' )
263- . mockReturnValue ( Promise . resolve ( { ok : false , entryId : '' } ) )
230+ . mockReturnValue ( Promise . resolve ( { ok : false , entryId : '' , message : '' } ) )
264231
265232 const cacheId = await saveCache ( [ paths ] , key , options )
266233
@@ -319,7 +286,7 @@ test('save with valid inputs uploads a cache', async () => {
319286 jest
320287 . spyOn ( CacheServiceClientJSON . prototype , 'CreateCacheEntry' )
321288 . mockReturnValue (
322- Promise . resolve ( { ok : true , signedUploadUrl : signedUploadURL } )
289+ Promise . resolve ( { ok : true , signedUploadUrl : signedUploadURL , message : '' } )
323290 )
324291
325292 const saveCacheMock = jest . spyOn ( cacheHttpClient , 'saveCache' )
@@ -332,7 +299,9 @@ test('save with valid inputs uploads a cache', async () => {
332299
333300 const finalizeCacheEntryMock = jest
334301 . spyOn ( CacheServiceClientJSON . prototype , 'FinalizeCacheEntryUpload' )
335- . mockReturnValue ( Promise . resolve ( { ok : true , entryId : cacheId . toString ( ) } ) )
302+ . mockReturnValue (
303+ Promise . resolve ( { ok : true , entryId : cacheId . toString ( ) , message : '' } )
304+ )
336305
337306 const expectedCacheId = await saveCache ( [ paths ] , key )
338307
@@ -360,6 +329,252 @@ test('save with valid inputs uploads a cache', async () => {
360329 expect ( expectedCacheId ) . toBe ( cacheId )
361330} )
362331
332+ test ( 'save with extremely large cache should succeed in v2 (no size limit)' , async ( ) => {
333+ const paths = 'node_modules'
334+ const key = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
335+ const cachePaths = [ path . resolve ( paths ) ]
336+ const signedUploadURL = 'https://blob-storage.local?signed=true'
337+ const createTarMock = jest . spyOn ( tar , 'createTar' )
338+ // Simulate a very large cache (20GB)
339+ const archiveFileSize = 20 * 1024 * 1024 * 1024 // 20GB
340+ const options : UploadOptions = {
341+ archiveSizeBytes : archiveFileSize ,
342+ useAzureSdk : true ,
343+ uploadChunkSize : 64 * 1024 * 1024 ,
344+ uploadConcurrency : 8
345+ }
346+
347+ jest
348+ . spyOn ( cacheUtils , 'getArchiveFileSizeInBytes' )
349+ . mockReturnValueOnce ( archiveFileSize )
350+
351+ const cacheId = 4
352+ jest
353+ . spyOn ( CacheServiceClientJSON . prototype , 'CreateCacheEntry' )
354+ . mockReturnValue (
355+ Promise . resolve ( { ok : true , signedUploadUrl : signedUploadURL , message : '' } )
356+ )
357+
358+ const saveCacheMock = jest . spyOn ( cacheHttpClient , 'saveCache' )
359+
360+ const compression = CompressionMethod . Zstd
361+ const getCompressionMock = jest
362+ . spyOn ( cacheUtils , 'getCompressionMethod' )
363+ . mockReturnValue ( Promise . resolve ( compression ) )
364+ const cacheVersion = cacheUtils . getCacheVersion ( [ paths ] , compression )
365+
366+ const finalizeCacheEntryMock = jest
367+ . spyOn ( CacheServiceClientJSON . prototype , 'FinalizeCacheEntryUpload' )
368+ . mockReturnValue (
369+ Promise . resolve ( { ok : true , entryId : cacheId . toString ( ) , message : '' } )
370+ )
371+
372+ const expectedCacheId = await saveCache ( [ paths ] , key )
373+
374+ const archiveFolder = '/foo/bar'
375+ const archiveFile = path . join ( archiveFolder , CacheFilename . Zstd )
376+ expect ( saveCacheMock ) . toHaveBeenCalledWith (
377+ - 1 ,
378+ archiveFile ,
379+ signedUploadURL ,
380+ options
381+ )
382+ expect ( createTarMock ) . toHaveBeenCalledWith (
383+ archiveFolder ,
384+ cachePaths ,
385+ compression
386+ )
387+
388+ expect ( finalizeCacheEntryMock ) . toHaveBeenCalledWith ( {
389+ key,
390+ version : cacheVersion ,
391+ sizeBytes : archiveFileSize . toString ( )
392+ } )
393+
394+ expect ( getCompressionMock ) . toHaveBeenCalledTimes ( 1 )
395+ expect ( expectedCacheId ) . toBe ( cacheId )
396+ } )
397+
398+ test ( 'save with create cache entry failure and specific error message' , async ( ) => {
399+ const paths = [ 'node_modules' ]
400+ const key = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
401+ const infoLogMock = jest . spyOn ( core , 'info' )
402+ const warningLogMock = jest . spyOn ( core , 'warning' )
403+ const errorMessage = 'Cache storage quota exceeded for repository'
404+
405+ const createCacheEntryMock = jest
406+ . spyOn ( CacheServiceClientJSON . prototype , 'CreateCacheEntry' )
407+ . mockResolvedValue ( { ok : false , signedUploadUrl : '' , message : errorMessage } )
408+
409+ const createTarMock = jest . spyOn ( tar , 'createTar' )
410+ const compression = CompressionMethod . Zstd
411+ const getCompressionMock = jest
412+ . spyOn ( cacheUtils , 'getCompressionMethod' )
413+ . mockResolvedValueOnce ( compression )
414+ const archiveFileSize = 1024
415+ jest
416+ . spyOn ( cacheUtils , 'getArchiveFileSizeInBytes' )
417+ . mockReturnValueOnce ( archiveFileSize )
418+
419+ const cacheId = await saveCache ( paths , key )
420+ expect ( cacheId ) . toBe ( - 1 )
421+ expect ( warningLogMock ) . toHaveBeenCalledWith (
422+ `Cache reservation failed: ${ errorMessage } `
423+ )
424+ expect ( infoLogMock ) . toHaveBeenCalledWith (
425+ `Failed to save: Unable to reserve cache with key ${ key } , another job may be creating this cache.`
426+ )
427+
428+ expect ( createCacheEntryMock ) . toHaveBeenCalledWith ( {
429+ key,
430+ version : cacheUtils . getCacheVersion ( paths , compression )
431+ } )
432+ expect ( createTarMock ) . toHaveBeenCalledTimes ( 1 )
433+ expect ( getCompressionMock ) . toHaveBeenCalledTimes ( 1 )
434+ } )
435+
436+ test ( 'save with finalize cache entry failure and specific error message' , async ( ) => {
437+ const paths = 'node_modules'
438+ const key = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
439+ const cachePaths = [ path . resolve ( paths ) ]
440+ const logWarningMock = jest . spyOn ( core , 'warning' )
441+ const signedUploadURL = 'https://blob-storage.local?signed=true'
442+ const archiveFileSize = 1024
443+ const errorMessage =
444+ 'Cache entry finalization failed due to concurrent access'
445+ const options : UploadOptions = {
446+ archiveSizeBytes : archiveFileSize ,
447+ useAzureSdk : true ,
448+ uploadChunkSize : 64 * 1024 * 1024 ,
449+ uploadConcurrency : 8
450+ }
451+
452+ const createCacheEntryMock = jest
453+ . spyOn ( CacheServiceClientJSON . prototype , 'CreateCacheEntry' )
454+ . mockReturnValue (
455+ Promise . resolve ( { ok : true , signedUploadUrl : signedUploadURL , message : '' } )
456+ )
457+
458+ const createTarMock = jest . spyOn ( tar , 'createTar' )
459+ const saveCacheMock = jest
460+ . spyOn ( cacheHttpClient , 'saveCache' )
461+ . mockResolvedValue ( )
462+
463+ const compression = CompressionMethod . Zstd
464+ const getCompressionMock = jest
465+ . spyOn ( cacheUtils , 'getCompressionMethod' )
466+ . mockReturnValueOnce ( Promise . resolve ( compression ) )
467+
468+ const cacheVersion = cacheUtils . getCacheVersion ( [ paths ] , compression )
469+ jest
470+ . spyOn ( cacheUtils , 'getArchiveFileSizeInBytes' )
471+ . mockReturnValueOnce ( archiveFileSize )
472+
473+ const finalizeCacheEntryMock = jest
474+ . spyOn ( CacheServiceClientJSON . prototype , 'FinalizeCacheEntryUpload' )
475+ . mockReturnValue (
476+ Promise . resolve ( { ok : false , entryId : '' , message : errorMessage } )
477+ )
478+
479+ const cacheId = await saveCache ( [ paths ] , key , options )
480+
481+ expect ( createCacheEntryMock ) . toHaveBeenCalledWith ( {
482+ key,
483+ version : cacheVersion
484+ } )
485+
486+ const archiveFolder = '/foo/bar'
487+ const archiveFile = path . join ( archiveFolder , CacheFilename . Zstd )
488+ expect ( createTarMock ) . toHaveBeenCalledWith (
489+ archiveFolder ,
490+ cachePaths ,
491+ compression
492+ )
493+
494+ expect ( saveCacheMock ) . toHaveBeenCalledWith (
495+ - 1 ,
496+ archiveFile ,
497+ signedUploadURL ,
498+ options
499+ )
500+ expect ( getCompressionMock ) . toHaveBeenCalledTimes ( 1 )
501+
502+ expect ( finalizeCacheEntryMock ) . toHaveBeenCalledWith ( {
503+ key,
504+ version : cacheVersion ,
505+ sizeBytes : archiveFileSize . toString ( )
506+ } )
507+
508+ expect ( cacheId ) . toBe ( - 1 )
509+ expect ( logWarningMock ) . toHaveBeenCalledWith ( errorMessage )
510+ } )
511+
512+ test ( 'save with multiple large caches should succeed in v2 (testing 50GB)' , async ( ) => {
513+ const paths = [ 'large-dataset' , 'node_modules' , 'build-artifacts' ]
514+ const key = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
515+ const cachePaths = paths . map ( p => path . resolve ( p ) )
516+ const signedUploadURL = 'https://blob-storage.local?signed=true'
517+ const createTarMock = jest . spyOn ( tar , 'createTar' )
518+ // Simulate an extremely large cache (50GB)
519+ const archiveFileSize = 50 * 1024 * 1024 * 1024 // 50GB
520+ const options : UploadOptions = {
521+ archiveSizeBytes : archiveFileSize ,
522+ useAzureSdk : true ,
523+ uploadChunkSize : 64 * 1024 * 1024 ,
524+ uploadConcurrency : 8
525+ }
526+
527+ jest
528+ . spyOn ( cacheUtils , 'getArchiveFileSizeInBytes' )
529+ . mockReturnValueOnce ( archiveFileSize )
530+
531+ const cacheId = 7
532+ jest
533+ . spyOn ( CacheServiceClientJSON . prototype , 'CreateCacheEntry' )
534+ . mockReturnValue (
535+ Promise . resolve ( { ok : true , signedUploadUrl : signedUploadURL , message : '' } )
536+ )
537+
538+ const saveCacheMock = jest . spyOn ( cacheHttpClient , 'saveCache' )
539+
540+ const compression = CompressionMethod . Zstd
541+ const getCompressionMock = jest
542+ . spyOn ( cacheUtils , 'getCompressionMethod' )
543+ . mockReturnValue ( Promise . resolve ( compression ) )
544+ const cacheVersion = cacheUtils . getCacheVersion ( paths , compression )
545+
546+ const finalizeCacheEntryMock = jest
547+ . spyOn ( CacheServiceClientJSON . prototype , 'FinalizeCacheEntryUpload' )
548+ . mockReturnValue (
549+ Promise . resolve ( { ok : true , entryId : cacheId . toString ( ) , message : '' } )
550+ )
551+
552+ const expectedCacheId = await saveCache ( paths , key )
553+
554+ const archiveFolder = '/foo/bar'
555+ const archiveFile = path . join ( archiveFolder , CacheFilename . Zstd )
556+ expect ( saveCacheMock ) . toHaveBeenCalledWith (
557+ - 1 ,
558+ archiveFile ,
559+ signedUploadURL ,
560+ options
561+ )
562+ expect ( createTarMock ) . toHaveBeenCalledWith (
563+ archiveFolder ,
564+ cachePaths ,
565+ compression
566+ )
567+
568+ expect ( finalizeCacheEntryMock ) . toHaveBeenCalledWith ( {
569+ key,
570+ version : cacheVersion ,
571+ sizeBytes : archiveFileSize . toString ( )
572+ } )
573+
574+ expect ( getCompressionMock ) . toHaveBeenCalledTimes ( 1 )
575+ expect ( expectedCacheId ) . toBe ( cacheId )
576+ } )
577+
363578test ( 'save with non existing path should not save cache using v2 saveCache' , async ( ) => {
364579 const path = 'node_modules'
365580 const key = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
0 commit comments