diff --git a/lib/init-action.js b/lib/init-action.js index 86b8fb3a16..fda7608f0b 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -105442,6 +105442,17 @@ var CODEQL_OVERLAY_MINIMUM_VERSION_PYTHON = "2.23.9"; var CODEQL_OVERLAY_MINIMUM_VERSION_RUBY = "2.23.9"; var OVERLAY_BASE_DATABASE_MAX_UPLOAD_SIZE_MB = 7500; var OVERLAY_BASE_DATABASE_MAX_UPLOAD_SIZE_BYTES = OVERLAY_BASE_DATABASE_MAX_UPLOAD_SIZE_MB * 1e6; +function revertOverlayModeIfDiffInformedUnavailable(config, diffInformedAnalysisExpected, logger) { + if (config.overlayDatabaseMode !== "overlay" /* Overlay */) { + return; + } + if (diffInformedAnalysisExpected && !fs4.existsSync(getDiffRangesJsonFilePath())) { + logger.warning( + `Diff-informed analysis is not available for this pull request. Reverting overlay database mode to ${"none" /* None */}.` + ); + config.overlayDatabaseMode = "none" /* None */; + } +} async function writeBaseDatabaseOidsFile(config, sourceRoot) { const gitFileOids = await getFileOidsUnderPath(sourceRoot); const gitFileOidsJson = JSON.stringify(gitFileOids); @@ -110338,6 +110349,24 @@ async function run(startedAt) { } return; } + let diffInformedAnalysisExpected = false; + try { + diffInformedAnalysisExpected = await shouldPerformDiffInformedAnalysis( + codeql, + features, + logger + ); + } catch (e) { + logger.warning( + `Failed to determine diff-informed analysis availability: ${getErrorMessage(e)}` + ); + diffInformedAnalysisExpected = true; + } + revertOverlayModeIfDiffInformedUnavailable( + config, + diffInformedAnalysisExpected, + logger + ); let overlayBaseDatabaseStats; let dependencyCachingStatus; try { diff --git a/src/init-action.ts b/src/init-action.ts index 70d5d79ce5..263008113d 100644 --- a/src/init-action.ts +++ b/src/init-action.ts @@ -40,6 +40,7 @@ import { import { getDiffInformedAnalysisBranches, getPullRequestEditedDiffRanges, + shouldPerformDiffInformedAnalysis, writeDiffRangesJsonFile, } from "./diff-informed-analysis-utils"; import { EnvVar } from "./environment"; @@ -64,6 +65,7 @@ import { downloadOverlayBaseDatabaseFromCache, OverlayBaseDatabaseDownloadStats, OverlayDatabaseMode, + revertOverlayModeIfDiffInformedUnavailable, } from "./overlay"; import { getRepositoryNwo, RepositoryNwo } from "./repository"; import { ToolsSource } from "./setup-codeql"; @@ -438,6 +440,27 @@ async function run(startedAt: Date) { return; } + let diffInformedAnalysisExpected = false; + try { + diffInformedAnalysisExpected = await shouldPerformDiffInformedAnalysis( + codeql, + features, + logger, + ); + } catch (e) { + logger.warning( + `Failed to determine diff-informed analysis availability: ${getErrorMessage(e)}`, + ); + // Treat errors conservatively: assume diff-informed was expected so that + // the overlay fallback triggers if the diff ranges file is missing. + diffInformedAnalysisExpected = true; + } + revertOverlayModeIfDiffInformedUnavailable( + config, + diffInformedAnalysisExpected, + logger, + ); + let overlayBaseDatabaseStats: OverlayBaseDatabaseDownloadStats | undefined; let dependencyCachingStatus: DependencyCacheRestoreStatusReport | undefined; try { diff --git a/src/overlay/index.test.ts b/src/overlay/index.test.ts index 2d0b4d3fcb..114909c0a2 100644 --- a/src/overlay/index.test.ts +++ b/src/overlay/index.test.ts @@ -24,6 +24,7 @@ import { getCacheRestoreKeyPrefix, getCacheSaveKey, OverlayDatabaseMode, + revertOverlayModeIfDiffInformedUnavailable, writeBaseDatabaseOidsFile, writeOverlayChangesFile, } from "."; @@ -602,3 +603,77 @@ test.serial("overlay-base database cache keys remain stable", async (t) => { `Expected save key "${saveKey}" to start with restore key prefix "${restoreKeyPrefix}"`, ); }); + +test.serial( + "revertOverlayModeIfDiffInformedUnavailable: no-op when mode is not Overlay", + (t) => { + const config = createTestConfig({}); + config.overlayDatabaseMode = OverlayDatabaseMode.None; + const logger = getRunnerLogger(true); + + revertOverlayModeIfDiffInformedUnavailable(config, true, logger); + + t.is(config.overlayDatabaseMode, OverlayDatabaseMode.None); + }, +); + +test.serial( + "revertOverlayModeIfDiffInformedUnavailable: no-op when diff ranges file exists", + async (t) => { + await withTmpDir(async (tmpDir) => { + const config = createTestConfig({}); + config.overlayDatabaseMode = OverlayDatabaseMode.Overlay; + const logger = getRunnerLogger(true); + + // Create the diff ranges file so it exists + const diffRangesPath = path.join(tmpDir, "pr-diff-range.json"); + await fs.promises.writeFile(diffRangesPath, "[]"); + const stub = sinon + .stub(actionsUtil, "getDiffRangesJsonFilePath") + .returns(diffRangesPath); + + try { + revertOverlayModeIfDiffInformedUnavailable(config, true, logger); + t.is(config.overlayDatabaseMode, OverlayDatabaseMode.Overlay); + } finally { + stub.restore(); + } + }); + }, +); + +test.serial( + "revertOverlayModeIfDiffInformedUnavailable: reverts to None when diff ranges file is missing", + (t) => { + const config = createTestConfig({}); + config.overlayDatabaseMode = + OverlayDatabaseMode.Overlay as OverlayDatabaseMode; + const logger = getRunnerLogger(true); + + const stub = sinon + .stub(actionsUtil, "getDiffRangesJsonFilePath") + .returns("/nonexistent/path/pr-diff-range.json"); + + try { + revertOverlayModeIfDiffInformedUnavailable(config, true, logger); + t.is( + config.overlayDatabaseMode, + OverlayDatabaseMode.None as OverlayDatabaseMode, + ); + } finally { + stub.restore(); + } + }, +); + +test.serial( + "revertOverlayModeIfDiffInformedUnavailable: no-op when diff-informed analysis is not expected", + (t) => { + const config = createTestConfig({}); + config.overlayDatabaseMode = OverlayDatabaseMode.Overlay; + const logger = getRunnerLogger(true); + + revertOverlayModeIfDiffInformedUnavailable(config, false, logger); + t.is(config.overlayDatabaseMode, OverlayDatabaseMode.Overlay); + }, +); diff --git a/src/overlay/index.ts b/src/overlay/index.ts index 4905b254f7..bafa74f6be 100644 --- a/src/overlay/index.ts +++ b/src/overlay/index.ts @@ -62,6 +62,39 @@ const OVERLAY_BASE_DATABASE_MAX_UPLOAD_SIZE_MB = 7500; const OVERLAY_BASE_DATABASE_MAX_UPLOAD_SIZE_BYTES = OVERLAY_BASE_DATABASE_MAX_UPLOAD_SIZE_MB * 1_000_000; +/** + * If overlay analysis is enabled but diff-informed analysis failed to produce + * its output file, reverts the overlay database mode to `None`. Overlay + * without diff-informed is an untested combination that can produce inaccurate + * results, so we fall back to a full non-overlay analysis instead. + * + * @param config The configuration object whose `overlayDatabaseMode` may be mutated. + * @param diffInformedAnalysisExpected Whether diff-informed analysis was expected + * to run for this workflow. When true and the diff ranges file is missing, the + * overlay mode is reverted. + * @param logger The logger instance. + */ +export function revertOverlayModeIfDiffInformedUnavailable( + config: Config, + diffInformedAnalysisExpected: boolean, + logger: Logger, +): void { + if (config.overlayDatabaseMode !== OverlayDatabaseMode.Overlay) { + return; + } + + if ( + diffInformedAnalysisExpected && + !fs.existsSync(actionsUtil.getDiffRangesJsonFilePath()) + ) { + logger.warning( + "Diff-informed analysis is not available for this pull request. " + + `Reverting overlay database mode to ${OverlayDatabaseMode.None}.`, + ); + config.overlayDatabaseMode = OverlayDatabaseMode.None; + } +} + /** * Writes a JSON file containing Git OIDs for all tracked files (represented * by path relative to the source root) under the source root. The file is