Prefer providing CodeQL via dependency injection

This commit is contained in:
Henry Mercer
2025-08-07 12:13:59 +01:00
parent c7884c6fd8
commit f8c2086872
42 changed files with 138 additions and 126 deletions

View File

@@ -71,8 +71,10 @@ test("analyze action with RAM & threads from environment variables", async (t) =
// wait for the action promise to complete before starting verification.
await analyzeAction.runPromise;
t.assert(runFinalizeStub.calledOnce);
t.deepEqual(runFinalizeStub.firstCall.args[1], "--threads=-1");
t.deepEqual(runFinalizeStub.firstCall.args[2], "--ram=4992");
t.assert(runQueriesStub.calledOnce);
t.deepEqual(runQueriesStub.firstCall.args[3], "--threads=-1");
t.deepEqual(runQueriesStub.firstCall.args[1], "--ram=4992");
});

View File

@@ -71,8 +71,10 @@ test("analyze action with RAM & threads from action inputs", async (t) => {
// wait for the action promise to complete before starting verification.
await analyzeAction.runPromise;
t.assert(runFinalizeStub.calledOnce);
t.deepEqual(runFinalizeStub.firstCall.args[1], "--threads=-1");
t.deepEqual(runFinalizeStub.firstCall.args[2], "--ram=3012");
t.assert(runQueriesStub.calledOnce);
t.deepEqual(runQueriesStub.firstCall.args[3], "--threads=-1");
t.deepEqual(runQueriesStub.firstCall.args[1], "--ram=3012");
});

View File

