mirror of
https://github.com/actions/setup-python.git
synced 2026-07-16 06:32:19 +02:00
Migrate to ESM and upgrade dependencies (#1330)
* Migrate to ESM and upgrade dependencies * Add ESM migration note to README for V7 * Remove unnecessary devDependencies: ts-node, @types/jest * npm audit fix * Upgrade @types/node to version 26.0.0 * Clarify ESM migration details in README for V7 * Update README and dependencies * Fix lint issue
This commit is contained in:
+106
-49
@@ -1,10 +1,62 @@
|
||||
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
|
||||
import {fileURLToPath} from 'url';
|
||||
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';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// Mock @actions modules before importing anything that depends on them
|
||||
jest.unstable_mockModule('@actions/core', () => ({
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
notice: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
setOutput: jest.fn(),
|
||||
getInput: jest.fn(),
|
||||
getBooleanInput: jest.fn(),
|
||||
getMultilineInput: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
exportVariable: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn(),
|
||||
setSecret: jest.fn(),
|
||||
isDebug: jest.fn(() => false),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn(),
|
||||
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
|
||||
toPlatformPath: jest.fn((p: string) => p),
|
||||
toWin32Path: jest.fn((p: string) => p),
|
||||
toPosixPath: jest.fn((p: string) => p)
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/cache', () => ({
|
||||
saveCache: jest.fn(),
|
||||
restoreCache: jest.fn(),
|
||||
isFeatureAvailable: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/exec', () => ({
|
||||
exec: jest.fn(),
|
||||
getExecOutput: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/io', () => ({
|
||||
which: jest.fn(),
|
||||
mkdirP: jest.fn(),
|
||||
rmRF: jest.fn(),
|
||||
mv: jest.fn(),
|
||||
cp: jest.fn()
|
||||
}));
|
||||
|
||||
// Dynamic imports after mocking
|
||||
const core = await import('@actions/core');
|
||||
const cache = await import('@actions/cache');
|
||||
const exec = await import('@actions/exec');
|
||||
const io = await import('@actions/io');
|
||||
const {getCacheDistributor} =
|
||||
await import('../src/cache-distributions/cache-factory.js');
|
||||
const {State} = await import('../src/cache-distributions/cache-distributor.js');
|
||||
|
||||
describe('restore-cache', () => {
|
||||
const pipFileLockHash =
|
||||
@@ -24,43 +76,38 @@ 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;
|
||||
let infoSpy: jest.Mock;
|
||||
let warningSpy: jest.Mock;
|
||||
let debugSpy: jest.Mock;
|
||||
let saveStateSpy: jest.Mock;
|
||||
let getStateSpy: jest.Mock;
|
||||
let setOutputSpy: jest.Mock;
|
||||
let restoreCacheSpy: jest.Mock;
|
||||
let getExecOutputSpy: jest.Mock;
|
||||
let whichSpy: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
process.env['RUNNER_OS'] = process.env['RUNNER_OS'] ?? 'linux';
|
||||
|
||||
infoSpy = jest.spyOn(core, 'info');
|
||||
infoSpy.mockImplementation(input => undefined);
|
||||
infoSpy = core.info as jest.Mock;
|
||||
infoSpy.mockImplementation(() => undefined);
|
||||
|
||||
warningSpy = jest.spyOn(core, 'warning');
|
||||
warningSpy.mockImplementation(input => undefined);
|
||||
warningSpy = core.warning as jest.Mock;
|
||||
warningSpy.mockImplementation(() => undefined);
|
||||
|
||||
debugSpy = jest.spyOn(core, 'debug');
|
||||
debugSpy.mockImplementation(input => undefined);
|
||||
debugSpy = core.debug as jest.Mock;
|
||||
debugSpy.mockImplementation(() => undefined);
|
||||
|
||||
saveStateSpy = jest.spyOn(core, 'saveState');
|
||||
saveStateSpy.mockImplementation(input => undefined);
|
||||
saveStateSpy = core.saveState as jest.Mock;
|
||||
saveStateSpy.mockImplementation(() => undefined);
|
||||
|
||||
getStateSpy = jest.spyOn(core, 'getState');
|
||||
getStateSpy.mockImplementation(input => undefined);
|
||||
getStateSpy = core.getState as jest.Mock;
|
||||
getStateSpy.mockImplementation(() => undefined);
|
||||
|
||||
getExecOutputSpy = jest.spyOn(exec, 'getExecOutput');
|
||||
getExecOutputSpy.mockImplementation((input: string) => {
|
||||
getExecOutputSpy = exec.getExecOutput as jest.Mock;
|
||||
(
|
||||
getExecOutputSpy as jest.Mock<typeof exec.getExecOutput>
|
||||
).mockImplementation(async (input: string) => {
|
||||
if (input.includes('pip')) {
|
||||
return {stdout: 'pip', stderr: '', exitCode: 0};
|
||||
}
|
||||
@@ -74,17 +121,19 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py
|
||||
return {stdout: '', stderr: 'Error occured', exitCode: 2};
|
||||
});
|
||||
|
||||
setOutputSpy = jest.spyOn(core, 'setOutput');
|
||||
setOutputSpy.mockImplementation(input => undefined);
|
||||
setOutputSpy = core.setOutput as jest.Mock;
|
||||
setOutputSpy.mockImplementation(() => undefined);
|
||||
|
||||
restoreCacheSpy = jest.spyOn(cache, 'restoreCache');
|
||||
restoreCacheSpy.mockImplementation(
|
||||
(cachePaths: string[], primaryKey: string, restoreKey?: string) => {
|
||||
return primaryKey;
|
||||
restoreCacheSpy = cache.restoreCache as jest.Mock;
|
||||
(
|
||||
restoreCacheSpy as jest.Mock<typeof cache.restoreCache>
|
||||
).mockImplementation(
|
||||
(cachePaths: string[], primaryKey: string, restoreKey?: string[]) => {
|
||||
return Promise.resolve(primaryKey);
|
||||
}
|
||||
);
|
||||
|
||||
whichSpy = jest.spyOn(io, 'which');
|
||||
whichSpy = io.which as jest.Mock;
|
||||
whichSpy.mockImplementation(() => '/path/to/python');
|
||||
});
|
||||
|
||||
@@ -163,9 +212,13 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py
|
||||
fileHash,
|
||||
cachePaths
|
||||
) => {
|
||||
restoreCacheSpy.mockImplementation(
|
||||
(cachePaths: string[], primaryKey: string, restoreKey?: string) => {
|
||||
return primaryKey.includes(fileHash) ? primaryKey : '';
|
||||
(
|
||||
restoreCacheSpy as jest.Mock<typeof cache.restoreCache>
|
||||
).mockImplementation(
|
||||
(cachePaths: string[], primaryKey: string, restoreKey?: string[]) => {
|
||||
return Promise.resolve(
|
||||
primaryKey.includes(fileHash) ? primaryKey : ''
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -184,8 +237,8 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py
|
||||
);
|
||||
}
|
||||
|
||||
const restoredKeys = restoreCacheSpy.mock.results.map(
|
||||
result => result.value
|
||||
const restoredKeys = await Promise.all(
|
||||
restoreCacheSpy.mock.results.map(result => result.value)
|
||||
);
|
||||
|
||||
restoredKeys.forEach(restoredKey => {
|
||||
@@ -284,9 +337,13 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py
|
||||
])(
|
||||
'restored dependencies for %s by primaryKey',
|
||||
async (packageManager, pythonVersion, dependencyFile, fileHash) => {
|
||||
restoreCacheSpy.mockImplementation(
|
||||
(cachePaths: string[], primaryKey: string, restoreKey?: string) => {
|
||||
return primaryKey !== fileHash && restoreKey ? pipFileLockHash : '';
|
||||
(
|
||||
restoreCacheSpy as jest.Mock<typeof cache.restoreCache>
|
||||
).mockImplementation(
|
||||
(cachePaths: string[], primaryKey: string, restoreKey?: string[]) => {
|
||||
return Promise.resolve(
|
||||
primaryKey !== fileHash && restoreKey ? pipFileLockHash : ''
|
||||
);
|
||||
}
|
||||
);
|
||||
const cacheDistributor = getCacheDistributor(
|
||||
|
||||
+143
-79
@@ -1,8 +1,61 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as cache from '@actions/cache';
|
||||
import * as exec from '@actions/exec';
|
||||
import {run} from '../src/cache-save';
|
||||
import {State} from '../src/cache-distributions/cache-distributor';
|
||||
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
|
||||
import {fileURLToPath} from 'url';
|
||||
import path from 'path';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// Mock @actions modules before importing anything that depends on them
|
||||
jest.unstable_mockModule('@actions/core', () => ({
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
notice: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
setOutput: jest.fn(),
|
||||
getInput: jest.fn(),
|
||||
getBooleanInput: jest.fn(),
|
||||
getMultilineInput: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
exportVariable: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn(),
|
||||
setSecret: jest.fn(),
|
||||
isDebug: jest.fn(() => false),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn(),
|
||||
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
|
||||
toPlatformPath: jest.fn((p: string) => p),
|
||||
toWin32Path: jest.fn((p: string) => p),
|
||||
toPosixPath: jest.fn((p: string) => p)
|
||||
}));
|
||||
|
||||
class MockValidationError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'ValidationError';
|
||||
}
|
||||
}
|
||||
|
||||
jest.unstable_mockModule('@actions/cache', () => ({
|
||||
saveCache: jest.fn(),
|
||||
restoreCache: jest.fn(),
|
||||
isFeatureAvailable: jest.fn(),
|
||||
ValidationError: MockValidationError,
|
||||
ReserveCacheError: MockValidationError
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/exec', () => ({
|
||||
exec: jest.fn(),
|
||||
getExecOutput: jest.fn()
|
||||
}));
|
||||
|
||||
// Dynamic imports after mocking
|
||||
const core = await import('@actions/core');
|
||||
const cache = await import('@actions/cache');
|
||||
const exec = await import('@actions/exec');
|
||||
const {run} = await import('../src/cache-save.js');
|
||||
const {State} = await import('../src/cache-distributions/cache-distributor.js');
|
||||
|
||||
describe('run', () => {
|
||||
const pipFileLockHash =
|
||||
@@ -14,53 +67,54 @@ describe('run', () => {
|
||||
const poetryLockHash =
|
||||
'571bf984f8d210e6a97f854e479fdd4a2b5af67b5fdac109ec337a0ea16e7836';
|
||||
|
||||
// core spy
|
||||
let infoSpy: jest.SpyInstance;
|
||||
let warningSpy: jest.SpyInstance;
|
||||
let debugSpy: jest.SpyInstance;
|
||||
let saveStateSpy: jest.SpyInstance;
|
||||
let getStateSpy: jest.SpyInstance;
|
||||
let getInputSpy: jest.SpyInstance;
|
||||
let setFailedSpy: jest.SpyInstance;
|
||||
|
||||
// cache spy
|
||||
let saveCacheSpy: jest.SpyInstance;
|
||||
|
||||
// exec spy
|
||||
let getExecOutputSpy: jest.SpyInstance;
|
||||
let infoSpy: jest.Mock;
|
||||
let warningSpy: jest.Mock;
|
||||
let debugSpy: jest.Mock;
|
||||
let saveStateSpy: jest.Mock;
|
||||
let getStateSpy: jest.Mock;
|
||||
let getInputSpy: jest.Mock;
|
||||
let setFailedSpy: jest.Mock;
|
||||
let saveCacheSpy: jest.Mock;
|
||||
let getExecOutputSpy: jest.Mock;
|
||||
|
||||
let inputs = {} as any;
|
||||
|
||||
beforeEach(() => {
|
||||
process.env['RUNNER_OS'] = process.env['RUNNER_OS'] ?? 'linux';
|
||||
|
||||
infoSpy = jest.spyOn(core, 'info');
|
||||
infoSpy.mockImplementation(input => undefined);
|
||||
infoSpy = core.info as jest.Mock;
|
||||
infoSpy.mockImplementation(() => undefined);
|
||||
|
||||
warningSpy = jest.spyOn(core, 'warning');
|
||||
warningSpy.mockImplementation(input => undefined);
|
||||
warningSpy = core.warning as jest.Mock;
|
||||
warningSpy.mockImplementation(() => undefined);
|
||||
|
||||
debugSpy = jest.spyOn(core, 'debug');
|
||||
debugSpy.mockImplementation(input => undefined);
|
||||
debugSpy = core.debug as jest.Mock;
|
||||
debugSpy.mockImplementation(() => undefined);
|
||||
|
||||
saveStateSpy = jest.spyOn(core, 'saveState');
|
||||
saveStateSpy.mockImplementation(input => undefined);
|
||||
saveStateSpy = core.saveState as jest.Mock;
|
||||
saveStateSpy.mockImplementation(() => undefined);
|
||||
|
||||
getStateSpy = jest.spyOn(core, 'getState');
|
||||
getStateSpy.mockImplementation(input => {
|
||||
if (input === State.CACHE_PATHS) {
|
||||
return JSON.stringify([__dirname]);
|
||||
getStateSpy = core.getState as jest.Mock;
|
||||
(getStateSpy as jest.Mock<typeof core.getState>).mockImplementation(
|
||||
(input: string) => {
|
||||
if (input === State.CACHE_PATHS) {
|
||||
return JSON.stringify([__dirname]);
|
||||
}
|
||||
return requirementsHash;
|
||||
}
|
||||
return requirementsHash;
|
||||
});
|
||||
);
|
||||
|
||||
setFailedSpy = jest.spyOn(core, 'setFailed');
|
||||
setFailedSpy = core.setFailed as jest.Mock;
|
||||
|
||||
getInputSpy = jest.spyOn(core, 'getInput');
|
||||
getInputSpy.mockImplementation(input => inputs[input]);
|
||||
getInputSpy = core.getInput as jest.Mock;
|
||||
(getInputSpy as jest.Mock<typeof core.getInput>).mockImplementation(
|
||||
(input: string) => inputs[input]
|
||||
);
|
||||
|
||||
getExecOutputSpy = jest.spyOn(exec, 'getExecOutput');
|
||||
getExecOutputSpy.mockImplementation((input: string) => {
|
||||
getExecOutputSpy = exec.getExecOutput as jest.Mock;
|
||||
(
|
||||
getExecOutputSpy as jest.Mock<typeof exec.getExecOutput>
|
||||
).mockImplementation(async (input: string) => {
|
||||
if (input.includes('pip')) {
|
||||
return {stdout: 'pip', stderr: '', exitCode: 0};
|
||||
}
|
||||
@@ -68,7 +122,7 @@ describe('run', () => {
|
||||
return {stdout: '', stderr: 'Error occured', exitCode: 2};
|
||||
});
|
||||
|
||||
saveCacheSpy = jest.spyOn(cache, 'saveCache');
|
||||
saveCacheSpy = cache.saveCache as jest.Mock;
|
||||
saveCacheSpy.mockImplementation(() => undefined);
|
||||
});
|
||||
|
||||
@@ -124,15 +178,17 @@ describe('run', () => {
|
||||
it('saves cache from pip', async () => {
|
||||
inputs['cache'] = 'pip';
|
||||
inputs['python-version'] = '3.10.0';
|
||||
getStateSpy.mockImplementation((name: string) => {
|
||||
if (name === State.CACHE_MATCHED_KEY) {
|
||||
return requirementsHash;
|
||||
} else if (name === State.CACHE_PATHS) {
|
||||
return JSON.stringify([__dirname]);
|
||||
} else {
|
||||
return pipFileLockHash;
|
||||
(getStateSpy as jest.Mock<typeof core.getState>).mockImplementation(
|
||||
(name: string) => {
|
||||
if (name === State.CACHE_MATCHED_KEY) {
|
||||
return requirementsHash;
|
||||
} else if (name === State.CACHE_PATHS) {
|
||||
return JSON.stringify([__dirname]);
|
||||
} else {
|
||||
return pipFileLockHash;
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
await run();
|
||||
|
||||
@@ -151,15 +207,17 @@ describe('run', () => {
|
||||
it('saves cache from pipenv', async () => {
|
||||
inputs['cache'] = 'pipenv';
|
||||
inputs['python-version'] = '3.10.0';
|
||||
getStateSpy.mockImplementation((name: string) => {
|
||||
if (name === State.CACHE_MATCHED_KEY) {
|
||||
return pipFileLockHash;
|
||||
} else if (name === State.CACHE_PATHS) {
|
||||
return JSON.stringify([__dirname]);
|
||||
} else {
|
||||
return requirementsHash;
|
||||
(getStateSpy as jest.Mock<typeof core.getState>).mockImplementation(
|
||||
(name: string) => {
|
||||
if (name === State.CACHE_MATCHED_KEY) {
|
||||
return pipFileLockHash;
|
||||
} else if (name === State.CACHE_PATHS) {
|
||||
return JSON.stringify([__dirname]);
|
||||
} else {
|
||||
return requirementsHash;
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
await run();
|
||||
|
||||
@@ -178,15 +236,17 @@ describe('run', () => {
|
||||
it('saves cache from poetry', async () => {
|
||||
inputs['cache'] = 'poetry';
|
||||
inputs['python-version'] = '3.10.0';
|
||||
getStateSpy.mockImplementation((name: string) => {
|
||||
if (name === State.CACHE_MATCHED_KEY) {
|
||||
return poetryLockHash;
|
||||
} else if (name === State.CACHE_PATHS) {
|
||||
return JSON.stringify([__dirname]);
|
||||
} else {
|
||||
return requirementsHash;
|
||||
(getStateSpy as jest.Mock<typeof core.getState>).mockImplementation(
|
||||
(name: string) => {
|
||||
if (name === State.CACHE_MATCHED_KEY) {
|
||||
return poetryLockHash;
|
||||
} else if (name === State.CACHE_PATHS) {
|
||||
return JSON.stringify([__dirname]);
|
||||
} else {
|
||||
return requirementsHash;
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
await run();
|
||||
|
||||
@@ -205,15 +265,17 @@ describe('run', () => {
|
||||
it('saves with -1 cacheId , should not fail workflow', async () => {
|
||||
inputs['cache'] = 'poetry';
|
||||
inputs['python-version'] = '3.10.0';
|
||||
getStateSpy.mockImplementation((name: string) => {
|
||||
if (name === State.STATE_CACHE_PRIMARY_KEY) {
|
||||
return poetryLockHash;
|
||||
} else if (name === State.CACHE_PATHS) {
|
||||
return JSON.stringify([__dirname]);
|
||||
} else {
|
||||
return requirementsHash;
|
||||
(getStateSpy as jest.Mock<typeof core.getState>).mockImplementation(
|
||||
(name: string) => {
|
||||
if (name === State.STATE_CACHE_PRIMARY_KEY) {
|
||||
return poetryLockHash;
|
||||
} else if (name === State.CACHE_PATHS) {
|
||||
return JSON.stringify([__dirname]);
|
||||
} else {
|
||||
return requirementsHash;
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
saveCacheSpy.mockImplementation(() => {
|
||||
return -1;
|
||||
@@ -234,15 +296,17 @@ describe('run', () => {
|
||||
it('saves with error from toolkit, should not fail the workflow', async () => {
|
||||
inputs['cache'] = 'npm';
|
||||
inputs['python-version'] = '3.10.0';
|
||||
getStateSpy.mockImplementation((name: string) => {
|
||||
if (name === State.STATE_CACHE_PRIMARY_KEY) {
|
||||
return poetryLockHash;
|
||||
} else if (name === State.CACHE_PATHS) {
|
||||
return JSON.stringify([__dirname]);
|
||||
} else {
|
||||
return requirementsHash;
|
||||
(getStateSpy as jest.Mock<typeof core.getState>).mockImplementation(
|
||||
(name: string) => {
|
||||
if (name === State.STATE_CACHE_PRIMARY_KEY) {
|
||||
return poetryLockHash;
|
||||
} else if (name === State.CACHE_PATHS) {
|
||||
return JSON.stringify([__dirname]);
|
||||
} else {
|
||||
return requirementsHash;
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
saveCacheSpy.mockImplementation(() => {
|
||||
throw new cache.ValidationError('Validation failed');
|
||||
|
||||
+141
-84
@@ -1,18 +1,70 @@
|
||||
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
|
||||
import {fileURLToPath} from 'url';
|
||||
import fs from 'fs';
|
||||
|
||||
import {HttpClient} from '@actions/http-client';
|
||||
import * as ifm from '@actions/http-client/lib/interfaces';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
import * as exec from '@actions/exec';
|
||||
import * as core from '@actions/core';
|
||||
|
||||
import * as path from 'path';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
import * as semver from 'semver';
|
||||
|
||||
import * as finder from '../src/find-graalpy';
|
||||
import {IGraalPyManifestRelease} from '../src/utils';
|
||||
// Mock @actions modules
|
||||
jest.unstable_mockModule('@actions/core', () => ({
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
notice: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
setOutput: jest.fn(),
|
||||
getInput: jest.fn(),
|
||||
getBooleanInput: jest.fn(),
|
||||
getMultilineInput: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
exportVariable: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn(),
|
||||
setSecret: jest.fn(),
|
||||
isDebug: jest.fn(() => false),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn(),
|
||||
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
|
||||
toPlatformPath: jest.fn((p: string) => p),
|
||||
toWin32Path: jest.fn((p: string) => p),
|
||||
toPosixPath: jest.fn((p: string) => p)
|
||||
}));
|
||||
|
||||
import manifestData from './data/graalpy.json';
|
||||
jest.unstable_mockModule('@actions/tool-cache', () => ({
|
||||
find: jest.fn(),
|
||||
findAllVersions: jest.fn(),
|
||||
downloadTool: jest.fn(),
|
||||
extractZip: jest.fn(),
|
||||
extractTar: jest.fn(),
|
||||
extract7z: jest.fn(),
|
||||
extractXar: jest.fn(),
|
||||
cacheDir: jest.fn(),
|
||||
cacheFile: jest.fn(),
|
||||
getManifestFromRepo: jest.fn(),
|
||||
findFromManifest: jest.fn(),
|
||||
evaluateVersions: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/exec', () => ({
|
||||
exec: jest.fn(),
|
||||
getExecOutput: jest.fn()
|
||||
}));
|
||||
|
||||
// Dynamic imports after mocking
|
||||
const core = await import('@actions/core');
|
||||
const tc = await import('@actions/tool-cache');
|
||||
const exec = await import('@actions/exec');
|
||||
const finder = await import('../src/find-graalpy.js');
|
||||
const utils = await import('../src/utils.js');
|
||||
|
||||
// Non-mocked imports
|
||||
import {HttpClient} from '@actions/http-client';
|
||||
import type * as ifm from '@actions/http-client/lib/interfaces';
|
||||
|
||||
import type {IGraalPyManifestRelease} from '../src/utils.js';
|
||||
import manifestData from './data/graalpy.json' with {type: 'json'};
|
||||
|
||||
const architecture = 'x64';
|
||||
|
||||
@@ -41,39 +93,41 @@ describe('parseGraalPyVersion', () => {
|
||||
describe('findGraalPyToolCache', () => {
|
||||
const actualGraalPyVersion = '23.0.0';
|
||||
const graalpyPath = path.join('GraalPy', actualGraalPyVersion, architecture);
|
||||
let tcFind: jest.SpyInstance;
|
||||
let infoSpy: jest.SpyInstance;
|
||||
let warningSpy: jest.SpyInstance;
|
||||
let debugSpy: jest.SpyInstance;
|
||||
let addPathSpy: jest.SpyInstance;
|
||||
let exportVariableSpy: jest.SpyInstance;
|
||||
let setOutputSpy: jest.SpyInstance;
|
||||
let tcFind: jest.Mock;
|
||||
let infoSpy: jest.Mock;
|
||||
let warningSpy: jest.Mock;
|
||||
let debugSpy: jest.Mock;
|
||||
let addPathSpy: jest.Mock;
|
||||
let exportVariableSpy: jest.Mock;
|
||||
let setOutputSpy: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
tcFind = jest.spyOn(tc, 'find');
|
||||
tcFind.mockImplementation((toolname: string, pythonVersion: string) => {
|
||||
const semverVersion = new semver.Range(pythonVersion);
|
||||
return semver.satisfies(actualGraalPyVersion, semverVersion)
|
||||
? graalpyPath
|
||||
: '';
|
||||
});
|
||||
tcFind = tc.find as jest.Mock;
|
||||
(tcFind as jest.Mock<typeof tc.find>).mockImplementation(
|
||||
(toolname: string, pythonVersion: string) => {
|
||||
const semverVersion = new semver.Range(pythonVersion);
|
||||
return semver.satisfies(actualGraalPyVersion, semverVersion)
|
||||
? graalpyPath
|
||||
: '';
|
||||
}
|
||||
);
|
||||
|
||||
infoSpy = jest.spyOn(core, 'info');
|
||||
infoSpy = core.info as jest.Mock;
|
||||
infoSpy.mockImplementation(() => null);
|
||||
|
||||
warningSpy = jest.spyOn(core, 'warning');
|
||||
warningSpy = core.warning as jest.Mock;
|
||||
warningSpy.mockImplementation(() => null);
|
||||
|
||||
debugSpy = jest.spyOn(core, 'debug');
|
||||
debugSpy = core.debug as jest.Mock;
|
||||
debugSpy.mockImplementation(() => null);
|
||||
|
||||
addPathSpy = jest.spyOn(core, 'addPath');
|
||||
addPathSpy = core.addPath as jest.Mock;
|
||||
addPathSpy.mockImplementation(() => null);
|
||||
|
||||
exportVariableSpy = jest.spyOn(core, 'exportVariable');
|
||||
exportVariableSpy = core.exportVariable as jest.Mock;
|
||||
exportVariableSpy.mockImplementation(() => null);
|
||||
|
||||
setOutputSpy = jest.spyOn(core, 'setOutput');
|
||||
setOutputSpy = core.setOutput as jest.Mock;
|
||||
setOutputSpy.mockImplementation(() => null);
|
||||
});
|
||||
|
||||
@@ -106,73 +160,74 @@ describe('findGraalPyToolCache', () => {
|
||||
});
|
||||
|
||||
describe('findGraalPyVersion', () => {
|
||||
let getBooleanInputSpy: jest.SpyInstance;
|
||||
let warningSpy: jest.SpyInstance;
|
||||
let debugSpy: jest.SpyInstance;
|
||||
let infoSpy: jest.SpyInstance;
|
||||
let addPathSpy: jest.SpyInstance;
|
||||
let exportVariableSpy: jest.SpyInstance;
|
||||
let setOutputSpy: jest.SpyInstance;
|
||||
let tcFind: jest.SpyInstance;
|
||||
let spyExtractZip: jest.SpyInstance;
|
||||
let spyExtractTar: jest.SpyInstance;
|
||||
let spyHttpClient: jest.SpyInstance;
|
||||
let spyExistsSync: jest.SpyInstance;
|
||||
let spyExec: jest.SpyInstance;
|
||||
let spySymlinkSync: jest.SpyInstance;
|
||||
let spyDownloadTool: jest.SpyInstance;
|
||||
let spyFsReadDir: jest.SpyInstance;
|
||||
let spyCacheDir: jest.SpyInstance;
|
||||
let spyChmodSync: jest.SpyInstance;
|
||||
let spyCoreAddPath: jest.SpyInstance;
|
||||
let spyCoreExportVariable: jest.SpyInstance;
|
||||
let getBooleanInputSpy: jest.Mock;
|
||||
let warningSpy: jest.Mock;
|
||||
let debugSpy: jest.Mock;
|
||||
let infoSpy: jest.Mock;
|
||||
let addPathSpy: jest.Mock;
|
||||
let exportVariableSpy: jest.Mock;
|
||||
let setOutputSpy: jest.Mock;
|
||||
let tcFind: jest.Mock;
|
||||
let spyExtractZip: jest.Mock;
|
||||
let spyExtractTar: jest.Mock;
|
||||
let spyHttpClient: jest.SpiedFunction<typeof HttpClient.prototype.getJson>;
|
||||
let spyExistsSync: jest.SpiedFunction<typeof fs.existsSync>;
|
||||
let spyExec: jest.Mock;
|
||||
let spySymlinkSync: jest.SpiedFunction<typeof fs.symlinkSync>;
|
||||
let spyDownloadTool: jest.Mock;
|
||||
let spyFsReadDir: jest.SpiedFunction<typeof fs.readdirSync>;
|
||||
let spyCacheDir: jest.Mock;
|
||||
let spyChmodSync: jest.SpiedFunction<typeof fs.chmodSync>;
|
||||
let spyCoreAddPath: jest.Mock;
|
||||
let spyCoreExportVariable: jest.Mock;
|
||||
const env = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
|
||||
getBooleanInputSpy = core.getBooleanInput as jest.Mock;
|
||||
getBooleanInputSpy.mockImplementation(() => false);
|
||||
|
||||
infoSpy = jest.spyOn(core, 'info');
|
||||
infoSpy = core.info as jest.Mock;
|
||||
infoSpy.mockImplementation(() => {});
|
||||
|
||||
warningSpy = jest.spyOn(core, 'warning');
|
||||
warningSpy = core.warning as jest.Mock;
|
||||
warningSpy.mockImplementation(() => null);
|
||||
|
||||
debugSpy = jest.spyOn(core, 'debug');
|
||||
debugSpy = core.debug as jest.Mock;
|
||||
debugSpy.mockImplementation(() => null);
|
||||
|
||||
addPathSpy = jest.spyOn(core, 'addPath');
|
||||
addPathSpy = core.addPath as jest.Mock;
|
||||
addPathSpy.mockImplementation(() => null);
|
||||
|
||||
exportVariableSpy = jest.spyOn(core, 'exportVariable');
|
||||
exportVariableSpy = core.exportVariable as jest.Mock;
|
||||
exportVariableSpy.mockImplementation(() => null);
|
||||
|
||||
setOutputSpy = jest.spyOn(core, 'setOutput');
|
||||
setOutputSpy = core.setOutput as jest.Mock;
|
||||
setOutputSpy.mockImplementation(() => null);
|
||||
|
||||
jest.resetModules();
|
||||
process.env = {...env};
|
||||
tcFind = jest.spyOn(tc, 'find');
|
||||
tcFind.mockImplementation((tool: string, version: string) => {
|
||||
const semverRange = new semver.Range(version);
|
||||
let graalpyPath = '';
|
||||
if (semver.satisfies('23.0.0', semverRange)) {
|
||||
graalpyPath = path.join(toolDir, 'GraalPy', '23.0.0', architecture);
|
||||
tcFind = tc.find as jest.Mock;
|
||||
(tcFind as jest.Mock<typeof tc.find>).mockImplementation(
|
||||
(tool: string, version: string) => {
|
||||
const semverRange = new semver.Range(version);
|
||||
let graalpyPath = '';
|
||||
if (semver.satisfies('23.0.0', semverRange)) {
|
||||
graalpyPath = path.join(toolDir, 'GraalPy', '23.0.0', architecture);
|
||||
}
|
||||
return graalpyPath;
|
||||
}
|
||||
return graalpyPath;
|
||||
});
|
||||
);
|
||||
|
||||
spyDownloadTool = jest.spyOn(tc, 'downloadTool');
|
||||
spyDownloadTool = tc.downloadTool as jest.Mock;
|
||||
spyDownloadTool.mockImplementation(() => path.join(tempDir, 'GraalPy'));
|
||||
|
||||
spyExtractZip = jest.spyOn(tc, 'extractZip');
|
||||
spyExtractZip = tc.extractZip as jest.Mock;
|
||||
spyExtractZip.mockImplementation(() => tempDir);
|
||||
|
||||
spyExtractTar = jest.spyOn(tc, 'extractTar');
|
||||
spyExtractTar = tc.extractTar as jest.Mock;
|
||||
spyExtractTar.mockImplementation(() => tempDir);
|
||||
|
||||
spyFsReadDir = jest.spyOn(fs, 'readdirSync');
|
||||
spyFsReadDir.mockImplementation((directory: string) => ['GraalPyTest']);
|
||||
spyFsReadDir.mockImplementation(() => ['GraalPyTest'] as any);
|
||||
|
||||
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
|
||||
spyHttpClient.mockImplementation(
|
||||
@@ -186,7 +241,7 @@ describe('findGraalPyVersion', () => {
|
||||
}
|
||||
);
|
||||
|
||||
spyExec = jest.spyOn(exec, 'exec');
|
||||
spyExec = exec.exec as jest.Mock;
|
||||
spyExec.mockImplementation(() => undefined);
|
||||
|
||||
spySymlinkSync = jest.spyOn(fs, 'symlinkSync');
|
||||
@@ -195,9 +250,9 @@ describe('findGraalPyVersion', () => {
|
||||
spyExistsSync = jest.spyOn(fs, 'existsSync');
|
||||
spyExistsSync.mockReturnValue(true);
|
||||
|
||||
spyCoreAddPath = jest.spyOn(core, 'addPath');
|
||||
spyCoreAddPath = core.addPath as jest.Mock;
|
||||
|
||||
spyCoreExportVariable = jest.spyOn(core, 'exportVariable');
|
||||
spyCoreExportVariable = core.exportVariable as jest.Mock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -235,7 +290,7 @@ describe('findGraalPyVersion', () => {
|
||||
});
|
||||
|
||||
it('found and install successfully', async () => {
|
||||
spyCacheDir = jest.spyOn(tc, 'cacheDir');
|
||||
spyCacheDir = tc.cacheDir as jest.Mock;
|
||||
spyCacheDir.mockImplementation(() =>
|
||||
path.join(toolDir, 'GraalPy', '23.0.0', architecture)
|
||||
);
|
||||
@@ -262,7 +317,7 @@ describe('findGraalPyVersion', () => {
|
||||
});
|
||||
|
||||
it('found and install successfully without environment update', async () => {
|
||||
spyCacheDir = jest.spyOn(tc, 'cacheDir');
|
||||
spyCacheDir = tc.cacheDir as jest.Mock;
|
||||
spyCacheDir.mockImplementation(() =>
|
||||
path.join(toolDir, 'GraalPy', '23.0.0', architecture)
|
||||
);
|
||||
@@ -310,7 +365,7 @@ describe('findGraalPyVersion', () => {
|
||||
});
|
||||
|
||||
it('check-latest enabled version found and install successfully', async () => {
|
||||
spyCacheDir = jest.spyOn(tc, 'cacheDir');
|
||||
spyCacheDir = tc.cacheDir as jest.Mock;
|
||||
spyCacheDir.mockImplementation(() =>
|
||||
path.join(toolDir, 'GraalPy', '23.0.0', architecture)
|
||||
);
|
||||
@@ -329,14 +384,16 @@ describe('findGraalPyVersion', () => {
|
||||
});
|
||||
|
||||
it('check-latest enabled version is not found and used from toolcache', async () => {
|
||||
tcFind.mockImplementationOnce((tool: string, version: string) => {
|
||||
const semverRange = new semver.Range(version);
|
||||
let graalpyPath = '';
|
||||
if (semver.satisfies('22.3.4', semverRange)) {
|
||||
graalpyPath = path.join(toolDir, 'GraalPy', '22.3.4', architecture);
|
||||
(tcFind as jest.Mock<typeof tc.find>).mockImplementationOnce(
|
||||
(tool: string, version: string) => {
|
||||
const semverRange = new semver.Range(version);
|
||||
let graalpyPath = '';
|
||||
if (semver.satisfies('22.3.4', semverRange)) {
|
||||
graalpyPath = path.join(toolDir, 'GraalPy', '22.3.4', architecture);
|
||||
}
|
||||
return graalpyPath;
|
||||
}
|
||||
return graalpyPath;
|
||||
});
|
||||
);
|
||||
await expect(
|
||||
finder.findGraalPyVersion(
|
||||
'graalpy-22.3.4',
|
||||
@@ -353,7 +410,7 @@ describe('findGraalPyVersion', () => {
|
||||
});
|
||||
|
||||
it('found and install successfully, pre-release fallback', async () => {
|
||||
spyCacheDir = jest.spyOn(tc, 'cacheDir');
|
||||
spyCacheDir = tc.cacheDir as jest.Mock;
|
||||
spyCacheDir.mockImplementation(() =>
|
||||
path.join(toolDir, 'GraalPy', '24.1', architecture)
|
||||
);
|
||||
|
||||
+160
-98
@@ -1,23 +1,83 @@
|
||||
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
|
||||
import {fileURLToPath} from 'url';
|
||||
import fs from 'fs';
|
||||
|
||||
import * as utils from '../src/utils';
|
||||
import {HttpClient} from '@actions/http-client';
|
||||
import * as ifm from '@actions/http-client/lib/interfaces';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
import * as exec from '@actions/exec';
|
||||
import * as core from '@actions/core';
|
||||
|
||||
import * as path from 'path';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
import * as semver from 'semver';
|
||||
|
||||
import * as finder from '../src/find-pypy';
|
||||
import {
|
||||
IPyPyManifestRelease,
|
||||
IS_WINDOWS,
|
||||
getPyPyVersionFromPath
|
||||
} from '../src/utils';
|
||||
// Mock @actions modules
|
||||
jest.unstable_mockModule('@actions/core', () => ({
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
notice: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
setOutput: jest.fn(),
|
||||
getInput: jest.fn(),
|
||||
getBooleanInput: jest.fn(),
|
||||
getMultilineInput: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
exportVariable: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn(),
|
||||
setSecret: jest.fn(),
|
||||
isDebug: jest.fn(() => false),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn(),
|
||||
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
|
||||
toPlatformPath: jest.fn((p: string) => p),
|
||||
toWin32Path: jest.fn((p: string) => p),
|
||||
toPosixPath: jest.fn((p: string) => p)
|
||||
}));
|
||||
|
||||
import manifestData from './data/pypy.json';
|
||||
jest.unstable_mockModule('@actions/tool-cache', () => ({
|
||||
find: jest.fn(),
|
||||
findAllVersions: jest.fn(),
|
||||
downloadTool: jest.fn(),
|
||||
extractZip: jest.fn(),
|
||||
extractTar: jest.fn(),
|
||||
extract7z: jest.fn(),
|
||||
extractXar: jest.fn(),
|
||||
cacheDir: jest.fn(),
|
||||
cacheFile: jest.fn(),
|
||||
getManifestFromRepo: jest.fn(),
|
||||
findFromManifest: jest.fn(),
|
||||
evaluateVersions: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/exec', () => ({
|
||||
exec: jest.fn(),
|
||||
getExecOutput: jest.fn()
|
||||
}));
|
||||
|
||||
// Import real utils BEFORE mock registration to get real function references
|
||||
const realUtils = await import('../src/utils.js');
|
||||
|
||||
// Mock local utils module for readExactPyPyVersionFile/writeExactPyPyVersionFile
|
||||
jest.unstable_mockModule('../src/utils.js', () => ({
|
||||
...realUtils,
|
||||
readExactPyPyVersionFile: jest.fn(),
|
||||
writeExactPyPyVersionFile: jest.fn()
|
||||
}));
|
||||
|
||||
// Dynamic imports after mocking
|
||||
const core = await import('@actions/core');
|
||||
const tc = await import('@actions/tool-cache');
|
||||
const exec = await import('@actions/exec');
|
||||
const utils = await import('../src/utils.js');
|
||||
const finder = await import('../src/find-pypy.js');
|
||||
|
||||
// Non-mocked imports
|
||||
import {HttpClient} from '@actions/http-client';
|
||||
import type * as ifm from '@actions/http-client/lib/interfaces';
|
||||
|
||||
import type {IPyPyManifestRelease} from '../src/utils.js';
|
||||
import manifestData from './data/pypy.json' with {type: 'json'};
|
||||
|
||||
const IS_WINDOWS = utils.IS_WINDOWS;
|
||||
const getPyPyVersionFromPath = utils.getPyPyVersionFromPath;
|
||||
|
||||
let architecture: string;
|
||||
|
||||
@@ -79,43 +139,45 @@ describe('findPyPyToolCache', () => {
|
||||
const actualPythonVersion = '3.6.17';
|
||||
const actualPyPyVersion = '7.5.4';
|
||||
const pypyPath = path.join('PyPy', actualPythonVersion, architecture);
|
||||
let tcFind: jest.SpyInstance;
|
||||
let spyReadExactPyPyVersion: jest.SpyInstance;
|
||||
let infoSpy: jest.SpyInstance;
|
||||
let warningSpy: jest.SpyInstance;
|
||||
let debugSpy: jest.SpyInstance;
|
||||
let addPathSpy: jest.SpyInstance;
|
||||
let exportVariableSpy: jest.SpyInstance;
|
||||
let setOutputSpy: jest.SpyInstance;
|
||||
let tcFind: jest.Mock;
|
||||
let spyReadExactPyPyVersion: jest.Mock;
|
||||
let infoSpy: jest.Mock;
|
||||
let warningSpy: jest.Mock;
|
||||
let debugSpy: jest.Mock;
|
||||
let addPathSpy: jest.Mock;
|
||||
let exportVariableSpy: jest.Mock;
|
||||
let setOutputSpy: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
tcFind = jest.spyOn(tc, 'find');
|
||||
tcFind.mockImplementation((toolname: string, pythonVersion: string) => {
|
||||
const semverVersion = new semver.Range(pythonVersion);
|
||||
return semver.satisfies(actualPythonVersion, semverVersion)
|
||||
? pypyPath
|
||||
: '';
|
||||
});
|
||||
tcFind = tc.find as jest.Mock;
|
||||
(tcFind as jest.Mock<typeof tc.find>).mockImplementation(
|
||||
(toolname: string, pythonVersion: string) => {
|
||||
const semverVersion = new semver.Range(pythonVersion);
|
||||
return semver.satisfies(actualPythonVersion, semverVersion)
|
||||
? pypyPath
|
||||
: '';
|
||||
}
|
||||
);
|
||||
|
||||
spyReadExactPyPyVersion = jest.spyOn(utils, 'readExactPyPyVersionFile');
|
||||
spyReadExactPyPyVersion = utils.readExactPyPyVersionFile as jest.Mock;
|
||||
spyReadExactPyPyVersion.mockImplementation(() => actualPyPyVersion);
|
||||
|
||||
infoSpy = jest.spyOn(core, 'info');
|
||||
infoSpy = core.info as jest.Mock;
|
||||
infoSpy.mockImplementation(() => null);
|
||||
|
||||
warningSpy = jest.spyOn(core, 'warning');
|
||||
warningSpy = core.warning as jest.Mock;
|
||||
warningSpy.mockImplementation(() => null);
|
||||
|
||||
debugSpy = jest.spyOn(core, 'debug');
|
||||
debugSpy = core.debug as jest.Mock;
|
||||
debugSpy.mockImplementation(() => null);
|
||||
|
||||
addPathSpy = jest.spyOn(core, 'addPath');
|
||||
addPathSpy = core.addPath as jest.Mock;
|
||||
addPathSpy.mockImplementation(() => null);
|
||||
|
||||
exportVariableSpy = jest.spyOn(core, 'exportVariable');
|
||||
exportVariableSpy = core.exportVariable as jest.Mock;
|
||||
exportVariableSpy.mockImplementation(() => null);
|
||||
|
||||
setOutputSpy = jest.spyOn(core, 'setOutput');
|
||||
setOutputSpy = core.setOutput as jest.Mock;
|
||||
setOutputSpy.mockImplementation(() => null);
|
||||
});
|
||||
|
||||
@@ -159,84 +221,82 @@ describe('findPyPyToolCache', () => {
|
||||
});
|
||||
|
||||
describe('findPyPyVersion', () => {
|
||||
let getBooleanInputSpy: jest.SpyInstance;
|
||||
let warningSpy: jest.SpyInstance;
|
||||
let debugSpy: jest.SpyInstance;
|
||||
let infoSpy: jest.SpyInstance;
|
||||
let addPathSpy: jest.SpyInstance;
|
||||
let exportVariableSpy: jest.SpyInstance;
|
||||
let setOutputSpy: jest.SpyInstance;
|
||||
let tcFind: jest.SpyInstance;
|
||||
let spyExtractZip: jest.SpyInstance;
|
||||
let spyExtractTar: jest.SpyInstance;
|
||||
let spyHttpClient: jest.SpyInstance;
|
||||
let spyExistsSync: jest.SpyInstance;
|
||||
let spyExec: jest.SpyInstance;
|
||||
let spySymlinkSync: jest.SpyInstance;
|
||||
let spyDownloadTool: jest.SpyInstance;
|
||||
let spyReadExactPyPyVersion: jest.SpyInstance;
|
||||
let spyFsReadDir: jest.SpyInstance;
|
||||
let spyWriteExactPyPyVersionFile: jest.SpyInstance;
|
||||
let spyCacheDir: jest.SpyInstance;
|
||||
let spyChmodSync: jest.SpyInstance;
|
||||
let spyCoreAddPath: jest.SpyInstance;
|
||||
let spyCoreExportVariable: jest.SpyInstance;
|
||||
let getBooleanInputSpy: jest.Mock;
|
||||
let warningSpy: jest.Mock;
|
||||
let debugSpy: jest.Mock;
|
||||
let infoSpy: jest.Mock;
|
||||
let addPathSpy: jest.Mock;
|
||||
let exportVariableSpy: jest.Mock;
|
||||
let setOutputSpy: jest.Mock;
|
||||
let tcFind: jest.Mock;
|
||||
let spyExtractZip: jest.Mock;
|
||||
let spyExtractTar: jest.Mock;
|
||||
let spyHttpClient: jest.SpiedFunction<typeof HttpClient.prototype.getJson>;
|
||||
let spyExistsSync: jest.SpiedFunction<typeof fs.existsSync>;
|
||||
let spyExec: jest.Mock;
|
||||
let spySymlinkSync: jest.SpiedFunction<typeof fs.symlinkSync>;
|
||||
let spyDownloadTool: jest.Mock;
|
||||
let spyReadExactPyPyVersion: jest.Mock;
|
||||
let spyFsReadDir: jest.SpiedFunction<typeof fs.readdirSync>;
|
||||
let spyWriteExactPyPyVersionFile: jest.Mock;
|
||||
let spyCacheDir: jest.Mock;
|
||||
let spyChmodSync: jest.SpiedFunction<typeof fs.chmodSync>;
|
||||
let spyCoreAddPath: jest.Mock;
|
||||
let spyCoreExportVariable: jest.Mock;
|
||||
const env = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
|
||||
getBooleanInputSpy = core.getBooleanInput as jest.Mock;
|
||||
getBooleanInputSpy.mockImplementation(() => false);
|
||||
|
||||
infoSpy = jest.spyOn(core, 'info');
|
||||
infoSpy = core.info as jest.Mock;
|
||||
infoSpy.mockImplementation(() => {});
|
||||
|
||||
warningSpy = jest.spyOn(core, 'warning');
|
||||
warningSpy = core.warning as jest.Mock;
|
||||
warningSpy.mockImplementation(() => null);
|
||||
|
||||
debugSpy = jest.spyOn(core, 'debug');
|
||||
debugSpy = core.debug as jest.Mock;
|
||||
debugSpy.mockImplementation(() => null);
|
||||
|
||||
addPathSpy = jest.spyOn(core, 'addPath');
|
||||
addPathSpy = core.addPath as jest.Mock;
|
||||
addPathSpy.mockImplementation(() => null);
|
||||
|
||||
exportVariableSpy = jest.spyOn(core, 'exportVariable');
|
||||
exportVariableSpy = core.exportVariable as jest.Mock;
|
||||
exportVariableSpy.mockImplementation(() => null);
|
||||
|
||||
setOutputSpy = jest.spyOn(core, 'setOutput');
|
||||
setOutputSpy = core.setOutput as jest.Mock;
|
||||
setOutputSpy.mockImplementation(() => null);
|
||||
|
||||
jest.resetModules();
|
||||
process.env = {...env};
|
||||
tcFind = jest.spyOn(tc, 'find');
|
||||
tcFind.mockImplementation((tool: string, version: string) => {
|
||||
const semverRange = new semver.Range(version);
|
||||
let pypyPath = '';
|
||||
if (semver.satisfies('3.6.12', semverRange)) {
|
||||
pypyPath = path.join(toolDir, 'PyPy', '3.6.12', architecture);
|
||||
tcFind = tc.find as jest.Mock;
|
||||
(tcFind as jest.Mock<typeof tc.find>).mockImplementation(
|
||||
(tool: string, version: string) => {
|
||||
const semverRange = new semver.Range(version);
|
||||
let pypyPath = '';
|
||||
if (semver.satisfies('3.6.12', semverRange)) {
|
||||
pypyPath = path.join(toolDir, 'PyPy', '3.6.12', architecture);
|
||||
}
|
||||
return pypyPath;
|
||||
}
|
||||
return pypyPath;
|
||||
});
|
||||
|
||||
spyWriteExactPyPyVersionFile = jest.spyOn(
|
||||
utils,
|
||||
'writeExactPyPyVersionFile'
|
||||
);
|
||||
|
||||
spyWriteExactPyPyVersionFile = utils.writeExactPyPyVersionFile as jest.Mock;
|
||||
spyWriteExactPyPyVersionFile.mockImplementation(() => null);
|
||||
|
||||
spyReadExactPyPyVersion = jest.spyOn(utils, 'readExactPyPyVersionFile');
|
||||
spyReadExactPyPyVersion = utils.readExactPyPyVersionFile as jest.Mock;
|
||||
spyReadExactPyPyVersion.mockImplementation(() => '7.3.3');
|
||||
|
||||
spyDownloadTool = jest.spyOn(tc, 'downloadTool');
|
||||
spyDownloadTool = tc.downloadTool as jest.Mock;
|
||||
spyDownloadTool.mockImplementation(() => path.join(tempDir, 'PyPy'));
|
||||
|
||||
spyExtractZip = jest.spyOn(tc, 'extractZip');
|
||||
spyExtractZip = tc.extractZip as jest.Mock;
|
||||
spyExtractZip.mockImplementation(() => tempDir);
|
||||
|
||||
spyExtractTar = jest.spyOn(tc, 'extractTar');
|
||||
spyExtractTar = tc.extractTar as jest.Mock;
|
||||
spyExtractTar.mockImplementation(() => tempDir);
|
||||
|
||||
spyFsReadDir = jest.spyOn(fs, 'readdirSync');
|
||||
spyFsReadDir.mockImplementation((directory: string) => ['PyPyTest']);
|
||||
spyFsReadDir.mockImplementation(() => ['PyPyTest'] as any);
|
||||
|
||||
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
|
||||
spyHttpClient.mockImplementation(
|
||||
@@ -250,7 +310,7 @@ describe('findPyPyVersion', () => {
|
||||
}
|
||||
);
|
||||
|
||||
spyExec = jest.spyOn(exec, 'exec');
|
||||
spyExec = exec.exec as jest.Mock;
|
||||
spyExec.mockImplementation(() => undefined);
|
||||
|
||||
spySymlinkSync = jest.spyOn(fs, 'symlinkSync');
|
||||
@@ -259,9 +319,9 @@ describe('findPyPyVersion', () => {
|
||||
spyExistsSync = jest.spyOn(fs, 'existsSync');
|
||||
spyExistsSync.mockReturnValue(true);
|
||||
|
||||
spyCoreAddPath = jest.spyOn(core, 'addPath');
|
||||
spyCoreAddPath = core.addPath as jest.Mock;
|
||||
|
||||
spyCoreExportVariable = jest.spyOn(core, 'exportVariable');
|
||||
spyCoreExportVariable = core.exportVariable as jest.Mock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -308,7 +368,7 @@ describe('findPyPyVersion', () => {
|
||||
});
|
||||
|
||||
it('found and install successfully', async () => {
|
||||
spyCacheDir = jest.spyOn(tc, 'cacheDir');
|
||||
spyCacheDir = tc.cacheDir as jest.Mock;
|
||||
spyCacheDir.mockImplementation(() =>
|
||||
path.join(toolDir, 'PyPy', '3.7.7', architecture)
|
||||
);
|
||||
@@ -338,7 +398,7 @@ describe('findPyPyVersion', () => {
|
||||
});
|
||||
|
||||
it('found and install successfully without environment update', async () => {
|
||||
spyCacheDir = jest.spyOn(tc, 'cacheDir');
|
||||
spyCacheDir = tc.cacheDir as jest.Mock;
|
||||
spyCacheDir.mockImplementation(() =>
|
||||
path.join(toolDir, 'PyPy', '3.7.7', architecture)
|
||||
);
|
||||
@@ -394,7 +454,7 @@ describe('findPyPyVersion', () => {
|
||||
});
|
||||
|
||||
it('check-latest enabled version found and install successfully', async () => {
|
||||
spyCacheDir = jest.spyOn(tc, 'cacheDir');
|
||||
spyCacheDir = tc.cacheDir as jest.Mock;
|
||||
spyCacheDir.mockImplementation(() =>
|
||||
path.join(toolDir, 'PyPy', '3.7.7', architecture)
|
||||
);
|
||||
@@ -418,14 +478,16 @@ describe('findPyPyVersion', () => {
|
||||
});
|
||||
|
||||
it('check-latest enabled version is not found and used from toolcache', async () => {
|
||||
tcFind.mockImplementationOnce((tool: string, version: string) => {
|
||||
const semverRange = new semver.Range(version);
|
||||
let pypyPath = '';
|
||||
if (semver.satisfies('3.8.8', semverRange)) {
|
||||
pypyPath = path.join(toolDir, 'PyPy', '3.8.8', architecture);
|
||||
(tcFind as jest.Mock<typeof tc.find>).mockImplementationOnce(
|
||||
(tool: string, version: string) => {
|
||||
const semverRange = new semver.Range(version);
|
||||
let pypyPath = '';
|
||||
if (semver.satisfies('3.8.8', semverRange)) {
|
||||
pypyPath = path.join(toolDir, 'PyPy', '3.8.8', architecture);
|
||||
}
|
||||
return pypyPath;
|
||||
}
|
||||
return pypyPath;
|
||||
});
|
||||
);
|
||||
await expect(
|
||||
finder.findPyPyVersion(
|
||||
'pypy-3.8-v7.3.x',
|
||||
@@ -445,7 +507,7 @@ describe('findPyPyVersion', () => {
|
||||
});
|
||||
|
||||
it('found and install successfully, pre-release fallback', async () => {
|
||||
spyCacheDir = jest.spyOn(tc, 'cacheDir');
|
||||
spyCacheDir = tc.cacheDir as jest.Mock;
|
||||
spyCacheDir.mockImplementation(() =>
|
||||
path.join(toolDir, 'PyPy', '3.8.12', architecture)
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {desugarVersion, pythonVersionToSemantic} from '../src/find-python';
|
||||
import {describe, it, expect} from '@jest/globals';
|
||||
import {desugarVersion, pythonVersionToSemantic} from '../src/find-python.js';
|
||||
|
||||
describe('desugarVersion', () => {
|
||||
it.each([
|
||||
|
||||
+97
-59
@@ -1,8 +1,12 @@
|
||||
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
|
||||
import {fileURLToPath} from 'url';
|
||||
import * as io from '@actions/io';
|
||||
import os from 'os';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const toolDir = path.join(
|
||||
__dirname,
|
||||
'runner',
|
||||
@@ -19,26 +23,78 @@ const tempDir = path.join(
|
||||
process.env['RUNNER_TOOL_CACHE'] = toolDir;
|
||||
process.env['RUNNER_TEMP'] = tempDir;
|
||||
|
||||
import * as tc from '@actions/tool-cache';
|
||||
import * as core from '@actions/core';
|
||||
import * as finder from '../src/find-python';
|
||||
import * as installer from '../src/install-python';
|
||||
// Mock @actions modules
|
||||
jest.unstable_mockModule('@actions/core', () => ({
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
notice: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
setOutput: jest.fn(),
|
||||
getInput: jest.fn(),
|
||||
getBooleanInput: jest.fn(),
|
||||
getMultilineInput: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
exportVariable: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn(),
|
||||
setSecret: jest.fn(),
|
||||
isDebug: jest.fn(() => false),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn(),
|
||||
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
|
||||
toPlatformPath: jest.fn((p: string) => p),
|
||||
toWin32Path: jest.fn((p: string) => p),
|
||||
toPosixPath: jest.fn((p: string) => p)
|
||||
}));
|
||||
|
||||
import manifestData from './data/versions-manifest.json';
|
||||
// Pre-import real @actions/tool-cache before any mocks
|
||||
const realTc = await import('@actions/tool-cache');
|
||||
|
||||
jest.unstable_mockModule('@actions/tool-cache', () => ({
|
||||
...realTc,
|
||||
find: jest.fn(realTc.find),
|
||||
getManifestFromRepo: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/exec', () => ({
|
||||
exec: jest.fn(),
|
||||
getExecOutput: jest.fn()
|
||||
}));
|
||||
|
||||
// Pre-import real install-python AFTER all its dependency mocks are registered
|
||||
// so it captures the mocked @actions/tool-cache, @actions/core, @actions/exec
|
||||
const realInstaller = await import('../src/install-python.js');
|
||||
|
||||
// Mock local install-python module - keep real getManifest/findReleaseFromManifest
|
||||
jest.unstable_mockModule('../src/install-python.js', () => ({
|
||||
...realInstaller,
|
||||
installCpythonFromRelease: jest.fn()
|
||||
}));
|
||||
|
||||
// Dynamic imports after mocking
|
||||
const core = await import('@actions/core');
|
||||
const tc = await import('@actions/tool-cache');
|
||||
const finder = await import('../src/find-python.js');
|
||||
const installer = await import('../src/install-python.js');
|
||||
|
||||
import manifestData from './data/versions-manifest.json' with {type: 'json'};
|
||||
|
||||
describe('Finder tests', () => {
|
||||
let writeSpy: jest.SpyInstance;
|
||||
let spyCoreAddPath: jest.SpyInstance;
|
||||
let spyCoreExportVariable: jest.SpyInstance;
|
||||
let writeSpy: jest.SpiedFunction<typeof process.stdout.write>;
|
||||
let spyCoreAddPath: jest.Mock;
|
||||
let spyCoreExportVariable: jest.Mock;
|
||||
const env = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
writeSpy = jest.spyOn(process.stdout, 'write');
|
||||
writeSpy.mockImplementation(() => {});
|
||||
jest.resetModules();
|
||||
writeSpy.mockImplementation(() => true);
|
||||
process.env = {...env};
|
||||
spyCoreAddPath = jest.spyOn(core, 'addPath');
|
||||
spyCoreExportVariable = jest.spyOn(core, 'exportVariable');
|
||||
spyCoreAddPath = core.addPath as jest.Mock;
|
||||
spyCoreExportVariable = core.exportVariable as jest.Mock;
|
||||
// Restore real tc.find default (cleared by jest.resetAllMocks)
|
||||
(tc.find as jest.Mock).mockImplementation(realTc.find as any);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -49,8 +105,8 @@ describe('Finder tests', () => {
|
||||
});
|
||||
|
||||
it('Finds Python if it is installed', async () => {
|
||||
const getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
|
||||
getBooleanInputSpy.mockImplementation(input => false);
|
||||
const getBooleanInputSpy = core.getBooleanInput as jest.Mock;
|
||||
getBooleanInputSpy.mockImplementation(() => false);
|
||||
|
||||
const pythonDir: string = path.join(toolDir, 'Python', '3.0.0', 'x64');
|
||||
await io.mkdirP(pythonDir);
|
||||
@@ -79,16 +135,13 @@ describe('Finder tests', () => {
|
||||
});
|
||||
|
||||
it('Finds stable Python version if it is not installed, but exists in the manifest', async () => {
|
||||
const findSpy: jest.SpyInstance = jest.spyOn(tc, 'getManifestFromRepo');
|
||||
findSpy.mockImplementation(() => <tc.IToolRelease[]>manifestData);
|
||||
const findSpy = tc.getManifestFromRepo as jest.Mock;
|
||||
findSpy.mockImplementation(() => manifestData);
|
||||
|
||||
const getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
|
||||
getBooleanInputSpy.mockImplementation(input => false);
|
||||
const getBooleanInputSpy = core.getBooleanInput as jest.Mock;
|
||||
getBooleanInputSpy.mockImplementation(() => false);
|
||||
|
||||
const installSpy: jest.SpyInstance = jest.spyOn(
|
||||
installer,
|
||||
'installCpythonFromRelease'
|
||||
);
|
||||
const installSpy = installer.installCpythonFromRelease as jest.Mock;
|
||||
installSpy.mockImplementation(async () => {
|
||||
const pythonDir: string = path.join(toolDir, 'Python', '1.2.3', 'x64');
|
||||
await io.mkdirP(pythonDir);
|
||||
@@ -113,16 +166,13 @@ describe('Finder tests', () => {
|
||||
});
|
||||
|
||||
it('Finds pre-release Python version in the manifest', async () => {
|
||||
const findSpy: jest.SpyInstance = jest.spyOn(tc, 'getManifestFromRepo');
|
||||
findSpy.mockImplementation(() => <tc.IToolRelease[]>manifestData);
|
||||
const findSpy = tc.getManifestFromRepo as jest.Mock;
|
||||
findSpy.mockImplementation(() => manifestData);
|
||||
|
||||
const getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
|
||||
getBooleanInputSpy.mockImplementation(input => false);
|
||||
const getBooleanInputSpy = core.getBooleanInput as jest.Mock;
|
||||
getBooleanInputSpy.mockImplementation(() => false);
|
||||
|
||||
const installSpy: jest.SpyInstance = jest.spyOn(
|
||||
installer,
|
||||
'installCpythonFromRelease'
|
||||
);
|
||||
const installSpy = installer.installCpythonFromRelease as jest.Mock;
|
||||
installSpy.mockImplementation(async () => {
|
||||
const pythonDir: string = path.join(
|
||||
toolDir,
|
||||
@@ -150,40 +200,34 @@ describe('Finder tests', () => {
|
||||
});
|
||||
|
||||
it('Check-latest true, finds the latest version in the manifest', async () => {
|
||||
const findSpy: jest.SpyInstance = jest.spyOn(tc, 'getManifestFromRepo');
|
||||
findSpy.mockImplementation(() => <tc.IToolRelease[]>manifestData);
|
||||
const findSpy = tc.getManifestFromRepo as jest.Mock;
|
||||
findSpy.mockImplementation(() => manifestData);
|
||||
|
||||
const getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
|
||||
getBooleanInputSpy.mockImplementation(input => true);
|
||||
const getBooleanInputSpy = core.getBooleanInput as jest.Mock;
|
||||
getBooleanInputSpy.mockImplementation(() => true);
|
||||
|
||||
const cnSpy: jest.SpyInstance = jest.spyOn(process.stdout, 'write');
|
||||
cnSpy.mockImplementation(line => {
|
||||
// uncomment to debug
|
||||
// process.stderr.write('write:' + line + '\n');
|
||||
});
|
||||
const cnSpy = jest.spyOn(process.stdout, 'write');
|
||||
cnSpy.mockImplementation(() => true);
|
||||
|
||||
const addPathSpy: jest.SpyInstance = jest.spyOn(core, 'addPath');
|
||||
const addPathSpy = core.addPath as jest.Mock;
|
||||
addPathSpy.mockImplementation(() => null);
|
||||
|
||||
const infoSpy: jest.SpyInstance = jest.spyOn(core, 'info');
|
||||
const infoSpy = core.info as jest.Mock;
|
||||
infoSpy.mockImplementation(() => {});
|
||||
|
||||
const debugSpy: jest.SpyInstance = jest.spyOn(core, 'debug');
|
||||
const debugSpy = core.debug as jest.Mock;
|
||||
debugSpy.mockImplementation(() => {});
|
||||
|
||||
const pythonDir: string = path.join(toolDir, 'Python', '1.2.2', 'x64');
|
||||
const expPath: string = path.join(toolDir, 'Python', '1.2.3', 'x64');
|
||||
|
||||
const installSpy: jest.SpyInstance = jest.spyOn(
|
||||
installer,
|
||||
'installCpythonFromRelease'
|
||||
);
|
||||
const installSpy = installer.installCpythonFromRelease as jest.Mock;
|
||||
installSpy.mockImplementation(async () => {
|
||||
await io.mkdirP(expPath);
|
||||
fs.writeFileSync(`${expPath}.complete`, 'hello');
|
||||
});
|
||||
|
||||
const tcFindSpy: jest.SpyInstance = jest.spyOn(tc, 'find');
|
||||
const tcFindSpy = tc.find as jest.Mock;
|
||||
tcFindSpy
|
||||
.mockImplementationOnce(() => '')
|
||||
.mockImplementationOnce(() => expPath);
|
||||
@@ -224,13 +268,10 @@ describe('Finder tests', () => {
|
||||
});
|
||||
|
||||
it('Finds stable Python version if it is not installed, but exists in the manifest, skipping newer pre-release', async () => {
|
||||
const findSpy: jest.SpyInstance = jest.spyOn(tc, 'getManifestFromRepo');
|
||||
findSpy.mockImplementation(() => <tc.IToolRelease[]>manifestData);
|
||||
const findSpy = tc.getManifestFromRepo as jest.Mock;
|
||||
findSpy.mockImplementation(() => manifestData);
|
||||
|
||||
const installSpy: jest.SpyInstance = jest.spyOn(
|
||||
installer,
|
||||
'installCpythonFromRelease'
|
||||
);
|
||||
const installSpy = installer.installCpythonFromRelease as jest.Mock;
|
||||
installSpy.mockImplementation(async () => {
|
||||
const pythonDir: string = path.join(toolDir, 'Python', '1.2.3', 'x64');
|
||||
await io.mkdirP(pythonDir);
|
||||
@@ -246,13 +287,10 @@ describe('Finder tests', () => {
|
||||
});
|
||||
|
||||
it('Finds Python version if it is not installed, but exists in the manifest, pre-release fallback', async () => {
|
||||
const findSpy: jest.SpyInstance = jest.spyOn(tc, 'getManifestFromRepo');
|
||||
findSpy.mockImplementation(() => <tc.IToolRelease[]>manifestData);
|
||||
const findSpy = tc.getManifestFromRepo as jest.Mock;
|
||||
findSpy.mockImplementation(() => manifestData);
|
||||
|
||||
const installSpy: jest.SpyInstance = jest.spyOn(
|
||||
installer,
|
||||
'installCpythonFromRelease'
|
||||
);
|
||||
const installSpy = installer.installCpythonFromRelease as jest.Mock;
|
||||
installSpy.mockImplementation(async () => {
|
||||
const pythonDir: string = path.join(
|
||||
toolDir,
|
||||
|
||||
@@ -1,20 +1,75 @@
|
||||
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
|
||||
import {fileURLToPath} from 'url';
|
||||
import fs from 'fs';
|
||||
|
||||
import {HttpClient} from '@actions/http-client';
|
||||
import * as ifm from '@actions/http-client/lib/interfaces';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
import * as exec from '@actions/exec';
|
||||
import * as core from '@actions/core';
|
||||
import * as path from 'path';
|
||||
|
||||
import * as installer from '../src/install-graalpy';
|
||||
import {
|
||||
IGraalPyManifestRelease,
|
||||
IGraalPyManifestAsset,
|
||||
IS_WINDOWS
|
||||
} from '../src/utils';
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
import manifestData from './data/graalpy.json';
|
||||
// Mock @actions modules
|
||||
jest.unstable_mockModule('@actions/core', () => ({
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
notice: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
setOutput: jest.fn(),
|
||||
getInput: jest.fn(),
|
||||
getBooleanInput: jest.fn(),
|
||||
getMultilineInput: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
exportVariable: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn(),
|
||||
setSecret: jest.fn(),
|
||||
isDebug: jest.fn(() => false),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn(),
|
||||
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
|
||||
toPlatformPath: jest.fn((p: string) => p),
|
||||
toWin32Path: jest.fn((p: string) => p),
|
||||
toPosixPath: jest.fn((p: string) => p)
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/tool-cache', () => ({
|
||||
find: jest.fn(),
|
||||
findAllVersions: jest.fn(),
|
||||
downloadTool: jest.fn(),
|
||||
extractZip: jest.fn(),
|
||||
extractTar: jest.fn(),
|
||||
extract7z: jest.fn(),
|
||||
extractXar: jest.fn(),
|
||||
cacheDir: jest.fn(),
|
||||
cacheFile: jest.fn(),
|
||||
getManifestFromRepo: jest.fn(),
|
||||
findFromManifest: jest.fn(),
|
||||
evaluateVersions: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/exec', () => ({
|
||||
exec: jest.fn(),
|
||||
getExecOutput: jest.fn()
|
||||
}));
|
||||
|
||||
// Dynamic imports after mocking
|
||||
const core = await import('@actions/core');
|
||||
const tc = await import('@actions/tool-cache');
|
||||
const exec = await import('@actions/exec');
|
||||
|
||||
// Non-mocked imports
|
||||
import {HttpClient} from '@actions/http-client';
|
||||
import type * as ifm from '@actions/http-client/lib/interfaces';
|
||||
|
||||
const installer = await import('../src/install-graalpy.js');
|
||||
const utils = await import('../src/utils.js');
|
||||
|
||||
import type {
|
||||
IGraalPyManifestRelease,
|
||||
IGraalPyManifestAsset
|
||||
} from '../src/utils.js';
|
||||
import manifestData from './data/graalpy.json' with {type: 'json'};
|
||||
|
||||
const IS_WINDOWS = utils.IS_WINDOWS;
|
||||
|
||||
const architecture = 'x64';
|
||||
|
||||
@@ -48,18 +103,18 @@ describe('findRelease', () => {
|
||||
browser_download_url: `https://github.com/graalvm/graal-languages-ea-builds/releases/download/graalpy-24.1.0-ea.09/graalpy-24.1.0-ea.09-${extensionName}`
|
||||
};
|
||||
|
||||
let warningSpy: jest.SpyInstance;
|
||||
let debugSpy: jest.SpyInstance;
|
||||
let infoSpy: jest.SpyInstance;
|
||||
let warningSpy: jest.Mock;
|
||||
let debugSpy: jest.Mock;
|
||||
let infoSpy: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
infoSpy = jest.spyOn(core, 'info');
|
||||
infoSpy = core.info as jest.Mock;
|
||||
infoSpy.mockImplementation(() => {});
|
||||
|
||||
warningSpy = jest.spyOn(core, 'warning');
|
||||
warningSpy = core.warning as jest.Mock;
|
||||
warningSpy.mockImplementation(() => null);
|
||||
|
||||
debugSpy = jest.spyOn(core, 'debug');
|
||||
debugSpy = core.debug as jest.Mock;
|
||||
debugSpy.mockImplementation(() => null);
|
||||
});
|
||||
|
||||
@@ -115,51 +170,57 @@ describe('findRelease', () => {
|
||||
resolvedGraalPyVersion: '24.1.0-ea.9'
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.clearAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
});
|
||||
|
||||
describe('installGraalPy', () => {
|
||||
let tcFind: jest.SpyInstance;
|
||||
let warningSpy: jest.SpyInstance;
|
||||
let debugSpy: jest.SpyInstance;
|
||||
let infoSpy: jest.SpyInstance;
|
||||
let spyExtractZip: jest.SpyInstance;
|
||||
let spyExtractTar: jest.SpyInstance;
|
||||
let spyFsReadDir: jest.SpyInstance;
|
||||
let spyFsWriteFile: jest.SpyInstance;
|
||||
let spyHttpClient: jest.SpyInstance;
|
||||
let spyExistsSync: jest.SpyInstance;
|
||||
let spyExec: jest.SpyInstance;
|
||||
let spySymlinkSync: jest.SpyInstance;
|
||||
let spyDownloadTool: jest.SpyInstance;
|
||||
let spyCacheDir: jest.SpyInstance;
|
||||
let spyChmodSync: jest.SpyInstance;
|
||||
let tcFind: jest.Mock;
|
||||
let infoSpy: jest.Mock;
|
||||
let warningSpy: jest.Mock;
|
||||
let debugSpy: jest.Mock;
|
||||
let spyExtractZip: jest.Mock;
|
||||
let spyExtractTar: jest.Mock;
|
||||
let spyFsReadDir: jest.SpiedFunction<typeof fs.readdirSync>;
|
||||
let spyFsWriteFile: jest.SpiedFunction<typeof fs.writeFileSync>;
|
||||
let spyHttpClient: jest.SpiedFunction<typeof HttpClient.prototype.getJson>;
|
||||
let spyExistsSync: jest.SpiedFunction<typeof fs.existsSync>;
|
||||
let spyExec: jest.Mock;
|
||||
let spySymlinkSync: jest.SpiedFunction<typeof fs.symlinkSync>;
|
||||
let spyDownloadTool: jest.Mock;
|
||||
let spyCacheDir: jest.Mock;
|
||||
let spyChmodSync: jest.SpiedFunction<typeof fs.chmodSync>;
|
||||
|
||||
beforeEach(() => {
|
||||
tcFind = jest.spyOn(tc, 'find');
|
||||
tcFind = tc.find as jest.Mock;
|
||||
tcFind.mockImplementation(() =>
|
||||
path.join('GraalPy', '3.6.12', architecture)
|
||||
);
|
||||
|
||||
spyDownloadTool = jest.spyOn(tc, 'downloadTool');
|
||||
spyDownloadTool = tc.downloadTool as jest.Mock;
|
||||
spyDownloadTool.mockImplementation(() => path.join(tempDir, 'GraalPy'));
|
||||
|
||||
spyExtractZip = jest.spyOn(tc, 'extractZip');
|
||||
spyExtractZip = tc.extractZip as jest.Mock;
|
||||
spyExtractZip.mockImplementation(() => tempDir);
|
||||
|
||||
spyExtractTar = jest.spyOn(tc, 'extractTar');
|
||||
spyExtractTar = tc.extractTar as jest.Mock;
|
||||
spyExtractTar.mockImplementation(() => tempDir);
|
||||
|
||||
infoSpy = jest.spyOn(core, 'info');
|
||||
infoSpy = core.info as jest.Mock;
|
||||
infoSpy.mockImplementation(() => {});
|
||||
|
||||
warningSpy = jest.spyOn(core, 'warning');
|
||||
warningSpy = core.warning as jest.Mock;
|
||||
warningSpy.mockImplementation(() => null);
|
||||
|
||||
debugSpy = jest.spyOn(core, 'debug');
|
||||
debugSpy = core.debug as jest.Mock;
|
||||
debugSpy.mockImplementation(() => null);
|
||||
|
||||
spyFsReadDir = jest.spyOn(fs, 'readdirSync');
|
||||
spyFsReadDir.mockImplementation(() => ['GraalPyTest']);
|
||||
spyFsReadDir.mockImplementation(() => ['GraalPyTest'] as any);
|
||||
|
||||
spyFsWriteFile = jest.spyOn(fs, 'writeFileSync');
|
||||
spyFsWriteFile.mockImplementation(() => undefined);
|
||||
@@ -176,7 +237,7 @@ describe('installGraalPy', () => {
|
||||
}
|
||||
);
|
||||
|
||||
spyExec = jest.spyOn(exec, 'exec');
|
||||
spyExec = exec.exec as jest.Mock;
|
||||
spyExec.mockImplementation(() => undefined);
|
||||
|
||||
spySymlinkSync = jest.spyOn(fs, 'symlinkSync');
|
||||
@@ -205,7 +266,7 @@ describe('installGraalPy', () => {
|
||||
});
|
||||
|
||||
it('found and install GraalPy', async () => {
|
||||
spyCacheDir = jest.spyOn(tc, 'cacheDir');
|
||||
spyCacheDir = tc.cacheDir as jest.Mock;
|
||||
spyCacheDir.mockImplementation(() =>
|
||||
path.join(toolDir, 'GraalPy', '21.3.0', architecture)
|
||||
);
|
||||
@@ -227,7 +288,7 @@ describe('installGraalPy', () => {
|
||||
});
|
||||
|
||||
it('found and install GraalPy, pre-release fallback', async () => {
|
||||
spyCacheDir = jest.spyOn(tc, 'cacheDir');
|
||||
spyCacheDir = tc.cacheDir as jest.Mock;
|
||||
spyCacheDir.mockImplementation(() =>
|
||||
path.join(toolDir, 'GraalPy', '24.1.0', architecture)
|
||||
);
|
||||
|
||||
+103
-47
@@ -1,20 +1,72 @@
|
||||
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
|
||||
import {fileURLToPath} from 'url';
|
||||
import fs from 'fs';
|
||||
|
||||
import {HttpClient} from '@actions/http-client';
|
||||
import * as ifm from '@actions/http-client/lib/interfaces';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
import * as exec from '@actions/exec';
|
||||
import * as core from '@actions/core';
|
||||
import * as path from 'path';
|
||||
|
||||
import * as installer from '../src/install-pypy';
|
||||
import {
|
||||
IPyPyManifestRelease,
|
||||
IPyPyManifestAsset,
|
||||
IS_WINDOWS
|
||||
} from '../src/utils';
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
import manifestData from './data/pypy.json';
|
||||
// Mock @actions modules
|
||||
jest.unstable_mockModule('@actions/core', () => ({
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
notice: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
setOutput: jest.fn(),
|
||||
getInput: jest.fn(),
|
||||
getBooleanInput: jest.fn(),
|
||||
getMultilineInput: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
exportVariable: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn(),
|
||||
setSecret: jest.fn(),
|
||||
isDebug: jest.fn(() => false),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn(),
|
||||
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
|
||||
toPlatformPath: jest.fn((p: string) => p),
|
||||
toWin32Path: jest.fn((p: string) => p),
|
||||
toPosixPath: jest.fn((p: string) => p)
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/tool-cache', () => ({
|
||||
find: jest.fn(),
|
||||
findAllVersions: jest.fn(),
|
||||
downloadTool: jest.fn(),
|
||||
extractZip: jest.fn(),
|
||||
extractTar: jest.fn(),
|
||||
extract7z: jest.fn(),
|
||||
extractXar: jest.fn(),
|
||||
cacheDir: jest.fn(),
|
||||
cacheFile: jest.fn(),
|
||||
getManifestFromRepo: jest.fn(),
|
||||
findFromManifest: jest.fn(),
|
||||
evaluateVersions: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/exec', () => ({
|
||||
exec: jest.fn(),
|
||||
getExecOutput: jest.fn()
|
||||
}));
|
||||
|
||||
// Dynamic imports after mocking
|
||||
const core = await import('@actions/core');
|
||||
const tc = await import('@actions/tool-cache');
|
||||
const exec = await import('@actions/exec');
|
||||
|
||||
// Non-mocked imports
|
||||
import {HttpClient} from '@actions/http-client';
|
||||
import type * as ifm from '@actions/http-client/lib/interfaces';
|
||||
|
||||
const installer = await import('../src/install-pypy.js');
|
||||
const utils = await import('../src/utils.js');
|
||||
|
||||
import type {IPyPyManifestRelease, IPyPyManifestAsset} from '../src/utils.js';
|
||||
import manifestData from './data/pypy.json' with {type: 'json'};
|
||||
|
||||
const IS_WINDOWS = utils.IS_WINDOWS;
|
||||
|
||||
let architecture: string;
|
||||
if (IS_WINDOWS) {
|
||||
@@ -58,19 +110,18 @@ describe('findRelease', () => {
|
||||
download_url: `https://test.download.python.org/pypy/pypy3.6-v7.4.0rc1-${extensionName}`
|
||||
};
|
||||
|
||||
let getBooleanInputSpy: jest.SpyInstance;
|
||||
let warningSpy: jest.SpyInstance;
|
||||
let debugSpy: jest.SpyInstance;
|
||||
let infoSpy: jest.SpyInstance;
|
||||
let infoSpy: jest.Mock;
|
||||
let warningSpy: jest.Mock;
|
||||
let debugSpy: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
infoSpy = jest.spyOn(core, 'info');
|
||||
infoSpy = core.info as jest.Mock;
|
||||
infoSpy.mockImplementation(() => {});
|
||||
|
||||
warningSpy = jest.spyOn(core, 'warning');
|
||||
warningSpy = core.warning as jest.Mock;
|
||||
warningSpy.mockImplementation(() => null);
|
||||
|
||||
debugSpy = jest.spyOn(core, 'debug');
|
||||
debugSpy = core.debug as jest.Mock;
|
||||
debugSpy.mockImplementation(() => null);
|
||||
});
|
||||
|
||||
@@ -215,50 +266,55 @@ describe('findRelease', () => {
|
||||
resolvedPyPyVersion: pypyVersion
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.clearAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
});
|
||||
|
||||
describe('installPyPy', () => {
|
||||
let tcFind: jest.SpyInstance;
|
||||
let getBooleanInputSpy: jest.SpyInstance;
|
||||
let warningSpy: jest.SpyInstance;
|
||||
let debugSpy: jest.SpyInstance;
|
||||
let infoSpy: jest.SpyInstance;
|
||||
let spyExtractZip: jest.SpyInstance;
|
||||
let spyExtractTar: jest.SpyInstance;
|
||||
let spyFsReadDir: jest.SpyInstance;
|
||||
let spyFsWriteFile: jest.SpyInstance;
|
||||
let spyHttpClient: jest.SpyInstance;
|
||||
let spyExistsSync: jest.SpyInstance;
|
||||
let spyExec: jest.SpyInstance;
|
||||
let spySymlinkSync: jest.SpyInstance;
|
||||
let spyDownloadTool: jest.SpyInstance;
|
||||
let spyCacheDir: jest.SpyInstance;
|
||||
let spyChmodSync: jest.SpyInstance;
|
||||
let tcFind: jest.Mock;
|
||||
let infoSpy: jest.Mock;
|
||||
let warningSpy: jest.Mock;
|
||||
let debugSpy: jest.Mock;
|
||||
let spyExtractZip: jest.Mock;
|
||||
let spyExtractTar: jest.Mock;
|
||||
let spyFsReadDir: jest.SpiedFunction<typeof fs.readdirSync>;
|
||||
let spyFsWriteFile: jest.SpiedFunction<typeof fs.writeFileSync>;
|
||||
let spyHttpClient: jest.SpiedFunction<typeof HttpClient.prototype.getJson>;
|
||||
let spyExistsSync: jest.SpiedFunction<typeof fs.existsSync>;
|
||||
let spyExec: jest.Mock;
|
||||
let spySymlinkSync: jest.SpiedFunction<typeof fs.symlinkSync>;
|
||||
let spyDownloadTool: jest.Mock;
|
||||
let spyCacheDir: jest.Mock;
|
||||
let spyChmodSync: jest.SpiedFunction<typeof fs.chmodSync>;
|
||||
|
||||
beforeEach(() => {
|
||||
tcFind = jest.spyOn(tc, 'find');
|
||||
tcFind = tc.find as jest.Mock;
|
||||
tcFind.mockImplementation(() => path.join('PyPy', '3.6.12', architecture));
|
||||
|
||||
spyDownloadTool = jest.spyOn(tc, 'downloadTool');
|
||||
spyDownloadTool = tc.downloadTool as jest.Mock;
|
||||
spyDownloadTool.mockImplementation(() => path.join(tempDir, 'PyPy'));
|
||||
|
||||
spyExtractZip = jest.spyOn(tc, 'extractZip');
|
||||
spyExtractZip = tc.extractZip as jest.Mock;
|
||||
spyExtractZip.mockImplementation(() => tempDir);
|
||||
|
||||
spyExtractTar = jest.spyOn(tc, 'extractTar');
|
||||
spyExtractTar = tc.extractTar as jest.Mock;
|
||||
spyExtractTar.mockImplementation(() => tempDir);
|
||||
|
||||
infoSpy = jest.spyOn(core, 'info');
|
||||
infoSpy = core.info as jest.Mock;
|
||||
infoSpy.mockImplementation(() => {});
|
||||
|
||||
warningSpy = jest.spyOn(core, 'warning');
|
||||
warningSpy = core.warning as jest.Mock;
|
||||
warningSpy.mockImplementation(() => null);
|
||||
|
||||
debugSpy = jest.spyOn(core, 'debug');
|
||||
debugSpy = core.debug as jest.Mock;
|
||||
debugSpy.mockImplementation(() => null);
|
||||
|
||||
spyFsReadDir = jest.spyOn(fs, 'readdirSync');
|
||||
spyFsReadDir.mockImplementation(() => ['PyPyTest']);
|
||||
spyFsReadDir.mockImplementation(() => ['PyPyTest'] as any);
|
||||
|
||||
spyFsWriteFile = jest.spyOn(fs, 'writeFileSync');
|
||||
spyFsWriteFile.mockImplementation(() => undefined);
|
||||
@@ -275,7 +331,7 @@ describe('installPyPy', () => {
|
||||
}
|
||||
);
|
||||
|
||||
spyExec = jest.spyOn(exec, 'exec');
|
||||
spyExec = exec.exec as jest.Mock;
|
||||
spyExec.mockImplementation(() => undefined);
|
||||
|
||||
spySymlinkSync = jest.spyOn(fs, 'symlinkSync');
|
||||
@@ -304,7 +360,7 @@ describe('installPyPy', () => {
|
||||
});
|
||||
|
||||
it('found and install PyPy', async () => {
|
||||
spyCacheDir = jest.spyOn(tc, 'cacheDir');
|
||||
spyCacheDir = tc.cacheDir as jest.Mock;
|
||||
spyCacheDir.mockImplementation(() =>
|
||||
path.join(toolDir, 'PyPy', '3.6.12', architecture)
|
||||
);
|
||||
@@ -328,7 +384,7 @@ describe('installPyPy', () => {
|
||||
});
|
||||
|
||||
it('found and install PyPy, pre-release fallback', async () => {
|
||||
spyCacheDir = jest.spyOn(tc, 'cacheDir');
|
||||
spyCacheDir = tc.cacheDir as jest.Mock;
|
||||
spyCacheDir.mockImplementation(() =>
|
||||
path.join(toolDir, 'PyPy', '3.6.12', architecture)
|
||||
);
|
||||
|
||||
@@ -1,16 +1,76 @@
|
||||
import {
|
||||
getManifest,
|
||||
getManifestFromRepo,
|
||||
getManifestFromURL
|
||||
} from '../src/install-python';
|
||||
import * as httpm from '@actions/http-client';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
|
||||
|
||||
jest.mock('@actions/http-client');
|
||||
jest.mock('@actions/tool-cache');
|
||||
jest.mock('@actions/tool-cache', () => ({
|
||||
class MockHttpClientError extends Error {
|
||||
statusCode: number;
|
||||
constructor(message: string, statusCode: number) {
|
||||
super(message);
|
||||
this.name = 'HttpClientError';
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
}
|
||||
|
||||
// Mock @actions/http-client
|
||||
jest.unstable_mockModule('@actions/http-client', () => ({
|
||||
HttpClient: jest.fn().mockImplementation(() => ({
|
||||
getJson: jest.fn()
|
||||
})),
|
||||
HttpClientError: MockHttpClientError,
|
||||
HttpCodes: {
|
||||
OK: 200,
|
||||
NotFound: 404,
|
||||
InternalServerError: 500
|
||||
}
|
||||
}));
|
||||
|
||||
// Mock @actions/cache (needed transitively by utils.ts)
|
||||
jest.unstable_mockModule('@actions/cache', () => ({
|
||||
saveCache: jest.fn(),
|
||||
restoreCache: jest.fn(),
|
||||
isFeatureAvailable: jest.fn()
|
||||
}));
|
||||
|
||||
// Mock @actions/tool-cache
|
||||
jest.unstable_mockModule('@actions/tool-cache', () => ({
|
||||
getManifestFromRepo: jest.fn()
|
||||
}));
|
||||
|
||||
// Mock @actions/core (needed by install-python.ts)
|
||||
jest.unstable_mockModule('@actions/core', () => ({
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
notice: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
setOutput: jest.fn(),
|
||||
getInput: jest.fn(),
|
||||
getBooleanInput: jest.fn(),
|
||||
getMultilineInput: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
exportVariable: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn(),
|
||||
setSecret: jest.fn(),
|
||||
isDebug: jest.fn(() => false),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn(),
|
||||
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
|
||||
toPlatformPath: jest.fn((p: string) => p),
|
||||
toWin32Path: jest.fn((p: string) => p),
|
||||
toPosixPath: jest.fn((p: string) => p)
|
||||
}));
|
||||
|
||||
// Mock @actions/exec (needed by install-python.ts)
|
||||
jest.unstable_mockModule('@actions/exec', () => ({
|
||||
exec: jest.fn(),
|
||||
getExecOutput: jest.fn()
|
||||
}));
|
||||
|
||||
// Dynamic imports after mocking
|
||||
const httpm = await import('@actions/http-client');
|
||||
const tc = await import('@actions/tool-cache');
|
||||
const {getManifest, getManifestFromRepo, getManifestFromURL} =
|
||||
await import('../src/install-python.js');
|
||||
const mockManifest = [
|
||||
{
|
||||
version: '1.0.0',
|
||||
@@ -36,19 +96,19 @@ describe('getManifest', () => {
|
||||
});
|
||||
|
||||
it('should return manifest from repo', async () => {
|
||||
(tc.getManifestFromRepo as jest.Mock).mockResolvedValue(mockManifest);
|
||||
(tc.getManifestFromRepo as jest.Mock<any>).mockResolvedValue(mockManifest);
|
||||
const manifest = await getManifest();
|
||||
expect(manifest).toEqual(mockManifest);
|
||||
});
|
||||
|
||||
it('should return manifest from URL if repo fetch fails', async () => {
|
||||
jest.useFakeTimers();
|
||||
(tc.getManifestFromRepo as jest.Mock).mockRejectedValue(
|
||||
(tc.getManifestFromRepo as jest.Mock<any>).mockRejectedValue(
|
||||
new Error('Fetch failed')
|
||||
);
|
||||
(httpm.HttpClient.prototype.getJson as jest.Mock).mockResolvedValue({
|
||||
result: mockManifest
|
||||
});
|
||||
(httpm.HttpClient as jest.Mock<any>).mockImplementation(() => ({
|
||||
getJson: jest.fn(async () => ({result: mockManifest}))
|
||||
}));
|
||||
const promise = getManifest();
|
||||
await jest.runAllTimersAsync();
|
||||
const manifest = await promise;
|
||||
@@ -57,10 +117,10 @@ describe('getManifest', () => {
|
||||
|
||||
it('should fall back to URL if repo returns a truncated/empty manifest', async () => {
|
||||
jest.useFakeTimers();
|
||||
(tc.getManifestFromRepo as jest.Mock).mockResolvedValue([]);
|
||||
(httpm.HttpClient.prototype.getJson as jest.Mock).mockResolvedValue({
|
||||
result: mockManifest
|
||||
});
|
||||
(tc.getManifestFromRepo as jest.Mock<any>).mockResolvedValue([]);
|
||||
(httpm.HttpClient as jest.Mock<any>).mockImplementation(() => ({
|
||||
getJson: jest.fn(async () => ({result: mockManifest}))
|
||||
}));
|
||||
const promise = getManifest();
|
||||
await jest.runAllTimersAsync();
|
||||
const manifest = await promise;
|
||||
@@ -69,22 +129,22 @@ describe('getManifest', () => {
|
||||
|
||||
it('should retry on a transient invalid manifest and then succeed', async () => {
|
||||
jest.useFakeTimers();
|
||||
(tc.getManifestFromRepo as jest.Mock)
|
||||
(tc.getManifestFromRepo as jest.Mock<any>)
|
||||
.mockResolvedValueOnce([])
|
||||
.mockResolvedValueOnce(mockManifest);
|
||||
const promise = getManifest();
|
||||
await jest.runAllTimersAsync();
|
||||
const manifest = await promise;
|
||||
expect(manifest).toEqual(mockManifest);
|
||||
expect(tc.getManifestFromRepo as jest.Mock).toHaveBeenCalledTimes(2);
|
||||
expect(tc.getManifestFromRepo as jest.Mock<any>).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should fail loudly when the manifest is truncated/empty on every source', async () => {
|
||||
jest.useFakeTimers();
|
||||
(tc.getManifestFromRepo as jest.Mock).mockResolvedValue([]);
|
||||
(httpm.HttpClient.prototype.getJson as jest.Mock).mockResolvedValue({
|
||||
result: []
|
||||
});
|
||||
(tc.getManifestFromRepo as jest.Mock<any>).mockResolvedValue([]);
|
||||
(httpm.HttpClient as jest.Mock<any>).mockImplementation(() => ({
|
||||
getJson: jest.fn(async () => ({result: []}))
|
||||
}));
|
||||
const promise = getManifest();
|
||||
// Prevent unhandled rejection before timers advance.
|
||||
const catchPromise = promise.catch(() => {});
|
||||
@@ -99,37 +159,47 @@ describe('getManifest', () => {
|
||||
const rateLimitError = Object.assign(new Error('API rate limit exceeded'), {
|
||||
httpStatusCode: 403
|
||||
});
|
||||
(tc.getManifestFromRepo as jest.Mock).mockRejectedValue(rateLimitError);
|
||||
(httpm.HttpClient.prototype.getJson as jest.Mock).mockResolvedValue({
|
||||
result: mockManifest
|
||||
});
|
||||
(tc.getManifestFromRepo as jest.Mock<any>).mockRejectedValue(
|
||||
rateLimitError
|
||||
);
|
||||
(httpm.HttpClient as jest.Mock<any>).mockImplementation(() => ({
|
||||
getJson: jest.fn(async () => ({result: mockManifest}))
|
||||
}));
|
||||
const manifest = await getManifest();
|
||||
expect(manifest).toEqual(mockManifest);
|
||||
expect(tc.getManifestFromRepo as jest.Mock).toHaveBeenCalledTimes(1);
|
||||
expect(tc.getManifestFromRepo as jest.Mock<any>).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getManifestFromRepo', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should return manifest from repo', async () => {
|
||||
(tc.getManifestFromRepo as jest.Mock).mockResolvedValue(mockManifest);
|
||||
(tc.getManifestFromRepo as jest.Mock<any>).mockResolvedValue(mockManifest);
|
||||
const manifest = await getManifestFromRepo();
|
||||
expect(manifest).toEqual(mockManifest);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getManifestFromURL', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should return manifest from URL', async () => {
|
||||
(httpm.HttpClient.prototype.getJson as jest.Mock).mockResolvedValue({
|
||||
result: mockManifest
|
||||
});
|
||||
(httpm.HttpClient as jest.Mock<any>).mockImplementation(() => ({
|
||||
getJson: jest.fn(async () => ({result: mockManifest}))
|
||||
}));
|
||||
const manifest = await getManifestFromURL();
|
||||
expect(manifest).toEqual(mockManifest);
|
||||
});
|
||||
|
||||
it('should throw error if unable to get manifest from URL', async () => {
|
||||
(httpm.HttpClient.prototype.getJson as jest.Mock).mockResolvedValue({
|
||||
result: null
|
||||
});
|
||||
(httpm.HttpClient as jest.Mock<any>).mockImplementation(() => ({
|
||||
getJson: jest.fn(async () => ({result: null}))
|
||||
}));
|
||||
await expect(getManifestFromURL()).rejects.toThrow(
|
||||
'Unable to get manifest from'
|
||||
);
|
||||
|
||||
+61
-14
@@ -1,11 +1,57 @@
|
||||
import * as cache from '@actions/cache';
|
||||
import * as core from '@actions/core';
|
||||
import {
|
||||
jest,
|
||||
describe,
|
||||
it,
|
||||
expect,
|
||||
afterAll,
|
||||
afterEach,
|
||||
beforeEach
|
||||
} from '@jest/globals';
|
||||
import {fileURLToPath} from 'url';
|
||||
import path from 'path';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// Mock @actions/cache
|
||||
jest.unstable_mockModule('@actions/cache', () => ({
|
||||
isFeatureAvailable: jest.fn(),
|
||||
saveCache: jest.fn(),
|
||||
restoreCache: jest.fn()
|
||||
}));
|
||||
|
||||
// Mock @actions/core
|
||||
jest.unstable_mockModule('@actions/core', () => ({
|
||||
getInput: jest.fn(),
|
||||
getBooleanInput: jest.fn(),
|
||||
getMultilineInput: jest.fn(),
|
||||
setOutput: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
info: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
notice: jest.fn(),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
exportVariable: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn(),
|
||||
setSecret: jest.fn(),
|
||||
isDebug: jest.fn(() => false),
|
||||
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
|
||||
toPlatformPath: jest.fn((p: string) => p),
|
||||
toWin32Path: jest.fn((p: string) => p),
|
||||
toPosixPath: jest.fn((p: string) => p)
|
||||
}));
|
||||
|
||||
const cache = await import('@actions/cache');
|
||||
const core = await import('@actions/core');
|
||||
import * as io from '@actions/io';
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import {
|
||||
const {
|
||||
validateVersion,
|
||||
validatePythonVersionFormatForPyPy,
|
||||
isCacheFeatureAvailable,
|
||||
@@ -18,10 +64,7 @@ import {
|
||||
IS_WINDOWS,
|
||||
getDownloadFileName,
|
||||
getVersionInputFromToolVersions
|
||||
} from '../src/utils';
|
||||
|
||||
jest.mock('@actions/cache');
|
||||
jest.mock('@actions/core');
|
||||
} = await import('../src/utils.js');
|
||||
|
||||
describe('validatePythonVersionFormatForPyPy', () => {
|
||||
it.each([
|
||||
@@ -55,8 +98,8 @@ describe('validateVersion', () => {
|
||||
|
||||
describe('isCacheFeatureAvailable', () => {
|
||||
it('isCacheFeatureAvailable disabled on GHES', () => {
|
||||
jest.spyOn(cache, 'isFeatureAvailable').mockImplementation(() => false);
|
||||
const infoMock = jest.spyOn(core, 'warning');
|
||||
(cache.isFeatureAvailable as jest.Mock).mockImplementation(() => false);
|
||||
const infoMock = core.warning as jest.Mock;
|
||||
const message =
|
||||
'Caching is only supported on GHES version >= 3.5. If you are on a version >= 3.5, please check with your GHES admin if the Actions cache service is enabled or not.';
|
||||
try {
|
||||
@@ -69,8 +112,8 @@ describe('isCacheFeatureAvailable', () => {
|
||||
});
|
||||
|
||||
it('isCacheFeatureAvailable disabled on dotcom', () => {
|
||||
jest.spyOn(cache, 'isFeatureAvailable').mockImplementation(() => false);
|
||||
const infoMock = jest.spyOn(core, 'warning');
|
||||
(cache.isFeatureAvailable as jest.Mock).mockImplementation(() => false);
|
||||
const infoMock = core.warning as jest.Mock;
|
||||
const message =
|
||||
'The runner was not able to contact the cache service. Caching will be skipped';
|
||||
try {
|
||||
@@ -83,9 +126,14 @@ describe('isCacheFeatureAvailable', () => {
|
||||
});
|
||||
|
||||
it('isCacheFeatureAvailable is enabled', () => {
|
||||
jest.spyOn(cache, 'isFeatureAvailable').mockImplementation(() => true);
|
||||
(cache.isFeatureAvailable as jest.Mock).mockImplementation(() => true);
|
||||
expect(isCacheFeatureAvailable()).toBe(true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
});
|
||||
|
||||
const tempDir = path.join(
|
||||
@@ -345,7 +393,6 @@ describe('isGhes', () => {
|
||||
const pristineEnv = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
process.env = {...pristineEnv};
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user