mirror of
https://github.com/github/codeql-action.git
synced 2026-05-12 00:30:10 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 26812c842e | |||
| 6d90f4c71e | |||
| 37f3bfc967 | |||
| b30d90c496 | |||
| 503c5b9421 | |||
| c59e24e20a | |||
| 7cbb19ece7 |
Generated
+1026
-2123
File diff suppressed because it is too large
Load Diff
Generated
+2009
-3150
File diff suppressed because it is too large
Load Diff
Generated
+21
-23
@@ -103609,32 +103609,30 @@ function joinAtMost(array, separator, limit) {
|
||||
}
|
||||
return array.join(separator);
|
||||
}
|
||||
var Success = class {
|
||||
constructor(value) {
|
||||
var Result = class _Result {
|
||||
constructor(_ok, value) {
|
||||
this._ok = _ok;
|
||||
this.value = value;
|
||||
}
|
||||
/** Creates a success result. */
|
||||
static success(value) {
|
||||
return new _Result(true, value);
|
||||
}
|
||||
/** Creates a failure result. */
|
||||
static failure(value) {
|
||||
return new _Result(false, value);
|
||||
}
|
||||
/** Whether this result represents a success. */
|
||||
isSuccess() {
|
||||
return true;
|
||||
return this._ok;
|
||||
}
|
||||
/** Whether this result represents a failure. */
|
||||
isFailure() {
|
||||
return false;
|
||||
}
|
||||
orElse(_defaultValue) {
|
||||
return this.value;
|
||||
}
|
||||
};
|
||||
var Failure = class {
|
||||
constructor(value) {
|
||||
this.value = value;
|
||||
}
|
||||
isSuccess() {
|
||||
return false;
|
||||
}
|
||||
isFailure() {
|
||||
return true;
|
||||
return !this._ok;
|
||||
}
|
||||
/** Get the value if this is a success, or return the default value if this is a failure. */
|
||||
orElse(defaultValue) {
|
||||
return defaultValue;
|
||||
return this.isSuccess() ? this.value : defaultValue;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -109784,23 +109782,23 @@ async function loadRepositoryProperties(repositoryNwo, gitHubVersion, features,
|
||||
logger.debug(
|
||||
"Skipping loading repository properties because the repository is owned by a user and therefore cannot have repository properties."
|
||||
);
|
||||
return new Success({});
|
||||
return Result.success({});
|
||||
}
|
||||
if (!await features.getValue("use_repository_properties_v2" /* UseRepositoryProperties */)) {
|
||||
logger.debug(
|
||||
"Skipping loading repository properties because the UseRepositoryProperties feature flag is disabled."
|
||||
);
|
||||
return new Success({});
|
||||
return Result.success({});
|
||||
}
|
||||
try {
|
||||
return new Success(
|
||||
return Result.success(
|
||||
await loadPropertiesFromApi(gitHubVersion, logger, repositoryNwo)
|
||||
);
|
||||
} catch (error3) {
|
||||
logger.warning(
|
||||
`Failed to load repository properties: ${getErrorMessage(error3)}`
|
||||
);
|
||||
return new Failure(error3);
|
||||
return Result.failure(error3);
|
||||
}
|
||||
}
|
||||
function getTrapCachingEnabled() {
|
||||
|
||||
Generated
+2355
-2388
File diff suppressed because it is too large
Load Diff
Generated
+3309
-3287
File diff suppressed because it is too large
Load Diff
@@ -363,7 +363,9 @@ async function run(startedAt: Date) {
|
||||
|
||||
uploadResults = await postProcessAndUploadSarif(
|
||||
logger,
|
||||
config.tempDir,
|
||||
features,
|
||||
async () => codeql,
|
||||
uploadKind,
|
||||
checkoutPath,
|
||||
outputDir,
|
||||
|
||||
@@ -602,6 +602,8 @@ async function testFailedSarifUpload(
|
||||
}
|
||||
t.true(
|
||||
uploadFiles.calledOnceWith(
|
||||
sinon.match.string,
|
||||
codeqlObject,
|
||||
sinon.match.string,
|
||||
sinon.match.string,
|
||||
category,
|
||||
|
||||
@@ -107,6 +107,8 @@ async function maybeUploadFailedSarif(
|
||||
|
||||
logger.info(`Uploading failed SARIF file ${sarifFile}`);
|
||||
const uploadResult = await uploadLib.uploadFiles(
|
||||
config.tempDir,
|
||||
codeql,
|
||||
sarifFile,
|
||||
checkoutPath,
|
||||
category,
|
||||
|
||||
+4
-6
@@ -96,8 +96,6 @@ import {
|
||||
GitHubVersion,
|
||||
Result,
|
||||
getOptionalEnvVar,
|
||||
Success,
|
||||
Failure,
|
||||
} from "./util";
|
||||
import { checkWorkflow } from "./workflow";
|
||||
|
||||
@@ -836,25 +834,25 @@ async function loadRepositoryProperties(
|
||||
"Skipping loading repository properties because the repository is owned by a user and " +
|
||||
"therefore cannot have repository properties.",
|
||||
);
|
||||
return new Success({});
|
||||
return Result.success({});
|
||||
}
|
||||
|
||||
if (!(await features.getValue(Feature.UseRepositoryProperties))) {
|
||||
logger.debug(
|
||||
"Skipping loading repository properties because the UseRepositoryProperties feature flag is disabled.",
|
||||
);
|
||||
return new Success({});
|
||||
return Result.success({});
|
||||
}
|
||||
|
||||
try {
|
||||
return new Success(
|
||||
return Result.success(
|
||||
await loadPropertiesFromApi(gitHubVersion, logger, repositoryNwo),
|
||||
);
|
||||
} catch (error) {
|
||||
logger.warning(
|
||||
`Failed to load repository properties: ${getErrorMessage(error)}`,
|
||||
);
|
||||
return new Failure(error);
|
||||
return Result.failure(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+42
-59
@@ -157,8 +157,8 @@ export interface LoggedMessage {
|
||||
|
||||
export class RecordingLogger implements Logger {
|
||||
messages: LoggedMessage[] = [];
|
||||
readonly groups: string[] = [];
|
||||
readonly unfinishedGroups: Set<string> = new Set();
|
||||
groups: string[] = [];
|
||||
unfinishedGroups: Set<string> = new Set();
|
||||
private currentGroup: string | undefined = undefined;
|
||||
|
||||
constructor(private readonly logToConsole: boolean = true) {}
|
||||
@@ -172,19 +172,6 @@ export class RecordingLogger implements Logger {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the logged messages contain `messageOrRegExp`.
|
||||
*
|
||||
* If `messageOrRegExp` is a string, this function returns true as long as
|
||||
* `messageOrRegExp` appears as part of one of the `messages`.
|
||||
*
|
||||
* If `messageOrRegExp` is a regular expression, this function returns true as long as
|
||||
* one of the `messages` matches `messageOrRegExp`.
|
||||
*/
|
||||
hasMessage(messageOrRegExp: string | RegExp): boolean {
|
||||
return hasLoggedMessage(this.messages, messageOrRegExp);
|
||||
}
|
||||
|
||||
isDebug() {
|
||||
return true;
|
||||
}
|
||||
@@ -223,37 +210,41 @@ export function getRecordingLogger(
|
||||
messages: LoggedMessage[],
|
||||
{ logToConsole }: { logToConsole?: boolean } = { logToConsole: true },
|
||||
): Logger {
|
||||
const logger = new RecordingLogger(logToConsole);
|
||||
logger.messages = messages;
|
||||
return logger;
|
||||
return {
|
||||
debug: (message: string) => {
|
||||
messages.push({ type: "debug", message });
|
||||
if (logToConsole) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug(message);
|
||||
}
|
||||
},
|
||||
info: (message: string) => {
|
||||
messages.push({ type: "info", message });
|
||||
if (logToConsole) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(message);
|
||||
}
|
||||
},
|
||||
warning: (message: string | Error) => {
|
||||
messages.push({ type: "warning", message });
|
||||
if (logToConsole) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(message);
|
||||
}
|
||||
},
|
||||
error: (message: string | Error) => {
|
||||
messages.push({ type: "error", message });
|
||||
if (logToConsole) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(message);
|
||||
}
|
||||
},
|
||||
isDebug: () => true,
|
||||
startGroup: () => undefined,
|
||||
endGroup: () => undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether `messages` contains `messageOrRegExp`.
|
||||
*
|
||||
* If `messageOrRegExp` is a string, this function returns true as long as
|
||||
* `messageOrRegExp` appears as part of one of the `messages`.
|
||||
*
|
||||
* If `messageOrRegExp` is a regular expression, this function returns true as long as
|
||||
* one of the `messages` matches `messageOrRegExp`.
|
||||
*/
|
||||
function hasLoggedMessage(
|
||||
messages: LoggedMessage[],
|
||||
messageOrRegExp: string | RegExp,
|
||||
): boolean {
|
||||
const check = (val: string) =>
|
||||
typeof messageOrRegExp === "string"
|
||||
? val.includes(messageOrRegExp)
|
||||
: messageOrRegExp.test(val);
|
||||
|
||||
return messages.some(
|
||||
(msg) => typeof msg.message === "string" && check(msg.message),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that `messages` contains all of `expectedMessages`.
|
||||
*/
|
||||
export function checkExpectedLogMessages(
|
||||
t: ExecutionContext<any>,
|
||||
messages: LoggedMessage[],
|
||||
@@ -262,7 +253,13 @@ export function checkExpectedLogMessages(
|
||||
const missingMessages: string[] = [];
|
||||
|
||||
for (const expectedMessage of expectedMessages) {
|
||||
if (!hasLoggedMessage(messages, expectedMessage)) {
|
||||
if (
|
||||
!messages.some(
|
||||
(msg) =>
|
||||
typeof msg.message === "string" &&
|
||||
msg.message.includes(expectedMessage),
|
||||
)
|
||||
) {
|
||||
missingMessages.push(expectedMessage);
|
||||
}
|
||||
}
|
||||
@@ -279,20 +276,6 @@ export function checkExpectedLogMessages(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that `message` should not have been logged to `logger`.
|
||||
*/
|
||||
export function assertNotLogged(
|
||||
t: ExecutionContext<any>,
|
||||
logger: RecordingLogger,
|
||||
message: string | RegExp,
|
||||
) {
|
||||
t.false(
|
||||
logger.hasMessage(message),
|
||||
`'${message}' should not have been logged, but was.`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises a recording logger and calls `body` with it.
|
||||
*
|
||||
|
||||
+60
-42
@@ -11,8 +11,7 @@ import * as actionsUtil from "./actions-util";
|
||||
import * as analyses from "./analyses";
|
||||
import * as api from "./api-client";
|
||||
import { getGitHubVersion, wrapApiConfigurationError } from "./api-client";
|
||||
import { CodeQL, getCodeQL } from "./codeql";
|
||||
import { getConfig } from "./config-utils";
|
||||
import { type CodeQL } from "./codeql";
|
||||
import { readDiffRangesJsonFile } from "./diff-informed-analysis-utils";
|
||||
import { EnvVar } from "./environment";
|
||||
import { FeatureEnablement } from "./feature-flags";
|
||||
@@ -183,6 +182,44 @@ async function shouldDisableCombineSarifFiles(
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises a `CodeQL` instance that we can use to combine SARIF files.
|
||||
*/
|
||||
export async function minimalInitCodeQL(
|
||||
logger: Logger,
|
||||
gitHubVersion: GitHubVersion,
|
||||
features: FeatureEnablement,
|
||||
): Promise<CodeQL> {
|
||||
logger.info(
|
||||
"Initializing CodeQL since the 'init' Action was not called before this step.",
|
||||
);
|
||||
|
||||
const apiDetails = {
|
||||
auth: actionsUtil.getRequiredInput("token"),
|
||||
externalRepoAuth: actionsUtil.getOptionalInput("external-repository-token"),
|
||||
url: getRequiredEnvParam("GITHUB_SERVER_URL"),
|
||||
apiURL: getRequiredEnvParam("GITHUB_API_URL"),
|
||||
};
|
||||
|
||||
const codeQLDefaultVersionInfo = await features.getDefaultCliVersion(
|
||||
gitHubVersion.type,
|
||||
);
|
||||
|
||||
const initCodeQLResult = await initCodeQL(
|
||||
undefined, // There is no tools input on the upload action
|
||||
apiDetails,
|
||||
actionsUtil.getTemporaryDirectory(),
|
||||
gitHubVersion.type,
|
||||
codeQLDefaultVersionInfo,
|
||||
features,
|
||||
logger,
|
||||
);
|
||||
|
||||
return initCodeQLResult.codeql;
|
||||
}
|
||||
|
||||
export type CodeQLGetter = () => Promise<CodeQL>;
|
||||
|
||||
// Takes a list of paths to sarif files and combines them together using the
|
||||
// CLI `github merge-results` command when all SARIF files are produced by
|
||||
// CodeQL. Otherwise, it will fall back to combining the files in the action.
|
||||
@@ -190,8 +227,10 @@ async function shouldDisableCombineSarifFiles(
|
||||
async function combineSarifFilesUsingCLI(
|
||||
sarifFiles: string[],
|
||||
gitHubVersion: GitHubVersion,
|
||||
features: FeatureEnablement,
|
||||
_features: FeatureEnablement,
|
||||
logger: Logger,
|
||||
getCodeQL: CodeQLGetter,
|
||||
tempDir: string,
|
||||
): Promise<SarifFile> {
|
||||
logger.info("Combining SARIF files using the CodeQL CLI");
|
||||
|
||||
@@ -229,45 +268,10 @@ async function combineSarifFilesUsingCLI(
|
||||
return combineSarifFiles(sarifFiles, logger);
|
||||
}
|
||||
|
||||
// Initialize CodeQL, either by using the config file from the 'init' step,
|
||||
// or by initializing it here.
|
||||
let codeQL: CodeQL;
|
||||
let tempDir: string = actionsUtil.getTemporaryDirectory();
|
||||
|
||||
const config = await getConfig(tempDir, logger);
|
||||
if (config !== undefined) {
|
||||
codeQL = await getCodeQL(config.codeQLCmd);
|
||||
tempDir = config.tempDir;
|
||||
} else {
|
||||
logger.info(
|
||||
"Initializing CodeQL since the 'init' Action was not called before this step.",
|
||||
);
|
||||
|
||||
const apiDetails = {
|
||||
auth: actionsUtil.getRequiredInput("token"),
|
||||
externalRepoAuth: actionsUtil.getOptionalInput(
|
||||
"external-repository-token",
|
||||
),
|
||||
url: getRequiredEnvParam("GITHUB_SERVER_URL"),
|
||||
apiURL: getRequiredEnvParam("GITHUB_API_URL"),
|
||||
};
|
||||
|
||||
const codeQLDefaultVersionInfo = await features.getDefaultCliVersion(
|
||||
gitHubVersion.type,
|
||||
);
|
||||
|
||||
const initCodeQLResult = await initCodeQL(
|
||||
undefined, // There is no tools input on the upload action
|
||||
apiDetails,
|
||||
tempDir,
|
||||
gitHubVersion.type,
|
||||
codeQLDefaultVersionInfo,
|
||||
features,
|
||||
logger,
|
||||
);
|
||||
|
||||
codeQL = initCodeQLResult.codeql;
|
||||
}
|
||||
// Obtain a `CodeQL` instance. For `analyze`, this is typically the instance that was used for running the queries.
|
||||
// For `upload-sarif`, this either initialises a new instance or returns a previously initialised one if `getCodeQL`
|
||||
// is called more than once.
|
||||
const codeQL: CodeQL = await getCodeQL();
|
||||
|
||||
const baseTempDir = path.resolve(tempDir, "combined-sarif");
|
||||
fs.mkdirSync(baseTempDir, { recursive: true });
|
||||
@@ -673,6 +677,8 @@ export interface PostProcessingResults {
|
||||
*
|
||||
* @param logger The logger to use.
|
||||
* @param features Information about enabled features.
|
||||
* @param getCodeQL A function to retrieve a `CodeQL` instance.
|
||||
* @param tempPath A path to a temporary directory.
|
||||
* @param checkoutPath The path where the repo was checked out at.
|
||||
* @param sarifPaths The paths of the SARIF files to post-process.
|
||||
* @param category The analysis category.
|
||||
@@ -684,6 +690,8 @@ export interface PostProcessingResults {
|
||||
export async function postProcessSarifFiles(
|
||||
logger: Logger,
|
||||
features: FeatureEnablement,
|
||||
getCodeQL: CodeQLGetter,
|
||||
tempPath: string,
|
||||
checkoutPath: string,
|
||||
sarifPaths: string[],
|
||||
category: string | undefined,
|
||||
@@ -708,6 +716,8 @@ export async function postProcessSarifFiles(
|
||||
gitHubVersion,
|
||||
features,
|
||||
logger,
|
||||
getCodeQL,
|
||||
tempPath,
|
||||
);
|
||||
} else {
|
||||
const sarifPath = sarifPaths[0];
|
||||
@@ -768,6 +778,8 @@ export async function writePostProcessedFiles(
|
||||
* to.
|
||||
*/
|
||||
export async function uploadFiles(
|
||||
tempDir: string,
|
||||
codeql: CodeQL,
|
||||
inputSarifPath: string,
|
||||
checkoutPath: string,
|
||||
category: string | undefined,
|
||||
@@ -781,6 +793,8 @@ export async function uploadFiles(
|
||||
);
|
||||
|
||||
return uploadSpecifiedFiles(
|
||||
tempDir,
|
||||
codeql,
|
||||
sarifPaths,
|
||||
checkoutPath,
|
||||
category,
|
||||
@@ -794,6 +808,8 @@ export async function uploadFiles(
|
||||
* Uploads the given array of SARIF files.
|
||||
*/
|
||||
async function uploadSpecifiedFiles(
|
||||
tempDir: string,
|
||||
codeql: CodeQL,
|
||||
sarifPaths: string[],
|
||||
checkoutPath: string,
|
||||
category: string | undefined,
|
||||
@@ -804,6 +820,8 @@ async function uploadSpecifiedFiles(
|
||||
const processingResults: PostProcessingResults = await postProcessSarifFiles(
|
||||
logger,
|
||||
features,
|
||||
async () => codeql,
|
||||
tempDir,
|
||||
checkoutPath,
|
||||
sarifPaths,
|
||||
category,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import { getActionVersion, getTemporaryDirectory } from "./actions-util";
|
||||
import * as analyses from "./analyses";
|
||||
import { getGitHubVersion } from "./api-client";
|
||||
import { getConfig } from "./config-utils";
|
||||
import { initFeatures } from "./feature-flags";
|
||||
import { Logger, getActionsLogger } from "./logging";
|
||||
import { getRepositoryNwo } from "./repository";
|
||||
@@ -17,7 +17,11 @@ import {
|
||||
isThirdPartyAnalysis,
|
||||
} from "./status-report";
|
||||
import * as upload_lib from "./upload-lib";
|
||||
import { postProcessAndUploadSarif } from "./upload-sarif";
|
||||
import {
|
||||
getOrInitCodeQL,
|
||||
postProcessAndUploadSarif,
|
||||
UploadSarifState,
|
||||
} from "./upload-sarif";
|
||||
import {
|
||||
ConfigurationError,
|
||||
checkActionVersion,
|
||||
@@ -59,12 +63,13 @@ async function run(startedAt: Date) {
|
||||
// possible, and only use safe functions outside.
|
||||
|
||||
const logger = getActionsLogger();
|
||||
const state: UploadSarifState = { cachedCodeQL: undefined };
|
||||
|
||||
try {
|
||||
initializeEnvironment(getActionVersion());
|
||||
initializeEnvironment(actionsUtil.getActionVersion());
|
||||
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
checkActionVersion(getActionVersion(), gitHubVersion);
|
||||
checkActionVersion(actionsUtil.getActionVersion(), gitHubVersion);
|
||||
|
||||
// Make inputs accessible in the `post` step.
|
||||
actionsUtil.persistInputs();
|
||||
@@ -73,7 +78,7 @@ async function run(startedAt: Date) {
|
||||
const features = initFeatures(
|
||||
gitHubVersion,
|
||||
repositoryNwo,
|
||||
getTemporaryDirectory(),
|
||||
actionsUtil.getTemporaryDirectory(),
|
||||
logger,
|
||||
);
|
||||
|
||||
@@ -94,9 +99,20 @@ async function run(startedAt: Date) {
|
||||
const checkoutPath = actionsUtil.getRequiredInput("checkout_path");
|
||||
const category = actionsUtil.getOptionalInput("category");
|
||||
|
||||
// Determine the temporary directory to use. If we are able to read a `Config` from a previous CodeQL Action
|
||||
// step in the job, then use the temporary directory configured there. Otherwise, use our default.
|
||||
let tempDir: string = actionsUtil.getTemporaryDirectory();
|
||||
|
||||
const config = await getConfig(tempDir, logger);
|
||||
if (config !== undefined) {
|
||||
tempDir = config.tempDir;
|
||||
}
|
||||
|
||||
const uploadResults = await postProcessAndUploadSarif(
|
||||
logger,
|
||||
tempDir,
|
||||
features,
|
||||
() => getOrInitCodeQL(state, logger, gitHubVersion, features, config),
|
||||
"always",
|
||||
checkoutPath,
|
||||
sarifPath,
|
||||
|
||||
@@ -5,15 +5,102 @@ import test, { ExecutionContext } from "ava";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import { AnalysisKind, getAnalysisConfig } from "./analyses";
|
||||
import { getCodeQLForTesting } from "./codeql";
|
||||
import * as codeql from "./codeql";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import { createFeatures, setupTests } from "./testing-utils";
|
||||
import { createFeatures, createTestConfig, setupTests } from "./testing-utils";
|
||||
import { UploadResult } from "./upload-lib";
|
||||
import * as uploadLib from "./upload-lib";
|
||||
import { postProcessAndUploadSarif } from "./upload-sarif";
|
||||
import {
|
||||
getOrInitCodeQL,
|
||||
postProcessAndUploadSarif,
|
||||
UploadSarifState,
|
||||
} from "./upload-sarif";
|
||||
import * as util from "./util";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
test("getOrInitCodeQL - gets cached CodeQL instance when available", async (t) => {
|
||||
const cachedCodeQL = await getCodeQLForTesting();
|
||||
const getCodeQL = sinon.stub(codeql, "getCodeQL").resolves(undefined);
|
||||
const minimalInitCodeQL = sinon
|
||||
.stub(uploadLib, "minimalInitCodeQL")
|
||||
.resolves(undefined);
|
||||
|
||||
const result = await getOrInitCodeQL(
|
||||
{ cachedCodeQL },
|
||||
getRunnerLogger(true),
|
||||
{ type: util.GitHubVariant.GHES, version: "3.0" },
|
||||
createFeatures([]),
|
||||
undefined,
|
||||
);
|
||||
|
||||
// Neither of the two functions to get a CodeQL instance were called.
|
||||
t.true(getCodeQL.notCalled);
|
||||
t.true(minimalInitCodeQL.notCalled);
|
||||
|
||||
// But we have an instance that refers to the same object as the one we put into the state.
|
||||
t.truthy(result);
|
||||
t.is(result, cachedCodeQL);
|
||||
});
|
||||
|
||||
test("getOrInitCodeQL - uses minimalInitCodeQL when there's no config", async (t) => {
|
||||
const newInstance = await getCodeQLForTesting();
|
||||
const getCodeQL = sinon.stub(codeql, "getCodeQL").resolves(undefined);
|
||||
const minimalInitCodeQL = sinon
|
||||
.stub(uploadLib, "minimalInitCodeQL")
|
||||
.resolves(newInstance);
|
||||
|
||||
const state: UploadSarifState = { cachedCodeQL: undefined };
|
||||
const result = await getOrInitCodeQL(
|
||||
state,
|
||||
getRunnerLogger(true),
|
||||
{ type: util.GitHubVariant.GHES, version: "3.0" },
|
||||
createFeatures([]),
|
||||
undefined,
|
||||
);
|
||||
|
||||
// Check that the right function was called.
|
||||
t.true(getCodeQL.notCalled);
|
||||
t.true(minimalInitCodeQL.calledOnce);
|
||||
|
||||
// And that we received the instance that we expected.
|
||||
t.truthy(result);
|
||||
t.is(result, newInstance);
|
||||
|
||||
// And that it was cached.
|
||||
t.is(state.cachedCodeQL, newInstance);
|
||||
});
|
||||
|
||||
test("getOrInitCodeQL - uses getCodeQL when there's a config", async (t) => {
|
||||
const newInstance = await getCodeQLForTesting();
|
||||
const getCodeQL = sinon.stub(codeql, "getCodeQL").resolves(newInstance);
|
||||
const minimalInitCodeQL = sinon
|
||||
.stub(uploadLib, "minimalInitCodeQL")
|
||||
.resolves(undefined);
|
||||
const config = createTestConfig({});
|
||||
|
||||
const state: UploadSarifState = { cachedCodeQL: undefined };
|
||||
const result = await getOrInitCodeQL(
|
||||
state,
|
||||
getRunnerLogger(true),
|
||||
{ type: util.GitHubVariant.GHES, version: "3.0" },
|
||||
createFeatures([]),
|
||||
config,
|
||||
);
|
||||
|
||||
// Check that the right function was called.
|
||||
t.true(getCodeQL.calledOnce);
|
||||
t.true(minimalInitCodeQL.notCalled);
|
||||
|
||||
// And that we received the instance that we expected.
|
||||
t.truthy(result);
|
||||
t.is(result, newInstance);
|
||||
|
||||
// And that it was cached.
|
||||
t.is(state.cachedCodeQL, newInstance);
|
||||
});
|
||||
|
||||
interface UploadSarifExpectedResult {
|
||||
uploadResult?: UploadResult;
|
||||
expectedFiles?: string[];
|
||||
@@ -31,6 +118,8 @@ function mockPostProcessSarifFiles() {
|
||||
sinon.match.any,
|
||||
sinon.match.any,
|
||||
sinon.match.any,
|
||||
sinon.match.any,
|
||||
sinon.match.any,
|
||||
analysisConfig,
|
||||
)
|
||||
.resolves({ sarif: { runs: [] }, analysisKey: "", environment: "" });
|
||||
@@ -73,7 +162,9 @@ const postProcessAndUploadSarifMacro = test.macro({
|
||||
|
||||
const actual = await postProcessAndUploadSarif(
|
||||
logger,
|
||||
tempDir,
|
||||
features,
|
||||
async () => getCodeQLForTesting(),
|
||||
"always",
|
||||
"",
|
||||
testPath,
|
||||
@@ -90,6 +181,8 @@ const postProcessAndUploadSarifMacro = test.macro({
|
||||
postProcessSarifFiles.calledWith(
|
||||
logger,
|
||||
features,
|
||||
sinon.match.func,
|
||||
tempDir,
|
||||
sinon.match.any,
|
||||
analysisKindResult.expectedFiles?.map(toFullPath) ??
|
||||
fullSarifPaths,
|
||||
@@ -221,7 +314,9 @@ test("postProcessAndUploadSarif doesn't upload if upload is disabled", async (t)
|
||||
|
||||
const actual = await postProcessAndUploadSarif(
|
||||
logger,
|
||||
tempDir,
|
||||
features,
|
||||
() => getCodeQLForTesting(),
|
||||
"never",
|
||||
"",
|
||||
tempDir,
|
||||
@@ -248,7 +343,9 @@ test("postProcessAndUploadSarif writes post-processed SARIF files if output dire
|
||||
const postProcessedOutPath = path.join(tempDir, "post-processed");
|
||||
const actual = await postProcessAndUploadSarif(
|
||||
logger,
|
||||
tempDir,
|
||||
features,
|
||||
() => getCodeQLForTesting(),
|
||||
"never",
|
||||
"",
|
||||
tempDir,
|
||||
|
||||
+42
-1
@@ -1,20 +1,57 @@
|
||||
import { UploadKind } from "./actions-util";
|
||||
import * as analyses from "./analyses";
|
||||
import type { CodeQL } from "./codeql";
|
||||
import * as codeql from "./codeql";
|
||||
import { Config } from "./config-utils";
|
||||
import { FeatureEnablement } from "./feature-flags";
|
||||
import { Logger } from "./logging";
|
||||
import * as upload_lib from "./upload-lib";
|
||||
import { unsafeEntriesInvariant } from "./util";
|
||||
import { GitHubVersion, unsafeEntriesInvariant } from "./util";
|
||||
|
||||
export interface UploadSarifState {
|
||||
/** The cached `CodeQL` instance, if any. */
|
||||
cachedCodeQL: CodeQL | undefined;
|
||||
}
|
||||
|
||||
// Maps analysis kinds to SARIF IDs.
|
||||
export type UploadSarifResults = Partial<
|
||||
Record<analyses.AnalysisKind, upload_lib.UploadResult>
|
||||
>;
|
||||
|
||||
/** Get or initialise a `CodeQL` instance for use by the `upload-sarif` action. */
|
||||
export async function getOrInitCodeQL(
|
||||
actionState: UploadSarifState,
|
||||
logger: Logger,
|
||||
gitHubVersion: GitHubVersion,
|
||||
features: FeatureEnablement,
|
||||
config: Config | undefined,
|
||||
): Promise<CodeQL> {
|
||||
// Return the cached instance, if we have one.
|
||||
if (actionState.cachedCodeQL !== undefined) return actionState.cachedCodeQL;
|
||||
|
||||
// If we have been able to load a `Config` from an earlier CodeQL Action step in the job,
|
||||
// then use the CodeQL executable that we have used previously. Otherwise, initialise the
|
||||
// CLI specifically for `upload-sarif`. Either way, we cache the instance.
|
||||
if (config !== undefined) {
|
||||
actionState.cachedCodeQL = await codeql.getCodeQL(config.codeQLCmd);
|
||||
} else {
|
||||
actionState.cachedCodeQL = await upload_lib.minimalInitCodeQL(
|
||||
logger,
|
||||
gitHubVersion,
|
||||
features,
|
||||
);
|
||||
}
|
||||
|
||||
return actionState.cachedCodeQL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds SARIF files in `sarifPath`, post-processes them, and uploads them to the appropriate services.
|
||||
*
|
||||
* @param logger The logger to use.
|
||||
* @param tempPath The path to the temporary directory.
|
||||
* @param features Information about enabled features.
|
||||
* @param getCodeQL A function to retrieve a `CodeQL` instance.
|
||||
* @param uploadKind The kind of upload that is requested.
|
||||
* @param checkoutPath The path where the repository was checked out at.
|
||||
* @param sarifPath The path to the file or directory to upload.
|
||||
@@ -25,7 +62,9 @@ export type UploadSarifResults = Partial<
|
||||
*/
|
||||
export async function postProcessAndUploadSarif(
|
||||
logger: Logger,
|
||||
tempPath: string,
|
||||
features: FeatureEnablement,
|
||||
getCodeQL: upload_lib.CodeQLGetter,
|
||||
uploadKind: UploadKind,
|
||||
checkoutPath: string,
|
||||
sarifPath: string,
|
||||
@@ -45,6 +84,8 @@ export async function postProcessAndUploadSarif(
|
||||
const postProcessingResults = await upload_lib.postProcessSarifFiles(
|
||||
logger,
|
||||
features,
|
||||
getCodeQL,
|
||||
tempPath,
|
||||
checkoutPath,
|
||||
sarifFiles,
|
||||
category,
|
||||
|
||||
+8
-8
@@ -564,27 +564,27 @@ test("joinAtMost - truncates list if array is > than limit", (t) => {
|
||||
t.false(result.includes("test6"));
|
||||
});
|
||||
|
||||
test("Success creates a success result", (t) => {
|
||||
const result = new util.Success("test value");
|
||||
test("Result.success creates a success result", (t) => {
|
||||
const result = util.Result.success("test value");
|
||||
t.true(result.isSuccess());
|
||||
t.false(result.isFailure());
|
||||
t.is(result.value, "test value");
|
||||
});
|
||||
|
||||
test("Failure creates a failure result", (t) => {
|
||||
test("Result.failure creates a failure result", (t) => {
|
||||
const error = new Error("test error");
|
||||
const result = new util.Failure(error);
|
||||
const result = util.Result.failure(error);
|
||||
t.false(result.isSuccess());
|
||||
t.true(result.isFailure());
|
||||
t.is(result.value, error);
|
||||
});
|
||||
|
||||
test("Success.orElse returns the value for a success result", (t) => {
|
||||
const result = new util.Success("success value");
|
||||
test("Result.orElse returns the value for a success result", (t) => {
|
||||
const result = util.Result.success("success value");
|
||||
t.is(result.orElse("default value"), "success value");
|
||||
});
|
||||
|
||||
test("Failure.orElse returns the default value for a failure result", (t) => {
|
||||
const result = new util.Failure(new Error("test error"));
|
||||
test("Result.orElse returns the default value for a failure result", (t) => {
|
||||
const result = util.Result.failure(new Error("test error"));
|
||||
t.is(result.orElse("default value"), "default value");
|
||||
});
|
||||
|
||||
+30
-39
@@ -1291,51 +1291,42 @@ export function joinAtMost(
|
||||
return array.join(separator);
|
||||
}
|
||||
|
||||
/** An interface representing something that is either a success or a failure. */
|
||||
interface ResultLike<T, E> {
|
||||
/** The value of the result, which can be either a success value or a failure value. */
|
||||
value: T | E;
|
||||
/** A success result. */
|
||||
type Success<T> = Result<T, never>;
|
||||
/** A failure result. */
|
||||
type Failure<E> = Result<never, E>;
|
||||
|
||||
/**
|
||||
* A simple result type representing either a success or a failure.
|
||||
*/
|
||||
export class Result<T, E> {
|
||||
private constructor(
|
||||
private readonly _ok: boolean,
|
||||
public readonly value: T | E,
|
||||
) {}
|
||||
|
||||
/** Creates a success result. */
|
||||
static success<T>(value: T): Success<T> {
|
||||
return new Result(true, value) as Success<T>;
|
||||
}
|
||||
|
||||
/** Creates a failure result. */
|
||||
static failure<E>(value: E): Failure<E> {
|
||||
return new Result(false, value) as Failure<E>;
|
||||
}
|
||||
|
||||
/** Whether this result represents a success. */
|
||||
isSuccess(): this is Success<T>;
|
||||
/** Whether this result represents a failure. */
|
||||
isFailure(): this is Failure<E>;
|
||||
/** Get the value if this is a success, or return the default value if this is a failure. */
|
||||
orElse<U>(defaultValue: U): T | U;
|
||||
}
|
||||
|
||||
/** A simple result type representing either a success or a failure. */
|
||||
export type Result<T, E> = Success<T> | Failure<E>;
|
||||
|
||||
/** A result representing a success. */
|
||||
export class Success<T> implements ResultLike<T, never> {
|
||||
constructor(public readonly value: T) {}
|
||||
|
||||
isSuccess(): this is Success<T> {
|
||||
return true;
|
||||
}
|
||||
|
||||
isFailure(): this is Failure<never> {
|
||||
return false;
|
||||
}
|
||||
|
||||
orElse<U>(_defaultValue: U): T {
|
||||
return this.value;
|
||||
}
|
||||
}
|
||||
|
||||
/** A result representing a failure. */
|
||||
export class Failure<E> implements ResultLike<never, E> {
|
||||
constructor(public readonly value: E) {}
|
||||
|
||||
isSuccess(): this is Success<never> {
|
||||
return false;
|
||||
return this._ok;
|
||||
}
|
||||
|
||||
/** Whether this result represents a failure. */
|
||||
isFailure(): this is Failure<E> {
|
||||
return true;
|
||||
return !this._ok;
|
||||
}
|
||||
|
||||
orElse<U>(defaultValue: U): U {
|
||||
return defaultValue;
|
||||
/** Get the value if this is a success, or return the default value if this is a failure. */
|
||||
orElse<U>(defaultValue: U): T | U {
|
||||
return this.isSuccess() ? this.value : defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user