Add getCodeQLVersionFromOverlayBaseDatabase()

This commit is contained in:
Chuan-kai Lin
2025-09-25 10:47:52 -07:00
parent ef45800e5c
commit 1b32ed334b
2 changed files with 204 additions and 1 deletions

View File

@@ -14,6 +14,7 @@ import {
getCacheRestoreKeyPrefix,
getCacheSaveKey,
getCacheWorkflowKeyPrefix,
getCodeQLVersionFromOverlayBaseDatabase,
OverlayDatabaseMode,
writeBaseDatabaseOidsFile,
writeOverlayChangesFile,
@@ -315,3 +316,146 @@ test("overlay-base database cache keys remain stable", async (t) => {
`Expected restore key prefix "${restoreKeyPrefix}" to start with workflow key prefix "${workflowKeyPrefix}"`,
);
});
/**
* Helper function to generate a cache save key for testing.
* Sets up the necessary sinon stubs and returns the generated cache key.
*/
async function generateTestCacheKey(codeQlVersion: string): Promise<string> {
const config = createTestConfig({ languages: ["python", "javascript"] });
const commitOid = "abc123def456";
sinon.stub(apiClient, "getAutomationID").resolves("test-automation-id/");
sinon.stub(gitUtils, "getCommitOid").resolves(commitOid);
return await getCacheSaveKey(config, codeQlVersion, "checkout-path");
}
/**
* Helper function to stub getMostRecentActionsCacheEntry with a given key and creation date.
* Returns the stubbed function for cleanup if needed.
*/
function stubMostRecentActionsCacheEntry(key?: string, createdAt?: Date) {
const cacheItem =
key !== undefined || createdAt !== undefined
? {
key,
created_at: createdAt?.toISOString(),
}
: undefined;
return sinon
.stub(apiClient, "getMostRecentActionsCacheEntry")
.resolves(cacheItem);
}
test("getCodeQLVersionFromOverlayBaseDatabase returns version when cache entry is valid", async (t) => {
const logger = getRunnerLogger(true);
const cacheKey = await generateTestCacheKey("2.23.0");
stubMostRecentActionsCacheEntry(cacheKey, new Date());
const result = await getCodeQLVersionFromOverlayBaseDatabase(logger);
t.is(result, "2.23.0", "Should return the extracted CodeQL version");
});
test("getCodeQLVersionFromOverlayBaseDatabase returns undefined when no cache entries found", async (t) => {
const logger = getRunnerLogger(true);
sinon.stub(apiClient, "getAutomationID").resolves("test-automation-id/");
stubMostRecentActionsCacheEntry();
const result = await getCodeQLVersionFromOverlayBaseDatabase(logger);
t.is(
result,
undefined,
"Should return undefined when no cache entries found",
);
});
test("getCodeQLVersionFromOverlayBaseDatabase returns undefined when cache entry is too old", async (t) => {
const logger = getRunnerLogger(true);
const cacheKey = await generateTestCacheKey("2.23.0");
const oldDate = new Date();
oldDate.setDate(oldDate.getDate() - 15); // 15 days ago (older than 14 day limit)
stubMostRecentActionsCacheEntry(cacheKey, oldDate);
const result = await getCodeQLVersionFromOverlayBaseDatabase(logger);
t.is(
result,
undefined,
"Should return undefined when cache entry is too old",
);
});
test("getCodeQLVersionFromOverlayBaseDatabase returns undefined when cache key format is invalid", async (t) => {
const logger = getRunnerLogger(true);
sinon.stub(apiClient, "getAutomationID").resolves("test-automation-id/");
stubMostRecentActionsCacheEntry("invalid-key-format", new Date());
const result = await getCodeQLVersionFromOverlayBaseDatabase(logger);
t.is(
result,
undefined,
"Should return undefined when cache key format is invalid",
);
});
test("getCodeQLVersionFromOverlayBaseDatabase returns undefined when CodeQL version is invalid semver", async (t) => {
const logger = getRunnerLogger(true);
const invalidCacheKey = await generateTestCacheKey("invalid.version");
stubMostRecentActionsCacheEntry(invalidCacheKey, new Date());
const result = await getCodeQLVersionFromOverlayBaseDatabase(logger);
t.is(
result,
undefined,
"Should return undefined when CodeQL version is invalid semver",
);
});
test("getCodeQLVersionFromOverlayBaseDatabase returns undefined when CodeQL version is too old", async (t) => {
const logger = getRunnerLogger(true);
const cacheKey = await generateTestCacheKey("2.20.0"); // Older than minimum required version (2.22.4)
stubMostRecentActionsCacheEntry(cacheKey, new Date());
const result = await getCodeQLVersionFromOverlayBaseDatabase(logger);
t.is(
result,
undefined,
"Should return undefined when CodeQL version is older than minimum required version",
);
});
test("getCodeQLVersionFromOverlayBaseDatabase returns undefined when cache entry has no key", async (t) => {
const logger = getRunnerLogger(true);
sinon.stub(apiClient, "getAutomationID").resolves("test-automation-id/");
stubMostRecentActionsCacheEntry(undefined, new Date());
const result = await getCodeQLVersionFromOverlayBaseDatabase(logger);
t.is(
result,
undefined,
"Should return undefined when cache entry has no key",
);
});
test("getCodeQLVersionFromOverlayBaseDatabase returns undefined when cache entry has no created_at", async (t) => {
const logger = getRunnerLogger(true);
const cacheKey = await generateTestCacheKey("2.23.0");
stubMostRecentActionsCacheEntry(cacheKey, undefined);
const result = await getCodeQLVersionFromOverlayBaseDatabase(logger);
t.is(
result,
undefined,
"Should return undefined when cache entry has no created_at",
);
});

