2023-01-03 18:13:00 +02:00
import * as path from 'path' ;
2021-11-17 13:31:22 +03:00
import * as core from '@actions/core' ;
import * as cache from '@actions/cache' ;
import * as exec from '@actions/exec' ;
2022-07-25 15:02:06 +02:00
import * as io from '@actions/io' ;
2021-11-17 13:31:22 +03:00
import { getCacheDistributor } from '../src/cache-distributions/cache-factory' ;
2023-01-03 18:13:00 +02:00
import { State } from '../src/cache-distributions/cache-distributor' ;
2021-11-17 13:31:22 +03:00
describe ( 'restore-cache' , () => {
const pipFileLockHash =
2023-07-13 14:11:40 +02:00
'f8428d7cf00ea53a5c3702f0a9cb3cc467f76cd86a34723009350c4e4b32751a' ;
2021-11-17 13:31:22 +03:00
const requirementsHash =
'd8110e0006d7fb5ee76365d565eef9d37df1d11598b912d3eb66d398d57a1121' ;
const requirementsLinuxHash =
'2d0ff7f46b0e120e3d3294db65768b474934242637b9899b873e6283dfd16d7c' ;
2022-03-06 20:30:49 -06:00
const poetryLockHash =
2023-01-03 18:13:00 +02:00
'f24ea1ad73968e6c8d80c16a093ade72d9332c433aeef979a0dd943e6a99b2ab' ;
2021-11-24 18:40:05 +00:00
const poetryConfigOutput = `
cache-dir = "/Users/patrick/Library/Caches/pypoetry"
experimental.new-installer = false
installer.parallel = true
virtualenvs.create = true
virtualenvs.in-project = true
virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/pypoetry/virtualenvs
` ;
2021-11-17 13:31:22 +03:00
// core spy
let infoSpy : jest.SpyInstance ;
let warningSpy : jest.SpyInstance ;
let debugSpy : jest.SpyInstance ;
2023-01-03 18:13:00 +02:00
let saveStateSpy : jest.SpyInstance ;
2021-11-17 13:31:22 +03:00
let getStateSpy : jest.SpyInstance ;
2022-04-05 06:57:13 -07:00
let setOutputSpy : jest.SpyInstance ;
2021-11-17 13:31:22 +03:00
// cache spy
let restoreCacheSpy : jest.SpyInstance ;
// exec spy
let getExecOutputSpy : jest.SpyInstance ;
2022-07-25 15:02:06 +02:00
// io spy
let whichSpy : jest.SpyInstance ;
2021-11-17 13:31:22 +03:00
beforeEach (() => {
process . env [ 'RUNNER_OS' ] = process . env [ 'RUNNER_OS' ] ?? 'linux' ;
infoSpy = jest . spyOn ( core , 'info' );
infoSpy . mockImplementation ( input => undefined );
warningSpy = jest . spyOn ( core , 'warning' );
warningSpy . mockImplementation ( input => undefined );
debugSpy = jest . spyOn ( core , 'debug' );
debugSpy . mockImplementation ( input => undefined );
2023-01-03 18:13:00 +02:00
saveStateSpy = jest . spyOn ( core , 'saveState' );
saveStateSpy . mockImplementation ( input => undefined );
2021-11-17 13:31:22 +03:00
getStateSpy = jest . spyOn ( core , 'getState' );
getStateSpy . mockImplementation ( input => undefined );
getExecOutputSpy = jest . spyOn ( exec , 'getExecOutput' );
getExecOutputSpy . mockImplementation (( input : string ) => {
if ( input . includes ( 'pip' )) {
return { stdout : 'pip' , stderr : '' , exitCode : 0 };
}
2021-11-24 18:40:05 +00:00
if ( input . includes ( 'poetry' )) {
return { stdout : poetryConfigOutput , stderr : '' , exitCode : 0 };
}
2022-12-07 18:12:42 +01:00
if ( input . includes ( 'lsb_release' )) {
return { stdout : 'Ubuntu\n20.04' , stderr : '' , exitCode : 0 };
}
2021-11-17 13:31:22 +03:00
return { stdout : '' , stderr : 'Error occured' , exitCode : 2 };
});
2022-04-05 06:57:13 -07:00
setOutputSpy = jest . spyOn ( core , 'setOutput' );
setOutputSpy . mockImplementation ( input => undefined );
2021-11-17 13:31:22 +03:00
restoreCacheSpy = jest . spyOn ( cache , 'restoreCache' );
restoreCacheSpy . mockImplementation (
( cachePaths : string [], primaryKey : string , restoreKey? : string ) => {
return primaryKey ;
}
);
2022-07-19 14:20:19 +02:00
2022-07-25 15:02:06 +02:00
whichSpy = jest . spyOn ( io , 'which' );
whichSpy . mockImplementation (() => '/path/to/python' );
2021-11-17 13:31:22 +03:00
});
describe ( 'Validate provided package manager' , () => {
it . each ([ 'npm' , 'pip2' , 'pip21' , 'pip21.3' , 'pipenv32' ])(
'Throw an error because %s is not supported' ,
async packageManager => {
expect (() =>
getCacheDistributor ( packageManager , '3.8.12' , undefined )
2023-03-09 12:44:56 +02:00
). toThrow ( `Caching for ' ${ packageManager } ' is not supported` );
2021-11-17 13:31:22 +03:00
}
);
});
describe ( 'Restore dependencies' , () => {
it . each ([
2023-01-03 18:13:00 +02:00
[
'pip' ,
'3.8.12' ,
'__tests__/data/**/requirements.txt' ,
requirementsHash ,
undefined
],
[
'pip' ,
'3.8.12' ,
'__tests__/data/**/requirements-linux.txt' ,
requirementsLinuxHash ,
undefined
],
2021-11-17 13:31:22 +03:00
[
'pip' ,
'3.8.12' ,
'__tests__/data/requirements-linux.txt' ,
2023-01-03 18:13:00 +02:00
requirementsLinuxHash ,
undefined
2021-11-17 13:31:22 +03:00
],
2023-01-03 18:13:00 +02:00
[
'pip' ,
'3.8.12' ,
'__tests__/data/requirements.txt' ,
requirementsHash ,
undefined
],
[
'pipenv' ,
'3.9.1' ,
'__tests__/data/**/Pipfile.lock' ,
pipFileLockHash ,
undefined
],
[
'pipenv' ,
'3.9.12' ,
'__tests__/data/requirements.txt' ,
requirementsHash ,
undefined
],
[
'poetry' ,
'3.9.1' ,
'__tests__/data/**/poetry.lock' ,
poetryLockHash ,
[
'/Users/patrick/Library/Caches/pypoetry/virtualenvs' ,
path . join ( __dirname , 'data' , 'inner' , '.venv' ),
path . join ( __dirname , 'data' , '.venv' )
]
]
2021-11-17 13:31:22 +03:00
])(
'restored dependencies for %s by primaryKey' ,
2023-01-03 18:13:00 +02:00
async (
packageManager ,
pythonVersion ,
dependencyFile ,
fileHash ,
cachePaths
) => {
2023-04-04 16:18:24 +02:00
restoreCacheSpy . mockImplementation (
( cachePaths : string [], primaryKey : string , restoreKey? : string ) => {
return primaryKey . includes ( fileHash ) ? primaryKey : '' ;
}
);
2022-04-05 06:57:13 -07:00
const cacheDistributor = getCacheDistributor (
2021-11-17 13:31:22 +03:00
packageManager ,
pythonVersion ,
dependencyFile
);
2022-07-19 14:20:19 +02:00
2021-11-17 13:31:22 +03:00
await cacheDistributor . restoreCache ();
2023-01-03 18:13:00 +02:00
if ( cachePaths !== undefined ) {
expect ( saveStateSpy ). toHaveBeenCalledWith (
State . CACHE_PATHS ,
cachePaths
);
}
2024-08-14 00:33:23 +05:30
const restoredKeys = restoreCacheSpy . mock . results . map (
result => result . value
);
restoredKeys . forEach ( restoredKey => {
if ( restoredKey ) {
if ( process . platform === 'linux' && packageManager === 'pip' ) {
expect ( infoSpy ). toHaveBeenCalledWith (
`Cache restored from key: setup-python- ${ process . env [ 'RUNNER_OS' ] } - ${ process . arch } -20.04-Ubuntu-python- ${ pythonVersion } - ${ packageManager } - ${ fileHash } `
);
} else if ( packageManager === 'poetry' ) {
expect ( infoSpy ). toHaveBeenCalledWith (
`Cache restored from key: setup-python- ${ process . env [ 'RUNNER_OS' ] } - ${ process . arch } -python- ${ pythonVersion } - ${ packageManager } -v2- ${ fileHash } `
);
} else {
expect ( infoSpy ). toHaveBeenCalledWith (
`Cache restored from key: setup-python- ${ process . env [ 'RUNNER_OS' ] } - ${ process . arch } -python- ${ pythonVersion } - ${ packageManager } - ${ fileHash } `
);
}
} else {
expect ( infoSpy ). toHaveBeenCalledWith (
` ${ packageManager } cache is not found`
);
}
});
2022-02-04 14:00:41 +03:00
},
30000
2021-11-17 13:31:22 +03:00
);
2023-02-20 13:36:57 +01:00
it . each ([[ 'pipenv' , '3.9.12' , 'requirements.txt' , 'requirements.txt' ]])(
2021-11-17 13:31:22 +03:00
'Should throw an error because dependency file is not found' ,
async (
packageManager ,
pythonVersion ,
dependencyFile ,
cacheDependencyPath
) => {
2022-04-05 06:57:13 -07:00
const cacheDistributor = getCacheDistributor (
2021-11-17 13:31:22 +03:00
packageManager ,
pythonVersion ,
dependencyFile
);
2023-02-20 13:36:57 +01:00
2023-03-09 12:44:56 +02:00
await expect ( cacheDistributor . restoreCache ()). rejects . toThrow (
2021-11-17 13:31:22 +03:00
`No file in ${ process . cwd () } matched to [ ${ cacheDependencyPath
. split ( '\n' )
. join ( ',' ) } ], make sure you have checked out the target repository`
);
}
);
2023-02-20 13:36:57 +01:00
it . each ([
[ 'pip' , '3.8.12' , 'requirements-linux.txt' ],
[ 'pip' , '3.8.12' , 'requirements.txt' ]
])(
'Shouldn`t throw an error as there is a default file `pyproject.toml` to use when requirements.txt is not specified' ,
async ( packageManager , pythonVersion , dependencyFile ) => {
const cacheDistributor = getCacheDistributor (
packageManager ,
pythonVersion ,
dependencyFile
);
2023-03-09 12:44:56 +02:00
await expect ( cacheDistributor . restoreCache ()). resolves . not . toThrow ();
2023-02-20 13:36:57 +01:00
}
);
it . each ([
[ 'pip' , '3.8.12' , 'requirements-linux.txt' ],
[ 'pip' , '3.8.12' , 'requirements.txt' ]
])(
'Should throw an error as there is no default file `pyproject.toml` to use when requirements.txt is not specified' ,
async ( packageManager , pythonVersion , dependencyFile ) => {
const cacheDistributor = getCacheDistributor (
packageManager ,
pythonVersion ,
dependencyFile
2023-03-09 12:44:56 +02:00
) as any ; // Widening PipCache | PipenvCache | PoetryCache type to any allow us to change private property on the cacheDistributor to test value: "**/pyprojecttest.toml"
cacheDistributor . cacheDependencyBackupPath = '**/pyprojecttest.toml' ;
await expect ( cacheDistributor . restoreCache ()). rejects . toThrow ();
2023-02-20 13:36:57 +01:00
}
);
2021-11-17 13:31:22 +03:00
});
describe ( 'Dependencies changed' , () => {
it . each ([
2023-01-03 18:13:00 +02:00
[ 'pip' , '3.8.12' , '__tests__/data/**/requirements.txt' , pipFileLockHash ],
[
'pip' ,
'3.8.12' ,
'__tests__/data/**/requirements-linux.txt' ,
pipFileLockHash
],
2021-11-17 13:31:22 +03:00
[
'pip' ,
'3.8.12' ,
'__tests__/data/requirements-linux.txt' ,
pipFileLockHash
],
[ 'pip' , '3.8.12' , '__tests__/data/requirements.txt' , pipFileLockHash ],
2023-01-03 18:13:00 +02:00
[ 'pipenv' , '3.9.1' , '__tests__/data/**/Pipfile.lock' , requirementsHash ],
2021-11-24 18:40:05 +00:00
[ 'pipenv' , '3.9.12' , '__tests__/data/requirements.txt' , requirementsHash ],
2023-01-03 18:13:00 +02:00
[ 'poetry' , '3.9.1' , '__tests__/data/**/poetry.lock' , requirementsHash ]
2021-11-17 13:31:22 +03:00
])(
'restored dependencies for %s by primaryKey' ,
async ( packageManager , pythonVersion , dependencyFile , fileHash ) => {
restoreCacheSpy . mockImplementation (
( cachePaths : string [], primaryKey : string , restoreKey? : string ) => {
return primaryKey !== fileHash && restoreKey ? pipFileLockHash : '' ;
}
);
2022-04-05 06:57:13 -07:00
const cacheDistributor = getCacheDistributor (
2021-11-17 13:31:22 +03:00
packageManager ,
pythonVersion ,
dependencyFile
);
await cacheDistributor . restoreCache ();
let result = '' ;
2021-11-24 18:40:05 +00:00
switch ( packageManager ) {
case 'pip' :
result = `Cache restored from key: ${ fileHash } ` ;
break ;
case 'pipenv' :
result = 'pipenv cache is not found' ;
break ;
case 'poetry' :
result = 'poetry cache is not found' ;
break ;
2021-11-17 13:31:22 +03:00
}
expect ( infoSpy ). toHaveBeenCalledWith ( result );
}
);
});
2022-04-05 06:57:13 -07:00
describe ( 'Check if handleMatchResult' , () => {
it . each ([
[ 'pip' , '3.8.12' , 'requirements.txt' , 'someKey' , 'someKey' , true ],
[ 'pipenv' , '3.9.1' , 'requirements.txt' , 'someKey' , 'someKey' , true ],
[ 'poetry' , '3.8.12' , 'requirements.txt' , 'someKey' , 'someKey' , true ],
[ 'pip' , '3.9.2' , 'requirements.txt' , undefined , 'someKey' , false ],
[ 'pipenv' , '3.8.12' , 'requirements.txt' , undefined , 'someKey' , false ],
[ 'poetry' , '3.9.12' , 'requirements.txt' , undefined , 'someKey' , false ]
])(
'sets correct outputs' ,
async (
packageManager ,
pythonVersion ,
dependencyFile ,
matchedKey ,
restoredKey ,
expectedOutputValue
) => {
const cacheDistributor = getCacheDistributor (
packageManager ,
pythonVersion ,
dependencyFile
);
cacheDistributor . handleMatchResult ( matchedKey , restoredKey );
expect ( setOutputSpy ). toHaveBeenCalledWith (
'cache-hit' ,
expectedOutputValue
);
}
);
});
2021-11-17 13:31:22 +03:00
afterEach (() => {
jest . resetAllMocks ();
jest . clearAllMocks ();
});
});