Compare commits

..
8 changed files with 553 additions and 483 deletions

No files matched your search

+470 -460
View File
File diff suppressed because it is too large. Load diff
+13 -1
View File
@@ -1,9 +1,13 @@
import * as fs from "fs";
import * as os from "os";
import * as core from "@actions/core";
import { ActionsEnv, getActionsEnv } from "./actions-util";
import type { ApiClient } from "./api-client";
import { Env, ReadOnlyEnv } from "./environment";
import type { FeatureEnablement } from "./feature-flags";
import type { FileSystem } from "./fs";
import { getActionsLogger, Logger } from "./logging";
import {
ActionName,
@@ -23,6 +27,8 @@ export interface BaseState {
platform: NodeJS.Platform;
/** The architecture of the host. */
arch: NodeJS.Architecture;
/** The version of the operating system. */
osRelease: string;
}
/** Describes different state features that an Action may have. */
@@ -51,6 +57,10 @@ export interface FeatureState {
/** Information about enabled feature flags. */
features: FeatureEnablement;
};
FS: {
/** The file system operations to use. */
fs: FileSystem;
};
}
/** Identifies a type of state an Action may have. */
@@ -143,7 +153,7 @@ export function extendActionState<
* Each Action can then augment the `state` further if additional features are required.
*/
export type ActionMain = (
state: ActionState<["Base", "Logger", "Env", "Actions"]>,
state: ActionState<["Base", "FS", "Logger", "Env", "Actions"]>,
) => Promise<void>;
/** A specification for a CodeQL Action step. */
@@ -173,6 +183,8 @@ export async function runInActions(action: Action) {
startedAt,
platform: process.platform,
arch: process.arch,
osRelease: os.release(),
fs,
logger,
env,
actions: actionsEnv,
+1 -1
View File
@@ -72,7 +72,7 @@ export const getOptionalInput = function (name: string): string | undefined {
* directory that has been set in `CODEQL_ACTION_TEMP` by e.g. a previous step, or the
* value of `RUNNER_TEMP` otherwise.
*/
export function getTemporaryDirectory(env: Env = getEnv()): string {
export function getTemporaryDirectory(env: ReadOnlyEnv = getEnv()): string {
return (
env.getOptional(EnvVar.TEMP) ?? env.getRequired(ActionsEnvVars.RUNNER_TEMP)
);
+2 -2
View File
@@ -2,7 +2,7 @@ import * as fs from "fs";
import path from "path";
import { getTemporaryDirectory } from "../actions-util";
import { Env } from "../environment";
import { ReadOnlyEnv } from "../environment";
import * as json from "../json";
import { Logger } from "../logging";
@@ -51,7 +51,7 @@ export function resetCachedCodeQlVersion(): void {
* Returns the path to the temporary file that backs the
* on-disk cache of CLI responses between workflow steps.
*/
export function getCommandCacheFilePath(env: Env): string {
export function getCommandCacheFilePath(env: ReadOnlyEnv): string {
return path.join(getTemporaryDirectory(env), COMMAND_CACHE_FILENAME);
}
+26 -18
View File
@@ -17,7 +17,7 @@ import type { VersionInfo } from "./cli/types";
import { CliError, wrapCliConfigurationError } from "./cli-errors";
import { appendExtraQueryExclusions, type Config } from "./config-utils";
import { DocUrl } from "./doc-url";
import { EnvVar, getEnv, exportEnvVar } from "./environment";
import { EnvVar, getEnv, exportEnvVar, Env, ReadOnlyEnv } from "./environment";
import {
CodeQLDefaultVersionInfo,
Feature,
@@ -493,8 +493,9 @@ export function createStubCodeQL(partialCodeql: Partial<CodeQL>): CodeQL {
export async function getCodeQLForTesting(
cmd = "codeql-for-testing",
logger: Logger = getRunnerLogger(true),
env: Env = getEnv(),
): Promise<CodeQL> {
return getCodeQLForCmd(logger, cmd, false);
return getCodeQLForCmd(logger, cmd, false, env);
}
/**
@@ -509,13 +510,14 @@ async function getCodeQLForCmd(
logger: Logger,
cmd: string,
checkVersion: boolean,
env: Env = getEnv(),
): Promise<CodeQL> {
const codeql: CodeQL = {
getPath() {
return cmd;
},
async getVersion() {
const cacheFilePath = outputCache.getCommandCacheFilePath(getEnv());
const cacheFilePath = outputCache.getCommandCacheFilePath(env);
let result = outputCache.getCachedCodeQlVersion(
logger,
cacheFilePath,
@@ -641,7 +643,7 @@ async function getCodeQLForCmd(
}
},
async runAutobuild(config: Config, language: Language) {
applyAutobuildAzurePipelinesTimeoutFix();
applyAutobuildAzurePipelinesTimeoutFix(env);
const autobuildCmd = path.join(
await this.resolveExtractor(language),
@@ -651,8 +653,11 @@ async function getCodeQLForCmd(
// Bump the verbosity of the autobuild command if we're in debug mode
if (config.debugMode) {
process.env[EnvVar.CLI_VERBOSITY] =
process.env[EnvVar.CLI_VERBOSITY] || EXTRACTION_DEBUG_MODE_VERBOSITY;
env.set(
EnvVar.CLI_VERBOSITY,
env.getOptional(EnvVar.CLI_VERBOSITY) ??
EXTRACTION_DEBUG_MODE_VERBOSITY,
);
}
// On macOS, System Integrity Protection (SIP) typically interferes with
@@ -684,7 +689,7 @@ async function getCodeQLForCmd(
},
async extractUsingBuildMode(config: Config, language: Language) {
if (config.buildMode === BuildMode.Autobuild) {
applyAutobuildAzurePipelinesTimeoutFix();
applyAutobuildAzurePipelinesTimeoutFix(env);
}
try {
await runCli(cmd, [
@@ -816,7 +821,7 @@ async function getCodeQLForCmd(
"--sarif-group-rules-by-pack",
"--sarif-include-query-help=always",
"--sublanguage-file-coverage",
...(await getJobRunUuidSarifOptions()),
...(await getJobRunUuidSarifOptions(env)),
...getExtraOptionsFromEnv(["database", "interpret-results"]),
];
if (sarifRunPropertyFlag !== undefined) {
@@ -1036,7 +1041,7 @@ async function getCodeQLForCmd(
);
} else if (
checkVersion &&
process.env[EnvVar.SUPPRESS_DEPRECATED_SOON_WARNING] !== "true" &&
env.getOptional(EnvVar.SUPPRESS_DEPRECATED_SOON_WARNING) !== "true" &&
!(await util.codeQlVersionAtLeast(codeql, CODEQL_NEXT_MINIMUM_VERSION))
) {
const result = await codeql.getVersion();
@@ -1256,17 +1261,20 @@ function getExtractionVerbosityArguments(
* Without the fix, long build processes will timeout when pulling down Java packages
* https://developercommunity.visualstudio.com/content/problem/292284/maven-hosted-agent-connection-timeout.html
*/
function applyAutobuildAzurePipelinesTimeoutFix() {
const javaToolOptions = process.env["JAVA_TOOL_OPTIONS"] || "";
process.env["JAVA_TOOL_OPTIONS"] = [
...javaToolOptions.split(/\s+/),
"-Dhttp.keepAlive=false",
"-Dmaven.wagon.http.pool=false",
].join(" ");
function applyAutobuildAzurePipelinesTimeoutFix(env: Env) {
const javaToolOptions = env.getOptional("JAVA_TOOL_OPTIONS") ?? "";
env.set(
"JAVA_TOOL_OPTIONS",
[
...javaToolOptions.split(/\s+/),
"-Dhttp.keepAlive=false",
"-Dmaven.wagon.http.pool=false",
].join(" "),
);
}
async function getJobRunUuidSarifOptions() {
const jobRunUuid = process.env[EnvVar.JOB_RUN_UUID];
async function getJobRunUuidSarifOptions(env: ReadOnlyEnv) {
const jobRunUuid = env.getOptional(EnvVar.JOB_RUN_UUID);
return jobRunUuid ? [`--sarif-run-property=jobRunUuid=${jobRunUuid}`] : [];
}
+35
View File
@@ -0,0 +1,35 @@
/**
* This module exports a `FileSystem` type which corresponds to the interface of the "fs" module.
*
* Functions which are parameterised over this type can then be passed a different implementation in tests:
*
* ```typescript
* import * as nodefs from "fs";
*
* function foo(fs: FileSystem = nodefs) {
* // Uses the real "fs" module by default, but can be given a different implementation.
* }
* ```
*
* The type can also be constrained to a subset of available operations. For example, in the following
* case we have a function that only needs `statSync`:
*
* ```
* function bar(fs: FileSystem<"statSync"> = nodefs) {
* // This function can only use `statSync`.
* }
* ```
*
* This is useful to define a clearer interface for what the function does and also only requires stubbing
* of the relevant functions.
*/
import * as fs from "fs";
/** Represents the names of operations exported from "fs". */
export type FileOperation = keyof typeof fs;
/** Represents the type of "fs", optionally filtered down to just `Ops`. */
export type FileSystem<Ops extends FileOperation = keyof typeof fs> = {
[Key in Ops]: (typeof fs)[Key];
};
+1 -1
View File
@@ -204,7 +204,7 @@ async function sendCompletedStatusReport(
}
async function run(
actionState: ActionState<["Base", "Logger", "Env", "Actions"]>,
actionState: ActionState<["Base", "Logger", "Env", "Actions", "FS"]>,
) {
// To capture errors appropriately, keep as much code within the try-catch as
// possible, and only use safe functions outside.
+5
View File
@@ -1,4 +1,6 @@
import * as fs from "fs";
import { TextDecoder } from "node:util";
import * as os from "os";
import path from "path";
import * as github from "@actions/github";
@@ -220,6 +222,7 @@ type AllState = [
"Actions",
"Api",
"FeatureFlags",
"FS",
];
/** Initialise a fresh `ActionState<AllState>` value. */
@@ -232,11 +235,13 @@ export function initAllState(
startedAt: new Date(),
platform: process.platform,
arch: process.arch,
osRelease: os.release(),
logger: new RecordingLogger(),
env,
actions: getTestActionsEnv(env),
apiClient: github.getOctokit("123"),
features: createFeatures([]),
fs,
...overrides,
};
}