mirror of
https://github.com/github/codeql-action.git
synced 2026-04-28 18:08:53 +00:00
Merge pull request #3477 from github/mbg/features/offline-features
This commit is contained in:
@@ -12,7 +12,7 @@ import {
|
||||
import { computeAutomationID } from "./api-client";
|
||||
import { EnvVar } from "./environment";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import { setupTests } from "./testing-utils";
|
||||
import { mockCCR, setupTests } from "./testing-utils";
|
||||
import { initializeEnvironment } from "./util";
|
||||
|
||||
setupTests(test);
|
||||
@@ -258,8 +258,8 @@ test("isDynamicWorkflow() returns true if event name is `dynamic`", (t) => {
|
||||
});
|
||||
|
||||
test("isCCR() returns true when expected", (t) => {
|
||||
process.env.GITHUB_EVENT_NAME = "dynamic";
|
||||
process.env[EnvVar.ANALYSIS_KEY] = "dynamic/copilot-pull-request-reviewer";
|
||||
mockCCR();
|
||||
|
||||
t.assert(isCCR());
|
||||
t.false(isDefaultSetup());
|
||||
});
|
||||
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
} from "./dependency-caching";
|
||||
import { getDiffInformedAnalysisBranches } from "./diff-informed-analysis-utils";
|
||||
import { EnvVar } from "./environment";
|
||||
import { Features } from "./feature-flags";
|
||||
import { initFeatures } from "./feature-flags";
|
||||
import { KnownLanguage } from "./languages";
|
||||
import { getActionsLogger, Logger } from "./logging";
|
||||
import { cleanupAndUploadOverlayBaseDatabaseToCache } from "./overlay-database-utils";
|
||||
@@ -293,7 +293,7 @@ async function run(startedAt: Date) {
|
||||
|
||||
util.checkActionVersion(actionsUtil.getActionVersion(), gitHubVersion);
|
||||
|
||||
const features = new Features(
|
||||
const features = initFeatures(
|
||||
gitHubVersion,
|
||||
repositoryNwo,
|
||||
actionsUtil.getTemporaryDirectory(),
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@ import { CodeQL, getCodeQL } from "./codeql";
|
||||
import * as configUtils from "./config-utils";
|
||||
import { DocUrl } from "./doc-url";
|
||||
import { EnvVar } from "./environment";
|
||||
import { Feature, featureConfig, Features } from "./feature-flags";
|
||||
import { Feature, featureConfig, initFeatures } from "./feature-flags";
|
||||
import { KnownLanguage, Language } from "./languages";
|
||||
import { Logger } from "./logging";
|
||||
import { getRepositoryNwo } from "./repository";
|
||||
@@ -117,7 +117,7 @@ export async function setupCppAutobuild(codeql: CodeQL, logger: Logger) {
|
||||
const featureName = "C++ automatic installation of dependencies";
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
const repositoryNwo = getRepositoryNwo();
|
||||
const features = new Features(
|
||||
const features = initFeatures(
|
||||
gitHubVersion,
|
||||
repositoryNwo,
|
||||
getTemporaryDirectory(),
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
shouldPerformDiffInformedAnalysis,
|
||||
exportedForTesting,
|
||||
} from "./diff-informed-analysis-utils";
|
||||
import { Feature, Features } from "./feature-flags";
|
||||
import { Feature, initFeatures } from "./feature-flags";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import { parseRepositoryNwo } from "./repository";
|
||||
import {
|
||||
@@ -63,7 +63,7 @@ const testShouldPerformDiffInformedAnalysis = test.macro({
|
||||
delete process.env.CODEQL_ACTION_DIFF_INFORMED_QUERIES;
|
||||
}
|
||||
|
||||
const features = new Features(
|
||||
const features = initFeatures(
|
||||
testCase.gitHubVersion,
|
||||
parseRepositoryNwo("github/example"),
|
||||
tmpDir,
|
||||
|
||||
+25
-110
@@ -1,33 +1,32 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
|
||||
import test, { ExecutionContext } from "ava";
|
||||
import test from "ava";
|
||||
|
||||
import * as defaults from "./defaults.json";
|
||||
import { EnvVar } from "./environment";
|
||||
import {
|
||||
Feature,
|
||||
featureConfig,
|
||||
FeatureEnablement,
|
||||
Features,
|
||||
FEATURE_FLAGS_FILE_NAME,
|
||||
FeatureConfig,
|
||||
FeatureWithoutCLI,
|
||||
} from "./feature-flags";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import { parseRepositoryNwo } from "./repository";
|
||||
import {
|
||||
setUpFeatureFlagTests,
|
||||
getFeatureIncludingCodeQlIfRequired,
|
||||
assertAllFeaturesUndefinedInApi,
|
||||
assertAllFeaturesHaveDefaultValues,
|
||||
} from "./feature-flags/testing-util";
|
||||
import {
|
||||
checkExpectedLogMessages,
|
||||
getRecordingLogger,
|
||||
initializeFeatures,
|
||||
LoggedMessage,
|
||||
mockCCR,
|
||||
mockCodeQLVersion,
|
||||
mockFeatureFlagApiEndpoint,
|
||||
setupActionsVars,
|
||||
setupTests,
|
||||
stubFeatureFlagApiEndpoint,
|
||||
} from "./testing-utils";
|
||||
import { ToolsFeature } from "./tools-features";
|
||||
import * as util from "./util";
|
||||
import { GitHubVariant, initializeEnvironment, withTmpDir } from "./util";
|
||||
|
||||
setupTests(test);
|
||||
@@ -36,11 +35,9 @@ test.beforeEach(() => {
|
||||
initializeEnvironment("1.2.3");
|
||||
});
|
||||
|
||||
const testRepositoryNwo = parseRepositoryNwo("github/example");
|
||||
|
||||
test(`All features use default values if running against GHES`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const loggedMessages = [];
|
||||
const features = setUpFeatureFlagTests(
|
||||
tmpDir,
|
||||
getRecordingLogger(loggedMessages),
|
||||
@@ -48,30 +45,9 @@ test(`All features use default values if running against GHES`, async (t) => {
|
||||
);
|
||||
|
||||
await assertAllFeaturesHaveDefaultValues(t, features);
|
||||
assertLoggedMessage(
|
||||
t,
|
||||
loggedMessages,
|
||||
checkExpectedLogMessages(t, loggedMessages, [
|
||||
"Not running against github.com. Using default values for all features.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test(`All features use default values if running in CCR`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const features = setUpFeatureFlagTests(
|
||||
tmpDir,
|
||||
getRecordingLogger(loggedMessages),
|
||||
);
|
||||
|
||||
process.env[EnvVar.ANALYSIS_KEY] = "dynamic/copilot-pull-request-reviewer";
|
||||
|
||||
await assertAllFeaturesHaveDefaultValues(t, features);
|
||||
assertLoggedMessage(
|
||||
t,
|
||||
loggedMessages,
|
||||
"Feature flags are not supported in Copilot Code Review. Using default values for all features.",
|
||||
);
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -553,79 +529,18 @@ test("non-legacy feature flags should not start with codeql_action_", async (t)
|
||||
}
|
||||
});
|
||||
|
||||
async function assertAllFeaturesHaveDefaultValues(
|
||||
t: ExecutionContext<unknown>,
|
||||
features: FeatureEnablement,
|
||||
) {
|
||||
for (const feature of Object.values(Feature)) {
|
||||
t.deepEqual(
|
||||
await getFeatureIncludingCodeQlIfRequired(features, feature),
|
||||
featureConfig[feature].defaultValue,
|
||||
);
|
||||
}
|
||||
}
|
||||
test("initFeatures returns a `Features` instance by default", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
t.is("Features", features.constructor.name);
|
||||
});
|
||||
});
|
||||
|
||||
function assertLoggedMessage(
|
||||
t: ExecutionContext<unknown>,
|
||||
loggedMessages: LoggedMessage[],
|
||||
expectedMessage: string,
|
||||
) {
|
||||
t.assert(
|
||||
loggedMessages.find(
|
||||
(v: LoggedMessage) => v.type === "debug" && v.message === expectedMessage,
|
||||
) !== undefined,
|
||||
);
|
||||
}
|
||||
test("initFeatures returns an `OfflineFeatures` instance in CCR", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
mockCCR();
|
||||
|
||||
function assertAllFeaturesUndefinedInApi(
|
||||
t: ExecutionContext<unknown>,
|
||||
loggedMessages: LoggedMessage[],
|
||||
) {
|
||||
for (const feature of Object.keys(featureConfig)) {
|
||||
t.assert(
|
||||
loggedMessages.find(
|
||||
(v) =>
|
||||
v.type === "debug" &&
|
||||
(v.message as string).includes(feature) &&
|
||||
(v.message as string).includes("undefined in API response"),
|
||||
) !== undefined,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function setUpFeatureFlagTests(
|
||||
tmpDir: string,
|
||||
logger = getRunnerLogger(true),
|
||||
gitHubVersion = { type: GitHubVariant.DOTCOM } as util.GitHubVersion,
|
||||
): FeatureEnablement {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
|
||||
return new Features(gitHubVersion, testRepositoryNwo, tmpDir, logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an argument to pass to `getValue` that if required includes a CodeQL object meeting the
|
||||
* minimum version or tool feature requirements specified by the feature.
|
||||
*/
|
||||
function getFeatureIncludingCodeQlIfRequired(
|
||||
features: FeatureEnablement,
|
||||
feature: Feature,
|
||||
) {
|
||||
const config = featureConfig[
|
||||
feature
|
||||
] satisfies FeatureConfig as FeatureConfig;
|
||||
if (
|
||||
config.minimumVersion === undefined &&
|
||||
config.toolsFeature === undefined
|
||||
) {
|
||||
return features.getValue(feature as FeatureWithoutCLI);
|
||||
}
|
||||
|
||||
return features.getValue(
|
||||
feature,
|
||||
mockCodeQLVersion(
|
||||
"9.9.9",
|
||||
Object.fromEntries(Object.values(ToolsFeature).map((v) => [v, true])),
|
||||
),
|
||||
);
|
||||
}
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
t.is("OfflineFeatures", features.constructor.name);
|
||||
});
|
||||
});
|
||||
|
||||
+125
-62
@@ -373,51 +373,60 @@ type GitHubFeatureFlagsApiResponse = Partial<Record<Feature, boolean>>;
|
||||
export const FEATURE_FLAGS_FILE_NAME = "cached-feature-flags.json";
|
||||
|
||||
/**
|
||||
* Determines the enablement status of a number of features.
|
||||
* If feature enablement is not able to be determined locally, a request to the
|
||||
* GitHub API is made to determine the enablement status.
|
||||
* Determines the enablement status of a number of features locally without
|
||||
* consulting the GitHub API.
|
||||
*/
|
||||
export class Features implements FeatureEnablement {
|
||||
private gitHubFeatureFlags: GitHubFeatureFlags;
|
||||
|
||||
constructor(
|
||||
gitHubVersion: util.GitHubVersion,
|
||||
repositoryNwo: RepositoryNwo,
|
||||
tempDir: string,
|
||||
private readonly logger: Logger,
|
||||
) {
|
||||
this.gitHubFeatureFlags = new GitHubFeatureFlags(
|
||||
gitHubVersion,
|
||||
repositoryNwo,
|
||||
path.join(tempDir, FEATURE_FLAGS_FILE_NAME),
|
||||
logger,
|
||||
);
|
||||
}
|
||||
class OfflineFeatures implements FeatureEnablement {
|
||||
constructor(protected readonly logger: Logger) {}
|
||||
|
||||
async getDefaultCliVersion(
|
||||
variant: util.GitHubVariant,
|
||||
_variant: util.GitHubVariant,
|
||||
): Promise<CodeQLDefaultVersionInfo> {
|
||||
return await this.gitHubFeatureFlags.getDefaultCliVersion(variant);
|
||||
return {
|
||||
cliVersion: defaults.cliVersion,
|
||||
tagName: defaults.bundleVersion,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `FeatureConfig` for `feature`.
|
||||
*/
|
||||
getFeatureConfig(feature: Feature): FeatureConfig {
|
||||
// Narrow the type to FeatureConfig to avoid type errors. To avoid unsafe use of `as`, we
|
||||
// check that the required properties exist using `satisfies`.
|
||||
return featureConfig[feature] satisfies FeatureConfig as FeatureConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether `feature` is enabled without consulting the GitHub API.
|
||||
*
|
||||
* @param feature The feature to check.
|
||||
* @param codeql An optional CodeQL object. If provided, and a `minimumVersion` is specified for the
|
||||
* feature, the version of the CodeQL CLI will be checked against the minimum version.
|
||||
* If the version is less than the minimum version, the feature will be considered
|
||||
* disabled. If not provided, and a `minimumVersion` is specified for the feature, the
|
||||
* disabled. If not provided, and a `minimumVersion` is specified for the feature, then
|
||||
* this function will throw.
|
||||
* @returns true if the feature is enabled, false otherwise.
|
||||
*
|
||||
* @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided.
|
||||
*/
|
||||
async getValue(feature: Feature, codeql?: CodeQL): Promise<boolean> {
|
||||
// Narrow the type to FeatureConfig to avoid type errors. To avoid unsafe use of `as`, we
|
||||
// check that the required properties exist using `satisfies`.
|
||||
const config = featureConfig[
|
||||
feature
|
||||
] satisfies FeatureConfig as FeatureConfig;
|
||||
const offlineValue = await this.getOfflineValue(feature, codeql);
|
||||
if (offlineValue !== undefined) {
|
||||
return offlineValue;
|
||||
}
|
||||
|
||||
return this.getDefaultValue(feature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether `feature` is enabled using the CLI and environment variables.
|
||||
*/
|
||||
protected async getOfflineValue(
|
||||
feature: Feature,
|
||||
codeql?: CodeQL,
|
||||
): Promise<boolean | undefined> {
|
||||
const config = this.getFeatureConfig(feature);
|
||||
|
||||
if (!codeql && config.minimumVersion) {
|
||||
throw new Error(
|
||||
@@ -483,6 +492,68 @@ export class Features implements FeatureEnablement {
|
||||
return true;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** Gets the default value of `feature`. */
|
||||
protected async getDefaultValue(feature: Feature): Promise<boolean> {
|
||||
const config = this.getFeatureConfig(feature);
|
||||
const defaultValue = config.defaultValue;
|
||||
this.logger.debug(
|
||||
`Feature ${feature} is ${
|
||||
defaultValue ? "enabled" : "disabled"
|
||||
} due to its default value.`,
|
||||
);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the enablement status of a number of features.
|
||||
* If feature enablement is not able to be determined locally, a request to the
|
||||
* GitHub API is made to determine the enablement status.
|
||||
*/
|
||||
class Features extends OfflineFeatures {
|
||||
private gitHubFeatureFlags: GitHubFeatureFlags;
|
||||
|
||||
constructor(repositoryNwo: RepositoryNwo, tempDir: string, logger: Logger) {
|
||||
super(logger);
|
||||
|
||||
this.gitHubFeatureFlags = new GitHubFeatureFlags(
|
||||
repositoryNwo,
|
||||
path.join(tempDir, FEATURE_FLAGS_FILE_NAME),
|
||||
logger,
|
||||
);
|
||||
}
|
||||
|
||||
async getDefaultCliVersion(
|
||||
variant: util.GitHubVariant,
|
||||
): Promise<CodeQLDefaultVersionInfo> {
|
||||
if (supportsFeatureFlags(variant)) {
|
||||
return await this.gitHubFeatureFlags.getDefaultCliVersionFromFlags();
|
||||
}
|
||||
return super.getDefaultCliVersion(variant);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param feature The feature to check.
|
||||
* @param codeql An optional CodeQL object. If provided, and a `minimumVersion` is specified for the
|
||||
* feature, the version of the CodeQL CLI will be checked against the minimum version.
|
||||
* If the version is less than the minimum version, the feature will be considered
|
||||
* disabled. If not provided, and a `minimumVersion` is specified for the feature, then
|
||||
* this function will throw.
|
||||
* @returns true if the feature is enabled, false otherwise.
|
||||
*
|
||||
* @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided.
|
||||
*/
|
||||
async getValue(feature: Feature, codeql?: CodeQL): Promise<boolean> {
|
||||
// Check whether the feature is enabled locally.
|
||||
const offlineValue = await this.getOfflineValue(feature, codeql);
|
||||
if (offlineValue !== undefined) {
|
||||
return offlineValue;
|
||||
}
|
||||
|
||||
// Ask the GitHub API if the feature is enabled.
|
||||
const apiValue = await this.gitHubFeatureFlags.getValue(feature);
|
||||
if (apiValue !== undefined) {
|
||||
@@ -494,13 +565,8 @@ export class Features implements FeatureEnablement {
|
||||
return apiValue;
|
||||
}
|
||||
|
||||
const defaultValue = config.defaultValue;
|
||||
this.logger.debug(
|
||||
`Feature ${feature} is ${
|
||||
defaultValue ? "enabled" : "disabled"
|
||||
} due to its default value.`,
|
||||
);
|
||||
return defaultValue;
|
||||
// Return the default value.
|
||||
return this.getDefaultValue(feature);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,7 +578,6 @@ class GitHubFeatureFlags {
|
||||
private hasAccessedRemoteFeatureFlags: boolean;
|
||||
|
||||
constructor(
|
||||
private readonly gitHubVersion: util.GitHubVersion,
|
||||
private readonly repositoryNwo: RepositoryNwo,
|
||||
private readonly featureFlagsFile: string,
|
||||
private readonly logger: Logger,
|
||||
@@ -543,18 +608,6 @@ class GitHubFeatureFlags {
|
||||
return version;
|
||||
}
|
||||
|
||||
async getDefaultCliVersion(
|
||||
variant: util.GitHubVariant,
|
||||
): Promise<CodeQLDefaultVersionInfo> {
|
||||
if (supportsFeatureFlags(variant)) {
|
||||
return await this.getDefaultCliVersionFromFlags();
|
||||
}
|
||||
return {
|
||||
cliVersion: defaults.cliVersion,
|
||||
tagName: defaults.bundleVersion,
|
||||
};
|
||||
}
|
||||
|
||||
async getDefaultCliVersionFromFlags(): Promise<CodeQLDefaultVersionInfo> {
|
||||
const response = await this.getAllFeatures();
|
||||
|
||||
@@ -680,21 +733,6 @@ class GitHubFeatureFlags {
|
||||
}
|
||||
|
||||
private async loadApiResponse(): Promise<GitHubFeatureFlagsApiResponse> {
|
||||
// Do nothing when not running against github.com
|
||||
if (!supportsFeatureFlags(this.gitHubVersion.type)) {
|
||||
this.logger.debug(
|
||||
"Not running against github.com. Using default values for all features.",
|
||||
);
|
||||
this.hasAccessedRemoteFeatureFlags = false;
|
||||
return {};
|
||||
}
|
||||
if (isCCR()) {
|
||||
this.logger.debug(
|
||||
"Feature flags are not supported in Copilot Code Review. Using default values for all features.",
|
||||
);
|
||||
this.hasAccessedRemoteFeatureFlags = false;
|
||||
return {};
|
||||
}
|
||||
try {
|
||||
const featuresToRequest = Object.entries(featureConfig)
|
||||
.filter(
|
||||
@@ -764,3 +802,28 @@ function supportsFeatureFlags(githubVariant: util.GitHubVariant): boolean {
|
||||
githubVariant === util.GitHubVariant.GHEC_DR
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises an instance of a `FeatureEnablement` implementation. The implementation used
|
||||
* is determined by the environment we are running in.
|
||||
*/
|
||||
export function initFeatures(
|
||||
gitHubVersion: util.GitHubVersion,
|
||||
repositoryNwo: RepositoryNwo,
|
||||
tempDir: string,
|
||||
logger: Logger,
|
||||
): FeatureEnablement {
|
||||
if (!supportsFeatureFlags(gitHubVersion.type)) {
|
||||
logger.debug(
|
||||
"Not running against github.com. Using default values for all features.",
|
||||
);
|
||||
return new OfflineFeatures(logger);
|
||||
} else if (isCCR()) {
|
||||
logger.debug(
|
||||
"Querying feature flags is not currently supported in Copilot Code Review. Using offline data for all features.",
|
||||
);
|
||||
return new OfflineFeatures(logger);
|
||||
} else {
|
||||
return new Features(repositoryNwo, tempDir, logger);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import test from "ava";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import * as apiClient from "../api-client";
|
||||
import {
|
||||
checkExpectedLogMessages,
|
||||
getRecordingLogger,
|
||||
LoggedMessage,
|
||||
mockCCR,
|
||||
setupTests,
|
||||
} from "../testing-utils";
|
||||
import { initializeEnvironment, withTmpDir } from "../util";
|
||||
|
||||
import {
|
||||
assertAllFeaturesHaveDefaultValues,
|
||||
setUpFeatureFlagTests,
|
||||
} from "./testing-util";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
test.beforeEach(() => {
|
||||
initializeEnvironment("1.2.3");
|
||||
mockCCR();
|
||||
});
|
||||
|
||||
test("OfflineFeatures makes no API requests", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(loggedMessages);
|
||||
const features = setUpFeatureFlagTests(tmpDir, logger);
|
||||
t.is("OfflineFeatures", features.constructor.name);
|
||||
|
||||
sinon
|
||||
.stub(apiClient, "getApiClient")
|
||||
.throws(new Error("Should not have called getApiClient"));
|
||||
|
||||
await assertAllFeaturesHaveDefaultValues(t, features);
|
||||
checkExpectedLogMessages(t, loggedMessages, [
|
||||
"Querying feature flags is not currently supported in Copilot Code Review. Using offline data for all features.",
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,87 @@
|
||||
import { type ExecutionContext } from "ava";
|
||||
|
||||
import {
|
||||
Feature,
|
||||
featureConfig,
|
||||
FeatureConfig,
|
||||
FeatureEnablement,
|
||||
FeatureWithoutCLI,
|
||||
initFeatures,
|
||||
} from "../feature-flags";
|
||||
import { getRunnerLogger } from "../logging";
|
||||
import { parseRepositoryNwo } from "../repository";
|
||||
import {
|
||||
LoggedMessage,
|
||||
mockCodeQLVersion,
|
||||
setupActionsVars,
|
||||
} from "../testing-utils";
|
||||
import { ToolsFeature } from "../tools-features";
|
||||
import { GitHubVariant } from "../util";
|
||||
import * as util from "../util";
|
||||
|
||||
const testRepositoryNwo = parseRepositoryNwo("github/example");
|
||||
|
||||
export async function assertAllFeaturesHaveDefaultValues(
|
||||
t: ExecutionContext<unknown>,
|
||||
features: FeatureEnablement,
|
||||
) {
|
||||
for (const feature of Object.values(Feature)) {
|
||||
t.deepEqual(
|
||||
await getFeatureIncludingCodeQlIfRequired(features, feature),
|
||||
featureConfig[feature].defaultValue,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function assertAllFeaturesUndefinedInApi(
|
||||
t: ExecutionContext<unknown>,
|
||||
loggedMessages: LoggedMessage[],
|
||||
) {
|
||||
for (const feature of Object.keys(featureConfig)) {
|
||||
t.assert(
|
||||
loggedMessages.find(
|
||||
(v) =>
|
||||
v.type === "debug" &&
|
||||
(v.message as string).includes(feature) &&
|
||||
(v.message as string).includes("undefined in API response"),
|
||||
) !== undefined,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function setUpFeatureFlagTests(
|
||||
tmpDir: string,
|
||||
logger = getRunnerLogger(true),
|
||||
gitHubVersion = { type: GitHubVariant.DOTCOM } as util.GitHubVersion,
|
||||
): FeatureEnablement {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
|
||||
return initFeatures(gitHubVersion, testRepositoryNwo, tmpDir, logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an argument to pass to `getValue` that if required includes a CodeQL object meeting the
|
||||
* minimum version or tool feature requirements specified by the feature.
|
||||
*/
|
||||
export function getFeatureIncludingCodeQlIfRequired(
|
||||
features: FeatureEnablement,
|
||||
feature: Feature,
|
||||
) {
|
||||
const config = featureConfig[
|
||||
feature
|
||||
] satisfies FeatureConfig as FeatureConfig;
|
||||
if (
|
||||
config.minimumVersion === undefined &&
|
||||
config.toolsFeature === undefined
|
||||
) {
|
||||
return features.getValue(feature as FeatureWithoutCLI);
|
||||
}
|
||||
|
||||
return features.getValue(
|
||||
feature,
|
||||
mockCodeQLVersion(
|
||||
"9.9.9",
|
||||
Object.fromEntries(Object.values(ToolsFeature).map((v) => [v, true])),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
getDependencyCacheUsage,
|
||||
} from "./dependency-caching";
|
||||
import { EnvVar } from "./environment";
|
||||
import { Features } from "./feature-flags";
|
||||
import { initFeatures } from "./feature-flags";
|
||||
import * as gitUtils from "./git-utils";
|
||||
import * as initActionPostHelper from "./init-action-post-helper";
|
||||
import { getActionsLogger } from "./logging";
|
||||
@@ -62,7 +62,7 @@ async function run(startedAt: Date) {
|
||||
checkGitHubVersionInRange(gitHubVersion, logger);
|
||||
|
||||
const repositoryNwo = getRepositoryNwo();
|
||||
const features = new Features(
|
||||
const features = initFeatures(
|
||||
gitHubVersion,
|
||||
repositoryNwo,
|
||||
getTemporaryDirectory(),
|
||||
|
||||
+3
-3
@@ -38,7 +38,7 @@ import {
|
||||
makeTelemetryDiagnostic,
|
||||
} from "./diagnostics";
|
||||
import { EnvVar } from "./environment";
|
||||
import { Feature, FeatureEnablement, Features } from "./feature-flags";
|
||||
import { Feature, FeatureEnablement, initFeatures } from "./feature-flags";
|
||||
import {
|
||||
loadPropertiesFromApi,
|
||||
RepositoryProperties,
|
||||
@@ -211,7 +211,7 @@ async function run(startedAt: Date) {
|
||||
let config: configUtils.Config | undefined;
|
||||
let configFile: string | undefined;
|
||||
let codeql: CodeQL;
|
||||
let features: Features;
|
||||
let features: FeatureEnablement;
|
||||
let sourceRoot: string;
|
||||
let toolsDownloadStatusReport: ToolsDownloadStatusReport | undefined;
|
||||
let toolsFeatureFlagsValid: boolean | undefined;
|
||||
@@ -238,7 +238,7 @@ async function run(startedAt: Date) {
|
||||
|
||||
const repositoryNwo = getRepositoryNwo();
|
||||
|
||||
features = new Features(
|
||||
features = initFeatures(
|
||||
gitHubVersion,
|
||||
repositoryNwo,
|
||||
getTemporaryDirectory(),
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
import { getGitHubVersion } from "./api-client";
|
||||
import { CodeQL } from "./codeql";
|
||||
import { EnvVar } from "./environment";
|
||||
import { Features } from "./feature-flags";
|
||||
import { initFeatures } from "./feature-flags";
|
||||
import { initCodeQL } from "./init";
|
||||
import { getActionsLogger, Logger } from "./logging";
|
||||
import { getRepositoryNwo } from "./repository";
|
||||
@@ -114,7 +114,7 @@ async function run(startedAt: Date): Promise<void> {
|
||||
|
||||
const repositoryNwo = getRepositoryNwo();
|
||||
|
||||
const features = new Features(
|
||||
const features = initFeatures(
|
||||
gitHubVersion,
|
||||
repositoryNwo,
|
||||
getTemporaryDirectory(),
|
||||
|
||||
@@ -5,7 +5,7 @@ import * as core from "@actions/core";
|
||||
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import { getGitHubVersion } from "./api-client";
|
||||
import { Feature, Features } from "./feature-flags";
|
||||
import { Feature, FeatureEnablement, initFeatures } from "./feature-flags";
|
||||
import { KnownLanguage } from "./languages";
|
||||
import { getActionsLogger, Logger } from "./logging";
|
||||
import { getRepositoryNwo } from "./repository";
|
||||
@@ -32,7 +32,7 @@ async function run(startedAt: Date) {
|
||||
// possible, and only use safe functions outside.
|
||||
|
||||
const logger = getActionsLogger();
|
||||
let features: Features | undefined;
|
||||
let features: FeatureEnablement | undefined;
|
||||
let language: KnownLanguage | undefined;
|
||||
|
||||
try {
|
||||
@@ -47,7 +47,7 @@ async function run(startedAt: Date) {
|
||||
// Initialise FFs.
|
||||
const repositoryNwo = getRepositoryNwo();
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
features = new Features(
|
||||
features = initFeatures(
|
||||
gitHubVersion,
|
||||
repositoryNwo,
|
||||
actionsUtil.getTemporaryDirectory(),
|
||||
|
||||
@@ -14,6 +14,7 @@ import { CachingKind } from "./caching-utils";
|
||||
import * as codeql from "./codeql";
|
||||
import { Config } from "./config-utils";
|
||||
import * as defaults from "./defaults.json";
|
||||
import { EnvVar } from "./environment";
|
||||
import {
|
||||
CodeQLDefaultVersionInfo,
|
||||
Feature,
|
||||
@@ -504,3 +505,9 @@ export function makeTestToken(length: number = 36) {
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
return chars.repeat(Math.ceil(length / chars.length)).slice(0, length);
|
||||
}
|
||||
|
||||
/** Sets the environment variables needed for isCCR() to be `true`. */
|
||||
export function mockCCR() {
|
||||
process.env.GITHUB_EVENT_NAME = "dynamic";
|
||||
process.env[EnvVar.ANALYSIS_KEY] = "dynamic/copilot-pull-request-reviewer";
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import * as actionsUtil from "./actions-util";
|
||||
import { getActionVersion, getTemporaryDirectory } from "./actions-util";
|
||||
import * as analyses from "./analyses";
|
||||
import { getGitHubVersion } from "./api-client";
|
||||
import { Features } from "./feature-flags";
|
||||
import { initFeatures } from "./feature-flags";
|
||||
import { Logger, getActionsLogger } from "./logging";
|
||||
import { getRepositoryNwo } from "./repository";
|
||||
import {
|
||||
@@ -70,7 +70,7 @@ async function run(startedAt: Date) {
|
||||
actionsUtil.persistInputs();
|
||||
|
||||
const repositoryNwo = getRepositoryNwo();
|
||||
const features = new Features(
|
||||
const features = initFeatures(
|
||||
gitHubVersion,
|
||||
repositoryNwo,
|
||||
getTemporaryDirectory(),
|
||||
|
||||
Reference in New Issue
Block a user