mirror of
https://git.mirrors.martin98.com/https://github.com/actions/toolkit
synced 2026-04-30 20:58:05 +08:00
Initial commit to create @actions/cache package
This commit is contained in:
1
packages/cache/__tests__/__fixtures__/helloWorld.txt
vendored
Normal file
1
packages/cache/__tests__/__fixtures__/helloWorld.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
hello world
|
||||
28
packages/cache/__tests__/cacheHttpClient.test.ts
vendored
Normal file
28
packages/cache/__tests__/cacheHttpClient.test.ts
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
import {getCacheVersion} from '../src/internal/cacheHttpClient'
|
||||
import {CompressionMethod} from '../src/internal/constants'
|
||||
|
||||
test('getCacheVersion with path input and compression method undefined returns version', async () => {
|
||||
const inputPath = 'node_modules'
|
||||
const result = getCacheVersion(inputPath)
|
||||
expect(result).toEqual(
|
||||
'b3e0c6cb5ecf32614eeb2997d905b9c297046d7cbf69062698f25b14b4cb0985'
|
||||
)
|
||||
})
|
||||
|
||||
test('getCacheVersion with zstd compression returns version', async () => {
|
||||
const inputPath = 'node_modules'
|
||||
const result = getCacheVersion(inputPath, CompressionMethod.Zstd)
|
||||
|
||||
expect(result).toEqual(
|
||||
'273877e14fd65d270b87a198edbfa2db5a43de567c9a548d2a2505b408befe24'
|
||||
)
|
||||
})
|
||||
|
||||
test('getCacheVersion with gzip compression does not change vesion', async () => {
|
||||
const inputPath = 'node_modules'
|
||||
const result = getCacheVersion(inputPath, CompressionMethod.Gzip)
|
||||
|
||||
expect(result).toEqual(
|
||||
'b3e0c6cb5ecf32614eeb2997d905b9c297046d7cbf69062698f25b14b4cb0985'
|
||||
)
|
||||
})
|
||||
177
packages/cache/__tests__/cacheUtils.test.ts
vendored
Normal file
177
packages/cache/__tests__/cacheUtils.test.ts
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
import * as core from '@actions/core'
|
||||
import * as io from '@actions/io'
|
||||
import {promises as fs} from 'fs'
|
||||
import * as os from 'os'
|
||||
import * as path from 'path'
|
||||
import {v4 as uuidV4} from 'uuid'
|
||||
import * as cacheUtils from '../src/internal/cacheUtils'
|
||||
|
||||
jest.mock('@actions/core')
|
||||
jest.mock('os')
|
||||
|
||||
function getTempDir(): string {
|
||||
return path.join(__dirname, '_temp', 'cacheUtils')
|
||||
}
|
||||
|
||||
afterAll(async () => {
|
||||
delete process.env['GITHUB_WORKSPACE']
|
||||
await io.rmRF(getTempDir())
|
||||
})
|
||||
|
||||
test('getArchiveFileSize returns file size', () => {
|
||||
const filePath = path.join(__dirname, '__fixtures__', 'helloWorld.txt')
|
||||
|
||||
const size = cacheUtils.getArchiveFileSize(filePath)
|
||||
|
||||
expect(size).toBe(11)
|
||||
})
|
||||
|
||||
test('logWarning logs a message with a warning prefix', () => {
|
||||
const message = 'A warning occurred.'
|
||||
|
||||
const infoMock = jest.spyOn(core, 'info')
|
||||
|
||||
cacheUtils.logWarning(message)
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(`[warning]${message}`)
|
||||
})
|
||||
|
||||
test('resolvePaths with no ~ in path', async () => {
|
||||
const filePath = '.cache'
|
||||
|
||||
// Create the following layout:
|
||||
// cwd
|
||||
// cwd/.cache
|
||||
// cwd/.cache/file.txt
|
||||
|
||||
const root = path.join(getTempDir(), 'no-tilde')
|
||||
// tarball entries will be relative to workspace
|
||||
process.env['GITHUB_WORKSPACE'] = root
|
||||
|
||||
await fs.mkdir(root, {recursive: true})
|
||||
const cache = path.join(root, '.cache')
|
||||
await fs.mkdir(cache, {recursive: true})
|
||||
await fs.writeFile(path.join(cache, 'file.txt'), 'cached')
|
||||
|
||||
const originalCwd = process.cwd()
|
||||
|
||||
try {
|
||||
process.chdir(root)
|
||||
|
||||
const resolvedPath = await cacheUtils.resolvePaths([filePath])
|
||||
|
||||
const expectedPath = [filePath]
|
||||
expect(resolvedPath).toStrictEqual(expectedPath)
|
||||
} finally {
|
||||
process.chdir(originalCwd)
|
||||
}
|
||||
})
|
||||
|
||||
test('resolvePaths with ~ in path', async () => {
|
||||
const cacheDir = uuidV4()
|
||||
const filePath = `~/${cacheDir}`
|
||||
// Create the following layout:
|
||||
// ~/uuid
|
||||
// ~/uuid/file.txt
|
||||
|
||||
const homedir = jest.requireActual('os').homedir()
|
||||
const homedirMock = jest.spyOn(os, 'homedir')
|
||||
homedirMock.mockReturnValue(homedir)
|
||||
|
||||
const target = path.join(homedir, cacheDir)
|
||||
await fs.mkdir(target, {recursive: true})
|
||||
await fs.writeFile(path.join(target, 'file.txt'), 'cached')
|
||||
|
||||
const root = getTempDir()
|
||||
process.env['GITHUB_WORKSPACE'] = root
|
||||
|
||||
try {
|
||||
const resolvedPath = await cacheUtils.resolvePaths([filePath])
|
||||
|
||||
const expectedPath = [path.relative(root, target)]
|
||||
expect(resolvedPath).toStrictEqual(expectedPath)
|
||||
} finally {
|
||||
await io.rmRF(target)
|
||||
}
|
||||
})
|
||||
|
||||
test('resolvePaths with home not found', async () => {
|
||||
const filePath = '~/.cache/yarn'
|
||||
const homedirMock = jest.spyOn(os, 'homedir')
|
||||
homedirMock.mockReturnValue('')
|
||||
|
||||
await expect(cacheUtils.resolvePaths([filePath])).rejects.toThrow(
|
||||
'Unable to determine HOME directory'
|
||||
)
|
||||
})
|
||||
|
||||
test('resolvePaths inclusion pattern returns found', async () => {
|
||||
const pattern = '*.ts'
|
||||
// Create the following layout:
|
||||
// inclusion-patterns
|
||||
// inclusion-patterns/miss.txt
|
||||
// inclusion-patterns/test.ts
|
||||
|
||||
const root = path.join(getTempDir(), 'inclusion-patterns')
|
||||
// tarball entries will be relative to workspace
|
||||
process.env['GITHUB_WORKSPACE'] = root
|
||||
|
||||
await fs.mkdir(root, {recursive: true})
|
||||
await fs.writeFile(path.join(root, 'miss.txt'), 'no match')
|
||||
await fs.writeFile(path.join(root, 'test.ts'), 'match')
|
||||
|
||||
const originalCwd = process.cwd()
|
||||
|
||||
try {
|
||||
process.chdir(root)
|
||||
|
||||
const resolvedPath = await cacheUtils.resolvePaths([pattern])
|
||||
|
||||
const expectedPath = ['test.ts']
|
||||
expect(resolvedPath).toStrictEqual(expectedPath)
|
||||
} finally {
|
||||
process.chdir(originalCwd)
|
||||
}
|
||||
})
|
||||
|
||||
test('resolvePaths exclusion pattern returns not found', async () => {
|
||||
const patterns = ['*.ts', '!test.ts']
|
||||
// Create the following layout:
|
||||
// exclusion-patterns
|
||||
// exclusion-patterns/miss.txt
|
||||
// exclusion-patterns/test.ts
|
||||
|
||||
const root = path.join(getTempDir(), 'exclusion-patterns')
|
||||
// tarball entries will be relative to workspace
|
||||
process.env['GITHUB_WORKSPACE'] = root
|
||||
|
||||
await fs.mkdir(root, {recursive: true})
|
||||
await fs.writeFile(path.join(root, 'miss.txt'), 'no match')
|
||||
await fs.writeFile(path.join(root, 'test.ts'), 'no match')
|
||||
|
||||
const originalCwd = process.cwd()
|
||||
|
||||
try {
|
||||
process.chdir(root)
|
||||
|
||||
const resolvedPath = await cacheUtils.resolvePaths(patterns)
|
||||
|
||||
const expectedPath: string[] = []
|
||||
expect(resolvedPath).toStrictEqual(expectedPath)
|
||||
} finally {
|
||||
process.chdir(originalCwd)
|
||||
}
|
||||
})
|
||||
|
||||
test('unlinkFile unlinks file', async () => {
|
||||
const testDirectory = await fs.mkdtemp('unlinkFileTest')
|
||||
const testFile = path.join(testDirectory, 'test.txt')
|
||||
await fs.writeFile(testFile, 'hello world')
|
||||
|
||||
await cacheUtils.unlinkFile(testFile)
|
||||
|
||||
// This should throw as testFile should not exist
|
||||
await expect(fs.stat(testFile)).rejects.toThrow()
|
||||
|
||||
await fs.rmdir(testDirectory)
|
||||
})
|
||||
303
packages/cache/__tests__/restoreCache.test.ts
vendored
Normal file
303
packages/cache/__tests__/restoreCache.test.ts
vendored
Normal file
@@ -0,0 +1,303 @@
|
||||
import * as core from '@actions/core'
|
||||
import * as path from 'path'
|
||||
import {restoreCache} from '../src/cache'
|
||||
import * as cacheHttpClient from '../src/internal/cacheHttpClient'
|
||||
import * as cacheUtils from '../src/internal/cacheUtils'
|
||||
import {CacheFilename, CompressionMethod} from '../src/internal/constants'
|
||||
import {ArtifactCacheEntry} from '../src/internal/contracts'
|
||||
import * as tar from '../src/internal/tar'
|
||||
|
||||
jest.mock('../src/internal/cacheHttpClient')
|
||||
jest.mock('../src/internal/cacheUtils')
|
||||
jest.mock('../src/internal/tar')
|
||||
|
||||
beforeAll(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
jest.spyOn(cacheUtils, 'getCacheFileName').mockImplementation(cm => {
|
||||
const actualUtils = jest.requireActual('../src/internal/cacheUtils')
|
||||
return actualUtils.getCacheFileName(cm)
|
||||
})
|
||||
})
|
||||
|
||||
test('restore with no path should fail', async () => {
|
||||
const inputPath = ''
|
||||
const key = 'node-test'
|
||||
const failedMock = jest.spyOn(core, 'setFailed')
|
||||
await restoreCache(inputPath, key)
|
||||
expect(failedMock).toHaveBeenCalledWith(
|
||||
'Input required and not supplied: path'
|
||||
)
|
||||
})
|
||||
|
||||
test('restore with too many keys should fail', async () => {
|
||||
const inputPath = 'node_modules'
|
||||
const key = 'node-test'
|
||||
const restoreKeys = [...Array(20).keys()].map(x => x.toString())
|
||||
const failedMock = jest.spyOn(core, 'setFailed')
|
||||
await restoreCache(inputPath, key, restoreKeys)
|
||||
expect(failedMock).toHaveBeenCalledWith(
|
||||
`Key Validation Error: Keys are limited to a maximum of 10.`
|
||||
)
|
||||
})
|
||||
|
||||
test('restore with large key should fail', async () => {
|
||||
const inputPath = 'node_modules'
|
||||
const key = 'foo'.repeat(512) // Over the 512 character limit
|
||||
const failedMock = jest.spyOn(core, 'setFailed')
|
||||
await restoreCache(inputPath, key)
|
||||
expect(failedMock).toHaveBeenCalledWith(
|
||||
`Key Validation Error: ${key} cannot be larger than 512 characters.`
|
||||
)
|
||||
})
|
||||
|
||||
test('restore with invalid key should fail', async () => {
|
||||
const inputPath = 'node_modules'
|
||||
const key = 'comma,comma'
|
||||
const failedMock = jest.spyOn(core, 'setFailed')
|
||||
await restoreCache(inputPath, key)
|
||||
expect(failedMock).toHaveBeenCalledWith(
|
||||
`Key Validation Error: ${key} cannot contain commas.`
|
||||
)
|
||||
})
|
||||
|
||||
test('restore with no cache found', async () => {
|
||||
const inputPath = 'node_modules'
|
||||
const key = 'node-test'
|
||||
|
||||
const infoMock = jest.spyOn(core, 'info')
|
||||
const failedMock = jest.spyOn(core, 'setFailed')
|
||||
|
||||
const clientMock = jest.spyOn(cacheHttpClient, 'getCacheEntry')
|
||||
clientMock.mockImplementation(async () => {
|
||||
return Promise.resolve(null)
|
||||
})
|
||||
|
||||
await restoreCache(inputPath, key)
|
||||
|
||||
expect(failedMock).toHaveBeenCalledTimes(0)
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
`Cache not found for input keys: ${key}`
|
||||
)
|
||||
})
|
||||
|
||||
test('restore with server error should fail', async () => {
|
||||
const inputPath = 'node_modules'
|
||||
const key = 'node-test'
|
||||
|
||||
const logWarningMock = jest.spyOn(cacheUtils, 'logWarning')
|
||||
const failedMock = jest.spyOn(core, 'setFailed')
|
||||
|
||||
const clientMock = jest.spyOn(cacheHttpClient, 'getCacheEntry')
|
||||
clientMock.mockImplementation(() => {
|
||||
throw new Error('HTTP Error Occurred')
|
||||
})
|
||||
|
||||
await restoreCache(inputPath, key)
|
||||
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(1)
|
||||
expect(logWarningMock).toHaveBeenCalledWith('HTTP Error Occurred')
|
||||
expect(failedMock).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
test('restore with restore keys and no cache found', async () => {
|
||||
const inputPath = 'node_modules'
|
||||
const key = 'node-test'
|
||||
const restoreKey = 'node-'
|
||||
|
||||
const infoMock = jest.spyOn(core, 'info')
|
||||
const failedMock = jest.spyOn(core, 'setFailed')
|
||||
|
||||
const clientMock = jest.spyOn(cacheHttpClient, 'getCacheEntry')
|
||||
clientMock.mockImplementation(async () => {
|
||||
return Promise.resolve(null)
|
||||
})
|
||||
|
||||
await restoreCache(inputPath, key, [restoreKey])
|
||||
|
||||
expect(failedMock).toHaveBeenCalledTimes(0)
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
`Cache not found for input keys: ${key}, ${restoreKey}`
|
||||
)
|
||||
})
|
||||
|
||||
test('restore with gzip compressed cache found', async () => {
|
||||
const inputPath = 'node_modules'
|
||||
const key = 'node-test'
|
||||
|
||||
const infoMock = jest.spyOn(core, 'info')
|
||||
const failedMock = jest.spyOn(core, 'setFailed')
|
||||
|
||||
const cacheEntry: ArtifactCacheEntry = {
|
||||
cacheKey: key,
|
||||
scope: 'refs/heads/master',
|
||||
archiveLocation: 'www.actionscache.test/download'
|
||||
}
|
||||
const getCacheMock = jest.spyOn(cacheHttpClient, 'getCacheEntry')
|
||||
getCacheMock.mockImplementation(async () => {
|
||||
return Promise.resolve(cacheEntry)
|
||||
})
|
||||
|
||||
const tempPath = '/foo/bar'
|
||||
|
||||
const createTempDirectoryMock = jest.spyOn(cacheUtils, 'createTempDirectory')
|
||||
createTempDirectoryMock.mockImplementation(async () => {
|
||||
return Promise.resolve(tempPath)
|
||||
})
|
||||
|
||||
const archivePath = path.join(tempPath, CacheFilename.Gzip)
|
||||
const downloadCacheMock = jest.spyOn(cacheHttpClient, 'downloadCache')
|
||||
|
||||
const fileSize = 142
|
||||
const getArchiveFileSizeMock = jest
|
||||
.spyOn(cacheUtils, 'getArchiveFileSize')
|
||||
.mockReturnValue(fileSize)
|
||||
|
||||
const extractTarMock = jest.spyOn(tar, 'extractTar')
|
||||
const unlinkFileMock = jest.spyOn(cacheUtils, 'unlinkFile')
|
||||
|
||||
const compression = CompressionMethod.Gzip
|
||||
const getCompressionMock = jest
|
||||
.spyOn(cacheUtils, 'getCompressionMethod')
|
||||
.mockReturnValue(Promise.resolve(compression))
|
||||
|
||||
await restoreCache(inputPath, key)
|
||||
|
||||
expect(getCacheMock).toHaveBeenCalledWith([key], inputPath, {
|
||||
compressionMethod: compression
|
||||
})
|
||||
expect(createTempDirectoryMock).toHaveBeenCalledTimes(1)
|
||||
expect(downloadCacheMock).toHaveBeenCalledWith(
|
||||
cacheEntry.archiveLocation,
|
||||
archivePath
|
||||
)
|
||||
expect(getArchiveFileSizeMock).toHaveBeenCalledWith(archivePath)
|
||||
|
||||
expect(extractTarMock).toHaveBeenCalledTimes(1)
|
||||
expect(extractTarMock).toHaveBeenCalledWith(archivePath, compression)
|
||||
|
||||
expect(unlinkFileMock).toHaveBeenCalledTimes(1)
|
||||
expect(unlinkFileMock).toHaveBeenCalledWith(archivePath)
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`)
|
||||
expect(failedMock).toHaveBeenCalledTimes(0)
|
||||
expect(getCompressionMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('restore with a pull request event and zstd compressed cache found', async () => {
|
||||
const inputPath = 'node_modules'
|
||||
const key = 'node-test'
|
||||
|
||||
const infoMock = jest.spyOn(core, 'info')
|
||||
const failedMock = jest.spyOn(core, 'setFailed')
|
||||
|
||||
const cacheEntry: ArtifactCacheEntry = {
|
||||
cacheKey: key,
|
||||
scope: 'refs/heads/master',
|
||||
archiveLocation: 'www.actionscache.test/download'
|
||||
}
|
||||
const getCacheMock = jest.spyOn(cacheHttpClient, 'getCacheEntry')
|
||||
getCacheMock.mockImplementation(async () => {
|
||||
return Promise.resolve(cacheEntry)
|
||||
})
|
||||
const tempPath = '/foo/bar'
|
||||
|
||||
const createTempDirectoryMock = jest.spyOn(cacheUtils, 'createTempDirectory')
|
||||
createTempDirectoryMock.mockImplementation(async () => {
|
||||
return Promise.resolve(tempPath)
|
||||
})
|
||||
|
||||
const archivePath = path.join(tempPath, CacheFilename.Zstd)
|
||||
const downloadCacheMock = jest.spyOn(cacheHttpClient, 'downloadCache')
|
||||
|
||||
const fileSize = 62915000
|
||||
const getArchiveFileSizeMock = jest
|
||||
.spyOn(cacheUtils, 'getArchiveFileSize')
|
||||
.mockReturnValue(fileSize)
|
||||
|
||||
const extractTarMock = jest.spyOn(tar, 'extractTar')
|
||||
const compression = CompressionMethod.Zstd
|
||||
const getCompressionMock = jest
|
||||
.spyOn(cacheUtils, 'getCompressionMethod')
|
||||
.mockReturnValue(Promise.resolve(compression))
|
||||
|
||||
await restoreCache(inputPath, key)
|
||||
|
||||
expect(getCacheMock).toHaveBeenCalledWith([key], inputPath, {
|
||||
compressionMethod: compression
|
||||
})
|
||||
expect(createTempDirectoryMock).toHaveBeenCalledTimes(1)
|
||||
expect(downloadCacheMock).toHaveBeenCalledWith(
|
||||
cacheEntry.archiveLocation,
|
||||
archivePath
|
||||
)
|
||||
expect(getArchiveFileSizeMock).toHaveBeenCalledWith(archivePath)
|
||||
expect(infoMock).toHaveBeenCalledWith(`Cache Size: ~60 MB (62915000 B)`)
|
||||
|
||||
expect(extractTarMock).toHaveBeenCalledTimes(1)
|
||||
expect(extractTarMock).toHaveBeenCalledWith(archivePath, compression)
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`)
|
||||
expect(failedMock).toHaveBeenCalledTimes(0)
|
||||
expect(getCompressionMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('restore with cache found for restore key', async () => {
|
||||
const inputPath = 'node_modules'
|
||||
const key = 'node-test'
|
||||
const restoreKey = 'node-'
|
||||
|
||||
const infoMock = jest.spyOn(core, 'info')
|
||||
const failedMock = jest.spyOn(core, 'setFailed')
|
||||
|
||||
const cacheEntry: ArtifactCacheEntry = {
|
||||
cacheKey: restoreKey,
|
||||
scope: 'refs/heads/master',
|
||||
archiveLocation: 'www.actionscache.test/download'
|
||||
}
|
||||
const getCacheMock = jest.spyOn(cacheHttpClient, 'getCacheEntry')
|
||||
getCacheMock.mockImplementation(async () => {
|
||||
return Promise.resolve(cacheEntry)
|
||||
})
|
||||
const tempPath = '/foo/bar'
|
||||
|
||||
const createTempDirectoryMock = jest.spyOn(cacheUtils, 'createTempDirectory')
|
||||
createTempDirectoryMock.mockImplementation(async () => {
|
||||
return Promise.resolve(tempPath)
|
||||
})
|
||||
|
||||
const archivePath = path.join(tempPath, CacheFilename.Zstd)
|
||||
const downloadCacheMock = jest.spyOn(cacheHttpClient, 'downloadCache')
|
||||
|
||||
const fileSize = 142
|
||||
const getArchiveFileSizeMock = jest
|
||||
.spyOn(cacheUtils, 'getArchiveFileSize')
|
||||
.mockReturnValue(fileSize)
|
||||
|
||||
const extractTarMock = jest.spyOn(tar, 'extractTar')
|
||||
const compression = CompressionMethod.Zstd
|
||||
const getCompressionMock = jest
|
||||
.spyOn(cacheUtils, 'getCompressionMethod')
|
||||
.mockReturnValue(Promise.resolve(compression))
|
||||
|
||||
await restoreCache(inputPath, key, [restoreKey])
|
||||
|
||||
expect(getCacheMock).toHaveBeenCalledWith([key, restoreKey], inputPath, {
|
||||
compressionMethod: compression
|
||||
})
|
||||
expect(createTempDirectoryMock).toHaveBeenCalledTimes(1)
|
||||
expect(downloadCacheMock).toHaveBeenCalledWith(
|
||||
cacheEntry.archiveLocation,
|
||||
archivePath
|
||||
)
|
||||
expect(getArchiveFileSizeMock).toHaveBeenCalledWith(archivePath)
|
||||
expect(infoMock).toHaveBeenCalledWith(`Cache Size: ~0 MB (142 B)`)
|
||||
|
||||
expect(extractTarMock).toHaveBeenCalledTimes(1)
|
||||
expect(extractTarMock).toHaveBeenCalledWith(archivePath, compression)
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
`Cache restored from key: ${restoreKey}`
|
||||
)
|
||||
expect(failedMock).toHaveBeenCalledTimes(0)
|
||||
expect(getCompressionMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
219
packages/cache/__tests__/saveCache.test.ts
vendored
Normal file
219
packages/cache/__tests__/saveCache.test.ts
vendored
Normal file
@@ -0,0 +1,219 @@
|
||||
import * as core from '@actions/core'
|
||||
import * as path from 'path'
|
||||
import {saveCache} from '../src/cache'
|
||||
import * as cacheHttpClient from '../src/internal/cacheHttpClient'
|
||||
import * as cacheUtils from '../src/internal/cacheUtils'
|
||||
import {CacheFilename, CompressionMethod} from '../src/internal/constants'
|
||||
import * as tar from '../src/internal/tar'
|
||||
|
||||
jest.mock('@actions/core')
|
||||
jest.mock('../src/internal/cacheHttpClient')
|
||||
jest.mock('../src/internal/cacheUtils')
|
||||
jest.mock('../src/internal/tar')
|
||||
|
||||
beforeAll(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
jest.spyOn(cacheUtils, 'getCacheFileName').mockImplementation(cm => {
|
||||
const actualUtils = jest.requireActual('../src/internal/cacheUtils')
|
||||
return actualUtils.getCacheFileName(cm)
|
||||
})
|
||||
|
||||
jest.spyOn(cacheUtils, 'resolvePaths').mockImplementation(async filePaths => {
|
||||
return filePaths.map(x => path.resolve(x))
|
||||
})
|
||||
|
||||
jest.spyOn(cacheUtils, 'createTempDirectory').mockImplementation(async () => {
|
||||
return Promise.resolve('/foo/bar')
|
||||
})
|
||||
})
|
||||
|
||||
test('save with missing input outputs warning', async () => {
|
||||
const logWarningMock = jest.spyOn(cacheUtils, 'logWarning')
|
||||
const failedMock = jest.spyOn(core, 'setFailed')
|
||||
|
||||
const inputPath = ''
|
||||
const primaryKey = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
|
||||
|
||||
await saveCache(inputPath, primaryKey)
|
||||
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
'Input required and not supplied: path'
|
||||
)
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(1)
|
||||
expect(failedMock).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
test('save with large cache outputs warning', async () => {
|
||||
const logWarningMock = jest.spyOn(cacheUtils, 'logWarning')
|
||||
const failedMock = jest.spyOn(core, 'setFailed')
|
||||
|
||||
const inputPath = 'node_modules'
|
||||
const primaryKey = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
|
||||
const cachePaths = [path.resolve(inputPath)]
|
||||
|
||||
const createTarMock = jest.spyOn(tar, 'createTar')
|
||||
|
||||
const cacheSize = 6 * 1024 * 1024 * 1024 //~6GB, over the 5GB limit
|
||||
jest.spyOn(cacheUtils, 'getArchiveFileSize').mockImplementationOnce(() => {
|
||||
return cacheSize
|
||||
})
|
||||
const compression = CompressionMethod.Gzip
|
||||
const getCompressionMock = jest
|
||||
.spyOn(cacheUtils, 'getCompressionMethod')
|
||||
.mockReturnValue(Promise.resolve(compression))
|
||||
|
||||
await saveCache(inputPath, primaryKey)
|
||||
|
||||
const archiveFolder = '/foo/bar'
|
||||
|
||||
expect(createTarMock).toHaveBeenCalledTimes(1)
|
||||
expect(createTarMock).toHaveBeenCalledWith(
|
||||
archiveFolder,
|
||||
cachePaths,
|
||||
compression
|
||||
)
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(1)
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
'Cache size of ~6144 MB (6442450944 B) is over the 5GB limit, not saving cache.'
|
||||
)
|
||||
expect(failedMock).toHaveBeenCalledTimes(0)
|
||||
expect(getCompressionMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('save with reserve cache failure outputs warning', async () => {
|
||||
const infoMock = jest.spyOn(core, 'info')
|
||||
const logWarningMock = jest.spyOn(cacheUtils, 'logWarning')
|
||||
const failedMock = jest.spyOn(core, 'setFailed')
|
||||
|
||||
const inputPath = 'node_modules'
|
||||
const primaryKey = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
|
||||
|
||||
const reserveCacheMock = jest
|
||||
.spyOn(cacheHttpClient, 'reserveCache')
|
||||
.mockImplementation(async () => {
|
||||
return -1
|
||||
})
|
||||
|
||||
const createTarMock = jest.spyOn(tar, 'createTar')
|
||||
const saveCacheMock = jest.spyOn(cacheHttpClient, 'saveCache')
|
||||
const compression = CompressionMethod.Zstd
|
||||
const getCompressionMock = jest
|
||||
.spyOn(cacheUtils, 'getCompressionMethod')
|
||||
.mockReturnValue(Promise.resolve(compression))
|
||||
|
||||
await saveCache(inputPath, primaryKey)
|
||||
|
||||
expect(reserveCacheMock).toHaveBeenCalledTimes(1)
|
||||
expect(reserveCacheMock).toHaveBeenCalledWith(primaryKey, inputPath, {
|
||||
compressionMethod: compression
|
||||
})
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
`Unable to reserve cache with key ${primaryKey}, another job may be creating this cache.`
|
||||
)
|
||||
|
||||
expect(createTarMock).toHaveBeenCalledTimes(0)
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(0)
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(0)
|
||||
expect(failedMock).toHaveBeenCalledTimes(0)
|
||||
expect(getCompressionMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('save with server error outputs warning', async () => {
|
||||
const logWarningMock = jest.spyOn(cacheUtils, 'logWarning')
|
||||
const failedMock = jest.spyOn(core, 'setFailed')
|
||||
|
||||
const inputPath = 'node_modules'
|
||||
const primaryKey = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
|
||||
const cachePaths = [path.resolve(inputPath)]
|
||||
|
||||
const cacheId = 4
|
||||
const reserveCacheMock = jest
|
||||
.spyOn(cacheHttpClient, 'reserveCache')
|
||||
.mockImplementation(async () => {
|
||||
return cacheId
|
||||
})
|
||||
|
||||
const createTarMock = jest.spyOn(tar, 'createTar')
|
||||
|
||||
const saveCacheMock = jest
|
||||
.spyOn(cacheHttpClient, 'saveCache')
|
||||
.mockImplementationOnce(async () => {
|
||||
throw new Error('HTTP Error Occurred')
|
||||
})
|
||||
const compression = CompressionMethod.Zstd
|
||||
const getCompressionMock = jest
|
||||
.spyOn(cacheUtils, 'getCompressionMethod')
|
||||
.mockReturnValue(Promise.resolve(compression))
|
||||
|
||||
await saveCache(inputPath, primaryKey)
|
||||
|
||||
expect(reserveCacheMock).toHaveBeenCalledTimes(1)
|
||||
expect(reserveCacheMock).toHaveBeenCalledWith(primaryKey, inputPath, {
|
||||
compressionMethod: compression
|
||||
})
|
||||
|
||||
const archiveFolder = '/foo/bar'
|
||||
const archiveFile = path.join(archiveFolder, CacheFilename.Zstd)
|
||||
|
||||
expect(createTarMock).toHaveBeenCalledTimes(1)
|
||||
expect(createTarMock).toHaveBeenCalledWith(
|
||||
archiveFolder,
|
||||
cachePaths,
|
||||
compression
|
||||
)
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(1)
|
||||
expect(saveCacheMock).toHaveBeenCalledWith(cacheId, archiveFile)
|
||||
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(1)
|
||||
expect(logWarningMock).toHaveBeenCalledWith('HTTP Error Occurred')
|
||||
|
||||
expect(failedMock).toHaveBeenCalledTimes(0)
|
||||
expect(getCompressionMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('save with valid inputs uploads a cache', async () => {
|
||||
const failedMock = jest.spyOn(core, 'setFailed')
|
||||
|
||||
const inputPath = 'node_modules'
|
||||
const primaryKey = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
|
||||
const cachePaths = [path.resolve(inputPath)]
|
||||
|
||||
const cacheId = 4
|
||||
const reserveCacheMock = jest
|
||||
.spyOn(cacheHttpClient, 'reserveCache')
|
||||
.mockImplementation(async () => {
|
||||
return cacheId
|
||||
})
|
||||
const createTarMock = jest.spyOn(tar, 'createTar')
|
||||
|
||||
const saveCacheMock = jest.spyOn(cacheHttpClient, 'saveCache')
|
||||
const compression = CompressionMethod.Zstd
|
||||
const getCompressionMock = jest
|
||||
.spyOn(cacheUtils, 'getCompressionMethod')
|
||||
.mockReturnValue(Promise.resolve(compression))
|
||||
|
||||
await saveCache(inputPath, primaryKey)
|
||||
|
||||
expect(reserveCacheMock).toHaveBeenCalledTimes(1)
|
||||
expect(reserveCacheMock).toHaveBeenCalledWith(primaryKey, inputPath, {
|
||||
compressionMethod: compression
|
||||
})
|
||||
|
||||
const archiveFolder = '/foo/bar'
|
||||
const archiveFile = path.join(archiveFolder, CacheFilename.Zstd)
|
||||
|
||||
expect(createTarMock).toHaveBeenCalledTimes(1)
|
||||
expect(createTarMock).toHaveBeenCalledWith(
|
||||
archiveFolder,
|
||||
cachePaths,
|
||||
compression
|
||||
)
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(1)
|
||||
expect(saveCacheMock).toHaveBeenCalledWith(cacheId, archiveFile)
|
||||
|
||||
expect(failedMock).toHaveBeenCalledTimes(0)
|
||||
expect(getCompressionMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
191
packages/cache/__tests__/tar.test.ts
vendored
Normal file
191
packages/cache/__tests__/tar.test.ts
vendored
Normal file
@@ -0,0 +1,191 @@
|
||||
import * as exec from '@actions/exec'
|
||||
import * as io from '@actions/io'
|
||||
import * as path from 'path'
|
||||
import {CacheFilename, CompressionMethod} from '../src/internal/constants'
|
||||
import * as tar from '../src/internal/tar'
|
||||
import * as utils from '../src/internal/cacheUtils'
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import fs = require('fs')
|
||||
|
||||
jest.mock('@actions/exec')
|
||||
jest.mock('@actions/io')
|
||||
|
||||
const IS_WINDOWS = process.platform === 'win32'
|
||||
|
||||
function getTempDir(): string {
|
||||
return path.join(__dirname, '_temp', 'tar')
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.spyOn(io, 'which').mockImplementation(async tool => {
|
||||
return tool
|
||||
})
|
||||
|
||||
process.env['GITHUB_WORKSPACE'] = process.cwd()
|
||||
await jest.requireActual('@actions/io').rmRF(getTempDir())
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
delete process.env['GITHUB_WORKSPACE']
|
||||
await jest.requireActual('@actions/io').rmRF(getTempDir())
|
||||
})
|
||||
|
||||
test('zstd extract tar', async () => {
|
||||
const mkdirMock = jest.spyOn(io, 'mkdirP')
|
||||
const execMock = jest.spyOn(exec, 'exec')
|
||||
|
||||
const archivePath = IS_WINDOWS
|
||||
? `${process.env['windir']}\\fakepath\\cache.tar`
|
||||
: 'cache.tar'
|
||||
const workspace = process.env['GITHUB_WORKSPACE']
|
||||
|
||||
await tar.extractTar(archivePath, CompressionMethod.Zstd)
|
||||
|
||||
expect(mkdirMock).toHaveBeenCalledWith(workspace)
|
||||
const tarPath = IS_WINDOWS
|
||||
? `${process.env['windir']}\\System32\\tar.exe`
|
||||
: 'tar'
|
||||
expect(execMock).toHaveBeenCalledTimes(1)
|
||||
expect(execMock).toHaveBeenCalledWith(
|
||||
`"${tarPath}"`,
|
||||
[
|
||||
'--use-compress-program',
|
||||
'zstd -d --long=30',
|
||||
'-xf',
|
||||
IS_WINDOWS ? archivePath.replace(/\\/g, '/') : archivePath,
|
||||
'-P',
|
||||
'-C',
|
||||
IS_WINDOWS ? workspace?.replace(/\\/g, '/') : workspace
|
||||
],
|
||||
{cwd: undefined}
|
||||
)
|
||||
})
|
||||
|
||||
test('gzip extract tar', async () => {
|
||||
const mkdirMock = jest.spyOn(io, 'mkdirP')
|
||||
const execMock = jest.spyOn(exec, 'exec')
|
||||
const archivePath = IS_WINDOWS
|
||||
? `${process.env['windir']}\\fakepath\\cache.tar`
|
||||
: 'cache.tar'
|
||||
const workspace = process.env['GITHUB_WORKSPACE']
|
||||
|
||||
await tar.extractTar(archivePath, CompressionMethod.Gzip)
|
||||
|
||||
expect(mkdirMock).toHaveBeenCalledWith(workspace)
|
||||
const tarPath = IS_WINDOWS
|
||||
? `${process.env['windir']}\\System32\\tar.exe`
|
||||
: 'tar'
|
||||
expect(execMock).toHaveBeenCalledTimes(1)
|
||||
expect(execMock).toHaveBeenCalledWith(
|
||||
`"${tarPath}"`,
|
||||
[
|
||||
'-z',
|
||||
'-xf',
|
||||
IS_WINDOWS ? archivePath.replace(/\\/g, '/') : archivePath,
|
||||
'-P',
|
||||
'-C',
|
||||
IS_WINDOWS ? workspace?.replace(/\\/g, '/') : workspace
|
||||
],
|
||||
{cwd: undefined}
|
||||
)
|
||||
})
|
||||
|
||||
test('gzip extract GNU tar on windows', async () => {
|
||||
if (IS_WINDOWS) {
|
||||
jest.spyOn(fs, 'existsSync').mockReturnValueOnce(false)
|
||||
|
||||
const isGnuMock = jest
|
||||
.spyOn(utils, 'useGnuTar')
|
||||
.mockReturnValue(Promise.resolve(true))
|
||||
const execMock = jest.spyOn(exec, 'exec')
|
||||
const archivePath = `${process.env['windir']}\\fakepath\\cache.tar`
|
||||
const workspace = process.env['GITHUB_WORKSPACE']
|
||||
|
||||
await tar.extractTar(archivePath, CompressionMethod.Gzip)
|
||||
|
||||
expect(isGnuMock).toHaveBeenCalledTimes(1)
|
||||
expect(execMock).toHaveBeenCalledTimes(1)
|
||||
expect(execMock).toHaveBeenCalledWith(
|
||||
`"tar"`,
|
||||
[
|
||||
'-z',
|
||||
'-xf',
|
||||
archivePath.replace(/\\/g, '/'),
|
||||
'-P',
|
||||
'-C',
|
||||
workspace?.replace(/\\/g, '/'),
|
||||
'--force-local'
|
||||
],
|
||||
{cwd: undefined}
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
test('zstd create tar', async () => {
|
||||
const execMock = jest.spyOn(exec, 'exec')
|
||||
|
||||
const archiveFolder = getTempDir()
|
||||
const workspace = process.env['GITHUB_WORKSPACE']
|
||||
const sourceDirectories = ['~/.npm/cache', `${workspace}/dist`]
|
||||
|
||||
await fs.promises.mkdir(archiveFolder, {recursive: true})
|
||||
|
||||
await tar.createTar(archiveFolder, sourceDirectories, CompressionMethod.Zstd)
|
||||
|
||||
const tarPath = IS_WINDOWS
|
||||
? `${process.env['windir']}\\System32\\tar.exe`
|
||||
: 'tar'
|
||||
|
||||
expect(execMock).toHaveBeenCalledTimes(1)
|
||||
expect(execMock).toHaveBeenCalledWith(
|
||||
`"${tarPath}"`,
|
||||
[
|
||||
'--use-compress-program',
|
||||
'zstd -T0 --long=30',
|
||||
'-cf',
|
||||
IS_WINDOWS ? CacheFilename.Zstd.replace(/\\/g, '/') : CacheFilename.Zstd,
|
||||
'-P',
|
||||
'-C',
|
||||
IS_WINDOWS ? workspace?.replace(/\\/g, '/') : workspace,
|
||||
'--files-from',
|
||||
'manifest.txt'
|
||||
],
|
||||
{
|
||||
cwd: archiveFolder
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('gzip create tar', async () => {
|
||||
const execMock = jest.spyOn(exec, 'exec')
|
||||
|
||||
const archiveFolder = getTempDir()
|
||||
const workspace = process.env['GITHUB_WORKSPACE']
|
||||
const sourceDirectories = ['~/.npm/cache', `${workspace}/dist`]
|
||||
|
||||
await fs.promises.mkdir(archiveFolder, {recursive: true})
|
||||
|
||||
await tar.createTar(archiveFolder, sourceDirectories, CompressionMethod.Gzip)
|
||||
|
||||
const tarPath = IS_WINDOWS
|
||||
? `${process.env['windir']}\\System32\\tar.exe`
|
||||
: 'tar'
|
||||
|
||||
expect(execMock).toHaveBeenCalledTimes(1)
|
||||
expect(execMock).toHaveBeenCalledWith(
|
||||
`"${tarPath}"`,
|
||||
[
|
||||
'-z',
|
||||
'-cf',
|
||||
IS_WINDOWS ? CacheFilename.Gzip.replace(/\\/g, '/') : CacheFilename.Gzip,
|
||||
'-P',
|
||||
'-C',
|
||||
IS_WINDOWS ? workspace?.replace(/\\/g, '/') : workspace,
|
||||
'--files-from',
|
||||
'manifest.txt'
|
||||
],
|
||||
{
|
||||
cwd: archiveFolder
|
||||
}
|
||||
)
|
||||
})
|
||||
Reference in New Issue
Block a user