@@ -308,6 +308,7 @@ async function run() {
threads,
diffRangePackDir,
actionsUtil.getOptionalInput("category"),
codeql,
config,
logger,
features,

View File

@@ -11,7 +11,7 @@ import {
defaultSuites,
resolveQuerySuiteAlias,
} from "./analyze";
import { setCodeQL } from "./codeql";
import { createStubCodeQL } from "./codeql";
import { Feature } from "./feature-flags";
import { KnownLanguage } from "./languages";
import { getRunnerLogger } from "./logging";
@@ -42,7 +42,7 @@ test("status report fields", async (t) => {
sinon.stub(uploadLib, "validateSarifFileSchema");
for (const language of Object.values(KnownLanguage)) {
setCodeQL({
const codeql = createStubCodeQL({
databaseRunQueries: async () => {},
packDownload: async () => ({ packs: [] }),
databaseInterpretResults: async (
@@ -108,6 +108,7 @@ test("status report fields", async (t) => {
threadsFlag,
undefined,
undefined,
codeql,
config,
getRunnerLogger(true),
createFeatures([Feature.QaTelemetryEnabled]),

View File

@@ -13,7 +13,7 @@ import {
} from "./actions-util";
import { getApiClient } from "./api-client";
import { setupCppAutobuild } from "./autobuild";
import { CodeQL, getCodeQL } from "./codeql";
import { type CodeQL } from "./codeql";
import * as configUtils from "./config-utils";
import { getJavaTempDependencyDir } from "./dependency-caching";
import { addDiagnostic, makeDiagnostic } from "./diagnostics";
@@ -614,6 +614,7 @@ export async function runQueries(
threadsFlag: string,
diffRangePackDir: string | undefined,
automationDetailsId: string | undefined,
codeql: CodeQL,
config: configUtils.Config,
logger: Logger,
features: FeatureEnablement,
@@ -655,8 +656,6 @@ export async function runQueries(
? `--sarif-run-property=incrementalMode=${incrementalMode.join(",")}`
: undefined;
const codeql = await getCodeQL(config.codeQLCmd);
for (const language of config.languages) {
try {
const sarifFile = path.join(sarifFolder, `${language}.sarif`);

View File

@@ -285,7 +285,6 @@ interface PackDownloadItem {
/**
* Stores the CodeQL object, and is populated by `setupCodeQL` or `getCodeQL`.
* Can be overridden in tests using `setCodeQL`.
*/
let cachedCodeQL: CodeQL | undefined = undefined;
@@ -424,6 +423,17 @@ export async function getCodeQL(cmd: string): Promise<CodeQL> {
return cachedCodeQL;
}
/**
* Overrides the CodeQL object. Only for use in tests that cannot override
* CodeQL via dependency injection.
*
* Accepts a partial object. Any undefined methods will be implemented
* to immediately throw an exception indicating which method is missing.
*/
export function setCodeQL(codeql: Partial<CodeQL>): void {
cachedCodeQL = createStubCodeQL(codeql);
}
function resolveFunction<T>(
partialCodeql: Partial<CodeQL>,
methodName: string,
@@ -442,13 +452,13 @@ function resolveFunction<T>(
}
/**
* Set the functionality for CodeQL methods. Only for use in tests.
* Creates a stub CodeQL object. Only for use in tests.
*
* Accepts a partial object and any undefined methods will be implemented
* Accepts a partial object. Any undefined methods will be implemented
* to immediately throw an exception indicating which method is missing.
*/
export function setCodeQL(partialCodeql: Partial<CodeQL>): CodeQL {
cachedCodeQL = {
export function createStubCodeQL(partialCodeql: Partial<CodeQL>): CodeQL {
return {
getPath: resolveFunction(partialCodeql, "getPath", () => "/tmp/dummy-path"),
getVersion: resolveFunction(partialCodeql, "getVersion", async () => ({
version: "1.0.0",
@@ -509,21 +519,6 @@ export function setCodeQL(partialCodeql: Partial<CodeQL>): CodeQL {
resolveExtractor: resolveFunction(partialCodeql, "resolveExtractor"),
mergeResults: resolveFunction(partialCodeql, "mergeResults"),
};
return cachedCodeQL;
}
/**
* Get the cached CodeQL object. Should only be used from tests.
*
* TODO: Work out a good way for tests to get this from the test context
* instead of having to have this method.
*/
export function getCachedCodeQL(): CodeQL {
if (cachedCodeQL === undefined) {
// Should never happen as setCodeQL is called by testing-utils.setupTests
throw new Error("cachedCodeQL undefined");
}
return cachedCodeQL;
}
/**

View File

@@ -9,12 +9,7 @@ import * as sinon from "sinon";
import * as actionsUtil from "./actions-util";
import * as api from "./api-client";
import { CachingKind } from "./caching-utils";
import {
CodeQL,
getCachedCodeQL,
PackDownloadOutput,
setCodeQL,
} from "./codeql";
import { PackDownloadOutput, createStubCodeQL } from "./codeql";
import * as configUtils from "./config-utils";
import { Feature } from "./feature-flags";
import * as gitUtils from "./git-utils";
@@ -64,7 +59,15 @@ function createTestInitConfigInputs(
debugDatabaseName: "",
repository: { owner: "github", repo: "example" },
tempDir: "",
codeql: {} as CodeQL,
codeql: createStubCodeQL({
async betterResolveLanguages() {
return {
extractors: {
javascript: [{ extractor_root: "" }],
},
};
},
}),
workspacePath: "",
sourceRoot: "",
githubVersion,
@@ -127,7 +130,7 @@ test("load empty config", async (t) => {
const logger = getRunnerLogger(true);
const languages = "javascript,python";
const codeql = setCodeQL({
const codeql = createStubCodeQL({
async betterResolveLanguages() {
return {
extractors: {
@@ -179,7 +182,7 @@ test("loading config saves config", async (t) => {
return await withTmpDir(async (tempDir) => {
const logger = getRunnerLogger(true);
const codeql = setCodeQL({
const codeql = createStubCodeQL({
async betterResolveLanguages() {
return {
extractors: {
@@ -240,7 +243,6 @@ test("load input outside of workspace", async (t) => {
createTestInitConfigInputs({
configFile: "../input",
tempDir,
codeql: getCachedCodeQL(),
workspacePath: tempDir,
}),
);
@@ -268,7 +270,6 @@ test("load non-local input with invalid repo syntax", async (t) => {
createTestInitConfigInputs({
configFile,
tempDir,
codeql: getCachedCodeQL(),
workspacePath: tempDir,
}),
);
@@ -298,7 +299,6 @@ test("load non-existent input", async (t) => {
languagesInput,
configFile,
tempDir,
codeql: getCachedCodeQL(),
workspacePath: tempDir,
}),
);
@@ -318,7 +318,7 @@ test("load non-existent input", async (t) => {
test("load non-empty input", async (t) => {
return await withTmpDir(async (tempDir) => {
const codeql = setCodeQL({
const codeql = createStubCodeQL({
async betterResolveLanguages() {
return {
extractors: {
@@ -449,7 +449,7 @@ test("Using config input and file together, config input should be used.", async
queries: string[];
extraSearchPath: string | undefined;
}> = [];
const codeql = setCodeQL({
const codeql = createStubCodeQL({
async betterResolveLanguages() {
return {
extractors: {
@@ -490,7 +490,7 @@ test("Using config input and file together, config input should be used.", async
test("API client used when reading remote config", async (t) => {
return await withTmpDir(async (tempDir) => {
const codeql = setCodeQL({
const codeql = createStubCodeQL({
async betterResolveLanguages() {
return {
extractors: {
@@ -561,7 +561,6 @@ test("Remote config handles the case where a directory is provided", async (t) =
createTestInitConfigInputs({
configFile: repoReference,
tempDir,
codeql: getCachedCodeQL(),
workspacePath: tempDir,
}),
);
@@ -590,7 +589,6 @@ test("Invalid format of remote config handled correctly", async (t) => {
createTestInitConfigInputs({
configFile: repoReference,
tempDir,
codeql: getCachedCodeQL(),
workspacePath: tempDir,
}),
);
@@ -609,7 +607,7 @@ test("Invalid format of remote config handled correctly", async (t) => {
test("No detected languages", async (t) => {
return await withTmpDir(async (tempDir) => {
mockListLanguages([]);
const codeql = setCodeQL({
const codeql = createStubCodeQL({
async resolveLanguages() {
return {};
},
@@ -645,7 +643,6 @@ test("Unknown languages", async (t) => {
createTestInitConfigInputs({
languagesInput,
tempDir,
codeql: getCachedCodeQL(),
workspacePath: tempDir,
}),
);
@@ -1134,7 +1131,7 @@ const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
const stubExtractorEntry = {
extractor_root: "",
};
const codeQL = setCodeQL({
const codeQL = createStubCodeQL({
betterResolveLanguages: () =>
Promise.resolve({
aliases: {

View File

@@ -7,7 +7,7 @@ import * as sinon from "sinon";
import * as actionsUtil from "./actions-util";
import { GitHubApiDetails } from "./api-client";
import * as apiClient from "./api-client";
import { setCodeQL } from "./codeql";
import { createStubCodeQL } from "./codeql";
import { Config } from "./config-utils";
import { uploadDatabases } from "./database-upload";
import * as gitUtils from "./git-utils";
@@ -70,7 +70,7 @@ async function mockHttpRequests(databaseUploadStatusCode: number) {
}
function getCodeQL() {
return setCodeQL({
return createStubCodeQL({
async databaseBundle(_: string, outputFilePath: string) {
fs.writeFileSync(outputFilePath, "");
},

View File

@@ -9,7 +9,7 @@ import del from "del";
import { getOptionalInput, getTemporaryDirectory } from "./actions-util";
import { dbIsFinalized } from "./analyze";
import { getCodeQL } from "./codeql";
import { type CodeQL } from "./codeql";
import { Config } from "./config-utils";
import { EnvVar } from "./environment";
import { Language } from "./languages";
@@ -134,6 +134,7 @@ function tryPrepareSarifDebugArtifact(
* @return The path to the database bundle, or undefined if an error occurs.
*/
async function tryBundleDatabase(
codeql: CodeQL,
config: Config,
language: Language,
logger: Logger,
@@ -141,7 +142,7 @@ async function tryBundleDatabase(
try {
if (dbIsFinalized(config, language, logger)) {
try {
return await createDatabaseBundleCli(config, language);
return await createDatabaseBundleCli(codeql, config, language);
} catch (e) {
logger.warning(
`Failed to bundle database for ${language} using the CLI. ` +
@@ -166,6 +167,7 @@ async function tryBundleDatabase(
* Logs and suppresses any errors that occur.
*/
export async function tryUploadAllAvailableDebugArtifacts(
codeql: CodeQL,
config: Config,
logger: Logger,
codeQlVersion: string | undefined,
@@ -207,6 +209,7 @@ export async function tryUploadAllAvailableDebugArtifacts(
// Add database bundle
logger.info("Preparing database bundle debug artifact...");
const databaseBundle = await tryBundleDatabase(
codeql,
config,
language,
logger,
@@ -369,13 +372,14 @@ async function createPartialDatabaseBundle(
* Runs `codeql database bundle` command and returns the path.
*/
async function createDatabaseBundleCli(
codeql: CodeQL,
config: Config,
language: Language,
): Promise<string> {
const databaseBundlePath = await bundleDb(
config,
language,
await getCodeQL(config.codeQLCmd),
codeql,
`${config.debugDatabaseName}-${language}`,
);
return databaseBundlePath;

View File

@@ -41,6 +41,7 @@ test("post: init action with debug mode off", async (t) => {
await initActionPostHelper.run(
uploadAllAvailableDebugArtifactsSpy,
printDebugLogsSpy,
codeql.createStubCodeQL({}),
createTestConfig({ debugMode: false }),
parseRepositoryNwo("github/codeql-action"),
createFeatures([]),
@@ -63,6 +64,7 @@ test("post: init action with debug mode on", async (t) => {
await initActionPostHelper.run(
uploadAllAvailableDebugArtifactsSpy,
printDebugLogsSpy,
codeql.createStubCodeQL({}),
createTestConfig({ debugMode: true }),
parseRepositoryNwo("github/codeql-action"),
createFeatures([]),

View File

@@ -5,7 +5,7 @@ import * as github from "@actions/github";
import * as actionsUtil from "./actions-util";
import { getApiClient } from "./api-client";
import { getCodeQL } from "./codeql";
import { CodeQL, getCodeQL } from "./codeql";
import { Config } from "./config-utils";
import { EnvVar } from "./environment";
import { Feature, FeatureEnablement } from "./feature-flags";
@@ -160,11 +160,13 @@ export async function tryUploadSarifIfRunFailed(
export async function run(
uploadAllAvailableDebugArtifacts: (
codeql: CodeQL,
config: Config,
logger: Logger,
codeQlVersion: string,
) => Promise<void>,
printDebugLogs: (config: Config) => Promise<void>,
codeql: CodeQL,
config: Config,
repositoryNwo: RepositoryNwo,
features: FeatureEnablement,
@@ -212,9 +214,13 @@ export async function run(
logger.info(
"Debug mode is on. Uploading available database bundles and logs as Actions debugging artifacts...",
);
const codeql = await getCodeQL(config.codeQLCmd);
const version = await codeql.getVersion();
await uploadAllAvailableDebugArtifacts(config, logger, version.version);
await uploadAllAvailableDebugArtifacts(
codeql,
config,
logger,
version.version,
);
await printDebugLogs(config);
}

View File

@@ -12,6 +12,7 @@ import {
printDebugLogs,
} from "./actions-util";
import { getGitHubVersion } from "./api-client";
import { getCodeQL } from "./codeql";
import { Config, getConfig } from "./config-utils";
import * as debugArtifacts from "./debug-artifacts";
import { Features } from "./feature-flags";
@@ -61,9 +62,12 @@ async function runWrapper() {
"Debugging artifacts are unavailable since the 'init' Action failed before it could produce any.",
);
} else {
const codeql = await getCodeQL(config.codeQLCmd);
uploadFailedSarifResult = await initActionPostHelper.run(
debugArtifacts.tryUploadAllAvailableDebugArtifacts,
printDebugLogs,
codeql,
config,
repositoryNwo,
features,

View File

@@ -263,7 +263,7 @@ export function mockCodeQLVersion(
version: string,
features?: { [name: string]: boolean },
) {
return codeql.setCodeQL({
return codeql.createStubCodeQL({
async getVersion() {
return makeVersionInfo(version, features);
},

View File

@@ -8,9 +8,9 @@ import * as sinon from "sinon";
import * as actionsUtil from "./actions-util";
import * as apiClient from "./api-client";
import {
setCodeQL,
getTrapCachingExtractorConfigArgs,
getTrapCachingExtractorConfigArgsForLang,
createStubCodeQL,
} from "./codeql";
import * as configUtils from "./config-utils";
import { Feature } from "./feature-flags";
@@ -34,7 +34,7 @@ import * as util from "./util";
setupTests(test);
const stubCodeql = setCodeQL({
const stubCodeql = createStubCodeQL({
async getVersion() {
return makeVersionInfo("2.10.3");
},