2026-07-15 22:10:44 +05:30
import { jest , describe , it , expect , beforeEach , afterEach } from '@jest/globals' ;
import { fileURLToPath } from 'url' ;
2023-10-10 14:59:54 +02:00
import fs from 'fs' ;
import * as path from 'path' ;
2026-07-15 22:10:44 +05:30
const __dirname = path . dirname ( fileURLToPath ( import . meta . url ));
2023-10-10 14:59:54 +02:00
import * as semver from 'semver' ;
2026-07-15 22:10:44 +05:30
// 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' );
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' ;
2023-10-10 14:59:54 +02:00
2026-07-15 22:10:44 +05:30
import type { IGraalPyManifestRelease } from '../src/utils.js' ;
import manifestData from './data/graalpy.json' with { type : 'json' };
2023-10-10 14:59:54 +02:00
const architecture = 'x64' ;
const toolDir = path . join ( __dirname , 'runner' , 'tools' );
const tempDir = path . join ( __dirname , 'runner' , 'temp' );
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 );
2026-07-15 22:10:44 +05:30
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 ;
2023-10-10 14:59:54 +02:00
beforeEach (() => {
2026-07-15 22:10:44 +05:30
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
: '' ;
}
);
2023-10-10 14:59:54 +02:00
2026-07-15 22:10:44 +05:30
infoSpy = core . info as jest . Mock ;
2023-10-10 14:59:54 +02:00
infoSpy . mockImplementation (() => null );
2026-07-15 22:10:44 +05:30
warningSpy = core . warning as jest . Mock ;
2023-10-10 14:59:54 +02:00
warningSpy . mockImplementation (() => null );
2026-07-15 22:10:44 +05:30
debugSpy = core . debug as jest . Mock ;
2023-10-10 14:59:54 +02:00
debugSpy . mockImplementation (() => null );
2026-07-15 22:10:44 +05:30
addPathSpy = core . addPath as jest . Mock ;
2023-10-10 14:59:54 +02:00
addPathSpy . mockImplementation (() => null );
2026-07-15 22:10:44 +05:30
exportVariableSpy = core . exportVariable as jest . Mock ;
2023-10-10 14:59:54 +02:00
exportVariableSpy . mockImplementation (() => null );
2026-07-15 22:10:44 +05:30
setOutputSpy = core . setOutput as jest . Mock ;
2023-10-10 14:59:54 +02:00
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 : ''
});
});
});
2025-10-22 18:16:57 +02:00
describe ( 'findGraalPyVersion' , () => {
2026-07-15 22:10:44 +05:30
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 ;
2023-10-10 14:59:54 +02:00
const env = process . env ;
beforeEach (() => {
2026-07-15 22:10:44 +05:30
getBooleanInputSpy = core . getBooleanInput as jest . Mock ;
2023-10-10 14:59:54 +02:00
getBooleanInputSpy . mockImplementation (() => false );
2026-07-15 22:10:44 +05:30
infoSpy = core . info as jest . Mock ;
2023-10-10 14:59:54 +02:00
infoSpy . mockImplementation (() => {});
2026-07-15 22:10:44 +05:30
warningSpy = core . warning as jest . Mock ;
2023-10-10 14:59:54 +02:00
warningSpy . mockImplementation (() => null );
2026-07-15 22:10:44 +05:30
debugSpy = core . debug as jest . Mock ;
2023-10-10 14:59:54 +02:00
debugSpy . mockImplementation (() => null );
2026-07-15 22:10:44 +05:30
addPathSpy = core . addPath as jest . Mock ;
2023-10-10 14:59:54 +02:00
addPathSpy . mockImplementation (() => null );
2026-07-15 22:10:44 +05:30
exportVariableSpy = core . exportVariable as jest . Mock ;
2023-10-10 14:59:54 +02:00
exportVariableSpy . mockImplementation (() => null );
2026-07-15 22:10:44 +05:30
setOutputSpy = core . setOutput as jest . Mock ;
2023-10-10 14:59:54 +02:00
setOutputSpy . mockImplementation (() => null );
process . env = {... env };
2026-07-15 22:10:44 +05:30
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 ;
2023-10-10 14:59:54 +02:00
}
2026-07-15 22:10:44 +05:30
);
2023-10-10 14:59:54 +02:00
2026-07-15 22:10:44 +05:30
spyDownloadTool = tc . downloadTool as jest . Mock ;
2023-10-10 14:59:54 +02:00
spyDownloadTool . mockImplementation (() => path . join ( tempDir , 'GraalPy' ));
2026-07-15 22:10:44 +05:30
spyExtractZip = tc . extractZip as jest . Mock ;
2023-10-10 14:59:54 +02:00
spyExtractZip . mockImplementation (() => tempDir );
2026-07-15 22:10:44 +05:30
spyExtractTar = tc . extractTar as jest . Mock ;
2023-10-10 14:59:54 +02:00
spyExtractTar . mockImplementation (() => tempDir );
spyFsReadDir = jest . spyOn ( fs , 'readdirSync' );
2026-07-15 22:10:44 +05:30
spyFsReadDir . mockImplementation (() => [ 'GraalPyTest' ] as any );
2023-10-10 14:59:54 +02:00
spyHttpClient = jest . spyOn ( HttpClient . prototype , 'getJson' );
spyHttpClient . mockImplementation (
2023-12-05 14:52:09 +01:00
async () : Promise < ifm.TypedResponse < IGraalPyManifestRelease [] >> => {
2023-10-10 14:59:54 +02:00
const result = JSON . stringify ( manifestData );
return {
statusCode : 200 ,
headers : {},
result : JSON.parse ( result ) as IGraalPyManifestRelease []
};
}
);
2026-07-15 22:10:44 +05:30
spyExec = exec . exec as jest . Mock ;
2023-10-10 14:59:54 +02:00
spyExec . mockImplementation (() => undefined );
spySymlinkSync = jest . spyOn ( fs , 'symlinkSync' );
spySymlinkSync . mockImplementation (() => undefined );
spyExistsSync = jest . spyOn ( fs , 'existsSync' );
spyExistsSync . mockReturnValue ( true );
2026-07-15 22:10:44 +05:30
spyCoreAddPath = core . addPath as jest . Mock ;
2023-10-10 14:59:54 +02:00
2026-07-15 22:10:44 +05:30
spyCoreExportVariable = core . exportVariable as jest . Mock ;
2023-10-10 14:59:54 +02:00
});
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 () => {
2026-07-15 22:10:44 +05:30
spyCacheDir = tc . cacheDir as jest . Mock ;
2023-10-10 14:59:54 +02:00
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 () => {
2026-07-15 22:10:44 +05:30
spyCacheDir = tc . cacheDir as jest . Mock ;
2023-10-10 14:59:54 +02:00
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 () => {
2026-07-15 22:10:44 +05:30
spyCacheDir = tc . cacheDir as jest . Mock ;
2023-10-10 14:59:54 +02:00
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 () => {
2026-07-15 22:10:44 +05:30
( 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 ;
2023-10-10 14:59:54 +02:00
}
2026-07-15 22:10:44 +05:30
);
2023-10-10 14:59:54 +02:00
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 () => {
2026-07-15 22:10:44 +05:30
spyCacheDir = tc . cacheDir as jest . Mock ;
2023-10-10 14:59:54 +02:00
spyCacheDir . mockImplementation (() =>
2025-10-22 18:16:57 +02:00
path . join ( toolDir , 'GraalPy' , '24.1' , architecture )
2023-10-10 14:59:54 +02:00
);
spyChmodSync = jest . spyOn ( fs , 'chmodSync' );
spyChmodSync . mockImplementation (() => undefined );
await expect (
finder . findGraalPyVersion (
2025-10-22 18:16:57 +02:00
'graalpy24.1' ,
2023-10-10 14:59:54 +02:00
architecture ,
false ,
false ,
false
)
). rejects . toThrow ();
await expect (
2025-10-22 18:16:57 +02:00
finder . findGraalPyVersion ( 'graalpy24.1' , architecture , false , false , true )
). resolves . toEqual ( '24.1.0-ea.9' );
2023-10-10 14:59:54 +02:00
});
});