Skip to content

Commit 313a0b9

Browse files
authored
Merge pull request #4122 from github/henrymercer/friendly-potato
Don't record an overlay status when the job was cancelled
2 parents 46dfb14 + a48f2d3 commit 313a0b9

5 files changed

Lines changed: 245 additions & 9 deletions

File tree

init/action.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,13 @@ inputs:
164164
[Internal] The ID of the check run, as provided by the Actions runtime environment. Do not set this value manually.
165165
default: ${{ job.check_run_id }}
166166
required: false
167+
job-status:
168+
description: >-
169+
[Internal] The status of the job, as provided by the Actions runtime environment. This is how the
170+
post step learns whether the job as a whole succeeded, failed, or was cancelled. Do not set this
171+
value manually.
172+
default: ${{ job.status }}
173+
required: false
167174
outputs:
168175
codeql-path:
169176
description: The path of the CodeQL binary used for analysis

lib/entry-points.js

Lines changed: 26 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/init-action-post-helper.test.ts

Lines changed: 151 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ import { getRunnerLogger } from "./logging";
1515
import { OverlayDatabaseMode } from "./overlay/overlay-database-mode";
1616
import * as overlayStatus from "./overlay/status";
1717
import { parseRepositoryNwo } from "./repository";
18+
import { JobStatus } from "./status-report";
1819
import {
1920
createFeatures,
2021
createTestConfig,
2122
DEFAULT_ACTIONS_VARS,
23+
getTestEnv,
2224
makeMacro,
2325
makeVersionInfo,
2426
RecordingLogger,
@@ -58,6 +60,8 @@ test.serial("init-post action with debug mode off", async (t) => {
5860
createTestConfig({ debugMode: false }),
5961
parseRepositoryNwo("github/codeql-action"),
6062
createFeatures([]),
63+
"success",
64+
getTestEnv(),
6165
getRunnerLogger(true),
6266
);
6367

@@ -80,6 +84,8 @@ test.serial("init-post action with debug mode on", async (t) => {
8084
createTestConfig({ debugMode: true }),
8185
parseRepositoryNwo("github/codeql-action"),
8286
createFeatures([]),
87+
"success",
88+
getTestEnv(),
8389
getRunnerLogger(true),
8490
);
8591

@@ -375,6 +381,8 @@ test.serial(
375381
}),
376382
parseRepositoryNwo("github/codeql-action"),
377383
createFeatures([Feature.OverlayAnalysisStatusSave]),
384+
"success",
385+
getTestEnv(),
378386
getRunnerLogger(true),
379387
);
380388

@@ -443,6 +451,8 @@ test.serial(
443451
}),
444452
parseRepositoryNwo("github/codeql-action"),
445453
createFeatures([]),
454+
"success",
455+
getTestEnv(),
446456
getRunnerLogger(true),
447457
);
448458

