setup-python/__tests__/find-graalpy.test.ts
Tim Felgentreff 5f2af211d6
Add GraalPy support (#694)
* Add support for graalpy

* add graalpy test workflow

* format, lint and build

* symlink graalpy binaries names

* fix macos names for graalpy

* Don't attempt to update pip for graalpy

* Remove test schedule

* Extract common getBinaryDirectory function for PyPy and GraalPy

* Clean up and format

* Pass GitHub token to GraalPy queries

* Utilize pagination when querying GraalPy GitHub releases

* Build

* Fix lint errors

* Deal with possible multiple artifacts for a single releases

* Skip few GraalPy tests on windows - we don't have a windows release yet

* Fix GraalPy test on Mac OS

* Build

* Skip one more GraalPy test on windows

---------

Co-authored-by: Michael Simacek <michael.simacek@oracle.com>
2023-10-10 14:59:54 +02:00

379 lines
11 KiB
TypeScript

import fs from 'fs';
import {HttpClient} from '@actions/http-client';
import * as ifm from '@actions/http-client/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 semver from 'semver';
import * as finder from '../src/find-graalpy';
import {IGraalPyManifestRelease, IS_WINDOWS} from '../src/utils';
import manifestData from './data/graalpy.json';
const architecture = 'x64';
const toolDir = path.join(__dirname, 'runner', 'tools');
const tempDir = path.join(__dirname, 'runner', 'temp');
/* GraalPy doesn't have a windows release yet */
const describeSkipOnWindows = IS_WINDOWS ? describe.skip : describe;
describe('parseGraalPyVersion', () => {
it.each([
['graalpy-23', '23'],
['graalpy-23.0', '23.0'],
['graalpy23.0', '23.0']
])('%s -> %s', (input, expected) => {
expect(finder.parseGraalPyVersion(input)).toEqual(expected);
});
it.each(['', 'graalpy-', 'graalpy', 'p', 'notgraalpy-'])(
'throw on invalid input "%s"',
input => {
expect(() => finder.parseGraalPyVersion(input)).toThrow(
"Invalid 'version' property for GraalPy. GraalPy version should be specified as 'graalpy<python-version>' or 'graalpy-<python-version>'. See README for examples and documentation."
);
}
);
});
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;
beforeEach(() => {
tcFind = jest.spyOn(tc, 'find');
tcFind.mockImplementation((toolname: string, pythonVersion: string) => {
const semverVersion = new semver.Range(pythonVersion);
return semver.satisfies(actualGraalPyVersion, semverVersion)
? graalpyPath
: '';
});
infoSpy = jest.spyOn(core, 'info');
infoSpy.mockImplementation(() => null);
warningSpy = jest.spyOn(core, 'warning');
warningSpy.mockImplementation(() => null);
debugSpy = jest.spyOn(core, 'debug');
debugSpy.mockImplementation(() => null);
addPathSpy = jest.spyOn(core, 'addPath');
addPathSpy.mockImplementation(() => null);
exportVariableSpy = jest.spyOn(core, 'exportVariable');
exportVariableSpy.mockImplementation(() => null);
setOutputSpy = jest.spyOn(core, 'setOutput');
setOutputSpy.mockImplementation(() => null);
});
afterEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
jest.restoreAllMocks();
});
it('GraalPy exists on the path and versions are satisfied', () => {
expect(finder.findGraalPyToolCache('23.0.0', architecture)).toEqual({
installDir: graalpyPath,
resolvedGraalPyVersion: actualGraalPyVersion
});
});
it('GraalPy exists on the path and versions are satisfied with semver', () => {
expect(finder.findGraalPyToolCache('23.0', architecture)).toEqual({
installDir: graalpyPath,
resolvedGraalPyVersion: actualGraalPyVersion
});
});
it("GraalPy exists on the path, but version doesn't match", () => {
expect(finder.findGraalPyToolCache('22.3', architecture)).toEqual({
installDir: '',
resolvedGraalPyVersion: ''
});
});
});
describeSkipOnWindows('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;
const env = process.env;
beforeEach(() => {
getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
getBooleanInputSpy.mockImplementation(() => false);
infoSpy = jest.spyOn(core, 'info');
infoSpy.mockImplementation(() => {});
warningSpy = jest.spyOn(core, 'warning');
warningSpy.mockImplementation(() => null);
debugSpy = jest.spyOn(core, 'debug');
debugSpy.mockImplementation(() => null);
addPathSpy = jest.spyOn(core, 'addPath');
addPathSpy.mockImplementation(() => null);
exportVariableSpy = jest.spyOn(core, 'exportVariable');
exportVariableSpy.mockImplementation(() => null);
setOutputSpy = jest.spyOn(core, 'setOutput');
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);
}
return graalpyPath;
});
spyDownloadTool = jest.spyOn(tc, 'downloadTool');
spyDownloadTool.mockImplementation(() => path.join(tempDir, 'GraalPy'));
spyExtractZip = jest.spyOn(tc, 'extractZip');
spyExtractZip.mockImplementation(() => tempDir);
spyExtractTar = jest.spyOn(tc, 'extractTar');
spyExtractTar.mockImplementation(() => tempDir);
spyFsReadDir = jest.spyOn(fs, 'readdirSync');
spyFsReadDir.mockImplementation((directory: string) => ['GraalPyTest']);
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
spyHttpClient.mockImplementation(
async (): Promise<ifm.ITypedResponse<IGraalPyManifestRelease[]>> => {
const result = JSON.stringify(manifestData);
return {
statusCode: 200,
headers: {},
result: JSON.parse(result) as IGraalPyManifestRelease[]
};
}
);
spyExec = jest.spyOn(exec, 'exec');
spyExec.mockImplementation(() => undefined);
spySymlinkSync = jest.spyOn(fs, 'symlinkSync');
spySymlinkSync.mockImplementation(() => undefined);
spyExistsSync = jest.spyOn(fs, 'existsSync');
spyExistsSync.mockReturnValue(true);
spyCoreAddPath = jest.spyOn(core, 'addPath');
spyCoreExportVariable = jest.spyOn(core, 'exportVariable');
});
afterEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
jest.restoreAllMocks();
process.env = env;
});
it('found GraalPy in toolcache', async () => {
await expect(
finder.findGraalPyVersion(
'graalpy-23.0',
architecture,
true,
false,
false
)
).resolves.toEqual('23.0.0');
expect(spyCoreAddPath).toHaveBeenCalled();
expect(spyCoreExportVariable).toHaveBeenCalledWith(
'pythonLocation',
expect.anything()
);
expect(spyCoreExportVariable).toHaveBeenCalledWith(
'PKG_CONFIG_PATH',
expect.anything()
);
});
it('throw on invalid input format', async () => {
await expect(
finder.findGraalPyVersion('graalpy-x23', architecture, true, false, false)
).rejects.toThrow();
});
it('found and install successfully', async () => {
spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'GraalPy', '23.0.0', architecture)
);
spyChmodSync = jest.spyOn(fs, 'chmodSync');
spyChmodSync.mockImplementation(() => undefined);
await expect(
finder.findGraalPyVersion(
'graalpy-23.0.0',
architecture,
true,
false,
false
)
).resolves.toEqual('23.0.0');
expect(spyCoreAddPath).toHaveBeenCalled();
expect(spyCoreExportVariable).toHaveBeenCalledWith(
'pythonLocation',
expect.anything()
);
expect(spyCoreExportVariable).toHaveBeenCalledWith(
'PKG_CONFIG_PATH',
expect.anything()
);
});
it('found and install successfully without environment update', async () => {
spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'GraalPy', '23.0.0', architecture)
);
spyChmodSync = jest.spyOn(fs, 'chmodSync');
spyChmodSync.mockImplementation(() => undefined);
await expect(
finder.findGraalPyVersion(
'graalpy-23.0.0',
architecture,
false,
false,
false
)
).resolves.toEqual('23.0.0');
expect(spyCoreAddPath).not.toHaveBeenCalled();
expect(spyCoreExportVariable).not.toHaveBeenCalled();
});
it('throw if release is not found', async () => {
await expect(
finder.findGraalPyVersion(
'graalpy-19.0.0',
architecture,
true,
false,
false
)
).rejects.toThrow(
`GraalPy version 19.0.0 with arch ${architecture} not found`
);
});
it('check-latest enabled version found and used from toolcache', async () => {
await expect(
finder.findGraalPyVersion(
'graalpy-23.0.0',
architecture,
false,
true,
false
)
).resolves.toEqual('23.0.0');
expect(infoSpy).toHaveBeenCalledWith('Resolved as GraalPy 23.0.0');
});
it('check-latest enabled version found and install successfully', async () => {
spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'GraalPy', '23.0.0', architecture)
);
spyChmodSync = jest.spyOn(fs, 'chmodSync');
spyChmodSync.mockImplementation(() => undefined);
await expect(
finder.findGraalPyVersion(
'graalpy-23.0.0',
architecture,
false,
true,
false
)
).resolves.toEqual('23.0.0');
expect(infoSpy).toHaveBeenCalledWith('Resolved as GraalPy 23.0.0');
});
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);
}
return graalpyPath;
});
await expect(
finder.findGraalPyVersion(
'graalpy-22.3.4',
architecture,
false,
true,
false
)
).resolves.toEqual('22.3.4');
expect(infoSpy).toHaveBeenCalledWith(
'Failed to resolve GraalPy 22.3.4 from manifest'
);
});
it('found and install successfully, pre-release fallback', async () => {
spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'GraalPy', '23.1', architecture)
);
spyChmodSync = jest.spyOn(fs, 'chmodSync');
spyChmodSync.mockImplementation(() => undefined);
await expect(
finder.findGraalPyVersion(
'graalpy23.1',
architecture,
false,
false,
false
)
).rejects.toThrow();
await expect(
finder.findGraalPyVersion('graalpy23.1', architecture, false, false, true)
).resolves.toEqual('23.1.0-a.1');
});
});