View File

@@ -3,9 +3,10 @@ import * as fs from "fs";
import * as path from "path";
import * as actionsCache from "@actions/cache";
import * as semver from "semver";
import { getRequiredInput, getTemporaryDirectory } from "./actions-util";
import { getAutomationID } from "./api-client";
import { getAutomationID, getMostRecentActionsCacheEntry } from "./api-client";
import { type CodeQL } from "./codeql";
import { type Config } from "./config-utils";
import { getCommitOid, getFileOidsUnderPath } from "./git-utils";
@@ -441,6 +442,64 @@ export async function downloadOverlayBaseDatabaseFromCache(
};
}
const IGNORE_DATABASES_OLDER_THAN_N_DAYS = 14;
export async function getCodeQLVersionFromOverlayBaseDatabase(
logger: Logger,
): Promise<string | undefined> {
const keyPrefix = await getCacheWorkflowKeyPrefix();
const cacheItem = await getMostRecentActionsCacheEntry(keyPrefix);
if (cacheItem?.created_at === undefined || cacheItem.key === undefined) {
logger.info("No overlay-base database cache entries found");
return undefined;
}
const cutoffTime = new Date();
cutoffTime.setDate(cutoffTime.getDate() - IGNORE_DATABASES_OLDER_THAN_N_DAYS);
const cacheCreationTime = new Date(cacheItem.created_at);
if (cacheCreationTime < cutoffTime) {
logger.info(
`Not considering overlay-base database cache entry ${cacheItem.key} ` +
`because it is too old (created at ${cacheItem.created_at})`,
);
return undefined;
}
const keyParts = cacheItem.key.split("-");
if (keyParts.length < 9) {
logger.info(
`Overlay-base database cache entry ${cacheItem.key} has invalid key format`,
);
return undefined;
}
const codeQlVersion = keyParts[keyParts.length - 2];
if (!semver.valid(codeQlVersion)) {
logger.info(
`Overlay-base database cache entry ${cacheItem.key} has invalid ` +
`CodeQL version ${codeQlVersion}`,
);
return undefined;
}
if (semver.lt(codeQlVersion, CODEQL_OVERLAY_MINIMUM_VERSION)) {
logger.info(
`Overlay-base database cache entry ${cacheItem.key} has ` +
`CodeQL version ${codeQlVersion}, which is older than the ` +
`minimum required version ${CODEQL_OVERLAY_MINIMUM_VERSION}`,
);
return undefined;
}
logger.info(
`Found overlay-base database cache entry ${cacheItem.key} ` +
`created at ${cacheItem.created_at} with CodeQL version ${codeQlVersion}`,
);
return codeQlVersion;
}
/**
* Computes the cache key for saving the overlay-base database to the GitHub
* Actions cache.