2022-05-25 18:07:29 +08:00
import * as exec from '@actions/exec' ;
import * as cache from '@actions/cache' ;
import * as core from '@actions/core' ;
import * as cacheUtils from '../src/cache-utils' ;
import { PackageManagerInfo } from '../src/package-managers' ;
2023-10-25 14:53:26 +08:00
import fs , { ObjectEncodingOptions , PathLike } from 'fs' ;
import { getToolchainDirectoriesFromCachedDirectories } from '../src/cache-utils' ;
2022-05-25 18:07:29 +08:00
describe ( 'getCommandOutput' , ( ) = > {
//Arrange
2023-03-08 16:45:16 +08:00
const getExecOutputSpy = jest . spyOn ( exec , 'getExecOutput' ) ;
2022-05-25 18:07:29 +08:00
it ( 'should return trimmed stdout in case of successful exit code' , async ( ) = > {
//Arrange
const stdoutResult = ' stdout ' ;
const trimmedStdout = stdoutResult . trim ( ) ;
getExecOutputSpy . mockImplementation ( ( commandLine : string ) = > {
return new Promise < exec.ExecOutput > ( resolve = > {
resolve ( { exitCode : 0 , stdout : stdoutResult , stderr : '' } ) ;
} ) ;
} ) ;
//Act + Assert
return cacheUtils
. getCommandOutput ( 'command' )
. then ( data = > expect ( data ) . toBe ( trimmedStdout ) ) ;
} ) ;
it ( 'should return error in case of unsuccessful exit code' , async ( ) = > {
//Arrange
const stderrResult = 'error message' ;
getExecOutputSpy . mockImplementation ( ( commandLine : string ) = > {
return new Promise < exec.ExecOutput > ( resolve = > {
resolve ( { exitCode : 10 , stdout : '' , stderr : stderrResult } ) ;
} ) ;
} ) ;
//Act + Assert
2023-03-08 16:45:16 +08:00
await expect ( async ( ) = > {
2022-05-25 18:07:29 +08:00
await cacheUtils . getCommandOutput ( 'command' ) ;
} ) . rejects . toThrow ( ) ;
} ) ;
} ) ;
describe ( 'getPackageManagerInfo' , ( ) = > {
it ( 'should return package manager info in case of valid package manager name' , async ( ) = > {
//Arrange
const packageManagerName = 'default' ;
const expectedResult = {
dependencyFilePattern : 'go.sum' ,
cacheFolderCommandList : [ 'go env GOMODCACHE' , 'go env GOCACHE' ]
} ;
//Act + Assert
return cacheUtils
. getPackageManagerInfo ( packageManagerName )
. then ( data = > expect ( data ) . toEqual ( expectedResult ) ) ;
} ) ;
it ( 'should throw the error in case of invalid package manager name' , async ( ) = > {
//Arrange
const packageManagerName = 'invalidName' ;
//Act + Assert
2023-03-08 16:45:16 +08:00
await expect ( async ( ) = > {
2022-05-25 18:07:29 +08:00
await cacheUtils . getPackageManagerInfo ( packageManagerName ) ;
} ) . rejects . toThrow ( ) ;
} ) ;
} ) ;
describe ( 'getCacheDirectoryPath' , ( ) = > {
//Arrange
2023-03-08 16:45:16 +08:00
const getExecOutputSpy = jest . spyOn ( exec , 'getExecOutput' ) ;
2022-05-25 18:07:29 +08:00
const validPackageManager : PackageManagerInfo = {
dependencyFilePattern : 'go.sum' ,
cacheFolderCommandList : [ 'go env GOMODCACHE' , 'go env GOCACHE' ]
} ;
it ( 'should return path to the cache folders which specified package manager uses' , async ( ) = > {
//Arrange
getExecOutputSpy . mockImplementation ( ( commandLine : string ) = > {
return new Promise < exec.ExecOutput > ( resolve = > {
resolve ( { exitCode : 0 , stdout : 'path/to/cache/folder' , stderr : '' } ) ;
} ) ;
} ) ;
const expectedResult = [ 'path/to/cache/folder' , 'path/to/cache/folder' ] ;
//Act + Assert
return cacheUtils
. getCacheDirectoryPath ( validPackageManager )
. then ( data = > expect ( data ) . toEqual ( expectedResult ) ) ;
} ) ;
2022-12-19 18:22:17 +08:00
it ( 'should return path to the cache folder if one command return empty str' , async ( ) = > {
//Arrange
getExecOutputSpy . mockImplementationOnce ( ( commandLine : string ) = > {
return new Promise < exec.ExecOutput > ( resolve = > {
resolve ( { exitCode : 0 , stdout : 'path/to/cache/folder' , stderr : '' } ) ;
} ) ;
} ) ;
getExecOutputSpy . mockImplementationOnce ( ( commandLine : string ) = > {
return new Promise < exec.ExecOutput > ( resolve = > {
resolve ( { exitCode : 0 , stdout : '' , stderr : '' } ) ;
} ) ;
} ) ;
const expectedResult = [ 'path/to/cache/folder' ] ;
//Act + Assert
return cacheUtils
. getCacheDirectoryPath ( validPackageManager )
. then ( data = > expect ( data ) . toEqual ( expectedResult ) ) ;
} ) ;
it ( 'should throw if the both commands return empty str' , async ( ) = > {
getExecOutputSpy . mockImplementation ( ( commandLine : string ) = > {
return new Promise < exec.ExecOutput > ( resolve = > {
resolve ( { exitCode : 10 , stdout : '' , stderr : '' } ) ;
} ) ;
} ) ;
//Act + Assert
2023-03-08 16:45:16 +08:00
await expect ( async ( ) = > {
2022-12-19 18:22:17 +08:00
await cacheUtils . getCacheDirectoryPath ( validPackageManager ) ;
} ) . rejects . toThrow ( ) ;
} ) ;
2022-05-25 18:07:29 +08:00
it ( 'should throw if the specified package name is invalid' , async ( ) = > {
getExecOutputSpy . mockImplementation ( ( commandLine : string ) = > {
return new Promise < exec.ExecOutput > ( resolve = > {
resolve ( { exitCode : 10 , stdout : '' , stderr : 'Error message' } ) ;
} ) ;
} ) ;
//Act + Assert
2023-03-08 16:45:16 +08:00
await expect ( async ( ) = > {
2022-05-25 18:07:29 +08:00
await cacheUtils . getCacheDirectoryPath ( validPackageManager ) ;
} ) . rejects . toThrow ( ) ;
} ) ;
} ) ;
describe ( 'isCacheFeatureAvailable' , ( ) = > {
//Arrange
2023-03-08 16:45:16 +08:00
const isFeatureAvailableSpy = jest . spyOn ( cache , 'isFeatureAvailable' ) ;
const warningSpy = jest . spyOn ( core , 'warning' ) ;
2022-05-25 18:07:29 +08:00
it ( 'should return true when cache feature is available' , ( ) = > {
//Arrange
isFeatureAvailableSpy . mockImplementation ( ( ) = > {
return true ;
} ) ;
//Act
2023-03-08 16:45:16 +08:00
const functionResult = cacheUtils . isCacheFeatureAvailable ( ) ;
2022-05-25 18:07:29 +08:00
//Assert
expect ( functionResult ) . toBeTruthy ( ) ;
} ) ;
2023-03-08 16:45:16 +08:00
it ( 'should warn when cache feature is unavailable and GHES is not used' , ( ) = > {
2022-05-25 18:07:29 +08:00
//Arrange
isFeatureAvailableSpy . mockImplementation ( ( ) = > {
return false ;
} ) ;
process . env [ 'GITHUB_SERVER_URL' ] = 'https://github.com' ;
2023-03-08 16:45:16 +08:00
const warningMessage =
2022-05-25 18:07:29 +08:00
'The runner was not able to contact the cache service. Caching will be skipped' ;
//Act
cacheUtils . isCacheFeatureAvailable ( ) ;
//Assert
expect ( warningSpy ) . toHaveBeenCalledWith ( warningMessage ) ;
} ) ;
it ( 'should return false when cache feature is unavailable' , ( ) = > {
//Arrange
isFeatureAvailableSpy . mockImplementation ( ( ) = > {
return false ;
} ) ;
process . env [ 'GITHUB_SERVER_URL' ] = 'https://github.com' ;
//Act
2023-03-08 16:45:16 +08:00
const functionResult = cacheUtils . isCacheFeatureAvailable ( ) ;
2022-05-25 18:07:29 +08:00
//Assert
expect ( functionResult ) . toBeFalsy ( ) ;
} ) ;
2022-12-16 22:05:54 +08:00
it ( 'should warn when cache feature is unavailable and GHES is used' , ( ) = > {
2022-05-25 18:07:29 +08:00
//Arrange
isFeatureAvailableSpy . mockImplementation ( ( ) = > {
return false ;
} ) ;
process . env [ 'GITHUB_SERVER_URL' ] = 'https://nongithub.com' ;
2023-03-08 16:45:16 +08:00
const warningMessage =
2022-05-25 18:07:29 +08:00
'Cache action is only supported on GHES version >= 3.5. If you are on version >=3.5 Please check with GHES admin if Actions cache service is enabled or not.' ;
//Act + Assert
2022-12-16 22:05:54 +08:00
expect ( cacheUtils . isCacheFeatureAvailable ( ) ) . toBeFalsy ( ) ;
expect ( warningSpy ) . toHaveBeenCalledWith ( warningMessage ) ;
2022-05-25 18:07:29 +08:00
} ) ;
} ) ;
2023-10-25 14:53:26 +08:00
describe ( 'parseGoModForToolchainVersion' , ( ) = > {
const readFileSyncSpy = jest . spyOn ( fs , 'readFileSync' ) ;
afterEach ( ( ) = > {
jest . clearAllMocks ( ) ;
} ) ;
it ( 'should return null when go.mod file not exist' , async ( ) = > {
//Arrange
//Act
const toolchainVersion = cacheUtils . parseGoModForToolchainVersion (
'/tmp/non/exist/foo.bar'
) ;
//Assert
expect ( toolchainVersion ) . toBeNull ( ) ;
} ) ;
it ( 'should return null when go.mod file is empty' , async ( ) = > {
//Arrange
readFileSyncSpy . mockImplementation ( ( ) = > '' ) ;
//Act
const toolchainVersion = cacheUtils . parseGoModForToolchainVersion ( 'go.mod' ) ;
//Assert
expect ( toolchainVersion ) . toBeNull ( ) ;
} ) ;
it ( 'should return null when go.mod file does not contain toolchain version' , async ( ) = > {
//Arrange
readFileSyncSpy . mockImplementation ( ( ) = >
`
module example - mod
go 1.21 . 0
require golang . org / x / tools v0 . 13.0
require (
golang . org / x / mod v0 . 12.0 // indirect
golang . org / x / sys v0 . 12.0 // indirect
)
` .replace(/^ \ s+/gm, '')
) ;
//Act
const toolchainVersion = cacheUtils . parseGoModForToolchainVersion ( 'go.mod' ) ;
//Assert
expect ( toolchainVersion ) . toBeNull ( ) ;
} ) ;
it ( 'should return go version when go.mod file contains go version' , ( ) = > {
//Arrange
readFileSyncSpy . mockImplementation ( ( ) = >
`
module example - mod
go 1.21 . 0
toolchain go1 . 21.1
require golang . org / x / tools v0 . 13.0
require (
golang . org / x / mod v0 . 12.0 // indirect
golang . org / x / sys v0 . 12.0 // indirect
)
` .replace(/^ \ s+/gm, '')
) ;
//Act
const toolchainVersion = cacheUtils . parseGoModForToolchainVersion ( 'go.mod' ) ;
//Assert
expect ( toolchainVersion ) . toBe ( '1.21.1' ) ;
} ) ;
it ( 'should return go version when go.mod file contains more than one go version' , ( ) = > {
//Arrange
readFileSyncSpy . mockImplementation ( ( ) = >
`
module example - mod
go 1.21 . 0
toolchain go1 . 21.0
toolchain go1 . 21.1
require golang . org / x / tools v0 . 13.0
require (
golang . org / x / mod v0 . 12.0 // indirect
golang . org / x / sys v0 . 12.0 // indirect
)
` .replace(/^ \ s+/gm, '')
) ;
//Act
const toolchainVersion = cacheUtils . parseGoModForToolchainVersion ( 'go.mod' ) ;
//Assert
expect ( toolchainVersion ) . toBe ( '1.21.1' ) ;
} ) ;
} ) ;
describe ( 'getToolchainDirectoriesFromCachedDirectories' , ( ) = > {
const readdirSyncSpy = jest . spyOn ( fs , 'readdirSync' ) ;
const existsSyncSpy = jest . spyOn ( fs , 'existsSync' ) ;
const lstatSync = jest . spyOn ( fs , 'lstatSync' ) ;
afterEach ( ( ) = > {
jest . clearAllMocks ( ) ;
} ) ;
it ( 'should return empty array when cacheDirectories is empty' , async ( ) = > {
const toolcacheDirectories = getToolchainDirectoriesFromCachedDirectories (
'foo' ,
[ ]
) ;
expect ( toolcacheDirectories ) . toEqual ( [ ] ) ;
} ) ;
it ( 'should return empty array when cacheDirectories does not contain /go/pkg' , async ( ) = > {
readdirSyncSpy . mockImplementation ( dir = >
[ ` ${ dir } 1 ` , ` ${ dir } 2 ` , ` ${ dir } 3 ` ] . map ( s = > {
const de = new fs . Dirent ( ) ;
de . name = s ;
de . isDirectory = ( ) = > true ;
return de ;
} )
) ;
const toolcacheDirectories = getToolchainDirectoriesFromCachedDirectories (
'1.1.1' ,
[ 'foo' , 'bar' ]
) ;
expect ( toolcacheDirectories ) . toEqual ( [ ] ) ;
} ) ;
it ( 'should return empty array when cacheDirectories does not contain toolchain@v[0-9.]+-go{goVersion}' , async ( ) = > {
readdirSyncSpy . mockImplementation ( dir = >
[ ` ${ dir } 1 ` , ` ${ dir } 2 ` , ` ${ dir } 3 ` ] . map ( s = > {
const de = new fs . Dirent ( ) ;
de . name = s ;
de . isDirectory = ( ) = > true ;
return de ;
} )
) ;
const toolcacheDirectories = getToolchainDirectoriesFromCachedDirectories (
'foo' ,
[ 'foo/go/pkg/mod' , 'bar' ]
) ;
expect ( toolcacheDirectories ) . toEqual ( [ ] ) ;
} ) ;
it ( 'should return one entry when cacheDirectories contains toolchain@v[0-9.]+-go{goVersion} in /pkg/mod' , async ( ) = > {
let seqNo = 1 ;
readdirSyncSpy . mockImplementation ( dir = >
[ ` toolchain@v0.0.1-go1.1.1.arch- ${ seqNo ++ } ` ] . map ( s = > {
const de = new fs . Dirent ( ) ;
de . name = s ;
de . isDirectory = ( ) = > true ;
return de ;
} )
) ;
existsSyncSpy . mockReturnValue ( true ) ;
// @ts-ignore - jest does not have relaxed mocks, so we ignore not-implemented methods
lstatSync . mockImplementation ( ( ) = > ( { isDirectory : ( ) = > true } ) ) ;
const toolcacheDirectories = getToolchainDirectoriesFromCachedDirectories (
'1.1.1' ,
[ '/foo/go/pkg/mod' , 'bar' ]
) ;
expect ( toolcacheDirectories ) . toEqual ( [
'/foo/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.1.1.arch-1'
] ) ;
} ) ;
} ) ;