mirror of
				https://github.com/actions/setup-node.git
				synced 2025-10-31 08:04:00 +08:00 
			
		
		
		
	Detect cached folders from multiple directories (#735)
* Add project-dir * Fix find lock file * Remove package-dir input * format & resolve conflicts * Add unit tests * build dist * Apply change request fixes * handle non-dir cache-dependency-path * bump cache version * run checks * Handle globs in cacheDependencyPath * refactor, introduce `cacheDependencyPathToProjectsDirectories` it is necessary for the next PR related yarn optimization * Changes requests * Apply fixes * review fixes * add e2e * Add unique * review updates * review updates second stage * Review fixes 3 * imporve e2e tests
This commit is contained in:
		| @@ -1,9 +1,10 @@ | ||||
| import os from 'os'; | ||||
| import * as fs from 'fs'; | ||||
| import fs from 'fs'; | ||||
| import * as path from 'path'; | ||||
| import * as core from '@actions/core'; | ||||
| import * as io from '@actions/io'; | ||||
| import * as auth from '../src/authutil'; | ||||
| import * as cacheUtils from '../src/cache-utils'; | ||||
|  | ||||
| let rcFile: string; | ||||
|  | ||||
|   | ||||
| @@ -32,13 +32,13 @@ describe('cache-restore', () => { | ||||
|  | ||||
|   function findCacheFolder(command: string) { | ||||
|     switch (command) { | ||||
|       case utils.supportedPackageManagers.npm.getCacheFolderCommand: | ||||
|       case 'npm config get cache': | ||||
|         return npmCachePath; | ||||
|       case utils.supportedPackageManagers.pnpm.getCacheFolderCommand: | ||||
|       case 'pnpm store path --silent': | ||||
|         return pnpmCachePath; | ||||
|       case utils.supportedPackageManagers.yarn1.getCacheFolderCommand: | ||||
|       case 'yarn cache dir': | ||||
|         return yarn1CachePath; | ||||
|       case utils.supportedPackageManagers.yarn2.getCacheFolderCommand: | ||||
|       case 'yarn config get cacheFolder': | ||||
|         return yarn2CachePath; | ||||
|       default: | ||||
|         return 'packge/not/found'; | ||||
| @@ -108,7 +108,7 @@ describe('cache-restore', () => { | ||||
|     it.each([['npm7'], ['npm6'], ['pnpm6'], ['yarn1'], ['yarn2'], ['random']])( | ||||
|       'Throw an error because %s is not supported', | ||||
|       async packageManager => { | ||||
|         await expect(restoreCache(packageManager)).rejects.toThrow( | ||||
|         await expect(restoreCache(packageManager, '')).rejects.toThrow( | ||||
|           `Caching for '${packageManager}' is not supported` | ||||
|         ); | ||||
|       } | ||||
| @@ -132,7 +132,7 @@ describe('cache-restore', () => { | ||||
|           } | ||||
|         }); | ||||
|  | ||||
|         await restoreCache(packageManager); | ||||
|         await restoreCache(packageManager, ''); | ||||
|         expect(hashFilesSpy).toHaveBeenCalled(); | ||||
|         expect(infoSpy).toHaveBeenCalledWith( | ||||
|           `Cache restored from key: node-cache-${platform}-${packageManager}-${fileHash}` | ||||
| @@ -163,7 +163,7 @@ describe('cache-restore', () => { | ||||
|         }); | ||||
|  | ||||
|         restoreCacheSpy.mockImplementationOnce(() => undefined); | ||||
|         await restoreCache(packageManager); | ||||
|         await restoreCache(packageManager, ''); | ||||
|         expect(hashFilesSpy).toHaveBeenCalled(); | ||||
|         expect(infoSpy).toHaveBeenCalledWith( | ||||
|           `${packageManager} cache is not found` | ||||
|   | ||||
| @@ -107,18 +107,20 @@ describe('run', () => { | ||||
|   describe('Validate unchanged cache is not saved', () => { | ||||
|     it('should not save cache for yarn1', async () => { | ||||
|       inputs['cache'] = 'yarn'; | ||||
|       getStateSpy.mockImplementation(() => yarnFileHash); | ||||
|       getCommandOutputSpy | ||||
|         .mockImplementationOnce(() => '1.2.3') | ||||
|         .mockImplementationOnce(() => `${commonPath}/yarn1`); | ||||
|       getStateSpy.mockImplementation(key => | ||||
|         key === State.CachePrimaryKey || key === State.CacheMatchedKey | ||||
|           ? yarnFileHash | ||||
|           : key === State.CachePaths | ||||
|           ? '["/foo/bar"]' | ||||
|           : 'not expected' | ||||
|       ); | ||||
|  | ||||
|       await run(); | ||||
|  | ||||
|       expect(getInputSpy).toHaveBeenCalled(); | ||||
|       expect(getStateSpy).toHaveBeenCalledTimes(2); | ||||
|       expect(getCommandOutputSpy).toHaveBeenCalledTimes(2); | ||||
|       expect(debugSpy).toHaveBeenCalledWith(`yarn path is ${commonPath}/yarn1`); | ||||
|       expect(debugSpy).toHaveBeenCalledWith('Consumed yarn version is 1.2.3'); | ||||
|       expect(getStateSpy).toHaveBeenCalledTimes(3); | ||||
|       expect(getCommandOutputSpy).toHaveBeenCalledTimes(0); | ||||
|       expect(debugSpy).toHaveBeenCalledTimes(0); | ||||
|       expect(infoSpy).toHaveBeenCalledWith( | ||||
|         `Cache hit occurred on the primary key ${yarnFileHash}, not saving cache.` | ||||
|       ); | ||||
| @@ -127,18 +129,20 @@ describe('run', () => { | ||||
|  | ||||
|     it('should not save cache for yarn2', async () => { | ||||
|       inputs['cache'] = 'yarn'; | ||||
|       getStateSpy.mockImplementation(() => yarnFileHash); | ||||
|       getCommandOutputSpy | ||||
|         .mockImplementationOnce(() => '2.2.3') | ||||
|         .mockImplementationOnce(() => `${commonPath}/yarn2`); | ||||
|       getStateSpy.mockImplementation(key => | ||||
|         key === State.CachePrimaryKey || key === State.CacheMatchedKey | ||||
|           ? yarnFileHash | ||||
|           : key === State.CachePaths | ||||
|           ? '["/foo/bar"]' | ||||
|           : 'not expected' | ||||
|       ); | ||||
|  | ||||
|       await run(); | ||||
|  | ||||
|       expect(getInputSpy).toHaveBeenCalled(); | ||||
|       expect(getStateSpy).toHaveBeenCalledTimes(2); | ||||
|       expect(getCommandOutputSpy).toHaveBeenCalledTimes(2); | ||||
|       expect(debugSpy).toHaveBeenCalledWith(`yarn path is ${commonPath}/yarn2`); | ||||
|       expect(debugSpy).toHaveBeenCalledWith('Consumed yarn version is 2.2.3'); | ||||
|       expect(getStateSpy).toHaveBeenCalledTimes(3); | ||||
|       expect(getCommandOutputSpy).toHaveBeenCalledTimes(0); | ||||
|       expect(debugSpy).toHaveBeenCalledTimes(0); | ||||
|       expect(infoSpy).toHaveBeenCalledWith( | ||||
|         `Cache hit occurred on the primary key ${yarnFileHash}, not saving cache.` | ||||
|       ); | ||||
| @@ -147,35 +151,40 @@ describe('run', () => { | ||||
|  | ||||
|     it('should not save cache for npm', async () => { | ||||
|       inputs['cache'] = 'npm'; | ||||
|       getStateSpy.mockImplementation(() => npmFileHash); | ||||
|       getStateSpy.mockImplementation(key => | ||||
|         key === State.CachePrimaryKey || key === State.CacheMatchedKey | ||||
|           ? yarnFileHash | ||||
|           : key === State.CachePaths | ||||
|           ? '["/foo/bar"]' | ||||
|           : 'not expected' | ||||
|       ); | ||||
|       getCommandOutputSpy.mockImplementationOnce(() => `${commonPath}/npm`); | ||||
|  | ||||
|       await run(); | ||||
|  | ||||
|       expect(getInputSpy).toHaveBeenCalled(); | ||||
|       expect(getStateSpy).toHaveBeenCalledTimes(2); | ||||
|       expect(getCommandOutputSpy).toHaveBeenCalledTimes(1); | ||||
|       expect(debugSpy).toHaveBeenCalledWith(`npm path is ${commonPath}/npm`); | ||||
|       expect(infoSpy).toHaveBeenCalledWith( | ||||
|         `Cache hit occurred on the primary key ${npmFileHash}, not saving cache.` | ||||
|       ); | ||||
|       expect(getStateSpy).toHaveBeenCalledTimes(3); | ||||
|       expect(getCommandOutputSpy).toHaveBeenCalledTimes(0); | ||||
|       expect(debugSpy).toHaveBeenCalledTimes(0); | ||||
|       expect(setFailedSpy).not.toHaveBeenCalled(); | ||||
|     }); | ||||
|  | ||||
|     it('should not save cache for pnpm', async () => { | ||||
|       inputs['cache'] = 'pnpm'; | ||||
|       getStateSpy.mockImplementation(() => pnpmFileHash); | ||||
|       getCommandOutputSpy.mockImplementationOnce(() => `${commonPath}/pnpm`); | ||||
|       getStateSpy.mockImplementation(key => | ||||
|         key === State.CachePrimaryKey || key === State.CacheMatchedKey | ||||
|           ? yarnFileHash | ||||
|           : key === State.CachePaths | ||||
|           ? '["/foo/bar"]' | ||||
|           : 'not expected' | ||||
|       ); | ||||
|  | ||||
|       await run(); | ||||
|  | ||||
|       expect(getInputSpy).toHaveBeenCalled(); | ||||
|       expect(getStateSpy).toHaveBeenCalledTimes(2); | ||||
|       expect(getCommandOutputSpy).toHaveBeenCalledTimes(1); | ||||
|       expect(debugSpy).toHaveBeenCalledWith(`pnpm path is ${commonPath}/pnpm`); | ||||
|       expect(infoSpy).toHaveBeenCalledWith( | ||||
|         `Cache hit occurred on the primary key ${pnpmFileHash}, not saving cache.` | ||||
|       ); | ||||
|       expect(getStateSpy).toHaveBeenCalledTimes(3); | ||||
|       expect(getCommandOutputSpy).toHaveBeenCalledTimes(0); | ||||
|       expect(debugSpy).toHaveBeenCalledTimes(0); | ||||
|       expect(setFailedSpy).not.toHaveBeenCalled(); | ||||
|     }); | ||||
|   }); | ||||
| @@ -183,24 +192,22 @@ describe('run', () => { | ||||
|   describe('action saves the cache', () => { | ||||
|     it('saves cache from yarn 1', async () => { | ||||
|       inputs['cache'] = 'yarn'; | ||||
|       getStateSpy.mockImplementation((name: string) => { | ||||
|         if (name === State.CacheMatchedKey) { | ||||
|           return yarnFileHash; | ||||
|         } else { | ||||
|           return npmFileHash; | ||||
|         } | ||||
|       }); | ||||
|       getCommandOutputSpy | ||||
|         .mockImplementationOnce(() => '1.2.3') | ||||
|         .mockImplementationOnce(() => `${commonPath}/yarn1`); | ||||
|       getStateSpy.mockImplementation((key: string) => | ||||
|         key === State.CacheMatchedKey | ||||
|           ? yarnFileHash | ||||
|           : key === State.CachePrimaryKey | ||||
|           ? npmFileHash | ||||
|           : key === State.CachePaths | ||||
|           ? '["/foo/bar"]' | ||||
|           : 'not expected' | ||||
|       ); | ||||
|  | ||||
|       await run(); | ||||
|  | ||||
|       expect(getInputSpy).toHaveBeenCalled(); | ||||
|       expect(getStateSpy).toHaveBeenCalledTimes(2); | ||||
|       expect(getCommandOutputSpy).toHaveBeenCalledTimes(2); | ||||
|       expect(debugSpy).toHaveBeenCalledWith(`yarn path is ${commonPath}/yarn1`); | ||||
|       expect(debugSpy).toHaveBeenCalledWith('Consumed yarn version is 1.2.3'); | ||||
|       expect(getStateSpy).toHaveBeenCalledTimes(3); | ||||
|       expect(getCommandOutputSpy).toHaveBeenCalledTimes(0); | ||||
|       expect(debugSpy).toHaveBeenCalledTimes(0); | ||||
|       expect(infoSpy).not.toHaveBeenCalledWith( | ||||
|         `Cache hit occurred on the primary key ${yarnFileHash}, not saving cache.` | ||||
|       ); | ||||
| @@ -213,24 +220,22 @@ describe('run', () => { | ||||
|  | ||||
|     it('saves cache from yarn 2', async () => { | ||||
|       inputs['cache'] = 'yarn'; | ||||
|       getStateSpy.mockImplementation((name: string) => { | ||||
|         if (name === State.CacheMatchedKey) { | ||||
|           return yarnFileHash; | ||||
|         } else { | ||||
|           return npmFileHash; | ||||
|         } | ||||
|       }); | ||||
|       getCommandOutputSpy | ||||
|         .mockImplementationOnce(() => '2.2.3') | ||||
|         .mockImplementationOnce(() => `${commonPath}/yarn2`); | ||||
|       getStateSpy.mockImplementation((key: string) => | ||||
|         key === State.CacheMatchedKey | ||||
|           ? yarnFileHash | ||||
|           : key === State.CachePrimaryKey | ||||
|           ? npmFileHash | ||||
|           : key === State.CachePaths | ||||
|           ? '["/foo/bar"]' | ||||
|           : 'not expected' | ||||
|       ); | ||||
|  | ||||
|       await run(); | ||||
|  | ||||
|       expect(getInputSpy).toHaveBeenCalled(); | ||||
|       expect(getStateSpy).toHaveBeenCalledTimes(2); | ||||
|       expect(getCommandOutputSpy).toHaveBeenCalledTimes(2); | ||||
|       expect(debugSpy).toHaveBeenCalledWith(`yarn path is ${commonPath}/yarn2`); | ||||
|       expect(debugSpy).toHaveBeenCalledWith('Consumed yarn version is 2.2.3'); | ||||
|       expect(getStateSpy).toHaveBeenCalledTimes(3); | ||||
|       expect(getCommandOutputSpy).toHaveBeenCalledTimes(0); | ||||
|       expect(debugSpy).toHaveBeenCalledTimes(0); | ||||
|       expect(infoSpy).not.toHaveBeenCalledWith( | ||||
|         `Cache hit occurred on the primary key ${yarnFileHash}, not saving cache.` | ||||
|       ); | ||||
| @@ -243,21 +248,22 @@ describe('run', () => { | ||||
|  | ||||
|     it('saves cache from npm', async () => { | ||||
|       inputs['cache'] = 'npm'; | ||||
|       getStateSpy.mockImplementation((name: string) => { | ||||
|         if (name === State.CacheMatchedKey) { | ||||
|           return npmFileHash; | ||||
|         } else { | ||||
|           return yarnFileHash; | ||||
|         } | ||||
|       }); | ||||
|       getCommandOutputSpy.mockImplementationOnce(() => `${commonPath}/npm`); | ||||
|       getStateSpy.mockImplementation((key: string) => | ||||
|         key === State.CacheMatchedKey | ||||
|           ? npmFileHash | ||||
|           : key === State.CachePrimaryKey | ||||
|           ? yarnFileHash | ||||
|           : key === State.CachePaths | ||||
|           ? '["/foo/bar"]' | ||||
|           : 'not expected' | ||||
|       ); | ||||
|  | ||||
|       await run(); | ||||
|  | ||||
|       expect(getInputSpy).toHaveBeenCalled(); | ||||
|       expect(getStateSpy).toHaveBeenCalledTimes(2); | ||||
|       expect(getCommandOutputSpy).toHaveBeenCalledTimes(1); | ||||
|       expect(debugSpy).toHaveBeenCalledWith(`npm path is ${commonPath}/npm`); | ||||
|       expect(getStateSpy).toHaveBeenCalledTimes(3); | ||||
|       expect(getCommandOutputSpy).toHaveBeenCalledTimes(0); | ||||
|       expect(debugSpy).toHaveBeenCalledTimes(0); | ||||
|       expect(infoSpy).not.toHaveBeenCalledWith( | ||||
|         `Cache hit occurred on the primary key ${npmFileHash}, not saving cache.` | ||||
|       ); | ||||
| @@ -270,21 +276,22 @@ describe('run', () => { | ||||
|  | ||||
|     it('saves cache from pnpm', async () => { | ||||
|       inputs['cache'] = 'pnpm'; | ||||
|       getStateSpy.mockImplementation((name: string) => { | ||||
|         if (name === State.CacheMatchedKey) { | ||||
|           return pnpmFileHash; | ||||
|         } else { | ||||
|           return npmFileHash; | ||||
|         } | ||||
|       }); | ||||
|       getCommandOutputSpy.mockImplementationOnce(() => `${commonPath}/pnpm`); | ||||
|       getStateSpy.mockImplementation((key: string) => | ||||
|         key === State.CacheMatchedKey | ||||
|           ? pnpmFileHash | ||||
|           : key === State.CachePrimaryKey | ||||
|           ? npmFileHash | ||||
|           : key === State.CachePaths | ||||
|           ? '["/foo/bar"]' | ||||
|           : 'not expected' | ||||
|       ); | ||||
|  | ||||
|       await run(); | ||||
|  | ||||
|       expect(getInputSpy).toHaveBeenCalled(); | ||||
|       expect(getStateSpy).toHaveBeenCalledTimes(2); | ||||
|       expect(getCommandOutputSpy).toHaveBeenCalledTimes(1); | ||||
|       expect(debugSpy).toHaveBeenCalledWith(`pnpm path is ${commonPath}/pnpm`); | ||||
|       expect(getStateSpy).toHaveBeenCalledTimes(3); | ||||
|       expect(getCommandOutputSpy).toHaveBeenCalledTimes(0); | ||||
|       expect(debugSpy).toHaveBeenCalledTimes(0); | ||||
|       expect(infoSpy).not.toHaveBeenCalledWith( | ||||
|         `Cache hit occurred on the primary key ${pnpmFileHash}, not saving cache.` | ||||
|       ); | ||||
| @@ -297,14 +304,15 @@ describe('run', () => { | ||||
|  | ||||
|     it('save with -1 cacheId , should not fail workflow', async () => { | ||||
|       inputs['cache'] = 'npm'; | ||||
|       getStateSpy.mockImplementation((name: string) => { | ||||
|         if (name === State.CacheMatchedKey) { | ||||
|           return npmFileHash; | ||||
|         } else { | ||||
|           return yarnFileHash; | ||||
|         } | ||||
|       }); | ||||
|       getCommandOutputSpy.mockImplementationOnce(() => `${commonPath}/npm`); | ||||
|       getStateSpy.mockImplementation((key: string) => | ||||
|         key === State.CacheMatchedKey | ||||
|           ? npmFileHash | ||||
|           : key === State.CachePrimaryKey | ||||
|           ? yarnFileHash | ||||
|           : key === State.CachePaths | ||||
|           ? '["/foo/bar"]' | ||||
|           : 'not expected' | ||||
|       ); | ||||
|       saveCacheSpy.mockImplementation(() => { | ||||
|         return -1; | ||||
|       }); | ||||
| @@ -312,9 +320,9 @@ describe('run', () => { | ||||
|       await run(); | ||||
|  | ||||
|       expect(getInputSpy).toHaveBeenCalled(); | ||||
|       expect(getStateSpy).toHaveBeenCalledTimes(2); | ||||
|       expect(getCommandOutputSpy).toHaveBeenCalledTimes(1); | ||||
|       expect(debugSpy).toHaveBeenCalledWith(`npm path is ${commonPath}/npm`); | ||||
|       expect(getStateSpy).toHaveBeenCalledTimes(3); | ||||
|       expect(getCommandOutputSpy).toHaveBeenCalledTimes(0); | ||||
|       expect(debugSpy).toHaveBeenCalledTimes(0); | ||||
|       expect(infoSpy).not.toHaveBeenCalledWith( | ||||
|         `Cache hit occurred on the primary key ${npmFileHash}, not saving cache.` | ||||
|       ); | ||||
| @@ -327,14 +335,15 @@ describe('run', () => { | ||||
|  | ||||
|     it('saves with error from toolkit, should fail workflow', async () => { | ||||
|       inputs['cache'] = 'npm'; | ||||
|       getStateSpy.mockImplementation((name: string) => { | ||||
|         if (name === State.CacheMatchedKey) { | ||||
|           return npmFileHash; | ||||
|         } else { | ||||
|           return yarnFileHash; | ||||
|         } | ||||
|       }); | ||||
|       getCommandOutputSpy.mockImplementationOnce(() => `${commonPath}/npm`); | ||||
|       getStateSpy.mockImplementation((key: string) => | ||||
|         key === State.CacheMatchedKey | ||||
|           ? npmFileHash | ||||
|           : key === State.CachePrimaryKey | ||||
|           ? yarnFileHash | ||||
|           : key === State.CachePaths | ||||
|           ? '["/foo/bar"]' | ||||
|           : 'not expected' | ||||
|       ); | ||||
|       saveCacheSpy.mockImplementation(() => { | ||||
|         throw new cache.ValidationError('Validation failed'); | ||||
|       }); | ||||
| @@ -342,9 +351,9 @@ describe('run', () => { | ||||
|       await run(); | ||||
|  | ||||
|       expect(getInputSpy).toHaveBeenCalled(); | ||||
|       expect(getStateSpy).toHaveBeenCalledTimes(2); | ||||
|       expect(getCommandOutputSpy).toHaveBeenCalledTimes(1); | ||||
|       expect(debugSpy).toHaveBeenCalledWith(`npm path is ${commonPath}/npm`); | ||||
|       expect(getStateSpy).toHaveBeenCalledTimes(3); | ||||
|       expect(getCommandOutputSpy).toHaveBeenCalledTimes(0); | ||||
|       expect(debugSpy).toHaveBeenCalledTimes(0); | ||||
|       expect(infoSpy).not.toHaveBeenCalledWith( | ||||
|         `Cache hit occurred on the primary key ${npmFileHash}, not saving cache.` | ||||
|       ); | ||||
|   | ||||
| @@ -2,7 +2,17 @@ import * as core from '@actions/core'; | ||||
| import * as cache from '@actions/cache'; | ||||
| import path from 'path'; | ||||
| import * as utils from '../src/cache-utils'; | ||||
| import {PackageManagerInfo, isCacheFeatureAvailable} from '../src/cache-utils'; | ||||
| import { | ||||
|   PackageManagerInfo, | ||||
|   isCacheFeatureAvailable, | ||||
|   supportedPackageManagers, | ||||
|   getCommandOutput | ||||
| } from '../src/cache-utils'; | ||||
| import fs from 'fs'; | ||||
| import * as cacheUtils from '../src/cache-utils'; | ||||
| import * as glob from '@actions/glob'; | ||||
| import {Globber} from '@actions/glob'; | ||||
| import {MockGlobber} from './mock/glob-mock'; | ||||
|  | ||||
| describe('cache-utils', () => { | ||||
|   const versionYarn1 = '1.2.3'; | ||||
| @@ -30,7 +40,7 @@ describe('cache-utils', () => { | ||||
|     it.each<[string, PackageManagerInfo | null]>([ | ||||
|       ['npm', utils.supportedPackageManagers.npm], | ||||
|       ['pnpm', utils.supportedPackageManagers.pnpm], | ||||
|       ['yarn', utils.supportedPackageManagers.yarn1], | ||||
|       ['yarn', utils.supportedPackageManagers.yarn], | ||||
|       ['yarn1', null], | ||||
|       ['yarn2', null], | ||||
|       ['npm7', null] | ||||
| @@ -72,4 +82,261 @@ describe('cache-utils', () => { | ||||
|     jest.resetAllMocks(); | ||||
|     jest.clearAllMocks(); | ||||
|   }); | ||||
|  | ||||
|   describe('getCacheDirectoriesPaths', () => { | ||||
|     let existsSpy: jest.SpyInstance; | ||||
|     let lstatSpy: jest.SpyInstance; | ||||
|     let globCreateSpy: jest.SpyInstance; | ||||
|  | ||||
|     beforeEach(() => { | ||||
|       existsSpy = jest.spyOn(fs, 'existsSync'); | ||||
|       existsSpy.mockImplementation(() => true); | ||||
|  | ||||
|       lstatSpy = jest.spyOn(fs, 'lstatSync'); | ||||
|       lstatSpy.mockImplementation(arg => ({ | ||||
|         isDirectory: () => true | ||||
|       })); | ||||
|  | ||||
|       globCreateSpy = jest.spyOn(glob, 'create'); | ||||
|  | ||||
|       globCreateSpy.mockImplementation( | ||||
|         (pattern: string): Promise<Globber> => | ||||
|           MockGlobber.create(['/foo', '/bar']) | ||||
|       ); | ||||
|     }); | ||||
|  | ||||
|     afterEach(() => { | ||||
|       existsSpy.mockRestore(); | ||||
|       lstatSpy.mockRestore(); | ||||
|       globCreateSpy.mockRestore(); | ||||
|     }); | ||||
|  | ||||
|     it.each([ | ||||
|       [supportedPackageManagers.npm, ''], | ||||
|       [supportedPackageManagers.npm, '/dir/file.lock'], | ||||
|       [supportedPackageManagers.npm, '/**/file.lock'], | ||||
|       [supportedPackageManagers.pnpm, ''], | ||||
|       [supportedPackageManagers.pnpm, '/dir/file.lock'], | ||||
|       [supportedPackageManagers.pnpm, '/**/file.lock'] | ||||
|     ])( | ||||
|       'getCacheDirectoriesPaths should return one dir for non yarn', | ||||
|       async (packageManagerInfo, cacheDependency) => { | ||||
|         getCommandOutputSpy.mockImplementation(() => 'foo'); | ||||
|  | ||||
|         const dirs = await cacheUtils.getCacheDirectories( | ||||
|           packageManagerInfo, | ||||
|           cacheDependency | ||||
|         ); | ||||
|         expect(dirs).toEqual(['foo']); | ||||
|         // to do not call for a version | ||||
|         // call once for get cache folder | ||||
|         expect(getCommandOutputSpy).toHaveBeenCalledTimes(1); | ||||
|       } | ||||
|     ); | ||||
|  | ||||
|     it('getCacheDirectoriesPaths should return one dir for yarn without cacheDependency', async () => { | ||||
|       getCommandOutputSpy.mockImplementation(() => 'foo'); | ||||
|  | ||||
|       const dirs = await cacheUtils.getCacheDirectories( | ||||
|         supportedPackageManagers.yarn, | ||||
|         '' | ||||
|       ); | ||||
|       expect(dirs).toEqual(['foo']); | ||||
|     }); | ||||
|  | ||||
|     it.each([ | ||||
|       [supportedPackageManagers.npm, ''], | ||||
|       [supportedPackageManagers.npm, '/dir/file.lock'], | ||||
|       [supportedPackageManagers.npm, '/**/file.lock'], | ||||
|       [supportedPackageManagers.pnpm, ''], | ||||
|       [supportedPackageManagers.pnpm, '/dir/file.lock'], | ||||
|       [supportedPackageManagers.pnpm, '/**/file.lock'], | ||||
|       [supportedPackageManagers.yarn, ''], | ||||
|       [supportedPackageManagers.yarn, '/dir/file.lock'], | ||||
|       [supportedPackageManagers.yarn, '/**/file.lock'] | ||||
|     ])( | ||||
|       'getCacheDirectoriesPaths should throw for getCommandOutput returning empty', | ||||
|       async (packageManagerInfo, cacheDependency) => { | ||||
|         getCommandOutputSpy.mockImplementation((command: string) => | ||||
|           // return empty string to indicate getCacheFolderPath failed | ||||
|           //        --version still works | ||||
|           command.includes('version') ? '1.' : '' | ||||
|         ); | ||||
|  | ||||
|         await expect( | ||||
|           cacheUtils.getCacheDirectories(packageManagerInfo, cacheDependency) | ||||
|         ).rejects.toThrow(); //'Could not get cache folder path for /dir'); | ||||
|       } | ||||
|     ); | ||||
|  | ||||
|     it.each([ | ||||
|       [supportedPackageManagers.yarn, '/dir/file.lock'], | ||||
|       [supportedPackageManagers.yarn, '/**/file.lock'] | ||||
|     ])( | ||||
|       'getCacheDirectoriesPaths should nothrow in case of having not directories', | ||||
|       async (packageManagerInfo, cacheDependency) => { | ||||
|         lstatSpy.mockImplementation(arg => ({ | ||||
|           isDirectory: () => false | ||||
|         })); | ||||
|  | ||||
|         await cacheUtils.getCacheDirectories( | ||||
|           packageManagerInfo, | ||||
|           cacheDependency | ||||
|         ); | ||||
|         expect(warningSpy).toHaveBeenCalledTimes(1); | ||||
|         expect(warningSpy).toHaveBeenCalledWith( | ||||
|           `No existing directories found containing cache-dependency-path="${cacheDependency}"` | ||||
|         ); | ||||
|       } | ||||
|     ); | ||||
|  | ||||
|     it.each(['1.1.1', '2.2.2'])( | ||||
|       'getCacheDirectoriesPaths yarn v%s should return one dir without cacheDependency', | ||||
|       async version => { | ||||
|         getCommandOutputSpy.mockImplementationOnce(() => version); | ||||
|         getCommandOutputSpy.mockImplementationOnce(() => `foo${version}`); | ||||
|  | ||||
|         const dirs = await cacheUtils.getCacheDirectories( | ||||
|           supportedPackageManagers.yarn, | ||||
|           '' | ||||
|         ); | ||||
|         expect(dirs).toEqual([`foo${version}`]); | ||||
|       } | ||||
|     ); | ||||
|  | ||||
|     it.each(['1.1.1', '2.2.2'])( | ||||
|       'getCacheDirectoriesPaths yarn v%s should return 2 dirs with globbed cacheDependency', | ||||
|       async version => { | ||||
|         let dirNo = 1; | ||||
|         getCommandOutputSpy.mockImplementation((command: string) => | ||||
|           command.includes('version') ? version : `file_${version}_${dirNo++}` | ||||
|         ); | ||||
|         globCreateSpy.mockImplementation( | ||||
|           (pattern: string): Promise<Globber> => | ||||
|             MockGlobber.create(['/tmp/dir1/file', '/tmp/dir2/file']) | ||||
|         ); | ||||
|  | ||||
|         const dirs = await cacheUtils.getCacheDirectories( | ||||
|           supportedPackageManagers.yarn, | ||||
|           '/tmp/**/file' | ||||
|         ); | ||||
|         expect(dirs).toEqual([`file_${version}_1`, `file_${version}_2`]); | ||||
|       } | ||||
|     ); | ||||
|  | ||||
|     it.each(['1.1.1', '2.2.2'])( | ||||
|       'getCacheDirectoriesPaths yarn v%s should return 2 dirs  with globbed cacheDependency expanding to duplicates', | ||||
|       async version => { | ||||
|         let dirNo = 1; | ||||
|         getCommandOutputSpy.mockImplementation((command: string) => | ||||
|           command.includes('version') ? version : `file_${version}_${dirNo++}` | ||||
|         ); | ||||
|         globCreateSpy.mockImplementation( | ||||
|           (pattern: string): Promise<Globber> => | ||||
|             MockGlobber.create([ | ||||
|               '/tmp/dir1/file', | ||||
|               '/tmp/dir2/file', | ||||
|               '/tmp/dir1/file' | ||||
|             ]) | ||||
|         ); | ||||
|  | ||||
|         const dirs = await cacheUtils.getCacheDirectories( | ||||
|           supportedPackageManagers.yarn, | ||||
|           '/tmp/**/file' | ||||
|         ); | ||||
|         expect(dirs).toEqual([`file_${version}_1`, `file_${version}_2`]); | ||||
|       } | ||||
|     ); | ||||
|  | ||||
|     it.each(['1.1.1', '2.2.2'])( | ||||
|       'getCacheDirectoriesPaths yarn v%s should return 2 uniq dirs despite duplicate cache directories', | ||||
|       async version => { | ||||
|         let dirNo = 1; | ||||
|         getCommandOutputSpy.mockImplementation((command: string) => | ||||
|           command.includes('version') | ||||
|             ? version | ||||
|             : `file_${version}_${dirNo++ % 2}` | ||||
|         ); | ||||
|         globCreateSpy.mockImplementation( | ||||
|           (pattern: string): Promise<Globber> => | ||||
|             MockGlobber.create([ | ||||
|               '/tmp/dir1/file', | ||||
|               '/tmp/dir2/file', | ||||
|               '/tmp/dir3/file' | ||||
|             ]) | ||||
|         ); | ||||
|  | ||||
|         const dirs = await cacheUtils.getCacheDirectories( | ||||
|           supportedPackageManagers.yarn, | ||||
|           '/tmp/**/file' | ||||
|         ); | ||||
|         expect(dirs).toEqual([`file_${version}_1`, `file_${version}_0`]); | ||||
|         expect(getCommandOutputSpy).toHaveBeenCalledTimes(6); | ||||
|         expect(getCommandOutputSpy).toHaveBeenCalledWith( | ||||
|           'yarn --version', | ||||
|           '/tmp/dir1' | ||||
|         ); | ||||
|         expect(getCommandOutputSpy).toHaveBeenCalledWith( | ||||
|           'yarn --version', | ||||
|           '/tmp/dir2' | ||||
|         ); | ||||
|         expect(getCommandOutputSpy).toHaveBeenCalledWith( | ||||
|           'yarn --version', | ||||
|           '/tmp/dir3' | ||||
|         ); | ||||
|         expect(getCommandOutputSpy).toHaveBeenCalledWith( | ||||
|           version.startsWith('1.') | ||||
|             ? 'yarn cache dir' | ||||
|             : 'yarn config get cacheFolder', | ||||
|           '/tmp/dir1' | ||||
|         ); | ||||
|         expect(getCommandOutputSpy).toHaveBeenCalledWith( | ||||
|           version.startsWith('1.') | ||||
|             ? 'yarn cache dir' | ||||
|             : 'yarn config get cacheFolder', | ||||
|           '/tmp/dir2' | ||||
|         ); | ||||
|         expect(getCommandOutputSpy).toHaveBeenCalledWith( | ||||
|           version.startsWith('1.') | ||||
|             ? 'yarn cache dir' | ||||
|             : 'yarn config get cacheFolder', | ||||
|           '/tmp/dir3' | ||||
|         ); | ||||
|       } | ||||
|     ); | ||||
|  | ||||
|     it.each(['1.1.1', '2.2.2'])( | ||||
|       'getCacheDirectoriesPaths yarn v%s should return 4 dirs with multiple globs', | ||||
|       async version => { | ||||
|         // simulate wrong indents | ||||
|         const cacheDependencyPath = `/tmp/dir1/file | ||||
|           /tmp/dir2/file | ||||
| /tmp/**/file | ||||
|           `; | ||||
|         globCreateSpy.mockImplementation( | ||||
|           (pattern: string): Promise<Globber> => | ||||
|             MockGlobber.create([ | ||||
|               '/tmp/dir1/file', | ||||
|               '/tmp/dir2/file', | ||||
|               '/tmp/dir3/file', | ||||
|               '/tmp/dir4/file' | ||||
|             ]) | ||||
|         ); | ||||
|         let dirNo = 1; | ||||
|         getCommandOutputSpy.mockImplementation((command: string) => | ||||
|           command.includes('version') ? version : `file_${version}_${dirNo++}` | ||||
|         ); | ||||
|         const dirs = await cacheUtils.getCacheDirectories( | ||||
|           supportedPackageManagers.yarn, | ||||
|           cacheDependencyPath | ||||
|         ); | ||||
|         expect(dirs).toEqual([ | ||||
|           `file_${version}_1`, | ||||
|           `file_${version}_2`, | ||||
|           `file_${version}_3`, | ||||
|           `file_${version}_4` | ||||
|         ]); | ||||
|       } | ||||
|     ); | ||||
|   }); | ||||
| }); | ||||
|   | ||||
							
								
								
									
										18
									
								
								__tests__/mock/glob-mock.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								__tests__/mock/glob-mock.test.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| import {MockGlobber} from './glob-mock'; | ||||
|  | ||||
| describe('mocked globber tests', () => { | ||||
|   it('globber should return generator', async () => { | ||||
|     const globber = new MockGlobber(['aaa', 'bbb', 'ccc']); | ||||
|     const generator = globber.globGenerator(); | ||||
|     const result: string[] = []; | ||||
|     for await (const itemPath of generator) { | ||||
|       result.push(itemPath); | ||||
|     } | ||||
|     expect(result).toEqual(['aaa', 'bbb', 'ccc']); | ||||
|   }); | ||||
|   it('globber should return glob', async () => { | ||||
|     const globber = new MockGlobber(['aaa', 'bbb', 'ccc']); | ||||
|     const result: string[] = await globber.glob(); | ||||
|     expect(result).toEqual(['aaa', 'bbb', 'ccc']); | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										29
									
								
								__tests__/mock/glob-mock.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								__tests__/mock/glob-mock.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| import {Globber} from '@actions/glob'; | ||||
|  | ||||
| export class MockGlobber implements Globber { | ||||
|   private readonly expected: string[]; | ||||
|   constructor(expected: string[]) { | ||||
|     this.expected = expected; | ||||
|   } | ||||
|   getSearchPaths(): string[] { | ||||
|     return this.expected.slice(); | ||||
|   } | ||||
|  | ||||
|   async glob(): Promise<string[]> { | ||||
|     const result: string[] = []; | ||||
|     for await (const itemPath of this.globGenerator()) { | ||||
|       result.push(itemPath); | ||||
|     } | ||||
|     return result; | ||||
|   } | ||||
|  | ||||
|   async *globGenerator(): AsyncGenerator<string, void> { | ||||
|     for (const e of this.expected) { | ||||
|       yield e; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   static async create(expected: string[]): Promise<MockGlobber> { | ||||
|     return new MockGlobber(expected); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										48
									
								
								__tests__/prepare-subprojects.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										48
									
								
								__tests__/prepare-subprojects.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| #!/bin/sh -e | ||||
| export YARN_ENABLE_IMMUTABLE_INSTALLS=false | ||||
| rm package.json | ||||
| rm package-lock.json | ||||
| echo "create yarn2 project in the sub2" | ||||
| mkdir sub2 | ||||
| cd sub2 | ||||
| cat <<EOT >package.json | ||||
| { | ||||
|   "name": "subproject", | ||||
|   "dependencies": { | ||||
|     "random": "^3.0.6", | ||||
|     "uuid": "^9.0.0" | ||||
|   } | ||||
| } | ||||
| EOT | ||||
| yarn set version 2.4.3 | ||||
| yarn install | ||||
|  | ||||
| echo "create yarn3 project in the sub3" | ||||
| cd .. | ||||
| mkdir sub3 | ||||
| cd sub3 | ||||
| cat <<EOT >package.json | ||||
| { | ||||
|   "name": "subproject", | ||||
|   "dependencies": { | ||||
|     "random": "^3.0.6", | ||||
|     "uuid": "^9.0.0" | ||||
|   } | ||||
| } | ||||
| EOT | ||||
| yarn set version 3.5.1 | ||||
| yarn install | ||||
|  | ||||
| echo "create yarn1 project in the root" | ||||
| cd .. | ||||
| cat <<EOT >package.json | ||||
| { | ||||
|   "name": "subproject", | ||||
|   "dependencies": { | ||||
|     "random": "^3.0.6", | ||||
|     "uuid": "^9.0.0" | ||||
|   } | ||||
| } | ||||
| EOT | ||||
| yarn set version 1.22.19 | ||||
| yarn install | ||||
		Reference in New Issue
	
	Block a user
	 Sergey Dolin
					Sergey Dolin