Initial commit to create @actions/cache package

This commit is contained in:
Aiqiao Yan
2020-05-06 11:10:18 -04:00
parent 0471ed4ad7
commit 932779cf58
19 changed files with 1763 additions and 0 deletions

View File

@@ -0,0 +1 @@
hello world

View 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'
)
})

View 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)
})

View 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)
})

View 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
View 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
}
)
})