Merge pull request #4146 from github/henrymercer/per-language-bundles-pr

Use per-language CodeQL bundles
This commit is contained in:
Michael B. Gale
2026-09-18 12:30:36 +00:00
committed by GitHub
26 changed files with 2233 additions and 318 deletions

View File

@@ -1,6 +1,7 @@
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import { performance } from "perf_hooks";
import * as github from "@actions/github";
import * as toolcache from "@actions/tool-cache";
@@ -12,8 +13,10 @@ import * as api from "./api-client";
import * as diagnostics from "./diagnostics";
import { ActionsEnvVars, Env, EnvVar, ReadOnlyEnv } from "./environment";
import { Feature } from "./feature-flags";
import { BuiltInLanguage } from "./languages";
import { getRunnerLogger } from "./logging";
import { getCacheRestoreKeyPrefix } from "./overlay/caching";
import { MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION } from "./per-language-bundles";
import * as setupCodeql from "./setup-codeql";
import * as tar from "./tar";
import {
@@ -22,6 +25,7 @@ import {
SAMPLE_DEFAULT_CLI_VERSION,
SAMPLE_DOTCOM_API_DETAILS,
checkExpectedLogMessages,
checkUnexpectedLogMessages,
createFeatures,
createTestConfig,
getRecordingLogger,
@@ -56,6 +60,36 @@ function stubDownloadAndExtract() {
});
}
/** Models a hosted Linux runner with zstd and the latest nightly release. */
function stubHostedNightly(tagName: string) {
sinon.stub(process, "platform").value("linux");
sinon.stub(process, "arch").value("x64");
process.env[ActionsEnvVars.RUNNER_ENVIRONMENT] = "github-hosted";
sinon.stub(tar, "isZstdAvailable").resolves({
available: true,
foundZstdBinary: true,
});
const fetchRelease = sinon
.stub<Parameters<typeof fetch>, ReturnType<typeof fetch>>()
.rejects(new Error("Unexpected API request in nightly bundle test"));
fetchRelease
.withArgs(
"https://api.github.com/repos/dsp-testing/codeql-cli-nightlies/releases?per_page=1&page=1&prerelease=true",
sinon.match({ method: "GET" }),
)
.callsFake(
async () =>
new Response(JSON.stringify([{ tag_name: tagName }]), {
headers: { "content-type": "application/json" },
}),
);
const client = github.getOctokit("123", {
request: { fetch: fetchRelease },
});
sinon.stub(api, "getApiClient").value(() => client);
return fetchRelease;
}
test.serial("parse codeql bundle url version", (t) => {
t.deepEqual(
setupCodeql.getCodeQLURLVersion(
@@ -299,6 +333,10 @@ test.serial(
downloadDurationMs: 200,
totalDurationMs: 300,
});
checkUnexpectedLogMessages(t, loggedMessages, [
"Not caching the CodeQL tools",
"Could not cache CodeQL tools",
]);
// Ensure message logging CodeQL CLI version was present in user logs.
const expected_message: string = `Using CodeQL CLI version ${LINKED_CLI_VERSION.cliVersion}`;
@@ -375,20 +413,7 @@ test.serial(
const expectedDate = "30260213";
const expectedTag = `codeql-bundle-${expectedDate}`;
// Ensure that we consistently select "zstd" for the test.
sinon.stub(process, "platform").value("linux");
sinon.stub(tar, "isZstdAvailable").resolves({
available: true,
foundZstdBinary: true,
});
const client = github.getOctokit("123");
const listReleases = sinon.stub(client.rest.repos, "listReleases");
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
listReleases.resolves({
data: [{ tag_name: expectedTag }],
} as any);
sinon.stub(api, "getApiClient").value(() => client);
stubHostedNightly(expectedTag);
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
@@ -457,20 +482,7 @@ test.serial(
const expectedDate = "30260213";
const expectedTag = `codeql-bundle-${expectedDate}`;
// Ensure that we consistently select "zstd" for the test.
sinon.stub(process, "platform").value("linux");
sinon.stub(tar, "isZstdAvailable").resolves({
available: true,
foundZstdBinary: true,
});
const client = github.getOctokit("123");
const listReleases = sinon.stub(client.rest.repos, "listReleases");
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
listReleases.resolves({
data: [{ tag_name: expectedTag }],
} as any);
sinon.stub(api, "getApiClient").value(() => client);
stubHostedNightly(expectedTag);
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir, { GITHUB_EVENT_NAME: "dynamic" });
@@ -514,6 +526,8 @@ for (const bundlePath of [
"codeql-bundle.tar.gz",
"codeql-bundle.tar.zst",
"codeql-bundle-/codeql-bundle.tar.gz",
"codeql-bundle-linux64.tar.zst",
"codeql-bundle-ruby-linux64.tar.zst",
]) {
test.serial(
`setupCodeQLBundle reports an unknown version for ${bundlePath}`,
@@ -543,12 +557,21 @@ for (const bundlePath of [
t.is(downloadSpy.firstCall.args[0].toolsVersion, "unknown");
t.is(result.toolsVersion, "unknown");
t.is(result.toolsSource, setupCodeql.ToolsSource.Download);
t.is(
result.toolsDownloadStatusReport?.perLanguage?.tools_bundle_language,
bundlePath === "codeql-bundle-ruby-linux64.tar.zst"
? BuiltInLanguage.ruby
: undefined,
);
t.is(path.dirname(result.codeqlFolder), tmpDir);
t.true(fs.existsSync(result.codeqlFolder));
t.false(fs.existsSync(`${result.codeqlFolder}.complete`));
t.deepEqual(toolcache.findAllVersions("CodeQL"), []);
checkExpectedLogMessages(t, messages, [
`Using CodeQL CLI sourced from ${url}`,
bundlePath === "codeql-bundle-ruby-linux64.tar.zst"
? "Not caching the CodeQL tools because they came from a bundle that contains only a single language."
: `Could not cache CodeQL tools because we could not determine the bundle version from the URL ${url}.`,
]);
});
},
@@ -595,6 +618,234 @@ test.serial(
},
);
for (const toolsInput of ["nightly", "nightly-latest"]) {
test.serial(
`getCodeQLSource selects the latest per-language nightly for tools == ${toolsInput}`,
async (t) => {
const expectedTag = "codeql-bundle-30260213";
const baseURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}`;
const latestNightlyRequest = stubHostedNightly(expectedTag);
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
const source = await setupCodeql.getCodeQLSource(
toolsInput,
SAMPLE_DEFAULT_CLI_VERSION,
["java"],
false, // useOverlayAwareDefaultCliVersion
SAMPLE_DOTCOM_API_DETAILS,
GitHubVariant.DOTCOM,
true, // tarSupportsZstd
createFeatures([Feature.PerLanguageBundles]),
getRunnerLogger(true),
);
t.deepEqual(source, {
sourceType: "download",
bundle: {
kind: "per-language",
language: BuiltInLanguage.java,
url: `${baseURL}/codeql-bundle-java-linux64.tar.zst`,
combinedBundleURL: `${baseURL}/codeql-bundle-linux64.tar.zst`,
},
bundleVersion: "30260213",
cliVersion: undefined,
compressionMethod: "zstd",
toolsVersion: "0.0.0-30260213",
} satisfies setupCodeql.CodeQLDownloadSource);
t.true(latestNightlyRequest.calledOnce);
});
},
);
}
test.serial(
"getCodeQLSource downloads a combined nightly bundle when per-language selection is ineligible",
async (t) => {
const expectedTag = "codeql-bundle-30260213";
stubHostedNightly(expectedTag);
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
for (const { languages, features } of [
// The per-language feature is disabled.
{ languages: ["java"], features: createFeatures([]) },
// More than one language requires a combined bundle.
{
languages: ["java", "python"],
features: createFeatures([Feature.PerLanguageBundles]),
},
]) {
const source = await setupCodeql.getCodeQLSource(
"nightly",
SAMPLE_DEFAULT_CLI_VERSION,
languages,
false, // useOverlayAwareDefaultCliVersion
SAMPLE_DOTCOM_API_DETAILS,
GitHubVariant.DOTCOM,
true, // tarSupportsZstd
features,
getRunnerLogger(true),
);
t.is(source.sourceType, "download");
if (source.sourceType === "download") {
t.deepEqual(source.bundle, {
kind: "combined",
url: `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}/codeql-bundle-linux64.tar.zst`,
});
}
}
});
},
);
for (const perLanguageBundles of [false, true]) {
test.serial(
`getCodeQLSource uses the latest ${perLanguageBundles ? "per-language" : "combined"} bundle for a forced nightly`,
async (t) => {
const expectedTag = "codeql-bundle-30260213";
const baseURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}`;
const latestNightlyRequest = stubHostedNightly(expectedTag);
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir, { GITHUB_EVENT_NAME: "dynamic" });
const source = await setupCodeql.getCodeQLSource(
undefined, // toolsInput: the nightly is selected by ForceNightly
SAMPLE_DEFAULT_CLI_VERSION,
["java"],
false, // useOverlayAwareDefaultCliVersion
SAMPLE_DOTCOM_API_DETAILS,
GitHubVariant.DOTCOM,
true, // tarSupportsZstd
createFeatures(
perLanguageBundles
? [Feature.ForceNightly, Feature.PerLanguageBundles]
: [Feature.ForceNightly],
),
getRunnerLogger(true),
);
t.is(source.sourceType, "download");
t.true(latestNightlyRequest.calledOnce);
if (source.sourceType === "download") {
const combinedURL = `${baseURL}/codeql-bundle-linux64.tar.zst`;
t.deepEqual(
source.bundle,
perLanguageBundles
? {
kind: "per-language",
language: BuiltInLanguage.java,
url: `${baseURL}/codeql-bundle-java-linux64.tar.zst`,
combinedBundleURL: combinedURL,
}
: { kind: "combined", url: combinedURL },
);
}
});
},
);
}
for (const date of ["20200101", "30260213"]) {
for (const bundle of ["combined", "per-language"] as const) {
test.serial(
`getCodeQLSource preserves an explicit ${bundle} nightly URL for ${date}`,
async (t) => {
const latestNightlyRequest = stubHostedNightly(
"codeql-bundle-30260213",
);
const asset =
bundle === "combined"
? "codeql-bundle-linux64.tar.zst"
: "codeql-bundle-java-linux64.tar.zst";
const url = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/codeql-bundle-${date}/${asset}`;
const features = createFeatures([Feature.PerLanguageBundles]);
const logger = getRecordingLogger([], { logToConsole: false });
const error = new HTTPError("Not Found", 404);
const extractStub = sinon
.stub(toolsDownload, "downloadAndExtract")
.rejects(error);
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
const source = await setupCodeql.getCodeQLSource(
url,
SAMPLE_DEFAULT_CLI_VERSION,
["java"],
false,
SAMPLE_DOTCOM_API_DETAILS,
GitHubVariant.DOTCOM,
true,
features,
logger,
);
t.deepEqual(source, {
sourceType: "download",
bundle:
bundle === "combined"
? { kind: "combined", url }
: { kind: "per-language", url, language: BuiltInLanguage.java },
bundleVersion: date,
cliVersion: undefined,
compressionMethod: "zstd",
toolsVersion: `0.0.0-${date}`,
} satisfies setupCodeql.CodeQLDownloadSource);
await t.throwsAsync(
setupCodeql.setupCodeQLBundle(
url,
SAMPLE_DOTCOM_API_DETAILS,
tmpDir,
GitHubVariant.DOTCOM,
SAMPLE_DEFAULT_CLI_VERSION,
["java"],
false,
features,
logger,
),
{ is: error },
);
t.true(extractStub.calledOnce);
t.is(extractStub.firstCall.args[0], url);
t.true(latestNightlyRequest.notCalled);
});
},
);
}
}
test.serial(
"getCodeQLSource reports a missing release tag when a toolcache entry disappears",
async (t) => {
sinon
.stub(toolcache, "findAllVersions")
.returns([MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION]);
sinon.stub(toolcache, "find").returns("");
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir, { GITHUB_EVENT_NAME: "dynamic" });
await t.throwsAsync(
setupCodeql.getCodeQLSource(
"toolcache",
SAMPLE_DEFAULT_CLI_VERSION,
["java"],
false,
SAMPLE_DOTCOM_API_DETAILS,
GitHubVariant.DOTCOM,
true,
createFeatures([]),
getRunnerLogger(true),
),
{
message:
"Could not determine a release tag for the requested CodeQL bundle.",
},
);
});
},
);
test.serial(
"getCodeQLSource correctly returns latest version from toolcache when tools == toolcache",
async (t) => {
@@ -879,6 +1130,475 @@ test.serial(
},
);
const PER_LANGUAGE_CLI_VERSION = {
enabledVersions: [
{
cliVersion: MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION,
tagName: `codeql-bundle-v${MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION}`,
},
],
};
test.serial(
"getCodeQLBundleName returns a per-language bundle name only when a language is specified",
(t) => {
sinon.stub(process, "platform").value("linux");
sinon.stub(process, "arch").value("x64");
t.is(
setupCodeql.getCodeQLBundleName("zstd", BuiltInLanguage.java),
"codeql-bundle-java-linux64.tar.zst",
);
t.is(
setupCodeql.getCodeQLBundleName("zstd"),
"codeql-bundle-linux64.tar.zst",
);
},
);
test.serial("getCodeQLBundleName names the Swift bundle for macOS", (t) => {
sinon.stub(process, "platform").value("darwin");
t.is(
setupCodeql.getCodeQLBundleName("zstd", BuiltInLanguage.swift),
"codeql-bundle-swift-osx64.tar.zst",
);
});
test.serial(
"getCodeQLSource downloads the per-language bundle for a single explicit language",
async (t) => {
sinon.stub(process, "platform").value("linux");
sinon.stub(process, "arch").value("x64");
process.env[ActionsEnvVars.RUNNER_ENVIRONMENT] = "github-hosted";
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
const source = await setupCodeql.getCodeQLSource(
undefined,
PER_LANGUAGE_CLI_VERSION,
["java-kotlin"],
false, // useOverlayAwareDefaultCliVersion
SAMPLE_DOTCOM_API_DETAILS,
GitHubVariant.DOTCOM,
true, // tarSupportsZstd
createFeatures([Feature.PerLanguageBundles]),
getRunnerLogger(true),
);
t.is(source.sourceType, "download");
if (source.sourceType === "download") {
t.true(
source.bundle.url.endsWith("/codeql-bundle-java-linux64.tar.zst"),
`Unexpected URL ${source.bundle.url}`,
);
t.is(source.bundle.kind, "per-language");
if (source.bundle.kind === "per-language") {
t.is(source.bundle.language, BuiltInLanguage.java);
t.true(
source.bundle.combinedBundleURL?.endsWith(
"/codeql-bundle-linux64.tar.zst",
),
);
}
}
});
},
);
test.serial(
"getCodeQLSource downloads the combined bundle when the feature is disabled",
async (t) => {
sinon.stub(process, "platform").value("linux");
sinon.stub(process, "arch").value("x64");
process.env[ActionsEnvVars.RUNNER_ENVIRONMENT] = "github-hosted";
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
const source = await setupCodeql.getCodeQLSource(
undefined,
PER_LANGUAGE_CLI_VERSION,
["java"],
false, // useOverlayAwareDefaultCliVersion
SAMPLE_DOTCOM_API_DETAILS,
GitHubVariant.DOTCOM,
true, // tarSupportsZstd
createFeatures([]),
getRunnerLogger(true),
);
t.is(source.sourceType, "download");
if (source.sourceType === "download") {
t.true(source.bundle.url.endsWith("/codeql-bundle-linux64.tar.zst"));
t.is(source.bundle.kind, "combined");
}
});
},
);
for (const fallback of [false, true]) {
test.serial(
`setupCodeQLBundle retains the selected release identity for an opaque asset URL${fallback ? " with fallback" : ""}`,
async (t) => {
sinon.stub(process, "platform").value("linux");
sinon.stub(process, "arch").value("x64");
sinon.stub(actionsUtil, "isRunningLocalAction").returns(false);
process.env[ActionsEnvVars.RUNNER_ENVIRONMENT] = "github-hosted";
sinon.stub(tar, "isZstdAvailable").resolves({
available: true,
foundZstdBinary: true,
});
const tag = PER_LANGUAGE_CLI_VERSION.enabledVersions[0].tagName;
const assetURL =
"https://api.github.com/repos/codeql-testing/action-fork/releases/assets/123";
const combinedURL = `${assetURL}4`;
const fetchRelease = sinon
.stub<Parameters<typeof fetch>, ReturnType<typeof fetch>>()
.callsFake(
async () =>
new Response(
JSON.stringify({
assets: [
{ name: "codeql-bundle-java-linux64.tar.zst", url: assetURL },
{
name: "codeql-bundle-linux64.tar.zst",
url: combinedURL,
},
],
}),
{ headers: { "content-type": "application/json" } },
),
);
const client = github.getOctokit("123", {
request: { fetch: fetchRelease },
});
sinon.stub(api, "getApiClient").value(() => client);
const authorizationSpy = sinon.spy(api, "getAuthorizationHeaderFor");
const extractStub = stubDownloadAndExtract();
if (fallback) {
extractStub.onFirstCall().rejects(new HTTPError("Not Found", 404));
}
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir, {
GITHUB_ACTION_REPOSITORY: "codeql-testing/action-fork",
});
const result = await setupCodeql.setupCodeQLBundle(
undefined,
SAMPLE_DOTCOM_API_DETAILS,
tmpDir,
GitHubVariant.DOTCOM,
PER_LANGUAGE_CLI_VERSION,
["java"],
false, // useOverlayAwareDefaultCliVersion
createFeatures([Feature.PerLanguageBundles]),
getRunnerLogger(true),
);
t.true(fetchRelease.calledTwice);
t.is(
fetchRelease.firstCall.args[0],
`https://api.github.com/repos/codeql-testing/action-fork/releases/tags/${tag}`,
);
t.is(extractStub.callCount, fallback ? 2 : 1);
t.is(extractStub.firstCall.args[0], assetURL);
t.is(extractStub.lastCall.args[0], fallback ? combinedURL : assetURL);
t.is(authorizationSpy.callCount, extractStub.callCount);
t.is(authorizationSpy.firstCall.args[2], assetURL);
t.is(
authorizationSpy.lastCall.args[2],
fallback ? combinedURL : assetURL,
);
t.is(extractStub.lastCall.args[3], "token token");
t.is(result.toolsVersion, MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION);
t.is(
result.toolsDownloadStatusReport?.perLanguage?.tools_bundle_language,
fallback ? undefined : BuiltInLanguage.java,
);
t.is(
result.toolsDownloadStatusReport?.perLanguage
?.tools_per_language_bundle_fallback,
fallback ? true : undefined,
);
if (fallback) {
t.is(
result.codeqlFolder,
toolcache.find("CodeQL", MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION),
);
t.true(fs.existsSync(`${result.codeqlFolder}.complete`));
} else {
t.is(path.dirname(result.codeqlFolder), tmpDir);
t.deepEqual(toolcache.findAllVersions("CodeQL"), []);
t.false(fs.existsSync(`${result.codeqlFolder}.complete`));
}
});
},
);
}
for (const bundle of ["per-language", "combined", "fallback"] as const) {
test.serial(
`setupCodeQLBundle preserves the nightly version for a ${bundle} download`,
async (t) => {
const expectedDate = "30260213";
const expectedTag = `codeql-bundle-${expectedDate}`;
const expectedVersion = `0.0.0-${expectedDate}`;
const baseURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}`;
const combinedURL = `${baseURL}/codeql-bundle-linux64.tar.zst`;
const perLanguageURL = `${baseURL}/codeql-bundle-javascript-linux64.tar.zst`;
const loggedMessages: LoggedMessage[] = [];
const logger = getRecordingLogger(loggedMessages);
stubHostedNightly(expectedTag);
delete process.env[EnvVar.HAS_SET_UP_CODEQL];
const downloadSpy = sinon.spy(setupCodeql, "downloadCodeQL");
let elapsedMs = 1000;
sinon.stub(performance, "now").callsFake(() => elapsedMs);
const extractStub = sinon
.stub(toolsDownload, "downloadAndExtract")
.callsFake(async (_url, _compressionMethod, dest) => {
if (bundle === "fallback" && extractStub.callCount === 1) {
elapsedMs += 700.2;
throw new HTTPError("Not Found", 404);
}
elapsedMs += 300.2;
fs.mkdirSync(dest, { recursive: true });
return {
downloadDurationMs: 200,
extractionDurationMs: 100,
totalDurationMs: 300,
};
});
const addDiagnostic = sinon.stub(diagnostics, "addNoLanguageDiagnostic");
const features = createFeatures([
Feature.PerLanguageBundles,
Feature.CleanupToolcacheBundles,
]);
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
const result = await setupCodeql.setupCodeQLBundle(
"nightly",
SAMPLE_DOTCOM_API_DETAILS,
tmpDir,
GitHubVariant.DOTCOM,
SAMPLE_DEFAULT_CLI_VERSION,
bundle === "combined" ? ["javascript", "python"] : ["javascript"],
false, // useOverlayAwareDefaultCliVersion
features,
logger,
);
const source = downloadSpy.firstCall.args[0];
t.is(result.toolsVersion, expectedVersion);
t.is(result.toolsVersion, source.toolsVersion);
t.is(
source.bundle.kind,
bundle === "combined" ? "combined" : "per-language",
);
t.is(result.codeqlFolder, extractStub.lastCall.args[2]);
t.is(
result.toolsDownloadStatusReport?.totalDurationMs,
bundle === "fallback" ? 1000 : 300,
);
t.is(result.toolsDownloadStatusReport?.downloadDurationMs, 200);
t.is(result.toolsDownloadStatusReport?.extractionDurationMs, 100);
t.is(
(await downloadSpy.lastCall.returnValue).statusReport.perLanguage
?.tools_bundle_language,
bundle === "per-language" ? BuiltInLanguage.javascript : undefined,
);
t.is(extractStub.callCount, bundle === "fallback" ? 2 : 1);
t.is(downloadSpy.callCount, extractStub.callCount);
t.is(
extractStub.firstCall.args[0],
bundle === "combined" ? combinedURL : perLanguageURL,
);
t.is(
extractStub.lastCall.args[0],
bundle === "per-language" ? perLanguageURL : combinedURL,
);
t.is(
result.toolsDownloadStatusReport?.perLanguage?.tools_bundle_language,
bundle === "per-language" ? BuiltInLanguage.javascript : undefined,
);
t.is(
result.toolsDownloadStatusReport?.perLanguage
?.tools_per_language_bundle_fallback,
bundle === "fallback" ? true : undefined,
);
t.is(
addDiagnostic
.getCalls()
.filter(
(call) =>
call.args[1].source?.id ===
"codeql-action/toolcache-bundle-cleanup",
).length,
1,
);
if (bundle === "fallback") {
t.deepEqual(downloadSpy.secondCall.args[0], {
...source,
bundle: { kind: "combined", url: combinedURL },
});
checkExpectedLogMessages(t, loggedMessages, [
`No per-language CodeQL bundle for 'javascript' was found at ${perLanguageURL}`,
]);
}
if (bundle === "per-language") {
t.is(path.dirname(result.codeqlFolder), tmpDir);
t.deepEqual(toolcache.findAllVersions("CodeQL"), []);
t.false(fs.existsSync(`${result.codeqlFolder}.complete`));
} else {
t.is(
result.codeqlFolder,
toolsDownload.getToolcacheDirectory(expectedVersion),
);
t.true(fs.existsSync(`${result.codeqlFolder}.complete`));
const cachedResult = await setupCodeql.setupCodeQLBundle(
"nightly",
SAMPLE_DOTCOM_API_DETAILS,
tmpDir,
GitHubVariant.DOTCOM,
SAMPLE_DEFAULT_CLI_VERSION,
["javascript"],
false, // useOverlayAwareDefaultCliVersion
features,
logger,
);
t.is(cachedResult.toolsSource, setupCodeql.ToolsSource.Toolcache);
t.is(cachedResult.toolsVersion, expectedVersion);
t.is(cachedResult.codeqlFolder, result.codeqlFolder);
t.is(extractStub.callCount, bundle === "fallback" ? 2 : 1);
}
});
},
);
}
for (const asset of [
"codeql-bundle-ruby-linux64.tar.zst",
"codeql-bundle-%72uby-linux64.tar.zst",
]) {
test.serial(
`setupCodeQLBundle keeps explicitly requested ${asset} out of the toolcache`,
async (t) => {
const extractStub = stubDownloadAndExtract();
const messages: LoggedMessage[] = [];
const url = `https://github.com/github/codeql-action/releases/download/codeql-bundle-v9.9.9/${asset}`;
process.env[ActionsEnvVars.RUNNER_ENVIRONMENT] = "self-hosted";
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
const result = await setupCodeql.setupCodeQLBundle(
url,
SAMPLE_DOTCOM_API_DETAILS,
tmpDir,
GitHubVariant.DOTCOM,
SAMPLE_DEFAULT_CLI_VERSION,
undefined, // rawLanguages
false, // useOverlayAwareDefaultCliVersion
createFeatures([]),
getRecordingLogger(messages),
);
t.true(extractStub.calledOnce);
t.is(extractStub.firstCall.args[0], url);
t.is(result.toolsVersion, "9.9.9");
t.is(
result.toolsDownloadStatusReport?.perLanguage?.tools_bundle_language,
BuiltInLanguage.ruby,
);
t.is(path.dirname(result.codeqlFolder), tmpDir);
t.deepEqual(toolcache.findAllVersions("CodeQL"), []);
t.false(fs.existsSync(`${result.codeqlFolder}.complete`));
checkExpectedLogMessages(t, messages, [
"Not caching the CodeQL tools because they came from a bundle that contains only a single language.",
]);
checkUnexpectedLogMessages(t, messages, [
"Could not cache CodeQL tools because we could not determine the bundle version",
]);
});
},
);
}
for (const error of [
new HTTPError("Internal Server Error", 500),
new Error("Connection reset"),
]) {
test.serial(
`setupCodeQLBundle does not fall back after ${error.message}`,
async (t) => {
sinon.stub(process, "platform").value("linux");
sinon.stub(process, "arch").value("x64");
process.env[ActionsEnvVars.RUNNER_ENVIRONMENT] = "github-hosted";
sinon.stub(tar, "isZstdAvailable").resolves({
available: true,
foundZstdBinary: true,
});
const extractStub = sinon
.stub(toolsDownload, "downloadAndExtract")
.rejects(error);
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
await t.throwsAsync(
setupCodeql.setupCodeQLBundle(
undefined,
SAMPLE_DOTCOM_API_DETAILS,
tmpDir,
GitHubVariant.DOTCOM,
PER_LANGUAGE_CLI_VERSION,
["java"],
false, // useOverlayAwareDefaultCliVersion
createFeatures([Feature.PerLanguageBundles]),
getRunnerLogger(true),
),
{ is: error },
);
t.true(extractStub.calledOnce);
t.true(
extractStub.firstCall.args[0].endsWith(
"/codeql-bundle-java-linux64.tar.zst",
),
);
});
},
);
}
test.serial(
"setupCodeQLBundle does not substitute a bundle for an explicitly requested one that is missing",
async (t) => {
const error = new HTTPError("Not Found", 404);
const extractStub = sinon
.stub(toolsDownload, "downloadAndExtract")
.rejects(error);
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
await t.throwsAsync(
setupCodeql.setupCodeQLBundle(
"https://github.com/github/codeql-action/releases/download/codeql-bundle-v9.9.9/codeql-bundle-ruby-linux64.tar.zst",
SAMPLE_DOTCOM_API_DETAILS,
tmpDir,
GitHubVariant.DOTCOM,
SAMPLE_DEFAULT_CLI_VERSION,
undefined, // rawLanguages
false, // useOverlayAwareDefaultCliVersion
createFeatures([]),
getRunnerLogger(true),
),
{ is: error },
);
t.true(extractStub.calledOnce);
});
},
);
test.serial(
"getEnabledVersionsWithOverlayBaseDatabases returns flag-enabled versions present in cache, sorted desc",
async (t) => {