diff --git a/__tests__/action_failed.test.ts b/__tests__/action_failed.test.ts index 9fdfa12..38fe188 100644 --- a/__tests__/action_failed.test.ts +++ b/__tests__/action_failed.test.ts @@ -30,8 +30,9 @@ var coreError = jest.spyOn(core, 'error'); var toolsCacheDir = jest.spyOn(toolcache, 'cacheDir'); test('testing get-cmake action failure', async () => { - process.env.RUNNER_TEMP = path.join(os.tmpdir(), `${process.pid}`); - process.env.RUNNER_TOOL_CACHE = path.join(os.tmpdir(), `${process.pid}-cache`); + const testId = Math.random(); + process.env.RUNNER_TEMP = path.join(os.tmpdir(), `${testId}`); + process.env.RUNNER_TOOL_CACHE = path.join(os.tmpdir(), `${testId}-cache`); await getcmake.main(); expect(coreSetFailed).toBeCalledTimes(1); expect(coreError).toBeCalledTimes(0); diff --git a/__tests__/action_succeeded.localcloud.test.ts b/__tests__/action_succeeded.localcloud.test.ts index 96446e0..e175f9b 100644 --- a/__tests__/action_succeeded.localcloud.test.ts +++ b/__tests__/action_succeeded.localcloud.test.ts @@ -43,14 +43,18 @@ var toolsCacheDir = jest.spyOn(toolcache, 'cacheDir'); var toolsFind = jest.spyOn(toolcache, 'find'); test('testing get-cmake action success with cloud/local cache enabled', async () => { + const testId = Math.random(); + process.env.RUNNER_TEMP = path.join(os.tmpdir(), `${testId}`); + process.env.RUNNER_TOOL_CACHE = path.join(os.tmpdir(), `${testId}-cache`); + for (var matrix of [ { version: "latest", cloudCache: "true", localCache: "true" }, { version: "latest", cloudCache: "true", localCache: "false" }, { version: "latest", cloudCache: "false", localCache: "true" }, { version: "latest", cloudCache: "false", localCache: "false" }]) { - process.env.RUNNER_TEMP = path.join(os.tmpdir(), `${process.pid}`); - process.env.RUNNER_TOOL_CACHE = path.join(os.tmpdir(), `${process.pid}-cache`); + console.log(`\n\ntesting for: ${JSON.stringify(matrix)}:\n`) + process.env["CUSTOM_CMAKE_VERSION"] = matrix.version; process.env[localCacheInput] = matrix.localCache; process.env[cloudCacheInput] = matrix.cloudCache; @@ -58,7 +62,8 @@ test('testing get-cmake action success with cloud/local cache enabled', async () expect(coreSetFailed).toBeCalledTimes(0); expect(coreError).toBeCalledTimes(0); expect(toolsCacheDir).toBeCalledTimes(matrix.localCache === "true" ? 1 : 0); - expect(toolsFind).toBeCalledTimes(matrix.localCache === "true" ? 1 : 0); + const toolsFindInvocationCount = matrix.localCache === "true" ? 1 : 0; + expect(toolsFind).toBeCalledTimes(toolsFindInvocationCount); expect(saveCache).toBeCalledTimes(matrix.cloudCache === "true" ? 1 : 0); expect(restoreCache).toBeCalledTimes(matrix.cloudCache === "true" ? 1 : 0); @@ -70,6 +75,10 @@ test('testing get-cmake action success with cloud/local cache enabled', async () }); test('testing get-cmake action success with local or cloud cache hits', async () => { + const testId = Math.random(); + process.env.RUNNER_TEMP = path.join(os.tmpdir(), `${testId}`); + process.env.RUNNER_TOOL_CACHE = path.join(os.tmpdir(), `${testId}-cache`); + for (var matrix of [ { version: "latest", cloudCache: true, localCache: true, localHit: false, cloudHit: true }, { version: "latest", cloudCache: false, localCache: true, localHit: false, cloudHit: false }, @@ -87,8 +96,6 @@ test('testing get-cmake action success with local or cloud cache hits', async () }); console.log(`\n\ntesting for: ${JSON.stringify(matrix)}:\n`) - process.env.RUNNER_TEMP = path.join(os.tmpdir(), `${process.pid}`); - process.env.RUNNER_TOOL_CACHE = path.join(os.tmpdir(), `${process.pid}-cache`); process.env["CUSTOM_CMAKE_VERSION"] = matrix.version; process.env[localCacheInput] = String(matrix.localCache); process.env[cloudCacheInput] = String(matrix.cloudCache); @@ -97,9 +104,44 @@ test('testing get-cmake action success with local or cloud cache hits', async () await main(); expect(coreSetFailed).toBeCalledTimes(0); expect(coreError).toBeCalledTimes(0); - expect(toolsFind).toBeCalledTimes(matrix.localCache ? 1 : 0); - expect(toolsCacheDir).toBeCalledTimes(!matrix.localCache || matrix.localHit ? 0 : 1); + const toolsFindInvocationCount = matrix.localCache ? 1 : 0; + expect(toolsFind).toBeCalledTimes(toolsFindInvocationCount); + const toolsCacheDirInvocationCount: number = !matrix.localCache || matrix.localHit ? 0 : 1; + expect(toolsCacheDir).toBeCalledTimes(toolsCacheDirInvocationCount); + expect(toolsFind).toHaveNthReturnedWith(1, matrix.localHit ? "hit" : ""); expect(saveCache).toBeCalledTimes((matrix.cloudHit || !matrix.cloudCache || matrix.localHit) ? 0 : 1); expect(restoreCache).toBeCalledTimes((matrix.localHit || !matrix.cloudCache) ? 0 : 1); } }); + +test('testing get-cmake action store and restore local cache', async () => { + toolsCacheDir.mockRestore(); + toolsFind.mockRestore(); + + const testId = Math.random(); + process.env.RUNNER_TEMP = path.join(os.tmpdir(), `${testId}`); + process.env.RUNNER_TOOL_CACHE = path.join(os.tmpdir(), `${testId}-cache`); + let downloadMock = undefined; + + for (var matrix of [ + { version: "latest", cloudCache: false, localCache: true, localHit: false, cloudHit: false }, + { version: "latest", cloudCache: false, localCache: true, localHit: true, cloudHit: false }, + ]) { + console.log(`\n\ntesting for: ${JSON.stringify(matrix)}:\n`) + process.env["CUSTOM_CMAKE_VERSION"] = matrix.version; + process.env[localCacheInput] = String(matrix.localCache); + process.env[cloudCacheInput] = String(matrix.cloudCache); + await main(); + expect(coreSetFailed).toBeCalledTimes(0); + expect(coreError).toBeCalledTimes(0); + expect(saveCache).toBeCalledTimes(0); + expect(restoreCache).toBeCalledTimes(0); + // After cache has been stored once (in the first iteration), it must be fetched from the cache and not downloaded anymore. + if (downloadMock) { + expect(downloadMock).toBeCalledTimes(0); + } + + // Second iteration, check that the download function is not called because the local cache hits. + downloadMock = jest.spyOn(ToolsGetter.prototype as any, 'downloadTools'); + } +}); diff --git a/__tests__/cachehit.test.ts b/__tests__/cachehit.test.ts index a08c868..d3fba18 100644 --- a/__tests__/cachehit.test.ts +++ b/__tests__/cachehit.test.ts @@ -7,6 +7,7 @@ import * as process from 'process'; import * as os from 'os'; import { ToolsGetter } from '../src/get-cmake'; import * as cache from '@actions/cache'; +import path = require('path'); // 10 minutes jest.setTimeout(10 * 60 * 1000) @@ -19,8 +20,12 @@ const cacheRestoreCache = jest.spyOn(cache, 'restoreCache').mockImplementation(( Promise.resolve("key") ); +const addToolsToPath = jest.spyOn(ToolsGetter.prototype as any, 'addToolsToPath').mockResolvedValue(0); + test('testing get-cmake with cache-hit...', async () => { - process.env.RUNNER_TEMP = os.tmpdir(); + const testId = Math.random(); + process.env.RUNNER_TEMP = path.join(os.tmpdir(), `${testId}`); + const getter: ToolsGetter = new ToolsGetter(); await getter.run(); expect(cacheSaveCache).toBeCalledTimes(0); diff --git a/__tests__/cachemiss.test.ts b/__tests__/cachemiss.test.ts index a6d8226..3ae64a1 100644 --- a/__tests__/cachemiss.test.ts +++ b/__tests__/cachemiss.test.ts @@ -8,6 +8,7 @@ import * as os from 'os'; import { ToolsGetter } from '../src/get-cmake'; import * as cache from '@actions/cache'; import * as core from '@actions/core'; +import path = require('path'); // 10 minutes jest.setTimeout(10 * 60 * 1000) @@ -23,7 +24,8 @@ const cacheRestoreCache = jest.spyOn(cache, 'restoreCache').mockImplementation(( ); test('testing get-cmake with cache-miss...', async () => { - process.env.RUNNER_TEMP = os.tmpdir(); + const testId = Math.random(); + process.env.RUNNER_TEMP = path.join(os.tmpdir(), `${testId}`); const getter: ToolsGetter = new ToolsGetter(); await getter.run(); expect(cacheSaveCache).toBeCalledTimes(1); diff --git a/__tests__/cacherestorefailure.test.ts b/__tests__/cacherestorefailure.test.ts index f0ea20d..bb4719e 100644 --- a/__tests__/cacherestorefailure.test.ts +++ b/__tests__/cacherestorefailure.test.ts @@ -7,6 +7,7 @@ import * as process from 'process'; import * as os from 'os'; import { ToolsGetter } from '../src/get-cmake'; import * as cache from '@actions/cache'; +import path = require('path'); // 10 minutes jest.setTimeout(10 * 60 * 1000) @@ -20,7 +21,9 @@ jest.spyOn(cache, 'restoreCache').mockImplementation(() => { }); test('testing get-cmake with restoreCache failure', async () => { - process.env.RUNNER_TEMP = os.tmpdir(); + const testId = Math.random(); + process.env.RUNNER_TEMP = path.join(os.tmpdir(), `${testId}`); + const getter: ToolsGetter = new ToolsGetter(); await expect(getter.run()).rejects.toThrowError(); }); diff --git a/dist/index.js b/dist/index.js index 6132a0a..cedea70 100644 --- a/dist/index.js +++ b/dist/index.js @@ -45,7 +45,7 @@ function hashCode(text) { hash = ((hash << 5) + hash) ^ char; } } - return hash.toString(); + return Math.abs(hash); } class ToolsGetter { constructor(cmakeOverride, ninjaOverride, useCloudCache = true, useLocalCache = false) { @@ -119,7 +119,7 @@ class ToolsGetter { } get(cmakePackage, ninjaPackage) { return __awaiter(this, void 0, void 0, function* () { - let key, outPath; + let hashedKey, outPath; let cloudCacheHitKey = undefined; let localCacheHit = false; let localPath = undefined; @@ -127,10 +127,10 @@ class ToolsGetter { core.startGroup(`Computing cache key from the downloads' URLs`); // Get an unique output directory name from the URL. const inputHash = `${cmakePackage.url}${ninjaPackage.url}`; - key = hashCode(inputHash); - core.info(`Cache key: ${key}`); - core.debug(`hash('${inputHash}') === '${key}'`); - outPath = this.getOutputPath(key); + hashedKey = hashCode(inputHash); + core.info(`Cache key: '${hashedKey}'.`); + core.debug(`hash('${inputHash}') === '${hashedKey}'`); + outPath = this.getOutputPath(hashedKey.toString()); core.info(`Local install root: '${outPath}''.`); } finally { @@ -138,8 +138,8 @@ class ToolsGetter { } if (this.useLocalCache) { try { - core.startGroup(`Restoring from local GitHub runner cache using key '${key}' into '${outPath}'`); - localPath = tools.find(ToolsGetter.LocalCacheName, key, process.platform); + core.startGroup(`Restoring from local GitHub runner cache using key '${hashedKey}'`); + localPath = tools.find(ToolsGetter.LocalCacheName, ToolsGetter.hashToFakeSemver(hashedKey), process.platform); // Silly tool-cache API does return an empty string in case of cache miss. localCacheHit = localPath ? true : false; core.info(localCacheHit ? "Local cache hit." : "Local cache miss."); @@ -151,8 +151,8 @@ class ToolsGetter { if (!localCacheHit) { if (this.useCloudCache) { try { - core.startGroup(`Restoring from GitHub cloud cache using key '${key}' into '${outPath}'`); - cloudCacheHitKey = yield this.restoreCache(outPath, key); + core.startGroup(`Restoring from GitHub cloud cache using key '${hashedKey}' into '${outPath}'`); + cloudCacheHitKey = yield this.restoreCache(outPath, hashedKey); core.info(cloudCacheHitKey === undefined ? "Cloud cache miss." : "Cloud cache hit."); } finally { @@ -160,27 +160,23 @@ class ToolsGetter { } } if (cloudCacheHitKey === undefined) { - yield core.group("Downloading and extracting CMake", () => __awaiter(this, void 0, void 0, function* () { - const downloaded = yield tools.downloadTool(cmakePackage.url); - yield extractFunction[cmakePackage.dropSuffix](downloaded, outPath); - })); - yield core.group("Downloading and extracting Ninja", () => __awaiter(this, void 0, void 0, function* () { - const downloaded = yield tools.downloadTool(ninjaPackage.url); - yield extractFunction[ninjaPackage.dropSuffix](downloaded, outPath); - })); + yield this.downloadTools(cmakePackage, ninjaPackage, outPath); } - yield this.addToolsToPath(outPath, cmakePackage, ninjaPackage); localPath = outPath; } + if (!localPath) { + throw new Error(`Unexpectedly the directory of the tools is not defined`); + } + yield this.addToolsToPath(localPath, cmakePackage, ninjaPackage); if (this.useCloudCache && cloudCacheHitKey === undefined) { try { - core.startGroup(`Saving to GitHub cloud cache using key '${key}'`); + core.startGroup(`Saving to GitHub cloud cache using key '${hashedKey}'`); if (localCacheHit) { core.info("Skipping saving to cloud cache since there was local cache hit for the computed key."); } else if (cloudCacheHitKey === undefined) { - yield this.saveCache([outPath], key); - core.info(`Saved '${outPath}' to the GitHub cache service with key '${key}'.`); + yield this.saveCache([outPath], hashedKey); + core.info(`Saved '${outPath}' to the GitHub cache service with key '${hashedKey}'.`); } else { core.info("Skipping saving to cloud cache since there was a cache hit for the computed key."); @@ -192,9 +188,9 @@ class ToolsGetter { } if (this.useLocalCache && !localCacheHit && localPath) { try { - core.startGroup(`Saving to local cache using key '${key}' from '${outPath}'`); - yield tools.cacheDir(localPath, ToolsGetter.LocalCacheName, key, process.platform); - core.info(`Saved '${outPath}' to the local GitHub runner cache with key '${key}'.`); + core.startGroup(`Saving to local cache using key '${hashedKey}' from '${outPath}'`); + yield tools.cacheDir(localPath, ToolsGetter.LocalCacheName, ToolsGetter.hashToFakeSemver(hashedKey), process.platform); + core.info(`Saved '${outPath}' to the local GitHub runner cache with key '${hashedKey}'.`); } finally { core.endGroup(); @@ -243,7 +239,7 @@ class ToolsGetter { saveCache(paths, key) { return __awaiter(this, void 0, void 0, function* () { try { - return yield cache.saveCache(paths, key); + return yield cache.saveCache(paths, key.toString()); } catch (error) { if (error.name === cache.ValidationError.name) { @@ -259,7 +255,23 @@ class ToolsGetter { }); } restoreCache(outPath, key) { - return cache.restoreCache([outPath], key); + return cache.restoreCache([outPath], key.toString()); + } + downloadTools(cmakePackage, ninjaPackage, outputPath) { + return __awaiter(this, void 0, void 0, function* () { + let outPath; + yield core.group("Downloading and extracting CMake", () => __awaiter(this, void 0, void 0, function* () { + const downloaded = yield tools.downloadTool(cmakePackage.url); + yield extractFunction[cmakePackage.dropSuffix](downloaded, outputPath); + })); + yield core.group("Downloading and extracting Ninja", () => __awaiter(this, void 0, void 0, function* () { + const downloaded = yield tools.downloadTool(ninjaPackage.url); + yield extractFunction[ninjaPackage.dropSuffix](downloaded, outputPath); + })); + }); + } + static hashToFakeSemver(hashedKey) { + return `${hashedKey}.0.0`; } } exports.ToolsGetter = ToolsGetter; diff --git a/src/get-cmake.ts b/src/get-cmake.ts index 026bbfd..fae9dc1 100644 --- a/src/get-cmake.ts +++ b/src/get-cmake.ts @@ -21,7 +21,7 @@ const extractFunction: { [key: string]: { (url: string, outputPath: string): Pro * @param {string} text * @returns {string} */ -function hashCode(text: string): string { +function hashCode(text: string): number { let hash = 41; if (text.length != 0) { for (let i = 0; i < text.length; i++) { @@ -30,7 +30,7 @@ function hashCode(text: string): string { } } - return hash.toString(); + return Math.abs(hash); } export class ToolsGetter { @@ -111,7 +111,7 @@ export class ToolsGetter { } private async get(cmakePackage: shared.PackageInfo, ninjaPackage: shared.PackageInfo): Promise { - let key: string, outPath: string; + let hashedKey: number, outPath: string; let cloudCacheHitKey: string | undefined = undefined; let localCacheHit = false; let localPath: string | undefined = undefined; @@ -120,10 +120,10 @@ export class ToolsGetter { core.startGroup(`Computing cache key from the downloads' URLs`); // Get an unique output directory name from the URL. const inputHash = `${cmakePackage.url}${ninjaPackage.url}`; - key = hashCode(inputHash); - core.info(`Cache key: ${key}`); - core.debug(`hash('${inputHash}') === '${key}'`); - outPath = this.getOutputPath(key); + hashedKey = hashCode(inputHash); + core.info(`Cache key: '${hashedKey}'.`); + core.debug(`hash('${inputHash}') === '${hashedKey}'`); + outPath = this.getOutputPath(hashedKey.toString()); core.info(`Local install root: '${outPath}''.`) } finally { core.endGroup(); @@ -131,8 +131,9 @@ export class ToolsGetter { if (this.useLocalCache) { try { - core.startGroup(`Restoring from local GitHub runner cache using key '${key}' into '${outPath}'`); - localPath = tools.find(ToolsGetter.LocalCacheName, key, process.platform); + core.startGroup(`Restoring from local GitHub runner cache using key '${hashedKey}'`); + localPath = tools.find(ToolsGetter.LocalCacheName, + ToolsGetter.hashToFakeSemver(hashedKey), process.platform); // Silly tool-cache API does return an empty string in case of cache miss. localCacheHit = localPath ? true : false; core.info(localCacheHit ? "Local cache hit." : "Local cache miss."); @@ -144,8 +145,8 @@ export class ToolsGetter { if (!localCacheHit) { if (this.useCloudCache) { try { - core.startGroup(`Restoring from GitHub cloud cache using key '${key}' into '${outPath}'`); - cloudCacheHitKey = await this.restoreCache(outPath, key); + core.startGroup(`Restoring from GitHub cloud cache using key '${hashedKey}' into '${outPath}'`); + cloudCacheHitKey = await this.restoreCache(outPath, hashedKey); core.info(cloudCacheHitKey === undefined ? "Cloud cache miss." : "Cloud cache hit."); } finally { core.endGroup(); @@ -153,31 +154,27 @@ export class ToolsGetter { } if (cloudCacheHitKey === undefined) { - await core.group("Downloading and extracting CMake", async () => { - const downloaded = await tools.downloadTool(cmakePackage.url); - await extractFunction[cmakePackage.dropSuffix](downloaded, outPath); - }); - - await core.group("Downloading and extracting Ninja", async () => { - const downloaded = await tools.downloadTool(ninjaPackage.url); - await extractFunction[ninjaPackage.dropSuffix](downloaded, outPath); - }); + await this.downloadTools(cmakePackage, ninjaPackage, outPath); } - await this.addToolsToPath(outPath, cmakePackage, ninjaPackage); - localPath = outPath; } + if (!localPath) { + throw new Error(`Unexpectedly the directory of the tools is not defined`); + } + + await this.addToolsToPath(localPath, cmakePackage, ninjaPackage); + if (this.useCloudCache && cloudCacheHitKey === undefined) { try { - core.startGroup(`Saving to GitHub cloud cache using key '${key}'`); + core.startGroup(`Saving to GitHub cloud cache using key '${hashedKey}'`); if (localCacheHit) { core.info("Skipping saving to cloud cache since there was local cache hit for the computed key."); } else if (cloudCacheHitKey === undefined) { - await this.saveCache([outPath], key); - core.info(`Saved '${outPath}' to the GitHub cache service with key '${key}'.`); + await this.saveCache([outPath], hashedKey); + core.info(`Saved '${outPath}' to the GitHub cache service with key '${hashedKey}'.`); } else { core.info("Skipping saving to cloud cache since there was a cache hit for the computed key."); } @@ -188,9 +185,10 @@ export class ToolsGetter { if (this.useLocalCache && !localCacheHit && localPath) { try { - core.startGroup(`Saving to local cache using key '${key}' from '${outPath}'`); - await tools.cacheDir(localPath, ToolsGetter.LocalCacheName, key, process.platform); - core.info(`Saved '${outPath}' to the local GitHub runner cache with key '${key}'.`); + core.startGroup(`Saving to local cache using key '${hashedKey}' from '${outPath}'`); + await tools.cacheDir(localPath, ToolsGetter.LocalCacheName, + ToolsGetter.hashToFakeSemver(hashedKey), process.platform); + core.info(`Saved '${outPath}' to the local GitHub runner cache with key '${hashedKey}'.`); } finally { core.endGroup(); } @@ -235,9 +233,9 @@ export class ToolsGetter { return path.join(process.env.RUNNER_TEMP, subDir);; } - private async saveCache(paths: string[], key: string): Promise { + private async saveCache(paths: string[], key: number): Promise { try { - return await cache.saveCache(paths, key); + return await cache.saveCache(paths, key.toString()); } catch (error: any) { if (error.name === cache.ValidationError.name) { @@ -250,8 +248,27 @@ export class ToolsGetter { } } - private restoreCache(outPath: string, key: string): Promise { - return cache.restoreCache([outPath], key); + private restoreCache(outPath: string, key: number): Promise { + return cache.restoreCache([outPath], key.toString()); + } + + private async downloadTools( + cmakePackage: shared.PackageInfo, ninjaPackage: shared.PackageInfo, + outputPath: string): Promise { + let outPath: string; + await core.group("Downloading and extracting CMake", async () => { + const downloaded = await tools.downloadTool(cmakePackage.url); + await extractFunction[cmakePackage.dropSuffix](downloaded, outputPath); + }); + + await core.group("Downloading and extracting Ninja", async () => { + const downloaded = await tools.downloadTool(ninjaPackage.url); + await extractFunction[ninjaPackage.dropSuffix](downloaded, outputPath); + }); + } + + private static hashToFakeSemver(hashedKey: number): string { + return `${hashedKey}.0.0`; } }