2020-12-17 18:03:54 +03:00
import fs from 'fs' ;
import * as utils from '../src/utils' ;
import { HttpClient } from '@actions/http-client' ;
2023-12-05 14:52:09 +01:00
import * as ifm from '@actions/http-client/lib/interfaces' ;
2020-12-17 18:03:54 +03:00
import * as tc from '@actions/tool-cache' ;
import * as exec from '@actions/exec' ;
2022-06-29 17:00:51 +02:00
import * as core from '@actions/core' ;
2020-12-17 18:03:54 +03:00
import * as path from 'path' ;
import * as semver from 'semver' ;
import * as finder from '../src/find-pypy' ;
import {
IPyPyManifestRelease ,
IS_WINDOWS ,
getPyPyVersionFromPath
} from '../src/utils' ;
2023-03-09 12:44:56 +02:00
import manifestData from './data/pypy.json' ;
2020-12-17 18:03:54 +03:00
let architecture : string ;
if ( IS_WINDOWS ) {
architecture = 'x86' ;
} else {
architecture = 'x64' ;
}
const toolDir = path . join ( __dirname , 'runner' , 'tools' );
const tempDir = path . join ( __dirname , 'runner' , 'temp' );
describe ( 'parsePyPyVersion' , () => {
it . each ([
[ 'pypy-3.6-v7.3.3' , { pythonVersion : '3.6' , pypyVersion : 'v7.3.3' }],
[ 'pypy-3.6-v7.3.x' , { pythonVersion : '3.6' , pypyVersion : 'v7.3.x' }],
[ 'pypy-3.6-v7.x' , { pythonVersion : '3.6' , pypyVersion : 'v7.x' }],
[ 'pypy-3.6' , { pythonVersion : '3.6' , pypyVersion : 'x' }],
[ 'pypy-3.6-nightly' , { pythonVersion : '3.6' , pypyVersion : 'nightly' }],
2022-05-18 15:20:53 +02:00
[ 'pypy-3.6-v7.3.3rc1' , { pythonVersion : '3.6' , pypyVersion : 'v7.3.3-rc.1' }],
[ 'pypy3.8-v7.3.7' , { pythonVersion : '3.8' , pypyVersion : 'v7.3.7' }],
[ 'pypy3.8-v7.3.x' , { pythonVersion : '3.8' , pypyVersion : 'v7.3.x' }],
[ 'pypy3.8-v7.x' , { pythonVersion : '3.8' , pypyVersion : 'v7.x' }],
[ 'pypy3.8' , { pythonVersion : '3.8' , pypyVersion : 'x' }],
[ 'pypy3.9-nightly' , { pythonVersion : '3.9' , pypyVersion : 'nightly' }],
[ 'pypy3.9-v7.3.8rc1' , { pythonVersion : '3.9' , pypyVersion : 'v7.3.8-rc.1' }]
2020-12-17 18:03:54 +03:00
])( '%s -> %s' , ( input , expected ) => {
expect ( finder . parsePyPyVersion ( input )). toEqual ( expected );
});
2022-05-18 15:20:53 +02:00
it . each ([ '' , 'pypy-' , 'pypy' , 'p' , 'notpypy-' ])(
'throw on invalid input "%s"' ,
input => {
2023-03-09 12:44:56 +02:00
expect (() => finder . parsePyPyVersion ( input )). toThrow (
2022-05-18 15:20:53 +02:00
"Invalid 'version' property for PyPy. PyPy version should be specified as 'pypy<python-version>' or 'pypy-<python-version>'. See README for examples and documentation."
);
}
);
it . each ([ 'pypy-2' , 'pypy-3' , 'pypy2' , 'pypy3' , 'pypy3.x' , 'pypy3.8.10' ])(
'throw on invalid input "%s"' ,
input => {
2023-03-09 12:44:56 +02:00
expect (() => finder . parsePyPyVersion ( input )). toThrow (
2022-05-18 15:20:53 +02:00
"Invalid format of Python version for PyPy. Python version should be specified in format 'x.y'. See README for examples and documentation."
);
}
);
2020-12-17 18:03:54 +03:00
});
describe ( 'getPyPyVersionFromPath' , () => {
it ( '/fake/toolcache/PyPy/3.6.5/x64 -> 3.6.5' , () => {
expect ( getPyPyVersionFromPath ( '/fake/toolcache/PyPy/3.6.5/x64' )). toEqual (
'3.6.5'
);
});
});
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 ;
2022-07-25 16:54:04 +02:00
let infoSpy : jest.SpyInstance ;
let warningSpy : jest.SpyInstance ;
let debugSpy : jest.SpyInstance ;
let addPathSpy : jest.SpyInstance ;
let exportVariableSpy : jest.SpyInstance ;
let setOutputSpy : jest.SpyInstance ;
2020-12-17 18:03:54 +03:00
beforeEach (() => {
tcFind = jest . spyOn ( tc , 'find' );
tcFind . mockImplementation (( toolname : string , pythonVersion : string ) => {
const semverVersion = new semver . Range ( pythonVersion );
return semver . satisfies ( actualPythonVersion , semverVersion )
? pypyPath
: '' ;
});
spyReadExactPyPyVersion = jest . spyOn ( utils , 'readExactPyPyVersionFile' );
spyReadExactPyPyVersion . mockImplementation (() => actualPyPyVersion );
2022-07-25 16:54:04 +02:00
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 );
2020-12-17 18:03:54 +03:00
});
afterEach (() => {
jest . resetAllMocks ();
jest . clearAllMocks ();
jest . restoreAllMocks ();
});
it ( 'PyPy exists on the path and versions are satisfied' , () => {
expect ( finder . findPyPyToolCache ( '3.6.17' , 'v7.5.4' , architecture )). toEqual ({
installDir : pypyPath ,
resolvedPythonVersion : actualPythonVersion ,
resolvedPyPyVersion : actualPyPyVersion
});
});
it ( 'PyPy exists on the path and versions are satisfied with semver' , () => {
expect ( finder . findPyPyToolCache ( '3.6' , 'v7.5.x' , architecture )). toEqual ({
installDir : pypyPath ,
resolvedPythonVersion : actualPythonVersion ,
resolvedPyPyVersion : actualPyPyVersion
});
});
it ( "PyPy exists on the path, but Python version doesn't match" , () => {
expect ( finder . findPyPyToolCache ( '3.7' , 'v7.5.4' , architecture )). toEqual ({
installDir : '' ,
resolvedPythonVersion : '' ,
resolvedPyPyVersion : ''
});
});
it ( "PyPy exists on the path, but PyPy version doesn't match" , () => {
expect ( finder . findPyPyToolCache ( '3.6' , 'v7.5.1' , architecture )). toEqual ({
installDir : null ,
resolvedPythonVersion : '' ,
resolvedPyPyVersion : ''
});
});
});
describe ( 'findPyPyVersion' , () => {
2022-07-25 16:54:04 +02:00
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 ;
2020-12-17 18:03:54 +03:00
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 ;
2022-06-29 17:00:51 +02:00
let spyCoreAddPath : jest.SpyInstance ;
let spyCoreExportVariable : jest.SpyInstance ;
2022-07-02 11:40:53 +02:00
const env = process . env ;
2020-12-17 18:03:54 +03:00
beforeEach (() => {
2022-07-25 16:54:04 +02:00
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 );
2022-07-02 11:40:53 +02:00
jest . resetModules ();
process . env = {... env };
2020-12-17 18:03:54 +03:00
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 );
}
return pypyPath ;
});
spyWriteExactPyPyVersionFile = jest . spyOn (
utils ,
'writeExactPyPyVersionFile'
);
spyWriteExactPyPyVersionFile . mockImplementation (() => null );
spyReadExactPyPyVersion = jest . spyOn ( utils , 'readExactPyPyVersionFile' );
spyReadExactPyPyVersion . mockImplementation (() => '7.3.3' );
spyDownloadTool = jest . spyOn ( tc , 'downloadTool' );
spyDownloadTool . mockImplementation (() => path . join ( tempDir , 'PyPy' ));
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 ) => [ 'PyPyTest' ]);
spyHttpClient = jest . spyOn ( HttpClient . prototype , 'getJson' );
spyHttpClient . mockImplementation (
2023-12-05 14:52:09 +01:00
async () : Promise < ifm.TypedResponse < IPyPyManifestRelease [] >> => {
2020-12-17 18:03:54 +03:00
const result = JSON . stringify ( manifestData );
return {
statusCode : 200 ,
headers : {},
result : JSON.parse ( result ) as IPyPyManifestRelease []
};
}
);
spyExec = jest . spyOn ( exec , 'exec' );
spyExec . mockImplementation (() => undefined );
spySymlinkSync = jest . spyOn ( fs , 'symlinkSync' );
spySymlinkSync . mockImplementation (() => undefined );
spyExistsSync = jest . spyOn ( fs , 'existsSync' );
spyExistsSync . mockReturnValue ( true );
2022-06-29 17:00:51 +02:00
spyCoreAddPath = jest . spyOn ( core , 'addPath' );
spyCoreExportVariable = jest . spyOn ( core , 'exportVariable' );
2020-12-17 18:03:54 +03:00
});
afterEach (() => {
jest . resetAllMocks ();
jest . clearAllMocks ();
jest . restoreAllMocks ();
2022-07-02 11:40:53 +02:00
process . env = env ;
2020-12-17 18:03:54 +03:00
});
it ( 'found PyPy in toolcache' , async () => {
await expect (
2023-01-27 22:19:31 +01:00
finder . findPyPyVersion (
'pypy-3.6-v7.3.x' ,
architecture ,
true ,
false ,
false
)
2020-12-17 18:03:54 +03:00
). resolves . toEqual ({
resolvedPythonVersion : '3.6.12' ,
resolvedPyPyVersion : '7.3.3'
});
2022-06-29 17:00:51 +02:00
expect ( spyCoreAddPath ). toHaveBeenCalled ();
expect ( spyCoreExportVariable ). toHaveBeenCalledWith (
'pythonLocation' ,
expect . anything ()
);
expect ( spyCoreExportVariable ). toHaveBeenCalledWith (
'PKG_CONFIG_PATH' ,
expect . anything ()
);
2020-12-17 18:03:54 +03:00
});
it ( 'throw on invalid input format' , async () => {
await expect (
2023-01-27 22:19:31 +01:00
finder . findPyPyVersion ( 'pypy3.7-v7.3.x' , architecture , true , false , false )
2020-12-17 18:03:54 +03:00
). rejects . toThrow ();
});
it ( 'throw on invalid input format pypy3.7-7.3.x' , async () => {
await expect (
2023-01-27 22:19:31 +01:00
finder . findPyPyVersion ( 'pypy3.7-v7.3.x' , architecture , true , false , false )
2020-12-17 18:03:54 +03:00
). rejects . toThrow ();
});
it ( 'found and install successfully' , async () => {
spyCacheDir = jest . spyOn ( tc , 'cacheDir' );
spyCacheDir . mockImplementation (() =>
path . join ( toolDir , 'PyPy' , '3.7.7' , architecture )
);
spyChmodSync = jest . spyOn ( fs , 'chmodSync' );
spyChmodSync . mockImplementation (() => undefined );
await expect (
2023-01-27 22:19:31 +01:00
finder . findPyPyVersion (
'pypy-3.7-v7.3.x' ,
architecture ,
true ,
false ,
false
)
2022-06-29 17:00:51 +02:00
). resolves . toEqual ({
resolvedPythonVersion : '3.7.9' ,
resolvedPyPyVersion : '7.3.3'
});
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 , 'PyPy' , '3.7.7' , architecture )
);
spyChmodSync = jest . spyOn ( fs , 'chmodSync' );
spyChmodSync . mockImplementation (() => undefined );
await expect (
2023-01-27 22:19:31 +01:00
finder . findPyPyVersion (
'pypy-3.7-v7.3.x' ,
architecture ,
false ,
false ,
false
)
2020-12-17 18:03:54 +03:00
). resolves . toEqual ({
resolvedPythonVersion : '3.7.9' ,
resolvedPyPyVersion : '7.3.3'
});
2022-06-29 17:00:51 +02:00
expect ( spyCoreAddPath ). not . toHaveBeenCalled ();
expect ( spyCoreExportVariable ). not . toHaveBeenCalled ();
2020-12-17 18:03:54 +03:00
});
it ( 'throw if release is not found' , async () => {
await expect (
2023-01-27 22:19:31 +01:00
finder . findPyPyVersion (
'pypy-3.7-v7.5.x' ,
architecture ,
true ,
false ,
false
)
2023-03-09 12:44:56 +02:00
). rejects . toThrow (
2020-12-17 18:03:54 +03:00
`PyPy version 3.7 (v7.5.x) with arch ${ architecture } not found`
);
});
2022-07-25 16:54:04 +02:00
it ( 'check-latest enabled version found and used from toolcache' , async () => {
await expect (
2023-01-27 22:19:31 +01:00
finder . findPyPyVersion (
'pypy-3.6-v7.3.x' ,
architecture ,
false ,
true ,
false
)
2022-07-25 16:54:04 +02:00
). resolves . toEqual ({
resolvedPythonVersion : '3.6.12' ,
resolvedPyPyVersion : '7.3.3'
});
expect ( infoSpy ). toHaveBeenCalledWith (
'Resolved as PyPy 7.3.3 with Python (3.6.12)'
);
});
it ( 'check-latest enabled version found and install successfully' , async () => {
spyCacheDir = jest . spyOn ( tc , 'cacheDir' );
spyCacheDir . mockImplementation (() =>
path . join ( toolDir , 'PyPy' , '3.7.7' , architecture )
);
spyChmodSync = jest . spyOn ( fs , 'chmodSync' );
spyChmodSync . mockImplementation (() => undefined );
await expect (
2023-01-27 22:19:31 +01:00
finder . findPyPyVersion (
'pypy-3.7-v7.3.x' ,
architecture ,
false ,
true ,
false
)
2022-07-25 16:54:04 +02:00
). resolves . toEqual ({
resolvedPythonVersion : '3.7.9' ,
resolvedPyPyVersion : '7.3.3'
});
expect ( infoSpy ). toHaveBeenCalledWith (
'Resolved as PyPy 7.3.3 with Python (3.7.9)'
);
});
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 );
}
return pypyPath ;
});
await expect (
2023-01-27 22:19:31 +01:00
finder . findPyPyVersion (
'pypy-3.8-v7.3.x' ,
architecture ,
false ,
true ,
false
)
2022-07-25 16:54:04 +02:00
). resolves . toEqual ({
resolvedPythonVersion : '3.8.8' ,
resolvedPyPyVersion : '7.3.3'
});
expect ( infoSpy ). toHaveBeenCalledWith (
'Failed to resolve PyPy v7.3.x with Python (3.8) from manifest'
);
});
2023-01-27 22:19:31 +01:00
it ( 'found and install successfully, pre-release fallback' , async () => {
spyCacheDir = jest . spyOn ( tc , 'cacheDir' );
spyCacheDir . mockImplementation (() =>
path . join ( toolDir , 'PyPy' , '3.8.12' , architecture )
);
spyChmodSync = jest . spyOn ( fs , 'chmodSync' );
spyChmodSync . mockImplementation (() => undefined );
await expect (
finder . findPyPyVersion ( 'pypy3.8' , architecture , false , false , false )
2023-03-09 12:44:56 +02:00
). rejects . toThrow ();
2023-01-27 22:19:31 +01:00
await expect (
finder . findPyPyVersion ( 'pypy3.8' , architecture , false , false , true )
). resolves . toEqual ({
resolvedPythonVersion : '3.8.12' ,
resolvedPyPyVersion : '7.3.8rc2'
});
});
2020-12-17 18:03:54 +03:00
});