mirror of
https://github.com/actions/setup-python
synced 2024-11-30 21:22:28 +08:00
8b89ef08a0
* Use correct Poetry config when collecting Poetry projects When collecting Poetry projects for caching, a '**/poetry.lock' glob is used. However, in order to process the Poetry configuration, the "poetry" command is run from the repo's root directory; this causes Poetry to return an invalid configuration when there is a Poetry project inside an inner directory. Instead of running a single Poetry command, glob for the same pattern, and run a Poetry command for every discovered project. * Fix typo: saveSatetSpy -> saveStateSpy * poetry: Support same virtualenv appearing in multiple projects * Add nested Poetry projects test * poetry: Set up environment for each project individually * tests/cache-restore: Do not look for dependency files outside `data` When the default dependency path is used for cache distributors, they are looking for the dependency file in the project's root (including the source code), which leads to tests taking a significant amount of time, especially on Windows runners. We thus hit sporadic test failures. Change the test cases such that dependency files are always searched for inside of `__tests__/data`, ignoring the rest of the project. * poetry: Simplify `virtualenvs.in-project` boolean check * README: Explain that poetry might create multiple caches * poetry: Run `poetry env use` only after cache is loaded The virtualenv cache might contain invalid entries, such as virtualenvs built in previous, buggy versions of this action. The `poetry env use` command will recreate virtualenvs in case they are invalid, but it has to be run only *after* the cache is loaded. Refactor `CacheDistributor` a bit such that the validation (and possible recreation) of virtualenvs happens only after the cache is loaded. * poetry: Bump cache primary key
314 lines
9.5 KiB
TypeScript
314 lines
9.5 KiB
TypeScript
import * as path from 'path';
|
|
import * as core from '@actions/core';
|
|
import * as cache from '@actions/cache';
|
|
import * as exec from '@actions/exec';
|
|
import * as io from '@actions/io';
|
|
import {getCacheDistributor} from '../src/cache-distributions/cache-factory';
|
|
import {State} from '../src/cache-distributions/cache-distributor';
|
|
import * as utils from './../src/utils';
|
|
|
|
describe('restore-cache', () => {
|
|
const pipFileLockHash =
|
|
'a3bdcc71289e4979ca9e051810d81999cc99823109faf6912e17ff14c8e621a6';
|
|
const requirementsHash =
|
|
'd8110e0006d7fb5ee76365d565eef9d37df1d11598b912d3eb66d398d57a1121';
|
|
const requirementsLinuxHash =
|
|
'2d0ff7f46b0e120e3d3294db65768b474934242637b9899b873e6283dfd16d7c';
|
|
const poetryLockHash =
|
|
'f24ea1ad73968e6c8d80c16a093ade72d9332c433aeef979a0dd943e6a99b2ab';
|
|
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
|
|
`;
|
|
|
|
// core spy
|
|
let infoSpy: jest.SpyInstance;
|
|
let warningSpy: jest.SpyInstance;
|
|
let debugSpy: jest.SpyInstance;
|
|
let saveStateSpy: jest.SpyInstance;
|
|
let getStateSpy: jest.SpyInstance;
|
|
let setOutputSpy: jest.SpyInstance;
|
|
|
|
// cache spy
|
|
let restoreCacheSpy: jest.SpyInstance;
|
|
|
|
// exec spy
|
|
let getExecOutputSpy: jest.SpyInstance;
|
|
|
|
// io spy
|
|
let whichSpy: jest.SpyInstance;
|
|
|
|
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);
|
|
|
|
saveStateSpy = jest.spyOn(core, 'saveState');
|
|
saveStateSpy.mockImplementation(input => undefined);
|
|
|
|
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};
|
|
}
|
|
if (input.includes('poetry')) {
|
|
return {stdout: poetryConfigOutput, stderr: '', exitCode: 0};
|
|
}
|
|
if (input.includes('lsb_release')) {
|
|
return {stdout: 'Ubuntu\n20.04', stderr: '', exitCode: 0};
|
|
}
|
|
|
|
return {stdout: '', stderr: 'Error occured', exitCode: 2};
|
|
});
|
|
|
|
setOutputSpy = jest.spyOn(core, 'setOutput');
|
|
setOutputSpy.mockImplementation(input => undefined);
|
|
|
|
restoreCacheSpy = jest.spyOn(cache, 'restoreCache');
|
|
restoreCacheSpy.mockImplementation(
|
|
(cachePaths: string[], primaryKey: string, restoreKey?: string) => {
|
|
return primaryKey;
|
|
}
|
|
);
|
|
|
|
whichSpy = jest.spyOn(io, 'which');
|
|
whichSpy.mockImplementation(() => '/path/to/python');
|
|
});
|
|
|
|
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)
|
|
).toThrowError(`Caching for '${packageManager}' is not supported`);
|
|
}
|
|
);
|
|
});
|
|
|
|
describe('Restore dependencies', () => {
|
|
it.each([
|
|
[
|
|
'pip',
|
|
'3.8.12',
|
|
'__tests__/data/**/requirements.txt',
|
|
requirementsHash,
|
|
undefined
|
|
],
|
|
[
|
|
'pip',
|
|
'3.8.12',
|
|
'__tests__/data/**/requirements-linux.txt',
|
|
requirementsLinuxHash,
|
|
undefined
|
|
],
|
|
[
|
|
'pip',
|
|
'3.8.12',
|
|
'__tests__/data/requirements-linux.txt',
|
|
requirementsLinuxHash,
|
|
undefined
|
|
],
|
|
[
|
|
'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')
|
|
]
|
|
]
|
|
])(
|
|
'restored dependencies for %s by primaryKey',
|
|
async (
|
|
packageManager,
|
|
pythonVersion,
|
|
dependencyFile,
|
|
fileHash,
|
|
cachePaths
|
|
) => {
|
|
const cacheDistributor = getCacheDistributor(
|
|
packageManager,
|
|
pythonVersion,
|
|
dependencyFile
|
|
);
|
|
|
|
await cacheDistributor.restoreCache();
|
|
|
|
if (cachePaths !== undefined) {
|
|
expect(saveStateSpy).toHaveBeenCalledWith(
|
|
State.CACHE_PATHS,
|
|
cachePaths
|
|
);
|
|
}
|
|
|
|
if (process.platform === 'linux' && packageManager === 'pip') {
|
|
expect(infoSpy).toHaveBeenCalledWith(
|
|
`Cache restored from key: setup-python-${process.env['RUNNER_OS']}-20.04-Ubuntu-python-${pythonVersion}-${packageManager}-${fileHash}`
|
|
);
|
|
} else if (packageManager === 'poetry') {
|
|
expect(infoSpy).toHaveBeenCalledWith(
|
|
`Cache restored from key: setup-python-${process.env['RUNNER_OS']}-python-${pythonVersion}-${packageManager}-v2-${fileHash}`
|
|
);
|
|
} else {
|
|
expect(infoSpy).toHaveBeenCalledWith(
|
|
`Cache restored from key: setup-python-${process.env['RUNNER_OS']}-python-${pythonVersion}-${packageManager}-${fileHash}`
|
|
);
|
|
}
|
|
},
|
|
30000
|
|
);
|
|
|
|
it.each([
|
|
['pip', '3.8.12', 'requirements-linux.txt', 'requirements-linux.txt'],
|
|
['pip', '3.8.12', 'requirements.txt', 'requirements.txt'],
|
|
['pipenv', '3.9.12', 'requirements.txt', 'requirements.txt']
|
|
])(
|
|
'Should throw an error because dependency file is not found',
|
|
async (
|
|
packageManager,
|
|
pythonVersion,
|
|
dependencyFile,
|
|
cacheDependencyPath
|
|
) => {
|
|
const cacheDistributor = getCacheDistributor(
|
|
packageManager,
|
|
pythonVersion,
|
|
dependencyFile
|
|
);
|
|
await expect(cacheDistributor.restoreCache()).rejects.toThrowError(
|
|
`No file in ${process.cwd()} matched to [${cacheDependencyPath
|
|
.split('\n')
|
|
.join(',')}], make sure you have checked out the target repository`
|
|
);
|
|
}
|
|
);
|
|
});
|
|
|
|
describe('Dependencies changed', () => {
|
|
it.each([
|
|
['pip', '3.8.12', '__tests__/data/**/requirements.txt', pipFileLockHash],
|
|
[
|
|
'pip',
|
|
'3.8.12',
|
|
'__tests__/data/**/requirements-linux.txt',
|
|
pipFileLockHash
|
|
],
|
|
[
|
|
'pip',
|
|
'3.8.12',
|
|
'__tests__/data/requirements-linux.txt',
|
|
pipFileLockHash
|
|
],
|
|
['pip', '3.8.12', '__tests__/data/requirements.txt', pipFileLockHash],
|
|
['pipenv', '3.9.1', '__tests__/data/**/Pipfile.lock', requirementsHash],
|
|
['pipenv', '3.9.12', '__tests__/data/requirements.txt', requirementsHash],
|
|
['poetry', '3.9.1', '__tests__/data/**/poetry.lock', requirementsHash]
|
|
])(
|
|
'restored dependencies for %s by primaryKey',
|
|
async (packageManager, pythonVersion, dependencyFile, fileHash) => {
|
|
restoreCacheSpy.mockImplementation(
|
|
(cachePaths: string[], primaryKey: string, restoreKey?: string) => {
|
|
return primaryKey !== fileHash && restoreKey ? pipFileLockHash : '';
|
|
}
|
|
);
|
|
const cacheDistributor = getCacheDistributor(
|
|
packageManager,
|
|
pythonVersion,
|
|
dependencyFile
|
|
);
|
|
await cacheDistributor.restoreCache();
|
|
let result = '';
|
|
|
|
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;
|
|
}
|
|
|
|
expect(infoSpy).toHaveBeenCalledWith(result);
|
|
}
|
|
);
|
|
});
|
|
|
|
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
|
|
);
|
|
}
|
|
);
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.resetAllMocks();
|
|
jest.clearAllMocks();
|
|
});
|
|
});
|