mirror of
https://github.com/github/codeql-action.git
synced 2026-04-01 17:22:19 +00:00
Run some unit tests in parallel
This commit is contained in:
2
lib/analyze-action-post.js
generated
2
lib/analyze-action-post.js
generated
@@ -45995,7 +45995,7 @@ var require_package = __commonJS({
|
||||
lint: "eslint --report-unused-disable-directives --max-warnings=0 .",
|
||||
"lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif",
|
||||
"lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix",
|
||||
ava: "npm run transpile && ava --serial --verbose",
|
||||
ava: "npm run transpile && ava --verbose",
|
||||
test: "npm run ava -- src/",
|
||||
"test-debug": "npm run test -- --timeout=20m",
|
||||
transpile: "tsc --build --verbose"
|
||||
|
||||
2
lib/analyze-action.js
generated
2
lib/analyze-action.js
generated
@@ -45995,7 +45995,7 @@ var require_package = __commonJS({
|
||||
lint: "eslint --report-unused-disable-directives --max-warnings=0 .",
|
||||
"lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif",
|
||||
"lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix",
|
||||
ava: "npm run transpile && ava --serial --verbose",
|
||||
ava: "npm run transpile && ava --verbose",
|
||||
test: "npm run ava -- src/",
|
||||
"test-debug": "npm run test -- --timeout=20m",
|
||||
transpile: "tsc --build --verbose"
|
||||
|
||||
2
lib/autobuild-action.js
generated
2
lib/autobuild-action.js
generated
@@ -45995,7 +45995,7 @@ var require_package = __commonJS({
|
||||
lint: "eslint --report-unused-disable-directives --max-warnings=0 .",
|
||||
"lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif",
|
||||
"lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix",
|
||||
ava: "npm run transpile && ava --serial --verbose",
|
||||
ava: "npm run transpile && ava --verbose",
|
||||
test: "npm run ava -- src/",
|
||||
"test-debug": "npm run test -- --timeout=20m",
|
||||
transpile: "tsc --build --verbose"
|
||||
|
||||
2
lib/init-action-post.js
generated
2
lib/init-action-post.js
generated
@@ -45995,7 +45995,7 @@ var require_package = __commonJS({
|
||||
lint: "eslint --report-unused-disable-directives --max-warnings=0 .",
|
||||
"lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif",
|
||||
"lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix",
|
||||
ava: "npm run transpile && ava --serial --verbose",
|
||||
ava: "npm run transpile && ava --verbose",
|
||||
test: "npm run ava -- src/",
|
||||
"test-debug": "npm run test -- --timeout=20m",
|
||||
transpile: "tsc --build --verbose"
|
||||
|
||||
2
lib/init-action.js
generated
2
lib/init-action.js
generated
@@ -45995,7 +45995,7 @@ var require_package = __commonJS({
|
||||
lint: "eslint --report-unused-disable-directives --max-warnings=0 .",
|
||||
"lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif",
|
||||
"lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix",
|
||||
ava: "npm run transpile && ava --serial --verbose",
|
||||
ava: "npm run transpile && ava --verbose",
|
||||
test: "npm run ava -- src/",
|
||||
"test-debug": "npm run test -- --timeout=20m",
|
||||
transpile: "tsc --build --verbose"
|
||||
|
||||
2
lib/resolve-environment-action.js
generated
2
lib/resolve-environment-action.js
generated
@@ -45995,7 +45995,7 @@ var require_package = __commonJS({
|
||||
lint: "eslint --report-unused-disable-directives --max-warnings=0 .",
|
||||
"lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif",
|
||||
"lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix",
|
||||
ava: "npm run transpile && ava --serial --verbose",
|
||||
ava: "npm run transpile && ava --verbose",
|
||||
test: "npm run ava -- src/",
|
||||
"test-debug": "npm run test -- --timeout=20m",
|
||||
transpile: "tsc --build --verbose"
|
||||
|
||||
2
lib/setup-codeql-action.js
generated
2
lib/setup-codeql-action.js
generated
@@ -45995,7 +45995,7 @@ var require_package = __commonJS({
|
||||
lint: "eslint --report-unused-disable-directives --max-warnings=0 .",
|
||||
"lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif",
|
||||
"lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix",
|
||||
ava: "npm run transpile && ava --serial --verbose",
|
||||
ava: "npm run transpile && ava --verbose",
|
||||
test: "npm run ava -- src/",
|
||||
"test-debug": "npm run test -- --timeout=20m",
|
||||
transpile: "tsc --build --verbose"
|
||||
|
||||
2
lib/start-proxy-action-post.js
generated
2
lib/start-proxy-action-post.js
generated
@@ -45995,7 +45995,7 @@ var require_package = __commonJS({
|
||||
lint: "eslint --report-unused-disable-directives --max-warnings=0 .",
|
||||
"lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif",
|
||||
"lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix",
|
||||
ava: "npm run transpile && ava --serial --verbose",
|
||||
ava: "npm run transpile && ava --verbose",
|
||||
test: "npm run ava -- src/",
|
||||
"test-debug": "npm run test -- --timeout=20m",
|
||||
transpile: "tsc --build --verbose"
|
||||
|
||||
2
lib/start-proxy-action.js
generated
2
lib/start-proxy-action.js
generated
@@ -45995,7 +45995,7 @@ var require_package = __commonJS({
|
||||
lint: "eslint --report-unused-disable-directives --max-warnings=0 .",
|
||||
"lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif",
|
||||
"lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix",
|
||||
ava: "npm run transpile && ava --serial --verbose",
|
||||
ava: "npm run transpile && ava --verbose",
|
||||
test: "npm run ava -- src/",
|
||||
"test-debug": "npm run test -- --timeout=20m",
|
||||
transpile: "tsc --build --verbose"
|
||||
|
||||
2
lib/upload-lib.js
generated
2
lib/upload-lib.js
generated
@@ -47292,7 +47292,7 @@ var require_package = __commonJS({
|
||||
lint: "eslint --report-unused-disable-directives --max-warnings=0 .",
|
||||
"lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif",
|
||||
"lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix",
|
||||
ava: "npm run transpile && ava --serial --verbose",
|
||||
ava: "npm run transpile && ava --verbose",
|
||||
test: "npm run ava -- src/",
|
||||
"test-debug": "npm run test -- --timeout=20m",
|
||||
transpile: "tsc --build --verbose"
|
||||
|
||||
2
lib/upload-sarif-action-post.js
generated
2
lib/upload-sarif-action-post.js
generated
@@ -45995,7 +45995,7 @@ var require_package = __commonJS({
|
||||
lint: "eslint --report-unused-disable-directives --max-warnings=0 .",
|
||||
"lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif",
|
||||
"lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix",
|
||||
ava: "npm run transpile && ava --serial --verbose",
|
||||
ava: "npm run transpile && ava --verbose",
|
||||
test: "npm run ava -- src/",
|
||||
"test-debug": "npm run test -- --timeout=20m",
|
||||
transpile: "tsc --build --verbose"
|
||||
|
||||
2
lib/upload-sarif-action.js
generated
2
lib/upload-sarif-action.js
generated
@@ -45995,7 +45995,7 @@ var require_package = __commonJS({
|
||||
lint: "eslint --report-unused-disable-directives --max-warnings=0 .",
|
||||
"lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif",
|
||||
"lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix",
|
||||
ava: "npm run transpile && ava --serial --verbose",
|
||||
ava: "npm run transpile && ava --verbose",
|
||||
test: "npm run ava -- src/",
|
||||
"test-debug": "npm run test -- --timeout=20m",
|
||||
transpile: "tsc --build --verbose"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"lint": "eslint --report-unused-disable-directives --max-warnings=0 .",
|
||||
"lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif",
|
||||
"lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix",
|
||||
"ava": "npm run transpile && ava --serial --verbose",
|
||||
"ava": "npm run transpile && ava --verbose",
|
||||
"test": "npm run ava -- src/",
|
||||
"test-debug": "npm run test -- --timeout=20m",
|
||||
"transpile": "tsc --build --verbose"
|
||||
|
||||
@@ -133,75 +133,87 @@ test("getPullRequestBranches() returns undefined with push context", (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("getPullRequestBranches() with Default Setup environment variables", (t) => {
|
||||
withMockedContext({}, () => {
|
||||
withMockedEnv(
|
||||
{
|
||||
CODE_SCANNING_REF: "refs/heads/feature-branch",
|
||||
CODE_SCANNING_BASE_BRANCH: "main",
|
||||
},
|
||||
() => {
|
||||
t.deepEqual(getPullRequestBranches(), {
|
||||
base: "main",
|
||||
head: "refs/heads/feature-branch",
|
||||
});
|
||||
t.is(isAnalyzingPullRequest(), true);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
test.serial(
|
||||
"getPullRequestBranches() with Default Setup environment variables",
|
||||
(t) => {
|
||||
withMockedContext({}, () => {
|
||||
withMockedEnv(
|
||||
{
|
||||
CODE_SCANNING_REF: "refs/heads/feature-branch",
|
||||
CODE_SCANNING_BASE_BRANCH: "main",
|
||||
},
|
||||
() => {
|
||||
t.deepEqual(getPullRequestBranches(), {
|
||||
base: "main",
|
||||
head: "refs/heads/feature-branch",
|
||||
});
|
||||
t.is(isAnalyzingPullRequest(), true);
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("getPullRequestBranches() returns undefined when only CODE_SCANNING_REF is set", (t) => {
|
||||
withMockedContext({}, () => {
|
||||
withMockedEnv(
|
||||
{
|
||||
CODE_SCANNING_REF: "refs/heads/feature-branch",
|
||||
CODE_SCANNING_BASE_BRANCH: undefined,
|
||||
},
|
||||
() => {
|
||||
t.is(getPullRequestBranches(), undefined);
|
||||
t.is(isAnalyzingPullRequest(), false);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
test.serial(
|
||||
"getPullRequestBranches() returns undefined when only CODE_SCANNING_REF is set",
|
||||
(t) => {
|
||||
withMockedContext({}, () => {
|
||||
withMockedEnv(
|
||||
{
|
||||
CODE_SCANNING_REF: "refs/heads/feature-branch",
|
||||
CODE_SCANNING_BASE_BRANCH: undefined,
|
||||
},
|
||||
() => {
|
||||
t.is(getPullRequestBranches(), undefined);
|
||||
t.is(isAnalyzingPullRequest(), false);
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("getPullRequestBranches() returns undefined when only CODE_SCANNING_BASE_BRANCH is set", (t) => {
|
||||
withMockedContext({}, () => {
|
||||
withMockedEnv(
|
||||
{
|
||||
CODE_SCANNING_REF: undefined,
|
||||
CODE_SCANNING_BASE_BRANCH: "main",
|
||||
},
|
||||
() => {
|
||||
t.is(getPullRequestBranches(), undefined);
|
||||
t.is(isAnalyzingPullRequest(), false);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
test.serial(
|
||||
"getPullRequestBranches() returns undefined when only CODE_SCANNING_BASE_BRANCH is set",
|
||||
(t) => {
|
||||
withMockedContext({}, () => {
|
||||
withMockedEnv(
|
||||
{
|
||||
CODE_SCANNING_REF: undefined,
|
||||
CODE_SCANNING_BASE_BRANCH: "main",
|
||||
},
|
||||
() => {
|
||||
t.is(getPullRequestBranches(), undefined);
|
||||
t.is(isAnalyzingPullRequest(), false);
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("getPullRequestBranches() returns undefined when no PR context", (t) => {
|
||||
withMockedContext({}, () => {
|
||||
withMockedEnv(
|
||||
{
|
||||
CODE_SCANNING_REF: undefined,
|
||||
CODE_SCANNING_BASE_BRANCH: undefined,
|
||||
},
|
||||
() => {
|
||||
t.is(getPullRequestBranches(), undefined);
|
||||
t.is(isAnalyzingPullRequest(), false);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
test.serial(
|
||||
"getPullRequestBranches() returns undefined when no PR context",
|
||||
(t) => {
|
||||
withMockedContext({}, () => {
|
||||
withMockedEnv(
|
||||
{
|
||||
CODE_SCANNING_REF: undefined,
|
||||
CODE_SCANNING_BASE_BRANCH: undefined,
|
||||
},
|
||||
() => {
|
||||
t.is(getPullRequestBranches(), undefined);
|
||||
t.is(isAnalyzingPullRequest(), false);
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("initializeEnvironment", (t) => {
|
||||
test.serial("initializeEnvironment", (t) => {
|
||||
initializeEnvironment("1.2.3");
|
||||
t.deepEqual(process.env[EnvVar.VERSION], "1.2.3");
|
||||
});
|
||||
|
||||
test("fixCodeQualityCategory", (t) => {
|
||||
test.serial("fixCodeQualityCategory", (t) => {
|
||||
withMockedEnv(
|
||||
{
|
||||
GITHUB_EVENT_NAME: "dynamic",
|
||||
@@ -249,14 +261,17 @@ test("fixCodeQualityCategory", (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("isDynamicWorkflow() returns true if event name is `dynamic`", (t) => {
|
||||
process.env.GITHUB_EVENT_NAME = "dynamic";
|
||||
t.assert(isDynamicWorkflow());
|
||||
process.env.GITHUB_EVENT_NAME = "push";
|
||||
t.false(isDynamicWorkflow());
|
||||
});
|
||||
test.serial(
|
||||
"isDynamicWorkflow() returns true if event name is `dynamic`",
|
||||
(t) => {
|
||||
process.env.GITHUB_EVENT_NAME = "dynamic";
|
||||
t.assert(isDynamicWorkflow());
|
||||
process.env.GITHUB_EVENT_NAME = "push";
|
||||
t.false(isDynamicWorkflow());
|
||||
},
|
||||
);
|
||||
|
||||
test("isDefaultSetup() returns true when expected", (t) => {
|
||||
test.serial("isDefaultSetup() returns true when expected", (t) => {
|
||||
process.env.GITHUB_EVENT_NAME = "dynamic";
|
||||
process.env[EnvVar.ANALYSIS_KEY] = "dynamic/github-code-scanning";
|
||||
t.assert(isDefaultSetup());
|
||||
|
||||
@@ -50,31 +50,40 @@ test("Parsing analysis kinds requires at least one analysis kind", async (t) =>
|
||||
});
|
||||
});
|
||||
|
||||
test("getAnalysisKinds - returns expected analysis kinds for `analysis-kinds` input", async (t) => {
|
||||
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
|
||||
requiredInputStub
|
||||
.withArgs("analysis-kinds")
|
||||
.returns("code-scanning,code-quality");
|
||||
const result = await getAnalysisKinds(getRunnerLogger(true), true);
|
||||
t.assert(result.includes(AnalysisKind.CodeScanning));
|
||||
t.assert(result.includes(AnalysisKind.CodeQuality));
|
||||
});
|
||||
test.serial(
|
||||
"getAnalysisKinds - returns expected analysis kinds for `analysis-kinds` input",
|
||||
async (t) => {
|
||||
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
|
||||
requiredInputStub
|
||||
.withArgs("analysis-kinds")
|
||||
.returns("code-scanning,code-quality");
|
||||
const result = await getAnalysisKinds(getRunnerLogger(true), true);
|
||||
t.assert(result.includes(AnalysisKind.CodeScanning));
|
||||
t.assert(result.includes(AnalysisKind.CodeQuality));
|
||||
},
|
||||
);
|
||||
|
||||
test("getAnalysisKinds - includes `code-quality` when deprecated `quality-queries` input is used", async (t) => {
|
||||
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
|
||||
requiredInputStub.withArgs("analysis-kinds").returns("code-scanning");
|
||||
const optionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
|
||||
optionalInputStub.withArgs("quality-queries").returns("code-quality");
|
||||
const result = await getAnalysisKinds(getRunnerLogger(true), true);
|
||||
t.assert(result.includes(AnalysisKind.CodeScanning));
|
||||
t.assert(result.includes(AnalysisKind.CodeQuality));
|
||||
});
|
||||
test.serial(
|
||||
"getAnalysisKinds - includes `code-quality` when deprecated `quality-queries` input is used",
|
||||
async (t) => {
|
||||
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
|
||||
requiredInputStub.withArgs("analysis-kinds").returns("code-scanning");
|
||||
const optionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
|
||||
optionalInputStub.withArgs("quality-queries").returns("code-quality");
|
||||
const result = await getAnalysisKinds(getRunnerLogger(true), true);
|
||||
t.assert(result.includes(AnalysisKind.CodeScanning));
|
||||
t.assert(result.includes(AnalysisKind.CodeQuality));
|
||||
},
|
||||
);
|
||||
|
||||
test("getAnalysisKinds - throws if `analysis-kinds` input is invalid", async (t) => {
|
||||
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
|
||||
requiredInputStub.withArgs("analysis-kinds").returns("no-such-thing");
|
||||
await t.throwsAsync(getAnalysisKinds(getRunnerLogger(true), true));
|
||||
});
|
||||
test.serial(
|
||||
"getAnalysisKinds - throws if `analysis-kinds` input is invalid",
|
||||
async (t) => {
|
||||
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
|
||||
requiredInputStub.withArgs("analysis-kinds").returns("no-such-thing");
|
||||
await t.throwsAsync(getAnalysisKinds(getRunnerLogger(true), true));
|
||||
},
|
||||
);
|
||||
|
||||
// Test the compatibility matrix by looping through all analysis kinds.
|
||||
const analysisKinds = Object.values(AnalysisKind);
|
||||
@@ -86,25 +95,31 @@ for (let i = 0; i < analysisKinds.length; i++) {
|
||||
|
||||
if (analysisKind === otherAnalysis) continue;
|
||||
if (compatibilityMatrix[analysisKind].has(otherAnalysis)) {
|
||||
test(`getAnalysisKinds - allows ${analysisKind} with ${otherAnalysis}`, async (t) => {
|
||||
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
|
||||
requiredInputStub
|
||||
.withArgs("analysis-kinds")
|
||||
.returns([analysisKind, otherAnalysis].join(","));
|
||||
const result = await getAnalysisKinds(getRunnerLogger(true), true);
|
||||
t.is(result.length, 2);
|
||||
});
|
||||
test.serial(
|
||||
`getAnalysisKinds - allows ${analysisKind} with ${otherAnalysis}`,
|
||||
async (t) => {
|
||||
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
|
||||
requiredInputStub
|
||||
.withArgs("analysis-kinds")
|
||||
.returns([analysisKind, otherAnalysis].join(","));
|
||||
const result = await getAnalysisKinds(getRunnerLogger(true), true);
|
||||
t.is(result.length, 2);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
test(`getAnalysisKinds - throws if ${analysisKind} is enabled with ${otherAnalysis}`, async (t) => {
|
||||
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
|
||||
requiredInputStub
|
||||
.withArgs("analysis-kinds")
|
||||
.returns([analysisKind, otherAnalysis].join(","));
|
||||
await t.throwsAsync(getAnalysisKinds(getRunnerLogger(true), true), {
|
||||
instanceOf: ConfigurationError,
|
||||
message: `${analysisKind} and ${otherAnalysis} cannot be enabled at the same time`,
|
||||
});
|
||||
});
|
||||
test.serial(
|
||||
`getAnalysisKinds - throws if ${analysisKind} is enabled with ${otherAnalysis}`,
|
||||
async (t) => {
|
||||
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
|
||||
requiredInputStub
|
||||
.withArgs("analysis-kinds")
|
||||
.returns([analysisKind, otherAnalysis].join(","));
|
||||
await t.throwsAsync(getAnalysisKinds(getRunnerLogger(true), true), {
|
||||
instanceOf: ConfigurationError,
|
||||
message: `${analysisKind} and ${otherAnalysis} cannot be enabled at the same time`,
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,44 +137,50 @@ test("Code Scanning configuration does not accept other SARIF extensions", (t) =
|
||||
}
|
||||
});
|
||||
|
||||
test("Risk Assessment configuration transforms SARIF upload payload", (t) => {
|
||||
process.env[EnvVar.RISK_ASSESSMENT_ID] = "1";
|
||||
const payload = RiskAssessment.transformPayload({
|
||||
commit_oid: "abc",
|
||||
sarif: "sarif",
|
||||
ref: "ref",
|
||||
workflow_run_attempt: 1,
|
||||
workflow_run_id: 1,
|
||||
checkout_uri: "uri",
|
||||
tool_names: [],
|
||||
}) as AssessmentPayload;
|
||||
test.serial(
|
||||
"Risk Assessment configuration transforms SARIF upload payload",
|
||||
(t) => {
|
||||
process.env[EnvVar.RISK_ASSESSMENT_ID] = "1";
|
||||
const payload = RiskAssessment.transformPayload({
|
||||
commit_oid: "abc",
|
||||
sarif: "sarif",
|
||||
ref: "ref",
|
||||
workflow_run_attempt: 1,
|
||||
workflow_run_id: 1,
|
||||
checkout_uri: "uri",
|
||||
tool_names: [],
|
||||
}) as AssessmentPayload;
|
||||
|
||||
const expected: AssessmentPayload = { sarif: "sarif", assessment_id: 1 };
|
||||
t.deepEqual(expected, payload);
|
||||
});
|
||||
const expected: AssessmentPayload = { sarif: "sarif", assessment_id: 1 };
|
||||
t.deepEqual(expected, payload);
|
||||
},
|
||||
);
|
||||
|
||||
test("Risk Assessment configuration throws for negative assessment IDs", (t) => {
|
||||
process.env[EnvVar.RISK_ASSESSMENT_ID] = "-1";
|
||||
t.throws(
|
||||
() =>
|
||||
RiskAssessment.transformPayload({
|
||||
commit_oid: "abc",
|
||||
sarif: "sarif",
|
||||
ref: "ref",
|
||||
workflow_run_attempt: 1,
|
||||
workflow_run_id: 1,
|
||||
checkout_uri: "uri",
|
||||
tool_names: [],
|
||||
}),
|
||||
{
|
||||
instanceOf: Error,
|
||||
message: (msg) =>
|
||||
msg.startsWith(`${EnvVar.RISK_ASSESSMENT_ID} must not be negative: `),
|
||||
},
|
||||
);
|
||||
});
|
||||
test.serial(
|
||||
"Risk Assessment configuration throws for negative assessment IDs",
|
||||
(t) => {
|
||||
process.env[EnvVar.RISK_ASSESSMENT_ID] = "-1";
|
||||
t.throws(
|
||||
() =>
|
||||
RiskAssessment.transformPayload({
|
||||
commit_oid: "abc",
|
||||
sarif: "sarif",
|
||||
ref: "ref",
|
||||
workflow_run_attempt: 1,
|
||||
workflow_run_id: 1,
|
||||
checkout_uri: "uri",
|
||||
tool_names: [],
|
||||
}),
|
||||
{
|
||||
instanceOf: Error,
|
||||
message: (msg) =>
|
||||
msg.startsWith(`${EnvVar.RISK_ASSESSMENT_ID} must not be negative: `),
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("Risk Assessment configuration throws for invalid IDs", (t) => {
|
||||
test.serial("Risk Assessment configuration throws for invalid IDs", (t) => {
|
||||
process.env[EnvVar.RISK_ASSESSMENT_ID] = "foo";
|
||||
t.throws(
|
||||
() =>
|
||||
|
||||
@@ -32,7 +32,7 @@ setupTests(test);
|
||||
* - Checks that the duration fields are populated for the correct language.
|
||||
* - Checks that the QA telemetry status report fields are populated when the QA feature flag is enabled.
|
||||
*/
|
||||
test("status report fields", async (t) => {
|
||||
test.serial("status report fields", async (t) => {
|
||||
return await util.withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ test.beforeEach(() => {
|
||||
util.initializeEnvironment(actionsUtil.getActionVersion());
|
||||
});
|
||||
|
||||
test("getApiClient", async (t) => {
|
||||
test.serial("getApiClient", async (t) => {
|
||||
const pluginStub: sinon.SinonStub = sinon.stub(githubUtils.GitHub, "plugin");
|
||||
const githubStub: sinon.SinonStub = sinon.stub();
|
||||
pluginStub.returns(githubStub);
|
||||
@@ -61,7 +61,7 @@ function mockGetMetaVersionHeader(
|
||||
return spyGetContents;
|
||||
}
|
||||
|
||||
test("getGitHubVersion for Dotcom", async (t) => {
|
||||
test.serial("getGitHubVersion for Dotcom", async (t) => {
|
||||
const apiDetails = {
|
||||
auth: "",
|
||||
url: "https://github.com",
|
||||
@@ -75,7 +75,7 @@ test("getGitHubVersion for Dotcom", async (t) => {
|
||||
t.deepEqual(util.GitHubVariant.DOTCOM, v.type);
|
||||
});
|
||||
|
||||
test("getGitHubVersion for GHES", async (t) => {
|
||||
test.serial("getGitHubVersion for GHES", async (t) => {
|
||||
mockGetMetaVersionHeader("2.0");
|
||||
const v2 = await api.getGitHubVersionFromApi(api.getApiClient(), {
|
||||
auth: "",
|
||||
@@ -88,7 +88,7 @@ test("getGitHubVersion for GHES", async (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("getGitHubVersion for different domain", async (t) => {
|
||||
test.serial("getGitHubVersion for different domain", async (t) => {
|
||||
mockGetMetaVersionHeader(undefined);
|
||||
const v3 = await api.getGitHubVersionFromApi(api.getApiClient(), {
|
||||
auth: "",
|
||||
@@ -98,7 +98,7 @@ test("getGitHubVersion for different domain", async (t) => {
|
||||
t.deepEqual({ type: util.GitHubVariant.DOTCOM }, v3);
|
||||
});
|
||||
|
||||
test("getGitHubVersion for GHEC-DR", async (t) => {
|
||||
test.serial("getGitHubVersion for GHEC-DR", async (t) => {
|
||||
mockGetMetaVersionHeader("ghe.com");
|
||||
const gheDotcom = await api.getGitHubVersionFromApi(api.getApiClient(), {
|
||||
auth: "",
|
||||
@@ -108,96 +108,99 @@ test("getGitHubVersion for GHEC-DR", async (t) => {
|
||||
t.deepEqual({ type: util.GitHubVariant.GHEC_DR }, gheDotcom);
|
||||
});
|
||||
|
||||
test("wrapApiConfigurationError correctly wraps specific configuration errors", (t) => {
|
||||
// We don't reclassify arbitrary errors
|
||||
const arbitraryError = new Error("arbitrary error");
|
||||
let res = api.wrapApiConfigurationError(arbitraryError);
|
||||
t.is(res, arbitraryError);
|
||||
test.serial(
|
||||
"wrapApiConfigurationError correctly wraps specific configuration errors",
|
||||
(t) => {
|
||||
// We don't reclassify arbitrary errors
|
||||
const arbitraryError = new Error("arbitrary error");
|
||||
let res = api.wrapApiConfigurationError(arbitraryError);
|
||||
t.is(res, arbitraryError);
|
||||
|
||||
// Same goes for arbitrary errors
|
||||
const configError = new util.ConfigurationError("arbitrary error");
|
||||
res = api.wrapApiConfigurationError(configError);
|
||||
t.is(res, configError);
|
||||
// Same goes for arbitrary errors
|
||||
const configError = new util.ConfigurationError("arbitrary error");
|
||||
res = api.wrapApiConfigurationError(configError);
|
||||
t.is(res, configError);
|
||||
|
||||
// If an HTTP error doesn't contain a specific error message, we don't
|
||||
// wrap is an an API error.
|
||||
const httpError = new util.HTTPError("arbitrary HTTP error", 456);
|
||||
res = api.wrapApiConfigurationError(httpError);
|
||||
t.is(res, httpError);
|
||||
// If an HTTP error doesn't contain a specific error message, we don't
|
||||
// wrap is an an API error.
|
||||
const httpError = new util.HTTPError("arbitrary HTTP error", 456);
|
||||
res = api.wrapApiConfigurationError(httpError);
|
||||
t.is(res, httpError);
|
||||
|
||||
// For other HTTP errors, we wrap them as Configuration errors if they contain
|
||||
// specific error messages.
|
||||
const httpNotFoundError = new util.HTTPError("commit not found", 404);
|
||||
res = api.wrapApiConfigurationError(httpNotFoundError);
|
||||
t.deepEqual(res, new util.ConfigurationError("commit not found"));
|
||||
// For other HTTP errors, we wrap them as Configuration errors if they contain
|
||||
// specific error messages.
|
||||
const httpNotFoundError = new util.HTTPError("commit not found", 404);
|
||||
res = api.wrapApiConfigurationError(httpNotFoundError);
|
||||
t.deepEqual(res, new util.ConfigurationError("commit not found"));
|
||||
|
||||
const refNotFoundError = new util.HTTPError(
|
||||
"ref 'refs/heads/jitsi' not found in this repository - https://docs.github.com/rest",
|
||||
404,
|
||||
);
|
||||
res = api.wrapApiConfigurationError(refNotFoundError);
|
||||
t.deepEqual(
|
||||
res,
|
||||
new util.ConfigurationError(
|
||||
const refNotFoundError = new util.HTTPError(
|
||||
"ref 'refs/heads/jitsi' not found in this repository - https://docs.github.com/rest",
|
||||
),
|
||||
);
|
||||
404,
|
||||
);
|
||||
res = api.wrapApiConfigurationError(refNotFoundError);
|
||||
t.deepEqual(
|
||||
res,
|
||||
new util.ConfigurationError(
|
||||
"ref 'refs/heads/jitsi' not found in this repository - https://docs.github.com/rest",
|
||||
),
|
||||
);
|
||||
|
||||
const apiRateLimitError = new util.HTTPError(
|
||||
"API rate limit exceeded for installation",
|
||||
403,
|
||||
);
|
||||
res = api.wrapApiConfigurationError(apiRateLimitError);
|
||||
t.deepEqual(
|
||||
res,
|
||||
new util.ConfigurationError("API rate limit exceeded for installation"),
|
||||
);
|
||||
const apiRateLimitError = new util.HTTPError(
|
||||
"API rate limit exceeded for installation",
|
||||
403,
|
||||
);
|
||||
res = api.wrapApiConfigurationError(apiRateLimitError);
|
||||
t.deepEqual(
|
||||
res,
|
||||
new util.ConfigurationError("API rate limit exceeded for installation"),
|
||||
);
|
||||
|
||||
const tokenSuggestionMessage =
|
||||
"Please check that your token is valid and has the required permissions: contents: read, security-events: write";
|
||||
const badCredentialsError = new util.HTTPError("Bad credentials", 401);
|
||||
res = api.wrapApiConfigurationError(badCredentialsError);
|
||||
t.deepEqual(res, new util.ConfigurationError(tokenSuggestionMessage));
|
||||
const tokenSuggestionMessage =
|
||||
"Please check that your token is valid and has the required permissions: contents: read, security-events: write";
|
||||
const badCredentialsError = new util.HTTPError("Bad credentials", 401);
|
||||
res = api.wrapApiConfigurationError(badCredentialsError);
|
||||
t.deepEqual(res, new util.ConfigurationError(tokenSuggestionMessage));
|
||||
|
||||
const notFoundError = new util.HTTPError("Not Found", 404);
|
||||
res = api.wrapApiConfigurationError(notFoundError);
|
||||
t.deepEqual(res, new util.ConfigurationError(tokenSuggestionMessage));
|
||||
const notFoundError = new util.HTTPError("Not Found", 404);
|
||||
res = api.wrapApiConfigurationError(notFoundError);
|
||||
t.deepEqual(res, new util.ConfigurationError(tokenSuggestionMessage));
|
||||
|
||||
const resourceNotAccessibleError = new util.HTTPError(
|
||||
"Resource not accessible by integration",
|
||||
403,
|
||||
);
|
||||
res = api.wrapApiConfigurationError(resourceNotAccessibleError);
|
||||
t.deepEqual(
|
||||
res,
|
||||
new util.ConfigurationError("Resource not accessible by integration"),
|
||||
);
|
||||
const resourceNotAccessibleError = new util.HTTPError(
|
||||
"Resource not accessible by integration",
|
||||
403,
|
||||
);
|
||||
res = api.wrapApiConfigurationError(resourceNotAccessibleError);
|
||||
t.deepEqual(
|
||||
res,
|
||||
new util.ConfigurationError("Resource not accessible by integration"),
|
||||
);
|
||||
|
||||
// Enablement errors.
|
||||
const enablementErrorMessages = [
|
||||
"Code Security must be enabled for this repository to use code scanning",
|
||||
"Advanced Security must be enabled for this repository to use code scanning",
|
||||
"Code Scanning is not enabled for this repository. Please enable code scanning in the repository settings.",
|
||||
];
|
||||
const transforms = [
|
||||
(msg: string) => msg,
|
||||
(msg: string) => msg.toLowerCase(),
|
||||
(msg: string) => msg.toLocaleUpperCase(),
|
||||
];
|
||||
// Enablement errors.
|
||||
const enablementErrorMessages = [
|
||||
"Code Security must be enabled for this repository to use code scanning",
|
||||
"Advanced Security must be enabled for this repository to use code scanning",
|
||||
"Code Scanning is not enabled for this repository. Please enable code scanning in the repository settings.",
|
||||
];
|
||||
const transforms = [
|
||||
(msg: string) => msg,
|
||||
(msg: string) => msg.toLowerCase(),
|
||||
(msg: string) => msg.toLocaleUpperCase(),
|
||||
];
|
||||
|
||||
for (const enablementErrorMessage of enablementErrorMessages) {
|
||||
for (const transform of transforms) {
|
||||
const enablementError = new util.HTTPError(
|
||||
transform(enablementErrorMessage),
|
||||
403,
|
||||
);
|
||||
res = api.wrapApiConfigurationError(enablementError);
|
||||
t.deepEqual(
|
||||
res,
|
||||
new util.ConfigurationError(
|
||||
api.getFeatureEnablementError(enablementError.message),
|
||||
),
|
||||
);
|
||||
for (const enablementErrorMessage of enablementErrorMessages) {
|
||||
for (const transform of transforms) {
|
||||
const enablementError = new util.HTTPError(
|
||||
transform(enablementErrorMessage),
|
||||
403,
|
||||
);
|
||||
res = api.wrapApiConfigurationError(enablementError);
|
||||
t.deepEqual(
|
||||
res,
|
||||
new util.ConfigurationError(
|
||||
api.getFeatureEnablementError(enablementError.message),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -131,27 +131,30 @@ for (const [platform, arch] of [
|
||||
["linux", "arm64"],
|
||||
["win32", "arm64"],
|
||||
]) {
|
||||
test(`wrapCliConfigurationError - ${platform}/${arch} unsupported`, (t) => {
|
||||
sinon.stub(process, "platform").value(platform);
|
||||
sinon.stub(process, "arch").value(arch);
|
||||
const commandError = new CommandInvocationError(
|
||||
"codeql",
|
||||
["version"],
|
||||
1,
|
||||
"Some error",
|
||||
);
|
||||
const cliError = new CliError(commandError);
|
||||
test.serial(
|
||||
`wrapCliConfigurationError - ${platform}/${arch} unsupported`,
|
||||
(t) => {
|
||||
sinon.stub(process, "platform").value(platform);
|
||||
sinon.stub(process, "arch").value(arch);
|
||||
const commandError = new CommandInvocationError(
|
||||
"codeql",
|
||||
["version"],
|
||||
1,
|
||||
"Some error",
|
||||
);
|
||||
const cliError = new CliError(commandError);
|
||||
|
||||
const wrappedError = wrapCliConfigurationError(cliError);
|
||||
const wrappedError = wrapCliConfigurationError(cliError);
|
||||
|
||||
t.true(wrappedError instanceof ConfigurationError);
|
||||
t.true(
|
||||
wrappedError.message.includes(
|
||||
"CodeQL CLI does not support the platform/architecture combination",
|
||||
),
|
||||
);
|
||||
t.true(wrappedError.message.includes(`${platform}/${arch}`));
|
||||
});
|
||||
t.true(wrappedError instanceof ConfigurationError);
|
||||
t.true(
|
||||
wrappedError.message.includes(
|
||||
"CodeQL CLI does not support the platform/architecture combination",
|
||||
),
|
||||
);
|
||||
t.true(wrappedError.message.includes(`${platform}/${arch}`));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
test("wrapCliConfigurationError - supported platform", (t) => {
|
||||
|
||||
@@ -120,19 +120,53 @@ async function stubCodeql(): Promise<codeql.CodeQL> {
|
||||
return codeqlObject;
|
||||
}
|
||||
|
||||
test("downloads and caches explicitly requested bundles that aren't in the toolcache", async (t) => {
|
||||
const features = createFeatures([]);
|
||||
test.serial(
|
||||
"downloads and caches explicitly requested bundles that aren't in the toolcache",
|
||||
async (t) => {
|
||||
const features = createFeatures([]);
|
||||
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
|
||||
const versions = ["20200601", "20200610"];
|
||||
const versions = ["20200601", "20200610"];
|
||||
|
||||
for (let i = 0; i < versions.length; i++) {
|
||||
const version = versions[i];
|
||||
for (let i = 0; i < versions.length; i++) {
|
||||
const version = versions[i];
|
||||
|
||||
const url = mockBundleDownloadApi({
|
||||
tagName: `codeql-bundle-${version}`,
|
||||
isPinned: false,
|
||||
});
|
||||
const result = await codeql.setupCodeQL(
|
||||
url,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
util.GitHubVariant.DOTCOM,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
false,
|
||||
);
|
||||
|
||||
t.assert(toolcache.find("CodeQL", `0.0.0-${version}`));
|
||||
t.is(result.toolsVersion, `0.0.0-${version}`);
|
||||
t.is(result.toolsSource, ToolsSource.Download);
|
||||
}
|
||||
|
||||
t.is(toolcache.findAllVersions("CodeQL").length, 2);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"caches semantically versioned bundles using their semantic version number",
|
||||
async (t) => {
|
||||
const features = createFeatures([]);
|
||||
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const url = mockBundleDownloadApi({
|
||||
tagName: `codeql-bundle-${version}`,
|
||||
tagName: `codeql-bundle-v2.15.0`,
|
||||
isPinned: false,
|
||||
});
|
||||
const result = await codeql.setupCodeQL(
|
||||
@@ -146,78 +180,53 @@ test("downloads and caches explicitly requested bundles that aren't in the toolc
|
||||
false,
|
||||
);
|
||||
|
||||
t.assert(toolcache.find("CodeQL", `0.0.0-${version}`));
|
||||
t.is(result.toolsVersion, `0.0.0-${version}`);
|
||||
t.is(toolcache.findAllVersions("CodeQL").length, 1);
|
||||
t.assert(toolcache.find("CodeQL", `2.15.0`));
|
||||
t.is(result.toolsVersion, `2.15.0`);
|
||||
t.is(result.toolsSource, ToolsSource.Download);
|
||||
}
|
||||
|
||||
t.is(toolcache.findAllVersions("CodeQL").length, 2);
|
||||
});
|
||||
});
|
||||
|
||||
test("caches semantically versioned bundles using their semantic version number", async (t) => {
|
||||
const features = createFeatures([]);
|
||||
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const url = mockBundleDownloadApi({
|
||||
tagName: `codeql-bundle-v2.15.0`,
|
||||
isPinned: false,
|
||||
if (result.toolsDownloadStatusReport) {
|
||||
assertDurationsInteger(t, result.toolsDownloadStatusReport);
|
||||
}
|
||||
});
|
||||
const result = await codeql.setupCodeQL(
|
||||
url,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
util.GitHubVariant.DOTCOM,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
false,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
t.is(toolcache.findAllVersions("CodeQL").length, 1);
|
||||
t.assert(toolcache.find("CodeQL", `2.15.0`));
|
||||
t.is(result.toolsVersion, `2.15.0`);
|
||||
t.is(result.toolsSource, ToolsSource.Download);
|
||||
if (result.toolsDownloadStatusReport) {
|
||||
assertDurationsInteger(t, result.toolsDownloadStatusReport);
|
||||
}
|
||||
});
|
||||
});
|
||||
test.serial(
|
||||
"downloads an explicitly requested bundle even if a different version is cached",
|
||||
async (t) => {
|
||||
const features = createFeatures([]);
|
||||
|
||||
test("downloads an explicitly requested bundle even if a different version is cached", async (t) => {
|
||||
const features = createFeatures([]);
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
await installIntoToolcache({
|
||||
tagName: "codeql-bundle-20200601",
|
||||
isPinned: true,
|
||||
tmpDir,
|
||||
});
|
||||
|
||||
await installIntoToolcache({
|
||||
tagName: "codeql-bundle-20200601",
|
||||
isPinned: true,
|
||||
tmpDir,
|
||||
const url = mockBundleDownloadApi({
|
||||
tagName: "codeql-bundle-20200610",
|
||||
});
|
||||
const result = await codeql.setupCodeQL(
|
||||
url,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
util.GitHubVariant.DOTCOM,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
false,
|
||||
);
|
||||
t.assert(toolcache.find("CodeQL", "0.0.0-20200610"));
|
||||
t.deepEqual(result.toolsVersion, "0.0.0-20200610");
|
||||
t.is(result.toolsSource, ToolsSource.Download);
|
||||
if (result.toolsDownloadStatusReport) {
|
||||
assertDurationsInteger(t, result.toolsDownloadStatusReport);
|
||||
}
|
||||
});
|
||||
|
||||
const url = mockBundleDownloadApi({
|
||||
tagName: "codeql-bundle-20200610",
|
||||
});
|
||||
const result = await codeql.setupCodeQL(
|
||||
url,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
util.GitHubVariant.DOTCOM,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
false,
|
||||
);
|
||||
t.assert(toolcache.find("CodeQL", "0.0.0-20200610"));
|
||||
t.deepEqual(result.toolsVersion, "0.0.0-20200610");
|
||||
t.is(result.toolsSource, ToolsSource.Download);
|
||||
if (result.toolsDownloadStatusReport) {
|
||||
assertDurationsInteger(t, result.toolsDownloadStatusReport);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const EXPLICITLY_REQUESTED_BUNDLE_TEST_CASES = [
|
||||
{
|
||||
@@ -234,37 +243,42 @@ for (const {
|
||||
tagName,
|
||||
expectedToolcacheVersion,
|
||||
} of EXPLICITLY_REQUESTED_BUNDLE_TEST_CASES) {
|
||||
test(`caches explicitly requested bundle ${tagName} as ${expectedToolcacheVersion}`, async (t) => {
|
||||
const features = createFeatures([]);
|
||||
test.serial(
|
||||
`caches explicitly requested bundle ${tagName} as ${expectedToolcacheVersion}`,
|
||||
async (t) => {
|
||||
const features = createFeatures([]);
|
||||
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
|
||||
mockApiDetails(SAMPLE_DOTCOM_API_DETAILS);
|
||||
sinon.stub(actionsUtil, "isRunningLocalAction").returns(true);
|
||||
mockApiDetails(SAMPLE_DOTCOM_API_DETAILS);
|
||||
sinon.stub(actionsUtil, "isRunningLocalAction").returns(true);
|
||||
|
||||
const url = mockBundleDownloadApi({
|
||||
tagName,
|
||||
const url = mockBundleDownloadApi({
|
||||
tagName,
|
||||
});
|
||||
|
||||
const result = await codeql.setupCodeQL(
|
||||
url,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
util.GitHubVariant.DOTCOM,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
false,
|
||||
);
|
||||
t.assert(toolcache.find("CodeQL", expectedToolcacheVersion));
|
||||
t.deepEqual(result.toolsVersion, expectedToolcacheVersion);
|
||||
t.is(result.toolsSource, ToolsSource.Download);
|
||||
t.assert(
|
||||
Number.isInteger(
|
||||
result.toolsDownloadStatusReport?.downloadDurationMs,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
const result = await codeql.setupCodeQL(
|
||||
url,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
util.GitHubVariant.DOTCOM,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
false,
|
||||
);
|
||||
t.assert(toolcache.find("CodeQL", expectedToolcacheVersion));
|
||||
t.deepEqual(result.toolsVersion, expectedToolcacheVersion);
|
||||
t.is(result.toolsSource, ToolsSource.Download);
|
||||
t.assert(
|
||||
Number.isInteger(result.toolsDownloadStatusReport?.downloadDurationMs),
|
||||
);
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
for (const toolcacheVersion of [
|
||||
@@ -273,7 +287,7 @@ for (const toolcacheVersion of [
|
||||
SAMPLE_DEFAULT_CLI_VERSION.cliVersion,
|
||||
`${SAMPLE_DEFAULT_CLI_VERSION.cliVersion}-20230101`,
|
||||
]) {
|
||||
test(
|
||||
test.serial(
|
||||
`uses tools from toolcache when ${SAMPLE_DEFAULT_CLI_VERSION.cliVersion} is requested and ` +
|
||||
`${toolcacheVersion} is installed`,
|
||||
async (t) => {
|
||||
@@ -308,158 +322,170 @@ for (const toolcacheVersion of [
|
||||
);
|
||||
}
|
||||
|
||||
test(`uses a cached bundle when no tools input is given on GHES`, async (t) => {
|
||||
const features = createFeatures([]);
|
||||
test.serial(
|
||||
`uses a cached bundle when no tools input is given on GHES`,
|
||||
async (t) => {
|
||||
const features = createFeatures([]);
|
||||
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
|
||||
await installIntoToolcache({
|
||||
tagName: "codeql-bundle-20200601",
|
||||
isPinned: true,
|
||||
tmpDir,
|
||||
await installIntoToolcache({
|
||||
tagName: "codeql-bundle-20200601",
|
||||
isPinned: true,
|
||||
tmpDir,
|
||||
});
|
||||
|
||||
const result = await codeql.setupCodeQL(
|
||||
undefined,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
util.GitHubVariant.GHES,
|
||||
{
|
||||
cliVersion: defaults.cliVersion,
|
||||
tagName: defaults.bundleVersion,
|
||||
},
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
false,
|
||||
);
|
||||
t.deepEqual(result.toolsVersion, "0.0.0-20200601");
|
||||
t.is(result.toolsSource, ToolsSource.Toolcache);
|
||||
t.is(result.toolsDownloadStatusReport?.combinedDurationMs, undefined);
|
||||
t.is(result.toolsDownloadStatusReport?.downloadDurationMs, undefined);
|
||||
t.is(result.toolsDownloadStatusReport?.extractionDurationMs, undefined);
|
||||
|
||||
const cachedVersions = toolcache.findAllVersions("CodeQL");
|
||||
t.is(cachedVersions.length, 1);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const result = await codeql.setupCodeQL(
|
||||
undefined,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
util.GitHubVariant.GHES,
|
||||
{
|
||||
cliVersion: defaults.cliVersion,
|
||||
test.serial(
|
||||
`downloads bundle if only an unpinned version is cached on GHES`,
|
||||
async (t) => {
|
||||
const features = createFeatures([]);
|
||||
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
|
||||
await installIntoToolcache({
|
||||
tagName: "codeql-bundle-20200601",
|
||||
isPinned: false,
|
||||
tmpDir,
|
||||
});
|
||||
|
||||
mockBundleDownloadApi({
|
||||
tagName: defaults.bundleVersion,
|
||||
},
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
false,
|
||||
);
|
||||
t.deepEqual(result.toolsVersion, "0.0.0-20200601");
|
||||
t.is(result.toolsSource, ToolsSource.Toolcache);
|
||||
t.is(result.toolsDownloadStatusReport?.combinedDurationMs, undefined);
|
||||
t.is(result.toolsDownloadStatusReport?.downloadDurationMs, undefined);
|
||||
t.is(result.toolsDownloadStatusReport?.extractionDurationMs, undefined);
|
||||
});
|
||||
const result = await codeql.setupCodeQL(
|
||||
undefined,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
util.GitHubVariant.GHES,
|
||||
{
|
||||
cliVersion: defaults.cliVersion,
|
||||
tagName: defaults.bundleVersion,
|
||||
},
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
false,
|
||||
);
|
||||
t.deepEqual(result.toolsVersion, defaults.cliVersion);
|
||||
t.is(result.toolsSource, ToolsSource.Download);
|
||||
if (result.toolsDownloadStatusReport) {
|
||||
assertDurationsInteger(t, result.toolsDownloadStatusReport);
|
||||
}
|
||||
|
||||
const cachedVersions = toolcache.findAllVersions("CodeQL");
|
||||
t.is(cachedVersions.length, 1);
|
||||
});
|
||||
});
|
||||
|
||||
test(`downloads bundle if only an unpinned version is cached on GHES`, async (t) => {
|
||||
const features = createFeatures([]);
|
||||
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
|
||||
await installIntoToolcache({
|
||||
tagName: "codeql-bundle-20200601",
|
||||
isPinned: false,
|
||||
tmpDir,
|
||||
const cachedVersions = toolcache.findAllVersions("CodeQL");
|
||||
t.is(cachedVersions.length, 2);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
mockBundleDownloadApi({
|
||||
tagName: defaults.bundleVersion,
|
||||
});
|
||||
const result = await codeql.setupCodeQL(
|
||||
undefined,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
util.GitHubVariant.GHES,
|
||||
{
|
||||
cliVersion: defaults.cliVersion,
|
||||
test.serial(
|
||||
'downloads bundle if "latest" tools specified but not cached',
|
||||
async (t) => {
|
||||
const features = createFeatures([]);
|
||||
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
|
||||
await installIntoToolcache({
|
||||
tagName: "codeql-bundle-20200601",
|
||||
isPinned: true,
|
||||
tmpDir,
|
||||
});
|
||||
|
||||
mockBundleDownloadApi({
|
||||
tagName: defaults.bundleVersion,
|
||||
},
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
false,
|
||||
);
|
||||
t.deepEqual(result.toolsVersion, defaults.cliVersion);
|
||||
t.is(result.toolsSource, ToolsSource.Download);
|
||||
if (result.toolsDownloadStatusReport) {
|
||||
assertDurationsInteger(t, result.toolsDownloadStatusReport);
|
||||
}
|
||||
});
|
||||
const result = await codeql.setupCodeQL(
|
||||
"latest",
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
util.GitHubVariant.DOTCOM,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
false,
|
||||
);
|
||||
t.deepEqual(result.toolsVersion, defaults.cliVersion);
|
||||
t.is(result.toolsSource, ToolsSource.Download);
|
||||
if (result.toolsDownloadStatusReport) {
|
||||
assertDurationsInteger(t, result.toolsDownloadStatusReport);
|
||||
}
|
||||
|
||||
const cachedVersions = toolcache.findAllVersions("CodeQL");
|
||||
t.is(cachedVersions.length, 2);
|
||||
});
|
||||
});
|
||||
|
||||
test('downloads bundle if "latest" tools specified but not cached', async (t) => {
|
||||
const features = createFeatures([]);
|
||||
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
|
||||
await installIntoToolcache({
|
||||
tagName: "codeql-bundle-20200601",
|
||||
isPinned: true,
|
||||
tmpDir,
|
||||
const cachedVersions = toolcache.findAllVersions("CodeQL");
|
||||
t.is(cachedVersions.length, 2);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
mockBundleDownloadApi({
|
||||
tagName: defaults.bundleVersion,
|
||||
test.serial(
|
||||
"bundle URL from another repo is cached as 0.0.0-bundleVersion",
|
||||
async (t) => {
|
||||
const features = createFeatures([]);
|
||||
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
|
||||
mockApiDetails(SAMPLE_DOTCOM_API_DETAILS);
|
||||
sinon.stub(actionsUtil, "isRunningLocalAction").returns(true);
|
||||
const releasesApiMock = mockReleaseApi({
|
||||
assetNames: ["cli-version-2.14.6.txt"],
|
||||
tagName: "codeql-bundle-20230203",
|
||||
});
|
||||
mockBundleDownloadApi({
|
||||
repo: "codeql-testing/codeql-cli-nightlies",
|
||||
platformSpecific: false,
|
||||
tagName: "codeql-bundle-20230203",
|
||||
});
|
||||
const result = await codeql.setupCodeQL(
|
||||
"https://github.com/codeql-testing/codeql-cli-nightlies/releases/download/codeql-bundle-20230203/codeql-bundle.tar.gz",
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
util.GitHubVariant.DOTCOM,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
false,
|
||||
);
|
||||
|
||||
t.is(result.toolsVersion, "0.0.0-20230203");
|
||||
t.is(result.toolsSource, ToolsSource.Download);
|
||||
if (result.toolsDownloadStatusReport) {
|
||||
assertDurationsInteger(t, result.toolsDownloadStatusReport);
|
||||
}
|
||||
|
||||
const cachedVersions = toolcache.findAllVersions("CodeQL");
|
||||
t.is(cachedVersions.length, 1);
|
||||
t.is(cachedVersions[0], "0.0.0-20230203");
|
||||
|
||||
t.false(releasesApiMock.isDone());
|
||||
});
|
||||
const result = await codeql.setupCodeQL(
|
||||
"latest",
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
util.GitHubVariant.DOTCOM,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
false,
|
||||
);
|
||||
t.deepEqual(result.toolsVersion, defaults.cliVersion);
|
||||
t.is(result.toolsSource, ToolsSource.Download);
|
||||
if (result.toolsDownloadStatusReport) {
|
||||
assertDurationsInteger(t, result.toolsDownloadStatusReport);
|
||||
}
|
||||
|
||||
const cachedVersions = toolcache.findAllVersions("CodeQL");
|
||||
t.is(cachedVersions.length, 2);
|
||||
});
|
||||
});
|
||||
|
||||
test("bundle URL from another repo is cached as 0.0.0-bundleVersion", async (t) => {
|
||||
const features = createFeatures([]);
|
||||
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
|
||||
mockApiDetails(SAMPLE_DOTCOM_API_DETAILS);
|
||||
sinon.stub(actionsUtil, "isRunningLocalAction").returns(true);
|
||||
const releasesApiMock = mockReleaseApi({
|
||||
assetNames: ["cli-version-2.14.6.txt"],
|
||||
tagName: "codeql-bundle-20230203",
|
||||
});
|
||||
mockBundleDownloadApi({
|
||||
repo: "codeql-testing/codeql-cli-nightlies",
|
||||
platformSpecific: false,
|
||||
tagName: "codeql-bundle-20230203",
|
||||
});
|
||||
const result = await codeql.setupCodeQL(
|
||||
"https://github.com/codeql-testing/codeql-cli-nightlies/releases/download/codeql-bundle-20230203/codeql-bundle.tar.gz",
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
util.GitHubVariant.DOTCOM,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
false,
|
||||
);
|
||||
|
||||
t.is(result.toolsVersion, "0.0.0-20230203");
|
||||
t.is(result.toolsSource, ToolsSource.Download);
|
||||
if (result.toolsDownloadStatusReport) {
|
||||
assertDurationsInteger(t, result.toolsDownloadStatusReport);
|
||||
}
|
||||
|
||||
const cachedVersions = toolcache.findAllVersions("CodeQL");
|
||||
t.is(cachedVersions.length, 1);
|
||||
t.is(cachedVersions[0], "0.0.0-20230203");
|
||||
|
||||
t.false(releasesApiMock.isDone());
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
function assertDurationsInteger(
|
||||
t: ExecutionContext<unknown>,
|
||||
@@ -472,7 +498,7 @@ function assertDurationsInteger(
|
||||
}
|
||||
}
|
||||
|
||||
test("getExtraOptions works for explicit paths", (t) => {
|
||||
test.serial("getExtraOptions works for explicit paths", (t) => {
|
||||
t.deepEqual(codeql.getExtraOptions({}, ["foo"], []), []);
|
||||
|
||||
t.deepEqual(codeql.getExtraOptions({ foo: [42] }, ["foo"], []), ["42"]);
|
||||
@@ -483,11 +509,11 @@ test("getExtraOptions works for explicit paths", (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("getExtraOptions works for wildcards", (t) => {
|
||||
test.serial("getExtraOptions works for wildcards", (t) => {
|
||||
t.deepEqual(codeql.getExtraOptions({ "*": [42] }, ["foo"], []), ["42"]);
|
||||
});
|
||||
|
||||
test("getExtraOptions works for wildcards and explicit paths", (t) => {
|
||||
test.serial("getExtraOptions works for wildcards and explicit paths", (t) => {
|
||||
const o1 = { "*": [42], foo: [87] };
|
||||
t.deepEqual(codeql.getExtraOptions(o1, ["foo"], []), ["42", "87"]);
|
||||
|
||||
@@ -499,7 +525,7 @@ test("getExtraOptions works for wildcards and explicit paths", (t) => {
|
||||
t.deepEqual(codeql.getExtraOptions(o3, p, []), ["42", "87", "99"]);
|
||||
});
|
||||
|
||||
test("getExtraOptions throws for bad content", (t) => {
|
||||
test.serial("getExtraOptions throws for bad content", (t) => {
|
||||
t.throws(() => codeql.getExtraOptions({ "*": 42 }, ["foo"], []));
|
||||
|
||||
t.throws(() => codeql.getExtraOptions({ foo: 87 }, ["foo"], []));
|
||||
@@ -564,7 +590,7 @@ const injectedConfigMacro = test.macro({
|
||||
`databaseInitCluster() injected config: ${providedTitle}`,
|
||||
});
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
"basic",
|
||||
injectedConfigMacro,
|
||||
{
|
||||
@@ -574,7 +600,7 @@ test(
|
||||
{},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
"injected packs from input",
|
||||
injectedConfigMacro,
|
||||
{
|
||||
@@ -587,7 +613,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
"injected packs from input with existing packs combines",
|
||||
injectedConfigMacro,
|
||||
{
|
||||
@@ -609,7 +635,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
"injected packs from input with existing packs overrides",
|
||||
injectedConfigMacro,
|
||||
{
|
||||
@@ -629,7 +655,7 @@ test(
|
||||
);
|
||||
|
||||
// similar, but with queries
|
||||
test(
|
||||
test.serial(
|
||||
"injected queries from input",
|
||||
injectedConfigMacro,
|
||||
{
|
||||
@@ -649,7 +675,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
"injected queries from input overrides",
|
||||
injectedConfigMacro,
|
||||
{
|
||||
@@ -673,7 +699,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
"injected queries from input combines",
|
||||
injectedConfigMacro,
|
||||
{
|
||||
@@ -701,7 +727,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
"injected queries from input combines 2",
|
||||
injectedConfigMacro,
|
||||
{
|
||||
@@ -723,7 +749,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
"injected queries and packs, but empty",
|
||||
injectedConfigMacro,
|
||||
{
|
||||
@@ -742,7 +768,7 @@ test(
|
||||
{},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
"repo property queries have the highest precedence",
|
||||
injectedConfigMacro,
|
||||
{
|
||||
@@ -764,7 +790,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
"repo property queries combines with queries input",
|
||||
injectedConfigMacro,
|
||||
{
|
||||
@@ -791,7 +817,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
"repo property queries combines everything else",
|
||||
injectedConfigMacro,
|
||||
{
|
||||
@@ -820,55 +846,61 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test("passes a code scanning config AND qlconfig to the CLI", async (t: ExecutionContext<unknown>) => {
|
||||
await util.withTmpDir(async (tempDir) => {
|
||||
const runnerConstructorStub = stubToolRunnerConstructor();
|
||||
const codeqlObject = await stubCodeql();
|
||||
await codeqlObject.databaseInitCluster(
|
||||
{ ...stubConfig, tempDir },
|
||||
"",
|
||||
undefined,
|
||||
"/path/to/qlconfig.yml",
|
||||
getRunnerLogger(true),
|
||||
);
|
||||
test.serial(
|
||||
"passes a code scanning config AND qlconfig to the CLI",
|
||||
async (t: ExecutionContext<unknown>) => {
|
||||
await util.withTmpDir(async (tempDir) => {
|
||||
const runnerConstructorStub = stubToolRunnerConstructor();
|
||||
const codeqlObject = await stubCodeql();
|
||||
await codeqlObject.databaseInitCluster(
|
||||
{ ...stubConfig, tempDir },
|
||||
"",
|
||||
undefined,
|
||||
"/path/to/qlconfig.yml",
|
||||
getRunnerLogger(true),
|
||||
);
|
||||
|
||||
const args = runnerConstructorStub.firstCall.args[1] as string[];
|
||||
// should have used a config file
|
||||
const hasCodeScanningConfigArg = args.some((arg: string) =>
|
||||
arg.startsWith("--codescanning-config="),
|
||||
);
|
||||
t.true(hasCodeScanningConfigArg, "Should have injected a qlconfig");
|
||||
const args = runnerConstructorStub.firstCall.args[1] as string[];
|
||||
// should have used a config file
|
||||
const hasCodeScanningConfigArg = args.some((arg: string) =>
|
||||
arg.startsWith("--codescanning-config="),
|
||||
);
|
||||
t.true(hasCodeScanningConfigArg, "Should have injected a qlconfig");
|
||||
|
||||
// should have passed a qlconfig file
|
||||
const hasQlconfigArg = args.some((arg: string) =>
|
||||
arg.startsWith("--qlconfig-file="),
|
||||
);
|
||||
t.truthy(hasQlconfigArg, "Should have injected a codescanning config");
|
||||
});
|
||||
});
|
||||
// should have passed a qlconfig file
|
||||
const hasQlconfigArg = args.some((arg: string) =>
|
||||
arg.startsWith("--qlconfig-file="),
|
||||
);
|
||||
t.truthy(hasQlconfigArg, "Should have injected a codescanning config");
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("does not pass a qlconfig to the CLI when it is undefined", async (t: ExecutionContext<unknown>) => {
|
||||
await util.withTmpDir(async (tempDir) => {
|
||||
const runnerConstructorStub = stubToolRunnerConstructor();
|
||||
const codeqlObject = await stubCodeql();
|
||||
test.serial(
|
||||
"does not pass a qlconfig to the CLI when it is undefined",
|
||||
async (t: ExecutionContext<unknown>) => {
|
||||
await util.withTmpDir(async (tempDir) => {
|
||||
const runnerConstructorStub = stubToolRunnerConstructor();
|
||||
const codeqlObject = await stubCodeql();
|
||||
|
||||
await codeqlObject.databaseInitCluster(
|
||||
{ ...stubConfig, tempDir },
|
||||
"",
|
||||
undefined,
|
||||
undefined, // undefined qlconfigFile
|
||||
getRunnerLogger(true),
|
||||
);
|
||||
await codeqlObject.databaseInitCluster(
|
||||
{ ...stubConfig, tempDir },
|
||||
"",
|
||||
undefined,
|
||||
undefined, // undefined qlconfigFile
|
||||
getRunnerLogger(true),
|
||||
);
|
||||
|
||||
const args = runnerConstructorStub.firstCall.args[1] as any[];
|
||||
const hasQlconfigArg = args.some((arg: string) =>
|
||||
arg.startsWith("--qlconfig-file="),
|
||||
);
|
||||
t.false(hasQlconfigArg, "should NOT have injected a qlconfig");
|
||||
});
|
||||
});
|
||||
const args = runnerConstructorStub.firstCall.args[1] as any[];
|
||||
const hasQlconfigArg = args.some((arg: string) =>
|
||||
arg.startsWith("--qlconfig-file="),
|
||||
);
|
||||
t.false(hasQlconfigArg, "should NOT have injected a qlconfig");
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("runTool summarizes several fatal errors", async (t) => {
|
||||
test.serial("runTool summarizes several fatal errors", async (t) => {
|
||||
const heapError =
|
||||
"A fatal error occurred: Evaluator heap must be at least 384.00 MiB";
|
||||
const datasetImportError =
|
||||
@@ -905,7 +937,7 @@ test("runTool summarizes several fatal errors", async (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("runTool summarizes autobuilder errors", async (t) => {
|
||||
test.serial("runTool summarizes autobuilder errors", async (t) => {
|
||||
const stderr = `
|
||||
[2019-09-18 12:00:00] [autobuild] A non-error message
|
||||
[2019-09-18 12:00:00] Untagged message
|
||||
@@ -938,7 +970,7 @@ test("runTool summarizes autobuilder errors", async (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("runTool truncates long autobuilder errors", async (t) => {
|
||||
test.serial("runTool truncates long autobuilder errors", async (t) => {
|
||||
const stderr = Array.from(
|
||||
{ length: 20 },
|
||||
(_, i) => `[2019-09-18 12:00:00] [autobuild] [ERROR] line${i + 1}`,
|
||||
@@ -964,7 +996,7 @@ test("runTool truncates long autobuilder errors", async (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("runTool recognizes fatal internal errors", async (t) => {
|
||||
test.serial("runTool recognizes fatal internal errors", async (t) => {
|
||||
const stderr = `
|
||||
[11/31 eval 8m19s] Evaluation done; writing results to codeql/go-queries/Security/CWE-020/MissingRegexpAnchor.bqrs.
|
||||
Oops! A fatal internal error occurred. Details:
|
||||
@@ -989,64 +1021,70 @@ test("runTool recognizes fatal internal errors", async (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("runTool outputs last line of stderr if fatal error could not be found", async (t) => {
|
||||
const cliStderr = "line1\nline2\nline3\nline4\nline5";
|
||||
stubToolRunnerConstructor(32, cliStderr);
|
||||
const codeqlObject = await stubCodeql();
|
||||
// io throws because of the test CodeQL object.
|
||||
sinon.stub(io, "which").resolves("");
|
||||
test.serial(
|
||||
"runTool outputs last line of stderr if fatal error could not be found",
|
||||
async (t) => {
|
||||
const cliStderr = "line1\nline2\nline3\nline4\nline5";
|
||||
stubToolRunnerConstructor(32, cliStderr);
|
||||
const codeqlObject = await stubCodeql();
|
||||
// io throws because of the test CodeQL object.
|
||||
sinon.stub(io, "which").resolves("");
|
||||
|
||||
await t.throwsAsync(
|
||||
async () =>
|
||||
await codeqlObject.finalizeDatabase(
|
||||
"db",
|
||||
"--threads=2",
|
||||
"--ram=2048",
|
||||
false,
|
||||
),
|
||||
{
|
||||
instanceOf: util.ConfigurationError,
|
||||
message: new RegExp(
|
||||
'Encountered a fatal error while running \\"codeql-for-testing database finalize --finalize-dataset --threads=2 --ram=2048 db\\"\\. ' +
|
||||
"Exit code was 32 and last log line was: line5\\. See the logs for more details\\.",
|
||||
),
|
||||
},
|
||||
);
|
||||
});
|
||||
await t.throwsAsync(
|
||||
async () =>
|
||||
await codeqlObject.finalizeDatabase(
|
||||
"db",
|
||||
"--threads=2",
|
||||
"--ram=2048",
|
||||
false,
|
||||
),
|
||||
{
|
||||
instanceOf: util.ConfigurationError,
|
||||
message: new RegExp(
|
||||
'Encountered a fatal error while running \\"codeql-for-testing database finalize --finalize-dataset --threads=2 --ram=2048 db\\"\\. ' +
|
||||
"Exit code was 32 and last log line was: line5\\. See the logs for more details\\.",
|
||||
),
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("Avoids duplicating --overwrite flag if specified in CODEQL_ACTION_EXTRA_OPTIONS", async (t) => {
|
||||
const runnerConstructorStub = stubToolRunnerConstructor();
|
||||
const codeqlObject = await stubCodeql();
|
||||
// io throws because of the test CodeQL object.
|
||||
sinon.stub(io, "which").resolves("");
|
||||
test.serial(
|
||||
"Avoids duplicating --overwrite flag if specified in CODEQL_ACTION_EXTRA_OPTIONS",
|
||||
async (t) => {
|
||||
const runnerConstructorStub = stubToolRunnerConstructor();
|
||||
const codeqlObject = await stubCodeql();
|
||||
// io throws because of the test CodeQL object.
|
||||
sinon.stub(io, "which").resolves("");
|
||||
|
||||
process.env["CODEQL_ACTION_EXTRA_OPTIONS"] =
|
||||
'{ "database": { "init": ["--overwrite"] } }';
|
||||
process.env["CODEQL_ACTION_EXTRA_OPTIONS"] =
|
||||
'{ "database": { "init": ["--overwrite"] } }';
|
||||
|
||||
await codeqlObject.databaseInitCluster(
|
||||
stubConfig,
|
||||
"sourceRoot",
|
||||
undefined,
|
||||
undefined,
|
||||
getRunnerLogger(false),
|
||||
);
|
||||
await codeqlObject.databaseInitCluster(
|
||||
stubConfig,
|
||||
"sourceRoot",
|
||||
undefined,
|
||||
undefined,
|
||||
getRunnerLogger(false),
|
||||
);
|
||||
|
||||
t.true(runnerConstructorStub.calledOnce);
|
||||
const args = runnerConstructorStub.firstCall.args[1] as string[];
|
||||
t.is(
|
||||
args.filter((option: string) => option === "--overwrite").length,
|
||||
1,
|
||||
"--overwrite should only be passed once",
|
||||
);
|
||||
t.true(runnerConstructorStub.calledOnce);
|
||||
const args = runnerConstructorStub.firstCall.args[1] as string[];
|
||||
t.is(
|
||||
args.filter((option: string) => option === "--overwrite").length,
|
||||
1,
|
||||
"--overwrite should only be passed once",
|
||||
);
|
||||
|
||||
// Clean up
|
||||
const configArg = args.find((arg: string) =>
|
||||
arg.startsWith("--codescanning-config="),
|
||||
);
|
||||
t.truthy(configArg, "Should have injected a codescanning config");
|
||||
const configFile = configArg!.split("=")[1];
|
||||
await fs.promises.rm(configFile, { force: true });
|
||||
});
|
||||
// Clean up
|
||||
const configArg = args.find((arg: string) =>
|
||||
arg.startsWith("--codescanning-config="),
|
||||
);
|
||||
t.truthy(configArg, "Should have injected a codescanning config");
|
||||
const configFile = configArg!.split("=")[1];
|
||||
await fs.promises.rm(configFile, { force: true });
|
||||
},
|
||||
);
|
||||
|
||||
export function stubToolRunnerConstructor(
|
||||
exitCode: number = 0,
|
||||
|
||||
@@ -137,7 +137,7 @@ function mockListLanguages(languages: string[]) {
|
||||
sinon.stub(api, "getApiClient").value(() => client);
|
||||
}
|
||||
|
||||
test("load empty config", async (t) => {
|
||||
test.serial("load empty config", async (t) => {
|
||||
return await withTmpDir(async (tempDir) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const languages = "javascript,python";
|
||||
@@ -178,7 +178,7 @@ test("load empty config", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("load code quality config", async (t) => {
|
||||
test.serial("load code quality config", async (t) => {
|
||||
return await withTmpDir(async (tempDir) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const languages = "actions";
|
||||
@@ -228,65 +228,68 @@ test("load code quality config", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("initActionState doesn't throw if there are queries configured in the repository properties", async (t) => {
|
||||
return await withTmpDir(async (tempDir) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const languages = "javascript";
|
||||
test.serial(
|
||||
"initActionState doesn't throw if there are queries configured in the repository properties",
|
||||
async (t) => {
|
||||
return await withTmpDir(async (tempDir) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const languages = "javascript";
|
||||
|
||||
const codeql = createStubCodeQL({
|
||||
async betterResolveLanguages() {
|
||||
return {
|
||||
extractors: {
|
||||
javascript: [{ extractor_root: "" }],
|
||||
},
|
||||
};
|
||||
},
|
||||
const codeql = createStubCodeQL({
|
||||
async betterResolveLanguages() {
|
||||
return {
|
||||
extractors: {
|
||||
javascript: [{ extractor_root: "" }],
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// This should be ignored and no error should be thrown.
|
||||
const repositoryProperties = {
|
||||
"github-codeql-extra-queries": "+foo",
|
||||
};
|
||||
|
||||
// Expected configuration for a CQ-only analysis.
|
||||
const computedConfig: UserConfig = {
|
||||
"disable-default-queries": true,
|
||||
queries: [{ uses: "code-quality" }],
|
||||
"query-filters": [],
|
||||
};
|
||||
|
||||
const expectedConfig = createTestConfig({
|
||||
analysisKinds: [AnalysisKind.CodeQuality],
|
||||
languages: [KnownLanguage.javascript],
|
||||
codeQLCmd: codeql.getPath(),
|
||||
computedConfig,
|
||||
dbLocation: path.resolve(tempDir, "codeql_databases"),
|
||||
debugArtifactName: "",
|
||||
debugDatabaseName: "",
|
||||
tempDir,
|
||||
repositoryProperties,
|
||||
});
|
||||
|
||||
await t.notThrowsAsync(async () => {
|
||||
const config = await configUtils.initConfig(
|
||||
createFeatures([]),
|
||||
createTestInitConfigInputs({
|
||||
analysisKinds: [AnalysisKind.CodeQuality],
|
||||
languagesInput: languages,
|
||||
repository: { owner: "github", repo: "example" },
|
||||
tempDir,
|
||||
codeql,
|
||||
repositoryProperties,
|
||||
logger,
|
||||
}),
|
||||
);
|
||||
|
||||
t.deepEqual(config, expectedConfig);
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// This should be ignored and no error should be thrown.
|
||||
const repositoryProperties = {
|
||||
"github-codeql-extra-queries": "+foo",
|
||||
};
|
||||
|
||||
// Expected configuration for a CQ-only analysis.
|
||||
const computedConfig: UserConfig = {
|
||||
"disable-default-queries": true,
|
||||
queries: [{ uses: "code-quality" }],
|
||||
"query-filters": [],
|
||||
};
|
||||
|
||||
const expectedConfig = createTestConfig({
|
||||
analysisKinds: [AnalysisKind.CodeQuality],
|
||||
languages: [KnownLanguage.javascript],
|
||||
codeQLCmd: codeql.getPath(),
|
||||
computedConfig,
|
||||
dbLocation: path.resolve(tempDir, "codeql_databases"),
|
||||
debugArtifactName: "",
|
||||
debugDatabaseName: "",
|
||||
tempDir,
|
||||
repositoryProperties,
|
||||
});
|
||||
|
||||
await t.notThrowsAsync(async () => {
|
||||
const config = await configUtils.initConfig(
|
||||
createFeatures([]),
|
||||
createTestInitConfigInputs({
|
||||
analysisKinds: [AnalysisKind.CodeQuality],
|
||||
languagesInput: languages,
|
||||
repository: { owner: "github", repo: "example" },
|
||||
tempDir,
|
||||
codeql,
|
||||
repositoryProperties,
|
||||
logger,
|
||||
}),
|
||||
);
|
||||
|
||||
t.deepEqual(config, expectedConfig);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("loading a saved config produces the same config", async (t) => {
|
||||
test.serial("loading a saved config produces the same config", async (t) => {
|
||||
return await withTmpDir(async (tempDir) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
|
||||
@@ -333,7 +336,7 @@ test("loading a saved config produces the same config", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("loading config with version mismatch throws", async (t) => {
|
||||
test.serial("loading config with version mismatch throws", async (t) => {
|
||||
return await withTmpDir(async (tempDir) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
|
||||
@@ -385,7 +388,7 @@ test("loading config with version mismatch throws", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("load input outside of workspace", async (t) => {
|
||||
test.serial("load input outside of workspace", async (t) => {
|
||||
return await withTmpDir(async (tempDir) => {
|
||||
try {
|
||||
await configUtils.initConfig(
|
||||
@@ -410,7 +413,7 @@ test("load input outside of workspace", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("load non-local input with invalid repo syntax", async (t) => {
|
||||
test.serial("load non-local input with invalid repo syntax", async (t) => {
|
||||
return await withTmpDir(async (tempDir) => {
|
||||
// no filename given, just a repo
|
||||
const configFile = "octo-org/codeql-config@main";
|
||||
@@ -438,7 +441,7 @@ test("load non-local input with invalid repo syntax", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("load non-existent input", async (t) => {
|
||||
test.serial("load non-existent input", async (t) => {
|
||||
return await withTmpDir(async (tempDir) => {
|
||||
const languagesInput = "javascript";
|
||||
const configFile = "input";
|
||||
@@ -468,7 +471,7 @@ test("load non-existent input", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("load non-empty input", async (t) => {
|
||||
test.serial("load non-empty input", async (t) => {
|
||||
return await withTmpDir(async (tempDir) => {
|
||||
const codeql = createStubCodeQL({
|
||||
async betterResolveLanguages() {
|
||||
@@ -539,18 +542,20 @@ test("load non-empty input", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("Using config input and file together, config input should be used.", async (t) => {
|
||||
return await withTmpDir(async (tempDir) => {
|
||||
process.env["RUNNER_TEMP"] = tempDir;
|
||||
process.env["GITHUB_WORKSPACE"] = tempDir;
|
||||
test.serial(
|
||||
"Using config input and file together, config input should be used.",
|
||||
async (t) => {
|
||||
return await withTmpDir(async (tempDir) => {
|
||||
process.env["RUNNER_TEMP"] = tempDir;
|
||||
process.env["GITHUB_WORKSPACE"] = tempDir;
|
||||
|
||||
const inputFileContents = `
|
||||
const inputFileContents = `
|
||||
name: my config
|
||||
queries:
|
||||
- uses: ./foo_file`;
|
||||
const configFilePath = createConfigFile(inputFileContents, tempDir);
|
||||
const configFilePath = createConfigFile(inputFileContents, tempDir);
|
||||
|
||||
const configInput = `
|
||||
const configInput = `
|
||||
name: my config
|
||||
queries:
|
||||
- uses: ./foo
|
||||
@@ -561,39 +566,40 @@ test("Using config input and file together, config input should be used.", async
|
||||
- c/d@1.2.3
|
||||
`;
|
||||
|
||||
fs.mkdirSync(path.join(tempDir, "foo"));
|
||||
fs.mkdirSync(path.join(tempDir, "foo"));
|
||||
|
||||
const codeql = createStubCodeQL({
|
||||
async betterResolveLanguages() {
|
||||
return {
|
||||
extractors: {
|
||||
javascript: [{ extractor_root: "" }],
|
||||
python: [{ extractor_root: "" }],
|
||||
},
|
||||
};
|
||||
},
|
||||
const codeql = createStubCodeQL({
|
||||
async betterResolveLanguages() {
|
||||
return {
|
||||
extractors: {
|
||||
javascript: [{ extractor_root: "" }],
|
||||
python: [{ extractor_root: "" }],
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// Only JS, python packs will be ignored
|
||||
const languagesInput = "javascript";
|
||||
|
||||
const config = await configUtils.initConfig(
|
||||
createFeatures([]),
|
||||
createTestInitConfigInputs({
|
||||
languagesInput,
|
||||
configFile: configFilePath,
|
||||
configInput,
|
||||
tempDir,
|
||||
codeql,
|
||||
workspacePath: tempDir,
|
||||
}),
|
||||
);
|
||||
|
||||
t.deepEqual(config.originalUserInput, yaml.load(configInput));
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Only JS, python packs will be ignored
|
||||
const languagesInput = "javascript";
|
||||
|
||||
const config = await configUtils.initConfig(
|
||||
createFeatures([]),
|
||||
createTestInitConfigInputs({
|
||||
languagesInput,
|
||||
configFile: configFilePath,
|
||||
configInput,
|
||||
tempDir,
|
||||
codeql,
|
||||
workspacePath: tempDir,
|
||||
}),
|
||||
);
|
||||
|
||||
t.deepEqual(config.originalUserInput, yaml.load(configInput));
|
||||
});
|
||||
});
|
||||
|
||||
test("API client used when reading remote config", async (t) => {
|
||||
test.serial("API client used when reading remote config", async (t) => {
|
||||
return await withTmpDir(async (tempDir) => {
|
||||
const codeql = createStubCodeQL({
|
||||
async betterResolveLanguages() {
|
||||
@@ -642,34 +648,37 @@ test("API client used when reading remote config", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("Remote config handles the case where a directory is provided", async (t) => {
|
||||
return await withTmpDir(async (tempDir) => {
|
||||
const dummyResponse = []; // directories are returned as arrays
|
||||
mockGetContents(dummyResponse);
|
||||
test.serial(
|
||||
"Remote config handles the case where a directory is provided",
|
||||
async (t) => {
|
||||
return await withTmpDir(async (tempDir) => {
|
||||
const dummyResponse = []; // directories are returned as arrays
|
||||
mockGetContents(dummyResponse);
|
||||
|
||||
const repoReference = "octo-org/codeql-config/config.yaml@main";
|
||||
try {
|
||||
await configUtils.initConfig(
|
||||
createFeatures([]),
|
||||
createTestInitConfigInputs({
|
||||
configFile: repoReference,
|
||||
tempDir,
|
||||
workspacePath: tempDir,
|
||||
}),
|
||||
);
|
||||
throw new Error("initConfig did not throw error");
|
||||
} catch (err) {
|
||||
t.deepEqual(
|
||||
err,
|
||||
new ConfigurationError(
|
||||
errorMessages.getConfigFileDirectoryGivenMessage(repoReference),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
const repoReference = "octo-org/codeql-config/config.yaml@main";
|
||||
try {
|
||||
await configUtils.initConfig(
|
||||
createFeatures([]),
|
||||
createTestInitConfigInputs({
|
||||
configFile: repoReference,
|
||||
tempDir,
|
||||
workspacePath: tempDir,
|
||||
}),
|
||||
);
|
||||
throw new Error("initConfig did not throw error");
|
||||
} catch (err) {
|
||||
t.deepEqual(
|
||||
err,
|
||||
new ConfigurationError(
|
||||
errorMessages.getConfigFileDirectoryGivenMessage(repoReference),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("Invalid format of remote config handled correctly", async (t) => {
|
||||
test.serial("Invalid format of remote config handled correctly", async (t) => {
|
||||
return await withTmpDir(async (tempDir) => {
|
||||
const dummyResponse = {
|
||||
// note no "content" property here
|
||||
@@ -698,7 +707,7 @@ test("Invalid format of remote config handled correctly", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("No detected languages", async (t) => {
|
||||
test.serial("No detected languages", async (t) => {
|
||||
return await withTmpDir(async (tempDir) => {
|
||||
mockListLanguages([]);
|
||||
const codeql = createStubCodeQL({
|
||||
@@ -726,7 +735,7 @@ test("No detected languages", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("Unknown languages", async (t) => {
|
||||
test.serial("Unknown languages", async (t) => {
|
||||
return await withTmpDir(async (tempDir) => {
|
||||
const languagesInput = "rubbish,english";
|
||||
|
||||
@@ -753,7 +762,7 @@ test("Unknown languages", async (t) => {
|
||||
|
||||
const mockLogger = getRunnerLogger(true);
|
||||
|
||||
test("no generateRegistries when registries is undefined", async (t) => {
|
||||
test.serial("no generateRegistries when registries is undefined", async (t) => {
|
||||
return await withTmpDir(async (tmpDir) => {
|
||||
const registriesInput = undefined;
|
||||
const logger = getRunnerLogger(true);
|
||||
@@ -765,24 +774,27 @@ test("no generateRegistries when registries is undefined", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("generateRegistries prefers original CODEQL_REGISTRIES_AUTH", async (t) => {
|
||||
return await withTmpDir(async (tmpDir) => {
|
||||
process.env.CODEQL_REGISTRIES_AUTH = "original";
|
||||
const registriesInput = yaml.dump([
|
||||
{
|
||||
url: "http://ghcr.io",
|
||||
packages: ["codeql/*", "codeql-testing/*"],
|
||||
token: "not-a-token",
|
||||
},
|
||||
]);
|
||||
const logger = getRunnerLogger(true);
|
||||
const { registriesAuthTokens, qlconfigFile } =
|
||||
await configUtils.generateRegistries(registriesInput, tmpDir, logger);
|
||||
test.serial(
|
||||
"generateRegistries prefers original CODEQL_REGISTRIES_AUTH",
|
||||
async (t) => {
|
||||
return await withTmpDir(async (tmpDir) => {
|
||||
process.env.CODEQL_REGISTRIES_AUTH = "original";
|
||||
const registriesInput = yaml.dump([
|
||||
{
|
||||
url: "http://ghcr.io",
|
||||
packages: ["codeql/*", "codeql-testing/*"],
|
||||
token: "not-a-token",
|
||||
},
|
||||
]);
|
||||
const logger = getRunnerLogger(true);
|
||||
const { registriesAuthTokens, qlconfigFile } =
|
||||
await configUtils.generateRegistries(registriesInput, tmpDir, logger);
|
||||
|
||||
t.is(registriesAuthTokens, "original");
|
||||
t.is(qlconfigFile, path.join(tmpDir, "qlconfig.yml"));
|
||||
});
|
||||
});
|
||||
t.is(registriesAuthTokens, "original");
|
||||
t.is(qlconfigFile, path.join(tmpDir, "qlconfig.yml"));
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// getLanguages
|
||||
|
||||
@@ -860,7 +872,7 @@ const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
|
||||
expectedLanguages: ["javascript"],
|
||||
},
|
||||
].forEach((args) => {
|
||||
test(`getLanguages: ${args.name}`, async (t) => {
|
||||
test.serial(`getLanguages: ${args.name}`, async (t) => {
|
||||
const mockRequest = mockLanguagesInRepo(args.languagesInRepository);
|
||||
const stubExtractorEntry = {
|
||||
extractor_root: "",
|
||||
@@ -930,46 +942,55 @@ for (const { displayName, language, feature } of [
|
||||
feature: Feature.DisableCsharpBuildless,
|
||||
},
|
||||
]) {
|
||||
test(`Build mode not overridden when disable ${displayName} buildless feature flag disabled`, async (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const buildMode = await configUtils.parseBuildModeInput(
|
||||
"none",
|
||||
[language],
|
||||
createFeatures([]),
|
||||
getRecordingLogger(messages),
|
||||
);
|
||||
t.is(buildMode, BuildMode.None);
|
||||
t.deepEqual(messages, []);
|
||||
});
|
||||
test.serial(
|
||||
`Build mode not overridden when disable ${displayName} buildless feature flag disabled`,
|
||||
async (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const buildMode = await configUtils.parseBuildModeInput(
|
||||
"none",
|
||||
[language],
|
||||
createFeatures([]),
|
||||
getRecordingLogger(messages),
|
||||
);
|
||||
t.is(buildMode, BuildMode.None);
|
||||
t.deepEqual(messages, []);
|
||||
},
|
||||
);
|
||||
|
||||
test(`Build mode not overridden for other languages when disable ${displayName} buildless feature flag enabled`, async (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const buildMode = await configUtils.parseBuildModeInput(
|
||||
"none",
|
||||
[KnownLanguage.python],
|
||||
createFeatures([feature]),
|
||||
getRecordingLogger(messages),
|
||||
);
|
||||
t.is(buildMode, BuildMode.None);
|
||||
t.deepEqual(messages, []);
|
||||
});
|
||||
test.serial(
|
||||
`Build mode not overridden for other languages when disable ${displayName} buildless feature flag enabled`,
|
||||
async (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const buildMode = await configUtils.parseBuildModeInput(
|
||||
"none",
|
||||
[KnownLanguage.python],
|
||||
createFeatures([feature]),
|
||||
getRecordingLogger(messages),
|
||||
);
|
||||
t.is(buildMode, BuildMode.None);
|
||||
t.deepEqual(messages, []);
|
||||
},
|
||||
);
|
||||
|
||||
test(`Build mode overridden when analyzing ${displayName} and disable ${displayName} buildless feature flag enabled`, async (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const buildMode = await configUtils.parseBuildModeInput(
|
||||
"none",
|
||||
[language],
|
||||
createFeatures([feature]),
|
||||
getRecordingLogger(messages),
|
||||
);
|
||||
t.is(buildMode, BuildMode.Autobuild);
|
||||
t.deepEqual(messages, [
|
||||
{
|
||||
message: `Scanning ${displayName} code without a build is temporarily unavailable. Falling back to 'autobuild' build mode.`,
|
||||
type: "warning",
|
||||
},
|
||||
]);
|
||||
});
|
||||
test.serial(
|
||||
`Build mode overridden when analyzing ${displayName} and disable ${displayName} buildless feature flag enabled`,
|
||||
async (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const buildMode = await configUtils.parseBuildModeInput(
|
||||
"none",
|
||||
[language],
|
||||
createFeatures([feature]),
|
||||
getRecordingLogger(messages),
|
||||
);
|
||||
t.is(buildMode, BuildMode.Autobuild);
|
||||
t.deepEqual(messages, [
|
||||
{
|
||||
message: `Scanning ${displayName} code without a build is temporarily unavailable. Falling back to 'autobuild' build mode.`,
|
||||
type: "warning",
|
||||
},
|
||||
]);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
interface OverlayDatabaseModeTestSetup {
|
||||
@@ -1106,7 +1127,7 @@ const getOverlayDatabaseModeMacro = test.macro({
|
||||
title: (_, title) => `getOverlayDatabaseMode: ${title}`,
|
||||
});
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Environment variable override - Overlay",
|
||||
{
|
||||
@@ -1118,7 +1139,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Environment variable override - OverlayBase",
|
||||
{
|
||||
@@ -1130,7 +1151,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Environment variable override - None",
|
||||
{
|
||||
@@ -1142,7 +1163,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Ignore invalid environment variable",
|
||||
{
|
||||
@@ -1155,7 +1176,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Ignore feature flag when analyzing non-default branch",
|
||||
{
|
||||
@@ -1168,7 +1189,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Overlay-base database on default branch when feature enabled",
|
||||
{
|
||||
@@ -1182,7 +1203,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Overlay-base database on default branch when feature enabled with custom analysis",
|
||||
{
|
||||
@@ -1199,7 +1220,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Overlay-base database on default branch when code-scanning feature enabled",
|
||||
{
|
||||
@@ -1216,7 +1237,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"No overlay-base database on default branch if runner disk space is too low",
|
||||
{
|
||||
@@ -1238,7 +1259,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"No overlay-base database on default branch if we can't determine runner disk space",
|
||||
{
|
||||
@@ -1257,7 +1278,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Overlay-base database on default branch if runner disk space is too low and skip resource checks flag is enabled",
|
||||
{
|
||||
@@ -1279,7 +1300,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"No overlay-base database on default branch if runner disk space is below v2 limit and v2 resource checks enabled",
|
||||
{
|
||||
@@ -1302,7 +1323,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Overlay-base database on default branch if runner disk space is between v2 and v1 limits and v2 resource checks enabled",
|
||||
{
|
||||
@@ -1324,7 +1345,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"No overlay-base database on default branch if runner disk space is between v2 and v1 limits and v2 resource checks not enabled",
|
||||
{
|
||||
@@ -1346,7 +1367,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"No overlay-base database on default branch if memory flag is too low",
|
||||
{
|
||||
@@ -1365,7 +1386,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Overlay-base database on default branch if memory flag is too low but CodeQL >= 2.24.3",
|
||||
{
|
||||
@@ -1384,7 +1405,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Overlay-base database on default branch if memory flag is too low and skip resource checks flag is enabled",
|
||||
{
|
||||
@@ -1403,7 +1424,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"No overlay-base database on default branch when cached status indicates previous failure",
|
||||
{
|
||||
@@ -1423,7 +1444,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"No overlay analysis on PR when cached status indicates previous failure",
|
||||
{
|
||||
@@ -1443,7 +1464,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"No overlay-base database on default branch when code-scanning feature enabled with disable-default-queries",
|
||||
{
|
||||
@@ -1464,7 +1485,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"No overlay-base database on default branch when code-scanning feature enabled with packs",
|
||||
{
|
||||
@@ -1485,7 +1506,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"No overlay-base database on default branch when code-scanning feature enabled with queries",
|
||||
{
|
||||
@@ -1506,7 +1527,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"No overlay-base database on default branch when code-scanning feature enabled with query-filters",
|
||||
{
|
||||
@@ -1527,7 +1548,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"No overlay-base database on default branch when only language-specific feature enabled",
|
||||
{
|
||||
@@ -1542,7 +1563,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"No overlay-base database on default branch when only code-scanning feature enabled",
|
||||
{
|
||||
@@ -1557,7 +1578,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"No overlay-base database on default branch when language-specific feature disabled",
|
||||
{
|
||||
@@ -1572,7 +1593,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Overlay analysis on PR when feature enabled",
|
||||
{
|
||||
@@ -1586,7 +1607,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Overlay analysis on PR when feature enabled with custom analysis",
|
||||
{
|
||||
@@ -1603,7 +1624,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Overlay analysis on PR when code-scanning feature enabled",
|
||||
{
|
||||
@@ -1620,7 +1641,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"No overlay analysis on PR if runner disk space is too low",
|
||||
{
|
||||
@@ -1642,7 +1663,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Overlay analysis on PR if runner disk space is too low and skip resource checks flag is enabled",
|
||||
{
|
||||
@@ -1664,7 +1685,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"No overlay analysis on PR if we can't determine runner disk space",
|
||||
{
|
||||
@@ -1683,7 +1704,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"No overlay analysis on PR if memory flag is too low",
|
||||
{
|
||||
@@ -1702,7 +1723,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Overlay analysis on PR if memory flag is too low but CodeQL >= 2.24.3",
|
||||
{
|
||||
@@ -1721,7 +1742,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Overlay analysis on PR if memory flag is too low and skip resource checks flag is enabled",
|
||||
{
|
||||
@@ -1740,7 +1761,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"No overlay analysis on PR when code-scanning feature enabled with disable-default-queries",
|
||||
{
|
||||
@@ -1761,7 +1782,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"No overlay analysis on PR when code-scanning feature enabled with packs",
|
||||
{
|
||||
@@ -1782,7 +1803,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"No overlay analysis on PR when code-scanning feature enabled with queries",
|
||||
{
|
||||
@@ -1803,7 +1824,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"No overlay analysis on PR when code-scanning feature enabled with query-filters",
|
||||
{
|
||||
@@ -1824,7 +1845,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"No overlay analysis on PR when only language-specific feature enabled",
|
||||
{
|
||||
@@ -1839,7 +1860,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"No overlay analysis on PR when only code-scanning feature enabled",
|
||||
{
|
||||
@@ -1854,7 +1875,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"No overlay analysis on PR when language-specific feature disabled",
|
||||
{
|
||||
@@ -1869,7 +1890,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Overlay PR analysis by env",
|
||||
{
|
||||
@@ -1881,7 +1902,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Overlay PR analysis by env on a runner with low disk space",
|
||||
{
|
||||
@@ -1894,7 +1915,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Overlay PR analysis by feature flag",
|
||||
{
|
||||
@@ -1908,7 +1929,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Fallback due to autobuild with traced language",
|
||||
{
|
||||
@@ -1923,7 +1944,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Fallback due to no build mode with traced language",
|
||||
{
|
||||
@@ -1938,7 +1959,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Fallback due to old CodeQL version",
|
||||
{
|
||||
@@ -1952,7 +1973,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Fallback due to missing git root",
|
||||
{
|
||||
@@ -1966,7 +1987,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Fallback due to old git version",
|
||||
{
|
||||
@@ -1980,7 +2001,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Fallback when git version cannot be determined",
|
||||
{
|
||||
@@ -1994,7 +2015,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"No overlay when disabled via repository property",
|
||||
{
|
||||
@@ -2012,7 +2033,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Overlay not disabled when repository property is false",
|
||||
{
|
||||
@@ -2029,7 +2050,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Environment variable override takes precedence over repository property",
|
||||
{
|
||||
@@ -2046,7 +2067,7 @@ test(
|
||||
|
||||
// Exercise language-specific overlay analysis features code paths
|
||||
for (const language in KnownLanguage) {
|
||||
test(
|
||||
test.serial(
|
||||
getOverlayDatabaseModeMacro,
|
||||
`Check default overlay analysis feature for ${language}`,
|
||||
{
|
||||
@@ -2062,13 +2083,16 @@ for (const language in KnownLanguage) {
|
||||
);
|
||||
}
|
||||
|
||||
test("hasActionsWorkflows doesn't throw if workflows folder doesn't exist", async (t) => {
|
||||
return withTmpDir(async (tmpDir) => {
|
||||
t.notThrows(() => configUtils.hasActionsWorkflows(tmpDir));
|
||||
});
|
||||
});
|
||||
test.serial(
|
||||
"hasActionsWorkflows doesn't throw if workflows folder doesn't exist",
|
||||
async (t) => {
|
||||
return withTmpDir(async (tmpDir) => {
|
||||
t.notThrows(() => configUtils.hasActionsWorkflows(tmpDir));
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("getPrimaryAnalysisConfig - single analysis kind", (t) => {
|
||||
test.serial("getPrimaryAnalysisConfig - single analysis kind", (t) => {
|
||||
// If only one analysis kind is configured, we expect to get the matching configuration.
|
||||
for (const analysisKind of supportedAnalysisKinds) {
|
||||
const singleKind = createTestConfig({ analysisKinds: [analysisKind] });
|
||||
@@ -2076,7 +2100,7 @@ test("getPrimaryAnalysisConfig - single analysis kind", (t) => {
|
||||
}
|
||||
});
|
||||
|
||||
test("getPrimaryAnalysisConfig - Code Scanning + Code Quality", (t) => {
|
||||
test.serial("getPrimaryAnalysisConfig - Code Scanning + Code Quality", (t) => {
|
||||
// For CS+CQ, we expect to get the Code Scanning configuration.
|
||||
const codeScanningAndCodeQuality = createTestConfig({
|
||||
analysisKinds: [AnalysisKind.CodeScanning, AnalysisKind.CodeQuality],
|
||||
|
||||
@@ -82,70 +82,76 @@ function getCodeQL() {
|
||||
});
|
||||
}
|
||||
|
||||
test("Abort database upload if 'upload-database' input set to false", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
sinon
|
||||
.stub(actionsUtil, "getRequiredInput")
|
||||
.withArgs("upload-database")
|
||||
.returns("false");
|
||||
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(true);
|
||||
test.serial(
|
||||
"Abort database upload if 'upload-database' input set to false",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
sinon
|
||||
.stub(actionsUtil, "getRequiredInput")
|
||||
.withArgs("upload-database")
|
||||
.returns("false");
|
||||
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(true);
|
||||
|
||||
const loggedMessages = [];
|
||||
await cleanupAndUploadDatabases(
|
||||
testRepoName,
|
||||
getCodeQL(),
|
||||
getTestConfig(tmpDir),
|
||||
testApiDetails,
|
||||
createFeatures([]),
|
||||
getRecordingLogger(loggedMessages),
|
||||
);
|
||||
t.assert(
|
||||
loggedMessages.find(
|
||||
(v: LoggedMessage) =>
|
||||
v.type === "debug" &&
|
||||
v.message ===
|
||||
"Database upload disabled in workflow. Skipping upload.",
|
||||
) !== undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
const loggedMessages = [];
|
||||
await cleanupAndUploadDatabases(
|
||||
testRepoName,
|
||||
getCodeQL(),
|
||||
getTestConfig(tmpDir),
|
||||
testApiDetails,
|
||||
createFeatures([]),
|
||||
getRecordingLogger(loggedMessages),
|
||||
);
|
||||
t.assert(
|
||||
loggedMessages.find(
|
||||
(v: LoggedMessage) =>
|
||||
v.type === "debug" &&
|
||||
v.message ===
|
||||
"Database upload disabled in workflow. Skipping upload.",
|
||||
) !== undefined,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("Abort database upload if 'analysis-kinds: code-scanning' is not enabled", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
sinon
|
||||
.stub(actionsUtil, "getRequiredInput")
|
||||
.withArgs("upload-database")
|
||||
.returns("true");
|
||||
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(true);
|
||||
test.serial(
|
||||
"Abort database upload if 'analysis-kinds: code-scanning' is not enabled",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
sinon
|
||||
.stub(actionsUtil, "getRequiredInput")
|
||||
.withArgs("upload-database")
|
||||
.returns("true");
|
||||
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(true);
|
||||
|
||||
await mockHttpRequests(201);
|
||||
await mockHttpRequests(201);
|
||||
|
||||
const loggedMessages = [];
|
||||
await cleanupAndUploadDatabases(
|
||||
testRepoName,
|
||||
getCodeQL(),
|
||||
{
|
||||
...getTestConfig(tmpDir),
|
||||
analysisKinds: [AnalysisKind.CodeQuality],
|
||||
},
|
||||
testApiDetails,
|
||||
createFeatures([]),
|
||||
getRecordingLogger(loggedMessages),
|
||||
);
|
||||
t.assert(
|
||||
loggedMessages.find(
|
||||
(v: LoggedMessage) =>
|
||||
v.type === "debug" &&
|
||||
v.message ===
|
||||
"Not uploading database because 'analysis-kinds: code-scanning' is not enabled.",
|
||||
) !== undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
const loggedMessages = [];
|
||||
await cleanupAndUploadDatabases(
|
||||
testRepoName,
|
||||
getCodeQL(),
|
||||
{
|
||||
...getTestConfig(tmpDir),
|
||||
analysisKinds: [AnalysisKind.CodeQuality],
|
||||
},
|
||||
testApiDetails,
|
||||
createFeatures([]),
|
||||
getRecordingLogger(loggedMessages),
|
||||
);
|
||||
t.assert(
|
||||
loggedMessages.find(
|
||||
(v: LoggedMessage) =>
|
||||
v.type === "debug" &&
|
||||
v.message ===
|
||||
"Not uploading database because 'analysis-kinds: code-scanning' is not enabled.",
|
||||
) !== undefined,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("Abort database upload if running against GHES", async (t) => {
|
||||
test.serial("Abort database upload if running against GHES", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
sinon
|
||||
@@ -177,35 +183,38 @@ test("Abort database upload if running against GHES", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("Abort database upload if not analyzing default branch", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
sinon
|
||||
.stub(actionsUtil, "getRequiredInput")
|
||||
.withArgs("upload-database")
|
||||
.returns("true");
|
||||
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(false);
|
||||
test.serial(
|
||||
"Abort database upload if not analyzing default branch",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
sinon
|
||||
.stub(actionsUtil, "getRequiredInput")
|
||||
.withArgs("upload-database")
|
||||
.returns("true");
|
||||
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(false);
|
||||
|
||||
const loggedMessages = [];
|
||||
await cleanupAndUploadDatabases(
|
||||
testRepoName,
|
||||
getCodeQL(),
|
||||
getTestConfig(tmpDir),
|
||||
testApiDetails,
|
||||
createFeatures([]),
|
||||
getRecordingLogger(loggedMessages),
|
||||
);
|
||||
t.assert(
|
||||
loggedMessages.find(
|
||||
(v: LoggedMessage) =>
|
||||
v.type === "debug" &&
|
||||
v.message === "Not analyzing default branch. Skipping upload.",
|
||||
) !== undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
const loggedMessages = [];
|
||||
await cleanupAndUploadDatabases(
|
||||
testRepoName,
|
||||
getCodeQL(),
|
||||
getTestConfig(tmpDir),
|
||||
testApiDetails,
|
||||
createFeatures([]),
|
||||
getRecordingLogger(loggedMessages),
|
||||
);
|
||||
t.assert(
|
||||
loggedMessages.find(
|
||||
(v: LoggedMessage) =>
|
||||
v.type === "debug" &&
|
||||
v.message === "Not analyzing default branch. Skipping upload.",
|
||||
) !== undefined,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("Don't crash if uploading a database fails", async (t) => {
|
||||
test.serial("Don't crash if uploading a database fails", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
sinon
|
||||
@@ -237,7 +246,7 @@ test("Don't crash if uploading a database fails", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("Successfully uploading a database to github.com", async (t) => {
|
||||
test.serial("Successfully uploading a database to github.com", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
sinon
|
||||
@@ -267,7 +276,7 @@ test("Successfully uploading a database to github.com", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("Successfully uploading a database to GHEC-DR", async (t) => {
|
||||
test.serial("Successfully uploading a database to GHEC-DR", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
sinon
|
||||
|
||||
@@ -44,27 +44,33 @@ function makeAbsolutePatterns(tmpDir: string, patterns: string[]): string[] {
|
||||
return patterns.map((pattern) => path.join(tmpDir, pattern));
|
||||
}
|
||||
|
||||
test("getCsharpDependencyDirs - does not include BMN dir if FF is enabled", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
process.env["RUNNER_TEMP"] = tmpDir;
|
||||
const codeql = createStubCodeQL({});
|
||||
const features = createFeatures([]);
|
||||
test.serial(
|
||||
"getCsharpDependencyDirs - does not include BMN dir if FF is enabled",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
process.env["RUNNER_TEMP"] = tmpDir;
|
||||
const codeql = createStubCodeQL({});
|
||||
const features = createFeatures([]);
|
||||
|
||||
const results = await getCsharpDependencyDirs(codeql, features);
|
||||
t.false(results.includes(getCsharpTempDependencyDir()));
|
||||
});
|
||||
});
|
||||
const results = await getCsharpDependencyDirs(codeql, features);
|
||||
t.false(results.includes(getCsharpTempDependencyDir()));
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("getCsharpDependencyDirs - includes BMN dir if FF is enabled", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
process.env["RUNNER_TEMP"] = tmpDir;
|
||||
const codeql = createStubCodeQL({});
|
||||
const features = createFeatures([Feature.CsharpCacheBuildModeNone]);
|
||||
test.serial(
|
||||
"getCsharpDependencyDirs - includes BMN dir if FF is enabled",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
process.env["RUNNER_TEMP"] = tmpDir;
|
||||
const codeql = createStubCodeQL({});
|
||||
const features = createFeatures([Feature.CsharpCacheBuildModeNone]);
|
||||
|
||||
const results = await getCsharpDependencyDirs(codeql, features);
|
||||
t.assert(results.includes(getCsharpTempDependencyDir()));
|
||||
});
|
||||
});
|
||||
const results = await getCsharpDependencyDirs(codeql, features);
|
||||
t.assert(results.includes(getCsharpTempDependencyDir()));
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("makePatternCheck - returns undefined if no patterns match", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
@@ -85,69 +91,81 @@ test("makePatternCheck - returns all patterns if any pattern matches", async (t)
|
||||
});
|
||||
});
|
||||
|
||||
test("getCsharpHashPatterns - returns base patterns if any pattern matches", async (t) => {
|
||||
const codeql = createStubCodeQL({});
|
||||
const features = createFeatures([]);
|
||||
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
|
||||
test.serial(
|
||||
"getCsharpHashPatterns - returns base patterns if any pattern matches",
|
||||
async (t) => {
|
||||
const codeql = createStubCodeQL({});
|
||||
const features = createFeatures([]);
|
||||
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
|
||||
|
||||
makePatternCheckStub
|
||||
.withArgs(CSHARP_BASE_PATTERNS)
|
||||
.resolves(CSHARP_BASE_PATTERNS);
|
||||
makePatternCheckStub.withArgs(CSHARP_EXTRA_PATTERNS).rejects();
|
||||
makePatternCheckStub
|
||||
.withArgs(CSHARP_BASE_PATTERNS)
|
||||
.resolves(CSHARP_BASE_PATTERNS);
|
||||
makePatternCheckStub.withArgs(CSHARP_EXTRA_PATTERNS).rejects();
|
||||
|
||||
await t.notThrowsAsync(async () => {
|
||||
const result = await getCsharpHashPatterns(codeql, features);
|
||||
t.deepEqual(result, CSHARP_BASE_PATTERNS);
|
||||
});
|
||||
});
|
||||
await t.notThrowsAsync(async () => {
|
||||
const result = await getCsharpHashPatterns(codeql, features);
|
||||
t.deepEqual(result, CSHARP_BASE_PATTERNS);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("getCsharpHashPatterns - returns base patterns if any base pattern matches and CsharpNewCacheKey is enabled", async (t) => {
|
||||
const codeql = createStubCodeQL({});
|
||||
const features = createFeatures([Feature.CsharpNewCacheKey]);
|
||||
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
|
||||
test.serial(
|
||||
"getCsharpHashPatterns - returns base patterns if any base pattern matches and CsharpNewCacheKey is enabled",
|
||||
async (t) => {
|
||||
const codeql = createStubCodeQL({});
|
||||
const features = createFeatures([Feature.CsharpNewCacheKey]);
|
||||
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
|
||||
|
||||
makePatternCheckStub
|
||||
.withArgs(CSHARP_BASE_PATTERNS)
|
||||
.resolves(CSHARP_BASE_PATTERNS);
|
||||
makePatternCheckStub
|
||||
.withArgs(CSHARP_EXTRA_PATTERNS)
|
||||
.resolves(CSHARP_EXTRA_PATTERNS);
|
||||
makePatternCheckStub
|
||||
.withArgs(CSHARP_BASE_PATTERNS)
|
||||
.resolves(CSHARP_BASE_PATTERNS);
|
||||
makePatternCheckStub
|
||||
.withArgs(CSHARP_EXTRA_PATTERNS)
|
||||
.resolves(CSHARP_EXTRA_PATTERNS);
|
||||
|
||||
await t.notThrowsAsync(async () => {
|
||||
const result = await getCsharpHashPatterns(codeql, features);
|
||||
t.deepEqual(result, CSHARP_BASE_PATTERNS);
|
||||
});
|
||||
});
|
||||
await t.notThrowsAsync(async () => {
|
||||
const result = await getCsharpHashPatterns(codeql, features);
|
||||
t.deepEqual(result, CSHARP_BASE_PATTERNS);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("getCsharpHashPatterns - returns extra patterns if any extra pattern matches and CsharpNewCacheKey is enabled", async (t) => {
|
||||
const codeql = createStubCodeQL({});
|
||||
const features = createFeatures([Feature.CsharpNewCacheKey]);
|
||||
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
|
||||
test.serial(
|
||||
"getCsharpHashPatterns - returns extra patterns if any extra pattern matches and CsharpNewCacheKey is enabled",
|
||||
async (t) => {
|
||||
const codeql = createStubCodeQL({});
|
||||
const features = createFeatures([Feature.CsharpNewCacheKey]);
|
||||
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
|
||||
|
||||
makePatternCheckStub.withArgs(CSHARP_BASE_PATTERNS).resolves(undefined);
|
||||
makePatternCheckStub
|
||||
.withArgs(CSHARP_EXTRA_PATTERNS)
|
||||
.resolves(CSHARP_EXTRA_PATTERNS);
|
||||
makePatternCheckStub.withArgs(CSHARP_BASE_PATTERNS).resolves(undefined);
|
||||
makePatternCheckStub
|
||||
.withArgs(CSHARP_EXTRA_PATTERNS)
|
||||
.resolves(CSHARP_EXTRA_PATTERNS);
|
||||
|
||||
await t.notThrowsAsync(async () => {
|
||||
const result = await getCsharpHashPatterns(codeql, features);
|
||||
t.deepEqual(result, CSHARP_EXTRA_PATTERNS);
|
||||
});
|
||||
});
|
||||
await t.notThrowsAsync(async () => {
|
||||
const result = await getCsharpHashPatterns(codeql, features);
|
||||
t.deepEqual(result, CSHARP_EXTRA_PATTERNS);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("getCsharpHashPatterns - returns undefined if neither base nor extra patterns match", async (t) => {
|
||||
const codeql = createStubCodeQL({});
|
||||
const features = createFeatures([Feature.CsharpNewCacheKey]);
|
||||
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
|
||||
test.serial(
|
||||
"getCsharpHashPatterns - returns undefined if neither base nor extra patterns match",
|
||||
async (t) => {
|
||||
const codeql = createStubCodeQL({});
|
||||
const features = createFeatures([Feature.CsharpNewCacheKey]);
|
||||
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
|
||||
|
||||
makePatternCheckStub.withArgs(CSHARP_BASE_PATTERNS).resolves(undefined);
|
||||
makePatternCheckStub.withArgs(CSHARP_EXTRA_PATTERNS).resolves(undefined);
|
||||
makePatternCheckStub.withArgs(CSHARP_BASE_PATTERNS).resolves(undefined);
|
||||
makePatternCheckStub.withArgs(CSHARP_EXTRA_PATTERNS).resolves(undefined);
|
||||
|
||||
await t.notThrowsAsync(async () => {
|
||||
const result = await getCsharpHashPatterns(codeql, features);
|
||||
t.deepEqual(result, undefined);
|
||||
});
|
||||
});
|
||||
await t.notThrowsAsync(async () => {
|
||||
const result = await getCsharpHashPatterns(codeql, features);
|
||||
t.deepEqual(result, undefined);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("checkHashPatterns - logs when no patterns match", async (t) => {
|
||||
const codeql = createStubCodeQL({});
|
||||
@@ -238,160 +256,169 @@ function makeMockCacheCheck(mockCacheKeys: string[]): RestoreCacheFunc {
|
||||
};
|
||||
}
|
||||
|
||||
test("downloadDependencyCaches - does not restore caches with feature keys if no features are enabled", async (t) => {
|
||||
process.env["RUNNER_OS"] = "Linux";
|
||||
test.serial(
|
||||
"downloadDependencyCaches - does not restore caches with feature keys if no features are enabled",
|
||||
async (t) => {
|
||||
process.env["RUNNER_OS"] = "Linux";
|
||||
|
||||
const codeql = createStubCodeQL({});
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
const codeql = createStubCodeQL({});
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
|
||||
sinon.stub(glob, "hashFiles").resolves("abcdef");
|
||||
sinon.stub(glob, "hashFiles").resolves("abcdef");
|
||||
|
||||
const keyWithFeature = await cacheKey(
|
||||
codeql,
|
||||
createFeatures([Feature.CsharpNewCacheKey]),
|
||||
KnownLanguage.csharp,
|
||||
// Patterns don't matter here because we have stubbed `hashFiles` to always return a specific hash above.
|
||||
[],
|
||||
);
|
||||
const keyWithFeature = await cacheKey(
|
||||
codeql,
|
||||
createFeatures([Feature.CsharpNewCacheKey]),
|
||||
KnownLanguage.csharp,
|
||||
// Patterns don't matter here because we have stubbed `hashFiles` to always return a specific hash above.
|
||||
[],
|
||||
);
|
||||
|
||||
const restoreCacheStub = sinon
|
||||
.stub(actionsCache, "restoreCache")
|
||||
.callsFake(makeMockCacheCheck([keyWithFeature]));
|
||||
const restoreCacheStub = sinon
|
||||
.stub(actionsCache, "restoreCache")
|
||||
.callsFake(makeMockCacheCheck([keyWithFeature]));
|
||||
|
||||
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
|
||||
makePatternCheckStub
|
||||
.withArgs(CSHARP_BASE_PATTERNS)
|
||||
.resolves(CSHARP_BASE_PATTERNS);
|
||||
makePatternCheckStub.withArgs(CSHARP_EXTRA_PATTERNS).resolves(undefined);
|
||||
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
|
||||
makePatternCheckStub
|
||||
.withArgs(CSHARP_BASE_PATTERNS)
|
||||
.resolves(CSHARP_BASE_PATTERNS);
|
||||
makePatternCheckStub.withArgs(CSHARP_EXTRA_PATTERNS).resolves(undefined);
|
||||
|
||||
const result = await downloadDependencyCaches(
|
||||
codeql,
|
||||
createFeatures([]),
|
||||
[KnownLanguage.csharp],
|
||||
logger,
|
||||
);
|
||||
const statusReport = result.statusReport;
|
||||
t.is(statusReport.length, 1);
|
||||
t.is(statusReport[0].language, KnownLanguage.csharp);
|
||||
t.is(statusReport[0].hit_kind, CacheHitKind.Miss);
|
||||
t.deepEqual(result.restoredKeys, []);
|
||||
t.assert(restoreCacheStub.calledOnce);
|
||||
});
|
||||
const result = await downloadDependencyCaches(
|
||||
codeql,
|
||||
createFeatures([]),
|
||||
[KnownLanguage.csharp],
|
||||
logger,
|
||||
);
|
||||
const statusReport = result.statusReport;
|
||||
t.is(statusReport.length, 1);
|
||||
t.is(statusReport[0].language, KnownLanguage.csharp);
|
||||
t.is(statusReport[0].hit_kind, CacheHitKind.Miss);
|
||||
t.deepEqual(result.restoredKeys, []);
|
||||
t.assert(restoreCacheStub.calledOnce);
|
||||
},
|
||||
);
|
||||
|
||||
test("downloadDependencyCaches - restores caches with feature keys if features are enabled", async (t) => {
|
||||
process.env["RUNNER_OS"] = "Linux";
|
||||
test.serial(
|
||||
"downloadDependencyCaches - restores caches with feature keys if features are enabled",
|
||||
async (t) => {
|
||||
process.env["RUNNER_OS"] = "Linux";
|
||||
|
||||
const codeql = createStubCodeQL({});
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
const features = createFeatures([Feature.CsharpNewCacheKey]);
|
||||
const codeql = createStubCodeQL({});
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
const features = createFeatures([Feature.CsharpNewCacheKey]);
|
||||
|
||||
const mockHash = "abcdef";
|
||||
sinon.stub(glob, "hashFiles").resolves(mockHash);
|
||||
const mockHash = "abcdef";
|
||||
sinon.stub(glob, "hashFiles").resolves(mockHash);
|
||||
|
||||
const keyWithFeature = await cacheKey(
|
||||
codeql,
|
||||
features,
|
||||
KnownLanguage.csharp,
|
||||
// Patterns don't matter here because we have stubbed `hashFiles` to always return a specific hash above.
|
||||
[],
|
||||
);
|
||||
const keyWithFeature = await cacheKey(
|
||||
codeql,
|
||||
features,
|
||||
KnownLanguage.csharp,
|
||||
// Patterns don't matter here because we have stubbed `hashFiles` to always return a specific hash above.
|
||||
[],
|
||||
);
|
||||
|
||||
const restoreCacheStub = sinon
|
||||
.stub(actionsCache, "restoreCache")
|
||||
.callsFake(makeMockCacheCheck([keyWithFeature]));
|
||||
const restoreCacheStub = sinon
|
||||
.stub(actionsCache, "restoreCache")
|
||||
.callsFake(makeMockCacheCheck([keyWithFeature]));
|
||||
|
||||
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
|
||||
makePatternCheckStub
|
||||
.withArgs(CSHARP_BASE_PATTERNS)
|
||||
.resolves(CSHARP_BASE_PATTERNS);
|
||||
makePatternCheckStub.withArgs(CSHARP_EXTRA_PATTERNS).resolves(undefined);
|
||||
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
|
||||
makePatternCheckStub
|
||||
.withArgs(CSHARP_BASE_PATTERNS)
|
||||
.resolves(CSHARP_BASE_PATTERNS);
|
||||
makePatternCheckStub.withArgs(CSHARP_EXTRA_PATTERNS).resolves(undefined);
|
||||
|
||||
const result = await downloadDependencyCaches(
|
||||
codeql,
|
||||
features,
|
||||
[KnownLanguage.csharp],
|
||||
logger,
|
||||
);
|
||||
const result = await downloadDependencyCaches(
|
||||
codeql,
|
||||
features,
|
||||
[KnownLanguage.csharp],
|
||||
logger,
|
||||
);
|
||||
|
||||
// Check that the status report for telemetry indicates that one cache was restored with an exact match.
|
||||
const statusReport = result.statusReport;
|
||||
t.is(statusReport.length, 1);
|
||||
t.is(statusReport[0].language, KnownLanguage.csharp);
|
||||
t.is(statusReport[0].hit_kind, CacheHitKind.Exact);
|
||||
// Check that the status report for telemetry indicates that one cache was restored with an exact match.
|
||||
const statusReport = result.statusReport;
|
||||
t.is(statusReport.length, 1);
|
||||
t.is(statusReport[0].language, KnownLanguage.csharp);
|
||||
t.is(statusReport[0].hit_kind, CacheHitKind.Exact);
|
||||
|
||||
// Check that the restored key has been returned.
|
||||
const restoredKeys = result.restoredKeys;
|
||||
t.is(restoredKeys.length, 1);
|
||||
t.assert(
|
||||
restoredKeys[0].endsWith(mockHash),
|
||||
"Expected restored key to end with hash returned by `hashFiles`",
|
||||
);
|
||||
// Check that the restored key has been returned.
|
||||
const restoredKeys = result.restoredKeys;
|
||||
t.is(restoredKeys.length, 1);
|
||||
t.assert(
|
||||
restoredKeys[0].endsWith(mockHash),
|
||||
"Expected restored key to end with hash returned by `hashFiles`",
|
||||
);
|
||||
|
||||
// `restoreCache` should have been called exactly once.
|
||||
t.assert(restoreCacheStub.calledOnce);
|
||||
});
|
||||
// `restoreCache` should have been called exactly once.
|
||||
t.assert(restoreCacheStub.calledOnce);
|
||||
},
|
||||
);
|
||||
|
||||
test("downloadDependencyCaches - restores caches with feature keys if features are enabled for partial matches", async (t) => {
|
||||
process.env["RUNNER_OS"] = "Linux";
|
||||
test.serial(
|
||||
"downloadDependencyCaches - restores caches with feature keys if features are enabled for partial matches",
|
||||
async (t) => {
|
||||
process.env["RUNNER_OS"] = "Linux";
|
||||
|
||||
const codeql = createStubCodeQL({});
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
const features = createFeatures([Feature.CsharpNewCacheKey]);
|
||||
const codeql = createStubCodeQL({});
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
const features = createFeatures([Feature.CsharpNewCacheKey]);
|
||||
|
||||
// We expect two calls to `hashFiles`: the first by the call to `cacheKey` below,
|
||||
// and the second by `downloadDependencyCaches`. We use the result of the first
|
||||
// call as part of the cache key that identifies a mock, existing cache. The result
|
||||
// of the second call is for the primary restore key, which we don't want to match
|
||||
// the first key so that we can test the restore keys logic.
|
||||
const restoredHash = "abcdef";
|
||||
const hashFilesStub = sinon.stub(glob, "hashFiles");
|
||||
hashFilesStub.onFirstCall().resolves(restoredHash);
|
||||
hashFilesStub.onSecondCall().resolves("123456");
|
||||
// We expect two calls to `hashFiles`: the first by the call to `cacheKey` below,
|
||||
// and the second by `downloadDependencyCaches`. We use the result of the first
|
||||
// call as part of the cache key that identifies a mock, existing cache. The result
|
||||
// of the second call is for the primary restore key, which we don't want to match
|
||||
// the first key so that we can test the restore keys logic.
|
||||
const restoredHash = "abcdef";
|
||||
const hashFilesStub = sinon.stub(glob, "hashFiles");
|
||||
hashFilesStub.onFirstCall().resolves(restoredHash);
|
||||
hashFilesStub.onSecondCall().resolves("123456");
|
||||
|
||||
const keyWithFeature = await cacheKey(
|
||||
codeql,
|
||||
features,
|
||||
KnownLanguage.csharp,
|
||||
// Patterns don't matter here because we have stubbed `hashFiles` to always return a specific hash above.
|
||||
[],
|
||||
);
|
||||
const keyWithFeature = await cacheKey(
|
||||
codeql,
|
||||
features,
|
||||
KnownLanguage.csharp,
|
||||
// Patterns don't matter here because we have stubbed `hashFiles` to always return a specific hash above.
|
||||
[],
|
||||
);
|
||||
|
||||
const restoreCacheStub = sinon
|
||||
.stub(actionsCache, "restoreCache")
|
||||
.callsFake(makeMockCacheCheck([keyWithFeature]));
|
||||
const restoreCacheStub = sinon
|
||||
.stub(actionsCache, "restoreCache")
|
||||
.callsFake(makeMockCacheCheck([keyWithFeature]));
|
||||
|
||||
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
|
||||
makePatternCheckStub
|
||||
.withArgs(CSHARP_BASE_PATTERNS)
|
||||
.resolves(CSHARP_BASE_PATTERNS);
|
||||
makePatternCheckStub.withArgs(CSHARP_EXTRA_PATTERNS).resolves(undefined);
|
||||
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
|
||||
makePatternCheckStub
|
||||
.withArgs(CSHARP_BASE_PATTERNS)
|
||||
.resolves(CSHARP_BASE_PATTERNS);
|
||||
makePatternCheckStub.withArgs(CSHARP_EXTRA_PATTERNS).resolves(undefined);
|
||||
|
||||
const result = await downloadDependencyCaches(
|
||||
codeql,
|
||||
features,
|
||||
[KnownLanguage.csharp],
|
||||
logger,
|
||||
);
|
||||
const result = await downloadDependencyCaches(
|
||||
codeql,
|
||||
features,
|
||||
[KnownLanguage.csharp],
|
||||
logger,
|
||||
);
|
||||
|
||||
// Check that the status report for telemetry indicates that one cache was restored with a partial match.
|
||||
const statusReport = result.statusReport;
|
||||
t.is(statusReport.length, 1);
|
||||
t.is(statusReport[0].language, KnownLanguage.csharp);
|
||||
t.is(statusReport[0].hit_kind, CacheHitKind.Partial);
|
||||
// Check that the status report for telemetry indicates that one cache was restored with a partial match.
|
||||
const statusReport = result.statusReport;
|
||||
t.is(statusReport.length, 1);
|
||||
t.is(statusReport[0].language, KnownLanguage.csharp);
|
||||
t.is(statusReport[0].hit_kind, CacheHitKind.Partial);
|
||||
|
||||
// Check that the restored key has been returned.
|
||||
const restoredKeys = result.restoredKeys;
|
||||
t.is(restoredKeys.length, 1);
|
||||
t.assert(
|
||||
restoredKeys[0].endsWith(restoredHash),
|
||||
"Expected restored key to end with hash returned by `hashFiles`",
|
||||
);
|
||||
// Check that the restored key has been returned.
|
||||
const restoredKeys = result.restoredKeys;
|
||||
t.is(restoredKeys.length, 1);
|
||||
t.assert(
|
||||
restoredKeys[0].endsWith(restoredHash),
|
||||
"Expected restored key to end with hash returned by `hashFiles`",
|
||||
);
|
||||
|
||||
t.assert(restoreCacheStub.calledOnce);
|
||||
});
|
||||
t.assert(restoreCacheStub.calledOnce);
|
||||
},
|
||||
);
|
||||
|
||||
test("uploadDependencyCaches - skips upload for a language with no cache config", async (t) => {
|
||||
const codeql = createStubCodeQL({});
|
||||
@@ -409,148 +436,62 @@ test("uploadDependencyCaches - skips upload for a language with no cache config"
|
||||
]);
|
||||
});
|
||||
|
||||
test("uploadDependencyCaches - skips upload if no files for the hash exist", async (t) => {
|
||||
const codeql = createStubCodeQL({});
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
const features = createFeatures([]);
|
||||
const config = createTestConfig({
|
||||
languages: [KnownLanguage.go],
|
||||
});
|
||||
test.serial(
|
||||
"uploadDependencyCaches - skips upload if no files for the hash exist",
|
||||
async (t) => {
|
||||
const codeql = createStubCodeQL({});
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
const features = createFeatures([]);
|
||||
const config = createTestConfig({
|
||||
languages: [KnownLanguage.go],
|
||||
});
|
||||
|
||||
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
|
||||
makePatternCheckStub.resolves(undefined);
|
||||
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
|
||||
makePatternCheckStub.resolves(undefined);
|
||||
|
||||
const result = await uploadDependencyCaches(codeql, features, config, logger);
|
||||
t.is(result.length, 1);
|
||||
t.is(result[0].language, KnownLanguage.go);
|
||||
t.is(result[0].result, CacheStoreResult.NoHash);
|
||||
});
|
||||
const result = await uploadDependencyCaches(
|
||||
codeql,
|
||||
features,
|
||||
config,
|
||||
logger,
|
||||
);
|
||||
t.is(result.length, 1);
|
||||
t.is(result[0].language, KnownLanguage.go);
|
||||
t.is(result[0].result, CacheStoreResult.NoHash);
|
||||
},
|
||||
);
|
||||
|
||||
test("uploadDependencyCaches - skips upload if we know the cache already exists", async (t) => {
|
||||
process.env["RUNNER_OS"] = "Linux";
|
||||
test.serial(
|
||||
"uploadDependencyCaches - skips upload if we know the cache already exists",
|
||||
async (t) => {
|
||||
process.env["RUNNER_OS"] = "Linux";
|
||||
|
||||
const codeql = createStubCodeQL({});
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
const features = createFeatures([]);
|
||||
const codeql = createStubCodeQL({});
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
const features = createFeatures([]);
|
||||
|
||||
const mockHash = "abcdef";
|
||||
sinon.stub(glob, "hashFiles").resolves(mockHash);
|
||||
const mockHash = "abcdef";
|
||||
sinon.stub(glob, "hashFiles").resolves(mockHash);
|
||||
|
||||
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
|
||||
makePatternCheckStub
|
||||
.withArgs(CSHARP_BASE_PATTERNS)
|
||||
.resolves(CSHARP_BASE_PATTERNS);
|
||||
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
|
||||
makePatternCheckStub
|
||||
.withArgs(CSHARP_BASE_PATTERNS)
|
||||
.resolves(CSHARP_BASE_PATTERNS);
|
||||
|
||||
const primaryCacheKey = await cacheKey(
|
||||
codeql,
|
||||
features,
|
||||
KnownLanguage.csharp,
|
||||
CSHARP_BASE_PATTERNS,
|
||||
);
|
||||
const primaryCacheKey = await cacheKey(
|
||||
codeql,
|
||||
features,
|
||||
KnownLanguage.csharp,
|
||||
CSHARP_BASE_PATTERNS,
|
||||
);
|
||||
|
||||
const config = createTestConfig({
|
||||
languages: [KnownLanguage.csharp],
|
||||
dependencyCachingRestoredKeys: [primaryCacheKey],
|
||||
});
|
||||
const config = createTestConfig({
|
||||
languages: [KnownLanguage.csharp],
|
||||
dependencyCachingRestoredKeys: [primaryCacheKey],
|
||||
});
|
||||
|
||||
const result = await uploadDependencyCaches(codeql, features, config, logger);
|
||||
t.is(result.length, 1);
|
||||
t.is(result[0].language, KnownLanguage.csharp);
|
||||
t.is(result[0].result, CacheStoreResult.Duplicate);
|
||||
});
|
||||
|
||||
test("uploadDependencyCaches - skips upload if cache size is 0", async (t) => {
|
||||
process.env["RUNNER_OS"] = "Linux";
|
||||
|
||||
const codeql = createStubCodeQL({});
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
const features = createFeatures([]);
|
||||
|
||||
const mockHash = "abcdef";
|
||||
sinon.stub(glob, "hashFiles").resolves(mockHash);
|
||||
|
||||
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
|
||||
makePatternCheckStub
|
||||
.withArgs(CSHARP_BASE_PATTERNS)
|
||||
.resolves(CSHARP_BASE_PATTERNS);
|
||||
|
||||
sinon.stub(cachingUtils, "getTotalCacheSize").resolves(0);
|
||||
|
||||
const config = createTestConfig({
|
||||
languages: [KnownLanguage.csharp],
|
||||
});
|
||||
|
||||
const result = await uploadDependencyCaches(codeql, features, config, logger);
|
||||
t.is(result.length, 1);
|
||||
t.is(result[0].language, KnownLanguage.csharp);
|
||||
t.is(result[0].result, CacheStoreResult.Empty);
|
||||
|
||||
checkExpectedLogMessages(t, messages, [
|
||||
"Skipping upload of dependency cache",
|
||||
]);
|
||||
});
|
||||
|
||||
test("uploadDependencyCaches - uploads caches when all requirements are met", async (t) => {
|
||||
process.env["RUNNER_OS"] = "Linux";
|
||||
|
||||
const codeql = createStubCodeQL({});
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
const features = createFeatures([]);
|
||||
|
||||
const mockHash = "abcdef";
|
||||
sinon.stub(glob, "hashFiles").resolves(mockHash);
|
||||
|
||||
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
|
||||
makePatternCheckStub
|
||||
.withArgs(CSHARP_BASE_PATTERNS)
|
||||
.resolves(CSHARP_BASE_PATTERNS);
|
||||
|
||||
sinon.stub(cachingUtils, "getTotalCacheSize").resolves(1024);
|
||||
sinon.stub(actionsCache, "saveCache").resolves();
|
||||
|
||||
const config = createTestConfig({
|
||||
languages: [KnownLanguage.csharp],
|
||||
});
|
||||
|
||||
const result = await uploadDependencyCaches(codeql, features, config, logger);
|
||||
t.is(result.length, 1);
|
||||
t.is(result[0].language, KnownLanguage.csharp);
|
||||
t.is(result[0].result, CacheStoreResult.Stored);
|
||||
t.is(result[0].upload_size_bytes, 1024);
|
||||
|
||||
checkExpectedLogMessages(t, messages, ["Uploading cache of size"]);
|
||||
});
|
||||
|
||||
test("uploadDependencyCaches - catches `ReserveCacheError` exceptions", async (t) => {
|
||||
process.env["RUNNER_OS"] = "Linux";
|
||||
|
||||
const codeql = createStubCodeQL({});
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
const features = createFeatures([]);
|
||||
|
||||
const mockHash = "abcdef";
|
||||
sinon.stub(glob, "hashFiles").resolves(mockHash);
|
||||
|
||||
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
|
||||
makePatternCheckStub
|
||||
.withArgs(CSHARP_BASE_PATTERNS)
|
||||
.resolves(CSHARP_BASE_PATTERNS);
|
||||
|
||||
sinon.stub(cachingUtils, "getTotalCacheSize").resolves(1024);
|
||||
sinon
|
||||
.stub(actionsCache, "saveCache")
|
||||
.throws(new actionsCache.ReserveCacheError("Already in use"));
|
||||
|
||||
const config = createTestConfig({
|
||||
languages: [KnownLanguage.csharp],
|
||||
});
|
||||
|
||||
await t.notThrowsAsync(async () => {
|
||||
const result = await uploadDependencyCaches(
|
||||
codeql,
|
||||
features,
|
||||
@@ -560,12 +501,133 @@ test("uploadDependencyCaches - catches `ReserveCacheError` exceptions", async (t
|
||||
t.is(result.length, 1);
|
||||
t.is(result[0].language, KnownLanguage.csharp);
|
||||
t.is(result[0].result, CacheStoreResult.Duplicate);
|
||||
},
|
||||
);
|
||||
|
||||
checkExpectedLogMessages(t, messages, ["Not uploading cache for"]);
|
||||
});
|
||||
});
|
||||
test.serial(
|
||||
"uploadDependencyCaches - skips upload if cache size is 0",
|
||||
async (t) => {
|
||||
process.env["RUNNER_OS"] = "Linux";
|
||||
|
||||
test("uploadDependencyCaches - throws other exceptions", async (t) => {
|
||||
const codeql = createStubCodeQL({});
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
const features = createFeatures([]);
|
||||
|
||||
const mockHash = "abcdef";
|
||||
sinon.stub(glob, "hashFiles").resolves(mockHash);
|
||||
|
||||
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
|
||||
makePatternCheckStub
|
||||
.withArgs(CSHARP_BASE_PATTERNS)
|
||||
.resolves(CSHARP_BASE_PATTERNS);
|
||||
|
||||
sinon.stub(cachingUtils, "getTotalCacheSize").resolves(0);
|
||||
|
||||
const config = createTestConfig({
|
||||
languages: [KnownLanguage.csharp],
|
||||
});
|
||||
|
||||
const result = await uploadDependencyCaches(
|
||||
codeql,
|
||||
features,
|
||||
config,
|
||||
logger,
|
||||
);
|
||||
t.is(result.length, 1);
|
||||
t.is(result[0].language, KnownLanguage.csharp);
|
||||
t.is(result[0].result, CacheStoreResult.Empty);
|
||||
|
||||
checkExpectedLogMessages(t, messages, [
|
||||
"Skipping upload of dependency cache",
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"uploadDependencyCaches - uploads caches when all requirements are met",
|
||||
async (t) => {
|
||||
process.env["RUNNER_OS"] = "Linux";
|
||||
|
||||
const codeql = createStubCodeQL({});
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
const features = createFeatures([]);
|
||||
|
||||
const mockHash = "abcdef";
|
||||
sinon.stub(glob, "hashFiles").resolves(mockHash);
|
||||
|
||||
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
|
||||
makePatternCheckStub
|
||||
.withArgs(CSHARP_BASE_PATTERNS)
|
||||
.resolves(CSHARP_BASE_PATTERNS);
|
||||
|
||||
sinon.stub(cachingUtils, "getTotalCacheSize").resolves(1024);
|
||||
sinon.stub(actionsCache, "saveCache").resolves();
|
||||
|
||||
const config = createTestConfig({
|
||||
languages: [KnownLanguage.csharp],
|
||||
});
|
||||
|
||||
const result = await uploadDependencyCaches(
|
||||
codeql,
|
||||
features,
|
||||
config,
|
||||
logger,
|
||||
);
|
||||
t.is(result.length, 1);
|
||||
t.is(result[0].language, KnownLanguage.csharp);
|
||||
t.is(result[0].result, CacheStoreResult.Stored);
|
||||
t.is(result[0].upload_size_bytes, 1024);
|
||||
|
||||
checkExpectedLogMessages(t, messages, ["Uploading cache of size"]);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"uploadDependencyCaches - catches `ReserveCacheError` exceptions",
|
||||
async (t) => {
|
||||
process.env["RUNNER_OS"] = "Linux";
|
||||
|
||||
const codeql = createStubCodeQL({});
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
const features = createFeatures([]);
|
||||
|
||||
const mockHash = "abcdef";
|
||||
sinon.stub(glob, "hashFiles").resolves(mockHash);
|
||||
|
||||
const makePatternCheckStub = sinon.stub(internal, "makePatternCheck");
|
||||
makePatternCheckStub
|
||||
.withArgs(CSHARP_BASE_PATTERNS)
|
||||
.resolves(CSHARP_BASE_PATTERNS);
|
||||
|
||||
sinon.stub(cachingUtils, "getTotalCacheSize").resolves(1024);
|
||||
sinon
|
||||
.stub(actionsCache, "saveCache")
|
||||
.throws(new actionsCache.ReserveCacheError("Already in use"));
|
||||
|
||||
const config = createTestConfig({
|
||||
languages: [KnownLanguage.csharp],
|
||||
});
|
||||
|
||||
await t.notThrowsAsync(async () => {
|
||||
const result = await uploadDependencyCaches(
|
||||
codeql,
|
||||
features,
|
||||
config,
|
||||
logger,
|
||||
);
|
||||
t.is(result.length, 1);
|
||||
t.is(result[0].language, KnownLanguage.csharp);
|
||||
t.is(result[0].result, CacheStoreResult.Duplicate);
|
||||
|
||||
checkExpectedLogMessages(t, messages, ["Not uploading cache for"]);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial("uploadDependencyCaches - throws other exceptions", async (t) => {
|
||||
process.env["RUNNER_OS"] = "Linux";
|
||||
|
||||
const codeql = createStubCodeQL({});
|
||||
|
||||
@@ -97,14 +97,14 @@ const testShouldPerformDiffInformedAnalysis = test.macro({
|
||||
title: (_, title) => `shouldPerformDiffInformedAnalysis: ${title}`,
|
||||
});
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
testShouldPerformDiffInformedAnalysis,
|
||||
"returns true in the default test case",
|
||||
{},
|
||||
true,
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
testShouldPerformDiffInformedAnalysis,
|
||||
"returns false when feature flag is disabled from the API",
|
||||
{
|
||||
@@ -113,7 +113,7 @@ test(
|
||||
false,
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
testShouldPerformDiffInformedAnalysis,
|
||||
"returns false when CODEQL_ACTION_DIFF_INFORMED_QUERIES is set to false",
|
||||
{
|
||||
@@ -123,7 +123,7 @@ test(
|
||||
false,
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
testShouldPerformDiffInformedAnalysis,
|
||||
"returns true when CODEQL_ACTION_DIFF_INFORMED_QUERIES is set to true",
|
||||
{
|
||||
@@ -133,7 +133,7 @@ test(
|
||||
true,
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
testShouldPerformDiffInformedAnalysis,
|
||||
"returns false for CodeQL version 2.20.0",
|
||||
{
|
||||
@@ -142,7 +142,7 @@ test(
|
||||
false,
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
testShouldPerformDiffInformedAnalysis,
|
||||
"returns false for invalid GHES version",
|
||||
{
|
||||
@@ -154,7 +154,7 @@ test(
|
||||
false,
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
testShouldPerformDiffInformedAnalysis,
|
||||
"returns false for GHES version 3.18.5",
|
||||
{
|
||||
@@ -166,7 +166,7 @@ test(
|
||||
false,
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
testShouldPerformDiffInformedAnalysis,
|
||||
"returns true for GHES version 3.19.0",
|
||||
{
|
||||
@@ -178,7 +178,7 @@ test(
|
||||
true,
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
testShouldPerformDiffInformedAnalysis,
|
||||
"returns false when not a pull request",
|
||||
{
|
||||
@@ -202,12 +202,12 @@ function runGetDiffRanges(changes: number, patch: string[] | undefined): any {
|
||||
);
|
||||
}
|
||||
|
||||
test("getDiffRanges: file unchanged", async (t) => {
|
||||
test.serial("getDiffRanges: file unchanged", async (t) => {
|
||||
const diffRanges = runGetDiffRanges(0, undefined);
|
||||
t.deepEqual(diffRanges, []);
|
||||
});
|
||||
|
||||
test("getDiffRanges: file diff too large", async (t) => {
|
||||
test.serial("getDiffRanges: file diff too large", async (t) => {
|
||||
const diffRanges = runGetDiffRanges(1000000, undefined);
|
||||
t.deepEqual(diffRanges, [
|
||||
{
|
||||
@@ -218,43 +218,49 @@ test("getDiffRanges: file diff too large", async (t) => {
|
||||
]);
|
||||
});
|
||||
|
||||
test("getDiffRanges: diff thunk with single addition range", async (t) => {
|
||||
const diffRanges = runGetDiffRanges(2, [
|
||||
"@@ -30,6 +50,8 @@",
|
||||
" a",
|
||||
" b",
|
||||
" c",
|
||||
"+1",
|
||||
"+2",
|
||||
" d",
|
||||
" e",
|
||||
" f",
|
||||
]);
|
||||
t.deepEqual(diffRanges, [
|
||||
{
|
||||
path: "/checkout/path/test.txt",
|
||||
startLine: 53,
|
||||
endLine: 54,
|
||||
},
|
||||
]);
|
||||
});
|
||||
test.serial(
|
||||
"getDiffRanges: diff thunk with single addition range",
|
||||
async (t) => {
|
||||
const diffRanges = runGetDiffRanges(2, [
|
||||
"@@ -30,6 +50,8 @@",
|
||||
" a",
|
||||
" b",
|
||||
" c",
|
||||
"+1",
|
||||
"+2",
|
||||
" d",
|
||||
" e",
|
||||
" f",
|
||||
]);
|
||||
t.deepEqual(diffRanges, [
|
||||
{
|
||||
path: "/checkout/path/test.txt",
|
||||
startLine: 53,
|
||||
endLine: 54,
|
||||
},
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
test("getDiffRanges: diff thunk with single deletion range", async (t) => {
|
||||
const diffRanges = runGetDiffRanges(2, [
|
||||
"@@ -30,8 +50,6 @@",
|
||||
" a",
|
||||
" b",
|
||||
" c",
|
||||
"-1",
|
||||
"-2",
|
||||
" d",
|
||||
" e",
|
||||
" f",
|
||||
]);
|
||||
t.deepEqual(diffRanges, []);
|
||||
});
|
||||
test.serial(
|
||||
"getDiffRanges: diff thunk with single deletion range",
|
||||
async (t) => {
|
||||
const diffRanges = runGetDiffRanges(2, [
|
||||
"@@ -30,8 +50,6 @@",
|
||||
" a",
|
||||
" b",
|
||||
" c",
|
||||
"-1",
|
||||
"-2",
|
||||
" d",
|
||||
" e",
|
||||
" f",
|
||||
]);
|
||||
t.deepEqual(diffRanges, []);
|
||||
},
|
||||
);
|
||||
|
||||
test("getDiffRanges: diff thunk with single update range", async (t) => {
|
||||
test.serial("getDiffRanges: diff thunk with single update range", async (t) => {
|
||||
const diffRanges = runGetDiffRanges(2, [
|
||||
"@@ -30,7 +50,7 @@",
|
||||
" a",
|
||||
@@ -275,7 +281,7 @@ test("getDiffRanges: diff thunk with single update range", async (t) => {
|
||||
]);
|
||||
});
|
||||
|
||||
test("getDiffRanges: diff thunk with addition ranges", async (t) => {
|
||||
test.serial("getDiffRanges: diff thunk with addition ranges", async (t) => {
|
||||
const diffRanges = runGetDiffRanges(2, [
|
||||
"@@ -30,7 +50,9 @@",
|
||||
" a",
|
||||
@@ -302,7 +308,7 @@ test("getDiffRanges: diff thunk with addition ranges", async (t) => {
|
||||
]);
|
||||
});
|
||||
|
||||
test("getDiffRanges: diff thunk with mixed ranges", async (t) => {
|
||||
test.serial("getDiffRanges: diff thunk with mixed ranges", async (t) => {
|
||||
const diffRanges = runGetDiffRanges(2, [
|
||||
"@@ -30,7 +50,7 @@",
|
||||
" a",
|
||||
@@ -334,7 +340,7 @@ test("getDiffRanges: diff thunk with mixed ranges", async (t) => {
|
||||
]);
|
||||
});
|
||||
|
||||
test("getDiffRanges: multiple diff thunks", async (t) => {
|
||||
test.serial("getDiffRanges: multiple diff thunks", async (t) => {
|
||||
const diffRanges = runGetDiffRanges(2, [
|
||||
"@@ -30,6 +50,8 @@",
|
||||
" a",
|
||||
@@ -369,7 +375,7 @@ test("getDiffRanges: multiple diff thunks", async (t) => {
|
||||
]);
|
||||
});
|
||||
|
||||
test("getDiffRanges: no diff context lines", async (t) => {
|
||||
test.serial("getDiffRanges: no diff context lines", async (t) => {
|
||||
const diffRanges = runGetDiffRanges(2, ["@@ -30 +50,2 @@", "+1", "+2"]);
|
||||
t.deepEqual(diffRanges, [
|
||||
{
|
||||
@@ -380,7 +386,7 @@ test("getDiffRanges: no diff context lines", async (t) => {
|
||||
]);
|
||||
});
|
||||
|
||||
test("getDiffRanges: malformed thunk header", async (t) => {
|
||||
test.serial("getDiffRanges: malformed thunk header", async (t) => {
|
||||
const diffRanges = runGetDiffRanges(2, ["@@ 30 +50,2 @@", "+1", "+2"]);
|
||||
t.deepEqual(diffRanges, undefined);
|
||||
});
|
||||
|
||||
@@ -34,23 +34,26 @@ test.beforeEach(() => {
|
||||
initializeEnvironment("1.2.3");
|
||||
});
|
||||
|
||||
test(`All features use default values if running against GHES`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const loggedMessages = [];
|
||||
const features = setUpFeatureFlagTests(
|
||||
tmpDir,
|
||||
getRecordingLogger(loggedMessages),
|
||||
{ type: GitHubVariant.GHES, version: "3.0.0" },
|
||||
);
|
||||
test.serial(
|
||||
`All features use default values if running against GHES`,
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const loggedMessages = [];
|
||||
const features = setUpFeatureFlagTests(
|
||||
tmpDir,
|
||||
getRecordingLogger(loggedMessages),
|
||||
{ type: GitHubVariant.GHES, version: "3.0.0" },
|
||||
);
|
||||
|
||||
await assertAllFeaturesHaveDefaultValues(t, features);
|
||||
checkExpectedLogMessages(t, loggedMessages, [
|
||||
"Not running against github.com. Using default values for all features.",
|
||||
]);
|
||||
});
|
||||
});
|
||||
await assertAllFeaturesHaveDefaultValues(t, features);
|
||||
checkExpectedLogMessages(t, loggedMessages, [
|
||||
"Not running against github.com. Using default values for all features.",
|
||||
]);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test(`Feature flags are requested in GHEC-DR`, async (t) => {
|
||||
test.serial(`Feature flags are requested in GHEC-DR`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const loggedMessages = [];
|
||||
const features = setUpFeatureFlagTests(
|
||||
@@ -78,254 +81,288 @@ test(`Feature flags are requested in GHEC-DR`, async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("API response missing and features use default value", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const features = setUpFeatureFlagTests(
|
||||
tmpDir,
|
||||
getRecordingLogger(loggedMessages),
|
||||
);
|
||||
|
||||
mockFeatureFlagApiEndpoint(403, {});
|
||||
|
||||
for (const feature of Object.values(Feature)) {
|
||||
t.assert(
|
||||
(await getFeatureIncludingCodeQlIfRequired(features, feature)) ===
|
||||
featureConfig[feature].defaultValue,
|
||||
);
|
||||
}
|
||||
assertAllFeaturesUndefinedInApi(t, loggedMessages);
|
||||
});
|
||||
});
|
||||
|
||||
test("Features use default value if they're not returned in API response", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const features = setUpFeatureFlagTests(
|
||||
tmpDir,
|
||||
getRecordingLogger(loggedMessages),
|
||||
);
|
||||
|
||||
mockFeatureFlagApiEndpoint(200, {});
|
||||
|
||||
for (const feature of Object.values(Feature)) {
|
||||
t.assert(
|
||||
(await getFeatureIncludingCodeQlIfRequired(features, feature)) ===
|
||||
featureConfig[feature].defaultValue,
|
||||
);
|
||||
}
|
||||
|
||||
assertAllFeaturesUndefinedInApi(t, loggedMessages);
|
||||
});
|
||||
});
|
||||
|
||||
test("Include no more than 25 features in each API request", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
|
||||
stubFeatureFlagApiEndpoint((request) => {
|
||||
const requestedFeatures = (request.features as string).split(",");
|
||||
return {
|
||||
status: requestedFeatures.length <= 25 ? 200 : 400,
|
||||
messageIfError: "Can request a maximum of 25 features.",
|
||||
data: {},
|
||||
};
|
||||
});
|
||||
|
||||
// We only need to call getValue once, and it does not matter which feature
|
||||
// we ask for. Under the hood, the features library will request all features
|
||||
// from the API.
|
||||
const feature = Object.values(Feature)[0];
|
||||
await t.notThrowsAsync(async () =>
|
||||
getFeatureIncludingCodeQlIfRequired(features, feature),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("Feature flags exception is propagated if the API request errors", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
|
||||
mockFeatureFlagApiEndpoint(500, {});
|
||||
|
||||
const someFeature = Object.values(Feature)[0];
|
||||
|
||||
await t.throwsAsync(
|
||||
async () => getFeatureIncludingCodeQlIfRequired(features, someFeature),
|
||||
{
|
||||
message:
|
||||
"Encountered an error while trying to determine feature enablement: Error: some error message",
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
for (const feature of Object.keys(featureConfig)) {
|
||||
test(`Only feature '${feature}' is enabled if enabled in the API response. Other features disabled`, async (t) => {
|
||||
test.serial(
|
||||
"API response missing and features use default value",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const features = setUpFeatureFlagTests(
|
||||
tmpDir,
|
||||
getRecordingLogger(loggedMessages),
|
||||
);
|
||||
|
||||
// set all features to false except the one we're testing
|
||||
const expectedFeatureEnablement: { [feature: string]: boolean } = {};
|
||||
for (const f of Object.keys(featureConfig)) {
|
||||
expectedFeatureEnablement[f] = f === feature;
|
||||
mockFeatureFlagApiEndpoint(403, {});
|
||||
|
||||
for (const feature of Object.values(Feature)) {
|
||||
t.assert(
|
||||
(await getFeatureIncludingCodeQlIfRequired(features, feature)) ===
|
||||
featureConfig[feature].defaultValue,
|
||||
);
|
||||
}
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
assertAllFeaturesUndefinedInApi(t, loggedMessages);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// retrieve the values of the actual features
|
||||
const actualFeatureEnablement: { [feature: string]: boolean } = {};
|
||||
for (const f of Object.keys(featureConfig)) {
|
||||
actualFeatureEnablement[f] = await getFeatureIncludingCodeQlIfRequired(
|
||||
features,
|
||||
f as Feature,
|
||||
test.serial(
|
||||
"Features use default value if they're not returned in API response",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const features = setUpFeatureFlagTests(
|
||||
tmpDir,
|
||||
getRecordingLogger(loggedMessages),
|
||||
);
|
||||
|
||||
mockFeatureFlagApiEndpoint(200, {});
|
||||
|
||||
for (const feature of Object.values(Feature)) {
|
||||
t.assert(
|
||||
(await getFeatureIncludingCodeQlIfRequired(features, feature)) ===
|
||||
featureConfig[feature].defaultValue,
|
||||
);
|
||||
}
|
||||
|
||||
// All features should be false except the one we're testing
|
||||
t.deepEqual(actualFeatureEnablement, expectedFeatureEnablement);
|
||||
assertAllFeaturesUndefinedInApi(t, loggedMessages);
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test(`Only feature '${feature}' is enabled if the associated environment variable is true. Others disabled.`, async (t) => {
|
||||
test.serial(
|
||||
"Include no more than 25 features in each API request",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
|
||||
const expectedFeatureEnablement = initializeFeatures(false);
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
stubFeatureFlagApiEndpoint((request) => {
|
||||
const requestedFeatures = (request.features as string).split(",");
|
||||
return {
|
||||
status: requestedFeatures.length <= 25 ? 200 : 400,
|
||||
messageIfError: "Can request a maximum of 25 features.",
|
||||
data: {},
|
||||
};
|
||||
});
|
||||
|
||||
// feature should be disabled initially
|
||||
t.assert(
|
||||
!(await getFeatureIncludingCodeQlIfRequired(
|
||||
features,
|
||||
feature as Feature,
|
||||
)),
|
||||
);
|
||||
|
||||
// set env var to true and check that the feature is now enabled
|
||||
process.env[featureConfig[feature].envVar] = "true";
|
||||
t.assert(
|
||||
await getFeatureIncludingCodeQlIfRequired(features, feature as Feature),
|
||||
// We only need to call getValue once, and it does not matter which feature
|
||||
// we ask for. Under the hood, the features library will request all features
|
||||
// from the API.
|
||||
const feature = Object.values(Feature)[0];
|
||||
await t.notThrowsAsync(async () =>
|
||||
getFeatureIncludingCodeQlIfRequired(features, feature),
|
||||
);
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test(`Feature '${feature}' is disabled if the associated environment variable is false, even if enabled in API`, async (t) => {
|
||||
test.serial(
|
||||
"Feature flags exception is propagated if the API request errors",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
mockFeatureFlagApiEndpoint(500, {});
|
||||
|
||||
// feature should be enabled initially
|
||||
t.assert(
|
||||
await getFeatureIncludingCodeQlIfRequired(features, feature as Feature),
|
||||
);
|
||||
const someFeature = Object.values(Feature)[0];
|
||||
|
||||
// set env var to false and check that the feature is now disabled
|
||||
process.env[featureConfig[feature].envVar] = "false";
|
||||
t.assert(
|
||||
!(await getFeatureIncludingCodeQlIfRequired(
|
||||
features,
|
||||
feature as Feature,
|
||||
)),
|
||||
await t.throwsAsync(
|
||||
async () => getFeatureIncludingCodeQlIfRequired(features, someFeature),
|
||||
{
|
||||
message:
|
||||
"Encountered an error while trying to determine feature enablement: Error: some error message",
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
for (const feature of Object.keys(featureConfig)) {
|
||||
test.serial(
|
||||
`Only feature '${feature}' is enabled if enabled in the API response. Other features disabled`,
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
|
||||
// set all features to false except the one we're testing
|
||||
const expectedFeatureEnablement: { [feature: string]: boolean } = {};
|
||||
for (const f of Object.keys(featureConfig)) {
|
||||
expectedFeatureEnablement[f] = f === feature;
|
||||
}
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
|
||||
// retrieve the values of the actual features
|
||||
const actualFeatureEnablement: { [feature: string]: boolean } = {};
|
||||
for (const f of Object.keys(featureConfig)) {
|
||||
actualFeatureEnablement[f] =
|
||||
await getFeatureIncludingCodeQlIfRequired(features, f as Feature);
|
||||
}
|
||||
|
||||
// All features should be false except the one we're testing
|
||||
t.deepEqual(actualFeatureEnablement, expectedFeatureEnablement);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
`Only feature '${feature}' is enabled if the associated environment variable is true. Others disabled.`,
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
|
||||
const expectedFeatureEnablement = initializeFeatures(false);
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
|
||||
// feature should be disabled initially
|
||||
t.assert(
|
||||
!(await getFeatureIncludingCodeQlIfRequired(
|
||||
features,
|
||||
feature as Feature,
|
||||
)),
|
||||
);
|
||||
|
||||
// set env var to true and check that the feature is now enabled
|
||||
process.env[featureConfig[feature].envVar] = "true";
|
||||
t.assert(
|
||||
await getFeatureIncludingCodeQlIfRequired(
|
||||
features,
|
||||
feature as Feature,
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
`Feature '${feature}' is disabled if the associated environment variable is false, even if enabled in API`,
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
|
||||
// feature should be enabled initially
|
||||
t.assert(
|
||||
await getFeatureIncludingCodeQlIfRequired(
|
||||
features,
|
||||
feature as Feature,
|
||||
),
|
||||
);
|
||||
|
||||
// set env var to false and check that the feature is now disabled
|
||||
process.env[featureConfig[feature].envVar] = "false";
|
||||
t.assert(
|
||||
!(await getFeatureIncludingCodeQlIfRequired(
|
||||
features,
|
||||
feature as Feature,
|
||||
)),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
if (
|
||||
featureConfig[feature].minimumVersion !== undefined ||
|
||||
featureConfig[feature].toolsFeature !== undefined
|
||||
) {
|
||||
test(`Getting feature '${feature} should throw if no codeql is provided`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
test.serial(
|
||||
`Getting feature '${feature} should throw if no codeql is provided`,
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
|
||||
// The type system should prevent this happening, but test that if we
|
||||
// bypass it we get the expected error.
|
||||
await t.throwsAsync(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
async () => features.getValue(feature as any),
|
||||
{
|
||||
message: `Internal error: A ${
|
||||
featureConfig[feature].minimumVersion !== undefined
|
||||
? "minimum version"
|
||||
: "required tools feature"
|
||||
} is specified for feature ${feature}, but no instance of CodeQL was provided.`,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
// The type system should prevent this happening, but test that if we
|
||||
// bypass it we get the expected error.
|
||||
await t.throwsAsync(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
async () => features.getValue(feature as any),
|
||||
{
|
||||
message: `Internal error: A ${
|
||||
featureConfig[feature].minimumVersion !== undefined
|
||||
? "minimum version"
|
||||
: "required tools feature"
|
||||
} is specified for feature ${feature}, but no instance of CodeQL was provided.`,
|
||||
},
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (featureConfig[feature].minimumVersion !== undefined) {
|
||||
test(`Feature '${feature}' is disabled if the minimum CLI version is below ${featureConfig[feature].minimumVersion}`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
test.serial(
|
||||
`Feature '${feature}' is disabled if the minimum CLI version is below ${featureConfig[feature].minimumVersion}`,
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
|
||||
// feature should be disabled when an old CLI version is set
|
||||
let codeql = mockCodeQLVersion("2.0.0");
|
||||
t.assert(!(await features.getValue(feature as Feature, codeql)));
|
||||
// feature should be disabled when an old CLI version is set
|
||||
let codeql = mockCodeQLVersion("2.0.0");
|
||||
t.assert(!(await features.getValue(feature as Feature, codeql)));
|
||||
|
||||
// even setting the env var to true should not enable the feature if
|
||||
// the minimum CLI version is not met
|
||||
process.env[featureConfig[feature].envVar] = "true";
|
||||
t.assert(!(await features.getValue(feature as Feature, codeql)));
|
||||
// even setting the env var to true should not enable the feature if
|
||||
// the minimum CLI version is not met
|
||||
process.env[featureConfig[feature].envVar] = "true";
|
||||
t.assert(!(await features.getValue(feature as Feature, codeql)));
|
||||
|
||||
// feature should be enabled when a new CLI version is set
|
||||
// and env var is not set
|
||||
process.env[featureConfig[feature].envVar] = "";
|
||||
codeql = mockCodeQLVersion(
|
||||
featureConfig[feature].minimumVersion as string,
|
||||
);
|
||||
t.assert(await features.getValue(feature as Feature, codeql));
|
||||
// feature should be enabled when a new CLI version is set
|
||||
// and env var is not set
|
||||
process.env[featureConfig[feature].envVar] = "";
|
||||
codeql = mockCodeQLVersion(
|
||||
featureConfig[feature].minimumVersion as string,
|
||||
);
|
||||
t.assert(await features.getValue(feature as Feature, codeql));
|
||||
|
||||
// set env var to false and check that the feature is now disabled
|
||||
process.env[featureConfig[feature].envVar] = "false";
|
||||
t.assert(!(await features.getValue(feature as Feature, codeql)));
|
||||
});
|
||||
});
|
||||
// set env var to false and check that the feature is now disabled
|
||||
process.env[featureConfig[feature].envVar] = "false";
|
||||
t.assert(!(await features.getValue(feature as Feature, codeql)));
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (featureConfig[feature].toolsFeature !== undefined) {
|
||||
test(`Feature '${feature}' is disabled if the required tools feature is not enabled`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
test.serial(
|
||||
`Feature '${feature}' is disabled if the required tools feature is not enabled`,
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
|
||||
// feature should be disabled when the required tools feature is not enabled
|
||||
let codeql = mockCodeQLVersion("2.0.0");
|
||||
t.assert(!(await features.getValue(feature as Feature, codeql)));
|
||||
// feature should be disabled when the required tools feature is not enabled
|
||||
let codeql = mockCodeQLVersion("2.0.0");
|
||||
t.assert(!(await features.getValue(feature as Feature, codeql)));
|
||||
|
||||
// even setting the env var to true should not enable the feature if
|
||||
// the required tools feature is not enabled
|
||||
process.env[featureConfig[feature].envVar] = "true";
|
||||
t.assert(!(await features.getValue(feature as Feature, codeql)));
|
||||
// even setting the env var to true should not enable the feature if
|
||||
// the required tools feature is not enabled
|
||||
process.env[featureConfig[feature].envVar] = "true";
|
||||
t.assert(!(await features.getValue(feature as Feature, codeql)));
|
||||
|
||||
// feature should be enabled when the required tools feature is enabled
|
||||
// and env var is not set
|
||||
process.env[featureConfig[feature].envVar] = "";
|
||||
codeql = mockCodeQLVersion("2.0.0", {
|
||||
[featureConfig[feature].toolsFeature]: true,
|
||||
// feature should be enabled when the required tools feature is enabled
|
||||
// and env var is not set
|
||||
process.env[featureConfig[feature].envVar] = "";
|
||||
codeql = mockCodeQLVersion("2.0.0", {
|
||||
[featureConfig[feature].toolsFeature]: true,
|
||||
});
|
||||
t.assert(await features.getValue(feature as Feature, codeql));
|
||||
|
||||
// set env var to false and check that the feature is now disabled
|
||||
process.env[featureConfig[feature].envVar] = "false";
|
||||
t.assert(!(await features.getValue(feature as Feature, codeql)));
|
||||
});
|
||||
t.assert(await features.getValue(feature as Feature, codeql));
|
||||
|
||||
// set env var to false and check that the feature is now disabled
|
||||
process.env[featureConfig[feature].envVar] = "false";
|
||||
t.assert(!(await features.getValue(feature as Feature, codeql)));
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
test("Feature flags are saved to disk", async (t) => {
|
||||
test.serial("Feature flags are saved to disk", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
@@ -376,38 +413,41 @@ test("Feature flags are saved to disk", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("Environment variable can override feature flag cache", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
test.serial(
|
||||
"Environment variable can override feature flag cache",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
|
||||
const cachedFeatureFlags = path.join(tmpDir, FEATURE_FLAGS_FILE_NAME);
|
||||
t.true(
|
||||
await getFeatureIncludingCodeQlIfRequired(
|
||||
features,
|
||||
Feature.QaTelemetryEnabled,
|
||||
),
|
||||
"Feature flag should be enabled initially",
|
||||
);
|
||||
const cachedFeatureFlags = path.join(tmpDir, FEATURE_FLAGS_FILE_NAME);
|
||||
t.true(
|
||||
await getFeatureIncludingCodeQlIfRequired(
|
||||
features,
|
||||
Feature.QaTelemetryEnabled,
|
||||
),
|
||||
"Feature flag should be enabled initially",
|
||||
);
|
||||
|
||||
t.true(
|
||||
fs.existsSync(cachedFeatureFlags),
|
||||
"Feature flag cached file should exist after getting feature flags",
|
||||
);
|
||||
process.env.CODEQL_ACTION_QA_TELEMETRY = "false";
|
||||
t.true(
|
||||
fs.existsSync(cachedFeatureFlags),
|
||||
"Feature flag cached file should exist after getting feature flags",
|
||||
);
|
||||
process.env.CODEQL_ACTION_QA_TELEMETRY = "false";
|
||||
|
||||
t.false(
|
||||
await getFeatureIncludingCodeQlIfRequired(
|
||||
features,
|
||||
Feature.QaTelemetryEnabled,
|
||||
),
|
||||
"Feature flag should be disabled after setting env var",
|
||||
);
|
||||
});
|
||||
});
|
||||
t.false(
|
||||
await getFeatureIncludingCodeQlIfRequired(
|
||||
features,
|
||||
Feature.QaTelemetryEnabled,
|
||||
),
|
||||
"Feature flag should be disabled after setting env var",
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test(`selects CLI from defaults.json on GHES`, async (t) => {
|
||||
test.serial(`selects CLI from defaults.json on GHES`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
|
||||
@@ -422,80 +462,94 @@ test(`selects CLI from defaults.json on GHES`, async (t) => {
|
||||
});
|
||||
|
||||
for (const variant of [GitHubVariant.DOTCOM, GitHubVariant.GHEC_DR]) {
|
||||
test(`selects CLI v2.20.1 on ${variant} when feature flags enable v2.20.0 and v2.20.1`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_0_enabled"] = true;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_1_enabled"] = true;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_2_enabled"] =
|
||||
false;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_3_enabled"] =
|
||||
false;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_4_enabled"] =
|
||||
false;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_5_enabled"] =
|
||||
false;
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
test.serial(
|
||||
`selects CLI v2.20.1 on ${variant} when feature flags enable v2.20.0 and v2.20.1`,
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_0_enabled"] =
|
||||
true;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_1_enabled"] =
|
||||
true;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_2_enabled"] =
|
||||
false;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_3_enabled"] =
|
||||
false;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_4_enabled"] =
|
||||
false;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_5_enabled"] =
|
||||
false;
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
|
||||
const defaultCliVersion = await features.getDefaultCliVersion(variant);
|
||||
t.deepEqual(defaultCliVersion, {
|
||||
cliVersion: "2.20.1",
|
||||
tagName: "codeql-bundle-v2.20.1",
|
||||
toolsFeatureFlagsValid: true,
|
||||
const defaultCliVersion = await features.getDefaultCliVersion(variant);
|
||||
t.deepEqual(defaultCliVersion, {
|
||||
cliVersion: "2.20.1",
|
||||
tagName: "codeql-bundle-v2.20.1",
|
||||
toolsFeatureFlagsValid: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test(`selects CLI from defaults.json on ${variant} when no default version feature flags are enabled`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
test.serial(
|
||||
`selects CLI from defaults.json on ${variant} when no default version feature flags are enabled`,
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
|
||||
const defaultCliVersion = await features.getDefaultCliVersion(variant);
|
||||
t.deepEqual(defaultCliVersion, {
|
||||
cliVersion: defaults.cliVersion,
|
||||
tagName: defaults.bundleVersion,
|
||||
toolsFeatureFlagsValid: false,
|
||||
const defaultCliVersion = await features.getDefaultCliVersion(variant);
|
||||
t.deepEqual(defaultCliVersion, {
|
||||
cliVersion: defaults.cliVersion,
|
||||
tagName: defaults.bundleVersion,
|
||||
toolsFeatureFlagsValid: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test(`ignores invalid version numbers in default version feature flags on ${variant}`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const loggedMessages = [];
|
||||
const features = setUpFeatureFlagTests(
|
||||
tmpDir,
|
||||
getRecordingLogger(loggedMessages),
|
||||
);
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_0_enabled"] = true;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_1_enabled"] = true;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_invalid_enabled"] =
|
||||
true;
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
test.serial(
|
||||
`ignores invalid version numbers in default version feature flags on ${variant}`,
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const loggedMessages = [];
|
||||
const features = setUpFeatureFlagTests(
|
||||
tmpDir,
|
||||
getRecordingLogger(loggedMessages),
|
||||
);
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_0_enabled"] =
|
||||
true;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_1_enabled"] =
|
||||
true;
|
||||
expectedFeatureEnablement[
|
||||
"default_codeql_version_2_20_invalid_enabled"
|
||||
] = true;
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
|
||||
const defaultCliVersion = await features.getDefaultCliVersion(variant);
|
||||
t.deepEqual(defaultCliVersion, {
|
||||
cliVersion: "2.20.1",
|
||||
tagName: "codeql-bundle-v2.20.1",
|
||||
toolsFeatureFlagsValid: true,
|
||||
const defaultCliVersion = await features.getDefaultCliVersion(variant);
|
||||
t.deepEqual(defaultCliVersion, {
|
||||
cliVersion: "2.20.1",
|
||||
tagName: "codeql-bundle-v2.20.1",
|
||||
toolsFeatureFlagsValid: true,
|
||||
});
|
||||
|
||||
t.assert(
|
||||
loggedMessages.find(
|
||||
(v: LoggedMessage) =>
|
||||
v.type === "warning" &&
|
||||
v.message ===
|
||||
"Ignoring feature flag default_codeql_version_2_20_invalid_enabled as it does not specify a valid CodeQL version.",
|
||||
) !== undefined,
|
||||
);
|
||||
});
|
||||
|
||||
t.assert(
|
||||
loggedMessages.find(
|
||||
(v: LoggedMessage) =>
|
||||
v.type === "warning" &&
|
||||
v.message ===
|
||||
"Ignoring feature flag default_codeql_version_2_20_invalid_enabled as it does not specify a valid CodeQL version.",
|
||||
) !== undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
test("legacy feature flags should end with _enabled", async (t) => {
|
||||
test.serial("legacy feature flags should end with _enabled", async (t) => {
|
||||
for (const [feature, config] of Object.entries(featureConfig)) {
|
||||
if ((config satisfies FeatureConfig as FeatureConfig).legacyApi) {
|
||||
t.assert(
|
||||
@@ -506,31 +560,40 @@ test("legacy feature flags should end with _enabled", async (t) => {
|
||||
}
|
||||
});
|
||||
|
||||
test("non-legacy feature flags should not end with _enabled", async (t) => {
|
||||
for (const [feature, config] of Object.entries(featureConfig)) {
|
||||
if (!(config satisfies FeatureConfig as FeatureConfig).legacyApi) {
|
||||
t.false(
|
||||
feature.endsWith("_enabled"),
|
||||
`non-legacy feature ${feature} should not end with '_enabled'`,
|
||||
);
|
||||
test.serial(
|
||||
"non-legacy feature flags should not end with _enabled",
|
||||
async (t) => {
|
||||
for (const [feature, config] of Object.entries(featureConfig)) {
|
||||
if (!(config satisfies FeatureConfig as FeatureConfig).legacyApi) {
|
||||
t.false(
|
||||
feature.endsWith("_enabled"),
|
||||
`non-legacy feature ${feature} should not end with '_enabled'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("non-legacy feature flags should not start with codeql_action_", async (t) => {
|
||||
for (const [feature, config] of Object.entries(featureConfig)) {
|
||||
if (!(config satisfies FeatureConfig as FeatureConfig).legacyApi) {
|
||||
t.false(
|
||||
feature.startsWith("codeql_action_"),
|
||||
`non-legacy feature ${feature} should not start with 'codeql_action_'`,
|
||||
);
|
||||
test.serial(
|
||||
"non-legacy feature flags should not start with codeql_action_",
|
||||
async (t) => {
|
||||
for (const [feature, config] of Object.entries(featureConfig)) {
|
||||
if (!(config satisfies FeatureConfig as FeatureConfig).legacyApi) {
|
||||
t.false(
|
||||
feature.startsWith("codeql_action_"),
|
||||
`non-legacy feature ${feature} should not start with 'codeql_action_'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("initFeatures returns a `Features` instance by default", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
t.is("Features", features.constructor.name);
|
||||
});
|
||||
});
|
||||
test.serial(
|
||||
"initFeatures returns a `Features` instance by default",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
t.is("Features", features.constructor.name);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -11,76 +11,86 @@ import * as properties from "./properties";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
test("loadPropertiesFromApi throws if response data is not an array", async (t) => {
|
||||
sinon.stub(api, "getRepositoryProperties").resolves({
|
||||
headers: {},
|
||||
status: 200,
|
||||
url: "",
|
||||
data: {},
|
||||
});
|
||||
const logger = getRunnerLogger(true);
|
||||
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
|
||||
await t.throwsAsync(
|
||||
properties.loadPropertiesFromApi(
|
||||
test.serial(
|
||||
"loadPropertiesFromApi throws if response data is not an array",
|
||||
async (t) => {
|
||||
sinon.stub(api, "getRepositoryProperties").resolves({
|
||||
headers: {},
|
||||
status: 200,
|
||||
url: "",
|
||||
data: {},
|
||||
});
|
||||
const logger = getRunnerLogger(true);
|
||||
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
|
||||
await t.throwsAsync(
|
||||
properties.loadPropertiesFromApi(
|
||||
{
|
||||
type: util.GitHubVariant.DOTCOM,
|
||||
},
|
||||
logger,
|
||||
mockRepositoryNwo,
|
||||
),
|
||||
{
|
||||
type: util.GitHubVariant.DOTCOM,
|
||||
message: /Expected repository properties API to return an array/,
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"loadPropertiesFromApi throws if response data contains unexpected objects",
|
||||
async (t) => {
|
||||
sinon.stub(api, "getRepositoryProperties").resolves({
|
||||
headers: {},
|
||||
status: 200,
|
||||
url: "",
|
||||
data: [{}],
|
||||
});
|
||||
const logger = getRunnerLogger(true);
|
||||
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
|
||||
await t.throwsAsync(
|
||||
properties.loadPropertiesFromApi(
|
||||
{
|
||||
type: util.GitHubVariant.DOTCOM,
|
||||
},
|
||||
logger,
|
||||
mockRepositoryNwo,
|
||||
),
|
||||
{
|
||||
message:
|
||||
/Expected repository property object to have a 'property_name'/,
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"loadPropertiesFromApi returns empty object if on GHES",
|
||||
async (t) => {
|
||||
sinon.stub(api, "getRepositoryProperties").resolves({
|
||||
headers: {},
|
||||
status: 200,
|
||||
url: "",
|
||||
data: [
|
||||
{ property_name: "github-codeql-extra-queries", value: "+queries" },
|
||||
{ property_name: "unknown-property", value: "something" },
|
||||
] satisfies properties.GitHubPropertiesResponse,
|
||||
});
|
||||
const logger = getRunnerLogger(true);
|
||||
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
|
||||
const response = await properties.loadPropertiesFromApi(
|
||||
{
|
||||
type: util.GitHubVariant.GHES,
|
||||
version: "",
|
||||
},
|
||||
logger,
|
||||
mockRepositoryNwo,
|
||||
),
|
||||
{
|
||||
message: /Expected repository properties API to return an array/,
|
||||
},
|
||||
);
|
||||
});
|
||||
);
|
||||
t.deepEqual(response, {});
|
||||
},
|
||||
);
|
||||
|
||||
test("loadPropertiesFromApi throws if response data contains unexpected objects", async (t) => {
|
||||
sinon.stub(api, "getRepositoryProperties").resolves({
|
||||
headers: {},
|
||||
status: 200,
|
||||
url: "",
|
||||
data: [{}],
|
||||
});
|
||||
const logger = getRunnerLogger(true);
|
||||
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
|
||||
await t.throwsAsync(
|
||||
properties.loadPropertiesFromApi(
|
||||
{
|
||||
type: util.GitHubVariant.DOTCOM,
|
||||
},
|
||||
logger,
|
||||
mockRepositoryNwo,
|
||||
),
|
||||
{
|
||||
message: /Expected repository property object to have a 'property_name'/,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("loadPropertiesFromApi returns empty object if on GHES", async (t) => {
|
||||
sinon.stub(api, "getRepositoryProperties").resolves({
|
||||
headers: {},
|
||||
status: 200,
|
||||
url: "",
|
||||
data: [
|
||||
{ property_name: "github-codeql-extra-queries", value: "+queries" },
|
||||
{ property_name: "unknown-property", value: "something" },
|
||||
] satisfies properties.GitHubPropertiesResponse,
|
||||
});
|
||||
const logger = getRunnerLogger(true);
|
||||
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
|
||||
const response = await properties.loadPropertiesFromApi(
|
||||
{
|
||||
type: util.GitHubVariant.GHES,
|
||||
version: "",
|
||||
},
|
||||
logger,
|
||||
mockRepositoryNwo,
|
||||
);
|
||||
t.deepEqual(response, {});
|
||||
});
|
||||
|
||||
test("loadPropertiesFromApi loads known properties", async (t) => {
|
||||
test.serial("loadPropertiesFromApi loads known properties", async (t) => {
|
||||
sinon.stub(api, "getRepositoryProperties").resolves({
|
||||
headers: {},
|
||||
status: 200,
|
||||
@@ -102,7 +112,7 @@ test("loadPropertiesFromApi loads known properties", async (t) => {
|
||||
t.deepEqual(response, { "github-codeql-extra-queries": "+queries" });
|
||||
});
|
||||
|
||||
test("loadPropertiesFromApi parses true boolean property", async (t) => {
|
||||
test.serial("loadPropertiesFromApi parses true boolean property", async (t) => {
|
||||
sinon.stub(api, "getRepositoryProperties").resolves({
|
||||
headers: {},
|
||||
status: 200,
|
||||
@@ -132,86 +142,95 @@ test("loadPropertiesFromApi parses true boolean property", async (t) => {
|
||||
t.true(warningSpy.notCalled);
|
||||
});
|
||||
|
||||
test("loadPropertiesFromApi parses false boolean property", async (t) => {
|
||||
sinon.stub(api, "getRepositoryProperties").resolves({
|
||||
headers: {},
|
||||
status: 200,
|
||||
url: "",
|
||||
data: [
|
||||
{
|
||||
property_name: "github-codeql-disable-overlay",
|
||||
value: "false",
|
||||
},
|
||||
] satisfies properties.GitHubPropertiesResponse,
|
||||
});
|
||||
const logger = getRunnerLogger(true);
|
||||
const warningSpy = sinon.spy(logger, "warning");
|
||||
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
|
||||
const response = await properties.loadPropertiesFromApi(
|
||||
{
|
||||
type: util.GitHubVariant.DOTCOM,
|
||||
},
|
||||
logger,
|
||||
mockRepositoryNwo,
|
||||
);
|
||||
t.deepEqual(response, {
|
||||
"github-codeql-disable-overlay": false,
|
||||
});
|
||||
t.true(warningSpy.notCalled);
|
||||
});
|
||||
|
||||
test("loadPropertiesFromApi throws if property value is not a string", async (t) => {
|
||||
sinon.stub(api, "getRepositoryProperties").resolves({
|
||||
headers: {},
|
||||
status: 200,
|
||||
url: "",
|
||||
data: [{ property_name: "github-codeql-extra-queries", value: 123 }],
|
||||
});
|
||||
const logger = getRunnerLogger(true);
|
||||
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
|
||||
await t.throwsAsync(
|
||||
properties.loadPropertiesFromApi(
|
||||
test.serial(
|
||||
"loadPropertiesFromApi parses false boolean property",
|
||||
async (t) => {
|
||||
sinon.stub(api, "getRepositoryProperties").resolves({
|
||||
headers: {},
|
||||
status: 200,
|
||||
url: "",
|
||||
data: [
|
||||
{
|
||||
property_name: "github-codeql-disable-overlay",
|
||||
value: "false",
|
||||
},
|
||||
] satisfies properties.GitHubPropertiesResponse,
|
||||
});
|
||||
const logger = getRunnerLogger(true);
|
||||
const warningSpy = sinon.spy(logger, "warning");
|
||||
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
|
||||
const response = await properties.loadPropertiesFromApi(
|
||||
{
|
||||
type: util.GitHubVariant.DOTCOM,
|
||||
},
|
||||
logger,
|
||||
mockRepositoryNwo,
|
||||
),
|
||||
{
|
||||
message:
|
||||
/Expected repository property 'github-codeql-extra-queries' to have a string value/,
|
||||
},
|
||||
);
|
||||
});
|
||||
);
|
||||
t.deepEqual(response, {
|
||||
"github-codeql-disable-overlay": false,
|
||||
});
|
||||
t.true(warningSpy.notCalled);
|
||||
},
|
||||
);
|
||||
|
||||
test("loadPropertiesFromApi warns if boolean property has unexpected value", async (t) => {
|
||||
sinon.stub(api, "getRepositoryProperties").resolves({
|
||||
headers: {},
|
||||
status: 200,
|
||||
url: "",
|
||||
data: [
|
||||
test.serial(
|
||||
"loadPropertiesFromApi throws if property value is not a string",
|
||||
async (t) => {
|
||||
sinon.stub(api, "getRepositoryProperties").resolves({
|
||||
headers: {},
|
||||
status: 200,
|
||||
url: "",
|
||||
data: [{ property_name: "github-codeql-extra-queries", value: 123 }],
|
||||
});
|
||||
const logger = getRunnerLogger(true);
|
||||
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
|
||||
await t.throwsAsync(
|
||||
properties.loadPropertiesFromApi(
|
||||
{
|
||||
type: util.GitHubVariant.DOTCOM,
|
||||
},
|
||||
logger,
|
||||
mockRepositoryNwo,
|
||||
),
|
||||
{
|
||||
property_name: "github-codeql-disable-overlay",
|
||||
value: "yes",
|
||||
message:
|
||||
/Expected repository property 'github-codeql-extra-queries' to have a string value/,
|
||||
},
|
||||
] satisfies properties.GitHubPropertiesResponse,
|
||||
});
|
||||
const logger = getRunnerLogger(true);
|
||||
const warningSpy = sinon.spy(logger, "warning");
|
||||
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
|
||||
const response = await properties.loadPropertiesFromApi(
|
||||
{
|
||||
type: util.GitHubVariant.DOTCOM,
|
||||
},
|
||||
logger,
|
||||
mockRepositoryNwo,
|
||||
);
|
||||
t.deepEqual(response, {
|
||||
"github-codeql-disable-overlay": false,
|
||||
});
|
||||
t.true(warningSpy.calledOnce);
|
||||
t.is(
|
||||
warningSpy.firstCall.args[0],
|
||||
"Repository property 'github-codeql-disable-overlay' has unexpected value 'yes'. Expected 'true' or 'false'. Defaulting to false.",
|
||||
);
|
||||
});
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"loadPropertiesFromApi warns if boolean property has unexpected value",
|
||||
async (t) => {
|
||||
sinon.stub(api, "getRepositoryProperties").resolves({
|
||||
headers: {},
|
||||
status: 200,
|
||||
url: "",
|
||||
data: [
|
||||
{
|
||||
property_name: "github-codeql-disable-overlay",
|
||||
value: "yes",
|
||||
},
|
||||
] satisfies properties.GitHubPropertiesResponse,
|
||||
});
|
||||
const logger = getRunnerLogger(true);
|
||||
const warningSpy = sinon.spy(logger, "warning");
|
||||
const mockRepositoryNwo = parseRepositoryNwo("owner/repo");
|
||||
const response = await properties.loadPropertiesFromApi(
|
||||
{
|
||||
type: util.GitHubVariant.DOTCOM,
|
||||
},
|
||||
logger,
|
||||
mockRepositoryNwo,
|
||||
);
|
||||
t.deepEqual(response, {
|
||||
"github-codeql-disable-overlay": false,
|
||||
});
|
||||
t.true(warningSpy.calledOnce);
|
||||
t.is(
|
||||
warningSpy.firstCall.args[0],
|
||||
"Repository property 'github-codeql-disable-overlay' has unexpected value 'yes'. Expected 'true' or 'false'. Defaulting to false.",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -13,154 +13,187 @@ import { withTmpDir } from "./util";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
test("getRef() throws on the empty string", async (t) => {
|
||||
test.serial("getRef() throws on the empty string", async (t) => {
|
||||
process.env["GITHUB_REF"] = "";
|
||||
await t.throwsAsync(gitUtils.getRef);
|
||||
});
|
||||
|
||||
test("getRef() returns merge PR ref if GITHUB_SHA still checked out", async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const expectedRef = "refs/pull/1/merge";
|
||||
const currentSha = "a".repeat(40);
|
||||
process.env["GITHUB_REF"] = expectedRef;
|
||||
process.env["GITHUB_SHA"] = currentSha;
|
||||
test.serial(
|
||||
"getRef() returns merge PR ref if GITHUB_SHA still checked out",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const expectedRef = "refs/pull/1/merge";
|
||||
const currentSha = "a".repeat(40);
|
||||
process.env["GITHUB_REF"] = expectedRef;
|
||||
process.env["GITHUB_SHA"] = currentSha;
|
||||
|
||||
const callback = sinon.stub(gitUtils, "getCommitOid");
|
||||
callback.withArgs("HEAD").resolves(currentSha);
|
||||
const callback = sinon.stub(gitUtils, "getCommitOid");
|
||||
callback.withArgs("HEAD").resolves(currentSha);
|
||||
|
||||
const actualRef = await gitUtils.getRef();
|
||||
t.deepEqual(actualRef, expectedRef);
|
||||
callback.restore();
|
||||
});
|
||||
});
|
||||
const actualRef = await gitUtils.getRef();
|
||||
t.deepEqual(actualRef, expectedRef);
|
||||
callback.restore();
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("getRef() returns merge PR ref if GITHUB_REF still checked out but sha has changed (actions checkout@v1)", async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const expectedRef = "refs/pull/1/merge";
|
||||
process.env["GITHUB_REF"] = expectedRef;
|
||||
process.env["GITHUB_SHA"] = "b".repeat(40);
|
||||
const sha = "a".repeat(40);
|
||||
test.serial(
|
||||
"getRef() returns merge PR ref if GITHUB_REF still checked out but sha has changed (actions checkout@v1)",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const expectedRef = "refs/pull/1/merge";
|
||||
process.env["GITHUB_REF"] = expectedRef;
|
||||
process.env["GITHUB_SHA"] = "b".repeat(40);
|
||||
const sha = "a".repeat(40);
|
||||
|
||||
const callback = sinon.stub(gitUtils, "getCommitOid");
|
||||
callback.withArgs("refs/remotes/pull/1/merge").resolves(sha);
|
||||
callback.withArgs("HEAD").resolves(sha);
|
||||
const callback = sinon.stub(gitUtils, "getCommitOid");
|
||||
callback.withArgs("refs/remotes/pull/1/merge").resolves(sha);
|
||||
callback.withArgs("HEAD").resolves(sha);
|
||||
|
||||
const actualRef = await gitUtils.getRef();
|
||||
t.deepEqual(actualRef, expectedRef);
|
||||
callback.restore();
|
||||
});
|
||||
});
|
||||
const actualRef = await gitUtils.getRef();
|
||||
t.deepEqual(actualRef, expectedRef);
|
||||
callback.restore();
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("getRef() returns head PR ref if GITHUB_REF no longer checked out", async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
process.env["GITHUB_REF"] = "refs/pull/1/merge";
|
||||
process.env["GITHUB_SHA"] = "a".repeat(40);
|
||||
test.serial(
|
||||
"getRef() returns head PR ref if GITHUB_REF no longer checked out",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
process.env["GITHUB_REF"] = "refs/pull/1/merge";
|
||||
process.env["GITHUB_SHA"] = "a".repeat(40);
|
||||
|
||||
const callback = sinon.stub(gitUtils, "getCommitOid");
|
||||
callback.withArgs(tmpDir, "refs/pull/1/merge").resolves("a".repeat(40));
|
||||
callback.withArgs(tmpDir, "HEAD").resolves("b".repeat(40));
|
||||
const callback = sinon.stub(gitUtils, "getCommitOid");
|
||||
callback.withArgs(tmpDir, "refs/pull/1/merge").resolves("a".repeat(40));
|
||||
callback.withArgs(tmpDir, "HEAD").resolves("b".repeat(40));
|
||||
|
||||
const actualRef = await gitUtils.getRef();
|
||||
t.deepEqual(actualRef, "refs/pull/1/head");
|
||||
callback.restore();
|
||||
});
|
||||
});
|
||||
const actualRef = await gitUtils.getRef();
|
||||
t.deepEqual(actualRef, "refs/pull/1/head");
|
||||
callback.restore();
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("getRef() returns ref provided as an input and ignores current HEAD", async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const getAdditionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
|
||||
getAdditionalInputStub.withArgs("ref").resolves("refs/pull/2/merge");
|
||||
getAdditionalInputStub.withArgs("sha").resolves("b".repeat(40));
|
||||
test.serial(
|
||||
"getRef() returns ref provided as an input and ignores current HEAD",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const getAdditionalInputStub = sinon.stub(
|
||||
actionsUtil,
|
||||
"getOptionalInput",
|
||||
);
|
||||
getAdditionalInputStub.withArgs("ref").resolves("refs/pull/2/merge");
|
||||
getAdditionalInputStub.withArgs("sha").resolves("b".repeat(40));
|
||||
|
||||
// These values are be ignored
|
||||
process.env["GITHUB_REF"] = "refs/pull/1/merge";
|
||||
process.env["GITHUB_SHA"] = "a".repeat(40);
|
||||
// These values are be ignored
|
||||
process.env["GITHUB_REF"] = "refs/pull/1/merge";
|
||||
process.env["GITHUB_SHA"] = "a".repeat(40);
|
||||
|
||||
const callback = sinon.stub(gitUtils, "getCommitOid");
|
||||
callback.withArgs("refs/pull/1/merge").resolves("b".repeat(40));
|
||||
callback.withArgs("HEAD").resolves("b".repeat(40));
|
||||
const callback = sinon.stub(gitUtils, "getCommitOid");
|
||||
callback.withArgs("refs/pull/1/merge").resolves("b".repeat(40));
|
||||
callback.withArgs("HEAD").resolves("b".repeat(40));
|
||||
|
||||
const actualRef = await gitUtils.getRef();
|
||||
t.deepEqual(actualRef, "refs/pull/2/merge");
|
||||
callback.restore();
|
||||
getAdditionalInputStub.restore();
|
||||
});
|
||||
});
|
||||
const actualRef = await gitUtils.getRef();
|
||||
t.deepEqual(actualRef, "refs/pull/2/merge");
|
||||
callback.restore();
|
||||
getAdditionalInputStub.restore();
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("getRef() returns CODE_SCANNING_REF as a fallback for GITHUB_REF", async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const expectedRef = "refs/pull/1/HEAD";
|
||||
const currentSha = "a".repeat(40);
|
||||
process.env["CODE_SCANNING_REF"] = expectedRef;
|
||||
process.env["GITHUB_REF"] = "";
|
||||
process.env["GITHUB_SHA"] = currentSha;
|
||||
test.serial(
|
||||
"getRef() returns CODE_SCANNING_REF as a fallback for GITHUB_REF",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const expectedRef = "refs/pull/1/HEAD";
|
||||
const currentSha = "a".repeat(40);
|
||||
process.env["CODE_SCANNING_REF"] = expectedRef;
|
||||
process.env["GITHUB_REF"] = "";
|
||||
process.env["GITHUB_SHA"] = currentSha;
|
||||
|
||||
const actualRef = await gitUtils.getRef();
|
||||
t.deepEqual(actualRef, expectedRef);
|
||||
});
|
||||
});
|
||||
const actualRef = await gitUtils.getRef();
|
||||
t.deepEqual(actualRef, expectedRef);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("getRef() returns GITHUB_REF over CODE_SCANNING_REF if both are provided", async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const expectedRef = "refs/pull/1/merge";
|
||||
const currentSha = "a".repeat(40);
|
||||
process.env["CODE_SCANNING_REF"] = "refs/pull/1/HEAD";
|
||||
process.env["GITHUB_REF"] = expectedRef;
|
||||
process.env["GITHUB_SHA"] = currentSha;
|
||||
test.serial(
|
||||
"getRef() returns GITHUB_REF over CODE_SCANNING_REF if both are provided",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const expectedRef = "refs/pull/1/merge";
|
||||
const currentSha = "a".repeat(40);
|
||||
process.env["CODE_SCANNING_REF"] = "refs/pull/1/HEAD";
|
||||
process.env["GITHUB_REF"] = expectedRef;
|
||||
process.env["GITHUB_SHA"] = currentSha;
|
||||
|
||||
const actualRef = await gitUtils.getRef();
|
||||
t.deepEqual(actualRef, expectedRef);
|
||||
});
|
||||
});
|
||||
const actualRef = await gitUtils.getRef();
|
||||
t.deepEqual(actualRef, expectedRef);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("getRef() throws an error if only `ref` is provided as an input", async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const getAdditionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
|
||||
getAdditionalInputStub.withArgs("ref").resolves("refs/pull/1/merge");
|
||||
test.serial(
|
||||
"getRef() throws an error if only `ref` is provided as an input",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const getAdditionalInputStub = sinon.stub(
|
||||
actionsUtil,
|
||||
"getOptionalInput",
|
||||
);
|
||||
getAdditionalInputStub.withArgs("ref").resolves("refs/pull/1/merge");
|
||||
|
||||
await t.throwsAsync(
|
||||
async () => {
|
||||
await gitUtils.getRef();
|
||||
},
|
||||
{
|
||||
instanceOf: Error,
|
||||
message:
|
||||
"Both 'ref' and 'sha' are required if one of them is provided.",
|
||||
},
|
||||
);
|
||||
getAdditionalInputStub.restore();
|
||||
});
|
||||
});
|
||||
await t.throwsAsync(
|
||||
async () => {
|
||||
await gitUtils.getRef();
|
||||
},
|
||||
{
|
||||
instanceOf: Error,
|
||||
message:
|
||||
"Both 'ref' and 'sha' are required if one of them is provided.",
|
||||
},
|
||||
);
|
||||
getAdditionalInputStub.restore();
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("getRef() throws an error if only `sha` is provided as an input", async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
process.env["GITHUB_WORKSPACE"] = "/tmp";
|
||||
const getAdditionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
|
||||
getAdditionalInputStub.withArgs("sha").resolves("a".repeat(40));
|
||||
test.serial(
|
||||
"getRef() throws an error if only `sha` is provided as an input",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
process.env["GITHUB_WORKSPACE"] = "/tmp";
|
||||
const getAdditionalInputStub = sinon.stub(
|
||||
actionsUtil,
|
||||
"getOptionalInput",
|
||||
);
|
||||
getAdditionalInputStub.withArgs("sha").resolves("a".repeat(40));
|
||||
|
||||
await t.throwsAsync(
|
||||
async () => {
|
||||
await gitUtils.getRef();
|
||||
},
|
||||
{
|
||||
instanceOf: Error,
|
||||
message:
|
||||
"Both 'ref' and 'sha' are required if one of them is provided.",
|
||||
},
|
||||
);
|
||||
getAdditionalInputStub.restore();
|
||||
});
|
||||
});
|
||||
await t.throwsAsync(
|
||||
async () => {
|
||||
await gitUtils.getRef();
|
||||
},
|
||||
{
|
||||
instanceOf: Error,
|
||||
message:
|
||||
"Both 'ref' and 'sha' are required if one of them is provided.",
|
||||
},
|
||||
);
|
||||
getAdditionalInputStub.restore();
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("isAnalyzingDefaultBranch()", async (t) => {
|
||||
test.serial("isAnalyzingDefaultBranch()", async (t) => {
|
||||
process.env["GITHUB_EVENT_NAME"] = "push";
|
||||
process.env["CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH"] = "true";
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), true);
|
||||
@@ -213,7 +246,7 @@ test("isAnalyzingDefaultBranch()", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("determineBaseBranchHeadCommitOid non-pullrequest", async (t) => {
|
||||
test.serial("determineBaseBranchHeadCommitOid non-pullrequest", async (t) => {
|
||||
const infoStub = sinon.stub(core, "info");
|
||||
|
||||
process.env["GITHUB_EVENT_NAME"] = "hucairz";
|
||||
@@ -225,27 +258,30 @@ test("determineBaseBranchHeadCommitOid non-pullrequest", async (t) => {
|
||||
infoStub.restore();
|
||||
});
|
||||
|
||||
test("determineBaseBranchHeadCommitOid not git repository", async (t) => {
|
||||
const infoStub = sinon.stub(core, "info");
|
||||
test.serial(
|
||||
"determineBaseBranchHeadCommitOid not git repository",
|
||||
async (t) => {
|
||||
const infoStub = sinon.stub(core, "info");
|
||||
|
||||
process.env["GITHUB_EVENT_NAME"] = "pull_request";
|
||||
process.env["GITHUB_SHA"] = "100912429fab4cb230e66ffb11e738ac5194e73a";
|
||||
process.env["GITHUB_EVENT_NAME"] = "pull_request";
|
||||
process.env["GITHUB_SHA"] = "100912429fab4cb230e66ffb11e738ac5194e73a";
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
await gitUtils.determineBaseBranchHeadCommitOid(tmpDir);
|
||||
});
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
await gitUtils.determineBaseBranchHeadCommitOid(tmpDir);
|
||||
});
|
||||
|
||||
t.deepEqual(1, infoStub.callCount);
|
||||
t.deepEqual(
|
||||
infoStub.firstCall.args[0],
|
||||
"git call failed. Will calculate the base branch SHA on the server. Error: " +
|
||||
"The checkout path provided to the action does not appear to be a git repository.",
|
||||
);
|
||||
t.deepEqual(1, infoStub.callCount);
|
||||
t.deepEqual(
|
||||
infoStub.firstCall.args[0],
|
||||
"git call failed. Will calculate the base branch SHA on the server. Error: " +
|
||||
"The checkout path provided to the action does not appear to be a git repository.",
|
||||
);
|
||||
|
||||
infoStub.restore();
|
||||
});
|
||||
infoStub.restore();
|
||||
},
|
||||
);
|
||||
|
||||
test("determineBaseBranchHeadCommitOid other error", async (t) => {
|
||||
test.serial("determineBaseBranchHeadCommitOid other error", async (t) => {
|
||||
const infoStub = sinon.stub(core, "info");
|
||||
|
||||
process.env["GITHUB_EVENT_NAME"] = "pull_request";
|
||||
@@ -269,7 +305,7 @@ test("determineBaseBranchHeadCommitOid other error", async (t) => {
|
||||
infoStub.restore();
|
||||
});
|
||||
|
||||
test("decodeGitFilePath unquoted strings", async (t) => {
|
||||
test.serial("decodeGitFilePath unquoted strings", async (t) => {
|
||||
t.deepEqual(gitUtils.decodeGitFilePath("foo"), "foo");
|
||||
t.deepEqual(gitUtils.decodeGitFilePath("foo bar"), "foo bar");
|
||||
t.deepEqual(gitUtils.decodeGitFilePath("foo\\\\bar"), "foo\\\\bar");
|
||||
@@ -288,7 +324,7 @@ test("decodeGitFilePath unquoted strings", async (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("decodeGitFilePath quoted strings", async (t) => {
|
||||
test.serial("decodeGitFilePath quoted strings", async (t) => {
|
||||
t.deepEqual(gitUtils.decodeGitFilePath('"foo"'), "foo");
|
||||
t.deepEqual(gitUtils.decodeGitFilePath('"foo bar"'), "foo bar");
|
||||
t.deepEqual(gitUtils.decodeGitFilePath('"foo\\\\bar"'), "foo\\bar");
|
||||
@@ -307,7 +343,7 @@ test("decodeGitFilePath quoted strings", async (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("getFileOidsUnderPath returns correct file mapping", async (t) => {
|
||||
test.serial("getFileOidsUnderPath returns correct file mapping", async (t) => {
|
||||
const runGitCommandStub = sinon
|
||||
.stub(gitUtils as any, "runGitCommand")
|
||||
.resolves(
|
||||
@@ -331,7 +367,7 @@ test("getFileOidsUnderPath returns correct file mapping", async (t) => {
|
||||
]);
|
||||
});
|
||||
|
||||
test("getFileOidsUnderPath handles quoted paths", async (t) => {
|
||||
test.serial("getFileOidsUnderPath handles quoted paths", async (t) => {
|
||||
sinon
|
||||
.stub(gitUtils as any, "runGitCommand")
|
||||
.resolves(
|
||||
@@ -349,44 +385,50 @@ test("getFileOidsUnderPath handles quoted paths", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("getFileOidsUnderPath handles empty output", async (t) => {
|
||||
test.serial("getFileOidsUnderPath handles empty output", async (t) => {
|
||||
sinon.stub(gitUtils as any, "runGitCommand").resolves("");
|
||||
|
||||
const result = await gitUtils.getFileOidsUnderPath("/fake/path");
|
||||
t.deepEqual(result, {});
|
||||
});
|
||||
|
||||
test("getFileOidsUnderPath throws on unexpected output format", async (t) => {
|
||||
sinon
|
||||
.stub(gitUtils as any, "runGitCommand")
|
||||
.resolves(
|
||||
"30d998ded095371488be3a729eb61d86ed721a18_lib/git-utils.js\n" +
|
||||
"invalid-line-format\n" +
|
||||
"a47c11f5bfdca7661942d2c8f1b7209fb0dfdf96_src/git-utils.ts",
|
||||
test.serial(
|
||||
"getFileOidsUnderPath throws on unexpected output format",
|
||||
async (t) => {
|
||||
sinon
|
||||
.stub(gitUtils as any, "runGitCommand")
|
||||
.resolves(
|
||||
"30d998ded095371488be3a729eb61d86ed721a18_lib/git-utils.js\n" +
|
||||
"invalid-line-format\n" +
|
||||
"a47c11f5bfdca7661942d2c8f1b7209fb0dfdf96_src/git-utils.ts",
|
||||
);
|
||||
|
||||
await t.throwsAsync(
|
||||
async () => {
|
||||
await gitUtils.getFileOidsUnderPath("/fake/path");
|
||||
},
|
||||
{
|
||||
instanceOf: Error,
|
||||
message: 'Unexpected "git ls-files" output: invalid-line-format',
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
await t.throwsAsync(
|
||||
async () => {
|
||||
await gitUtils.getFileOidsUnderPath("/fake/path");
|
||||
},
|
||||
{
|
||||
instanceOf: Error,
|
||||
message: 'Unexpected "git ls-files" output: invalid-line-format',
|
||||
},
|
||||
);
|
||||
});
|
||||
test.serial(
|
||||
"getGitVersionOrThrow returns version for valid git output",
|
||||
async (t) => {
|
||||
sinon
|
||||
.stub(gitUtils as any, "runGitCommand")
|
||||
.resolves(`git version 2.40.0${os.EOL}`);
|
||||
|
||||
test("getGitVersionOrThrow returns version for valid git output", async (t) => {
|
||||
sinon
|
||||
.stub(gitUtils as any, "runGitCommand")
|
||||
.resolves(`git version 2.40.0${os.EOL}`);
|
||||
const version = await gitUtils.getGitVersionOrThrow();
|
||||
t.is(version.truncatedVersion, "2.40.0");
|
||||
t.is(version.fullVersion, "2.40.0");
|
||||
},
|
||||
);
|
||||
|
||||
const version = await gitUtils.getGitVersionOrThrow();
|
||||
t.is(version.truncatedVersion, "2.40.0");
|
||||
t.is(version.fullVersion, "2.40.0");
|
||||
});
|
||||
|
||||
test("getGitVersionOrThrow throws for invalid git output", async (t) => {
|
||||
test.serial("getGitVersionOrThrow throws for invalid git output", async (t) => {
|
||||
sinon.stub(gitUtils as any, "runGitCommand").resolves("invalid output");
|
||||
|
||||
await t.throwsAsync(
|
||||
@@ -400,18 +442,21 @@ test("getGitVersionOrThrow throws for invalid git output", async (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("getGitVersionOrThrow handles Windows-style git output", async (t) => {
|
||||
sinon
|
||||
.stub(gitUtils as any, "runGitCommand")
|
||||
.resolves("git version 2.40.0.windows.1");
|
||||
test.serial(
|
||||
"getGitVersionOrThrow handles Windows-style git output",
|
||||
async (t) => {
|
||||
sinon
|
||||
.stub(gitUtils as any, "runGitCommand")
|
||||
.resolves("git version 2.40.0.windows.1");
|
||||
|
||||
const version = await gitUtils.getGitVersionOrThrow();
|
||||
// The truncated version should contain just the major.minor.patch portion
|
||||
t.is(version.truncatedVersion, "2.40.0");
|
||||
t.is(version.fullVersion, "2.40.0.windows.1");
|
||||
});
|
||||
const version = await gitUtils.getGitVersionOrThrow();
|
||||
// The truncated version should contain just the major.minor.patch portion
|
||||
t.is(version.truncatedVersion, "2.40.0");
|
||||
t.is(version.fullVersion, "2.40.0.windows.1");
|
||||
},
|
||||
);
|
||||
|
||||
test("getGitVersionOrThrow throws when git command fails", async (t) => {
|
||||
test.serial("getGitVersionOrThrow throws when git command fails", async (t) => {
|
||||
sinon
|
||||
.stub(gitUtils as any, "runGitCommand")
|
||||
.rejects(new Error("git not found"));
|
||||
@@ -427,16 +472,19 @@ test("getGitVersionOrThrow throws when git command fails", async (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("GitVersionInfo.isAtLeast correctly compares versions", async (t) => {
|
||||
const version = new gitUtils.GitVersionInfo("2.40.0", "2.40.0");
|
||||
test.serial(
|
||||
"GitVersionInfo.isAtLeast correctly compares versions",
|
||||
async (t) => {
|
||||
const version = new gitUtils.GitVersionInfo("2.40.0", "2.40.0");
|
||||
|
||||
t.true(version.isAtLeast("2.38.0"));
|
||||
t.true(version.isAtLeast("2.40.0"));
|
||||
t.false(version.isAtLeast("2.41.0"));
|
||||
t.false(version.isAtLeast("3.0.0"));
|
||||
});
|
||||
t.true(version.isAtLeast("2.38.0"));
|
||||
t.true(version.isAtLeast("2.40.0"));
|
||||
t.false(version.isAtLeast("2.41.0"));
|
||||
t.false(version.isAtLeast("3.0.0"));
|
||||
},
|
||||
);
|
||||
|
||||
test("listFiles returns array of file paths", async (t) => {
|
||||
test.serial("listFiles returns array of file paths", async (t) => {
|
||||
sinon
|
||||
.stub(gitUtils, "runGitCommand")
|
||||
.resolves(["dir/file.txt", "README.txt", ""].join(os.EOL));
|
||||
@@ -448,7 +496,7 @@ test("listFiles returns array of file paths", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("getGeneratedFiles returns generated files only", async (t) => {
|
||||
test.serial("getGeneratedFiles returns generated files only", async (t) => {
|
||||
const runGitCommandStub = sinon.stub(gitUtils, "runGitCommand");
|
||||
|
||||
runGitCommandStub
|
||||
|
||||
@@ -26,7 +26,7 @@ const NUM_BYTES_PER_GIB = 1024 * 1024 * 1024;
|
||||
|
||||
setupTests(test);
|
||||
|
||||
test("init-post action with debug mode off", async (t) => {
|
||||
test.serial("init-post action with debug mode off", async (t) => {
|
||||
return await util.withTmpDir(async (tmpDir) => {
|
||||
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
|
||||
process.env["RUNNER_TEMP"] = tmpDir;
|
||||
@@ -60,7 +60,7 @@ test("init-post action with debug mode off", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("init-post action with debug mode on", async (t) => {
|
||||
test.serial("init-post action with debug mode on", async (t) => {
|
||||
return await util.withTmpDir(async (tmpDir) => {
|
||||
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
|
||||
process.env["RUNNER_TEMP"] = tmpDir;
|
||||
@@ -83,83 +83,94 @@ test("init-post action with debug mode on", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("uploads failed SARIF run with `diagnostics export` if feature flag is off", async (t) => {
|
||||
const actionsWorkflow = createTestWorkflow([
|
||||
{
|
||||
name: "Checkout repository",
|
||||
uses: "actions/checkout@v5",
|
||||
},
|
||||
{
|
||||
name: "Initialize CodeQL",
|
||||
uses: "github/codeql-action/init@v4",
|
||||
with: {
|
||||
languages: "javascript",
|
||||
test.serial(
|
||||
"uploads failed SARIF run with `diagnostics export` if feature flag is off",
|
||||
async (t) => {
|
||||
const actionsWorkflow = createTestWorkflow([
|
||||
{
|
||||
name: "Checkout repository",
|
||||
uses: "actions/checkout@v5",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Perform CodeQL Analysis",
|
||||
uses: "github/codeql-action/analyze@v4",
|
||||
with: {
|
||||
category: "my-category",
|
||||
{
|
||||
name: "Initialize CodeQL",
|
||||
uses: "github/codeql-action/init@v4",
|
||||
with: {
|
||||
languages: "javascript",
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
await testFailedSarifUpload(t, actionsWorkflow, { category: "my-category" });
|
||||
});
|
||||
{
|
||||
name: "Perform CodeQL Analysis",
|
||||
uses: "github/codeql-action/analyze@v4",
|
||||
with: {
|
||||
category: "my-category",
|
||||
},
|
||||
},
|
||||
]);
|
||||
await testFailedSarifUpload(t, actionsWorkflow, {
|
||||
category: "my-category",
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("uploads failed SARIF run with `diagnostics export` if the database doesn't exist", async (t) => {
|
||||
const actionsWorkflow = createTestWorkflow([
|
||||
{
|
||||
name: "Checkout repository",
|
||||
uses: "actions/checkout@v5",
|
||||
},
|
||||
{
|
||||
name: "Initialize CodeQL",
|
||||
uses: "github/codeql-action/init@v4",
|
||||
with: {
|
||||
languages: "javascript",
|
||||
test.serial(
|
||||
"uploads failed SARIF run with `diagnostics export` if the database doesn't exist",
|
||||
async (t) => {
|
||||
const actionsWorkflow = createTestWorkflow([
|
||||
{
|
||||
name: "Checkout repository",
|
||||
uses: "actions/checkout@v5",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Perform CodeQL Analysis",
|
||||
uses: "github/codeql-action/analyze@v4",
|
||||
with: {
|
||||
category: "my-category",
|
||||
{
|
||||
name: "Initialize CodeQL",
|
||||
uses: "github/codeql-action/init@v4",
|
||||
with: {
|
||||
languages: "javascript",
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
await testFailedSarifUpload(t, actionsWorkflow, {
|
||||
category: "my-category",
|
||||
databaseExists: false,
|
||||
});
|
||||
});
|
||||
{
|
||||
name: "Perform CodeQL Analysis",
|
||||
uses: "github/codeql-action/analyze@v4",
|
||||
with: {
|
||||
category: "my-category",
|
||||
},
|
||||
},
|
||||
]);
|
||||
await testFailedSarifUpload(t, actionsWorkflow, {
|
||||
category: "my-category",
|
||||
databaseExists: false,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("uploads failed SARIF run with database export-diagnostics if the database exists and feature flag is on", async (t) => {
|
||||
const actionsWorkflow = createTestWorkflow([
|
||||
{
|
||||
name: "Checkout repository",
|
||||
uses: "actions/checkout@v5",
|
||||
},
|
||||
{
|
||||
name: "Initialize CodeQL",
|
||||
uses: "github/codeql-action/init@v4",
|
||||
with: {
|
||||
languages: "javascript",
|
||||
test.serial(
|
||||
"uploads failed SARIF run with database export-diagnostics if the database exists and feature flag is on",
|
||||
async (t) => {
|
||||
const actionsWorkflow = createTestWorkflow([
|
||||
{
|
||||
name: "Checkout repository",
|
||||
uses: "actions/checkout@v5",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Perform CodeQL Analysis",
|
||||
uses: "github/codeql-action/analyze@v4",
|
||||
with: {
|
||||
category: "my-category",
|
||||
{
|
||||
name: "Initialize CodeQL",
|
||||
uses: "github/codeql-action/init@v4",
|
||||
with: {
|
||||
languages: "javascript",
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
await testFailedSarifUpload(t, actionsWorkflow, {
|
||||
category: "my-category",
|
||||
exportDiagnosticsEnabled: true,
|
||||
});
|
||||
});
|
||||
{
|
||||
name: "Perform CodeQL Analysis",
|
||||
uses: "github/codeql-action/analyze@v4",
|
||||
with: {
|
||||
category: "my-category",
|
||||
},
|
||||
},
|
||||
]);
|
||||
await testFailedSarifUpload(t, actionsWorkflow, {
|
||||
category: "my-category",
|
||||
exportDiagnosticsEnabled: true,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const UPLOAD_INPUT_TEST_CASES = [
|
||||
{
|
||||
@@ -189,9 +200,49 @@ const UPLOAD_INPUT_TEST_CASES = [
|
||||
];
|
||||
|
||||
for (const { uploadInput, shouldUpload } of UPLOAD_INPUT_TEST_CASES) {
|
||||
test(`does ${
|
||||
shouldUpload ? "" : "not "
|
||||
}upload failed SARIF run for workflow with upload: ${uploadInput}`, async (t) => {
|
||||
test.serial(
|
||||
`does ${
|
||||
shouldUpload ? "" : "not "
|
||||
}upload failed SARIF run for workflow with upload: ${uploadInput}`,
|
||||
async (t) => {
|
||||
const actionsWorkflow = createTestWorkflow([
|
||||
{
|
||||
name: "Checkout repository",
|
||||
uses: "actions/checkout@v5",
|
||||
},
|
||||
{
|
||||
name: "Initialize CodeQL",
|
||||
uses: "github/codeql-action/init@v4",
|
||||
with: {
|
||||
languages: "javascript",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Perform CodeQL Analysis",
|
||||
uses: "github/codeql-action/analyze@v4",
|
||||
with: {
|
||||
category: "my-category",
|
||||
upload: uploadInput,
|
||||
},
|
||||
},
|
||||
]);
|
||||
const result = await testFailedSarifUpload(t, actionsWorkflow, {
|
||||
category: "my-category",
|
||||
expectUpload: shouldUpload,
|
||||
});
|
||||
if (!shouldUpload) {
|
||||
t.is(
|
||||
result.upload_failed_run_skipped_because,
|
||||
"SARIF upload is disabled",
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
test.serial(
|
||||
"uploading failed SARIF run succeeds when workflow uses an input with a matrix var",
|
||||
async (t) => {
|
||||
const actionsWorkflow = createTestWorkflow([
|
||||
{
|
||||
name: "Checkout repository",
|
||||
@@ -208,221 +259,202 @@ for (const { uploadInput, shouldUpload } of UPLOAD_INPUT_TEST_CASES) {
|
||||
name: "Perform CodeQL Analysis",
|
||||
uses: "github/codeql-action/analyze@v4",
|
||||
with: {
|
||||
category: "my-category",
|
||||
upload: uploadInput,
|
||||
category: "/language:${{ matrix.language }}",
|
||||
},
|
||||
},
|
||||
]);
|
||||
await testFailedSarifUpload(t, actionsWorkflow, {
|
||||
category: "/language:csharp",
|
||||
matrix: { language: "csharp" },
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"uploading failed SARIF run fails when workflow uses a complex upload input",
|
||||
async (t) => {
|
||||
const actionsWorkflow = createTestWorkflow([
|
||||
{
|
||||
name: "Checkout repository",
|
||||
uses: "actions/checkout@v5",
|
||||
},
|
||||
{
|
||||
name: "Initialize CodeQL",
|
||||
uses: "github/codeql-action/init@v4",
|
||||
with: {
|
||||
languages: "javascript",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Perform CodeQL Analysis",
|
||||
uses: "github/codeql-action/analyze@v4",
|
||||
with: {
|
||||
upload: "${{ matrix.language != 'csharp' }}",
|
||||
},
|
||||
},
|
||||
]);
|
||||
const result = await testFailedSarifUpload(t, actionsWorkflow, {
|
||||
category: "my-category",
|
||||
expectUpload: shouldUpload,
|
||||
expectUpload: false,
|
||||
});
|
||||
if (!shouldUpload) {
|
||||
t.is(
|
||||
result.upload_failed_run_skipped_because,
|
||||
"SARIF upload is disabled",
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
test("uploading failed SARIF run succeeds when workflow uses an input with a matrix var", async (t) => {
|
||||
const actionsWorkflow = createTestWorkflow([
|
||||
{
|
||||
name: "Checkout repository",
|
||||
uses: "actions/checkout@v5",
|
||||
},
|
||||
{
|
||||
name: "Initialize CodeQL",
|
||||
uses: "github/codeql-action/init@v4",
|
||||
with: {
|
||||
languages: "javascript",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Perform CodeQL Analysis",
|
||||
uses: "github/codeql-action/analyze@v4",
|
||||
with: {
|
||||
category: "/language:${{ matrix.language }}",
|
||||
},
|
||||
},
|
||||
]);
|
||||
await testFailedSarifUpload(t, actionsWorkflow, {
|
||||
category: "/language:csharp",
|
||||
matrix: { language: "csharp" },
|
||||
});
|
||||
});
|
||||
|
||||
test("uploading failed SARIF run fails when workflow uses a complex upload input", async (t) => {
|
||||
const actionsWorkflow = createTestWorkflow([
|
||||
{
|
||||
name: "Checkout repository",
|
||||
uses: "actions/checkout@v5",
|
||||
},
|
||||
{
|
||||
name: "Initialize CodeQL",
|
||||
uses: "github/codeql-action/init@v4",
|
||||
with: {
|
||||
languages: "javascript",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Perform CodeQL Analysis",
|
||||
uses: "github/codeql-action/analyze@v4",
|
||||
with: {
|
||||
upload: "${{ matrix.language != 'csharp' }}",
|
||||
},
|
||||
},
|
||||
]);
|
||||
const result = await testFailedSarifUpload(t, actionsWorkflow, {
|
||||
expectUpload: false,
|
||||
});
|
||||
t.is(
|
||||
result.upload_failed_run_error,
|
||||
"Could not get upload input to github/codeql-action/analyze since it contained an " +
|
||||
"unrecognized dynamic value.",
|
||||
);
|
||||
});
|
||||
|
||||
test("uploading failed SARIF run fails when workflow does not reference github/codeql-action", async (t) => {
|
||||
const actionsWorkflow = createTestWorkflow([
|
||||
{
|
||||
name: "Checkout repository",
|
||||
uses: "actions/checkout@v5",
|
||||
},
|
||||
]);
|
||||
const result = await testFailedSarifUpload(t, actionsWorkflow, {
|
||||
expectUpload: false,
|
||||
});
|
||||
t.is(
|
||||
result.upload_failed_run_error,
|
||||
"Could not get upload input to github/codeql-action/analyze since the analyze job does not " +
|
||||
"call github/codeql-action/analyze.",
|
||||
);
|
||||
t.truthy(result.upload_failed_run_stack_trace);
|
||||
});
|
||||
|
||||
test("not uploading failed SARIF when `code-scanning` is not an enabled analysis kind", async (t) => {
|
||||
const result = await testFailedSarifUpload(t, createTestWorkflow([]), {
|
||||
analysisKinds: [AnalysisKind.CodeQuality],
|
||||
expectUpload: false,
|
||||
});
|
||||
t.is(
|
||||
result.upload_failed_run_skipped_because,
|
||||
"Code Scanning is not enabled.",
|
||||
);
|
||||
});
|
||||
|
||||
test("saves overlay status when overlay-base analysis did not complete successfully", async (t) => {
|
||||
return await util.withTmpDir(async (tmpDir) => {
|
||||
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
|
||||
process.env["GITHUB_RUN_ID"] = "12345";
|
||||
process.env["GITHUB_RUN_ATTEMPT"] = "1";
|
||||
process.env["GITHUB_JOB"] = "analyze";
|
||||
process.env["RUNNER_TEMP"] = tmpDir;
|
||||
// Ensure analyze did not complete successfully.
|
||||
delete process.env[EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY];
|
||||
|
||||
const diskUsage: util.DiskUsage = {
|
||||
numAvailableBytes: 100 * NUM_BYTES_PER_GIB,
|
||||
numTotalBytes: 200 * NUM_BYTES_PER_GIB,
|
||||
};
|
||||
sinon.stub(util, "checkDiskUsage").resolves(diskUsage);
|
||||
|
||||
const saveOverlayStatusStub = sinon
|
||||
.stub(overlayStatus, "saveOverlayStatus")
|
||||
.resolves(true);
|
||||
|
||||
const stubCodeQL = codeql.createStubCodeQL({});
|
||||
|
||||
await initActionPostHelper.run(
|
||||
sinon.spy(),
|
||||
sinon.spy(),
|
||||
stubCodeQL,
|
||||
createTestConfig({
|
||||
debugMode: false,
|
||||
languages: ["javascript"],
|
||||
overlayDatabaseMode: OverlayDatabaseMode.OverlayBase,
|
||||
}),
|
||||
parseRepositoryNwo("github/codeql-action"),
|
||||
createFeatures([Feature.OverlayAnalysisStatusSave]),
|
||||
getRunnerLogger(true),
|
||||
t.is(
|
||||
result.upload_failed_run_error,
|
||||
"Could not get upload input to github/codeql-action/analyze since it contained an " +
|
||||
"unrecognized dynamic value.",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
t.true(
|
||||
saveOverlayStatusStub.calledOnce,
|
||||
"saveOverlayStatus should be called exactly once",
|
||||
);
|
||||
t.deepEqual(
|
||||
saveOverlayStatusStub.firstCall.args[0],
|
||||
stubCodeQL,
|
||||
"first arg should be the CodeQL instance",
|
||||
);
|
||||
t.deepEqual(
|
||||
saveOverlayStatusStub.firstCall.args[1],
|
||||
["javascript"],
|
||||
"second arg should be the languages",
|
||||
);
|
||||
t.deepEqual(
|
||||
saveOverlayStatusStub.firstCall.args[2],
|
||||
diskUsage,
|
||||
"third arg should be the disk usage",
|
||||
);
|
||||
t.deepEqual(
|
||||
saveOverlayStatusStub.firstCall.args[3],
|
||||
test.serial(
|
||||
"uploading failed SARIF run fails when workflow does not reference github/codeql-action",
|
||||
async (t) => {
|
||||
const actionsWorkflow = createTestWorkflow([
|
||||
{
|
||||
attemptedToBuildOverlayBaseDatabase: true,
|
||||
builtOverlayBaseDatabase: false,
|
||||
job: {
|
||||
checkRunId: undefined,
|
||||
workflowRunId: 12345,
|
||||
workflowRunAttempt: 1,
|
||||
name: "analyze",
|
||||
},
|
||||
name: "Checkout repository",
|
||||
uses: "actions/checkout@v5",
|
||||
},
|
||||
"fourth arg should be the overlay status recording an unsuccessful build attempt with job details",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("does not save overlay status when OverlayAnalysisStatusSave feature flag is disabled", async (t) => {
|
||||
return await util.withTmpDir(async (tmpDir) => {
|
||||
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
|
||||
process.env["RUNNER_TEMP"] = tmpDir;
|
||||
// Ensure analyze did not complete successfully.
|
||||
delete process.env[EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY];
|
||||
|
||||
sinon.stub(util, "checkDiskUsage").resolves({
|
||||
numAvailableBytes: 100 * NUM_BYTES_PER_GIB,
|
||||
numTotalBytes: 200 * NUM_BYTES_PER_GIB,
|
||||
]);
|
||||
const result = await testFailedSarifUpload(t, actionsWorkflow, {
|
||||
expectUpload: false,
|
||||
});
|
||||
|
||||
const saveOverlayStatusStub = sinon
|
||||
.stub(overlayStatus, "saveOverlayStatus")
|
||||
.resolves(true);
|
||||
|
||||
await initActionPostHelper.run(
|
||||
sinon.spy(),
|
||||
sinon.spy(),
|
||||
codeql.createStubCodeQL({}),
|
||||
createTestConfig({
|
||||
debugMode: false,
|
||||
languages: ["javascript"],
|
||||
overlayDatabaseMode: OverlayDatabaseMode.OverlayBase,
|
||||
}),
|
||||
parseRepositoryNwo("github/codeql-action"),
|
||||
createFeatures([]),
|
||||
getRunnerLogger(true),
|
||||
t.is(
|
||||
result.upload_failed_run_error,
|
||||
"Could not get upload input to github/codeql-action/analyze since the analyze job does not " +
|
||||
"call github/codeql-action/analyze.",
|
||||
);
|
||||
t.truthy(result.upload_failed_run_stack_trace);
|
||||
},
|
||||
);
|
||||
|
||||
t.true(
|
||||
saveOverlayStatusStub.notCalled,
|
||||
"saveOverlayStatus should not be called when OverlayAnalysisStatusSave feature flag is disabled",
|
||||
test.serial(
|
||||
"not uploading failed SARIF when `code-scanning` is not an enabled analysis kind",
|
||||
async (t) => {
|
||||
const result = await testFailedSarifUpload(t, createTestWorkflow([]), {
|
||||
analysisKinds: [AnalysisKind.CodeQuality],
|
||||
expectUpload: false,
|
||||
});
|
||||
t.is(
|
||||
result.upload_failed_run_skipped_because,
|
||||
"Code Scanning is not enabled.",
|
||||
);
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("does not save overlay status when build successful", async (t) => {
|
||||
test.serial(
|
||||
"saves overlay status when overlay-base analysis did not complete successfully",
|
||||
async (t) => {
|
||||
return await util.withTmpDir(async (tmpDir) => {
|
||||
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
|
||||
process.env["GITHUB_RUN_ID"] = "12345";
|
||||
process.env["GITHUB_RUN_ATTEMPT"] = "1";
|
||||
process.env["GITHUB_JOB"] = "analyze";
|
||||
process.env["RUNNER_TEMP"] = tmpDir;
|
||||
// Ensure analyze did not complete successfully.
|
||||
delete process.env[EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY];
|
||||
|
||||
const diskUsage: util.DiskUsage = {
|
||||
numAvailableBytes: 100 * NUM_BYTES_PER_GIB,
|
||||
numTotalBytes: 200 * NUM_BYTES_PER_GIB,
|
||||
};
|
||||
sinon.stub(util, "checkDiskUsage").resolves(diskUsage);
|
||||
|
||||
const saveOverlayStatusStub = sinon
|
||||
.stub(overlayStatus, "saveOverlayStatus")
|
||||
.resolves(true);
|
||||
|
||||
const stubCodeQL = codeql.createStubCodeQL({});
|
||||
|
||||
await initActionPostHelper.run(
|
||||
sinon.spy(),
|
||||
sinon.spy(),
|
||||
stubCodeQL,
|
||||
createTestConfig({
|
||||
debugMode: false,
|
||||
languages: ["javascript"],
|
||||
overlayDatabaseMode: OverlayDatabaseMode.OverlayBase,
|
||||
}),
|
||||
parseRepositoryNwo("github/codeql-action"),
|
||||
createFeatures([Feature.OverlayAnalysisStatusSave]),
|
||||
getRunnerLogger(true),
|
||||
);
|
||||
|
||||
t.true(
|
||||
saveOverlayStatusStub.calledOnce,
|
||||
"saveOverlayStatus should be called exactly once",
|
||||
);
|
||||
t.deepEqual(
|
||||
saveOverlayStatusStub.firstCall.args[0],
|
||||
stubCodeQL,
|
||||
"first arg should be the CodeQL instance",
|
||||
);
|
||||
t.deepEqual(
|
||||
saveOverlayStatusStub.firstCall.args[1],
|
||||
["javascript"],
|
||||
"second arg should be the languages",
|
||||
);
|
||||
t.deepEqual(
|
||||
saveOverlayStatusStub.firstCall.args[2],
|
||||
diskUsage,
|
||||
"third arg should be the disk usage",
|
||||
);
|
||||
t.deepEqual(
|
||||
saveOverlayStatusStub.firstCall.args[3],
|
||||
{
|
||||
attemptedToBuildOverlayBaseDatabase: true,
|
||||
builtOverlayBaseDatabase: false,
|
||||
job: {
|
||||
checkRunId: undefined,
|
||||
workflowRunId: 12345,
|
||||
workflowRunAttempt: 1,
|
||||
name: "analyze",
|
||||
},
|
||||
},
|
||||
"fourth arg should be the overlay status recording an unsuccessful build attempt with job details",
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"does not save overlay status when OverlayAnalysisStatusSave feature flag is disabled",
|
||||
async (t) => {
|
||||
return await util.withTmpDir(async (tmpDir) => {
|
||||
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
|
||||
process.env["RUNNER_TEMP"] = tmpDir;
|
||||
// Ensure analyze did not complete successfully.
|
||||
delete process.env[EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY];
|
||||
|
||||
sinon.stub(util, "checkDiskUsage").resolves({
|
||||
numAvailableBytes: 100 * NUM_BYTES_PER_GIB,
|
||||
numTotalBytes: 200 * NUM_BYTES_PER_GIB,
|
||||
});
|
||||
|
||||
const saveOverlayStatusStub = sinon
|
||||
.stub(overlayStatus, "saveOverlayStatus")
|
||||
.resolves(true);
|
||||
|
||||
await initActionPostHelper.run(
|
||||
sinon.spy(),
|
||||
sinon.spy(),
|
||||
codeql.createStubCodeQL({}),
|
||||
createTestConfig({
|
||||
debugMode: false,
|
||||
languages: ["javascript"],
|
||||
overlayDatabaseMode: OverlayDatabaseMode.OverlayBase,
|
||||
}),
|
||||
parseRepositoryNwo("github/codeql-action"),
|
||||
createFeatures([]),
|
||||
getRunnerLogger(true),
|
||||
);
|
||||
|
||||
t.true(
|
||||
saveOverlayStatusStub.notCalled,
|
||||
"saveOverlayStatus should not be called when OverlayAnalysisStatusSave feature flag is disabled",
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial("does not save overlay status when build successful", async (t) => {
|
||||
return await util.withTmpDir(async (tmpDir) => {
|
||||
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
|
||||
process.env["RUNNER_TEMP"] = tmpDir;
|
||||
@@ -459,41 +491,44 @@ test("does not save overlay status when build successful", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("does not save overlay status when overlay not enabled", async (t) => {
|
||||
return await util.withTmpDir(async (tmpDir) => {
|
||||
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
|
||||
process.env["RUNNER_TEMP"] = tmpDir;
|
||||
delete process.env[EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY];
|
||||
test.serial(
|
||||
"does not save overlay status when overlay not enabled",
|
||||
async (t) => {
|
||||
return await util.withTmpDir(async (tmpDir) => {
|
||||
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
|
||||
process.env["RUNNER_TEMP"] = tmpDir;
|
||||
delete process.env[EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY];
|
||||
|
||||
sinon.stub(util, "checkDiskUsage").resolves({
|
||||
numAvailableBytes: 100 * NUM_BYTES_PER_GIB,
|
||||
numTotalBytes: 200 * NUM_BYTES_PER_GIB,
|
||||
sinon.stub(util, "checkDiskUsage").resolves({
|
||||
numAvailableBytes: 100 * NUM_BYTES_PER_GIB,
|
||||
numTotalBytes: 200 * NUM_BYTES_PER_GIB,
|
||||
});
|
||||
|
||||
const saveOverlayStatusStub = sinon
|
||||
.stub(overlayStatus, "saveOverlayStatus")
|
||||
.resolves(true);
|
||||
|
||||
await initActionPostHelper.run(
|
||||
sinon.spy(),
|
||||
sinon.spy(),
|
||||
codeql.createStubCodeQL({}),
|
||||
createTestConfig({
|
||||
debugMode: false,
|
||||
languages: ["javascript"],
|
||||
overlayDatabaseMode: OverlayDatabaseMode.None,
|
||||
}),
|
||||
parseRepositoryNwo("github/codeql-action"),
|
||||
createFeatures([]),
|
||||
getRunnerLogger(true),
|
||||
);
|
||||
|
||||
t.true(
|
||||
saveOverlayStatusStub.notCalled,
|
||||
"saveOverlayStatus should not be called when overlay is not enabled",
|
||||
);
|
||||
});
|
||||
|
||||
const saveOverlayStatusStub = sinon
|
||||
.stub(overlayStatus, "saveOverlayStatus")
|
||||
.resolves(true);
|
||||
|
||||
await initActionPostHelper.run(
|
||||
sinon.spy(),
|
||||
sinon.spy(),
|
||||
codeql.createStubCodeQL({}),
|
||||
createTestConfig({
|
||||
debugMode: false,
|
||||
languages: ["javascript"],
|
||||
overlayDatabaseMode: OverlayDatabaseMode.None,
|
||||
}),
|
||||
parseRepositoryNwo("github/codeql-action"),
|
||||
createFeatures([]),
|
||||
getRunnerLogger(true),
|
||||
);
|
||||
|
||||
t.true(
|
||||
saveOverlayStatusStub.notCalled,
|
||||
"saveOverlayStatus should not be called when overlay is not enabled",
|
||||
);
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
function createTestWorkflow(
|
||||
steps: workflow.WorkflowJobStep[],
|
||||
|
||||
165
src/init.test.ts
165
src/init.test.ts
@@ -77,46 +77,49 @@ for (const { runnerEnv, ErrorConstructor, message } of [
|
||||
"otherwise we recommend rerunning the job.",
|
||||
},
|
||||
]) {
|
||||
test(`cleanupDatabaseClusterDirectory throws a ${ErrorConstructor.name} when cleanup fails on ${runnerEnv} runner`, async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
process.env["RUNNER_ENVIRONMENT"] = runnerEnv;
|
||||
test.serial(
|
||||
`cleanupDatabaseClusterDirectory throws a ${ErrorConstructor.name} when cleanup fails on ${runnerEnv} runner`,
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
process.env["RUNNER_ENVIRONMENT"] = runnerEnv;
|
||||
|
||||
const dbLocation = path.resolve(tmpDir, "dbs");
|
||||
fs.mkdirSync(dbLocation, { recursive: true });
|
||||
const dbLocation = path.resolve(tmpDir, "dbs");
|
||||
fs.mkdirSync(dbLocation, { recursive: true });
|
||||
|
||||
const fileToCleanUp = path.resolve(
|
||||
dbLocation,
|
||||
"something-to-cleanup.txt",
|
||||
);
|
||||
fs.writeFileSync(fileToCleanUp, "");
|
||||
const fileToCleanUp = path.resolve(
|
||||
dbLocation,
|
||||
"something-to-cleanup.txt",
|
||||
);
|
||||
fs.writeFileSync(fileToCleanUp, "");
|
||||
|
||||
const rmSyncError = `Failed to clean up file ${fileToCleanUp}`;
|
||||
const rmSyncError = `Failed to clean up file ${fileToCleanUp}`;
|
||||
|
||||
const messages: LoggedMessage[] = [];
|
||||
t.throws(
|
||||
() =>
|
||||
cleanupDatabaseClusterDirectory(
|
||||
createTestConfig({ dbLocation }),
|
||||
getRecordingLogger(messages),
|
||||
{},
|
||||
() => {
|
||||
throw new Error(rmSyncError);
|
||||
},
|
||||
),
|
||||
{
|
||||
instanceOf: ErrorConstructor,
|
||||
message: `${message(dbLocation)} Details: ${rmSyncError}`,
|
||||
},
|
||||
);
|
||||
const messages: LoggedMessage[] = [];
|
||||
t.throws(
|
||||
() =>
|
||||
cleanupDatabaseClusterDirectory(
|
||||
createTestConfig({ dbLocation }),
|
||||
getRecordingLogger(messages),
|
||||
{},
|
||||
() => {
|
||||
throw new Error(rmSyncError);
|
||||
},
|
||||
),
|
||||
{
|
||||
instanceOf: ErrorConstructor,
|
||||
message: `${message(dbLocation)} Details: ${rmSyncError}`,
|
||||
},
|
||||
);
|
||||
|
||||
t.is(messages.length, 1);
|
||||
t.is(messages[0].type, "warning");
|
||||
t.is(
|
||||
messages[0].message,
|
||||
`The database cluster directory ${dbLocation} must be empty. Attempting to clean it up.`,
|
||||
);
|
||||
});
|
||||
});
|
||||
t.is(messages.length, 1);
|
||||
t.is(messages[0].type, "warning");
|
||||
t.is(
|
||||
messages[0].message,
|
||||
`The database cluster directory ${dbLocation} must be empty. Attempting to clean it up.`,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
test("cleanupDatabaseClusterDirectory can disable warning with options", async (t) => {
|
||||
@@ -459,50 +462,62 @@ test("file coverage information enabled when debugMode is true", async (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("file coverage information enabled when not analyzing a pull request", async (t) => {
|
||||
sinon.stub(actionsUtil, "isAnalyzingPullRequest").returns(false);
|
||||
test.serial(
|
||||
"file coverage information enabled when not analyzing a pull request",
|
||||
async (t) => {
|
||||
sinon.stub(actionsUtil, "isAnalyzingPullRequest").returns(false);
|
||||
|
||||
t.true(
|
||||
await getFileCoverageInformationEnabled(
|
||||
false, // debugMode
|
||||
parseRepositoryNwo("github/codeql-action"),
|
||||
createFeatures([Feature.SkipFileCoverageOnPrs]),
|
||||
),
|
||||
);
|
||||
});
|
||||
t.true(
|
||||
await getFileCoverageInformationEnabled(
|
||||
false, // debugMode
|
||||
parseRepositoryNwo("github/codeql-action"),
|
||||
createFeatures([Feature.SkipFileCoverageOnPrs]),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("file coverage information enabled when owner is not 'github'", async (t) => {
|
||||
sinon.stub(actionsUtil, "isAnalyzingPullRequest").returns(true);
|
||||
test.serial(
|
||||
"file coverage information enabled when owner is not 'github'",
|
||||
async (t) => {
|
||||
sinon.stub(actionsUtil, "isAnalyzingPullRequest").returns(true);
|
||||
|
||||
t.true(
|
||||
await getFileCoverageInformationEnabled(
|
||||
false, // debugMode
|
||||
parseRepositoryNwo("other-org/some-repo"),
|
||||
createFeatures([Feature.SkipFileCoverageOnPrs]),
|
||||
),
|
||||
);
|
||||
});
|
||||
t.true(
|
||||
await getFileCoverageInformationEnabled(
|
||||
false, // debugMode
|
||||
parseRepositoryNwo("other-org/some-repo"),
|
||||
createFeatures([Feature.SkipFileCoverageOnPrs]),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("file coverage information enabled when feature flag is not enabled", async (t) => {
|
||||
sinon.stub(actionsUtil, "isAnalyzingPullRequest").returns(true);
|
||||
test.serial(
|
||||
"file coverage information enabled when feature flag is not enabled",
|
||||
async (t) => {
|
||||
sinon.stub(actionsUtil, "isAnalyzingPullRequest").returns(true);
|
||||
|
||||
t.true(
|
||||
await getFileCoverageInformationEnabled(
|
||||
false, // debugMode
|
||||
parseRepositoryNwo("github/codeql-action"),
|
||||
createFeatures([]),
|
||||
),
|
||||
);
|
||||
});
|
||||
t.true(
|
||||
await getFileCoverageInformationEnabled(
|
||||
false, // debugMode
|
||||
parseRepositoryNwo("github/codeql-action"),
|
||||
createFeatures([]),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("file coverage information disabled when all conditions for skipping are met", async (t) => {
|
||||
sinon.stub(actionsUtil, "isAnalyzingPullRequest").returns(true);
|
||||
test.serial(
|
||||
"file coverage information disabled when all conditions for skipping are met",
|
||||
async (t) => {
|
||||
sinon.stub(actionsUtil, "isAnalyzingPullRequest").returns(true);
|
||||
|
||||
t.false(
|
||||
await getFileCoverageInformationEnabled(
|
||||
false, // debugMode
|
||||
parseRepositoryNwo("github/codeql-action"),
|
||||
createFeatures([Feature.SkipFileCoverageOnPrs]),
|
||||
),
|
||||
);
|
||||
});
|
||||
t.false(
|
||||
await getFileCoverageInformationEnabled(
|
||||
false, // debugMode
|
||||
parseRepositoryNwo("github/codeql-action"),
|
||||
createFeatures([Feature.SkipFileCoverageOnPrs]),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -30,65 +30,68 @@ import {
|
||||
|
||||
setupTests(test);
|
||||
|
||||
test("writeOverlayChangesFile generates correct changes file", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const dbLocation = path.join(tmpDir, "db");
|
||||
await fs.promises.mkdir(dbLocation, { recursive: true });
|
||||
const sourceRoot = path.join(tmpDir, "src");
|
||||
await fs.promises.mkdir(sourceRoot, { recursive: true });
|
||||
const tempDir = path.join(tmpDir, "temp");
|
||||
await fs.promises.mkdir(tempDir, { recursive: true });
|
||||
test.serial(
|
||||
"writeOverlayChangesFile generates correct changes file",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const dbLocation = path.join(tmpDir, "db");
|
||||
await fs.promises.mkdir(dbLocation, { recursive: true });
|
||||
const sourceRoot = path.join(tmpDir, "src");
|
||||
await fs.promises.mkdir(sourceRoot, { recursive: true });
|
||||
const tempDir = path.join(tmpDir, "temp");
|
||||
await fs.promises.mkdir(tempDir, { recursive: true });
|
||||
|
||||
const logger = getRunnerLogger(true);
|
||||
const config = createTestConfig({ dbLocation });
|
||||
const logger = getRunnerLogger(true);
|
||||
const config = createTestConfig({ dbLocation });
|
||||
|
||||
// Mock the getFileOidsUnderPath function to return base OIDs
|
||||
const baseOids = {
|
||||
"unchanged.js": "aaa111",
|
||||
"modified.js": "bbb222",
|
||||
"deleted.js": "ccc333",
|
||||
};
|
||||
const getFileOidsStubForBase = sinon
|
||||
.stub(gitUtils, "getFileOidsUnderPath")
|
||||
.resolves(baseOids);
|
||||
// Mock the getFileOidsUnderPath function to return base OIDs
|
||||
const baseOids = {
|
||||
"unchanged.js": "aaa111",
|
||||
"modified.js": "bbb222",
|
||||
"deleted.js": "ccc333",
|
||||
};
|
||||
const getFileOidsStubForBase = sinon
|
||||
.stub(gitUtils, "getFileOidsUnderPath")
|
||||
.resolves(baseOids);
|
||||
|
||||
// Write the base database OIDs file
|
||||
await writeBaseDatabaseOidsFile(config, sourceRoot);
|
||||
getFileOidsStubForBase.restore();
|
||||
// Write the base database OIDs file
|
||||
await writeBaseDatabaseOidsFile(config, sourceRoot);
|
||||
getFileOidsStubForBase.restore();
|
||||
|
||||
// Mock the getFileOidsUnderPath function to return overlay OIDs
|
||||
const currentOids = {
|
||||
"unchanged.js": "aaa111",
|
||||
"modified.js": "ddd444", // Changed OID
|
||||
"added.js": "eee555", // New file
|
||||
};
|
||||
const getFileOidsStubForOverlay = sinon
|
||||
.stub(gitUtils, "getFileOidsUnderPath")
|
||||
.resolves(currentOids);
|
||||
// Mock the getFileOidsUnderPath function to return overlay OIDs
|
||||
const currentOids = {
|
||||
"unchanged.js": "aaa111",
|
||||
"modified.js": "ddd444", // Changed OID
|
||||
"added.js": "eee555", // New file
|
||||
};
|
||||
const getFileOidsStubForOverlay = sinon
|
||||
.stub(gitUtils, "getFileOidsUnderPath")
|
||||
.resolves(currentOids);
|
||||
|
||||
// Write the overlay changes file, which uses the mocked overlay OIDs
|
||||
// and the base database OIDs file
|
||||
const getTempDirStub = sinon
|
||||
.stub(actionsUtil, "getTemporaryDirectory")
|
||||
.returns(tempDir);
|
||||
const changesFilePath = await writeOverlayChangesFile(
|
||||
config,
|
||||
sourceRoot,
|
||||
logger,
|
||||
);
|
||||
getFileOidsStubForOverlay.restore();
|
||||
getTempDirStub.restore();
|
||||
// Write the overlay changes file, which uses the mocked overlay OIDs
|
||||
// and the base database OIDs file
|
||||
const getTempDirStub = sinon
|
||||
.stub(actionsUtil, "getTemporaryDirectory")
|
||||
.returns(tempDir);
|
||||
const changesFilePath = await writeOverlayChangesFile(
|
||||
config,
|
||||
sourceRoot,
|
||||
logger,
|
||||
);
|
||||
getFileOidsStubForOverlay.restore();
|
||||
getTempDirStub.restore();
|
||||
|
||||
const fileContent = await fs.promises.readFile(changesFilePath, "utf-8");
|
||||
const parsedContent = JSON.parse(fileContent) as { changes: string[] };
|
||||
const fileContent = await fs.promises.readFile(changesFilePath, "utf-8");
|
||||
const parsedContent = JSON.parse(fileContent) as { changes: string[] };
|
||||
|
||||
t.deepEqual(
|
||||
parsedContent.changes.sort(),
|
||||
["added.js", "deleted.js", "modified.js"],
|
||||
"Should identify added, deleted, and modified files",
|
||||
);
|
||||
});
|
||||
});
|
||||
t.deepEqual(
|
||||
parsedContent.changes.sort(),
|
||||
["added.js", "deleted.js", "modified.js"],
|
||||
"Should identify added, deleted, and modified files",
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
interface DownloadOverlayBaseDatabaseTestCase {
|
||||
overlayDatabaseMode: OverlayDatabaseMode;
|
||||
@@ -303,7 +306,7 @@ test(
|
||||
false,
|
||||
);
|
||||
|
||||
test("overlay-base database cache keys remain stable", async (t) => {
|
||||
test.serial("overlay-base database cache keys remain stable", async (t) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const config = createTestConfig({ languages: ["python", "javascript"] });
|
||||
const codeQlVersion = "2.23.0";
|
||||
|
||||
@@ -72,101 +72,110 @@ test("getCacheKey rounds disk space down to nearest 10 GiB", async (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("shouldSkipOverlayAnalysis returns false when no cached status exists", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
process.env["RUNNER_TEMP"] = tmpDir;
|
||||
const codeql = mockCodeQLVersion("2.20.0");
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
test.serial(
|
||||
"shouldSkipOverlayAnalysis returns false when no cached status exists",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
process.env["RUNNER_TEMP"] = tmpDir;
|
||||
const codeql = mockCodeQLVersion("2.20.0");
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
|
||||
sinon.stub(actionsCache, "restoreCache").resolves(undefined);
|
||||
sinon.stub(actionsCache, "restoreCache").resolves(undefined);
|
||||
|
||||
const result = await shouldSkipOverlayAnalysis(
|
||||
codeql,
|
||||
["javascript"],
|
||||
makeDiskUsage(50),
|
||||
logger,
|
||||
);
|
||||
const result = await shouldSkipOverlayAnalysis(
|
||||
codeql,
|
||||
["javascript"],
|
||||
makeDiskUsage(50),
|
||||
logger,
|
||||
);
|
||||
|
||||
t.false(result);
|
||||
t.true(
|
||||
messages.some(
|
||||
(m) =>
|
||||
m.type === "debug" &&
|
||||
typeof m.message === "string" &&
|
||||
m.message.includes("No overlay status found in Actions cache."),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("shouldSkipOverlayAnalysis returns true when cached status indicates failed build", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
process.env["RUNNER_TEMP"] = tmpDir;
|
||||
const codeql = mockCodeQLVersion("2.20.0");
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
|
||||
const status = {
|
||||
attemptedToBuildOverlayBaseDatabase: true,
|
||||
builtOverlayBaseDatabase: false,
|
||||
};
|
||||
|
||||
// Stub restoreCache to write the status file and return a key
|
||||
sinon.stub(actionsCache, "restoreCache").callsFake(async (paths) => {
|
||||
const statusFile = paths[0];
|
||||
await fs.promises.mkdir(path.dirname(statusFile), { recursive: true });
|
||||
await fs.promises.writeFile(statusFile, JSON.stringify(status));
|
||||
return "found-key";
|
||||
t.false(result);
|
||||
t.true(
|
||||
messages.some(
|
||||
(m) =>
|
||||
m.type === "debug" &&
|
||||
typeof m.message === "string" &&
|
||||
m.message.includes("No overlay status found in Actions cache."),
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const result = await shouldSkipOverlayAnalysis(
|
||||
codeql,
|
||||
["javascript"],
|
||||
makeDiskUsage(50),
|
||||
logger,
|
||||
);
|
||||
test.serial(
|
||||
"shouldSkipOverlayAnalysis returns true when cached status indicates failed build",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
process.env["RUNNER_TEMP"] = tmpDir;
|
||||
const codeql = mockCodeQLVersion("2.20.0");
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
|
||||
t.true(result);
|
||||
});
|
||||
});
|
||||
const status = {
|
||||
attemptedToBuildOverlayBaseDatabase: true,
|
||||
builtOverlayBaseDatabase: false,
|
||||
};
|
||||
|
||||
test("shouldSkipOverlayAnalysis returns false when cached status indicates successful build", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
process.env["RUNNER_TEMP"] = tmpDir;
|
||||
const codeql = mockCodeQLVersion("2.20.0");
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
// Stub restoreCache to write the status file and return a key
|
||||
sinon.stub(actionsCache, "restoreCache").callsFake(async (paths) => {
|
||||
const statusFile = paths[0];
|
||||
await fs.promises.mkdir(path.dirname(statusFile), { recursive: true });
|
||||
await fs.promises.writeFile(statusFile, JSON.stringify(status));
|
||||
return "found-key";
|
||||
});
|
||||
|
||||
const status = {
|
||||
attemptedToBuildOverlayBaseDatabase: true,
|
||||
builtOverlayBaseDatabase: true,
|
||||
};
|
||||
const result = await shouldSkipOverlayAnalysis(
|
||||
codeql,
|
||||
["javascript"],
|
||||
makeDiskUsage(50),
|
||||
logger,
|
||||
);
|
||||
|
||||
sinon.stub(actionsCache, "restoreCache").callsFake(async (paths) => {
|
||||
const statusFile = paths[0];
|
||||
await fs.promises.mkdir(path.dirname(statusFile), { recursive: true });
|
||||
await fs.promises.writeFile(statusFile, JSON.stringify(status));
|
||||
return "found-key";
|
||||
t.true(result);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const result = await shouldSkipOverlayAnalysis(
|
||||
codeql,
|
||||
["javascript"],
|
||||
makeDiskUsage(50),
|
||||
logger,
|
||||
);
|
||||
test.serial(
|
||||
"shouldSkipOverlayAnalysis returns false when cached status indicates successful build",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
process.env["RUNNER_TEMP"] = tmpDir;
|
||||
const codeql = mockCodeQLVersion("2.20.0");
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
|
||||
t.false(result);
|
||||
t.true(
|
||||
messages.some(
|
||||
(m) =>
|
||||
m.type === "debug" &&
|
||||
typeof m.message === "string" &&
|
||||
m.message.includes(
|
||||
"Cached overlay status does not indicate a previous unsuccessful attempt",
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
const status = {
|
||||
attemptedToBuildOverlayBaseDatabase: true,
|
||||
builtOverlayBaseDatabase: true,
|
||||
};
|
||||
|
||||
sinon.stub(actionsCache, "restoreCache").callsFake(async (paths) => {
|
||||
const statusFile = paths[0];
|
||||
await fs.promises.mkdir(path.dirname(statusFile), { recursive: true });
|
||||
await fs.promises.writeFile(statusFile, JSON.stringify(status));
|
||||
return "found-key";
|
||||
});
|
||||
|
||||
const result = await shouldSkipOverlayAnalysis(
|
||||
codeql,
|
||||
["javascript"],
|
||||
makeDiskUsage(50),
|
||||
logger,
|
||||
);
|
||||
|
||||
t.false(result);
|
||||
t.true(
|
||||
messages.some(
|
||||
(m) =>
|
||||
m.type === "debug" &&
|
||||
typeof m.message === "string" &&
|
||||
m.message.includes(
|
||||
"Cached overlay status does not indicate a previous unsuccessful attempt",
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -45,7 +45,7 @@ test.beforeEach(() => {
|
||||
initializeEnvironment("1.2.3");
|
||||
});
|
||||
|
||||
test("parse codeql bundle url version", (t) => {
|
||||
test.serial("parse codeql bundle url version", (t) => {
|
||||
t.deepEqual(
|
||||
setupCodeql.getCodeQLURLVersion(
|
||||
"https://github.com/.../codeql-bundle-20200601/...",
|
||||
@@ -54,7 +54,7 @@ test("parse codeql bundle url version", (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("convert to semver", (t) => {
|
||||
test.serial("convert to semver", (t) => {
|
||||
const tests = {
|
||||
"20200601": "0.0.0-20200601",
|
||||
"20200601.0": "0.0.0-20200601.0",
|
||||
@@ -77,7 +77,7 @@ test("convert to semver", (t) => {
|
||||
}
|
||||
});
|
||||
|
||||
test("getCodeQLActionRepository", (t) => {
|
||||
test.serial("getCodeQLActionRepository", (t) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
|
||||
initializeEnvironment("1.2.3");
|
||||
@@ -95,361 +95,385 @@ test("getCodeQLActionRepository", (t) => {
|
||||
t.deepEqual(repoEnv, "xxx/yyy");
|
||||
});
|
||||
|
||||
test("getCodeQLSource sets CLI version for a semver tagged bundle", async (t) => {
|
||||
const features = createFeatures([]);
|
||||
test.serial(
|
||||
"getCodeQLSource sets CLI version for a semver tagged bundle",
|
||||
async (t) => {
|
||||
const features = createFeatures([]);
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const tagName = "codeql-bundle-v1.2.3";
|
||||
mockBundleDownloadApi({ tagName });
|
||||
const source = await setupCodeql.getCodeQLSource(
|
||||
`https://github.com/github/codeql-action/releases/download/${tagName}/codeql-bundle-linux64.tar.gz`,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
GitHubVariant.DOTCOM,
|
||||
false,
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
);
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const tagName = "codeql-bundle-v1.2.3";
|
||||
mockBundleDownloadApi({ tagName });
|
||||
const source = await setupCodeql.getCodeQLSource(
|
||||
`https://github.com/github/codeql-action/releases/download/${tagName}/codeql-bundle-linux64.tar.gz`,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
GitHubVariant.DOTCOM,
|
||||
false,
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
);
|
||||
|
||||
t.is(source.sourceType, "download");
|
||||
t.is(source["cliVersion"], "1.2.3");
|
||||
});
|
||||
});
|
||||
t.is(source.sourceType, "download");
|
||||
t.is(source["cliVersion"], "1.2.3");
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("getCodeQLSource correctly returns bundled CLI version when tools == linked", async (t) => {
|
||||
const features = createFeatures([]);
|
||||
test.serial(
|
||||
"getCodeQLSource correctly returns bundled CLI version when tools == linked",
|
||||
async (t) => {
|
||||
const features = createFeatures([]);
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const source = await setupCodeql.getCodeQLSource(
|
||||
"linked",
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
GitHubVariant.DOTCOM,
|
||||
false,
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
);
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const source = await setupCodeql.getCodeQLSource(
|
||||
"linked",
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
GitHubVariant.DOTCOM,
|
||||
false,
|
||||
features,
|
||||
getRunnerLogger(true),
|
||||
);
|
||||
|
||||
t.is(source.toolsVersion, LINKED_CLI_VERSION.cliVersion);
|
||||
t.is(source.sourceType, "download");
|
||||
});
|
||||
});
|
||||
t.is(source.toolsVersion, LINKED_CLI_VERSION.cliVersion);
|
||||
t.is(source.sourceType, "download");
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("getCodeQLSource correctly returns bundled CLI version when tools == latest", async (t) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(loggedMessages);
|
||||
const features = createFeatures([]);
|
||||
test.serial(
|
||||
"getCodeQLSource correctly returns bundled CLI version when tools == latest",
|
||||
async (t) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(loggedMessages);
|
||||
const features = createFeatures([]);
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const source = await setupCodeql.getCodeQLSource(
|
||||
"latest",
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
GitHubVariant.DOTCOM,
|
||||
false,
|
||||
features,
|
||||
logger,
|
||||
);
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const source = await setupCodeql.getCodeQLSource(
|
||||
"latest",
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
GitHubVariant.DOTCOM,
|
||||
false,
|
||||
features,
|
||||
logger,
|
||||
);
|
||||
|
||||
// First, ensure that the CLI version is the linked version, so that backwards
|
||||
// compatibility is maintained.
|
||||
t.is(source.toolsVersion, LINKED_CLI_VERSION.cliVersion);
|
||||
t.is(source.sourceType, "download");
|
||||
// First, ensure that the CLI version is the linked version, so that backwards
|
||||
// compatibility is maintained.
|
||||
t.is(source.toolsVersion, LINKED_CLI_VERSION.cliVersion);
|
||||
t.is(source.sourceType, "download");
|
||||
|
||||
// Afterwards, ensure that we see the deprecation message in the log.
|
||||
const expected_message: string =
|
||||
"`tools: latest` has been renamed to `tools: linked`, but the old name is still supported. No action is required.";
|
||||
t.assert(
|
||||
loggedMessages.some(
|
||||
(msg) =>
|
||||
typeof msg.message === "string" &&
|
||||
msg.message.includes(expected_message),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("setupCodeQLBundle logs the CodeQL CLI version being used when asked to use linked tools", async (t) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(loggedMessages);
|
||||
const features = createFeatures([]);
|
||||
|
||||
// Stub the downloadCodeQL function to prevent downloading artefacts
|
||||
// during testing from being called.
|
||||
sinon.stub(setupCodeql, "downloadCodeQL").resolves({
|
||||
codeqlFolder: "codeql",
|
||||
statusReport: {
|
||||
combinedDurationMs: 500,
|
||||
compressionMethod: "gzip",
|
||||
downloadDurationMs: 200,
|
||||
extractionDurationMs: 300,
|
||||
streamExtraction: false,
|
||||
toolsUrl: "toolsUrl",
|
||||
},
|
||||
toolsVersion: LINKED_CLI_VERSION.cliVersion,
|
||||
});
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const result = await setupCodeql.setupCodeQLBundle(
|
||||
"linked",
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
"tmp/codeql_action_test/",
|
||||
GitHubVariant.DOTCOM,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
features,
|
||||
logger,
|
||||
);
|
||||
|
||||
// Basic sanity check that the version we got back is indeed
|
||||
// the linked (default) CLI version.
|
||||
t.is(result.toolsVersion, LINKED_CLI_VERSION.cliVersion);
|
||||
|
||||
// Ensure message logging CodeQL CLI version was present in user logs.
|
||||
const expected_message: string = `Using CodeQL CLI version ${LINKED_CLI_VERSION.cliVersion}`;
|
||||
t.assert(
|
||||
loggedMessages.some(
|
||||
(msg) =>
|
||||
typeof msg.message === "string" &&
|
||||
msg.message.includes(expected_message),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("setupCodeQLBundle logs the CodeQL CLI version being used when asked to download a non-default bundle", async (t) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(loggedMessages);
|
||||
const features = createFeatures([]);
|
||||
|
||||
const bundleUrl =
|
||||
"https://github.com/github/codeql-action/releases/download/codeql-bundle-v2.16.0/codeql-bundle-linux64.tar.gz";
|
||||
const expectedVersion = "2.16.0";
|
||||
|
||||
// Stub the downloadCodeQL function to prevent downloading artefacts
|
||||
// during testing from being called.
|
||||
sinon.stub(setupCodeql, "downloadCodeQL").resolves({
|
||||
codeqlFolder: "codeql",
|
||||
statusReport: {
|
||||
combinedDurationMs: 500,
|
||||
compressionMethod: "gzip",
|
||||
downloadDurationMs: 200,
|
||||
extractionDurationMs: 300,
|
||||
streamExtraction: false,
|
||||
toolsUrl: bundleUrl,
|
||||
},
|
||||
toolsVersion: expectedVersion,
|
||||
});
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const result = await setupCodeql.setupCodeQLBundle(
|
||||
bundleUrl,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
"tmp/codeql_action_test/",
|
||||
GitHubVariant.DOTCOM,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
features,
|
||||
logger,
|
||||
);
|
||||
|
||||
// Basic sanity check that the version we got back is indeed the version that the
|
||||
// bundle contains..
|
||||
t.is(result.toolsVersion, expectedVersion);
|
||||
|
||||
// Ensure message logging CodeQL CLI version was present in user logs.
|
||||
const expected_message: string = `Using CodeQL CLI version 2.16.0 sourced from ${bundleUrl} .`;
|
||||
t.assert(
|
||||
loggedMessages.some(
|
||||
(msg) =>
|
||||
typeof msg.message === "string" &&
|
||||
msg.message.includes(expected_message),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("getCodeQLSource correctly returns nightly CLI version when tools == nightly", async (t) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(loggedMessages);
|
||||
const features = createFeatures([]);
|
||||
|
||||
const expectedDate = "30260213";
|
||||
const expectedTag = `codeql-bundle-${expectedDate}`;
|
||||
|
||||
// Ensure that we consistently select "zstd" for the test.
|
||||
sinon.stub(process, "platform").value("linux");
|
||||
sinon.stub(tar, "isZstdAvailable").resolves({
|
||||
available: true,
|
||||
foundZstdBinary: true,
|
||||
});
|
||||
|
||||
const client = github.getOctokit("123");
|
||||
const listReleases = sinon.stub(client.rest.repos, "listReleases");
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
listReleases.resolves({
|
||||
data: [{ tag_name: expectedTag }],
|
||||
} as any);
|
||||
sinon.stub(api, "getApiClient").value(() => client);
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const source = await setupCodeql.getCodeQLSource(
|
||||
"nightly",
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
GitHubVariant.DOTCOM,
|
||||
false,
|
||||
features,
|
||||
logger,
|
||||
);
|
||||
|
||||
// Check that the `CodeQLToolsSource` object matches our expectations.
|
||||
const expectedVersion = `0.0.0-${expectedDate}`;
|
||||
const expectedURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}/${setupCodeql.getCodeQLBundleName("zstd")}`;
|
||||
t.deepEqual(source, {
|
||||
bundleVersion: expectedDate,
|
||||
cliVersion: undefined,
|
||||
codeqlURL: expectedURL,
|
||||
compressionMethod: "zstd",
|
||||
sourceType: "download",
|
||||
toolsVersion: expectedVersion,
|
||||
} satisfies setupCodeql.CodeQLToolsSource);
|
||||
|
||||
// Afterwards, ensure that we see the expected messages in the log.
|
||||
checkExpectedLogMessages(t, loggedMessages, [
|
||||
"Using the latest CodeQL CLI nightly, as requested by 'tools: nightly'.",
|
||||
`Bundle version ${expectedDate} is not in SemVer format. Will treat it as pre-release ${expectedVersion}.`,
|
||||
`Attempting to obtain CodeQL tools. CLI version: unknown, bundle tag name: ${expectedTag}`,
|
||||
`Using CodeQL CLI sourced from ${expectedURL}`,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
test("getCodeQLSource correctly returns nightly CLI version when forced by FF", async (t) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(loggedMessages);
|
||||
const features = createFeatures([Feature.ForceNightly]);
|
||||
|
||||
const expectedDate = "30260213";
|
||||
const expectedTag = `codeql-bundle-${expectedDate}`;
|
||||
|
||||
// Ensure that we consistently select "zstd" for the test.
|
||||
sinon.stub(process, "platform").value("linux");
|
||||
sinon.stub(tar, "isZstdAvailable").resolves({
|
||||
available: true,
|
||||
foundZstdBinary: true,
|
||||
});
|
||||
|
||||
const client = github.getOctokit("123");
|
||||
const listReleases = sinon.stub(client.rest.repos, "listReleases");
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
listReleases.resolves({
|
||||
data: [{ tag_name: expectedTag }],
|
||||
} as any);
|
||||
sinon.stub(api, "getApiClient").value(() => client);
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
process.env["GITHUB_EVENT_NAME"] = "dynamic";
|
||||
|
||||
const source = await setupCodeql.getCodeQLSource(
|
||||
undefined,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
GitHubVariant.DOTCOM,
|
||||
false,
|
||||
features,
|
||||
logger,
|
||||
);
|
||||
|
||||
// Check that the `CodeQLToolsSource` object matches our expectations.
|
||||
const expectedVersion = `0.0.0-${expectedDate}`;
|
||||
const expectedURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}/${setupCodeql.getCodeQLBundleName("zstd")}`;
|
||||
t.deepEqual(source, {
|
||||
bundleVersion: expectedDate,
|
||||
cliVersion: undefined,
|
||||
codeqlURL: expectedURL,
|
||||
compressionMethod: "zstd",
|
||||
sourceType: "download",
|
||||
toolsVersion: expectedVersion,
|
||||
} satisfies setupCodeql.CodeQLToolsSource);
|
||||
|
||||
// Afterwards, ensure that we see the expected messages in the log.
|
||||
checkExpectedLogMessages(t, loggedMessages, [
|
||||
`Using the latest CodeQL CLI nightly, as forced by the ${Feature.ForceNightly} feature flag.`,
|
||||
`Bundle version ${expectedDate} is not in SemVer format. Will treat it as pre-release ${expectedVersion}.`,
|
||||
`Attempting to obtain CodeQL tools. CLI version: unknown, bundle tag name: ${expectedTag}`,
|
||||
`Using CodeQL CLI sourced from ${expectedURL}`,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
test("getCodeQLSource correctly returns latest version from toolcache when tools == toolcache", async (t) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(loggedMessages);
|
||||
const features = createFeatures([Feature.AllowToolcacheInput]);
|
||||
|
||||
const latestToolcacheVersion = "3.2.1";
|
||||
const latestVersionPath = "/path/to/latest";
|
||||
const testVersions = ["2.3.1", latestToolcacheVersion, "1.2.3"];
|
||||
const findAllVersionsStub = sinon
|
||||
.stub(toolcache, "findAllVersions")
|
||||
.returns(testVersions);
|
||||
const findStub = sinon.stub(toolcache, "find");
|
||||
findStub
|
||||
.withArgs("CodeQL", latestToolcacheVersion)
|
||||
.returns(latestVersionPath);
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
process.env["GITHUB_EVENT_NAME"] = "dynamic";
|
||||
|
||||
const source = await setupCodeql.getCodeQLSource(
|
||||
"toolcache",
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
GitHubVariant.DOTCOM,
|
||||
false,
|
||||
features,
|
||||
logger,
|
||||
);
|
||||
|
||||
// Check that the toolcache functions were called with the expected arguments
|
||||
t.assert(
|
||||
findAllVersionsStub.calledOnceWith("CodeQL"),
|
||||
`toolcache.findAllVersions("CodeQL") wasn't called`,
|
||||
);
|
||||
t.assert(
|
||||
findStub.calledOnceWith("CodeQL", latestToolcacheVersion),
|
||||
`toolcache.find("CodeQL", ${latestToolcacheVersion}) wasn't called`,
|
||||
);
|
||||
|
||||
// Check that `sourceType` and `toolsVersion` match expectations.
|
||||
t.is(source.sourceType, "toolcache");
|
||||
t.is(source.toolsVersion, latestToolcacheVersion);
|
||||
|
||||
// Check that key messages we would expect to find in the log are present.
|
||||
const expectedMessages: string[] = [
|
||||
`Attempting to use the latest CodeQL CLI version in the toolcache, as requested by 'tools: toolcache'.`,
|
||||
`CLI version ${latestToolcacheVersion} is the latest version in the toolcache.`,
|
||||
`Using CodeQL CLI version ${latestToolcacheVersion} from toolcache at ${latestVersionPath}`,
|
||||
];
|
||||
for (const expectedMessage of expectedMessages) {
|
||||
// Afterwards, ensure that we see the deprecation message in the log.
|
||||
const expected_message: string =
|
||||
"`tools: latest` has been renamed to `tools: linked`, but the old name is still supported. No action is required.";
|
||||
t.assert(
|
||||
loggedMessages.some(
|
||||
(msg) =>
|
||||
typeof msg.message === "string" &&
|
||||
msg.message.includes(expectedMessage),
|
||||
msg.message.includes(expected_message),
|
||||
),
|
||||
`Expected '${expectedMessage}' in the logger output, but didn't find it in:\n ${loggedMessages.map((m) => ` - '${m.message}'`).join("\n")}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"setupCodeQLBundle logs the CodeQL CLI version being used when asked to use linked tools",
|
||||
async (t) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(loggedMessages);
|
||||
const features = createFeatures([]);
|
||||
|
||||
// Stub the downloadCodeQL function to prevent downloading artefacts
|
||||
// during testing from being called.
|
||||
sinon.stub(setupCodeql, "downloadCodeQL").resolves({
|
||||
codeqlFolder: "codeql",
|
||||
statusReport: {
|
||||
combinedDurationMs: 500,
|
||||
compressionMethod: "gzip",
|
||||
downloadDurationMs: 200,
|
||||
extractionDurationMs: 300,
|
||||
streamExtraction: false,
|
||||
toolsUrl: "toolsUrl",
|
||||
},
|
||||
toolsVersion: LINKED_CLI_VERSION.cliVersion,
|
||||
});
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const result = await setupCodeql.setupCodeQLBundle(
|
||||
"linked",
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
"tmp/codeql_action_test/",
|
||||
GitHubVariant.DOTCOM,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
features,
|
||||
logger,
|
||||
);
|
||||
|
||||
// Basic sanity check that the version we got back is indeed
|
||||
// the linked (default) CLI version.
|
||||
t.is(result.toolsVersion, LINKED_CLI_VERSION.cliVersion);
|
||||
|
||||
// Ensure message logging CodeQL CLI version was present in user logs.
|
||||
const expected_message: string = `Using CodeQL CLI version ${LINKED_CLI_VERSION.cliVersion}`;
|
||||
t.assert(
|
||||
loggedMessages.some(
|
||||
(msg) =>
|
||||
typeof msg.message === "string" &&
|
||||
msg.message.includes(expected_message),
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"setupCodeQLBundle logs the CodeQL CLI version being used when asked to download a non-default bundle",
|
||||
async (t) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(loggedMessages);
|
||||
const features = createFeatures([]);
|
||||
|
||||
const bundleUrl =
|
||||
"https://github.com/github/codeql-action/releases/download/codeql-bundle-v2.16.0/codeql-bundle-linux64.tar.gz";
|
||||
const expectedVersion = "2.16.0";
|
||||
|
||||
// Stub the downloadCodeQL function to prevent downloading artefacts
|
||||
// during testing from being called.
|
||||
sinon.stub(setupCodeql, "downloadCodeQL").resolves({
|
||||
codeqlFolder: "codeql",
|
||||
statusReport: {
|
||||
combinedDurationMs: 500,
|
||||
compressionMethod: "gzip",
|
||||
downloadDurationMs: 200,
|
||||
extractionDurationMs: 300,
|
||||
streamExtraction: false,
|
||||
toolsUrl: bundleUrl,
|
||||
},
|
||||
toolsVersion: expectedVersion,
|
||||
});
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const result = await setupCodeql.setupCodeQLBundle(
|
||||
bundleUrl,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
"tmp/codeql_action_test/",
|
||||
GitHubVariant.DOTCOM,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
features,
|
||||
logger,
|
||||
);
|
||||
|
||||
// Basic sanity check that the version we got back is indeed the version that the
|
||||
// bundle contains..
|
||||
t.is(result.toolsVersion, expectedVersion);
|
||||
|
||||
// Ensure message logging CodeQL CLI version was present in user logs.
|
||||
const expected_message: string = `Using CodeQL CLI version 2.16.0 sourced from ${bundleUrl} .`;
|
||||
t.assert(
|
||||
loggedMessages.some(
|
||||
(msg) =>
|
||||
typeof msg.message === "string" &&
|
||||
msg.message.includes(expected_message),
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"getCodeQLSource correctly returns nightly CLI version when tools == nightly",
|
||||
async (t) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(loggedMessages);
|
||||
const features = createFeatures([]);
|
||||
|
||||
const expectedDate = "30260213";
|
||||
const expectedTag = `codeql-bundle-${expectedDate}`;
|
||||
|
||||
// Ensure that we consistently select "zstd" for the test.
|
||||
sinon.stub(process, "platform").value("linux");
|
||||
sinon.stub(tar, "isZstdAvailable").resolves({
|
||||
available: true,
|
||||
foundZstdBinary: true,
|
||||
});
|
||||
|
||||
const client = github.getOctokit("123");
|
||||
const listReleases = sinon.stub(client.rest.repos, "listReleases");
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
listReleases.resolves({
|
||||
data: [{ tag_name: expectedTag }],
|
||||
} as any);
|
||||
sinon.stub(api, "getApiClient").value(() => client);
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const source = await setupCodeql.getCodeQLSource(
|
||||
"nightly",
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
GitHubVariant.DOTCOM,
|
||||
false,
|
||||
features,
|
||||
logger,
|
||||
);
|
||||
|
||||
// Check that the `CodeQLToolsSource` object matches our expectations.
|
||||
const expectedVersion = `0.0.0-${expectedDate}`;
|
||||
const expectedURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}/${setupCodeql.getCodeQLBundleName("zstd")}`;
|
||||
t.deepEqual(source, {
|
||||
bundleVersion: expectedDate,
|
||||
cliVersion: undefined,
|
||||
codeqlURL: expectedURL,
|
||||
compressionMethod: "zstd",
|
||||
sourceType: "download",
|
||||
toolsVersion: expectedVersion,
|
||||
} satisfies setupCodeql.CodeQLToolsSource);
|
||||
|
||||
// Afterwards, ensure that we see the expected messages in the log.
|
||||
checkExpectedLogMessages(t, loggedMessages, [
|
||||
"Using the latest CodeQL CLI nightly, as requested by 'tools: nightly'.",
|
||||
`Bundle version ${expectedDate} is not in SemVer format. Will treat it as pre-release ${expectedVersion}.`,
|
||||
`Attempting to obtain CodeQL tools. CLI version: unknown, bundle tag name: ${expectedTag}`,
|
||||
`Using CodeQL CLI sourced from ${expectedURL}`,
|
||||
]);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"getCodeQLSource correctly returns nightly CLI version when forced by FF",
|
||||
async (t) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(loggedMessages);
|
||||
const features = createFeatures([Feature.ForceNightly]);
|
||||
|
||||
const expectedDate = "30260213";
|
||||
const expectedTag = `codeql-bundle-${expectedDate}`;
|
||||
|
||||
// Ensure that we consistently select "zstd" for the test.
|
||||
sinon.stub(process, "platform").value("linux");
|
||||
sinon.stub(tar, "isZstdAvailable").resolves({
|
||||
available: true,
|
||||
foundZstdBinary: true,
|
||||
});
|
||||
|
||||
const client = github.getOctokit("123");
|
||||
const listReleases = sinon.stub(client.rest.repos, "listReleases");
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
listReleases.resolves({
|
||||
data: [{ tag_name: expectedTag }],
|
||||
} as any);
|
||||
sinon.stub(api, "getApiClient").value(() => client);
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
process.env["GITHUB_EVENT_NAME"] = "dynamic";
|
||||
|
||||
const source = await setupCodeql.getCodeQLSource(
|
||||
undefined,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
GitHubVariant.DOTCOM,
|
||||
false,
|
||||
features,
|
||||
logger,
|
||||
);
|
||||
|
||||
// Check that the `CodeQLToolsSource` object matches our expectations.
|
||||
const expectedVersion = `0.0.0-${expectedDate}`;
|
||||
const expectedURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}/${setupCodeql.getCodeQLBundleName("zstd")}`;
|
||||
t.deepEqual(source, {
|
||||
bundleVersion: expectedDate,
|
||||
cliVersion: undefined,
|
||||
codeqlURL: expectedURL,
|
||||
compressionMethod: "zstd",
|
||||
sourceType: "download",
|
||||
toolsVersion: expectedVersion,
|
||||
} satisfies setupCodeql.CodeQLToolsSource);
|
||||
|
||||
// Afterwards, ensure that we see the expected messages in the log.
|
||||
checkExpectedLogMessages(t, loggedMessages, [
|
||||
`Using the latest CodeQL CLI nightly, as forced by the ${Feature.ForceNightly} feature flag.`,
|
||||
`Bundle version ${expectedDate} is not in SemVer format. Will treat it as pre-release ${expectedVersion}.`,
|
||||
`Attempting to obtain CodeQL tools. CLI version: unknown, bundle tag name: ${expectedTag}`,
|
||||
`Using CodeQL CLI sourced from ${expectedURL}`,
|
||||
]);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"getCodeQLSource correctly returns latest version from toolcache when tools == toolcache",
|
||||
async (t) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(loggedMessages);
|
||||
const features = createFeatures([Feature.AllowToolcacheInput]);
|
||||
|
||||
const latestToolcacheVersion = "3.2.1";
|
||||
const latestVersionPath = "/path/to/latest";
|
||||
const testVersions = ["2.3.1", latestToolcacheVersion, "1.2.3"];
|
||||
const findAllVersionsStub = sinon
|
||||
.stub(toolcache, "findAllVersions")
|
||||
.returns(testVersions);
|
||||
const findStub = sinon.stub(toolcache, "find");
|
||||
findStub
|
||||
.withArgs("CodeQL", latestToolcacheVersion)
|
||||
.returns(latestVersionPath);
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
process.env["GITHUB_EVENT_NAME"] = "dynamic";
|
||||
|
||||
const source = await setupCodeql.getCodeQLSource(
|
||||
"toolcache",
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
GitHubVariant.DOTCOM,
|
||||
false,
|
||||
features,
|
||||
logger,
|
||||
);
|
||||
|
||||
// Check that the toolcache functions were called with the expected arguments
|
||||
t.assert(
|
||||
findAllVersionsStub.calledOnceWith("CodeQL"),
|
||||
`toolcache.findAllVersions("CodeQL") wasn't called`,
|
||||
);
|
||||
t.assert(
|
||||
findStub.calledOnceWith("CodeQL", latestToolcacheVersion),
|
||||
`toolcache.find("CodeQL", ${latestToolcacheVersion}) wasn't called`,
|
||||
);
|
||||
|
||||
// Check that `sourceType` and `toolsVersion` match expectations.
|
||||
t.is(source.sourceType, "toolcache");
|
||||
t.is(source.toolsVersion, latestToolcacheVersion);
|
||||
|
||||
// Check that key messages we would expect to find in the log are present.
|
||||
const expectedMessages: string[] = [
|
||||
`Attempting to use the latest CodeQL CLI version in the toolcache, as requested by 'tools: toolcache'.`,
|
||||
`CLI version ${latestToolcacheVersion} is the latest version in the toolcache.`,
|
||||
`Using CodeQL CLI version ${latestToolcacheVersion} from toolcache at ${latestVersionPath}`,
|
||||
];
|
||||
for (const expectedMessage of expectedMessages) {
|
||||
t.assert(
|
||||
loggedMessages.some(
|
||||
(msg) =>
|
||||
typeof msg.message === "string" &&
|
||||
msg.message.includes(expectedMessage),
|
||||
),
|
||||
`Expected '${expectedMessage}' in the logger output, but didn't find it in:\n ${loggedMessages.map((m) => ` - '${m.message}'`).join("\n")}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const toolcacheInputFallbackMacro = test.macro({
|
||||
exec: async (
|
||||
@@ -511,7 +535,7 @@ const toolcacheInputFallbackMacro = test.macro({
|
||||
`getCodeQLSource falls back to downloading the CLI if ${providedTitle}`,
|
||||
});
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
"the toolcache doesn't have a CodeQL CLI when tools == toolcache",
|
||||
toolcacheInputFallbackMacro,
|
||||
[Feature.AllowToolcacheInput],
|
||||
@@ -523,7 +547,7 @@ test(
|
||||
],
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
"the workflow trigger is not `dynamic`",
|
||||
toolcacheInputFallbackMacro,
|
||||
[Feature.AllowToolcacheInput],
|
||||
@@ -534,7 +558,7 @@ test(
|
||||
],
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
"the feature flag is not enabled",
|
||||
toolcacheInputFallbackMacro,
|
||||
[],
|
||||
@@ -543,24 +567,36 @@ test(
|
||||
[`Ignoring 'tools: toolcache' because the feature is not enabled.`],
|
||||
);
|
||||
|
||||
test('tryGetTagNameFromUrl extracts the right tag name for a repo name containing "codeql-bundle"', (t) => {
|
||||
t.is(
|
||||
setupCodeql.tryGetTagNameFromUrl(
|
||||
"https://github.com/org/codeql-bundle-testing/releases/download/codeql-bundle-v2.19.0/codeql-bundle-linux64.tar.zst",
|
||||
getRunnerLogger(true),
|
||||
),
|
||||
"codeql-bundle-v2.19.0",
|
||||
);
|
||||
});
|
||||
test.serial(
|
||||
'tryGetTagNameFromUrl extracts the right tag name for a repo name containing "codeql-bundle"',
|
||||
(t) => {
|
||||
t.is(
|
||||
setupCodeql.tryGetTagNameFromUrl(
|
||||
"https://github.com/org/codeql-bundle-testing/releases/download/codeql-bundle-v2.19.0/codeql-bundle-linux64.tar.zst",
|
||||
getRunnerLogger(true),
|
||||
),
|
||||
"codeql-bundle-v2.19.0",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("getLatestToolcacheVersion returns undefined if there are no CodeQL CLIs in the toolcache", (t) => {
|
||||
sinon.stub(toolcache, "findAllVersions").returns([]);
|
||||
t.is(setupCodeql.getLatestToolcacheVersion(getRunnerLogger(true)), undefined);
|
||||
});
|
||||
test.serial(
|
||||
"getLatestToolcacheVersion returns undefined if there are no CodeQL CLIs in the toolcache",
|
||||
(t) => {
|
||||
sinon.stub(toolcache, "findAllVersions").returns([]);
|
||||
t.is(
|
||||
setupCodeql.getLatestToolcacheVersion(getRunnerLogger(true)),
|
||||
undefined,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("getLatestToolcacheVersion returns latest version in the toolcache", (t) => {
|
||||
const testVersions = ["2.3.1", "3.2.1", "1.2.3"];
|
||||
sinon.stub(toolcache, "findAllVersions").returns(testVersions);
|
||||
test.serial(
|
||||
"getLatestToolcacheVersion returns latest version in the toolcache",
|
||||
(t) => {
|
||||
const testVersions = ["2.3.1", "3.2.1", "1.2.3"];
|
||||
sinon.stub(toolcache, "findAllVersions").returns(testVersions);
|
||||
|
||||
t.is(setupCodeql.getLatestToolcacheVersion(getRunnerLogger(true)), "3.2.1");
|
||||
});
|
||||
t.is(setupCodeql.getLatestToolcacheVersion(getRunnerLogger(true)), "3.2.1");
|
||||
},
|
||||
);
|
||||
|
||||
@@ -87,14 +87,14 @@ const sendFailedStatusReportTest = test.macro({
|
||||
title: (providedTitle = "") => `sendFailedStatusReport - ${providedTitle}`,
|
||||
});
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
"reports generic error message for non-StartProxyError error",
|
||||
sendFailedStatusReportTest,
|
||||
new Error("Something went wrong today"),
|
||||
"Error from start-proxy Action omitted (Error).",
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
"reports generic error message for non-StartProxyError error with safe error message",
|
||||
sendFailedStatusReportTest,
|
||||
new Error(
|
||||
@@ -105,7 +105,7 @@ test(
|
||||
"Error from start-proxy Action omitted (Error).",
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
"reports generic error message for ConfigurationError error",
|
||||
sendFailedStatusReportTest,
|
||||
new ConfigurationError("Something went wrong today"),
|
||||
@@ -124,110 +124,125 @@ const mixedCredentials = [
|
||||
{ type: "git_source", host: "github.com/github", token: "mno" },
|
||||
];
|
||||
|
||||
test("getCredentials prefers registriesCredentials over registrySecrets", async (t) => {
|
||||
const registryCredentials = Buffer.from(
|
||||
JSON.stringify([
|
||||
{ type: "npm_registry", host: "npm.pkg.github.com", token: "abc" },
|
||||
]),
|
||||
).toString("base64");
|
||||
const registrySecrets = JSON.stringify([
|
||||
{ type: "npm_registry", host: "registry.npmjs.org", token: "def" },
|
||||
]);
|
||||
test.serial(
|
||||
"getCredentials prefers registriesCredentials over registrySecrets",
|
||||
async (t) => {
|
||||
const registryCredentials = Buffer.from(
|
||||
JSON.stringify([
|
||||
{ type: "npm_registry", host: "npm.pkg.github.com", token: "abc" },
|
||||
]),
|
||||
).toString("base64");
|
||||
const registrySecrets = JSON.stringify([
|
||||
{ type: "npm_registry", host: "registry.npmjs.org", token: "def" },
|
||||
]);
|
||||
|
||||
const credentials = startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
registrySecrets,
|
||||
registryCredentials,
|
||||
undefined,
|
||||
);
|
||||
t.is(credentials.length, 1);
|
||||
t.is(credentials[0].host, "npm.pkg.github.com");
|
||||
});
|
||||
const credentials = startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
registrySecrets,
|
||||
registryCredentials,
|
||||
undefined,
|
||||
);
|
||||
t.is(credentials.length, 1);
|
||||
t.is(credentials[0].host, "npm.pkg.github.com");
|
||||
},
|
||||
);
|
||||
|
||||
test("getCredentials throws an error when configurations are not an array", async (t) => {
|
||||
const registryCredentials = Buffer.from(
|
||||
JSON.stringify({ type: "npm_registry", token: "abc" }),
|
||||
).toString("base64");
|
||||
test.serial(
|
||||
"getCredentials throws an error when configurations are not an array",
|
||||
async (t) => {
|
||||
const registryCredentials = Buffer.from(
|
||||
JSON.stringify({ type: "npm_registry", token: "abc" }),
|
||||
).toString("base64");
|
||||
|
||||
t.throws(
|
||||
() =>
|
||||
startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
undefined,
|
||||
registryCredentials,
|
||||
undefined,
|
||||
),
|
||||
{
|
||||
message:
|
||||
"Expected credentials data to be an array of configurations, but it is not.",
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("getCredentials throws error when credential is not an object", async (t) => {
|
||||
const testCredentials = [["foo"], [null]].map(toEncodedJSON);
|
||||
|
||||
for (const testCredential of testCredentials) {
|
||||
t.throws(
|
||||
() =>
|
||||
startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
undefined,
|
||||
testCredential,
|
||||
registryCredentials,
|
||||
undefined,
|
||||
),
|
||||
{
|
||||
message: "Invalid credentials - must be an object",
|
||||
message:
|
||||
"Expected credentials data to be an array of configurations, but it is not.",
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("getCredentials throws error when credential is missing type", async (t) => {
|
||||
const testCredentials = [[{ token: "abc", url: "https://localhost" }]].map(
|
||||
toEncodedJSON,
|
||||
);
|
||||
test.serial(
|
||||
"getCredentials throws error when credential is not an object",
|
||||
async (t) => {
|
||||
const testCredentials = [["foo"], [null]].map(toEncodedJSON);
|
||||
|
||||
for (const testCredential of testCredentials) {
|
||||
t.throws(
|
||||
() =>
|
||||
startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
undefined,
|
||||
testCredential,
|
||||
undefined,
|
||||
),
|
||||
{
|
||||
message: "Invalid credentials - must have a type",
|
||||
},
|
||||
for (const testCredential of testCredentials) {
|
||||
t.throws(
|
||||
() =>
|
||||
startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
undefined,
|
||||
testCredential,
|
||||
undefined,
|
||||
),
|
||||
{
|
||||
message: "Invalid credentials - must be an object",
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"getCredentials throws error when credential is missing type",
|
||||
async (t) => {
|
||||
const testCredentials = [[{ token: "abc", url: "https://localhost" }]].map(
|
||||
toEncodedJSON,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("getCredentials throws error when credential missing host and url", async (t) => {
|
||||
const testCredentials = [
|
||||
[{ type: "npm_registry", token: "abc" }],
|
||||
[{ type: "npm_registry", token: "abc", host: null }],
|
||||
[{ type: "npm_registry", token: "abc", url: null }],
|
||||
].map(toEncodedJSON);
|
||||
for (const testCredential of testCredentials) {
|
||||
t.throws(
|
||||
() =>
|
||||
startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
undefined,
|
||||
testCredential,
|
||||
undefined,
|
||||
),
|
||||
{
|
||||
message: "Invalid credentials - must have a type",
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
for (const testCredential of testCredentials) {
|
||||
t.throws(
|
||||
() =>
|
||||
startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
undefined,
|
||||
testCredential,
|
||||
undefined,
|
||||
),
|
||||
{
|
||||
message: "Invalid credentials - must specify host or url",
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
test.serial(
|
||||
"getCredentials throws error when credential missing host and url",
|
||||
async (t) => {
|
||||
const testCredentials = [
|
||||
[{ type: "npm_registry", token: "abc" }],
|
||||
[{ type: "npm_registry", token: "abc", host: null }],
|
||||
[{ type: "npm_registry", token: "abc", url: null }],
|
||||
].map(toEncodedJSON);
|
||||
|
||||
test("getCredentials filters by language when specified", async (t) => {
|
||||
for (const testCredential of testCredentials) {
|
||||
t.throws(
|
||||
() =>
|
||||
startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
undefined,
|
||||
testCredential,
|
||||
undefined,
|
||||
),
|
||||
{
|
||||
message: "Invalid credentials - must specify host or url",
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
test.serial("getCredentials filters by language when specified", async (t) => {
|
||||
const credentials = startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
undefined,
|
||||
@@ -238,97 +253,113 @@ test("getCredentials filters by language when specified", async (t) => {
|
||||
t.is(credentials[0].type, "maven_repository");
|
||||
});
|
||||
|
||||
test("getCredentials returns all for a language when specified", async (t) => {
|
||||
const credentials = startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
undefined,
|
||||
toEncodedJSON(mixedCredentials),
|
||||
KnownLanguage.go,
|
||||
);
|
||||
t.is(credentials.length, 2);
|
||||
|
||||
const credentialsTypes = credentials.map((c) => c.type);
|
||||
t.assert(credentialsTypes.includes("goproxy_server"));
|
||||
t.assert(credentialsTypes.includes("git_source"));
|
||||
});
|
||||
|
||||
test("getCredentials returns all credentials when no language specified", async (t) => {
|
||||
const credentialsInput = toEncodedJSON(mixedCredentials);
|
||||
|
||||
const credentials = startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
undefined,
|
||||
credentialsInput,
|
||||
undefined,
|
||||
);
|
||||
t.is(credentials.length, mixedCredentials.length);
|
||||
});
|
||||
|
||||
test("getCredentials throws an error when non-printable characters are used", async (t) => {
|
||||
const invalidCredentials = [
|
||||
{ type: "nuget_feed", host: "1nuget.pkg.github.com", token: "abc\u0000" }, // Non-printable character in token
|
||||
{ type: "nuget_feed", host: "2nuget.pkg.github.com\u0001" }, // Non-printable character in host
|
||||
{
|
||||
type: "nuget_feed",
|
||||
host: "3nuget.pkg.github.com",
|
||||
password: "ghi\u0002",
|
||||
}, // Non-printable character in password
|
||||
{ type: "nuget_feed", host: "4nuget.pkg.github.com", password: "ghi\x00" }, // Non-printable character in password
|
||||
];
|
||||
|
||||
for (const invalidCredential of invalidCredentials) {
|
||||
const credentialsInput = Buffer.from(
|
||||
JSON.stringify([invalidCredential]),
|
||||
).toString("base64");
|
||||
|
||||
t.throws(
|
||||
() =>
|
||||
startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
undefined,
|
||||
credentialsInput,
|
||||
undefined,
|
||||
),
|
||||
{
|
||||
message:
|
||||
"Invalid credentials - fields must contain only printable characters",
|
||||
},
|
||||
test.serial(
|
||||
"getCredentials returns all for a language when specified",
|
||||
async (t) => {
|
||||
const credentials = startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
undefined,
|
||||
toEncodedJSON(mixedCredentials),
|
||||
KnownLanguage.go,
|
||||
);
|
||||
}
|
||||
});
|
||||
t.is(credentials.length, 2);
|
||||
|
||||
test("getCredentials logs a warning when a PAT is used without a username", async (t) => {
|
||||
const loggedMessages = [];
|
||||
const logger = getRecordingLogger(loggedMessages);
|
||||
const likelyWrongCredentials = toEncodedJSON([
|
||||
{
|
||||
type: "git_server",
|
||||
host: "https://github.com/",
|
||||
password: `ghp_${makeTestToken()}`,
|
||||
},
|
||||
]);
|
||||
const credentialsTypes = credentials.map((c) => c.type);
|
||||
t.assert(credentialsTypes.includes("goproxy_server"));
|
||||
t.assert(credentialsTypes.includes("git_source"));
|
||||
},
|
||||
);
|
||||
|
||||
const results = startProxyExports.getCredentials(
|
||||
logger,
|
||||
undefined,
|
||||
likelyWrongCredentials,
|
||||
undefined,
|
||||
);
|
||||
test.serial(
|
||||
"getCredentials returns all credentials when no language specified",
|
||||
async (t) => {
|
||||
const credentialsInput = toEncodedJSON(mixedCredentials);
|
||||
|
||||
// The configuration should be accepted, despite the likely problem.
|
||||
t.assert(results);
|
||||
t.is(results.length, 1);
|
||||
t.is(results[0].type, "git_server");
|
||||
t.is(results[0].host, "https://github.com/");
|
||||
t.assert(results[0].password?.startsWith("ghp_"));
|
||||
const credentials = startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
undefined,
|
||||
credentialsInput,
|
||||
undefined,
|
||||
);
|
||||
t.is(credentials.length, mixedCredentials.length);
|
||||
},
|
||||
);
|
||||
|
||||
// A warning should have been logged.
|
||||
checkExpectedLogMessages(t, loggedMessages, [
|
||||
"using a GitHub Personal Access Token (PAT), but no username was provided",
|
||||
]);
|
||||
});
|
||||
test.serial(
|
||||
"getCredentials throws an error when non-printable characters are used",
|
||||
async (t) => {
|
||||
const invalidCredentials = [
|
||||
{ type: "nuget_feed", host: "1nuget.pkg.github.com", token: "abc\u0000" }, // Non-printable character in token
|
||||
{ type: "nuget_feed", host: "2nuget.pkg.github.com\u0001" }, // Non-printable character in host
|
||||
{
|
||||
type: "nuget_feed",
|
||||
host: "3nuget.pkg.github.com",
|
||||
password: "ghi\u0002",
|
||||
}, // Non-printable character in password
|
||||
{
|
||||
type: "nuget_feed",
|
||||
host: "4nuget.pkg.github.com",
|
||||
password: "ghi\x00",
|
||||
}, // Non-printable character in password
|
||||
];
|
||||
|
||||
test("parseLanguage", async (t) => {
|
||||
for (const invalidCredential of invalidCredentials) {
|
||||
const credentialsInput = Buffer.from(
|
||||
JSON.stringify([invalidCredential]),
|
||||
).toString("base64");
|
||||
|
||||
t.throws(
|
||||
() =>
|
||||
startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
undefined,
|
||||
credentialsInput,
|
||||
undefined,
|
||||
),
|
||||
{
|
||||
message:
|
||||
"Invalid credentials - fields must contain only printable characters",
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"getCredentials logs a warning when a PAT is used without a username",
|
||||
async (t) => {
|
||||
const loggedMessages = [];
|
||||
const logger = getRecordingLogger(loggedMessages);
|
||||
const likelyWrongCredentials = toEncodedJSON([
|
||||
{
|
||||
type: "git_server",
|
||||
host: "https://github.com/",
|
||||
password: `ghp_${makeTestToken()}`,
|
||||
},
|
||||
]);
|
||||
|
||||
const results = startProxyExports.getCredentials(
|
||||
logger,
|
||||
undefined,
|
||||
likelyWrongCredentials,
|
||||
undefined,
|
||||
);
|
||||
|
||||
// The configuration should be accepted, despite the likely problem.
|
||||
t.assert(results);
|
||||
t.is(results.length, 1);
|
||||
t.is(results[0].type, "git_server");
|
||||
t.is(results[0].host, "https://github.com/");
|
||||
t.assert(results[0].password?.startsWith("ghp_"));
|
||||
|
||||
// A warning should have been logged.
|
||||
checkExpectedLogMessages(t, loggedMessages, [
|
||||
"using a GitHub Personal Access Token (PAT), but no username was provided",
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial("parseLanguage", async (t) => {
|
||||
// Exact matches
|
||||
t.deepEqual(parseLanguage("csharp"), KnownLanguage.csharp);
|
||||
t.deepEqual(parseLanguage("cpp"), KnownLanguage.cpp);
|
||||
@@ -391,34 +422,14 @@ function mockOfflineFeatures(tempDir: string, logger: Logger) {
|
||||
return setUpFeatureFlagTests(tempDir, logger, gitHubVersion);
|
||||
}
|
||||
|
||||
test("getDownloadUrl returns fallback when `getReleaseByVersion` rejects", async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
mockGetReleaseByTag();
|
||||
test.serial(
|
||||
"getDownloadUrl returns fallback when `getReleaseByVersion` rejects",
|
||||
async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
mockGetReleaseByTag();
|
||||
|
||||
await withTmpDir(async (tempDir) => {
|
||||
const features = mockOfflineFeatures(tempDir, logger);
|
||||
const info = await startProxyExports.getDownloadUrl(
|
||||
getRunnerLogger(true),
|
||||
features,
|
||||
);
|
||||
|
||||
t.is(info.version, startProxyExports.UPDATEJOB_PROXY_VERSION);
|
||||
t.is(
|
||||
info.url,
|
||||
startProxyExports.getFallbackUrl(startProxyExports.getProxyPackage()),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("getDownloadUrl returns fallback when there's no matching release asset", async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
const testAssets = [[], [{ name: "foo" }]];
|
||||
|
||||
await withTmpDir(async (tempDir) => {
|
||||
const features = mockOfflineFeatures(tempDir, logger);
|
||||
|
||||
for (const assets of testAssets) {
|
||||
const stub = mockGetReleaseByTag(assets);
|
||||
await withTmpDir(async (tempDir) => {
|
||||
const features = mockOfflineFeatures(tempDir, logger);
|
||||
const info = await startProxyExports.getDownloadUrl(
|
||||
getRunnerLogger(true),
|
||||
features,
|
||||
@@ -429,13 +440,39 @@ test("getDownloadUrl returns fallback when there's no matching release asset", a
|
||||
info.url,
|
||||
startProxyExports.getFallbackUrl(startProxyExports.getProxyPackage()),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
stub.restore();
|
||||
}
|
||||
});
|
||||
});
|
||||
test.serial(
|
||||
"getDownloadUrl returns fallback when there's no matching release asset",
|
||||
async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
const testAssets = [[], [{ name: "foo" }]];
|
||||
|
||||
test("getDownloadUrl returns matching release asset", async (t) => {
|
||||
await withTmpDir(async (tempDir) => {
|
||||
const features = mockOfflineFeatures(tempDir, logger);
|
||||
|
||||
for (const assets of testAssets) {
|
||||
const stub = mockGetReleaseByTag(assets);
|
||||
const info = await startProxyExports.getDownloadUrl(
|
||||
getRunnerLogger(true),
|
||||
features,
|
||||
);
|
||||
|
||||
t.is(info.version, startProxyExports.UPDATEJOB_PROXY_VERSION);
|
||||
t.is(
|
||||
info.url,
|
||||
startProxyExports.getFallbackUrl(startProxyExports.getProxyPackage()),
|
||||
);
|
||||
|
||||
stub.restore();
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial("getDownloadUrl returns matching release asset", async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
const assets = [
|
||||
{ name: "foo", url: "other-url" },
|
||||
@@ -455,7 +492,7 @@ test("getDownloadUrl returns matching release asset", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("credentialToStr - hides passwords", (t) => {
|
||||
test.serial("credentialToStr - hides passwords", (t) => {
|
||||
const secret = "password123";
|
||||
const credential = {
|
||||
type: "maven_credential",
|
||||
@@ -472,7 +509,7 @@ test("credentialToStr - hides passwords", (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("credentialToStr - hides tokens", (t) => {
|
||||
test.serial("credentialToStr - hides tokens", (t) => {
|
||||
const secret = "password123";
|
||||
const credential = {
|
||||
type: "maven_credential",
|
||||
@@ -489,29 +526,35 @@ test("credentialToStr - hides tokens", (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("getSafeErrorMessage - returns actual message for `StartProxyError`", (t) => {
|
||||
const error = new startProxyExports.StartProxyError(
|
||||
startProxyExports.StartProxyErrorType.DownloadFailed,
|
||||
);
|
||||
t.is(
|
||||
startProxyExports.getSafeErrorMessage(error),
|
||||
startProxyExports.getStartProxyErrorMessage(error.errorType),
|
||||
);
|
||||
});
|
||||
|
||||
test("getSafeErrorMessage - does not return message for arbitrary errors", (t) => {
|
||||
const error = new Error(
|
||||
startProxyExports.getStartProxyErrorMessage(
|
||||
test.serial(
|
||||
"getSafeErrorMessage - returns actual message for `StartProxyError`",
|
||||
(t) => {
|
||||
const error = new startProxyExports.StartProxyError(
|
||||
startProxyExports.StartProxyErrorType.DownloadFailed,
|
||||
),
|
||||
);
|
||||
);
|
||||
t.is(
|
||||
startProxyExports.getSafeErrorMessage(error),
|
||||
startProxyExports.getStartProxyErrorMessage(error.errorType),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const message = startProxyExports.getSafeErrorMessage(error);
|
||||
test.serial(
|
||||
"getSafeErrorMessage - does not return message for arbitrary errors",
|
||||
(t) => {
|
||||
const error = new Error(
|
||||
startProxyExports.getStartProxyErrorMessage(
|
||||
startProxyExports.StartProxyErrorType.DownloadFailed,
|
||||
),
|
||||
);
|
||||
|
||||
t.not(message, error.message);
|
||||
t.assert(message.startsWith("Error from start-proxy Action omitted"));
|
||||
t.assert(message.includes(error.name));
|
||||
});
|
||||
const message = startProxyExports.getSafeErrorMessage(error);
|
||||
|
||||
t.not(message, error.message);
|
||||
t.assert(message.startsWith("Error from start-proxy Action omitted"));
|
||||
t.assert(message.includes(error.name));
|
||||
},
|
||||
);
|
||||
|
||||
const wrapFailureTest = test.macro({
|
||||
exec: async (
|
||||
@@ -530,7 +573,7 @@ const wrapFailureTest = test.macro({
|
||||
title: (providedTitle) => `${providedTitle} - wraps errors on failure`,
|
||||
});
|
||||
|
||||
test("downloadProxy - returns file path on success", async (t) => {
|
||||
test.serial("downloadProxy - returns file path on success", async (t) => {
|
||||
await withRecordingLoggerAsync(async (logger) => {
|
||||
const testPath = "/some/path";
|
||||
sinon.stub(toolcache, "downloadTool").resolves(testPath);
|
||||
@@ -544,7 +587,7 @@ test("downloadProxy - returns file path on success", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
"downloadProxy",
|
||||
wrapFailureTest,
|
||||
() => {
|
||||
@@ -555,7 +598,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test("extractProxy - returns file path on success", async (t) => {
|
||||
test.serial("extractProxy - returns file path on success", async (t) => {
|
||||
await withRecordingLoggerAsync(async (logger) => {
|
||||
const testPath = "/some/path";
|
||||
sinon.stub(toolcache, "extractTar").resolves(testPath);
|
||||
@@ -565,7 +608,7 @@ test("extractProxy - returns file path on success", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
"extractProxy",
|
||||
wrapFailureTest,
|
||||
() => {
|
||||
@@ -576,7 +619,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test("cacheProxy - returns file path on success", async (t) => {
|
||||
test.serial("cacheProxy - returns file path on success", async (t) => {
|
||||
await withRecordingLoggerAsync(async (logger) => {
|
||||
const testPath = "/some/path";
|
||||
sinon.stub(toolcache, "cacheDir").resolves(testPath);
|
||||
@@ -591,7 +634,7 @@ test("cacheProxy - returns file path on success", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
"cacheProxy",
|
||||
wrapFailureTest,
|
||||
() => {
|
||||
@@ -602,100 +645,37 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test("getProxyBinaryPath - returns path from tool cache if available", async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
mockGetReleaseByTag();
|
||||
test.serial(
|
||||
"getProxyBinaryPath - returns path from tool cache if available",
|
||||
async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
mockGetReleaseByTag();
|
||||
|
||||
await withTmpDir(async (tempDir) => {
|
||||
const toolcachePath = "/path/to/proxy/dir";
|
||||
sinon.stub(toolcache, "find").returns(toolcachePath);
|
||||
await withTmpDir(async (tempDir) => {
|
||||
const toolcachePath = "/path/to/proxy/dir";
|
||||
sinon.stub(toolcache, "find").returns(toolcachePath);
|
||||
|
||||
const features = mockOfflineFeatures(tempDir, logger);
|
||||
const path = await startProxyExports.getProxyBinaryPath(logger, features);
|
||||
const features = mockOfflineFeatures(tempDir, logger);
|
||||
const path = await startProxyExports.getProxyBinaryPath(logger, features);
|
||||
|
||||
t.assert(path);
|
||||
t.is(
|
||||
path,
|
||||
filepath.join(toolcachePath, startProxyExports.getProxyFilename()),
|
||||
);
|
||||
});
|
||||
});
|
||||
t.assert(path);
|
||||
t.is(
|
||||
path,
|
||||
filepath.join(toolcachePath, startProxyExports.getProxyFilename()),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("getProxyBinaryPath - downloads proxy if not in cache", async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
const downloadUrl = "url-we-want";
|
||||
mockGetReleaseByTag([
|
||||
{ name: startProxyExports.getProxyPackage(), url: downloadUrl },
|
||||
]);
|
||||
test.serial(
|
||||
"getProxyBinaryPath - downloads proxy if not in cache",
|
||||
async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
const downloadUrl = "url-we-want";
|
||||
mockGetReleaseByTag([
|
||||
{ name: startProxyExports.getProxyPackage(), url: downloadUrl },
|
||||
]);
|
||||
|
||||
const toolcachePath = "/path/to/proxy/dir";
|
||||
const find = sinon.stub(toolcache, "find").returns("");
|
||||
const getApiDetails = sinon.stub(apiClient, "getApiDetails").returns({
|
||||
auth: "",
|
||||
url: "",
|
||||
apiURL: "",
|
||||
});
|
||||
const getAuthorizationHeaderFor = sinon
|
||||
.stub(apiClient, "getAuthorizationHeaderFor")
|
||||
.returns(undefined);
|
||||
const archivePath = "/path/to/archive";
|
||||
const downloadTool = sinon
|
||||
.stub(toolcache, "downloadTool")
|
||||
.resolves(archivePath);
|
||||
const extractedPath = "/path/to/extracted";
|
||||
const extractTar = sinon
|
||||
.stub(toolcache, "extractTar")
|
||||
.resolves(extractedPath);
|
||||
const cacheDir = sinon.stub(toolcache, "cacheDir").resolves(toolcachePath);
|
||||
|
||||
const path = await startProxyExports.getProxyBinaryPath(
|
||||
logger,
|
||||
createFeatures([]),
|
||||
);
|
||||
|
||||
t.assert(find.calledOnce);
|
||||
t.assert(getApiDetails.calledOnce);
|
||||
t.assert(getAuthorizationHeaderFor.calledOnce);
|
||||
t.assert(downloadTool.calledOnceWith(downloadUrl));
|
||||
t.assert(extractTar.calledOnceWith(archivePath));
|
||||
t.assert(cacheDir.calledOnceWith(extractedPath));
|
||||
t.assert(path);
|
||||
t.is(
|
||||
path,
|
||||
filepath.join(toolcachePath, startProxyExports.getProxyFilename()),
|
||||
);
|
||||
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
`Found '${startProxyExports.getProxyPackage()}' in release '${defaults.bundleVersion}' at '${downloadUrl}'`,
|
||||
]);
|
||||
});
|
||||
|
||||
test("getProxyBinaryPath - downloads proxy based on features if not in cache", async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
const expectedTag = "codeql-bundle-v2.20.1";
|
||||
const expectedParams = {
|
||||
owner: "github",
|
||||
repo: "codeql-action",
|
||||
tag: expectedTag,
|
||||
};
|
||||
const downloadUrl = "url-we-want";
|
||||
const assets = [
|
||||
{
|
||||
name: startProxyExports.getProxyPackage(),
|
||||
url: downloadUrl,
|
||||
},
|
||||
];
|
||||
|
||||
const getReleaseByTag = sinon.stub();
|
||||
getReleaseByTag.withArgs(sinon.match(expectedParams)).resolves({
|
||||
status: 200,
|
||||
data: { assets },
|
||||
headers: {},
|
||||
url: "GET /repos/:owner/:repo/releases/tags/:tag",
|
||||
});
|
||||
mockGetApiClient({ repos: { getReleaseByTag } });
|
||||
|
||||
await withTmpDir(async (tempDir) => {
|
||||
const toolcachePath = "/path/to/proxy/dir";
|
||||
const find = sinon.stub(toolcache, "find").returns("");
|
||||
const getApiDetails = sinon.stub(apiClient, "getApiDetails").returns({
|
||||
@@ -716,40 +696,114 @@ test("getProxyBinaryPath - downloads proxy based on features if not in cache", a
|
||||
.resolves(extractedPath);
|
||||
const cacheDir = sinon.stub(toolcache, "cacheDir").resolves(toolcachePath);
|
||||
|
||||
const gitHubVersion: GitHubVersion = {
|
||||
type: GitHubVariant.DOTCOM,
|
||||
};
|
||||
sinon.stub(apiClient, "getGitHubVersion").resolves(gitHubVersion);
|
||||
|
||||
const features = setUpFeatureFlagTests(tempDir, logger, gitHubVersion);
|
||||
sinon.stub(features, "getValue").callsFake(async (_feature, _codeql) => {
|
||||
return true;
|
||||
});
|
||||
const getDefaultCliVersion = sinon
|
||||
.stub(features, "getDefaultCliVersion")
|
||||
.resolves({ cliVersion: "2.20.1", tagName: expectedTag });
|
||||
const path = await startProxyExports.getProxyBinaryPath(logger, features);
|
||||
|
||||
t.assert(getDefaultCliVersion.calledOnce);
|
||||
sinon.assert.calledOnceWithMatch(
|
||||
getReleaseByTag,
|
||||
sinon.match(expectedParams),
|
||||
const path = await startProxyExports.getProxyBinaryPath(
|
||||
logger,
|
||||
createFeatures([]),
|
||||
);
|
||||
|
||||
t.assert(find.calledOnce);
|
||||
t.assert(getApiDetails.calledOnce);
|
||||
t.assert(getAuthorizationHeaderFor.calledOnce);
|
||||
t.assert(downloadTool.calledOnceWith(downloadUrl));
|
||||
t.assert(extractTar.calledOnceWith(archivePath));
|
||||
t.assert(cacheDir.calledOnceWith(extractedPath));
|
||||
|
||||
t.assert(path);
|
||||
t.is(
|
||||
path,
|
||||
filepath.join(toolcachePath, startProxyExports.getProxyFilename()),
|
||||
);
|
||||
});
|
||||
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
`Found '${startProxyExports.getProxyPackage()}' in release '${expectedTag}' at '${downloadUrl}'`,
|
||||
]);
|
||||
});
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
`Found '${startProxyExports.getProxyPackage()}' in release '${defaults.bundleVersion}' at '${downloadUrl}'`,
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"getProxyBinaryPath - downloads proxy based on features if not in cache",
|
||||
async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
const expectedTag = "codeql-bundle-v2.20.1";
|
||||
const expectedParams = {
|
||||
owner: "github",
|
||||
repo: "codeql-action",
|
||||
tag: expectedTag,
|
||||
};
|
||||
const downloadUrl = "url-we-want";
|
||||
const assets = [
|
||||
{
|
||||
name: startProxyExports.getProxyPackage(),
|
||||
url: downloadUrl,
|
||||
},
|
||||
];
|
||||
|
||||
const getReleaseByTag = sinon.stub();
|
||||
getReleaseByTag.withArgs(sinon.match(expectedParams)).resolves({
|
||||
status: 200,
|
||||
data: { assets },
|
||||
headers: {},
|
||||
url: "GET /repos/:owner/:repo/releases/tags/:tag",
|
||||
});
|
||||
mockGetApiClient({ repos: { getReleaseByTag } });
|
||||
|
||||
await withTmpDir(async (tempDir) => {
|
||||
const toolcachePath = "/path/to/proxy/dir";
|
||||
const find = sinon.stub(toolcache, "find").returns("");
|
||||
const getApiDetails = sinon.stub(apiClient, "getApiDetails").returns({
|
||||
auth: "",
|
||||
url: "",
|
||||
apiURL: "",
|
||||
});
|
||||
const getAuthorizationHeaderFor = sinon
|
||||
.stub(apiClient, "getAuthorizationHeaderFor")
|
||||
.returns(undefined);
|
||||
const archivePath = "/path/to/archive";
|
||||
const downloadTool = sinon
|
||||
.stub(toolcache, "downloadTool")
|
||||
.resolves(archivePath);
|
||||
const extractedPath = "/path/to/extracted";
|
||||
const extractTar = sinon
|
||||
.stub(toolcache, "extractTar")
|
||||
.resolves(extractedPath);
|
||||
const cacheDir = sinon
|
||||
.stub(toolcache, "cacheDir")
|
||||
.resolves(toolcachePath);
|
||||
|
||||
const gitHubVersion: GitHubVersion = {
|
||||
type: GitHubVariant.DOTCOM,
|
||||
};
|
||||
sinon.stub(apiClient, "getGitHubVersion").resolves(gitHubVersion);
|
||||
|
||||
const features = setUpFeatureFlagTests(tempDir, logger, gitHubVersion);
|
||||
sinon.stub(features, "getValue").callsFake(async (_feature, _codeql) => {
|
||||
return true;
|
||||
});
|
||||
const getDefaultCliVersion = sinon
|
||||
.stub(features, "getDefaultCliVersion")
|
||||
.resolves({ cliVersion: "2.20.1", tagName: expectedTag });
|
||||
const path = await startProxyExports.getProxyBinaryPath(logger, features);
|
||||
|
||||
t.assert(getDefaultCliVersion.calledOnce);
|
||||
sinon.assert.calledOnceWithMatch(
|
||||
getReleaseByTag,
|
||||
sinon.match(expectedParams),
|
||||
);
|
||||
t.assert(find.calledOnce);
|
||||
t.assert(getApiDetails.calledOnce);
|
||||
t.assert(getAuthorizationHeaderFor.calledOnce);
|
||||
t.assert(downloadTool.calledOnceWith(downloadUrl));
|
||||
t.assert(extractTar.calledOnceWith(archivePath));
|
||||
t.assert(cacheDir.calledOnceWith(extractedPath));
|
||||
|
||||
t.assert(path);
|
||||
t.is(
|
||||
path,
|
||||
filepath.join(toolcachePath, startProxyExports.getProxyFilename()),
|
||||
);
|
||||
});
|
||||
|
||||
checkExpectedLogMessages(t, logger.messages, [
|
||||
`Found '${startProxyExports.getProxyPackage()}' in release '${expectedTag}' at '${downloadUrl}'`,
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -69,19 +69,22 @@ test("checkJavaEnvironment - none set", (t) => {
|
||||
assertEnvVarLogMessages(t, JAVA_PROXY_ENV_VARS, messages, false);
|
||||
});
|
||||
|
||||
test("checkJavaEnvironment - logs values when variables are set", (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
test.serial(
|
||||
"checkJavaEnvironment - logs values when variables are set",
|
||||
(t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
|
||||
for (const envVar of Object.values(JavaEnvVars)) {
|
||||
process.env[envVar] = envVar;
|
||||
}
|
||||
for (const envVar of Object.values(JavaEnvVars)) {
|
||||
process.env[envVar] = envVar;
|
||||
}
|
||||
|
||||
checkJavaEnvVars(logger);
|
||||
assertEnvVarLogMessages(t, JAVA_PROXY_ENV_VARS, messages, true);
|
||||
});
|
||||
checkJavaEnvVars(logger);
|
||||
assertEnvVarLogMessages(t, JAVA_PROXY_ENV_VARS, messages, true);
|
||||
},
|
||||
);
|
||||
|
||||
test("discoverActionsJdks - discovers JDK paths", (t) => {
|
||||
test.serial("discoverActionsJdks - discovers JDK paths", (t) => {
|
||||
// Clear GHA variables that may interfere with this test in CI.
|
||||
for (const envVar of Object.keys(process.env)) {
|
||||
if (envVar.startsWith("JAVA_HOME_")) {
|
||||
@@ -149,7 +152,7 @@ test("checkProxyEnvVars - none set", (t) => {
|
||||
assertEnvVarLogMessages(t, Object.values(ProxyEnvVars), messages, false);
|
||||
});
|
||||
|
||||
test("checkProxyEnvVars - logs values when variables are set", (t) => {
|
||||
test.serial("checkProxyEnvVars - logs values when variables are set", (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
|
||||
@@ -161,7 +164,7 @@ test("checkProxyEnvVars - logs values when variables are set", (t) => {
|
||||
assertEnvVarLogMessages(t, Object.values(ProxyEnvVars), messages, true);
|
||||
});
|
||||
|
||||
test("checkProxyEnvVars - credentials are removed from URLs", (t) => {
|
||||
test.serial("checkProxyEnvVars - credentials are removed from URLs", (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
|
||||
@@ -178,36 +181,45 @@ test("checkProxyEnvVars - credentials are removed from URLs", (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("checkProxyEnvironment - includes base checks for all known languages", async (t) => {
|
||||
stubToolrunner();
|
||||
test.serial(
|
||||
"checkProxyEnvironment - includes base checks for all known languages",
|
||||
async (t) => {
|
||||
stubToolrunner();
|
||||
|
||||
for (const language of Object.values(KnownLanguage)) {
|
||||
for (const language of Object.values(KnownLanguage)) {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
|
||||
await checkProxyEnvironment(logger, language);
|
||||
assertEnvVarLogMessages(t, Object.keys(ProxyEnvVars), messages, false);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"checkProxyEnvironment - includes Java checks for Java",
|
||||
async (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
|
||||
await checkProxyEnvironment(logger, language);
|
||||
stubToolrunner();
|
||||
|
||||
await checkProxyEnvironment(logger, KnownLanguage.java);
|
||||
assertEnvVarLogMessages(t, Object.keys(ProxyEnvVars), messages, false);
|
||||
}
|
||||
});
|
||||
assertEnvVarLogMessages(t, JAVA_PROXY_ENV_VARS, messages, false);
|
||||
},
|
||||
);
|
||||
|
||||
test("checkProxyEnvironment - includes Java checks for Java", async (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
test.serial(
|
||||
"checkProxyEnvironment - includes language-specific checks if the language is undefined",
|
||||
async (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
|
||||
stubToolrunner();
|
||||
stubToolrunner();
|
||||
|
||||
await checkProxyEnvironment(logger, KnownLanguage.java);
|
||||
assertEnvVarLogMessages(t, Object.keys(ProxyEnvVars), messages, false);
|
||||
assertEnvVarLogMessages(t, JAVA_PROXY_ENV_VARS, messages, false);
|
||||
});
|
||||
|
||||
test("checkProxyEnvironment - includes language-specific checks if the language is undefined", async (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
|
||||
stubToolrunner();
|
||||
|
||||
await checkProxyEnvironment(logger, undefined);
|
||||
assertEnvVarLogMessages(t, Object.keys(ProxyEnvVars), messages, false);
|
||||
assertEnvVarLogMessages(t, JAVA_PROXY_ENV_VARS, messages, false);
|
||||
});
|
||||
await checkProxyEnvironment(logger, undefined);
|
||||
assertEnvVarLogMessages(t, Object.keys(ProxyEnvVars), messages, false);
|
||||
assertEnvVarLogMessages(t, JAVA_PROXY_ENV_VARS, messages, false);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -42,7 +42,7 @@ function setupEnvironmentAndStub(tmpDir: string) {
|
||||
getRequiredInput.withArgs("matrix").resolves("input/matrix");
|
||||
}
|
||||
|
||||
test("createStatusReportBase", async (t) => {
|
||||
test.serial("createStatusReportBase", async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupEnvironmentAndStub(tmpDir);
|
||||
|
||||
@@ -92,7 +92,7 @@ test("createStatusReportBase", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("createStatusReportBase - empty configuration", async (t) => {
|
||||
test.serial("createStatusReportBase - empty configuration", async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupEnvironmentAndStub(tmpDir);
|
||||
|
||||
@@ -112,7 +112,7 @@ test("createStatusReportBase - empty configuration", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("createStatusReportBase - partial configuration", async (t) => {
|
||||
test.serial("createStatusReportBase - partial configuration", async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupEnvironmentAndStub(tmpDir);
|
||||
|
||||
@@ -135,7 +135,7 @@ test("createStatusReportBase - partial configuration", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("createStatusReportBase_firstParty", async (t) => {
|
||||
test.serial("createStatusReportBase_firstParty", async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupEnvironmentAndStub(tmpDir);
|
||||
|
||||
@@ -239,58 +239,61 @@ test("createStatusReportBase_firstParty", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("getActionStatus handling correctly various types of errors", (t) => {
|
||||
t.is(
|
||||
getActionsStatus(new Error("arbitrary error")),
|
||||
"failure",
|
||||
"We categorise an arbitrary error as a failure",
|
||||
);
|
||||
test.serial(
|
||||
"getActionStatus handling correctly various types of errors",
|
||||
(t) => {
|
||||
t.is(
|
||||
getActionsStatus(new Error("arbitrary error")),
|
||||
"failure",
|
||||
"We categorise an arbitrary error as a failure",
|
||||
);
|
||||
|
||||
t.is(
|
||||
getActionsStatus(new ConfigurationError("arbitrary error")),
|
||||
"user-error",
|
||||
"We categorise a ConfigurationError as a user error",
|
||||
);
|
||||
t.is(
|
||||
getActionsStatus(new ConfigurationError("arbitrary error")),
|
||||
"user-error",
|
||||
"We categorise a ConfigurationError as a user error",
|
||||
);
|
||||
|
||||
t.is(
|
||||
getActionsStatus(new Error("exit code 1"), "multiple things went wrong"),
|
||||
"failure",
|
||||
"getActionsStatus should return failure if passed an arbitrary error and an additional failure cause",
|
||||
);
|
||||
t.is(
|
||||
getActionsStatus(new Error("exit code 1"), "multiple things went wrong"),
|
||||
"failure",
|
||||
"getActionsStatus should return failure if passed an arbitrary error and an additional failure cause",
|
||||
);
|
||||
|
||||
t.is(
|
||||
getActionsStatus(
|
||||
new ConfigurationError("exit code 1"),
|
||||
"multiple things went wrong",
|
||||
),
|
||||
"user-error",
|
||||
"getActionsStatus should return user-error if passed a configuration error and an additional failure cause",
|
||||
);
|
||||
t.is(
|
||||
getActionsStatus(
|
||||
new ConfigurationError("exit code 1"),
|
||||
"multiple things went wrong",
|
||||
),
|
||||
"user-error",
|
||||
"getActionsStatus should return user-error if passed a configuration error and an additional failure cause",
|
||||
);
|
||||
|
||||
t.is(
|
||||
getActionsStatus(),
|
||||
"success",
|
||||
"getActionsStatus should return success if no error is passed",
|
||||
);
|
||||
t.is(
|
||||
getActionsStatus(),
|
||||
"success",
|
||||
"getActionsStatus should return success if no error is passed",
|
||||
);
|
||||
|
||||
t.is(
|
||||
getActionsStatus(new Object()),
|
||||
"failure",
|
||||
"getActionsStatus should return failure if passed an arbitrary object",
|
||||
);
|
||||
t.is(
|
||||
getActionsStatus(new Object()),
|
||||
"failure",
|
||||
"getActionsStatus should return failure if passed an arbitrary object",
|
||||
);
|
||||
|
||||
t.is(
|
||||
getActionsStatus(null, "an error occurred"),
|
||||
"failure",
|
||||
"getActionsStatus should return failure if passed null and an additional failure cause",
|
||||
);
|
||||
t.is(
|
||||
getActionsStatus(null, "an error occurred"),
|
||||
"failure",
|
||||
"getActionsStatus should return failure if passed null and an additional failure cause",
|
||||
);
|
||||
|
||||
t.is(
|
||||
getActionsStatus(wrapError(new ConfigurationError("arbitrary error"))),
|
||||
"user-error",
|
||||
"We still recognise a wrapped ConfigurationError as a user error",
|
||||
);
|
||||
});
|
||||
t.is(
|
||||
getActionsStatus(wrapError(new ConfigurationError("arbitrary error"))),
|
||||
"user-error",
|
||||
"We still recognise a wrapped ConfigurationError as a user error",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
const testCreateInitWithConfigStatusReport = test.macro({
|
||||
exec: async (
|
||||
@@ -341,7 +344,7 @@ const testCreateInitWithConfigStatusReport = test.macro({
|
||||
title: (_, title) => `createInitWithConfigStatusReport: ${title}`,
|
||||
});
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
testCreateInitWithConfigStatusReport,
|
||||
"returns a value",
|
||||
createTestConfig({
|
||||
@@ -356,7 +359,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
testCreateInitWithConfigStatusReport,
|
||||
"includes packs for a single language",
|
||||
createTestConfig({
|
||||
@@ -373,7 +376,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
testCreateInitWithConfigStatusReport,
|
||||
"includes packs for multiple languages",
|
||||
createTestConfig({
|
||||
|
||||
@@ -94,7 +94,7 @@ function getTestConfigWithTempDir(tempDir: string): configUtils.Config {
|
||||
});
|
||||
}
|
||||
|
||||
test("check flags for JS, analyzing default branch", async (t) => {
|
||||
test.serial("check flags for JS, analyzing default branch", async (t) => {
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
const config = getTestConfigWithTempDir(tmpDir);
|
||||
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(true);
|
||||
@@ -110,7 +110,7 @@ test("check flags for JS, analyzing default branch", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("check flags for all, not analyzing default branch", async (t) => {
|
||||
test.serial("check flags for all, not analyzing default branch", async (t) => {
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
const config = getTestConfigWithTempDir(tmpDir);
|
||||
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(false);
|
||||
@@ -137,7 +137,7 @@ test("get languages that support TRAP caching", async (t) => {
|
||||
t.deepEqual(languagesSupportingCaching, [KnownLanguage.javascript]);
|
||||
});
|
||||
|
||||
test("upload cache key contains right fields", async (t) => {
|
||||
test.serial("upload cache key contains right fields", async (t) => {
|
||||
const loggedMessages = [];
|
||||
const logger = getRecordingLogger(loggedMessages);
|
||||
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(true);
|
||||
@@ -156,47 +156,50 @@ test("upload cache key contains right fields", async (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("download cache looks for the right key and creates dir", async (t) => {
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
const loggedMessages = [];
|
||||
const logger = getRecordingLogger(loggedMessages);
|
||||
sinon.stub(actionsUtil, "getTemporaryDirectory").returns(tmpDir);
|
||||
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(false);
|
||||
const stubRestore = sinon.stub(cache, "restoreCache").resolves("found");
|
||||
const eventFile = path.resolve(tmpDir, "event.json");
|
||||
process.env.GITHUB_EVENT_NAME = "pull_request";
|
||||
process.env.GITHUB_EVENT_PATH = eventFile;
|
||||
fs.writeFileSync(
|
||||
eventFile,
|
||||
JSON.stringify({
|
||||
pull_request: {
|
||||
base: {
|
||||
sha: "somesha",
|
||||
test.serial(
|
||||
"download cache looks for the right key and creates dir",
|
||||
async (t) => {
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
const loggedMessages = [];
|
||||
const logger = getRecordingLogger(loggedMessages);
|
||||
sinon.stub(actionsUtil, "getTemporaryDirectory").returns(tmpDir);
|
||||
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(false);
|
||||
const stubRestore = sinon.stub(cache, "restoreCache").resolves("found");
|
||||
const eventFile = path.resolve(tmpDir, "event.json");
|
||||
process.env.GITHUB_EVENT_NAME = "pull_request";
|
||||
process.env.GITHUB_EVENT_PATH = eventFile;
|
||||
fs.writeFileSync(
|
||||
eventFile,
|
||||
JSON.stringify({
|
||||
pull_request: {
|
||||
base: {
|
||||
sha: "somesha",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
await downloadTrapCaches(
|
||||
stubCodeql,
|
||||
[KnownLanguage.javascript, KnownLanguage.cpp],
|
||||
logger,
|
||||
);
|
||||
t.assert(
|
||||
stubRestore.calledOnceWith(
|
||||
sinon.match.array.contains([
|
||||
path.resolve(tmpDir, "trapCaches", "javascript"),
|
||||
]),
|
||||
sinon
|
||||
.match("somesha")
|
||||
.and(sinon.match("2.10.3"))
|
||||
.and(sinon.match("javascript")),
|
||||
),
|
||||
);
|
||||
t.assert(fs.existsSync(path.resolve(tmpDir, "trapCaches", "javascript")));
|
||||
});
|
||||
});
|
||||
}),
|
||||
);
|
||||
await downloadTrapCaches(
|
||||
stubCodeql,
|
||||
[KnownLanguage.javascript, KnownLanguage.cpp],
|
||||
logger,
|
||||
);
|
||||
t.assert(
|
||||
stubRestore.calledOnceWith(
|
||||
sinon.match.array.contains([
|
||||
path.resolve(tmpDir, "trapCaches", "javascript"),
|
||||
]),
|
||||
sinon
|
||||
.match("somesha")
|
||||
.and(sinon.match("2.10.3"))
|
||||
.and(sinon.match("javascript")),
|
||||
),
|
||||
);
|
||||
t.assert(fs.existsSync(path.resolve(tmpDir, "trapCaches", "javascript")));
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("cleanup removes only old CodeQL TRAP caches", async (t) => {
|
||||
test.serial("cleanup removes only old CodeQL TRAP caches", async (t) => {
|
||||
await util.withTmpDir(async (tmpDir) => {
|
||||
// This config specifies that we are analyzing JavaScript and Ruby, but not Swift.
|
||||
const config = getTestConfigWithTempDir(tmpDir);
|
||||
|
||||
@@ -22,7 +22,7 @@ test.beforeEach(() => {
|
||||
initializeEnvironment("1.2.3");
|
||||
});
|
||||
|
||||
test("validateSarifFileSchema - valid", (t) => {
|
||||
test.serial("validateSarifFileSchema - valid", (t) => {
|
||||
const inputFile = `${__dirname}/../src/testdata/valid-sarif.sarif`;
|
||||
t.notThrows(() =>
|
||||
uploadLib.validateSarifFileSchema(
|
||||
@@ -33,7 +33,7 @@ test("validateSarifFileSchema - valid", (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("validateSarifFileSchema - invalid", (t) => {
|
||||
test.serial("validateSarifFileSchema - invalid", (t) => {
|
||||
const inputFile = `${__dirname}/../src/testdata/invalid-sarif.sarif`;
|
||||
t.throws(() =>
|
||||
uploadLib.validateSarifFileSchema(
|
||||
@@ -44,69 +44,72 @@ test("validateSarifFileSchema - invalid", (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("validate correct payload used for push, PR merge commit, and PR head", async (t) => {
|
||||
process.env["GITHUB_EVENT_NAME"] = "push";
|
||||
const pushPayload: any = uploadLib.buildPayload(
|
||||
"commit",
|
||||
"refs/heads/master",
|
||||
"key",
|
||||
undefined,
|
||||
"",
|
||||
1234,
|
||||
1,
|
||||
"/opt/src",
|
||||
undefined,
|
||||
["CodeQL", "eslint"],
|
||||
"mergeBaseCommit",
|
||||
);
|
||||
// Not triggered by a pull request
|
||||
t.falsy(pushPayload.base_ref);
|
||||
t.falsy(pushPayload.base_sha);
|
||||
test.serial(
|
||||
"validate correct payload used for push, PR merge commit, and PR head",
|
||||
async (t) => {
|
||||
process.env["GITHUB_EVENT_NAME"] = "push";
|
||||
const pushPayload: any = uploadLib.buildPayload(
|
||||
"commit",
|
||||
"refs/heads/master",
|
||||
"key",
|
||||
undefined,
|
||||
"",
|
||||
1234,
|
||||
1,
|
||||
"/opt/src",
|
||||
undefined,
|
||||
["CodeQL", "eslint"],
|
||||
"mergeBaseCommit",
|
||||
);
|
||||
// Not triggered by a pull request
|
||||
t.falsy(pushPayload.base_ref);
|
||||
t.falsy(pushPayload.base_sha);
|
||||
|
||||
process.env["GITHUB_EVENT_NAME"] = "pull_request";
|
||||
process.env["GITHUB_SHA"] = "commit";
|
||||
process.env["GITHUB_BASE_REF"] = "master";
|
||||
process.env["GITHUB_EVENT_PATH"] =
|
||||
`${__dirname}/../src/testdata/pull_request.json`;
|
||||
const prMergePayload: any = uploadLib.buildPayload(
|
||||
"commit",
|
||||
"refs/pull/123/merge",
|
||||
"key",
|
||||
undefined,
|
||||
"",
|
||||
1234,
|
||||
1,
|
||||
"/opt/src",
|
||||
undefined,
|
||||
["CodeQL", "eslint"],
|
||||
"mergeBaseCommit",
|
||||
);
|
||||
// Uploads for a merge commit use the merge base
|
||||
t.deepEqual(prMergePayload.base_ref, "refs/heads/master");
|
||||
t.deepEqual(prMergePayload.base_sha, "mergeBaseCommit");
|
||||
process.env["GITHUB_EVENT_NAME"] = "pull_request";
|
||||
process.env["GITHUB_SHA"] = "commit";
|
||||
process.env["GITHUB_BASE_REF"] = "master";
|
||||
process.env["GITHUB_EVENT_PATH"] =
|
||||
`${__dirname}/../src/testdata/pull_request.json`;
|
||||
const prMergePayload: any = uploadLib.buildPayload(
|
||||
"commit",
|
||||
"refs/pull/123/merge",
|
||||
"key",
|
||||
undefined,
|
||||
"",
|
||||
1234,
|
||||
1,
|
||||
"/opt/src",
|
||||
undefined,
|
||||
["CodeQL", "eslint"],
|
||||
"mergeBaseCommit",
|
||||
);
|
||||
// Uploads for a merge commit use the merge base
|
||||
t.deepEqual(prMergePayload.base_ref, "refs/heads/master");
|
||||
t.deepEqual(prMergePayload.base_sha, "mergeBaseCommit");
|
||||
|
||||
const prHeadPayload: any = uploadLib.buildPayload(
|
||||
"headCommit",
|
||||
"refs/pull/123/head",
|
||||
"key",
|
||||
undefined,
|
||||
"",
|
||||
1234,
|
||||
1,
|
||||
"/opt/src",
|
||||
undefined,
|
||||
["CodeQL", "eslint"],
|
||||
"mergeBaseCommit",
|
||||
);
|
||||
// Uploads for the head use the PR base
|
||||
t.deepEqual(prHeadPayload.base_ref, "refs/heads/master");
|
||||
t.deepEqual(
|
||||
prHeadPayload.base_sha,
|
||||
"f95f852bd8fca8fcc58a9a2d6c842781e32a215e",
|
||||
);
|
||||
});
|
||||
const prHeadPayload: any = uploadLib.buildPayload(
|
||||
"headCommit",
|
||||
"refs/pull/123/head",
|
||||
"key",
|
||||
undefined,
|
||||
"",
|
||||
1234,
|
||||
1,
|
||||
"/opt/src",
|
||||
undefined,
|
||||
["CodeQL", "eslint"],
|
||||
"mergeBaseCommit",
|
||||
);
|
||||
// Uploads for the head use the PR base
|
||||
t.deepEqual(prHeadPayload.base_ref, "refs/heads/master");
|
||||
t.deepEqual(
|
||||
prHeadPayload.base_sha,
|
||||
"f95f852bd8fca8fcc58a9a2d6c842781e32a215e",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("finding SARIF files", async (t) => {
|
||||
test.serial("finding SARIF files", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
// include a couple of sarif files
|
||||
fs.writeFileSync(path.join(tmpDir, "a.sarif"), "");
|
||||
@@ -190,7 +193,7 @@ test("finding SARIF files", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("getGroupedSarifFilePaths - Risk Assessment files", async (t) => {
|
||||
test.serial("getGroupedSarifFilePaths - Risk Assessment files", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const sarifPath = path.join(tmpDir, "a.csra.sarif");
|
||||
fs.writeFileSync(sarifPath, "");
|
||||
@@ -208,7 +211,7 @@ test("getGroupedSarifFilePaths - Risk Assessment files", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("getGroupedSarifFilePaths - Code Quality file", async (t) => {
|
||||
test.serial("getGroupedSarifFilePaths - Code Quality file", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const sarifPath = path.join(tmpDir, "a.quality.sarif");
|
||||
fs.writeFileSync(sarifPath, "");
|
||||
@@ -226,7 +229,7 @@ test("getGroupedSarifFilePaths - Code Quality file", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("getGroupedSarifFilePaths - Code Scanning file", async (t) => {
|
||||
test.serial("getGroupedSarifFilePaths - Code Scanning file", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const sarifPath = path.join(tmpDir, "a.sarif");
|
||||
fs.writeFileSync(sarifPath, "");
|
||||
@@ -244,7 +247,7 @@ test("getGroupedSarifFilePaths - Code Scanning file", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("getGroupedSarifFilePaths - Other file", async (t) => {
|
||||
test.serial("getGroupedSarifFilePaths - Other file", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const sarifPath = path.join(tmpDir, "a.json");
|
||||
fs.writeFileSync(sarifPath, "");
|
||||
@@ -262,7 +265,7 @@ test("getGroupedSarifFilePaths - Other file", async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("populateRunAutomationDetails", (t) => {
|
||||
test.serial("populateRunAutomationDetails", (t) => {
|
||||
const tool = { driver: { name: "test tool" } };
|
||||
let sarifLog: sarif.Log = {
|
||||
version: "2.1.0",
|
||||
@@ -338,7 +341,7 @@ test("populateRunAutomationDetails", (t) => {
|
||||
t.deepEqual(modifiedSarif, expectedSarif);
|
||||
});
|
||||
|
||||
test("validateUniqueCategory when empty", (t) => {
|
||||
test.serial("validateUniqueCategory when empty", (t) => {
|
||||
t.notThrows(() =>
|
||||
uploadLib.validateUniqueCategory(
|
||||
createMockSarif(),
|
||||
@@ -353,7 +356,7 @@ test("validateUniqueCategory when empty", (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("validateUniqueCategory for automation details id", (t) => {
|
||||
test.serial("validateUniqueCategory for automation details id", (t) => {
|
||||
t.notThrows(() =>
|
||||
uploadLib.validateUniqueCategory(
|
||||
createMockSarif("abc"),
|
||||
@@ -422,7 +425,7 @@ test("validateUniqueCategory for automation details id", (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("validateUniqueCategory for tool name", (t) => {
|
||||
test.serial("validateUniqueCategory for tool name", (t) => {
|
||||
t.notThrows(() =>
|
||||
uploadLib.validateUniqueCategory(
|
||||
createMockSarif(undefined, "abc"),
|
||||
@@ -491,77 +494,80 @@ test("validateUniqueCategory for tool name", (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("validateUniqueCategory for automation details id and tool name", (t) => {
|
||||
t.notThrows(() =>
|
||||
uploadLib.validateUniqueCategory(
|
||||
createMockSarif("abc", "abc"),
|
||||
CodeScanning.sentinelPrefix,
|
||||
),
|
||||
);
|
||||
t.throws(() =>
|
||||
uploadLib.validateUniqueCategory(
|
||||
createMockSarif("abc", "abc"),
|
||||
CodeScanning.sentinelPrefix,
|
||||
),
|
||||
);
|
||||
test.serial(
|
||||
"validateUniqueCategory for automation details id and tool name",
|
||||
(t) => {
|
||||
t.notThrows(() =>
|
||||
uploadLib.validateUniqueCategory(
|
||||
createMockSarif("abc", "abc"),
|
||||
CodeScanning.sentinelPrefix,
|
||||
),
|
||||
);
|
||||
t.throws(() =>
|
||||
uploadLib.validateUniqueCategory(
|
||||
createMockSarif("abc", "abc"),
|
||||
CodeScanning.sentinelPrefix,
|
||||
),
|
||||
);
|
||||
|
||||
t.notThrows(() =>
|
||||
uploadLib.validateUniqueCategory(
|
||||
createMockSarif("abc_", "def"),
|
||||
CodeScanning.sentinelPrefix,
|
||||
),
|
||||
);
|
||||
t.throws(() =>
|
||||
uploadLib.validateUniqueCategory(
|
||||
createMockSarif("abc_", "def"),
|
||||
CodeScanning.sentinelPrefix,
|
||||
),
|
||||
);
|
||||
t.notThrows(() =>
|
||||
uploadLib.validateUniqueCategory(
|
||||
createMockSarif("abc_", "def"),
|
||||
CodeScanning.sentinelPrefix,
|
||||
),
|
||||
);
|
||||
t.throws(() =>
|
||||
uploadLib.validateUniqueCategory(
|
||||
createMockSarif("abc_", "def"),
|
||||
CodeScanning.sentinelPrefix,
|
||||
),
|
||||
);
|
||||
|
||||
t.notThrows(() =>
|
||||
uploadLib.validateUniqueCategory(
|
||||
createMockSarif("ghi", "_jkl"),
|
||||
CodeScanning.sentinelPrefix,
|
||||
),
|
||||
);
|
||||
t.throws(() =>
|
||||
uploadLib.validateUniqueCategory(
|
||||
createMockSarif("ghi", "_jkl"),
|
||||
CodeScanning.sentinelPrefix,
|
||||
),
|
||||
);
|
||||
t.notThrows(() =>
|
||||
uploadLib.validateUniqueCategory(
|
||||
createMockSarif("ghi", "_jkl"),
|
||||
CodeScanning.sentinelPrefix,
|
||||
),
|
||||
);
|
||||
t.throws(() =>
|
||||
uploadLib.validateUniqueCategory(
|
||||
createMockSarif("ghi", "_jkl"),
|
||||
CodeScanning.sentinelPrefix,
|
||||
),
|
||||
);
|
||||
|
||||
// Our category sanitization is not perfect. Here are some examples
|
||||
// of where we see false clashes because we replace some characters
|
||||
// with `_` in `sanitize`.
|
||||
t.notThrows(() =>
|
||||
uploadLib.validateUniqueCategory(
|
||||
createMockSarif("abc", "def__"),
|
||||
CodeScanning.sentinelPrefix,
|
||||
),
|
||||
);
|
||||
t.throws(() =>
|
||||
uploadLib.validateUniqueCategory(
|
||||
createMockSarif("abc_def", "_"),
|
||||
CodeScanning.sentinelPrefix,
|
||||
),
|
||||
);
|
||||
// Our category sanitization is not perfect. Here are some examples
|
||||
// of where we see false clashes because we replace some characters
|
||||
// with `_` in `sanitize`.
|
||||
t.notThrows(() =>
|
||||
uploadLib.validateUniqueCategory(
|
||||
createMockSarif("abc", "def__"),
|
||||
CodeScanning.sentinelPrefix,
|
||||
),
|
||||
);
|
||||
t.throws(() =>
|
||||
uploadLib.validateUniqueCategory(
|
||||
createMockSarif("abc_def", "_"),
|
||||
CodeScanning.sentinelPrefix,
|
||||
),
|
||||
);
|
||||
|
||||
t.notThrows(() =>
|
||||
uploadLib.validateUniqueCategory(
|
||||
createMockSarif("mno_", "pqr"),
|
||||
CodeScanning.sentinelPrefix,
|
||||
),
|
||||
);
|
||||
t.throws(() =>
|
||||
uploadLib.validateUniqueCategory(
|
||||
createMockSarif("mno", "_pqr"),
|
||||
CodeScanning.sentinelPrefix,
|
||||
),
|
||||
);
|
||||
});
|
||||
t.notThrows(() =>
|
||||
uploadLib.validateUniqueCategory(
|
||||
createMockSarif("mno_", "pqr"),
|
||||
CodeScanning.sentinelPrefix,
|
||||
),
|
||||
);
|
||||
t.throws(() =>
|
||||
uploadLib.validateUniqueCategory(
|
||||
createMockSarif("mno", "_pqr"),
|
||||
CodeScanning.sentinelPrefix,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("validateUniqueCategory for multiple runs", (t) => {
|
||||
test.serial("validateUniqueCategory for multiple runs", (t) => {
|
||||
const sarif1 = createMockSarif("abc", "def");
|
||||
const sarif2 = createMockSarif("ghi", "jkl");
|
||||
|
||||
@@ -583,7 +589,7 @@ test("validateUniqueCategory for multiple runs", (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("validateUniqueCategory with different prefixes", (t) => {
|
||||
test.serial("validateUniqueCategory with different prefixes", (t) => {
|
||||
t.notThrows(() =>
|
||||
uploadLib.validateUniqueCategory(
|
||||
createMockSarif(),
|
||||
@@ -598,7 +604,7 @@ test("validateUniqueCategory with different prefixes", (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("accept results with invalid artifactLocation.uri value", (t) => {
|
||||
test.serial("accept results with invalid artifactLocation.uri value", (t) => {
|
||||
const loggedMessages: string[] = [];
|
||||
const mockLogger = {
|
||||
info: (message: string) => {
|
||||
@@ -621,100 +627,124 @@ test("accept results with invalid artifactLocation.uri value", (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("shouldShowCombineSarifFilesDeprecationWarning when on dotcom", async (t) => {
|
||||
t.true(
|
||||
await uploadLib.shouldShowCombineSarifFilesDeprecationWarning(
|
||||
[createMockSarif("abc", "def"), createMockSarif("abc", "def")],
|
||||
{
|
||||
type: GitHubVariant.DOTCOM,
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
test.serial(
|
||||
"shouldShowCombineSarifFilesDeprecationWarning when on dotcom",
|
||||
async (t) => {
|
||||
t.true(
|
||||
await uploadLib.shouldShowCombineSarifFilesDeprecationWarning(
|
||||
[createMockSarif("abc", "def"), createMockSarif("abc", "def")],
|
||||
{
|
||||
type: GitHubVariant.DOTCOM,
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("shouldShowCombineSarifFilesDeprecationWarning when on GHES 3.13", async (t) => {
|
||||
t.false(
|
||||
await uploadLib.shouldShowCombineSarifFilesDeprecationWarning(
|
||||
[createMockSarif("abc", "def"), createMockSarif("abc", "def")],
|
||||
{
|
||||
type: GitHubVariant.GHES,
|
||||
version: "3.13.2",
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
test.serial(
|
||||
"shouldShowCombineSarifFilesDeprecationWarning when on GHES 3.13",
|
||||
async (t) => {
|
||||
t.false(
|
||||
await uploadLib.shouldShowCombineSarifFilesDeprecationWarning(
|
||||
[createMockSarif("abc", "def"), createMockSarif("abc", "def")],
|
||||
{
|
||||
type: GitHubVariant.GHES,
|
||||
version: "3.13.2",
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("shouldShowCombineSarifFilesDeprecationWarning when on GHES 3.14", async (t) => {
|
||||
t.true(
|
||||
await uploadLib.shouldShowCombineSarifFilesDeprecationWarning(
|
||||
[createMockSarif("abc", "def"), createMockSarif("abc", "def")],
|
||||
{
|
||||
type: GitHubVariant.GHES,
|
||||
version: "3.14.0",
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
test.serial(
|
||||
"shouldShowCombineSarifFilesDeprecationWarning when on GHES 3.14",
|
||||
async (t) => {
|
||||
t.true(
|
||||
await uploadLib.shouldShowCombineSarifFilesDeprecationWarning(
|
||||
[createMockSarif("abc", "def"), createMockSarif("abc", "def")],
|
||||
{
|
||||
type: GitHubVariant.GHES,
|
||||
version: "3.14.0",
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("shouldShowCombineSarifFilesDeprecationWarning when on GHES 3.16 pre", async (t) => {
|
||||
t.true(
|
||||
await uploadLib.shouldShowCombineSarifFilesDeprecationWarning(
|
||||
[createMockSarif("abc", "def"), createMockSarif("abc", "def")],
|
||||
{
|
||||
type: GitHubVariant.GHES,
|
||||
version: "3.16.0.pre1",
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
test.serial(
|
||||
"shouldShowCombineSarifFilesDeprecationWarning when on GHES 3.16 pre",
|
||||
async (t) => {
|
||||
t.true(
|
||||
await uploadLib.shouldShowCombineSarifFilesDeprecationWarning(
|
||||
[createMockSarif("abc", "def"), createMockSarif("abc", "def")],
|
||||
{
|
||||
type: GitHubVariant.GHES,
|
||||
version: "3.16.0.pre1",
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("shouldShowCombineSarifFilesDeprecationWarning with only 1 run", async (t) => {
|
||||
t.false(
|
||||
await uploadLib.shouldShowCombineSarifFilesDeprecationWarning(
|
||||
[createMockSarif("abc", "def")],
|
||||
{
|
||||
type: GitHubVariant.DOTCOM,
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
test.serial(
|
||||
"shouldShowCombineSarifFilesDeprecationWarning with only 1 run",
|
||||
async (t) => {
|
||||
t.false(
|
||||
await uploadLib.shouldShowCombineSarifFilesDeprecationWarning(
|
||||
[createMockSarif("abc", "def")],
|
||||
{
|
||||
type: GitHubVariant.DOTCOM,
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("shouldShowCombineSarifFilesDeprecationWarning with distinct categories", async (t) => {
|
||||
t.false(
|
||||
await uploadLib.shouldShowCombineSarifFilesDeprecationWarning(
|
||||
[createMockSarif("abc", "def"), createMockSarif("def", "def")],
|
||||
{
|
||||
type: GitHubVariant.DOTCOM,
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
test.serial(
|
||||
"shouldShowCombineSarifFilesDeprecationWarning with distinct categories",
|
||||
async (t) => {
|
||||
t.false(
|
||||
await uploadLib.shouldShowCombineSarifFilesDeprecationWarning(
|
||||
[createMockSarif("abc", "def"), createMockSarif("def", "def")],
|
||||
{
|
||||
type: GitHubVariant.DOTCOM,
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("shouldShowCombineSarifFilesDeprecationWarning with distinct tools", async (t) => {
|
||||
t.false(
|
||||
await uploadLib.shouldShowCombineSarifFilesDeprecationWarning(
|
||||
[createMockSarif("abc", "abc"), createMockSarif("abc", "def")],
|
||||
{
|
||||
type: GitHubVariant.DOTCOM,
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
test.serial(
|
||||
"shouldShowCombineSarifFilesDeprecationWarning with distinct tools",
|
||||
async (t) => {
|
||||
t.false(
|
||||
await uploadLib.shouldShowCombineSarifFilesDeprecationWarning(
|
||||
[createMockSarif("abc", "abc"), createMockSarif("abc", "def")],
|
||||
{
|
||||
type: GitHubVariant.DOTCOM,
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("shouldShowCombineSarifFilesDeprecationWarning when environment variable is already set", async (t) => {
|
||||
process.env["CODEQL_MERGE_SARIF_DEPRECATION_WARNING"] = "true";
|
||||
test.serial(
|
||||
"shouldShowCombineSarifFilesDeprecationWarning when environment variable is already set",
|
||||
async (t) => {
|
||||
process.env["CODEQL_MERGE_SARIF_DEPRECATION_WARNING"] = "true";
|
||||
|
||||
t.false(
|
||||
await uploadLib.shouldShowCombineSarifFilesDeprecationWarning(
|
||||
[createMockSarif("abc", "def"), createMockSarif("abc", "def")],
|
||||
{
|
||||
type: GitHubVariant.DOTCOM,
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
t.false(
|
||||
await uploadLib.shouldShowCombineSarifFilesDeprecationWarning(
|
||||
[createMockSarif("abc", "def"), createMockSarif("abc", "def")],
|
||||
{
|
||||
type: GitHubVariant.DOTCOM,
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("throwIfCombineSarifFilesDisabled when on dotcom", async (t) => {
|
||||
test.serial("throwIfCombineSarifFilesDisabled when on dotcom", async (t) => {
|
||||
await t.throwsAsync(
|
||||
uploadLib.throwIfCombineSarifFilesDisabled(
|
||||
[createMockSarif("abc", "def"), createMockSarif("abc", "def")],
|
||||
@@ -729,7 +759,7 @@ test("throwIfCombineSarifFilesDisabled when on dotcom", async (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("throwIfCombineSarifFilesDisabled when on GHES 3.13", async (t) => {
|
||||
test.serial("throwIfCombineSarifFilesDisabled when on GHES 3.13", async (t) => {
|
||||
await t.notThrowsAsync(
|
||||
uploadLib.throwIfCombineSarifFilesDisabled(
|
||||
[createMockSarif("abc", "def"), createMockSarif("abc", "def")],
|
||||
@@ -741,7 +771,7 @@ test("throwIfCombineSarifFilesDisabled when on GHES 3.13", async (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("throwIfCombineSarifFilesDisabled when on GHES 3.14", async (t) => {
|
||||
test.serial("throwIfCombineSarifFilesDisabled when on GHES 3.14", async (t) => {
|
||||
await t.notThrowsAsync(
|
||||
uploadLib.throwIfCombineSarifFilesDisabled(
|
||||
[createMockSarif("abc", "def"), createMockSarif("abc", "def")],
|
||||
@@ -753,7 +783,7 @@ test("throwIfCombineSarifFilesDisabled when on GHES 3.14", async (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("throwIfCombineSarifFilesDisabled when on GHES 3.17", async (t) => {
|
||||
test.serial("throwIfCombineSarifFilesDisabled when on GHES 3.17", async (t) => {
|
||||
await t.notThrowsAsync(
|
||||
uploadLib.throwIfCombineSarifFilesDisabled(
|
||||
[createMockSarif("abc", "def"), createMockSarif("abc", "def")],
|
||||
@@ -765,39 +795,45 @@ test("throwIfCombineSarifFilesDisabled when on GHES 3.17", async (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("throwIfCombineSarifFilesDisabled when on GHES 3.18 pre", async (t) => {
|
||||
await t.throwsAsync(
|
||||
uploadLib.throwIfCombineSarifFilesDisabled(
|
||||
[createMockSarif("abc", "def"), createMockSarif("abc", "def")],
|
||||
test.serial(
|
||||
"throwIfCombineSarifFilesDisabled when on GHES 3.18 pre",
|
||||
async (t) => {
|
||||
await t.throwsAsync(
|
||||
uploadLib.throwIfCombineSarifFilesDisabled(
|
||||
[createMockSarif("abc", "def"), createMockSarif("abc", "def")],
|
||||
{
|
||||
type: GitHubVariant.GHES,
|
||||
version: "3.18.0.pre1",
|
||||
},
|
||||
),
|
||||
{
|
||||
type: GitHubVariant.GHES,
|
||||
version: "3.18.0.pre1",
|
||||
message:
|
||||
/The CodeQL Action does not support uploading multiple SARIF runs with the same category/,
|
||||
},
|
||||
),
|
||||
{
|
||||
message:
|
||||
/The CodeQL Action does not support uploading multiple SARIF runs with the same category/,
|
||||
},
|
||||
);
|
||||
});
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("throwIfCombineSarifFilesDisabled when on GHES 3.18 alpha", async (t) => {
|
||||
await t.throwsAsync(
|
||||
uploadLib.throwIfCombineSarifFilesDisabled(
|
||||
[createMockSarif("abc", "def"), createMockSarif("abc", "def")],
|
||||
test.serial(
|
||||
"throwIfCombineSarifFilesDisabled when on GHES 3.18 alpha",
|
||||
async (t) => {
|
||||
await t.throwsAsync(
|
||||
uploadLib.throwIfCombineSarifFilesDisabled(
|
||||
[createMockSarif("abc", "def"), createMockSarif("abc", "def")],
|
||||
{
|
||||
type: GitHubVariant.GHES,
|
||||
version: "3.18.0-alpha.1",
|
||||
},
|
||||
),
|
||||
{
|
||||
type: GitHubVariant.GHES,
|
||||
version: "3.18.0-alpha.1",
|
||||
message:
|
||||
/The CodeQL Action does not support uploading multiple SARIF runs with the same category/,
|
||||
},
|
||||
),
|
||||
{
|
||||
message:
|
||||
/The CodeQL Action does not support uploading multiple SARIF runs with the same category/,
|
||||
},
|
||||
);
|
||||
});
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("throwIfCombineSarifFilesDisabled when on GHES 3.18", async (t) => {
|
||||
test.serial("throwIfCombineSarifFilesDisabled when on GHES 3.18", async (t) => {
|
||||
await t.throwsAsync(
|
||||
uploadLib.throwIfCombineSarifFilesDisabled(
|
||||
[createMockSarif("abc", "def"), createMockSarif("abc", "def")],
|
||||
@@ -813,19 +849,22 @@ test("throwIfCombineSarifFilesDisabled when on GHES 3.18", async (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("throwIfCombineSarifFilesDisabled with an invalid GHES version", async (t) => {
|
||||
await t.notThrowsAsync(
|
||||
uploadLib.throwIfCombineSarifFilesDisabled(
|
||||
[createMockSarif("abc", "def"), createMockSarif("abc", "def")],
|
||||
{
|
||||
type: GitHubVariant.GHES,
|
||||
version: "foobar",
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
test.serial(
|
||||
"throwIfCombineSarifFilesDisabled with an invalid GHES version",
|
||||
async (t) => {
|
||||
await t.notThrowsAsync(
|
||||
uploadLib.throwIfCombineSarifFilesDisabled(
|
||||
[createMockSarif("abc", "def"), createMockSarif("abc", "def")],
|
||||
{
|
||||
type: GitHubVariant.GHES,
|
||||
version: "foobar",
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("throwIfCombineSarifFilesDisabled with only 1 run", async (t) => {
|
||||
test.serial("throwIfCombineSarifFilesDisabled with only 1 run", async (t) => {
|
||||
await t.notThrowsAsync(
|
||||
uploadLib.throwIfCombineSarifFilesDisabled(
|
||||
[createMockSarif("abc", "def")],
|
||||
@@ -836,68 +875,80 @@ test("throwIfCombineSarifFilesDisabled with only 1 run", async (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("throwIfCombineSarifFilesDisabled with distinct categories", async (t) => {
|
||||
await t.notThrowsAsync(
|
||||
uploadLib.throwIfCombineSarifFilesDisabled(
|
||||
[createMockSarif("abc", "def"), createMockSarif("def", "def")],
|
||||
{
|
||||
type: GitHubVariant.DOTCOM,
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
test.serial(
|
||||
"throwIfCombineSarifFilesDisabled with distinct categories",
|
||||
async (t) => {
|
||||
await t.notThrowsAsync(
|
||||
uploadLib.throwIfCombineSarifFilesDisabled(
|
||||
[createMockSarif("abc", "def"), createMockSarif("def", "def")],
|
||||
{
|
||||
type: GitHubVariant.DOTCOM,
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("throwIfCombineSarifFilesDisabled with distinct tools", async (t) => {
|
||||
await t.notThrowsAsync(
|
||||
uploadLib.throwIfCombineSarifFilesDisabled(
|
||||
[createMockSarif("abc", "abc"), createMockSarif("abc", "def")],
|
||||
{
|
||||
type: GitHubVariant.DOTCOM,
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
test.serial(
|
||||
"throwIfCombineSarifFilesDisabled with distinct tools",
|
||||
async (t) => {
|
||||
await t.notThrowsAsync(
|
||||
uploadLib.throwIfCombineSarifFilesDisabled(
|
||||
[createMockSarif("abc", "abc"), createMockSarif("abc", "def")],
|
||||
{
|
||||
type: GitHubVariant.DOTCOM,
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("shouldConsiderConfigurationError correctly detects configuration errors", (t) => {
|
||||
const error1 = [
|
||||
"CodeQL analyses from advanced configurations cannot be processed when the default setup is enabled",
|
||||
];
|
||||
t.true(uploadLib.shouldConsiderConfigurationError(error1));
|
||||
test.serial(
|
||||
"shouldConsiderConfigurationError correctly detects configuration errors",
|
||||
(t) => {
|
||||
const error1 = [
|
||||
"CodeQL analyses from advanced configurations cannot be processed when the default setup is enabled",
|
||||
];
|
||||
t.true(uploadLib.shouldConsiderConfigurationError(error1));
|
||||
|
||||
const error2 = [
|
||||
"rejecting delivery as the repository has too many logical alerts",
|
||||
];
|
||||
t.true(uploadLib.shouldConsiderConfigurationError(error2));
|
||||
const error2 = [
|
||||
"rejecting delivery as the repository has too many logical alerts",
|
||||
];
|
||||
t.true(uploadLib.shouldConsiderConfigurationError(error2));
|
||||
|
||||
// We fail cases where we get > 1 error messages back
|
||||
const error3 = [
|
||||
"rejecting delivery as the repository has too many alerts",
|
||||
"extra error message",
|
||||
];
|
||||
t.false(uploadLib.shouldConsiderConfigurationError(error3));
|
||||
});
|
||||
// We fail cases where we get > 1 error messages back
|
||||
const error3 = [
|
||||
"rejecting delivery as the repository has too many alerts",
|
||||
"extra error message",
|
||||
];
|
||||
t.false(uploadLib.shouldConsiderConfigurationError(error3));
|
||||
},
|
||||
);
|
||||
|
||||
test("shouldConsiderInvalidRequest returns correct recognises processing errors", (t) => {
|
||||
const error1 = [
|
||||
"rejecting SARIF",
|
||||
"an invalid URI was provided as a SARIF location",
|
||||
];
|
||||
t.true(uploadLib.shouldConsiderInvalidRequest(error1));
|
||||
test.serial(
|
||||
"shouldConsiderInvalidRequest returns correct recognises processing errors",
|
||||
(t) => {
|
||||
const error1 = [
|
||||
"rejecting SARIF",
|
||||
"an invalid URI was provided as a SARIF location",
|
||||
];
|
||||
t.true(uploadLib.shouldConsiderInvalidRequest(error1));
|
||||
|
||||
const error2 = [
|
||||
"locationFromSarifResult: expected artifact location",
|
||||
"an invalid URI was provided as a SARIF location",
|
||||
];
|
||||
t.true(uploadLib.shouldConsiderInvalidRequest(error2));
|
||||
const error2 = [
|
||||
"locationFromSarifResult: expected artifact location",
|
||||
"an invalid URI was provided as a SARIF location",
|
||||
];
|
||||
t.true(uploadLib.shouldConsiderInvalidRequest(error2));
|
||||
|
||||
// We expect ALL errors to be of processing errors, for the outcome to be classified as
|
||||
// an invalid SARIF upload error.
|
||||
const error3 = [
|
||||
"could not convert rules: invalid security severity value, is not a number",
|
||||
"an unknown error occurred",
|
||||
];
|
||||
t.false(uploadLib.shouldConsiderInvalidRequest(error3));
|
||||
});
|
||||
// We expect ALL errors to be of processing errors, for the outcome to be classified as
|
||||
// an invalid SARIF upload error.
|
||||
const error3 = [
|
||||
"could not convert rules: invalid security severity value, is not a number",
|
||||
"an unknown error occurred",
|
||||
];
|
||||
t.false(uploadLib.shouldConsiderInvalidRequest(error3));
|
||||
},
|
||||
);
|
||||
|
||||
function createMockSarif(id?: string, tool?: string): sarif.Log {
|
||||
return {
|
||||
@@ -962,55 +1013,70 @@ function uploadPayloadFixtures(analysis: analyses.AnalysisConfig) {
|
||||
for (const analysisKind of analyses.supportedAnalysisKinds) {
|
||||
const analysis = analyses.getAnalysisConfig(analysisKind);
|
||||
|
||||
test(`uploadPayload on ${analysis.name} uploads successfully`, async (t) => {
|
||||
const { upload, requestStub, mockData } = uploadPayloadFixtures(analysis);
|
||||
requestStub
|
||||
.withArgs(analysis.target, {
|
||||
owner: mockData.owner,
|
||||
repo: mockData.repo,
|
||||
data: mockData.payload,
|
||||
})
|
||||
.onFirstCall()
|
||||
.returns(Promise.resolve(mockData.response));
|
||||
const result = await upload();
|
||||
t.is(result, mockData.response.data.id);
|
||||
t.true(requestStub.calledOnce);
|
||||
});
|
||||
test.serial(
|
||||
`uploadPayload on ${analysis.name} uploads successfully`,
|
||||
async (t) => {
|
||||
const { upload, requestStub, mockData } = uploadPayloadFixtures(analysis);
|
||||
requestStub
|
||||
.withArgs(analysis.target, {
|
||||
owner: mockData.owner,
|
||||
repo: mockData.repo,
|
||||
data: mockData.payload,
|
||||
})
|
||||
.onFirstCall()
|
||||
.returns(Promise.resolve(mockData.response));
|
||||
const result = await upload();
|
||||
t.is(result, mockData.response.data.id);
|
||||
t.true(requestStub.calledOnce);
|
||||
},
|
||||
);
|
||||
|
||||
for (const envVar of [
|
||||
"CODEQL_ACTION_SKIP_SARIF_UPLOAD",
|
||||
"CODEQL_ACTION_TEST_MODE",
|
||||
]) {
|
||||
test(`uploadPayload on ${analysis.name} skips upload when ${envVar} is set`, async (t) => {
|
||||
const { upload, requestStub, mockData } = uploadPayloadFixtures(analysis);
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
process.env.RUNNER_TEMP = tmpDir;
|
||||
process.env[envVar] = "true";
|
||||
const result = await upload();
|
||||
t.is(result, "dummy-sarif-id");
|
||||
t.false(requestStub.called);
|
||||
test.serial(
|
||||
`uploadPayload on ${analysis.name} skips upload when ${envVar} is set`,
|
||||
async (t) => {
|
||||
const { upload, requestStub, mockData } =
|
||||
uploadPayloadFixtures(analysis);
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
process.env.RUNNER_TEMP = tmpDir;
|
||||
process.env[envVar] = "true";
|
||||
const result = await upload();
|
||||
t.is(result, "dummy-sarif-id");
|
||||
t.false(requestStub.called);
|
||||
|
||||
const payloadFile = path.join(tmpDir, `payload-${analysis.kind}.json`);
|
||||
t.true(fs.existsSync(payloadFile));
|
||||
const payloadFile = path.join(
|
||||
tmpDir,
|
||||
`payload-${analysis.kind}.json`,
|
||||
);
|
||||
t.true(fs.existsSync(payloadFile));
|
||||
|
||||
const savedPayload = JSON.parse(fs.readFileSync(payloadFile, "utf8"));
|
||||
t.deepEqual(savedPayload, mockData.payload);
|
||||
});
|
||||
});
|
||||
const savedPayload = JSON.parse(fs.readFileSync(payloadFile, "utf8"));
|
||||
t.deepEqual(savedPayload, mockData.payload);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
test(`uploadPayload on ${analysis.name} wraps request errors using wrapApiConfigurationError`, async (t) => {
|
||||
const { upload, requestStub } = uploadPayloadFixtures(analysis);
|
||||
const wrapApiConfigurationErrorStub = sinon.stub(
|
||||
api,
|
||||
"wrapApiConfigurationError",
|
||||
);
|
||||
const originalError = new HTTPError(404);
|
||||
const wrappedError = new Error("Wrapped error message");
|
||||
requestStub.rejects(originalError);
|
||||
wrapApiConfigurationErrorStub.withArgs(originalError).returns(wrappedError);
|
||||
await t.throwsAsync(upload, {
|
||||
is: wrappedError,
|
||||
});
|
||||
});
|
||||
test.serial(
|
||||
`uploadPayload on ${analysis.name} wraps request errors using wrapApiConfigurationError`,
|
||||
async (t) => {
|
||||
const { upload, requestStub } = uploadPayloadFixtures(analysis);
|
||||
const wrapApiConfigurationErrorStub = sinon.stub(
|
||||
api,
|
||||
"wrapApiConfigurationError",
|
||||
);
|
||||
const originalError = new HTTPError(404);
|
||||
const wrappedError = new Error("Wrapped error message");
|
||||
requestStub.rejects(originalError);
|
||||
wrapApiConfigurationErrorStub
|
||||
.withArgs(originalError)
|
||||
.returns(wrappedError);
|
||||
await t.throwsAsync(upload, {
|
||||
is: wrappedError,
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ const postProcessAndUploadSarifMacro = test.macro({
|
||||
title: (providedTitle = "") => `processAndUploadSarif - ${providedTitle}`,
|
||||
});
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
"SARIF file",
|
||||
postProcessAndUploadSarifMacro,
|
||||
["test.sarif"],
|
||||
@@ -138,7 +138,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
"JSON file",
|
||||
postProcessAndUploadSarifMacro,
|
||||
["test.json"],
|
||||
@@ -153,7 +153,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
"Code Scanning files",
|
||||
postProcessAndUploadSarifMacro,
|
||||
["test.json", "test.sarif"],
|
||||
@@ -169,7 +169,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
"Code Quality file",
|
||||
postProcessAndUploadSarifMacro,
|
||||
["test.quality.sarif"],
|
||||
@@ -184,7 +184,7 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
test.serial(
|
||||
"Mixed files",
|
||||
postProcessAndUploadSarifMacro,
|
||||
["test.sarif", "test.quality.sarif"],
|
||||
@@ -207,64 +207,70 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test("postProcessAndUploadSarif doesn't upload if upload is disabled", async (t) => {
|
||||
await util.withTmpDir(async (tempDir) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const features = createFeatures([]);
|
||||
test.serial(
|
||||
"postProcessAndUploadSarif doesn't upload if upload is disabled",
|
||||
async (t) => {
|
||||
await util.withTmpDir(async (tempDir) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const features = createFeatures([]);
|
||||
|
||||
const toFullPath = (filename: string) => path.join(tempDir, filename);
|
||||
const toFullPath = (filename: string) => path.join(tempDir, filename);
|
||||
|
||||
const postProcessSarifFiles = mockPostProcessSarifFiles();
|
||||
const uploadPostProcessedFiles = sinon.stub(
|
||||
uploadLib,
|
||||
"uploadPostProcessedFiles",
|
||||
);
|
||||
const postProcessSarifFiles = mockPostProcessSarifFiles();
|
||||
const uploadPostProcessedFiles = sinon.stub(
|
||||
uploadLib,
|
||||
"uploadPostProcessedFiles",
|
||||
);
|
||||
|
||||
fs.writeFileSync(toFullPath("test.sarif"), "");
|
||||
fs.writeFileSync(toFullPath("test.quality.sarif"), "");
|
||||
fs.writeFileSync(toFullPath("test.sarif"), "");
|
||||
fs.writeFileSync(toFullPath("test.quality.sarif"), "");
|
||||
|
||||
const actual = await postProcessAndUploadSarif(
|
||||
logger,
|
||||
features,
|
||||
"never",
|
||||
"",
|
||||
tempDir,
|
||||
);
|
||||
const actual = await postProcessAndUploadSarif(
|
||||
logger,
|
||||
features,
|
||||
"never",
|
||||
"",
|
||||
tempDir,
|
||||
);
|
||||
|
||||
t.truthy(actual);
|
||||
t.assert(postProcessSarifFiles.calledTwice);
|
||||
t.assert(uploadPostProcessedFiles.notCalled);
|
||||
});
|
||||
});
|
||||
t.truthy(actual);
|
||||
t.assert(postProcessSarifFiles.calledTwice);
|
||||
t.assert(uploadPostProcessedFiles.notCalled);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("postProcessAndUploadSarif writes post-processed SARIF files if output directory is provided", async (t) => {
|
||||
await util.withTmpDir(async (tempDir) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const features = createFeatures([]);
|
||||
test.serial(
|
||||
"postProcessAndUploadSarif writes post-processed SARIF files if output directory is provided",
|
||||
async (t) => {
|
||||
await util.withTmpDir(async (tempDir) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const features = createFeatures([]);
|
||||
|
||||
const toFullPath = (filename: string) => path.join(tempDir, filename);
|
||||
const toFullPath = (filename: string) => path.join(tempDir, filename);
|
||||
|
||||
const postProcessSarifFiles = mockPostProcessSarifFiles();
|
||||
const postProcessSarifFiles = mockPostProcessSarifFiles();
|
||||
|
||||
fs.writeFileSync(toFullPath("test.sarif"), "");
|
||||
fs.writeFileSync(toFullPath("test.quality.sarif"), "");
|
||||
fs.writeFileSync(toFullPath("test.sarif"), "");
|
||||
fs.writeFileSync(toFullPath("test.quality.sarif"), "");
|
||||
|
||||
const postProcessedOutPath = path.join(tempDir, "post-processed");
|
||||
const actual = await postProcessAndUploadSarif(
|
||||
logger,
|
||||
features,
|
||||
"never",
|
||||
"",
|
||||
tempDir,
|
||||
"",
|
||||
postProcessedOutPath,
|
||||
);
|
||||
const postProcessedOutPath = path.join(tempDir, "post-processed");
|
||||
const actual = await postProcessAndUploadSarif(
|
||||
logger,
|
||||
features,
|
||||
"never",
|
||||
"",
|
||||
tempDir,
|
||||
"",
|
||||
postProcessedOutPath,
|
||||
);
|
||||
|
||||
t.truthy(actual);
|
||||
t.assert(postProcessSarifFiles.calledTwice);
|
||||
t.assert(fs.existsSync(path.join(postProcessedOutPath, "upload.sarif")));
|
||||
t.assert(
|
||||
fs.existsSync(path.join(postProcessedOutPath, "upload.quality.sarif")),
|
||||
);
|
||||
});
|
||||
});
|
||||
t.truthy(actual);
|
||||
t.assert(postProcessSarifFiles.calledTwice);
|
||||
t.assert(fs.existsSync(path.join(postProcessedOutPath, "upload.sarif")));
|
||||
t.assert(
|
||||
fs.existsSync(path.join(postProcessedOutPath, "upload.quality.sarif")),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
137
src/util.test.ts
137
src/util.test.ts
@@ -68,22 +68,25 @@ for (const {
|
||||
expectedMemoryValue,
|
||||
reservedPercentageValue,
|
||||
} of GET_MEMORY_FLAG_TESTS) {
|
||||
test(`Memory flag value is ${expectedMemoryValue} for ${
|
||||
input ?? "no user input"
|
||||
} on ${platform} with ${totalMemoryMb} MB total system RAM${
|
||||
reservedPercentageValue
|
||||
? ` and reserved percentage env var set to ${reservedPercentageValue}`
|
||||
: ""
|
||||
}`, async (t) => {
|
||||
process.env[EnvVar.SCALING_RESERVED_RAM_PERCENTAGE] =
|
||||
reservedPercentageValue || undefined;
|
||||
const flag = util.getMemoryFlagValueForPlatform(
|
||||
input,
|
||||
totalMemoryMb * 1024 * 1024,
|
||||
platform,
|
||||
);
|
||||
t.deepEqual(flag, expectedMemoryValue);
|
||||
});
|
||||
test.serial(
|
||||
`Memory flag value is ${expectedMemoryValue} for ${
|
||||
input ?? "no user input"
|
||||
} on ${platform} with ${totalMemoryMb} MB total system RAM${
|
||||
reservedPercentageValue
|
||||
? ` and reserved percentage env var set to ${reservedPercentageValue}`
|
||||
: ""
|
||||
}`,
|
||||
async (t) => {
|
||||
process.env[EnvVar.SCALING_RESERVED_RAM_PERCENTAGE] =
|
||||
reservedPercentageValue || undefined;
|
||||
const flag = util.getMemoryFlagValueForPlatform(
|
||||
input,
|
||||
totalMemoryMb * 1024 * 1024,
|
||||
platform,
|
||||
);
|
||||
t.deepEqual(flag, expectedMemoryValue);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
test("getMemoryFlag() throws if the ram input is < 0 or NaN", async (t) => {
|
||||
@@ -114,19 +117,22 @@ test("getThreadsFlag() throws if the threads input is not an integer", (t) => {
|
||||
t.throws(() => util.getThreadsFlag("hello!", getRunnerLogger(true)));
|
||||
});
|
||||
|
||||
test("getExtraOptionsEnvParam() succeeds on valid JSON with invalid options (for now)", (t) => {
|
||||
const origExtraOptions = process.env.CODEQL_ACTION_EXTRA_OPTIONS;
|
||||
test.serial(
|
||||
"getExtraOptionsEnvParam() succeeds on valid JSON with invalid options (for now)",
|
||||
(t) => {
|
||||
const origExtraOptions = process.env.CODEQL_ACTION_EXTRA_OPTIONS;
|
||||
|
||||
const options = { foo: 42 };
|
||||
const options = { foo: 42 };
|
||||
|
||||
process.env.CODEQL_ACTION_EXTRA_OPTIONS = JSON.stringify(options);
|
||||
process.env.CODEQL_ACTION_EXTRA_OPTIONS = JSON.stringify(options);
|
||||
|
||||
t.deepEqual(util.getExtraOptionsEnvParam(), <any>options);
|
||||
t.deepEqual(util.getExtraOptionsEnvParam(), <any>options);
|
||||
|
||||
process.env.CODEQL_ACTION_EXTRA_OPTIONS = origExtraOptions;
|
||||
});
|
||||
process.env.CODEQL_ACTION_EXTRA_OPTIONS = origExtraOptions;
|
||||
},
|
||||
);
|
||||
|
||||
test("getExtraOptionsEnvParam() succeeds on valid JSON options", (t) => {
|
||||
test.serial("getExtraOptionsEnvParam() succeeds on valid JSON options", (t) => {
|
||||
const origExtraOptions = process.env.CODEQL_ACTION_EXTRA_OPTIONS;
|
||||
|
||||
const options = { database: { init: ["--debug"] } };
|
||||
@@ -137,7 +143,7 @@ test("getExtraOptionsEnvParam() succeeds on valid JSON options", (t) => {
|
||||
process.env.CODEQL_ACTION_EXTRA_OPTIONS = origExtraOptions;
|
||||
});
|
||||
|
||||
test("getExtraOptionsEnvParam() succeeds on valid YAML options", (t) => {
|
||||
test.serial("getExtraOptionsEnvParam() succeeds on valid YAML options", (t) => {
|
||||
const origExtraOptions = process.env.CODEQL_ACTION_EXTRA_OPTIONS;
|
||||
|
||||
const options = { database: { init: ["--debug"] } };
|
||||
@@ -148,7 +154,7 @@ test("getExtraOptionsEnvParam() succeeds on valid YAML options", (t) => {
|
||||
process.env.CODEQL_ACTION_EXTRA_OPTIONS = origExtraOptions;
|
||||
});
|
||||
|
||||
test("getExtraOptionsEnvParam() fails on invalid JSON", (t) => {
|
||||
test.serial("getExtraOptionsEnvParam() fails on invalid JSON", (t) => {
|
||||
const origExtraOptions = process.env.CODEQL_ACTION_EXTRA_OPTIONS;
|
||||
|
||||
process.env.CODEQL_ACTION_EXTRA_OPTIONS = "{{invalid-json}";
|
||||
@@ -233,7 +239,7 @@ test("allowed API versions", async (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("getRequiredEnvParam - gets environment variables", (t) => {
|
||||
test.serial("getRequiredEnvParam - gets environment variables", (t) => {
|
||||
process.env.SOME_UNIT_TEST_VAR = "foo";
|
||||
const result = util.getRequiredEnvParam("SOME_UNIT_TEST_VAR");
|
||||
t.is(result, "foo");
|
||||
@@ -243,17 +249,20 @@ test("getRequiredEnvParam - throws if an environment variable isn't set", (t) =>
|
||||
t.throws(() => util.getRequiredEnvParam("SOME_UNIT_TEST_VAR"));
|
||||
});
|
||||
|
||||
test("getOptionalEnvVar - gets environment variables", (t) => {
|
||||
test.serial("getOptionalEnvVar - gets environment variables", (t) => {
|
||||
process.env.SOME_UNIT_TEST_VAR = "foo";
|
||||
const result = util.getOptionalEnvVar("SOME_UNIT_TEST_VAR");
|
||||
t.is(result, "foo");
|
||||
});
|
||||
|
||||
test("getOptionalEnvVar - gets undefined for empty environment variables", (t) => {
|
||||
process.env.SOME_UNIT_TEST_VAR = "";
|
||||
const result = util.getOptionalEnvVar("SOME_UNIT_TEST_VAR");
|
||||
t.is(result, undefined);
|
||||
});
|
||||
test.serial(
|
||||
"getOptionalEnvVar - gets undefined for empty environment variables",
|
||||
(t) => {
|
||||
process.env.SOME_UNIT_TEST_VAR = "";
|
||||
const result = util.getOptionalEnvVar("SOME_UNIT_TEST_VAR");
|
||||
t.is(result, undefined);
|
||||
},
|
||||
);
|
||||
|
||||
test("getOptionalEnvVar - doesn't throw for undefined environment variables", (t) => {
|
||||
t.notThrows(() => {
|
||||
@@ -405,27 +414,32 @@ for (const [
|
||||
const versionsDescription = `CodeQL Action version ${version} and GitHub version ${formatGitHubVersion(
|
||||
githubVersion,
|
||||
)}`;
|
||||
test(`checkActionVersion ${reportErrorDescription} for ${versionsDescription}`, async (t) => {
|
||||
const warningSpy = sinon.spy(core, "warning");
|
||||
const versionStub = sinon
|
||||
.stub(api, "getGitHubVersion")
|
||||
.resolves(githubVersion);
|
||||
test.serial(
|
||||
`checkActionVersion ${reportErrorDescription} for ${versionsDescription}`,
|
||||
async (t) => {
|
||||
const warningSpy = sinon.spy(core, "warning");
|
||||
const versionStub = sinon
|
||||
.stub(api, "getGitHubVersion")
|
||||
.resolves(githubVersion);
|
||||
|
||||
// call checkActionVersion twice and assert below that warning is reported only once
|
||||
util.checkActionVersion(version, await api.getGitHubVersion());
|
||||
util.checkActionVersion(version, await api.getGitHubVersion());
|
||||
// call checkActionVersion twice and assert below that warning is reported only once
|
||||
util.checkActionVersion(version, await api.getGitHubVersion());
|
||||
util.checkActionVersion(version, await api.getGitHubVersion());
|
||||
|
||||
if (shouldReportError) {
|
||||
t.true(
|
||||
warningSpy.calledOnceWithExactly(
|
||||
sinon.match("CodeQL Action v3 will be deprecated in December 2026."),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
t.false(warningSpy.called);
|
||||
}
|
||||
versionStub.restore();
|
||||
});
|
||||
if (shouldReportError) {
|
||||
t.true(
|
||||
warningSpy.calledOnceWithExactly(
|
||||
sinon.match(
|
||||
"CodeQL Action v3 will be deprecated in December 2026.",
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
t.false(warningSpy.called);
|
||||
}
|
||||
versionStub.restore();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
test("getCgroupCpuCountFromCpus calculates the number of CPUs correctly", async (t) => {
|
||||
@@ -461,14 +475,17 @@ test("getCgroupCpuCountFromCpus returns undefined if the CPU file exists but is
|
||||
});
|
||||
});
|
||||
|
||||
test("checkDiskUsage succeeds and produces positive numbers", async (t) => {
|
||||
process.env["GITHUB_WORKSPACE"] = os.tmpdir();
|
||||
const diskUsage = await util.checkDiskUsage(getRunnerLogger(true));
|
||||
if (t.truthy(diskUsage)) {
|
||||
t.true(diskUsage.numAvailableBytes > 0);
|
||||
t.true(diskUsage.numTotalBytes > 0);
|
||||
}
|
||||
});
|
||||
test.serial(
|
||||
"checkDiskUsage succeeds and produces positive numbers",
|
||||
async (t) => {
|
||||
process.env["GITHUB_WORKSPACE"] = os.tmpdir();
|
||||
const diskUsage = await util.checkDiskUsage(getRunnerLogger(true));
|
||||
if (t.truthy(diskUsage)) {
|
||||
t.true(diskUsage.numAvailableBytes > 0);
|
||||
t.true(diskUsage.numTotalBytes > 0);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
test("joinAtMost - behaves like join if limit is <= 0", (t) => {
|
||||
const sep = ", ";
|
||||
|
||||
@@ -306,7 +306,7 @@ test("getWorkflowErrors() when on.pull_request for wildcard branches", async (t)
|
||||
t.deepEqual(...errorCodes(errors, []));
|
||||
});
|
||||
|
||||
test("getWorkflowErrors() when HEAD^2 is checked out", async (t) => {
|
||||
test.serial("getWorkflowErrors() when HEAD^2 is checked out", async (t) => {
|
||||
process.env.GITHUB_JOB = "test";
|
||||
|
||||
const errors = await getWorkflowErrors(
|
||||
@@ -320,47 +320,59 @@ test("getWorkflowErrors() when HEAD^2 is checked out", async (t) => {
|
||||
t.deepEqual(...errorCodes(errors, [WorkflowErrors.CheckoutWrongHead]));
|
||||
});
|
||||
|
||||
test("getWorkflowErrors() produces an error for workflow with language name and its alias", async (t) => {
|
||||
await testLanguageAliases(
|
||||
t,
|
||||
["java", "kotlin"],
|
||||
{ java: ["java-kotlin", "kotlin"] },
|
||||
[
|
||||
"CodeQL language 'java' is referenced by more than one entry in the 'language' matrix " +
|
||||
"parameter for job 'test'. This may result in duplicate alerts. Please edit the 'language' " +
|
||||
"matrix parameter to keep only one of the following: 'java', 'kotlin'.",
|
||||
],
|
||||
);
|
||||
});
|
||||
test.serial(
|
||||
"getWorkflowErrors() produces an error for workflow with language name and its alias",
|
||||
async (t) => {
|
||||
await testLanguageAliases(
|
||||
t,
|
||||
["java", "kotlin"],
|
||||
{ java: ["java-kotlin", "kotlin"] },
|
||||
[
|
||||
"CodeQL language 'java' is referenced by more than one entry in the 'language' matrix " +
|
||||
"parameter for job 'test'. This may result in duplicate alerts. Please edit the 'language' " +
|
||||
"matrix parameter to keep only one of the following: 'java', 'kotlin'.",
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("getWorkflowErrors() produces an error for workflow with two aliases same language", async (t) => {
|
||||
await testLanguageAliases(
|
||||
t,
|
||||
["java-kotlin", "kotlin"],
|
||||
{ java: ["java-kotlin", "kotlin"] },
|
||||
[
|
||||
"CodeQL language 'java' is referenced by more than one entry in the 'language' matrix " +
|
||||
"parameter for job 'test'. This may result in duplicate alerts. Please edit the 'language' " +
|
||||
"matrix parameter to keep only one of the following: 'java-kotlin', 'kotlin'.",
|
||||
],
|
||||
);
|
||||
});
|
||||
test.serial(
|
||||
"getWorkflowErrors() produces an error for workflow with two aliases same language",
|
||||
async (t) => {
|
||||
await testLanguageAliases(
|
||||
t,
|
||||
["java-kotlin", "kotlin"],
|
||||
{ java: ["java-kotlin", "kotlin"] },
|
||||
[
|
||||
"CodeQL language 'java' is referenced by more than one entry in the 'language' matrix " +
|
||||
"parameter for job 'test'. This may result in duplicate alerts. Please edit the 'language' " +
|
||||
"matrix parameter to keep only one of the following: 'java-kotlin', 'kotlin'.",
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("getWorkflowErrors() does not produce an error for workflow with two distinct languages", async (t) => {
|
||||
await testLanguageAliases(
|
||||
t,
|
||||
["java", "typescript"],
|
||||
{
|
||||
java: ["java-kotlin", "kotlin"],
|
||||
javascript: ["javascript-typescript", "typescript"],
|
||||
},
|
||||
[],
|
||||
);
|
||||
});
|
||||
test.serial(
|
||||
"getWorkflowErrors() does not produce an error for workflow with two distinct languages",
|
||||
async (t) => {
|
||||
await testLanguageAliases(
|
||||
t,
|
||||
["java", "typescript"],
|
||||
{
|
||||
java: ["java-kotlin", "kotlin"],
|
||||
javascript: ["javascript-typescript", "typescript"],
|
||||
},
|
||||
[],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("getWorkflowErrors() does not produce an error if codeql doesn't support language aliases", async (t) => {
|
||||
await testLanguageAliases(t, ["java-kotlin", "kotlin"], undefined, []);
|
||||
});
|
||||
test.serial(
|
||||
"getWorkflowErrors() does not produce an error if codeql doesn't support language aliases",
|
||||
async (t) => {
|
||||
await testLanguageAliases(t, ["java-kotlin", "kotlin"], undefined, []);
|
||||
},
|
||||
);
|
||||
|
||||
async function testLanguageAliases(
|
||||
t: ExecutionContext<unknown>,
|
||||
@@ -483,11 +495,13 @@ test("getWorkflowErrors() when on.push has a trailing comma", async (t) => {
|
||||
t.deepEqual(...errorCodes(errors, []));
|
||||
});
|
||||
|
||||
test("getWorkflowErrors() should only report the current job's CheckoutWrongHead", async (t) => {
|
||||
process.env.GITHUB_JOB = "test";
|
||||
test.serial(
|
||||
"getWorkflowErrors() should only report the current job's CheckoutWrongHead",
|
||||
async (t) => {
|
||||
process.env.GITHUB_JOB = "test";
|
||||
|
||||
const errors = await getWorkflowErrors(
|
||||
yaml.load(`
|
||||
const errors = await getWorkflowErrors(
|
||||
yaml.load(`
|
||||
name: "CodeQL"
|
||||
on:
|
||||
push:
|
||||
@@ -507,17 +521,20 @@ test("getWorkflowErrors() should only report the current job's CheckoutWrongHead
|
||||
test3:
|
||||
steps: []
|
||||
`) as Workflow,
|
||||
await getCodeQLForTesting(),
|
||||
);
|
||||
await getCodeQLForTesting(),
|
||||
);
|
||||
|
||||
t.deepEqual(...errorCodes(errors, [WorkflowErrors.CheckoutWrongHead]));
|
||||
});
|
||||
t.deepEqual(...errorCodes(errors, [WorkflowErrors.CheckoutWrongHead]));
|
||||
},
|
||||
);
|
||||
|
||||
test("getWorkflowErrors() should not report a different job's CheckoutWrongHead", async (t) => {
|
||||
process.env.GITHUB_JOB = "test3";
|
||||
test.serial(
|
||||
"getWorkflowErrors() should not report a different job's CheckoutWrongHead",
|
||||
async (t) => {
|
||||
process.env.GITHUB_JOB = "test3";
|
||||
|
||||
const errors = await getWorkflowErrors(
|
||||
yaml.load(`
|
||||
const errors = await getWorkflowErrors(
|
||||
yaml.load(`
|
||||
name: "CodeQL"
|
||||
on:
|
||||
push:
|
||||
@@ -537,11 +554,12 @@ test("getWorkflowErrors() should not report a different job's CheckoutWrongHead"
|
||||
test3:
|
||||
steps: []
|
||||
`) as Workflow,
|
||||
await getCodeQLForTesting(),
|
||||
);
|
||||
await getCodeQLForTesting(),
|
||||
);
|
||||
|
||||
t.deepEqual(...errorCodes(errors, []));
|
||||
});
|
||||
t.deepEqual(...errorCodes(errors, []));
|
||||
},
|
||||
);
|
||||
|
||||
test("getWorkflowErrors() when on is missing", async (t) => {
|
||||
const errors = await getWorkflowErrors(
|
||||
@@ -723,11 +741,13 @@ test("getWorkflowErrors() should not report a warning involving versions of othe
|
||||
t.deepEqual(...errorCodes(errors, []));
|
||||
});
|
||||
|
||||
test("getCategoryInputOrThrow returns category for simple workflow with category", (t) => {
|
||||
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
|
||||
t.is(
|
||||
getCategoryInputOrThrow(
|
||||
yaml.load(`
|
||||
test.serial(
|
||||
"getCategoryInputOrThrow returns category for simple workflow with category",
|
||||
(t) => {
|
||||
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
|
||||
t.is(
|
||||
getCategoryInputOrThrow(
|
||||
yaml.load(`
|
||||
jobs:
|
||||
analysis:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -738,18 +758,21 @@ test("getCategoryInputOrThrow returns category for simple workflow with category
|
||||
with:
|
||||
category: some-category
|
||||
`) as Workflow,
|
||||
"analysis",
|
||||
{},
|
||||
),
|
||||
"some-category",
|
||||
);
|
||||
});
|
||||
"analysis",
|
||||
{},
|
||||
),
|
||||
"some-category",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("getCategoryInputOrThrow returns undefined for simple workflow without category", (t) => {
|
||||
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
|
||||
t.is(
|
||||
getCategoryInputOrThrow(
|
||||
yaml.load(`
|
||||
test.serial(
|
||||
"getCategoryInputOrThrow returns undefined for simple workflow without category",
|
||||
(t) => {
|
||||
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
|
||||
t.is(
|
||||
getCategoryInputOrThrow(
|
||||
yaml.load(`
|
||||
jobs:
|
||||
analysis:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -758,18 +781,21 @@ test("getCategoryInputOrThrow returns undefined for simple workflow without cate
|
||||
- uses: github/codeql-action/init@v4
|
||||
- uses: github/codeql-action/analyze@v4
|
||||
`) as Workflow,
|
||||
"analysis",
|
||||
{},
|
||||
),
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
"analysis",
|
||||
{},
|
||||
),
|
||||
undefined,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("getCategoryInputOrThrow returns category for workflow with multiple jobs", (t) => {
|
||||
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
|
||||
t.is(
|
||||
getCategoryInputOrThrow(
|
||||
yaml.load(`
|
||||
test.serial(
|
||||
"getCategoryInputOrThrow returns category for workflow with multiple jobs",
|
||||
(t) => {
|
||||
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
|
||||
t.is(
|
||||
getCategoryInputOrThrow(
|
||||
yaml.load(`
|
||||
jobs:
|
||||
foo:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -790,18 +816,21 @@ test("getCategoryInputOrThrow returns category for workflow with multiple jobs",
|
||||
with:
|
||||
category: bar-category
|
||||
`) as Workflow,
|
||||
"bar",
|
||||
{},
|
||||
),
|
||||
"bar-category",
|
||||
);
|
||||
});
|
||||
"bar",
|
||||
{},
|
||||
),
|
||||
"bar-category",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("getCategoryInputOrThrow finds category for workflow with language matrix", (t) => {
|
||||
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
|
||||
t.is(
|
||||
getCategoryInputOrThrow(
|
||||
yaml.load(`
|
||||
test.serial(
|
||||
"getCategoryInputOrThrow finds category for workflow with language matrix",
|
||||
(t) => {
|
||||
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
|
||||
t.is(
|
||||
getCategoryInputOrThrow(
|
||||
yaml.load(`
|
||||
jobs:
|
||||
analysis:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -817,19 +846,22 @@ test("getCategoryInputOrThrow finds category for workflow with language matrix",
|
||||
with:
|
||||
category: "/language:\${{ matrix.language }}"
|
||||
`) as Workflow,
|
||||
"analysis",
|
||||
{ language: "javascript" },
|
||||
),
|
||||
"/language:javascript",
|
||||
);
|
||||
});
|
||||
"analysis",
|
||||
{ language: "javascript" },
|
||||
),
|
||||
"/language:javascript",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("getCategoryInputOrThrow throws error for workflow with dynamic category", (t) => {
|
||||
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
|
||||
t.throws(
|
||||
() =>
|
||||
getCategoryInputOrThrow(
|
||||
yaml.load(`
|
||||
test.serial(
|
||||
"getCategoryInputOrThrow throws error for workflow with dynamic category",
|
||||
(t) => {
|
||||
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
|
||||
t.throws(
|
||||
() =>
|
||||
getCategoryInputOrThrow(
|
||||
yaml.load(`
|
||||
jobs:
|
||||
analysis:
|
||||
steps:
|
||||
@@ -839,23 +871,26 @@ test("getCategoryInputOrThrow throws error for workflow with dynamic category",
|
||||
with:
|
||||
category: "\${{ github.workflow }}"
|
||||
`) as Workflow,
|
||||
"analysis",
|
||||
{},
|
||||
),
|
||||
{
|
||||
message:
|
||||
"Could not get category input to github/codeql-action/analyze since it contained " +
|
||||
"an unrecognized dynamic value.",
|
||||
},
|
||||
);
|
||||
});
|
||||
"analysis",
|
||||
{},
|
||||
),
|
||||
{
|
||||
message:
|
||||
"Could not get category input to github/codeql-action/analyze since it contained " +
|
||||
"an unrecognized dynamic value.",
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("getCategoryInputOrThrow throws error for workflow with multiple calls to analyze", (t) => {
|
||||
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
|
||||
t.throws(
|
||||
() =>
|
||||
getCategoryInputOrThrow(
|
||||
yaml.load(`
|
||||
test.serial(
|
||||
"getCategoryInputOrThrow throws error for workflow with multiple calls to analyze",
|
||||
(t) => {
|
||||
process.env["GITHUB_REPOSITORY"] = "github/codeql-action-fake-repository";
|
||||
t.throws(
|
||||
() =>
|
||||
getCategoryInputOrThrow(
|
||||
yaml.load(`
|
||||
jobs:
|
||||
analysis:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -869,88 +904,101 @@ test("getCategoryInputOrThrow throws error for workflow with multiple calls to a
|
||||
with:
|
||||
category: another-category
|
||||
`) as Workflow,
|
||||
"analysis",
|
||||
{},
|
||||
),
|
||||
{
|
||||
message:
|
||||
"Could not get category input to github/codeql-action/analyze since the analysis job " +
|
||||
"calls github/codeql-action/analyze multiple times.",
|
||||
},
|
||||
);
|
||||
});
|
||||
"analysis",
|
||||
{},
|
||||
),
|
||||
{
|
||||
message:
|
||||
"Could not get category input to github/codeql-action/analyze since the analysis job " +
|
||||
"calls github/codeql-action/analyze multiple times.",
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test("checkWorkflow - validates workflow if `SKIP_WORKFLOW_VALIDATION` is not set", async (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const codeql = createStubCodeQL({});
|
||||
test.serial(
|
||||
"checkWorkflow - validates workflow if `SKIP_WORKFLOW_VALIDATION` is not set",
|
||||
async (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const codeql = createStubCodeQL({});
|
||||
|
||||
sinon.stub(actionsUtil, "isDynamicWorkflow").returns(false);
|
||||
const validateWorkflow = sinon.stub(workflow.internal, "validateWorkflow");
|
||||
validateWorkflow.resolves(undefined);
|
||||
sinon.stub(actionsUtil, "isDynamicWorkflow").returns(false);
|
||||
const validateWorkflow = sinon.stub(workflow.internal, "validateWorkflow");
|
||||
validateWorkflow.resolves(undefined);
|
||||
|
||||
await checkWorkflow(getRecordingLogger(messages), codeql);
|
||||
await checkWorkflow(getRecordingLogger(messages), codeql);
|
||||
|
||||
t.assert(
|
||||
validateWorkflow.calledOnce,
|
||||
"`checkWorkflow` unexpectedly did not call `validateWorkflow`",
|
||||
);
|
||||
checkExpectedLogMessages(t, messages, [
|
||||
"Detected no issues with the code scanning workflow.",
|
||||
]);
|
||||
});
|
||||
t.assert(
|
||||
validateWorkflow.calledOnce,
|
||||
"`checkWorkflow` unexpectedly did not call `validateWorkflow`",
|
||||
);
|
||||
checkExpectedLogMessages(t, messages, [
|
||||
"Detected no issues with the code scanning workflow.",
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
test("checkWorkflow - logs problems with workflow validation", async (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const codeql = createStubCodeQL({});
|
||||
test.serial(
|
||||
"checkWorkflow - logs problems with workflow validation",
|
||||
async (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const codeql = createStubCodeQL({});
|
||||
|
||||
sinon.stub(actionsUtil, "isDynamicWorkflow").returns(false);
|
||||
const validateWorkflow = sinon.stub(workflow.internal, "validateWorkflow");
|
||||
validateWorkflow.resolves("problem");
|
||||
sinon.stub(actionsUtil, "isDynamicWorkflow").returns(false);
|
||||
const validateWorkflow = sinon.stub(workflow.internal, "validateWorkflow");
|
||||
validateWorkflow.resolves("problem");
|
||||
|
||||
await checkWorkflow(getRecordingLogger(messages), codeql);
|
||||
await checkWorkflow(getRecordingLogger(messages), codeql);
|
||||
|
||||
t.assert(
|
||||
validateWorkflow.calledOnce,
|
||||
"`checkWorkflow` unexpectedly did not call `validateWorkflow`",
|
||||
);
|
||||
checkExpectedLogMessages(t, messages, [
|
||||
"Unable to validate code scanning workflow: problem",
|
||||
]);
|
||||
});
|
||||
t.assert(
|
||||
validateWorkflow.calledOnce,
|
||||
"`checkWorkflow` unexpectedly did not call `validateWorkflow`",
|
||||
);
|
||||
checkExpectedLogMessages(t, messages, [
|
||||
"Unable to validate code scanning workflow: problem",
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
test("checkWorkflow - skips validation if `SKIP_WORKFLOW_VALIDATION` is `true`", async (t) => {
|
||||
process.env[EnvVar.SKIP_WORKFLOW_VALIDATION] = "true";
|
||||
test.serial(
|
||||
"checkWorkflow - skips validation if `SKIP_WORKFLOW_VALIDATION` is `true`",
|
||||
async (t) => {
|
||||
process.env[EnvVar.SKIP_WORKFLOW_VALIDATION] = "true";
|
||||
|
||||
const messages: LoggedMessage[] = [];
|
||||
const codeql = createStubCodeQL({});
|
||||
const messages: LoggedMessage[] = [];
|
||||
const codeql = createStubCodeQL({});
|
||||
|
||||
sinon.stub(actionsUtil, "isDynamicWorkflow").returns(false);
|
||||
const validateWorkflow = sinon.stub(workflow.internal, "validateWorkflow");
|
||||
sinon.stub(actionsUtil, "isDynamicWorkflow").returns(false);
|
||||
const validateWorkflow = sinon.stub(workflow.internal, "validateWorkflow");
|
||||
|
||||
await checkWorkflow(getRecordingLogger(messages), codeql);
|
||||
await checkWorkflow(getRecordingLogger(messages), codeql);
|
||||
|
||||
t.assert(
|
||||
validateWorkflow.notCalled,
|
||||
"`checkWorkflow` called `validateWorkflow` unexpectedly",
|
||||
);
|
||||
t.is(messages.length, 0);
|
||||
});
|
||||
t.assert(
|
||||
validateWorkflow.notCalled,
|
||||
"`checkWorkflow` called `validateWorkflow` unexpectedly",
|
||||
);
|
||||
t.is(messages.length, 0);
|
||||
},
|
||||
);
|
||||
|
||||
test("checkWorkflow - skips validation for `dynamic` workflows", async (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const codeql = createStubCodeQL({});
|
||||
test.serial(
|
||||
"checkWorkflow - skips validation for `dynamic` workflows",
|
||||
async (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const codeql = createStubCodeQL({});
|
||||
|
||||
const isDynamicWorkflow = sinon
|
||||
.stub(actionsUtil, "isDynamicWorkflow")
|
||||
.returns(true);
|
||||
const validateWorkflow = sinon.stub(workflow.internal, "validateWorkflow");
|
||||
const isDynamicWorkflow = sinon
|
||||
.stub(actionsUtil, "isDynamicWorkflow")
|
||||
.returns(true);
|
||||
const validateWorkflow = sinon.stub(workflow.internal, "validateWorkflow");
|
||||
|
||||
await checkWorkflow(getRecordingLogger(messages), codeql);
|
||||
await checkWorkflow(getRecordingLogger(messages), codeql);
|
||||
|
||||
t.assert(isDynamicWorkflow.calledOnce);
|
||||
t.assert(
|
||||
validateWorkflow.notCalled,
|
||||
"`checkWorkflow` called `validateWorkflow` unexpectedly",
|
||||
);
|
||||
t.is(messages.length, 0);
|
||||
});
|
||||
t.assert(isDynamicWorkflow.calledOnce);
|
||||
t.assert(
|
||||
validateWorkflow.notCalled,
|
||||
"`checkWorkflow` called `validateWorkflow` unexpectedly",
|
||||
);
|
||||
t.is(messages.length, 0);
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user