@@ -457,8 +467,13 @@ test.serial(
457467
test.serial("does not save overlay status when build successful", async (t) => {
458468
return await util.withTmpDir(async (tmpDir) => {
459469
setupActionsVars(tmpDir, tmpDir);
460-
// Mark analyze as having completed successfully.
470+
// Mark analyze as having completed successfully. `tryUploadSarifIfRunFailed` reads this from
471+
// the process environment, while `recordOverlayStatus` reads it from the environment it is
472+
// given.
461473
process.env[EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY] = "true";
474+
const env = getTestEnv({
475+
[EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY]: "true",
476+
});
462477

463478
sinon.stub(util, "checkDiskUsage").resolves({
464479
numAvailableBytes: 100 * NUM_BYTES_PER_GIB,
@@ -480,6 +495,8 @@ test.serial("does not save overlay status when build successful", async (t) => {
480495
}),
481496
parseRepositoryNwo("github/codeql-action"),
482497
createFeatures([Feature.OverlayAnalysisStatusSave]),
498+
"success",
499+
env,
483500
getRunnerLogger(true),
484501
);
485502

@@ -517,6 +534,8 @@ test.serial(
517534
}),
518535
parseRepositoryNwo("github/codeql-action"),
519536
createFeatures([]),
537+
"success",
538+
getTestEnv(),
520539
getRunnerLogger(true),
521540
);
522541

@@ -528,6 +547,137 @@ test.serial(
528547
},
529548
);
530549

550+
/**
551+
* Runs `uploadFailureInfo` for an overlay-base job that did not complete successfully, with the
552+
* given job status from the Actions runtime environment.
553+
*/
554+
async function runOverlayPostStep({
555+
jobStatus,
556+
codeQlReportedError = false,
557+
}: {
558+
jobStatus: string | undefined;
559+
codeQlReportedError?: boolean;
560+
}) {
561+
return await util.withTmpDir(async (tmpDir) => {
562+
setupActionsVars(tmpDir, tmpDir);
563+
delete process.env[EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY];
564+
const env = getTestEnv(
565+
codeQlReportedError
566+
? { [EnvVar.JOB_STATUS]: JobStatus.FailureStatus }
567+
: {},
568+
);
569+
570+
sinon.stub(util, "checkDiskUsage").resolves({
571+
numAvailableBytes: 100 * NUM_BYTES_PER_GIB,
572+
numTotalBytes: 200 * NUM_BYTES_PER_GIB,
573+
});
574+
575+
const saveOverlayStatusStub = sinon
576+
.stub(overlayStatus, "saveOverlayStatus")
577+
.resolves(true);
578+
579+
await initActionPostHelper.uploadFailureInfo(
580+
sinon.spy(),
581+
sinon.spy(),
582+
codeql.createStubCodeQL({}),
583+
createTestConfig({
584+
debugMode: false,
585+
languages: ["javascript"],
586+
overlayDatabaseMode: OverlayDatabaseMode.OverlayBase,
587+
}),
588+
parseRepositoryNwo("github/codeql-action"),
589+
createFeatures([Feature.OverlayAnalysisStatusSave]),
590+
jobStatus,
591+
env,
592+
getRunnerLogger(true),
593+
);
594+
595+
return { saveOverlayStatusStub };
596+
});
597+
}
598+
599+
test.serial(
600+
"does not save overlay status when the job was cancelled",
601+
async (t) => {
602+
const { saveOverlayStatusStub } = await runOverlayPostStep({
603+
jobStatus: "cancelled",
604+
});
605+
606+
t.true(
607+
saveOverlayStatusStub.notCalled,
608+
"a cancellation tells us nothing about whether the analysis would have succeeded",
609+
);
610+
},
611+
);
612+
613+
test.serial(
614+
"does not save overlay status when the job status is not recognised",
615+
async (t) => {
616+
const { saveOverlayStatusStub } = await runOverlayPostStep({
617+
jobStatus: "some-new-status",
618+
});
619+
620+
t.true(
621+
saveOverlayStatusStub.notCalled,
622+
"a status we do not recognise tells us nothing about whether the analysis would have succeeded",
623+
);
624+
},
625+
);
626+
627+
test.serial(
628+
"does not save overlay status when the job status is unavailable",
629+
async (t) => {
630+
const { saveOverlayStatusStub } = await runOverlayPostStep({
631+
jobStatus: undefined,
632+
});
633+
634+
t.true(
635+
saveOverlayStatusStub.notCalled,
636+
"without a job status we cannot tell whether the analysis would have succeeded",
637+
);
638+
},
639+
);
640+
641+
test.serial(
642+
"saves overlay status when the job failed rather than being cancelled",
643+
async (t) => {
644+
const { saveOverlayStatusStub } = await runOverlayPostStep({
645+
jobStatus: "failure",
646+
});
647+
648+
t.true(
649+
saveOverlayStatusStub.calledOnce,
650+
"a failed job indicates that the analysis itself failed",
651+
);
652+
},
653+
);
654+
655+
test.serial("saves overlay status when the job succeeded", async (t) => {
656+
const { saveOverlayStatusStub } = await runOverlayPostStep({
657+
jobStatus: "success",
658+
});
659+
660+
t.true(
661+
saveOverlayStatusStub.calledOnce,
662+
"the analysis did not complete successfully even though the job as a whole succeeded",
663+
);
664+
});
665+
666+
test.serial(
667+
"saves overlay status when a CodeQL Action reported an error before the run was cancelled",
668+
async (t) => {
669+
const { saveOverlayStatusStub } = await runOverlayPostStep({
670+
jobStatus: "cancelled",
671+
codeQlReportedError: true,
672+
});
673+
674+
t.true(
675+
saveOverlayStatusStub.calledOnce,
676+
"the analysis genuinely failed, even though the run was later cancelled",
677+
);
678+
},
679+
);
680+
531681
function createTestWorkflow(
532682
steps: workflow.WorkflowJobStep[],
533683
): workflow.Workflow {

src/init-action-post-helper.ts

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
sanitizeArtifactName,
1919
} from "./debug-artifacts";
2020
import * as dependencyCaching from "./dependency-caching";
21-
import { EnvVar } from "./environment";
21+
import { EnvVar, ReadOnlyEnv } from "./environment";
2222
import { Feature, FeatureEnablement } from "./feature-flags";
2323
import { Logger } from "./logging";
2424
import { OverlayDatabaseMode } from "./overlay/overlay-database-mode";
@@ -316,6 +316,8 @@ export async function tryUploadSarifIfRunFailed(
316316
* @param config The CodeQL Action configuration.
317317
* @param repositoryNwo The name and owner of the repository.
318318
* @param features Information about enabled features.
319+
* @param jobStatus The status of the job, as reported by the Actions runtime environment.
320+
* @param env The environment to read variables from.
319321
* @param logger The logger to use.
320322
* @returns The results of uploading the SARIF file for the failure.
321323
*/
@@ -331,9 +333,11 @@ export async function uploadFailureInfo(
331333
config: Config,
332334
repositoryNwo: RepositoryNwo,
333335
features: FeatureEnablement,
336+
jobStatus: string | undefined,
337+
env: ReadOnlyEnv,
334338
logger: Logger,
335339
): Promise<UploadFailedSarifResult> {
336-
await recordOverlayStatus(codeql, config, features, logger);
340+
await recordOverlayStatus(codeql, config, features, jobStatus, env, logger);
337341

338342
const uploadFailedSarifResult = await tryUploadSarifIfRunFailed(
339343
config,
@@ -412,6 +416,37 @@ export async function uploadFailureInfo(
412416
return uploadFailedSarifResult;
413417
}
414418

419+
/**
420+
* Whether one of the CodeQL Actions reported an error for this job, which means the analysis
421+
* genuinely failed.
422+
*
423+
* Note that the converse does not hold: an Action that is terminated abruptly, or that fails before
424+
* it can gather telemetry, does not get to report anything.
425+
*/
426+
function didCodeQlReportError(env: ReadOnlyEnv): boolean {
427+
const jobStatus = env.getOptional(EnvVar.JOB_STATUS);
428+
return (
429+
jobStatus === JobStatus.FailureStatus ||
430+
jobStatus === JobStatus.ConfigErrorStatus
431+
);
432+
}
433+
434+
/**
435+
* Whether the job status tells us anything about whether the analysis itself would have succeeded.
436+
*
437+
* We check for the statuses we know to be meaningful rather than excluding the ones that are not,
438+
* so that a status we do not recognise is treated as inconclusive.
439+
*/
440+
function isConclusiveJobStatus(jobStatus: string | undefined): boolean {
441+
switch (jobStatus?.trim().toLowerCase()) {
442+
case "failure":
443+
case "success":
444+
return true;
445+
default:
446+
return false;
447+
}
448+
}
449+
415450
/**
416451
* If overlay base database creation was attempted but the analysis did not complete
417452
* successfully, save the failure status to the Actions cache so that subsequent runs
@@ -421,16 +456,30 @@ async function recordOverlayStatus(
421456
codeql: CodeQL,
422457
config: Config,
423458
features: FeatureEnablement,
459+
jobStatus: string | undefined,
460+
env: ReadOnlyEnv,
424461
logger: Logger,
425462
) {
426463
if (
427464
config.overlayDatabaseMode !== OverlayDatabaseMode.OverlayBase ||
428-
process.env[EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY] === "true" ||
465+
env.getOptional(EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY) === "true" ||
429466
!(await features.getValue(Feature.OverlayAnalysisStatusSave))
430467
) {
431468
return;
432469
}
433470

471+
// Only record a failure when the job outcome tells us something about the analysis. A cancelled
472+
// job, or a status we do not recognise, says nothing about whether the analysis would have
473+
// succeeded, so recording a failure would disable overlay analysis needlessly. We still record
474+
// one if a CodeQL Action reported an error before the job ended.
475+
if (!isConclusiveJobStatus(jobStatus) && !didCodeQlReportError(env)) {
476+
logger.info(
477+
"Not recording an improved incremental analysis failure for this job because the job " +
478+
`status (${jobStatus ?? "unset"}) does not tell us whether the analysis itself failed.`,
479+
);
480+
return;
481+
}
482+
434483
const checkRunIdInput = actionsUtil.getOptionalInput("check-run-id");
435484
const checkRunId =
436485
checkRunIdInput !== undefined ? parseInt(checkRunIdInput, 10) : undefined;

0 commit comments

Comments
 (0)