2022-04-01 00:41:27 +05:30
import * as cache from '@actions/cache' ;
import * as core from '@actions/core' ;
2023-06-28 22:02:44 +02:00
import * as io from '@actions/io' ;
import fs from 'fs' ;
import path from 'path' ;
2020-12-17 18:03:54 +03:00
import {
validateVersion ,
2022-04-01 00:41:27 +05:30
validatePythonVersionFormatForPyPy ,
2023-06-28 22:02:44 +02:00
isCacheFeatureAvailable ,
getVersionInputFromFile ,
2025-05-21 23:19:28 +02:00
getVersionsInputFromPlainFile ,
2023-10-10 14:59:54 +02:00
getVersionInputFromTomlFile ,
2025-07-24 18:40:39 -04:00
getVersionInputFromPipfileFile ,
2024-08-05 22:53:34 +05:30
getNextPageUrl ,
2024-10-21 18:42:17 +02:00
isGhes ,
2024-08-05 22:53:34 +05:30
IS_WINDOWS ,
2025-03-13 20:51:27 +05:30
getDownloadFileName ,
getVersionInputFromToolVersions
2020-12-17 18:03:54 +03:00
} from '../src/utils' ;
2022-04-01 00:41:27 +05:30
jest . mock ( '@actions/cache' );
jest . mock ( '@actions/core' );
2020-12-17 18:03:54 +03:00
describe ( 'validatePythonVersionFormatForPyPy' , () => {
it . each ([
2025-05-21 23:19:28 +02:00
[ '3.12' , true ],
[ '3.13' , true ],
[ '3.12.x' , false ],
[ '3.13.x' , false ],
2020-12-17 18:03:54 +03:00
[ '3.x' , false ],
[ '3' , false ]
])( '%s -> %s' , ( input , expected ) => {
expect ( validatePythonVersionFormatForPyPy ( input )). toEqual ( expected );
});
});
describe ( 'validateVersion' , () => {
it . each ([
[ 'v7.3.3' , true ],
[ 'v7.3.x' , true ],
[ 'v7.x' , true ],
[ 'x' , true ],
[ 'v7.3.3-rc.1' , true ],
[ 'nightly' , true ],
[ 'v7.3.b' , false ],
[ '3.6' , true ],
[ '3.b' , false ],
[ '3' , true ]
])( '%s -> %s' , ( input , expected ) => {
expect ( validateVersion ( input )). toEqual ( expected );
});
});
2022-04-01 00:41:27 +05:30
describe ( 'isCacheFeatureAvailable' , () => {
it ( 'isCacheFeatureAvailable disabled on GHES' , () => {
jest . spyOn ( cache , 'isFeatureAvailable' ). mockImplementation (() => false );
2022-12-19 22:00:46 +09:00
const infoMock = jest . spyOn ( core , 'warning' );
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.' ;
2022-04-01 00:41:27 +05:30
try {
process . env [ 'GITHUB_SERVER_URL' ] = 'http://example.com' ;
2022-12-19 22:00:46 +09:00
expect ( isCacheFeatureAvailable ()). toBeFalsy ();
expect ( infoMock ). toHaveBeenCalledWith ( message );
2022-04-01 00:41:27 +05:30
} finally {
delete process . env [ 'GITHUB_SERVER_URL' ];
}
});
it ( 'isCacheFeatureAvailable disabled on dotcom' , () => {
jest . spyOn ( cache , 'isFeatureAvailable' ). mockImplementation (() => false );
const infoMock = jest . spyOn ( core , 'warning' );
const message =
'The runner was not able to contact the cache service. Caching will be skipped' ;
try {
process . env [ 'GITHUB_SERVER_URL' ] = 'http://github.com' ;
expect ( isCacheFeatureAvailable ()). toBe ( false );
expect ( infoMock ). toHaveBeenCalledWith ( message );
} finally {
delete process . env [ 'GITHUB_SERVER_URL' ];
}
});
it ( 'isCacheFeatureAvailable is enabled' , () => {
jest . spyOn ( cache , 'isFeatureAvailable' ). mockImplementation (() => true );
expect ( isCacheFeatureAvailable ()). toBe ( true );
});
});
2023-06-28 22:02:44 +02:00
const tempDir = path . join (
__dirname ,
'runner' ,
path . join ( Math . random (). toString ( 36 ). substring ( 7 )),
'temp'
);
describe ( 'Version from file test' , () => {
2025-05-21 23:19:28 +02:00
it . each ([ getVersionsInputFromPlainFile , getVersionInputFromFile ])(
2023-06-28 22:02:44 +02:00
'Version from plain file test' ,
async _fn => {
await io . mkdirP ( tempDir );
const pythonVersionFileName = 'python-version.file' ;
const pythonVersionFilePath = path . join ( tempDir , pythonVersionFileName );
2025-05-21 23:19:28 +02:00
const pythonVersionFileContent = '3.13' ;
2023-06-28 22:02:44 +02:00
fs . writeFileSync ( pythonVersionFilePath , pythonVersionFileContent );
expect ( _fn ( pythonVersionFilePath )). toEqual ([ pythonVersionFileContent ]);
}
);
2025-05-21 23:19:28 +02:00
it . each ([ getVersionsInputFromPlainFile , getVersionInputFromFile ])(
'Versions from multiline plain file test' ,
async _fn => {
await io . mkdirP ( tempDir );
const pythonVersionFileName = 'python-version.file' ;
const pythonVersionFilePath = path . join ( tempDir , pythonVersionFileName );
const pythonVersionFileContent = '3.13\r\n3.12' ;
fs . writeFileSync ( pythonVersionFilePath , pythonVersionFileContent );
expect ( _fn ( pythonVersionFilePath )). toEqual ([ '3.13' , '3.12' ]);
}
);
it . each ([ getVersionsInputFromPlainFile , getVersionInputFromFile ])(
'Version from complex plain file test' ,
async _fn => {
await io . mkdirP ( tempDir );
const pythonVersionFileName = 'python-version.file' ;
const pythonVersionFilePath = path . join ( tempDir , pythonVersionFileName );
const pythonVersionFileContent =
'3.13/envs/virtualenv\r# 3.12\n3.11\r\n3.10\r\n 3.9 \r\n' ;
fs . writeFileSync ( pythonVersionFilePath , pythonVersionFileContent );
expect ( _fn ( pythonVersionFilePath )). toEqual ([
'3.13' ,
'3.11' ,
'3.10' ,
'3.9'
]);
}
);
2023-06-28 22:02:44 +02:00
it . each ([ getVersionInputFromTomlFile , getVersionInputFromFile ])(
'Version from standard pyproject.toml test' ,
async _fn => {
await io . mkdirP ( tempDir );
const pythonVersionFileName = 'pyproject.toml' ;
const pythonVersionFilePath = path . join ( tempDir , pythonVersionFileName );
2025-05-21 23:19:28 +02:00
const pythonVersion = '>=3.13.0' ;
2023-06-28 22:02:44 +02:00
const pythonVersionFileContent = `[project] \ nrequires-python = " ${ pythonVersion } "` ;
fs . writeFileSync ( pythonVersionFilePath , pythonVersionFileContent );
expect ( _fn ( pythonVersionFilePath )). toEqual ([ pythonVersion ]);
}
);
it . each ([ getVersionInputFromTomlFile , getVersionInputFromFile ])(
'Version from poetry pyproject.toml test' ,
async _fn => {
await io . mkdirP ( tempDir );
const pythonVersionFileName = 'pyproject.toml' ;
const pythonVersionFilePath = path . join ( tempDir , pythonVersionFileName );
2025-05-21 23:19:28 +02:00
const pythonVersion = '>=3.13.0' ;
2023-06-28 22:02:44 +02:00
const pythonVersionFileContent = `[tool.poetry.dependencies] \ npython = " ${ pythonVersion } "` ;
fs . writeFileSync ( pythonVersionFilePath , pythonVersionFileContent );
expect ( _fn ( pythonVersionFilePath )). toEqual ([ pythonVersion ]);
}
);
it . each ([ getVersionInputFromTomlFile , getVersionInputFromFile ])(
'Version undefined' ,
async _fn => {
await io . mkdirP ( tempDir );
const pythonVersionFileName = 'pyproject.toml' ;
const pythonVersionFilePath = path . join ( tempDir , pythonVersionFileName );
fs . writeFileSync ( pythonVersionFilePath , `` );
expect ( _fn ( pythonVersionFilePath )). toEqual ([]);
}
);
2025-03-13 20:51:27 +05:30
it . each ([ getVersionInputFromToolVersions ])(
'Version from .tool-versions' ,
async _fn => {
const toolVersionFileName = '.tool-versions' ;
const toolVersionFilePath = path . join ( tempDir , toolVersionFileName );
2025-05-21 23:19:28 +02:00
const toolVersionContent = 'python 3.13.2\nnodejs 16' ;
2025-03-13 20:51:27 +05:30
fs . writeFileSync ( toolVersionFilePath , toolVersionContent );
2025-05-21 23:19:28 +02:00
expect ( _fn ( toolVersionFilePath )). toEqual ([ '3.13.2' ]);
2025-03-13 20:51:27 +05:30
}
);
it . each ([ getVersionInputFromToolVersions ])(
'Version from .tool-versions with comment' ,
async _fn => {
const toolVersionFileName = '.tool-versions' ;
const toolVersionFilePath = path . join ( tempDir , toolVersionFileName );
2025-05-21 23:19:28 +02:00
const toolVersionContent = '# python 3.13\npython 3.12' ;
2025-03-13 20:51:27 +05:30
fs . writeFileSync ( toolVersionFilePath , toolVersionContent );
2025-05-21 23:19:28 +02:00
expect ( _fn ( toolVersionFilePath )). toEqual ([ '3.12' ]);
2025-03-13 20:51:27 +05:30
}
);
it . each ([ getVersionInputFromToolVersions ])(
'Version from .tool-versions with whitespace' ,
async _fn => {
const toolVersionFileName = '.tool-versions' ;
const toolVersionFilePath = path . join ( tempDir , toolVersionFileName );
2025-05-21 23:19:28 +02:00
const toolVersionContent = ' python 3.13 ' ;
2025-03-13 20:51:27 +05:30
fs . writeFileSync ( toolVersionFilePath , toolVersionContent );
2025-05-21 23:19:28 +02:00
expect ( _fn ( toolVersionFilePath )). toEqual ([ '3.13' ]);
2025-03-13 20:51:27 +05:30
}
);
it . each ([ getVersionInputFromToolVersions ])(
'Version from .tool-versions with v prefix' ,
async _fn => {
const toolVersionFileName = '.tool-versions' ;
const toolVersionFilePath = path . join ( tempDir , toolVersionFileName );
2025-05-21 23:19:28 +02:00
const toolVersionContent = 'python v3.13.2' ;
2025-03-13 20:51:27 +05:30
fs . writeFileSync ( toolVersionFilePath , toolVersionContent );
2025-05-21 23:19:28 +02:00
expect ( _fn ( toolVersionFilePath )). toEqual ([ '3.13.2' ]);
2025-03-13 20:51:27 +05:30
}
);
it . each ([ getVersionInputFromToolVersions ])(
'Version from .tool-versions with pypy version' ,
async _fn => {
const toolVersionFileName = '.tool-versions' ;
const toolVersionFilePath = path . join ( tempDir , toolVersionFileName );
2025-05-21 23:19:28 +02:00
const toolVersionContent = 'python pypy3.10-7.3.19' ;
2025-03-13 20:51:27 +05:30
fs . writeFileSync ( toolVersionFilePath , toolVersionContent );
2025-05-21 23:19:28 +02:00
expect ( _fn ( toolVersionFilePath )). toEqual ([ 'pypy3.10-7.3.19' ]);
2025-03-13 20:51:27 +05:30
}
);
it . each ([ getVersionInputFromToolVersions ])(
'Version from .tool-versions with alpha Releases' ,
async _fn => {
const toolVersionFileName = '.tool-versions' ;
const toolVersionFilePath = path . join ( tempDir , toolVersionFileName );
const toolVersionContent = 'python 3.14.0a5t' ;
fs . writeFileSync ( toolVersionFilePath , toolVersionContent );
expect ( _fn ( toolVersionFilePath )). toEqual ([ '3.14.0a5t' ]);
}
);
it . each ([ getVersionInputFromToolVersions ])(
'Version from .tool-versions with dev suffix' ,
async _fn => {
const toolVersionFileName = '.tool-versions' ;
const toolVersionFilePath = path . join ( tempDir , toolVersionFileName );
const toolVersionContent = 'python 3.14t-dev' ;
fs . writeFileSync ( toolVersionFilePath , toolVersionContent );
expect ( _fn ( toolVersionFilePath )). toEqual ([ '3.14t-dev' ]);
}
);
2025-07-24 18:40:39 -04:00
it . each ([ getVersionInputFromPipfileFile , getVersionInputFromFile ])(
'Version from python_version in Pipfile' ,
async _fn => {
await io . mkdirP ( tempDir );
const pythonVersionFileName = 'Pipfile' ;
const pythonVersionFilePath = path . join ( tempDir , pythonVersionFileName );
const pythonVersion = '3.13' ;
const pythonVersionFileContent = `[requires] \ npython_version = " ${ pythonVersion } "` ;
fs . writeFileSync ( pythonVersionFilePath , pythonVersionFileContent );
expect ( _fn ( pythonVersionFilePath )). toEqual ([ pythonVersion ]);
}
);
it . each ([ getVersionInputFromPipfileFile , getVersionInputFromFile ])(
'Version from python_full_version in Pipfile' ,
async _fn => {
await io . mkdirP ( tempDir );
const pythonVersionFileName = 'Pipfile' ;
const pythonVersionFilePath = path . join ( tempDir , pythonVersionFileName );
const pythonVersion = '3.13.0' ;
const pythonVersionFileContent = `[requires] \ npython_full_version = " ${ pythonVersion } "` ;
fs . writeFileSync ( pythonVersionFilePath , pythonVersionFileContent );
expect ( _fn ( pythonVersionFilePath )). toEqual ([ pythonVersion ]);
}
);
it . each ([ getVersionInputFromPipfileFile , getVersionInputFromFile ])(
'Pipfile undefined version' ,
async _fn => {
await io . mkdirP ( tempDir );
const pythonVersionFileName = 'Pipfile' ;
const pythonVersionFilePath = path . join ( tempDir , pythonVersionFileName );
const pythonVersionFileContent = `` ;
fs . writeFileSync ( pythonVersionFilePath , pythonVersionFileContent );
expect ( _fn ( pythonVersionFilePath )). toEqual ([]);
}
);
2023-06-28 22:02:44 +02:00
});
2023-10-10 14:59:54 +02:00
describe ( 'getNextPageUrl' , () => {
it ( 'GitHub API pagination next page is parsed correctly' , () => {
function generateResponse ( link : string ) {
return {
statusCode : 200 ,
result : null ,
headers : {
link : link
}
};
}
const page1Links =
'<https://api.github.com/repositories/129883600/releases?page=2>; rel="next", <https://api.github.com/repositories/129883600/releases?page=3>; rel="last"' ;
expect ( getNextPageUrl ( generateResponse ( page1Links ))). toStrictEqual (
'https://api.github.com/repositories/129883600/releases?page=2'
);
const page2Links =
'<https://api.github.com/repositories/129883600/releases?page=1>; rel="prev", <https://api.github.com/repositories/129883600/releases?page=1>; rel="first"' ;
expect ( getNextPageUrl ( generateResponse ( page2Links ))). toBeNull ();
});
});
2024-08-05 22:53:34 +05:30
describe ( 'getDownloadFileName' , () => {
const originalEnv = process . env ;
const tempDir = path . join ( __dirname , 'runner' , 'temp' );
beforeEach (() => {
process . env = {... originalEnv };
});
afterEach (() => {
process . env = originalEnv ;
});
it ( 'should return the correct path on Windows' , () => {
if ( IS_WINDOWS ) {
process . env [ 'RUNNER_TEMP' ] = tempDir ;
const downloadUrl =
'https://github.com/actions/sometool/releases/tag/1.2.3-20200402.6/sometool-1.2.3-win32-x64.zip' ;
const expectedPath = path . join (
process . env . RUNNER_TEMP ,
path . basename ( downloadUrl )
);
expect ( getDownloadFileName ( downloadUrl )). toBe ( expectedPath );
}
});
it ( 'should return undefined on non-Windows' , () => {
if ( ! IS_WINDOWS ) {
const downloadUrl =
'https://github.com/actions/sometool/releases/tag/1.2.3-20200402.6/sometool-1.2.3-linux-x64.tar.gz' ;
expect ( getDownloadFileName ( downloadUrl )). toBeUndefined ();
}
});
});
2024-10-21 18:42:17 +02:00
describe ( 'isGhes' , () => {
const pristineEnv = process . env ;
beforeEach (() => {
jest . resetModules ();
process . env = {... pristineEnv };
});
afterAll (() => {
process . env = pristineEnv ;
});
it ( 'returns false when the GITHUB_SERVER_URL environment variable is not defined' , async () => {
delete process . env [ 'GITHUB_SERVER_URL' ];
expect ( isGhes ()). toBeFalsy ();
});
it ( 'returns false when the GITHUB_SERVER_URL environment variable is set to github.com' , async () => {
process . env [ 'GITHUB_SERVER_URL' ] = 'https://github.com' ;
expect ( isGhes ()). toBeFalsy ();
});
it ( 'returns false when the GITHUB_SERVER_URL environment variable is set to a GitHub Enterprise Cloud-style URL' , async () => {
process . env [ 'GITHUB_SERVER_URL' ] = 'https://contoso.ghe.com' ;
expect ( isGhes ()). toBeFalsy ();
});
it ( 'returns false when the GITHUB_SERVER_URL environment variable has a .localhost suffix' , async () => {
process . env [ 'GITHUB_SERVER_URL' ] = 'https://mock-github.localhost' ;
expect ( isGhes ()). toBeFalsy ();
});
it ( 'returns true when the GITHUB_SERVER_URL environment variable is set to some other URL' , async () => {
process . env [ 'GITHUB_SERVER_URL' ] = 'https://src.onpremise.fabrikam.com' ;
expect ( isGhes ()). toBeTruthy ();
});
});