Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions lib/entry-points.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 8 additions & 10 deletions src/cli/output-cache.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as fs from "fs";
import path from "path";

import test from "ava";

Expand All @@ -18,7 +17,8 @@ test.serial(
"getCachedCodeQlVersion reuses a version persisted by an earlier step",
async (t) => {
await util.withTmpDir(async (tmpDir: string) => {
const cacheFile = path.join(tmpDir, "codeql-action-command-cache.json");
const env = getTestEnv({ [EnvVar.TEMP]: tmpDir });
const cacheFile = outputCache.getCommandCacheFilePath(env);
Comment on lines +20 to +21

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A slightly better approach to address this issue than to set the right environment variable to make the test work is to make the path of the cache file a parameter of the getCachedCodeQlVersion and cacheCodeQlVersion functions.

Since you already have getCommandCacheFilePath, it is easy to provide the right argument in production code. In the test code, it can then be completely arbitrary.

fs.writeFileSync(
cacheFile,
JSON.stringify({
Expand All @@ -27,7 +27,6 @@ test.serial(
}),
"utf8",
);
const env = getTestEnv({ [EnvVar.TEMP]: tmpDir });
t.deepEqual(
outputCache.getCachedCodeQlVersion(logger, env, "/path/to/codeql"),
{
Expand All @@ -42,16 +41,16 @@ test.serial(
"getCachedCodeQlVersion ignores a persisted version from a different CLI",
async (t) => {
await util.withTmpDir(async (tmpDir: string) => {
const cacheFile = path.join(tmpDir, "version.json");
const env = getTestEnv({ [EnvVar.TEMP]: tmpDir });
const cacheFile = outputCache.getCommandCacheFilePath(env);
fs.writeFileSync(
cacheFile,
JSON.stringify({
cmd: "/path/to/other-codeql",
version: { version: "2.20.0" },
entries: { version: { version: "2.20.0" } },
}),
"utf8",
);
const env = getTestEnv({ [EnvVar.TEMP]: tmpDir });
t.is(
outputCache.getCachedCodeQlVersion(logger, env, "/path/to/codeql"),
undefined,
Expand All @@ -64,9 +63,9 @@ test.serial(
"getCachedCodeQlVersion ignores a malformed persisted value",
async (t) => {
await util.withTmpDir(async (tmpDir: string) => {
const cacheFile = path.join(tmpDir, "version.json");
fs.writeFileSync(cacheFile, "not valid json", "utf8");
const env = getTestEnv({ [EnvVar.TEMP]: tmpDir });
const cacheFile = outputCache.getCommandCacheFilePath(env);
fs.writeFileSync(cacheFile, "not valid json", "utf8");
t.is(
outputCache.getCachedCodeQlVersion(logger, env, "/path/to/codeql"),
undefined,
Expand All @@ -79,9 +78,8 @@ test.serial(
"getCachedCodeQlVersion ignores a persisted value with the wrong structure",
async (t) => {
await util.withTmpDir(async (tmpDir: string) => {
const cacheFile = path.join(tmpDir, "version.json");
const env = getTestEnv({ [EnvVar.TEMP]: tmpDir });

const cacheFile = outputCache.getCommandCacheFilePath(env);
const testValues = [
{ cmd: "/path/to/codeql" },
{ entries: { version: { version: "2.20.0" } } },
Expand Down
45 changes: 21 additions & 24 deletions src/cli/output-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,31 @@ import path from "path";

import { getTemporaryDirectory } from "../actions-util";
import { Env } from "../environment";
import * as json from "../json";
import { Logger } from "../logging";

import type { VersionInfo } from "./types";
import { VersionInfo, versionInfoBaseSchema } from "./types";

/**
* The keys of the command cache. Each key corresponds to a command whose output we cache.
*/
export type CommandCacheKey = string;

/**
* The type of the command cache that is persisted to disk.
* The JSON schema of the command cache that is persisted to disk.
*/
export interface OutputCache {
cmd: string;
entries: Record<CommandCacheKey, unknown>;
}
const outputCacheSchema = {
cmd: json.string,
entries: json.object({}),
} as const satisfies json.Schema;

/**
* The type that describes the command cache that is persisted to disk. This type
* is partially derived from {@link outputCacheSchema}.
*/
export type OutputCache = json.FromSchema<typeof outputCacheSchema> & {
entries: { version: VersionInfo };
};

/**
* The name of the temporary file that backs the on-disk cache of
Expand All @@ -43,7 +52,7 @@ export function resetCachedCodeQlVersion(): void {
* Returns the path to the temporary file that backs the
* on-disk cache of CLI responses between workflow steps.
*/
function getCommandCacheFilePath(env: Env): string {
export function getCommandCacheFilePath(env: Env): string {
return path.join(getTemporaryDirectory(env), COMMAND_CACHE_FILENAME);
}

Expand Down Expand Up @@ -127,30 +136,18 @@ export function getCachedCodeQlVersion(
* @param x The value to test
*/
function isVersionInfo(x: unknown): x is VersionInfo {
const candidate = x as Partial<VersionInfo> | null;
return (
typeof candidate === "object" &&
candidate !== null &&
typeof candidate.version === "string" &&
(candidate.features === undefined ||
(typeof candidate.features === "object" &&
candidate.features !== null)) &&
(candidate.overlayVersion === undefined ||
typeof candidate.overlayVersion === "number")
);
return json.isObject(x) && json.validateSchema(versionInfoBaseSchema, x);
}

/**
* Determines whether a value is a `OutputCache` object.
* @param x The value to test
*/
function isOutputCache(x: unknown): x is OutputCache {
const candidate = x as Partial<OutputCache> | null;
return (
typeof candidate === "object" &&
candidate !== null &&
typeof candidate.cmd === "string" &&
candidate.entries !== undefined &&
isVersionInfo(candidate.entries.version)
json.isObject(x) &&
json.validateSchema(outputCacheSchema, x) &&
json.isObject<{ version: unknown }>(x.entries) &&
isVersionInfo(x.entries.version)
);
}
31 changes: 26 additions & 5 deletions src/cli/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
export interface VersionInfo {
version: string;
features?: { [name: string]: boolean };
import * as json from "../json";

/**
* The JSON schema of the expected output of the `codeql version` command.
*/
export const versionInfoBaseSchema = {
version: json.string,
features: json.optional(json.object({})),
/**
* The overlay version helps deal with backward incompatible changes for
* overlay analysis. When a precompiled query pack reports the same overlay
Expand All @@ -9,5 +14,21 @@ export interface VersionInfo {
* or if either the pack or the CLI does not report an overlay version,
* we need to revert to non-overlay analysis.
*/
overlayVersion?: number;
}
overlayVersion: json.optional(json.number),
} as const satisfies json.Schema;

/**
* The base type that describes the expected output of the `codeql version` command.
* This type is partially derived from {@link versionInfoBaseSchema}.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* This type is partially derived from {@link versionInfoBaseSchema}.
* This type is derived from {@link versionInfoBaseSchema}.

But really, you can just remove this sentence.

*/
export type VersionInfoBase = json.FromSchema<typeof versionInfoBaseSchema>;

/**
* The full type that describes the expected output of the `codeql version` command.
* This type is partially derived from {@link VersionInfoBase}.
*/
export type VersionInfo = VersionInfoBase & {
// `features` remains optional, but the more specific type takes precedence
// over the `any` type derived by `FromSchema`.
features?: { [name: string]: boolean };
};
Loading