mirror of
https://github.com/github/codeql-action.git
synced 2026-10-03 09:14:58 +00:00
Merge remote-tracking branch 'origin/main' into mbg/status/registries
This commit is contained in:
4
.github/workflows/__job-run-uuid-sarif.yml
generated
vendored
4
.github/workflows/__job-run-uuid-sarif.yml
generated
vendored
@@ -71,8 +71,8 @@ jobs:
|
||||
run: |
|
||||
cd "$RUNNER_TEMP/results"
|
||||
actual=$(jq -r '.runs[0].properties.jobRunUuid' javascript.sarif)
|
||||
if [[ "$actual" != "$JOB_RUN_UUID" ]]; then
|
||||
echo "Expected SARIF output to contain job run UUID '$JOB_RUN_UUID', but found '$actual'."
|
||||
if [[ "$actual" != "$CODEQL_ACTION_JOB_RUN_UUID" ]]; then
|
||||
echo "Expected SARIF output to contain job run UUID '$CODEQL_ACTION_JOB_RUN_UUID', but found '$actual'."
|
||||
exit 1
|
||||
else
|
||||
echo "Found job run UUID '$actual'."
|
||||
|
||||
@@ -5,6 +5,7 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th
|
||||
## [UNRELEASED]
|
||||
|
||||
- This version of the CodeQL Action adds support for the `tools` input for the `codeql-action/init` step to be specified using a `github-codeql-tools` [repository property](https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization). This feature will gradually be rolled out following the release of this version. Once rolled out, this allows for the CodeQL CLI version that is used in GitHub-managed workflows, such as Default Setup, to be set to a custom value. For example, customers who run into issues with rate limits when a new CodeQL CLI version is released can set the value to `toolcache` to always use the CodeQL CLI version that is available in the runner toolcache. For Advanced Setup workflows, the value provided for `tools` in the workflow definition always takes precedence unless the value of the repository property starts with `!`. [#4037](https://github.com/github/codeql-action/pull/4037)
|
||||
- Update default CodeQL bundle version to [2.26.2](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2). [#4051](https://github.com/github/codeql-action/pull/4051)
|
||||
|
||||
## 4.37.3 - 22 Jul 2026
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"bundleVersion": "codeql-bundle-v2.26.1",
|
||||
"cliVersion": "2.26.1",
|
||||
"priorBundleVersion": "codeql-bundle-v2.26.0",
|
||||
"priorCliVersion": "2.26.0"
|
||||
"bundleVersion": "codeql-bundle-v2.26.2",
|
||||
"cliVersion": "2.26.2",
|
||||
"priorBundleVersion": "codeql-bundle-v2.26.1",
|
||||
"priorCliVersion": "2.26.1"
|
||||
}
|
||||
|
||||
202
lib/entry-points.js
generated
202
lib/entry-points.js
generated
@@ -21559,7 +21559,7 @@ var require_core = __commonJS({
|
||||
};
|
||||
Object.defineProperty(exports2, "__esModule", { value: true });
|
||||
exports2.platform = exports2.toPlatformPath = exports2.toWin32Path = exports2.toPosixPath = exports2.markdownSummary = exports2.summary = exports2.ExitCode = void 0;
|
||||
exports2.exportVariable = exportVariable15;
|
||||
exports2.exportVariable = exportVariable16;
|
||||
exports2.setSecret = setSecret2;
|
||||
exports2.addPath = addPath2;
|
||||
exports2.getInput = getInput2;
|
||||
@@ -21567,7 +21567,7 @@ var require_core = __commonJS({
|
||||
exports2.getBooleanInput = getBooleanInput;
|
||||
exports2.setOutput = setOutput7;
|
||||
exports2.setCommandEcho = setCommandEcho;
|
||||
exports2.setFailed = setFailed13;
|
||||
exports2.setFailed = setFailed12;
|
||||
exports2.isDebug = isDebug5;
|
||||
exports2.debug = debug6;
|
||||
exports2.error = error3;
|
||||
@@ -21591,7 +21591,7 @@ var require_core = __commonJS({
|
||||
ExitCode2[ExitCode2["Success"] = 0] = "Success";
|
||||
ExitCode2[ExitCode2["Failure"] = 1] = "Failure";
|
||||
})(ExitCode || (exports2.ExitCode = ExitCode = {}));
|
||||
function exportVariable15(name, val) {
|
||||
function exportVariable16(name, val) {
|
||||
const convertedVal = (0, utils_1.toCommandValue)(val);
|
||||
process.env[name] = convertedVal;
|
||||
const filePath = process.env["GITHUB_ENV"] || "";
|
||||
@@ -21651,7 +21651,7 @@ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``);
|
||||
function setCommandEcho(enabled) {
|
||||
(0, command_1.issue)("echo", enabled ? "on" : "off");
|
||||
}
|
||||
function setFailed13(message) {
|
||||
function setFailed12(message) {
|
||||
process.exitCode = ExitCode.Failure;
|
||||
error3(message);
|
||||
}
|
||||
@@ -30477,7 +30477,7 @@ var require_validator = __commonJS({
|
||||
Validator3.prototype.getSchema = function getSchema(urn) {
|
||||
return this.schemas[urn];
|
||||
};
|
||||
Validator3.prototype.validate = function validate(instance, schema, options, ctx) {
|
||||
Validator3.prototype.validate = function validate2(instance, schema, options, ctx) {
|
||||
if (typeof schema !== "boolean" && typeof schema !== "object" || schema === null) {
|
||||
throw new SchemaError("Expected `schema` to be an object or boolean");
|
||||
}
|
||||
@@ -121026,7 +121026,7 @@ var require_core3 = __commonJS({
|
||||
ExitCode2[ExitCode2["Success"] = 0] = "Success";
|
||||
ExitCode2[ExitCode2["Failure"] = 1] = "Failure";
|
||||
})(ExitCode || (exports2.ExitCode = ExitCode = {}));
|
||||
function exportVariable15(name, val) {
|
||||
function exportVariable16(name, val) {
|
||||
const convertedVal = (0, utils_1.toCommandValue)(val);
|
||||
process.env[name] = convertedVal;
|
||||
const filePath = process.env["GITHUB_ENV"] || "";
|
||||
@@ -121035,7 +121035,7 @@ var require_core3 = __commonJS({
|
||||
}
|
||||
(0, command_1.issueCommand)("set-env", { name }, convertedVal);
|
||||
}
|
||||
exports2.exportVariable = exportVariable15;
|
||||
exports2.exportVariable = exportVariable16;
|
||||
function setSecret2(secret) {
|
||||
(0, command_1.issueCommand)("add-mask", {}, secret);
|
||||
}
|
||||
@@ -121094,11 +121094,11 @@ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``);
|
||||
(0, command_1.issue)("echo", enabled ? "on" : "off");
|
||||
}
|
||||
exports2.setCommandEcho = setCommandEcho;
|
||||
function setFailed13(message) {
|
||||
function setFailed12(message) {
|
||||
process.exitCode = ExitCode.Failure;
|
||||
error3(message);
|
||||
}
|
||||
exports2.setFailed = setFailed13;
|
||||
exports2.setFailed = setFailed12;
|
||||
function isDebug5() {
|
||||
return process.env["RUNNER_DEBUG"] === "1";
|
||||
}
|
||||
@@ -141565,6 +141565,10 @@ var ReadOnlyEnv = class {
|
||||
clone() {
|
||||
return Object.create(this, { vars: { value: { ...this.vars } } });
|
||||
}
|
||||
/** Gets a copy of the underlying environment. */
|
||||
get() {
|
||||
return { ...this.vars };
|
||||
}
|
||||
/** Tries to get the value for `name` and throws if there isn't one. */
|
||||
getRequired(name) {
|
||||
return getRequiredEnvVar(this.vars, name);
|
||||
@@ -144598,13 +144602,13 @@ function isBoolean(value) {
|
||||
function isStringOrUndefined(value) {
|
||||
return value === void 0 || isString(value);
|
||||
}
|
||||
function defaultCheck(validate) {
|
||||
return (arg) => ({ unknownKeys: [], invalidKeys: [], valid: validate(arg) });
|
||||
function defaultCheck(validate2) {
|
||||
return (arg) => ({ unknownKeys: [], invalidKeys: [], valid: validate2(arg) });
|
||||
}
|
||||
function makeValidator(validate) {
|
||||
function makeValidator(validate2) {
|
||||
return {
|
||||
validate,
|
||||
check: defaultCheck(validate),
|
||||
validate: validate2,
|
||||
check: defaultCheck(validate2),
|
||||
required: true
|
||||
};
|
||||
}
|
||||
@@ -144612,11 +144616,11 @@ var string = makeValidator(isString);
|
||||
var number = makeValidator(isNumber);
|
||||
var boolean = makeValidator(isBoolean);
|
||||
function array(validator) {
|
||||
const validate = (val) => {
|
||||
const validate2 = (val) => {
|
||||
return isArray(val) && val.every((e) => validator.validate(e));
|
||||
};
|
||||
return {
|
||||
validate,
|
||||
validate: validate2,
|
||||
check: (val, opts, path29) => {
|
||||
const result = successfulCheckSchema();
|
||||
if (!isArray(val)) {
|
||||
@@ -145391,7 +145395,11 @@ var Failure = class {
|
||||
|
||||
// src/actions-util.ts
|
||||
function getActionsEnv() {
|
||||
return { getOptionalInput };
|
||||
return {
|
||||
getRequiredInput,
|
||||
getOptionalInput,
|
||||
exportVariable: core3.exportVariable
|
||||
};
|
||||
}
|
||||
var getRequiredInput = function(name) {
|
||||
const value = core3.getInput(name);
|
||||
@@ -145736,6 +145744,59 @@ function formatDuration(durationMs) {
|
||||
var os3 = __toESM(require("os"));
|
||||
var core7 = __toESM(require_core());
|
||||
|
||||
// node_modules/uuid/dist-node/regex.js
|
||||
var regex_default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i;
|
||||
|
||||
// node_modules/uuid/dist-node/validate.js
|
||||
function validate(uuid) {
|
||||
return typeof uuid === "string" && regex_default.test(uuid);
|
||||
}
|
||||
var validate_default = validate;
|
||||
|
||||
// node_modules/uuid/dist-node/stringify.js
|
||||
var byteToHex = [];
|
||||
for (let i = 0; i < 256; ++i) {
|
||||
byteToHex.push((i + 256).toString(16).slice(1));
|
||||
}
|
||||
function unsafeStringify(arr, offset = 0) {
|
||||
return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
|
||||
}
|
||||
|
||||
// node_modules/uuid/dist-node/rng.js
|
||||
var rnds8 = new Uint8Array(16);
|
||||
function rng() {
|
||||
return crypto.getRandomValues(rnds8);
|
||||
}
|
||||
|
||||
// node_modules/uuid/dist-node/v4.js
|
||||
function v4(options, buf, offset) {
|
||||
if (!buf && !options && crypto.randomUUID) {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
return _v4(options, buf, offset);
|
||||
}
|
||||
function _v4(options, buf, offset) {
|
||||
options = options || {};
|
||||
const rnds = options.random ?? options.rng?.() ?? rng();
|
||||
if (rnds.length < 16) {
|
||||
throw new Error("Random bytes length must be >= 16");
|
||||
}
|
||||
rnds[6] = rnds[6] & 15 | 64;
|
||||
rnds[8] = rnds[8] & 63 | 128;
|
||||
if (buf) {
|
||||
offset = offset || 0;
|
||||
if (offset < 0 || offset + 16 > buf.length) {
|
||||
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
|
||||
}
|
||||
for (let i = 0; i < 16; ++i) {
|
||||
buf[offset + i] = rnds[i];
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
return unsafeStringify(rnds);
|
||||
}
|
||||
var v4_default = v4;
|
||||
|
||||
// src/api-client.ts
|
||||
var core5 = __toESM(require_core());
|
||||
var githubUtils = __toESM(require_utils4());
|
||||
@@ -146496,6 +146557,17 @@ function getDisplayActionName(actionName) {
|
||||
}
|
||||
return actionName;
|
||||
}
|
||||
function getJobUUID(action) {
|
||||
const existingJobRunUuid = action.env.getOptional("CODEQL_ACTION_JOB_RUN_UUID" /* JOB_RUN_UUID */);
|
||||
if (existingJobRunUuid !== void 0 && validate_default(existingJobRunUuid)) {
|
||||
action.logger.info(`Existing job run UUID is ${existingJobRunUuid}.`);
|
||||
return existingJobRunUuid;
|
||||
}
|
||||
const jobRunUuid = v4_default();
|
||||
action.logger.info(`Job run UUID is ${jobRunUuid}.`);
|
||||
action.actions.exportVariable("CODEQL_ACTION_JOB_RUN_UUID" /* JOB_RUN_UUID */, jobRunUuid);
|
||||
return jobRunUuid;
|
||||
}
|
||||
function isFirstPartyAnalysis(actionName) {
|
||||
if (actionName !== "upload-sarif" /* UploadSarif */) {
|
||||
return true;
|
||||
@@ -146578,7 +146650,7 @@ async function createStatusReportBase(actionName, status, actionStartedAt, confi
|
||||
try {
|
||||
const commitOid = getOptionalInput("sha") || process.env["GITHUB_SHA"] || "";
|
||||
const ref = await getRef();
|
||||
const jobRunUUID = process.env["JOB_RUN_UUID" /* JOB_RUN_UUID */] || "";
|
||||
const jobRunUUID = process.env["CODEQL_ACTION_JOB_RUN_UUID" /* JOB_RUN_UUID */] || "";
|
||||
const workflowRunID = getWorkflowRunID();
|
||||
const workflowRunAttempt = getWorkflowRunAttempt();
|
||||
const workflowName = process.env["GITHUB_WORKFLOW"] || "";
|
||||
@@ -146807,18 +146879,26 @@ async function runInActions(action) {
|
||||
const env = getEnv();
|
||||
const actionsEnv = getActionsEnv();
|
||||
try {
|
||||
await action.run({
|
||||
const actionState = {
|
||||
name: action.name,
|
||||
startedAt,
|
||||
logger,
|
||||
env,
|
||||
actions: actionsEnv
|
||||
});
|
||||
};
|
||||
getJobUUID(actionState);
|
||||
await action.run(actionState);
|
||||
} catch (error3) {
|
||||
core8.setFailed(
|
||||
`${getDisplayActionName(action.name)} action failed: ${getErrorMessage(error3)}`
|
||||
);
|
||||
await sendUnhandledErrorStatusReport(action.name, startedAt, error3, logger);
|
||||
const statusReportError = action.transformTelemetryError !== void 0 ? action.transformTelemetryError(wrapError(error3)) : error3;
|
||||
await sendUnhandledErrorStatusReport(
|
||||
action.name,
|
||||
startedAt,
|
||||
statusReportError,
|
||||
logger
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146828,8 +146908,8 @@ var path5 = __toESM(require("path"));
|
||||
var semver4 = __toESM(require_semver2());
|
||||
|
||||
// src/defaults.json
|
||||
var bundleVersion = "codeql-bundle-v2.26.1";
|
||||
var cliVersion = "2.26.1";
|
||||
var bundleVersion = "codeql-bundle-v2.26.2";
|
||||
var cliVersion = "2.26.2";
|
||||
|
||||
// src/overlay/index.ts
|
||||
var fs4 = __toESM(require("fs"));
|
||||
@@ -150247,50 +150327,6 @@ var toolcache3 = __toESM(require_tool_cache());
|
||||
var import_fast_deep_equal = __toESM(require_fast_deep_equal());
|
||||
var semver9 = __toESM(require_semver2());
|
||||
|
||||
// node_modules/uuid/dist-node/stringify.js
|
||||
var byteToHex = [];
|
||||
for (let i = 0; i < 256; ++i) {
|
||||
byteToHex.push((i + 256).toString(16).slice(1));
|
||||
}
|
||||
function unsafeStringify(arr, offset = 0) {
|
||||
return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
|
||||
}
|
||||
|
||||
// node_modules/uuid/dist-node/rng.js
|
||||
var rnds8 = new Uint8Array(16);
|
||||
function rng() {
|
||||
return crypto.getRandomValues(rnds8);
|
||||
}
|
||||
|
||||
// node_modules/uuid/dist-node/v4.js
|
||||
function v4(options, buf, offset) {
|
||||
if (!buf && !options && crypto.randomUUID) {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
return _v4(options, buf, offset);
|
||||
}
|
||||
function _v4(options, buf, offset) {
|
||||
options = options || {};
|
||||
const rnds = options.random ?? options.rng?.() ?? rng();
|
||||
if (rnds.length < 16) {
|
||||
throw new Error("Random bytes length must be >= 16");
|
||||
}
|
||||
rnds[6] = rnds[6] & 15 | 64;
|
||||
rnds[8] = rnds[8] & 63 | 128;
|
||||
if (buf) {
|
||||
offset = offset || 0;
|
||||
if (offset < 0 || offset + 16 > buf.length) {
|
||||
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
|
||||
}
|
||||
for (let i = 0; i < 16; ++i) {
|
||||
buf[offset + i] = rnds[i];
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
return unsafeStringify(rnds);
|
||||
}
|
||||
var v4_default = v4;
|
||||
|
||||
// src/overlay/caching.ts
|
||||
var fs10 = __toESM(require("fs"));
|
||||
var actionsCache3 = __toESM(require_cache4());
|
||||
@@ -152163,7 +152199,7 @@ function applyAutobuildAzurePipelinesTimeoutFix() {
|
||||
].join(" ");
|
||||
}
|
||||
async function getJobRunUuidSarifOptions() {
|
||||
const jobRunUuid = process.env["JOB_RUN_UUID" /* JOB_RUN_UUID */];
|
||||
const jobRunUuid = process.env["CODEQL_ACTION_JOB_RUN_UUID" /* JOB_RUN_UUID */];
|
||||
return jobRunUuid ? [`--sarif-run-property=jobRunUuid=${jobRunUuid}`] : [];
|
||||
}
|
||||
|
||||
@@ -160930,9 +160966,6 @@ async function run3(actionState) {
|
||||
logger
|
||||
);
|
||||
const repositoryProperties = repositoryPropertiesResult.orElse({});
|
||||
const jobRunUuid = v4_default();
|
||||
logger.info(`Job run UUID is ${jobRunUuid}.`);
|
||||
core21.exportVariable("JOB_RUN_UUID" /* JOB_RUN_UUID */, jobRunUuid);
|
||||
core21.exportVariable("CODEQL_ACTION_INIT_HAS_RUN" /* INIT_ACTION_HAS_RUN */, "true");
|
||||
sourceRoot = path24.resolve(
|
||||
getRequiredEnvParam("GITHUB_WORKSPACE"),
|
||||
@@ -161930,9 +161963,6 @@ async function run6(actionState) {
|
||||
);
|
||||
const repositoryProperties = repositoryPropertiesResult.orElse({});
|
||||
const actionStateWithFeatures = { ...actionState, features };
|
||||
const jobRunUuid = v4_default();
|
||||
logger.info(`Job run UUID is ${jobRunUuid}.`);
|
||||
core24.exportVariable("JOB_RUN_UUID" /* JOB_RUN_UUID */, jobRunUuid);
|
||||
const statusReportBase = await createStatusReportBase(
|
||||
"setup-codeql" /* SetupCodeQL */,
|
||||
"starting",
|
||||
@@ -162667,8 +162697,9 @@ async function checkConnections(logger, proxy, backend) {
|
||||
}
|
||||
|
||||
// src/start-proxy-action.ts
|
||||
async function run7(startedAt) {
|
||||
const logger = getActionsLogger();
|
||||
async function run7(action) {
|
||||
const startedAt = action.startedAt;
|
||||
const logger = action.logger;
|
||||
let features;
|
||||
let language;
|
||||
try {
|
||||
@@ -162734,20 +162765,13 @@ async function run7(startedAt) {
|
||||
await sendFailedStatusReport(logger, startedAt, language, unwrappedError);
|
||||
}
|
||||
}
|
||||
var startProxyAction = {
|
||||
name: "start-proxy" /* StartProxy */,
|
||||
run: run7,
|
||||
transformTelemetryError: getSafeErrorMessage
|
||||
};
|
||||
async function runWrapper8() {
|
||||
const startedAt = /* @__PURE__ */ new Date();
|
||||
const logger = getActionsLogger();
|
||||
try {
|
||||
await run7(startedAt);
|
||||
} catch (error3) {
|
||||
core27.setFailed(`start-proxy action failed: ${getErrorMessage(error3)}`);
|
||||
await sendUnhandledErrorStatusReport(
|
||||
"start-proxy" /* StartProxy */,
|
||||
startedAt,
|
||||
getSafeErrorMessage(wrapError(error3)),
|
||||
logger
|
||||
);
|
||||
}
|
||||
await runInActions(startProxyAction);
|
||||
}
|
||||
async function startProxy(binPath, config, logFilePath, logger) {
|
||||
const host = "127.0.0.1";
|
||||
|
||||
@@ -21,8 +21,8 @@ steps:
|
||||
run: |
|
||||
cd "$RUNNER_TEMP/results"
|
||||
actual=$(jq -r '.runs[0].properties.jobRunUuid' javascript.sarif)
|
||||
if [[ "$actual" != "$JOB_RUN_UUID" ]]; then
|
||||
echo "Expected SARIF output to contain job run UUID '$JOB_RUN_UUID', but found '$actual'."
|
||||
if [[ "$actual" != "$CODEQL_ACTION_JOB_RUN_UUID" ]]; then
|
||||
echo "Expected SARIF output to contain job run UUID '$CODEQL_ACTION_JOB_RUN_UUID', but found '$actual'."
|
||||
exit 1
|
||||
else
|
||||
echo "Found job run UUID '$actual'."
|
||||
|
||||
123
src/action-common.test.ts
Normal file
123
src/action-common.test.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import * as core from "@actions/core";
|
||||
import test from "ava";
|
||||
import sinon from "sinon";
|
||||
|
||||
import * as common from "./action-common";
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import * as environment from "./environment";
|
||||
import * as logging from "./logging";
|
||||
import { ActionName } from "./status-report";
|
||||
import * as statusReport from "./status-report";
|
||||
import {
|
||||
getTestActionsEnv,
|
||||
getTestEnv,
|
||||
makeMacro,
|
||||
RecordingLogger,
|
||||
setupTests,
|
||||
} from "./testing-utils";
|
||||
import { getErrorMessage } from "./util";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
interface RunInActionsTestOpts {
|
||||
runFn?: () => Promise<any>;
|
||||
expectedErrorMessage?: string;
|
||||
expectedTelemetryError?: string;
|
||||
}
|
||||
|
||||
const runInActionsMacro = makeMacro({
|
||||
exec: async (t, opts: RunInActionsTestOpts) => {
|
||||
const expectFailure = opts?.expectedErrorMessage !== undefined;
|
||||
|
||||
const logger = new RecordingLogger();
|
||||
const getActionsLogger = sinon
|
||||
.stub(logging, "getActionsLogger")
|
||||
.returns(logger);
|
||||
|
||||
const env = getTestEnv();
|
||||
const getEnv = sinon.stub(environment, "getEnv").returns(env);
|
||||
|
||||
const actionsEnv = getTestActionsEnv(env);
|
||||
const getActionsEnv = sinon
|
||||
.stub(actionsUtil, "getActionsEnv")
|
||||
.returns(actionsEnv);
|
||||
|
||||
const getJobUUID = sinon
|
||||
.stub(statusReport, "getJobUUID")
|
||||
.returns("test-job-uuid");
|
||||
|
||||
const setFailed = sinon.stub(core, "setFailed");
|
||||
const sendUnhandledErrorStatusReport = sinon.stub(
|
||||
statusReport,
|
||||
"sendUnhandledErrorStatusReport",
|
||||
);
|
||||
|
||||
const name = ActionName.Init;
|
||||
const run = sinon.stub();
|
||||
|
||||
if (opts?.runFn) {
|
||||
run.callsFake(opts.runFn);
|
||||
}
|
||||
|
||||
const transformTelemetryError = sinon
|
||||
.stub()
|
||||
.callsFake((err) => opts?.expectedTelemetryError ?? getErrorMessage(err));
|
||||
const testAction: common.Action = {
|
||||
name,
|
||||
run,
|
||||
transformTelemetryError,
|
||||
};
|
||||
|
||||
await common.runInActions(testAction);
|
||||
|
||||
// These always should have been called once.
|
||||
t.true(getActionsLogger.calledOnce);
|
||||
t.true(getEnv.calledOnce);
|
||||
t.true(getActionsEnv.calledOnce);
|
||||
|
||||
const expectedActionState = {
|
||||
actions: actionsEnv,
|
||||
env,
|
||||
logger,
|
||||
name: ActionName.Init,
|
||||
};
|
||||
|
||||
t.true(getJobUUID.calledOnceWithExactly(sinon.match(expectedActionState)));
|
||||
t.true(run.calledOnceWithExactly(sinon.match(expectedActionState)));
|
||||
|
||||
t.is(setFailed.calledOnce, expectFailure ?? false);
|
||||
t.is(sendUnhandledErrorStatusReport.calledOnce, expectFailure ?? false);
|
||||
|
||||
if (expectFailure) {
|
||||
t.true(
|
||||
setFailed.calledOnceWithExactly(
|
||||
`${statusReport.getDisplayActionName(name)} action failed: ${opts?.expectedErrorMessage}`,
|
||||
),
|
||||
);
|
||||
t.true(
|
||||
sendUnhandledErrorStatusReport.calledOnceWithExactly(
|
||||
name,
|
||||
sinon.match.any,
|
||||
opts?.expectedTelemetryError ?? opts?.expectedErrorMessage,
|
||||
logger,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
title: (providedTitle) => `runInActions - ${providedTitle}`,
|
||||
});
|
||||
|
||||
runInActionsMacro.serial("calls run", {});
|
||||
runInActionsMacro.serial("handles run exceptions", {
|
||||
runFn: () => {
|
||||
throw new Error("Test failure");
|
||||
},
|
||||
expectedErrorMessage: "Test failure",
|
||||
});
|
||||
runInActionsMacro.serial("transforms run exceptions", {
|
||||
runFn: () => {
|
||||
throw new Error("Test failure");
|
||||
},
|
||||
expectedErrorMessage: "Test failure",
|
||||
expectedTelemetryError: "Transformed failure message",
|
||||
});
|
||||
@@ -8,9 +8,10 @@ import { getActionsLogger, Logger } from "./logging";
|
||||
import {
|
||||
ActionName,
|
||||
getDisplayActionName,
|
||||
getJobUUID,
|
||||
sendUnhandledErrorStatusReport,
|
||||
} from "./status-report";
|
||||
import { getEnv, getErrorMessage } from "./util";
|
||||
import { getEnv, getErrorMessage, wrapError } from "./util";
|
||||
|
||||
/** Base state that is available to an Action on startup. */
|
||||
export interface BaseState {
|
||||
@@ -78,6 +79,12 @@ export interface Action {
|
||||
name: ActionName;
|
||||
/** The entry point for the Action. */
|
||||
run: ActionMain;
|
||||
/**
|
||||
* An optional function that transforms a caught error into a message suitable for
|
||||
* inclusion in a status report. This is primarily intended for the `start-proxy`
|
||||
* action to replace the thrown `Error`'s message with a safe one.
|
||||
*/
|
||||
transformTelemetryError?: (error: Error) => string;
|
||||
}
|
||||
|
||||
/** A generic entry point that sets up the basic environment for the `action` and runs it. */
|
||||
@@ -88,17 +95,32 @@ export async function runInActions(action: Action) {
|
||||
const actionsEnv = getActionsEnv();
|
||||
|
||||
try {
|
||||
await action.run({
|
||||
const actionState = {
|
||||
name: action.name,
|
||||
startedAt,
|
||||
logger,
|
||||
env,
|
||||
actions: actionsEnv,
|
||||
});
|
||||
};
|
||||
|
||||
// Create a unique identifier for this run.
|
||||
getJobUUID(actionState);
|
||||
|
||||
await action.run(actionState);
|
||||
} catch (error) {
|
||||
core.setFailed(
|
||||
`${getDisplayActionName(action.name)} action failed: ${getErrorMessage(error)}`,
|
||||
);
|
||||
await sendUnhandledErrorStatusReport(action.name, startedAt, error, logger);
|
||||
|
||||
const statusReportError =
|
||||
action.transformTelemetryError !== undefined
|
||||
? action.transformTelemetryError(wrapError(error))
|
||||
: error;
|
||||
await sendUnhandledErrorStatusReport(
|
||||
action.name,
|
||||
startedAt,
|
||||
statusReportError,
|
||||
logger,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,14 +27,20 @@ declare const __CODEQL_ACTION_VERSION__: string;
|
||||
* global functions in tests.
|
||||
*/
|
||||
export interface ActionsEnv {
|
||||
getRequiredInput: (name: string) => string;
|
||||
getOptionalInput: (name: string) => string | undefined;
|
||||
exportVariable: (name: string, value: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the real `ActionsEnv` used by production code.
|
||||
*/
|
||||
export function getActionsEnv(): ActionsEnv {
|
||||
return { getOptionalInput };
|
||||
return {
|
||||
getRequiredInput,
|
||||
getOptionalInput,
|
||||
exportVariable: core.exportVariable,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import test from "ava";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { getActionsEnv } from "../actions-util";
|
||||
import { ActionsEnv } from "../actions-util";
|
||||
import { Feature } from "../feature-flags";
|
||||
import { RepositoryPropertyName } from "../feature-flags/properties";
|
||||
import { callee } from "../testing-utils";
|
||||
@@ -22,32 +22,26 @@ const expectedRepositoryPropertyResult: ComputedInput = {
|
||||
value: "repo-property-input-value",
|
||||
};
|
||||
|
||||
function stubGetToolsInput() {
|
||||
const actions = getActionsEnv();
|
||||
function stubGetToolsInput(actions: ActionsEnv) {
|
||||
sinon
|
||||
.stub(actions, "getOptionalInput")
|
||||
.withArgs(InputName.Tools)
|
||||
.returns(expectedWorkflowResult.value);
|
||||
return actions;
|
||||
}
|
||||
|
||||
const workflowLogMessage = `Using ${InputName.Tools} input from workflow:`;
|
||||
|
||||
test("getToolsInput - returns workflow input if available", async (t) => {
|
||||
const actions = stubGetToolsInput();
|
||||
|
||||
await callee(getToolsInput)
|
||||
.withActions(actions)
|
||||
.withActions(stubGetToolsInput)
|
||||
.withArgs({})
|
||||
.logs(t, workflowLogMessage)
|
||||
.passes(t.deepEqual, expectedWorkflowResult);
|
||||
});
|
||||
|
||||
test("getToolsInput - returns repository property value if enforced", async (t) => {
|
||||
const actions = stubGetToolsInput();
|
||||
|
||||
const target = callee(getToolsInput)
|
||||
.withActions(actions)
|
||||
.withActions(stubGetToolsInput)
|
||||
.withArgs({
|
||||
[RepositoryPropertyName.TOOLS]: `!${expectedRepositoryPropertyResult.value}`,
|
||||
});
|
||||
@@ -65,10 +59,8 @@ test("getToolsInput - returns repository property value if enforced", async (t)
|
||||
});
|
||||
|
||||
test("getToolsInput - prefers workflow input", async (t) => {
|
||||
const actions = stubGetToolsInput();
|
||||
|
||||
const target = callee(getToolsInput)
|
||||
.withActions(actions)
|
||||
.withActions(stubGetToolsInput)
|
||||
.withArgs({
|
||||
[RepositoryPropertyName.TOOLS]: expectedRepositoryPropertyResult.value,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"bundleVersion": "codeql-bundle-v2.26.1",
|
||||
"cliVersion": "2.26.1",
|
||||
"priorBundleVersion": "codeql-bundle-v2.26.0",
|
||||
"priorCliVersion": "2.26.0"
|
||||
"bundleVersion": "codeql-bundle-v2.26.2",
|
||||
"cliVersion": "2.26.2",
|
||||
"priorBundleVersion": "codeql-bundle-v2.26.1",
|
||||
"priorCliVersion": "2.26.1"
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ export enum EnvVar {
|
||||
LOG_VERSION_DEPRECATION = "CODEQL_ACTION_DID_LOG_VERSION_DEPRECATION",
|
||||
|
||||
/** UUID representing the current job run. */
|
||||
JOB_RUN_UUID = "JOB_RUN_UUID",
|
||||
JOB_RUN_UUID = "CODEQL_ACTION_JOB_RUN_UUID",
|
||||
|
||||
/** Status for the entire job, submitted to the status report in `init-post` */
|
||||
JOB_STATUS = "CODEQL_ACTION_JOB_STATUS",
|
||||
@@ -270,6 +270,11 @@ export class ReadOnlyEnv<T extends string | undefined = string | undefined> {
|
||||
return Object.create(this, { vars: { value: { ...this.vars } } }) as this;
|
||||
}
|
||||
|
||||
/** Gets a copy of the underlying environment. */
|
||||
public get(): Record<string, T> {
|
||||
return { ...this.vars };
|
||||
}
|
||||
|
||||
/** Tries to get the value for `name` and throws if there isn't one. */
|
||||
public getRequired(name: string): string {
|
||||
return getRequiredEnvVar(this.vars, name);
|
||||
|
||||
@@ -4,7 +4,6 @@ import * as path from "path";
|
||||
import * as core from "@actions/core";
|
||||
import * as io from "@actions/io";
|
||||
import * as semver from "semver";
|
||||
import { v4 as uuidV4 } from "uuid";
|
||||
|
||||
import { Action, ActionState, runInActions } from "./action-common";
|
||||
import {
|
||||
@@ -255,11 +254,6 @@ async function run(
|
||||
);
|
||||
const repositoryProperties = repositoryPropertiesResult.orElse({});
|
||||
|
||||
// Create a unique identifier for this run.
|
||||
const jobRunUuid = uuidV4();
|
||||
logger.info(`Job run UUID is ${jobRunUuid}.`);
|
||||
core.exportVariable(EnvVar.JOB_RUN_UUID, jobRunUuid);
|
||||
|
||||
core.exportVariable(EnvVar.INIT_ACTION_HAS_RUN, "true");
|
||||
|
||||
// path.resolve() respects the intended semantics of source-root. If
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as core from "@actions/core";
|
||||
import { v4 as uuidV4 } from "uuid";
|
||||
|
||||
import { Action, ActionState, runInActions } from "./action-common";
|
||||
import {
|
||||
@@ -95,7 +94,7 @@ async function sendCompletedStatusReport(
|
||||
|
||||
/** The main behaviour of this action. */
|
||||
async function run(
|
||||
actionState: ActionState<["Base", "Logger", "Actions"]>,
|
||||
actionState: ActionState<["Base", "Logger", "Env", "Actions"]>,
|
||||
): Promise<void> {
|
||||
// To capture errors appropriately, keep as much code within the try-catch as
|
||||
// possible, and only use safe functions outside.
|
||||
@@ -140,10 +139,6 @@ async function run(
|
||||
|
||||
const actionStateWithFeatures = { ...actionState, features };
|
||||
|
||||
const jobRunUuid = uuidV4();
|
||||
logger.info(`Job run UUID is ${jobRunUuid}.`);
|
||||
core.exportVariable(EnvVar.JOB_RUN_UUID, jobRunUuid);
|
||||
|
||||
const statusReportBase = await createStatusReportBase(
|
||||
ActionName.SetupCodeQL,
|
||||
"starting",
|
||||
|
||||
@@ -3,11 +3,12 @@ import * as path from "path";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import { Action, ActionState, runInActions } from "./action-common";
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import { getGitHubVersion } from "./api-client";
|
||||
import { FeatureEnablement, initFeatures } from "./feature-flags";
|
||||
import { BuiltInLanguage, parseBuiltInLanguage } from "./languages";
|
||||
import { getActionsLogger, Logger } from "./logging";
|
||||
import { Logger } from "./logging";
|
||||
import { getRepositoryNwo } from "./repository";
|
||||
import {
|
||||
credentialToStr,
|
||||
@@ -23,14 +24,14 @@ import {
|
||||
import { generateCertificateAuthority } from "./start-proxy/ca";
|
||||
import { checkProxyEnvironment } from "./start-proxy/environment";
|
||||
import { checkConnections } from "./start-proxy/reachability";
|
||||
import { ActionName, sendUnhandledErrorStatusReport } from "./status-report";
|
||||
import { ActionName } from "./status-report";
|
||||
import * as util from "./util";
|
||||
|
||||
async function run(startedAt: Date) {
|
||||
async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
|
||||
// To capture errors appropriately, keep as much code within the try-catch as
|
||||
// possible, and only use safe functions outside.
|
||||
|
||||
const logger = getActionsLogger();
|
||||
const startedAt = action.startedAt;
|
||||
const logger = action.logger;
|
||||
let features: FeatureEnablement | undefined;
|
||||
let language: BuiltInLanguage | undefined;
|
||||
|
||||
@@ -122,21 +123,15 @@ async function run(startedAt: Date) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function runWrapper() {
|
||||
const startedAt = new Date();
|
||||
const logger = getActionsLogger();
|
||||
/** Defines the `start-proxy` Action. */
|
||||
const startProxyAction: Action = {
|
||||
name: ActionName.StartProxy,
|
||||
run,
|
||||
transformTelemetryError: getSafeErrorMessage,
|
||||
};
|
||||
|
||||
try {
|
||||
await run(startedAt);
|
||||
} catch (error) {
|
||||
core.setFailed(`start-proxy action failed: ${util.getErrorMessage(error)}`);
|
||||
await sendUnhandledErrorStatusReport(
|
||||
ActionName.StartProxy,
|
||||
startedAt,
|
||||
getSafeErrorMessage(util.wrapError(error)),
|
||||
logger,
|
||||
);
|
||||
}
|
||||
export async function runWrapper() {
|
||||
await runInActions(startProxyAction);
|
||||
}
|
||||
|
||||
async function startProxy(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import test from "ava";
|
||||
import * as sinon from "sinon";
|
||||
import * as uuid from "uuid";
|
||||
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import { Config } from "./config-utils";
|
||||
@@ -14,6 +15,7 @@ import {
|
||||
createStatusReportBase,
|
||||
getActionsStatus,
|
||||
getRegistryTypesFromEnv,
|
||||
getJobUUID,
|
||||
InitStatusReport,
|
||||
InitWithConfigStatusReport,
|
||||
} from "./status-report";
|
||||
@@ -24,6 +26,7 @@ import {
|
||||
makeMacro,
|
||||
getTestEnv,
|
||||
RecordingLogger,
|
||||
callee,
|
||||
} from "./testing-utils";
|
||||
import { BuildMode, ConfigurationError, withTmpDir, wrapError } from "./util";
|
||||
|
||||
@@ -84,6 +87,43 @@ test("getRegistryTypesFromEnv - returns undefined if the env var is unexpected J
|
||||
);
|
||||
});
|
||||
|
||||
test("getJobUUID - generates valid UUIDs", async (t) => {
|
||||
await callee(getJobUUID)
|
||||
.withArgs()
|
||||
.logs(t, "Job run UUID is ")
|
||||
.hasEnv(t, (val) => {
|
||||
return {
|
||||
[EnvVar.JOB_RUN_UUID]: val,
|
||||
};
|
||||
})
|
||||
.passes((val) => {
|
||||
t.true(uuid.validate(val));
|
||||
});
|
||||
});
|
||||
|
||||
test("getJobUUID - retrieves existing job UUIDs", async (t) => {
|
||||
const existingJobUuid = uuid.v4();
|
||||
await callee(getJobUUID)
|
||||
.withArgs()
|
||||
.withEnv((env) => {
|
||||
env.set(EnvVar.JOB_RUN_UUID, existingJobUuid);
|
||||
})
|
||||
.logs(t, `Existing job run UUID is ${existingJobUuid}.`)
|
||||
.passes(t.deepEqual, existingJobUuid);
|
||||
});
|
||||
|
||||
test("getJobUUID - doesn't retrieve invalid UUIDs", async (t) => {
|
||||
const existingJobUuid = "not-a-uuid";
|
||||
await callee(getJobUUID)
|
||||
.withArgs()
|
||||
.withEnv((env) => {
|
||||
env.set(EnvVar.JOB_RUN_UUID, existingJobUuid);
|
||||
})
|
||||
.logs(t, `Job run UUID is `)
|
||||
.notLogs(t, `Existing job run UUID is ${existingJobUuid}.`)
|
||||
.passes(t.notDeepEqual, existingJobUuid);
|
||||
});
|
||||
|
||||
function setupEnvironmentAndStub(tmpDir: string) {
|
||||
setupActionsVars(tmpDir, tmpDir, {
|
||||
GITHUB_EVENT_NAME: "dynamic",
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import * as os from "os";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
import * as uuid from "uuid";
|
||||
|
||||
import type { ActionState } from "./action-common";
|
||||
import {
|
||||
getWorkflowEventName,
|
||||
getOptionalInput,
|
||||
@@ -61,6 +63,30 @@ export function getDisplayActionName(actionName: ActionName): string {
|
||||
return actionName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Either creates a UUIDv4 for the analysis or retrieves an existing one from the
|
||||
* environment and returns it.
|
||||
* If a new UUID is generated, it is also exported as an environment variable.
|
||||
*/
|
||||
export function getJobUUID(
|
||||
action: ActionState<["Logger", "ReadOnlyEnv", "Actions"]>,
|
||||
) {
|
||||
// Check if we already have a UUID for the analysis and return it if so.
|
||||
const existingJobRunUuid = action.env.getOptional(EnvVar.JOB_RUN_UUID);
|
||||
|
||||
if (existingJobRunUuid !== undefined && uuid.validate(existingJobRunUuid)) {
|
||||
action.logger.info(`Existing job run UUID is ${existingJobRunUuid}.`);
|
||||
return existingJobRunUuid;
|
||||
}
|
||||
|
||||
// Otherwise generate a new UUID.
|
||||
const jobRunUuid = uuid.v4();
|
||||
action.logger.info(`Job run UUID is ${jobRunUuid}.`);
|
||||
|
||||
action.actions.exportVariable(EnvVar.JOB_RUN_UUID, jobRunUuid);
|
||||
return jobRunUuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns a boolean indicating whether the analysis is considered to be first party.
|
||||
*
|
||||
|
||||
@@ -34,11 +34,14 @@ import { ActionName } from "./status-report";
|
||||
import {
|
||||
DEFAULT_DEBUG_ARTIFACT_NAME,
|
||||
DEFAULT_DEBUG_DATABASE_NAME,
|
||||
Failure,
|
||||
getEnv,
|
||||
GitHubVariant,
|
||||
GitHubVersion,
|
||||
HTTPError,
|
||||
resetCachedCodeQlVersion,
|
||||
Result,
|
||||
Success,
|
||||
} from "./util";
|
||||
|
||||
export const SAMPLE_DOTCOM_API_DETAILS = {
|
||||
@@ -182,13 +185,32 @@ export function getTestEnv(testEnv: NodeJS.ProcessEnv = {}): Env {
|
||||
return getEnv(testEnv);
|
||||
}
|
||||
|
||||
/** An implementation of `ActionsEnv` for use in tests. */
|
||||
class TestActionsEnv implements ActionsEnv {
|
||||
constructor(private readonly env: Env) {}
|
||||
|
||||
public clone(env: Env): this {
|
||||
return Object.create(this, { env: { value: env } }) as this;
|
||||
}
|
||||
|
||||
public getRequiredInput(name: string): string {
|
||||
throw new Error(`Input required and not supplied: ${name}`);
|
||||
}
|
||||
|
||||
public getOptionalInput(_name: string): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public exportVariable(name: string, value: string): void {
|
||||
this.env.set(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an `ActionsEnv` instance for use in tests.
|
||||
*/
|
||||
export function getTestActionsEnv(): ActionsEnv {
|
||||
return {
|
||||
getOptionalInput: () => undefined,
|
||||
};
|
||||
export function getTestActionsEnv(env: Env): TestActionsEnv {
|
||||
return new TestActionsEnv(env);
|
||||
}
|
||||
|
||||
/** For testing purposes, we make all available state features accessible in `TestEnv`. */
|
||||
@@ -206,12 +228,13 @@ type AllState = [
|
||||
export function initAllState(
|
||||
overrides?: Partial<ActionState<AllState>>,
|
||||
): ActionState<AllState> {
|
||||
const env = getTestEnv();
|
||||
return {
|
||||
name: ActionName.Init,
|
||||
startedAt: new Date(),
|
||||
logger: new RecordingLogger(),
|
||||
env: getTestEnv(),
|
||||
actions: getTestActionsEnv(),
|
||||
env,
|
||||
actions: getTestActionsEnv(env),
|
||||
apiClient: github.getOctokit("123"),
|
||||
features: createFeatures([]),
|
||||
...overrides,
|
||||
@@ -222,9 +245,13 @@ type DelayedCheck<
|
||||
Args extends readonly any[],
|
||||
R,
|
||||
Fs extends ReadonlyArray<AllState[number]>,
|
||||
> = (env: Readonly<BaseEnvBuilder<Args, R, Fs>>) => Promise<any>;
|
||||
> = (
|
||||
env: Readonly<BaseEnvBuilder<Args, R, Fs>>,
|
||||
result: Result<Awaited<R>, ThrownError<ErrorConstructor | Error>>,
|
||||
) => Promise<any>;
|
||||
|
||||
export type ValueOrMutation<T> = T | ((val: T) => void);
|
||||
export type Mutation<T> = (val: T) => void;
|
||||
export type ValueOrMutation<T> = T | Mutation<T>;
|
||||
|
||||
/**
|
||||
* Wraps a function that accepts an `ActionState` for testing in different environments.
|
||||
@@ -236,6 +263,7 @@ abstract class BaseEnvBuilder<
|
||||
> {
|
||||
protected readonly fn: (state: ActionState<Fs>, ...args: Args) => R;
|
||||
private logger: RecordingLogger;
|
||||
private actions: TestActionsEnv;
|
||||
protected state: ActionState<AllState>;
|
||||
protected checks: Array<DelayedCheck<Args, R, Fs>>;
|
||||
|
||||
@@ -245,15 +273,26 @@ abstract class BaseEnvBuilder<
|
||||
) {
|
||||
this.fn = fn;
|
||||
this.logger = new RecordingLogger();
|
||||
this.state =
|
||||
cloneFrom !== undefined
|
||||
? ({
|
||||
...cloneFrom.state,
|
||||
env: cloneFrom.state.env.clone(),
|
||||
actions: Object.create(cloneFrom.state.actions),
|
||||
logger: this.logger,
|
||||
} satisfies ActionState<AllState>)
|
||||
: initAllState({ logger: this.logger });
|
||||
|
||||
if (cloneFrom !== undefined) {
|
||||
const env = cloneFrom.state.env.clone();
|
||||
this.actions = cloneFrom.actions.clone(env);
|
||||
this.state = {
|
||||
...cloneFrom.state,
|
||||
env,
|
||||
actions: this.actions,
|
||||
logger: this.logger,
|
||||
} satisfies ActionState<AllState>;
|
||||
} else {
|
||||
const env = getTestEnv();
|
||||
this.actions = getTestActionsEnv(env);
|
||||
this.state = initAllState({
|
||||
logger: this.logger,
|
||||
env,
|
||||
actions: this.actions,
|
||||
});
|
||||
}
|
||||
|
||||
this.checks = [...(cloneFrom?.checks ?? [])];
|
||||
}
|
||||
|
||||
@@ -320,13 +359,10 @@ abstract class BaseEnvBuilder<
|
||||
return result;
|
||||
}
|
||||
|
||||
public withActions(arg: ValueOrMutation<ActionsEnv>): this {
|
||||
/** Applies `fn` to the `ActionsEnv`. */
|
||||
public withActions(fn: Mutation<ActionsEnv>): this {
|
||||
const result = this.clone();
|
||||
if (typeof arg === "function") {
|
||||
arg(result.state.actions);
|
||||
} else {
|
||||
result.state.actions = arg;
|
||||
}
|
||||
fn(result.state.actions);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -342,6 +378,28 @@ abstract class BaseEnvBuilder<
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a delayed check that the environment variables returned by `fn`
|
||||
* are present in the environment after the main assertion passes.
|
||||
*/
|
||||
public hasEnv(
|
||||
t: ExecutionContext<unknown>,
|
||||
fn: (
|
||||
value: Awaited<R> | undefined,
|
||||
error: ThrownError<ErrorConstructor | Error> | undefined,
|
||||
) => Record<string, string | undefined>,
|
||||
): this {
|
||||
const result = this.clone();
|
||||
result.checks.push(async (env, r) => {
|
||||
const value = r.orElse(undefined);
|
||||
const error = r.isFailure() ? r.value : undefined;
|
||||
const expected = fn(value, error);
|
||||
|
||||
t.like(env.getState().env.get(), expected);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a delayed check that `messages` are not logged. The check will be
|
||||
* performed after the main assertion passes.
|
||||
@@ -439,7 +497,7 @@ class CallableEnvBuilder<
|
||||
|
||||
// Run other delayed checks.
|
||||
for (const delayedCheck of this.checks) {
|
||||
await delayedCheck(this);
|
||||
await delayedCheck(this, new Success(result));
|
||||
}
|
||||
|
||||
// Return the results of the function call and the main assertion.
|
||||
@@ -465,7 +523,7 @@ class CallableEnvBuilder<
|
||||
|
||||
// Run other delayed checks.
|
||||
for (const delayedCheck of this.checks) {
|
||||
await delayedCheck(this);
|
||||
await delayedCheck(this, new Failure(error));
|
||||
}
|
||||
|
||||
// Return the error.
|
||||
|
||||
Reference in New Issue
Block a user