Files
codeql-action/src/trap-caching.test.ts
2026-10-16 14:00:40 +01:00

224 lines
6.8 KiB
TypeScript

import * as fs from "fs";
import * as path from "path";
import * as cache from "@actions/cache";
import test from "ava";
import * as sinon from "sinon";
import * as actionsUtil from "./actions-util";
import {
getTrapCachingExtractorConfigArgs,
getTrapCachingExtractorConfigArgsForLang,
createStubCodeQL,
} from "./codeql";
import * as configUtils from "./config-utils";
import * as gitUtils from "./git-utils";
import { BuiltInLanguage } from "./languages";
import {
callee,
createTestConfig,
getRecordingLogger,
makeVersionInfo,
setupTests,
} from "./testing-utils";
import {
downloadTrapCaches,
getLanguagesSupportingCaching,
uploadTrapCaches,
warnIfTrapCacheCleanupEnvVarIsSet,
} from "./trap-caching";
import * as util from "./util";
setupTests(test);
const stubCodeql = createStubCodeQL({
async getVersion() {
return makeVersionInfo("2.10.3");
},
async resolveLanguages() {
return {
extractors: {
[BuiltInLanguage.javascript]: [
{
extractor_root: "some_root",
extractor_options: {
trap: {
properties: {
cache: {
properties: {
dir: {
title: "Cache directory",
},
bound: {
title: "Cache bound",
},
write: {
title: "Cache write",
},
},
},
},
},
},
},
],
[BuiltInLanguage.cpp]: [
{
extractor_root: "other_root",
},
],
},
};
},
});
const testConfigWithoutTmpDir = createTestConfig({
languages: [BuiltInLanguage.javascript, BuiltInLanguage.cpp],
trapCaches: {
javascript: "/some/cache/dir",
},
});
function getTestConfigWithTempDir(tempDir: string): configUtils.Config {
return createTestConfig({
languages: [BuiltInLanguage.javascript, BuiltInLanguage.ruby],
tempDir,
dbLocation: path.resolve(tempDir, "codeql_databases"),
trapCaches: {
javascript: path.resolve(tempDir, "jsCache"),
ruby: path.resolve(tempDir, "rubyCache"),
},
});
}
test.serial("check flags for JS, analyzing default branch", async (t) => {
await util.withTmpDir(async (tmpDir) => {
const config = getTestConfigWithTempDir(tmpDir);
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(true);
const result = await getTrapCachingExtractorConfigArgsForLang(
config,
BuiltInLanguage.javascript,
);
t.deepEqual(result, [
`-O=javascript.trap.cache.dir=${path.resolve(tmpDir, "jsCache")}`,
"-O=javascript.trap.cache.bound=1024",
"-O=javascript.trap.cache.write=true",
]);
});
});
test.serial("check flags for all, not analyzing default branch", async (t) => {
await util.withTmpDir(async (tmpDir) => {
const config = getTestConfigWithTempDir(tmpDir);
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(false);
const result = await getTrapCachingExtractorConfigArgs(config);
t.deepEqual(result, [
`-O=javascript.trap.cache.dir=${path.resolve(tmpDir, "jsCache")}`,
"-O=javascript.trap.cache.bound=1024",
"-O=javascript.trap.cache.write=false",
`-O=ruby.trap.cache.dir=${path.resolve(tmpDir, "rubyCache")}`,
"-O=ruby.trap.cache.bound=1024",
"-O=ruby.trap.cache.write=false",
]);
});
});
test("get languages that support TRAP caching", async (t) => {
const loggedMessages = [];
const logger = getRecordingLogger(loggedMessages);
const languagesSupportingCaching = await getLanguagesSupportingCaching(
stubCodeql,
[BuiltInLanguage.javascript, BuiltInLanguage.cpp],
logger,
);
t.deepEqual(languagesSupportingCaching, [BuiltInLanguage.javascript]);
});
test.serial("upload cache key contains right fields", async (t) => {
const loggedMessages = [];
const logger = getRecordingLogger(loggedMessages);
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(true);
sinon.stub(util, "tryGetFolderBytes").resolves(999_999_999);
const stubSave = sinon.stub(cache, "saveCache");
process.env.GITHUB_SHA = "somesha";
await uploadTrapCaches(stubCodeql, testConfigWithoutTmpDir, logger);
t.assert(
stubSave.calledOnceWith(
sinon.match.array.contains(["/some/cache/dir"]),
sinon
.match("somesha")
.and(sinon.match("2.10.3"))
.and(sinon.match("javascript")),
),
);
});
test.serial(
"download cache looks for the right key and creates dir",
async (t) => {
await util.withTmpDir(async (tmpDir) => {
const loggedMessages = [];
const logger = getRecordingLogger(loggedMessages);
sinon.stub(actionsUtil, "getTemporaryDirectory").returns(tmpDir);
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(false);
const stubRestore = sinon.stub(cache, "restoreCache").resolves("found");
const eventFile = path.resolve(tmpDir, "event.json");
process.env.GITHUB_EVENT_NAME = "pull_request";
process.env.GITHUB_EVENT_PATH = eventFile;
fs.writeFileSync(
eventFile,
JSON.stringify({
pull_request: {
base: {
sha: "somesha",
},
},
}),
);
await downloadTrapCaches(
stubCodeql,
[BuiltInLanguage.javascript, BuiltInLanguage.cpp],
logger,
undefined,
);
t.assert(
stubRestore.calledOnceWith(
sinon.match.array.contains([
path.resolve(tmpDir, "trapCaches", "javascript"),
]),
sinon
.match("somesha")
.and(sinon.match("2.10.3"))
.and(sinon.match("javascript")),
),
);
t.assert(fs.existsSync(path.resolve(tmpDir, "trapCaches", "javascript")));
});
},
);
// `CODEQL_ACTION_CLEANUP_TRAP_CACHES` has no effect with either value, so we warn even when it's
// set to `false`.
for (const value of ["true", "false"]) {
test(`warnIfTrapCacheCleanupEnvVarIsSet warns when CODEQL_ACTION_CLEANUP_TRAP_CACHES is '${value}'`, async (t) => {
const target = callee(warnIfTrapCacheCleanupEnvVarIsSet)
.withEnv((env) => env.set("CODEQL_ACTION_CLEANUP_TRAP_CACHES", value))
.withArgs()
.logs(
t,
"CODEQL_ACTION_CLEANUP_TRAP_CACHES environment variable no longer has any effect",
);
await target.passes(t.is, undefined);
t.deepEqual(
target.getLogger().messages.map((m) => m.type),
["warning"],
);
});
}
test("warnIfTrapCacheCleanupEnvVarIsSet doesn't warn when CODEQL_ACTION_CLEANUP_TRAP_CACHES is unset", async (t) => {
const target = callee(warnIfTrapCacheCleanupEnvVarIsSet).withArgs();
await target.passes(t.is, undefined);
t.deepEqual(target.getLogger().messages, []);
});