Merge remote-tracking branch 'origin/main' into koesie10/filter-vscode-output

This commit is contained in:
Koen Vlaswinkel
2023-02-09 09:48:24 +00:00
15 changed files with 508 additions and 219 deletions

View File

@@ -94,6 +94,23 @@ jobs:
asset_name: ${{ format('vscode-codeql-{0}.vsix', steps.prepare-artifacts.outputs.ref_name) }}
asset_content_type: application/zip
- name: Create sourcemap ZIP file
run: |
cd dist/vscode-codeql/out
zip -r ../../vscode-codeql-sourcemaps.zip *.map
- name: Upload sourcemap ZIP file
uses: actions/upload-release-asset@v1.0.1
if: success()
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
# Get the `upload_url` from the `create-release` step above.
upload_url: ${{ steps.create-release.outputs.upload_url }}
asset_path: dist/vscode-codeql-sourcemaps.zip
asset_name: ${{ format('vscode-codeql-sourcemaps-{0}.zip', steps.prepare-artifacts.outputs.ref_name) }}
asset_content_type: application/zip
###
# Do Post release work: version bump and changelog PR
# Only do this if we are running from a PR (ie- this is part of the release process)

View File

@@ -228,6 +228,13 @@ Pre-recorded scenarios are stored in `./src/mocks/scenarios`. However, it's poss
1. Double-check the `CHANGELOG.md` contains all desired change comments and has the version to be released with date at the top.
* Go through all recent PRs and make sure they are properly accounted for.
* Make sure all changelog entries have links back to their PR(s) if appropriate.
* For picking the new version number, we default to increasing the patch version number, but make our own judgement about whether a change is big enough to warrant a minor version bump. Common reasons for a minor bump could include:
* Making substantial new features available to all users. This can include lifting a feature flag.
* Breakage in compatibility with recent versions of the CLI.
* Minimum required version of VS Code is increased.
* New telemetry events are added.
* Deprecation or removal of commands.
* Accumulation of many changes, none of which are individually big enough to warrant a minor bump, but which together are. This does not include changes which are purely internal to the extension, such as refactoring, or which are only available behind a feature flag.
1. Double-check that the node version we're using matches the one used for VS Code. If it doesn't, you will then need to update the node version in the following files:
* `.nvmrc` - this will enable `nvm` to automatically switch to the correct node version when you're in the project folder
* `.github/workflows/main.yml` - all the "node-version: <version>" settings

View File

@@ -0,0 +1,241 @@
/**
* This scripts helps finding the original source file and line number for a
* given file and line number in the compiled extension. It currently only
* works with released extensions.
*
* Usage: npx ts-node scripts/source-map.ts <version-number> <filename>:<line>:<column>
* For example: npx ts-node scripts/source-map.ts v1.7.8 "/Users/user/.vscode/extensions/github.vscode-codeql-1.7.8/out/extension.js:131164:13"
*
* Alternative usage: npx ts-node scripts/source-map.ts <version-number> <multi-line-stacktrace>
* For example: npx ts-node scripts/source-map.ts v1.7.8 'Error: Failed to find CodeQL distribution.
* at CodeQLCliServer.getCodeQlPath (/Users/user/.vscode/extensions/github.vscode-codeql-1.7.8/out/extension.js:131164:13)
* at CodeQLCliServer.launchProcess (/Users/user/.vscode/extensions/github.vscode-codeql-1.7.8/out/extension.js:131169:24)
* at CodeQLCliServer.runCodeQlCliInternal (/Users/user/.vscode/extensions/github.vscode-codeql-1.7.8/out/extension.js:131194:24)
* at CodeQLCliServer.runJsonCodeQlCliCommand (/Users/user/.vscode/extensions/github.vscode-codeql-1.7.8/out/extension.js:131330:20)
* at CodeQLCliServer.resolveRam (/Users/user/.vscode/extensions/github.vscode-codeql-1.7.8/out/extension.js:131455:12)
* at QueryServerClient2.startQueryServerImpl (/Users/user/.vscode/extensions/github.vscode-codeql-1.7.8/out/extension.js:138618:21)'
*/
import { spawnSync } from "child_process";
import { basename, resolve } from "path";
import { pathExists, readJSON } from "fs-extra";
import { RawSourceMap, SourceMapConsumer } from "source-map";
import { Open } from "unzipper";
if (process.argv.length !== 4) {
console.error(
"Expected 2 arguments - the version number and the filename:line number",
);
}
const stackLineRegex =
/at (?<name>.*)? \((?<file>.*):(?<line>\d+):(?<column>\d+)\)/gm;
const versionNumber = process.argv[2].startsWith("v")
? process.argv[2]
: `v${process.argv[2]}`;
const stacktrace = process.argv[3];
async function extractSourceMap() {
const releaseAssetsDirectory = resolve(
__dirname,
"..",
"release-assets",
versionNumber,
);
const sourceMapsDirectory = resolve(
__dirname,
"..",
"artifacts",
"source-maps",
versionNumber,
);
if (!(await pathExists(sourceMapsDirectory))) {
console.log("Downloading source maps...");
const release = runGhJSON<Release>([
"release",
"view",
versionNumber,
"--json",
"id,name,assets",
]);
const sourcemapAsset = release.assets.find(
(asset) => asset.name === `vscode-codeql-sourcemaps-${versionNumber}.zip`,
);
if (sourcemapAsset) {
// This downloads a ZIP file of the source maps
runGh([
"release",
"download",
versionNumber,
"--pattern",
sourcemapAsset.name,
"--dir",
releaseAssetsDirectory,
]);
const file = await Open.file(
resolve(releaseAssetsDirectory, sourcemapAsset.name),
);
await file.extract({ path: sourceMapsDirectory });
} else {
const workflowRuns = runGhJSON<WorkflowRunListItem[]>([
"run",
"list",
"--workflow",
"release.yml",
"--branch",
versionNumber,
"--json",
"databaseId,number",
]);
if (workflowRuns.length !== 1) {
throw new Error(
`Expected exactly one workflow run for ${versionNumber}, got ${workflowRuns.length}`,
);
}
const workflowRun = workflowRuns[0];
runGh([
"run",
"download",
workflowRun.databaseId.toString(),
"--name",
"vscode-codeql-sourcemaps",
"--dir",
sourceMapsDirectory,
]);
}
}
if (stacktrace.includes("at")) {
const rawSourceMaps = new Map<string, RawSourceMap>();
const mappedStacktrace = await replaceAsync(
stacktrace,
stackLineRegex,
async (match, name, file, line, column) => {
if (!rawSourceMaps.has(file)) {
const rawSourceMap: RawSourceMap = await readJSON(
resolve(sourceMapsDirectory, `${basename(file)}.map`),
);
rawSourceMaps.set(file, rawSourceMap);
}
const originalPosition = await SourceMapConsumer.with(
rawSourceMaps.get(file) as RawSourceMap,
null,
async function (consumer) {
return consumer.originalPositionFor({
line: parseInt(line, 10),
column: parseInt(column, 10),
});
},
);
if (!originalPosition.source) {
return match;
}
const originalFilename = resolve(file, "..", originalPosition.source);
return `at ${originalPosition.name ?? name} (${originalFilename}:${
originalPosition.line
}:${originalPosition.column})`;
},
);
console.log(mappedStacktrace);
} else {
// This means it's just a filename:line:column
const [filename, line, column] = stacktrace.split(":", 3);
const fileBasename = basename(filename);
const sourcemapName = `${fileBasename}.map`;
const sourcemapPath = resolve(sourceMapsDirectory, sourcemapName);
if (!(await pathExists(sourcemapPath))) {
throw new Error(`No source map found for ${fileBasename}`);
}
const rawSourceMap: RawSourceMap = await readJSON(sourcemapPath);
const originalPosition = await SourceMapConsumer.with(
rawSourceMap,
null,
async function (consumer) {
return consumer.originalPositionFor({
line: parseInt(line, 10),
column: parseInt(column, 10),
});
},
);
if (!originalPosition.source) {
throw new Error(`No source found for ${stacktrace}`);
}
const originalFilename = resolve(filename, "..", originalPosition.source);
console.log(
`${originalFilename}:${originalPosition.line}:${originalPosition.column}`,
);
}
}
extractSourceMap().catch((e: unknown) => {
console.error(e);
process.exit(2);
});
function runGh(args: readonly string[]): string {
const gh = spawnSync("gh", args);
if (gh.status !== 0) {
throw new Error(
`Failed to get the source map for ${versionNumber}: ${gh.stderr}`,
);
}
return gh.stdout.toString("utf-8");
}
function runGhJSON<T>(args: readonly string[]): T {
return JSON.parse(runGh(args));
}
type ReleaseAsset = {
id: string;
name: string;
};
type Release = {
id: string;
name: string;
assets: ReleaseAsset[];
};
type WorkflowRunListItem = {
databaseId: number;
number: number;
};
async function replaceAsync(
str: string,
regex: RegExp,
replacer: (substring: string, ...args: any[]) => Promise<string>,
) {
const promises: Array<Promise<string>> = [];
str.replace(regex, (match, ...args) => {
const promise = replacer(match, ...args);
promises.push(promise);
return match;
});
const data = await Promise.all(promises);
return str.replace(regex, () => data.shift() as string);
}

View File

@@ -56,15 +56,6 @@ export class Setting {
.getConfiguration(this.parent.qualifiedName)
.update(this.name, value, target);
}
inspect<T>(): InspectionResult<T> | undefined {
if (this.parent === undefined) {
throw new Error("Cannot update the value of a root setting.");
}
return workspace
.getConfiguration(this.parent.qualifiedName)
.inspect(this.name);
}
}
export interface InspectionResult<T> {

View File

@@ -1,5 +1,5 @@
[
"v2.12.1",
"v2.12.2",
"v2.11.6",
"v2.7.6",
"v2.8.5",

View File

@@ -8,23 +8,20 @@ import { dirname } from "path";
import fetch from "node-fetch";
import { DB_URL, dbLoc, setStoragePath, storagePath } from "./global.helper";
import * as tmp from "tmp";
import { getTestSetting } from "../test-config";
import { CUSTOM_CODEQL_PATH_SETTING } from "../../../src/config";
import { extensions, workspace } from "vscode";
import baseJestSetup from "../jest.setup";
export default baseJestSetup;
import { ConfigurationTarget, env, extensions, workspace } from "vscode";
import { beforeEachAction } from "../test-config";
// create an extension storage location
let removeStorage: tmp.DirResult["removeCallback"] | undefined;
beforeAll(async () => {
// Set the CLI version here before activation to ensure we don't accidentally try to download a cli
await getTestSetting(CUSTOM_CODEQL_PATH_SETTING)?.setInitialTestValue(
await beforeEachAction();
await CUSTOM_CODEQL_PATH_SETTING.updateValue(
process.env.CLI_PATH,
ConfigurationTarget.Workspace,
);
await getTestSetting(CUSTOM_CODEQL_PATH_SETTING)?.setup();
// ensure the test database is downloaded
mkdirpSync(dirname(dbLoc));
@@ -78,6 +75,17 @@ beforeAll(async () => {
await extensions.getExtension("GitHub.vscode-codeql")?.activate();
});
beforeEach(async () => {
jest.spyOn(env, "openExternal").mockResolvedValue(false);
await beforeEachAction();
await CUSTOM_CODEQL_PATH_SETTING.updateValue(
process.env.CLI_PATH,
ConfigurationTarget.Workspace,
);
});
// ensure extension is cleaned up.
afterAll(async () => {
// ensure temp directory is cleaned up.

View File

@@ -3,6 +3,7 @@ import { resolve } from "path";
import {
authentication,
commands,
ConfigurationTarget,
extensions,
QuickPickItem,
TextDocument,
@@ -12,7 +13,10 @@ import {
import { CodeQLExtensionInterface } from "../../../../src/extension";
import { MockGitHubApiServer } from "../../../../src/mocks/mock-gh-api-server";
import { mockConfiguration } from "../../utils/configuration-helpers";
import {
CANARY_FEATURES,
setRemoteControllerRepo,
} from "../../../../src/config";
jest.setTimeout(30_000);
@@ -36,17 +40,8 @@ describe("Variant Analysis Submission Integration", () => {
let showErrorMessageSpy: jest.SpiedFunction<typeof window.showErrorMessage>;
beforeEach(async () => {
mockConfiguration({
values: {
codeQL: {
canary: true,
},
"codeQL.variantAnalysis": {
liveResults: true,
controllerRepo: "github/vscode-codeql",
},
},
});
await CANARY_FEATURES.updateValue(true, ConfigurationTarget.Global);
await setRemoteControllerRepo("github/vscode-codeql");
jest.spyOn(authentication, "getSession").mockResolvedValue({
id: "test",

View File

@@ -1,10 +1,8 @@
import { env } from "vscode";
import { jestTestConfigHelper } from "./test-config";
import { beforeEachAction } from "./test-config";
(env as any).openExternal = () => {
/**/
};
beforeEach(async () => {
jest.spyOn(env, "openExternal").mockResolvedValue(false);
export default async function setupEnv() {
await jestTestConfigHelper();
}
await beforeEachAction();
});

View File

@@ -6,8 +6,13 @@ import {
QueryHistoryConfigListener,
QueryServerConfigListener,
} from "../../../src/config";
import { vscodeGetConfigurationMock } from "../test-config";
describe("config listeners", () => {
beforeEach(() => {
vscodeGetConfigurationMock.mockRestore();
});
interface TestConfig<T> {
clazz: new () => ConfigListener;
settings: Array<{

View File

@@ -13,7 +13,7 @@ import { DbTreeViewItem } from "../../../../src/databases/ui/db-tree-view-item";
import { ExtensionApp } from "../../../../src/common/vscode/vscode-app";
import { createMockExtensionContext } from "../../../factories/extension-context";
import { createDbConfig } from "../../../factories/db-config-factories";
import { mockConfiguration } from "../../utils/configuration-helpers";
import { setRemoteControllerRepo } from "../../../../src/config";
describe("db panel rendering nodes", () => {
const workspaceStoragePath = join(__dirname, "test-workspace-storage");
@@ -50,12 +50,8 @@ describe("db panel rendering nodes", () => {
});
describe("when controller repo is not set", () => {
mockConfiguration({
values: {
"codeQL.variantAnalysis": {
controllerRepo: undefined,
},
},
beforeEach(async () => {
await setRemoteControllerRepo(undefined);
});
it("should not have any items", async () => {
@@ -81,14 +77,8 @@ describe("db panel rendering nodes", () => {
});
describe("when controller repo is set", () => {
beforeEach(() => {
mockConfiguration({
values: {
"codeQL.variantAnalysis": {
controllerRepo: "github/codeql",
},
},
});
beforeEach(async () => {
await setRemoteControllerRepo("github/codeql");
});
it("should render default remote nodes when the config is empty", async () => {

View File

@@ -9,7 +9,7 @@ import { DbTreeViewItem } from "../../../../src/databases/ui/db-tree-view-item";
import { ExtensionApp } from "../../../../src/common/vscode/vscode-app";
import { createMockExtensionContext } from "../../../factories/extension-context";
import { createDbConfig } from "../../../factories/db-config-factories";
import { mockConfiguration } from "../../utils/configuration-helpers";
import { setRemoteControllerRepo } from "../../../../src/config";
describe("db panel", () => {
const workspaceStoragePath = join(__dirname, "test-workspace-storage");
@@ -40,13 +40,7 @@ describe("db panel", () => {
beforeEach(async () => {
await ensureDir(workspaceStoragePath);
mockConfiguration({
values: {
"codeQL.variantAnalysis": {
controllerRepo: "github/codeql",
},
},
});
await setRemoteControllerRepo("github/codeql");
});
afterEach(async () => {

View File

@@ -15,7 +15,7 @@ import {
import { ExtensionApp } from "../../../../src/common/vscode/vscode-app";
import { createMockExtensionContext } from "../../../factories/extension-context";
import { createDbConfig } from "../../../factories/db-config-factories";
import { mockConfiguration } from "../../utils/configuration-helpers";
import { setRemoteControllerRepo } from "../../../../src/config";
describe("db panel selection", () => {
const workspaceStoragePath = join(__dirname, "test-workspace-storage");
@@ -46,13 +46,7 @@ describe("db panel selection", () => {
beforeEach(async () => {
await ensureDir(workspaceStoragePath);
mockConfiguration({
values: {
"codeQL.variantAnalysis": {
controllerRepo: "github/codeql",
},
},
});
await setRemoteControllerRepo("github/codeql");
});
afterEach(async () => {

View File

@@ -13,6 +13,7 @@ import { UserCancellationException } from "../../../src/commandRunner";
import { ENABLE_TELEMETRY } from "../../../src/config";
import * as Config from "../../../src/config";
import { createMockExtensionContext } from "./index";
import { vscodeGetConfigurationMock } from "../test-config";
import { redactableError } from "../../../src/pure/errors";
// setting preferences can trigger lots of background activity
@@ -41,6 +42,8 @@ describe("telemetry reporting", () => {
>;
beforeEach(async () => {
vscodeGetConfigurationMock.mockRestore();
try {
// in case a previous test has accidentally activated this extension,
// need to disable it first.

View File

@@ -1,126 +1,225 @@
import {
ConfigurationScope,
ConfigurationTarget,
workspace,
WorkspaceConfiguration as VSCodeWorkspaceConfiguration,
} from "vscode";
import { readFileSync } from "fs-extra";
import { join } from "path";
import { ConfigurationTarget } from "vscode";
import { ALL_SETTINGS, InspectionResult, Setting } from "../../src/config";
class TestSetting<T> {
private initialSettingState: InspectionResult<T> | undefined;
function getIn(object: any, path: string): any {
const parts = path.split(".");
let current = object;
for (const part of parts) {
current = current[part];
if (current === undefined) {
return undefined;
}
}
return current;
}
constructor(
public readonly setting: Setting,
private initialTestValue: T | undefined = undefined,
) {}
function setIn(object: any, path: string, value: any): void {
const parts = path.split(".");
let current = object;
for (const part of parts.slice(0, -1)) {
if (current[part] === undefined) {
current[part] = {};
}
current = current[part];
}
current[parts[parts.length - 1]] = value;
}
public async get(): Promise<T | undefined> {
return this.setting.getValue();
interface WorkspaceConfiguration {
scope?: ConfigurationTarget;
get<T>(section: string | undefined, key: string): T | undefined;
has(section: string | undefined, key: string): boolean;
update(section: string | undefined, key: string, value: unknown): void;
}
class InMemoryConfiguration implements WorkspaceConfiguration {
private readonly values: Record<string, unknown> = {};
public constructor(public readonly scope: ConfigurationTarget) {}
public get<T>(section: string | undefined, key: string): T | undefined {
return getIn(this.values, this.getKey(section, key)) as T | undefined;
}
public async set(
value: T | undefined,
target: ConfigurationTarget = ConfigurationTarget.Global,
): Promise<void> {
await this.setting.updateValue(value, target);
public has(section: string | undefined, key: string): boolean {
return getIn(this.values, this.getKey(section, key)) !== undefined;
}
public async setInitialTestValue(value: T | undefined) {
this.initialTestValue = value;
public update(
section: string | undefined,
key: string,
value: unknown,
): void {
setIn(this.values, this.getKey(section, key), value);
}
public async initialSetup() {
this.initialSettingState = this.setting.inspect();
private getKey(section: string | undefined, key: string): string {
return section ? `${section}.${key}` : key;
}
}
// Unfortunately it's not well-documented how to check whether we can write to a workspace
// configuration. This is the best I could come up with. It only fails for initial test values
// which are not undefined.
if (this.initialSettingState?.workspaceValue !== undefined) {
await this.set(this.initialTestValue, ConfigurationTarget.Workspace);
}
if (this.initialSettingState?.workspaceFolderValue !== undefined) {
await this.set(
this.initialTestValue,
ConfigurationTarget.WorkspaceFolder,
);
}
class DefaultConfiguration implements WorkspaceConfiguration {
private readonly values: Record<string, unknown> = {};
await this.setup();
public constructor(configurations: Record<string, { default: unknown }>) {
for (const [section, config] of Object.entries(configurations)) {
setIn(this.values, section, config.default);
}
}
public async setup() {
await this.set(this.initialTestValue, ConfigurationTarget.Global);
public get<T>(section: string | undefined, key: string): T | undefined {
return getIn(this.values, this.getKey(section, key)) as T | undefined;
}
public async restoreToInitialValues() {
const state = this.setting.inspect();
public has(section: string | undefined, key: string): boolean {
return getIn(this.values, this.getKey(section, key)) !== undefined;
}
// We need to check the state of the setting before we restore it. This is less important for the global
// configuration target, but the workspace/workspace folder configuration might not even exist. If they
// don't exist, VSCode will error when trying to write the new value (even if that value is undefined).
if (state?.globalValue !== this.initialSettingState?.globalValue) {
await this.set(
this.initialSettingState?.globalValue,
ConfigurationTarget.Global,
);
public update(
_section: string | undefined,
_key: string,
_value: unknown,
): void {
throw new Error("Cannot update default configuration");
}
private getKey(section: string | undefined, key: string): string {
return section ? `${section}.${key}` : key;
}
}
class ChainedInMemoryConfiguration {
constructor(private readonly configurations: WorkspaceConfiguration[]) {}
public getConfiguration(target: ConfigurationTarget) {
const configuration = this.configurations.find(
(configuration) => configuration.scope === target,
);
if (configuration === undefined) {
throw new Error(`Unknown configuration target ${target}`);
}
if (state?.workspaceValue !== this.initialSettingState?.workspaceValue) {
await this.set(
this.initialSettingState?.workspaceValue,
ConfigurationTarget.Workspace,
);
}
if (
state?.workspaceFolderValue !==
this.initialSettingState?.workspaceFolderValue
) {
await this.set(
this.initialSettingState?.workspaceFolderValue,
ConfigurationTarget.WorkspaceFolder,
);
return configuration;
}
public get<T>(section: string | undefined, key: string): T | undefined {
for (const configuration of this.configurations) {
if (configuration.has(section, key)) {
return configuration.get(section, key);
}
}
return undefined;
}
public has(section: string | undefined, key: string): boolean {
return this.configurations.some((configuration) =>
configuration.has(section, key),
);
}
public update<T>(
section: string | undefined,
key: string,
value: T,
target: ConfigurationTarget,
): void {
const configuration = this.getConfiguration(target);
configuration.update(section, key, value);
}
}
// Public configuration keys are the ones defined in the package.json.
// These keys are documented in the settings page. Other keys are
// internal and not documented.
const PKG_CONFIGURATION: Record<string, any> =
(function initConfigurationKeys() {
// Note we are using synchronous file reads here. This is fine because
// we are in tests.
const pkg = JSON.parse(
readFileSync(join(__dirname, "../../package.json"), "utf-8"),
const packageConfiguration: Record<
string,
{
default: any | undefined;
}
> = (function initConfigurationKeys() {
// Note we are using synchronous file reads here. This is fine because
// we are in tests.
const pkg = JSON.parse(
readFileSync(join(__dirname, "../../package.json"), "utf-8"),
);
return pkg.contributes.configuration.properties;
})();
export const vsCodeGetConfiguration = workspace.getConfiguration;
export let vscodeGetConfigurationMock: jest.SpiedFunction<
typeof workspace.getConfiguration
>;
export const beforeEachAction = async () => {
const defaultConfiguration = new DefaultConfiguration(packageConfiguration);
const configuration = new ChainedInMemoryConfiguration([
new InMemoryConfiguration(ConfigurationTarget.WorkspaceFolder),
new InMemoryConfiguration(ConfigurationTarget.Workspace),
new InMemoryConfiguration(ConfigurationTarget.Global),
defaultConfiguration,
]);
vscodeGetConfigurationMock = jest
.spyOn(workspace, "getConfiguration")
.mockImplementation(
(
section?: string,
scope?: ConfigurationScope | null,
): VSCodeWorkspaceConfiguration => {
if (scope) {
throw new Error("Scope is not supported in tests");
}
return {
get(key: string, defaultValue?: unknown) {
return configuration.get(section, key) ?? defaultValue;
},
has(key: string) {
return configuration.has(section, key);
},
inspect(_key: string) {
throw new Error("inspect is not supported in tests");
},
async update(
key: string,
value: unknown,
configurationTarget?: ConfigurationTarget | boolean | null,
overrideInLanguage?: boolean,
) {
if (overrideInLanguage) {
throw new Error("overrideInLanguage is not supported in tests");
}
function getActualConfigurationTarget(): ConfigurationTarget {
if (
configurationTarget === undefined ||
configurationTarget === null
) {
return ConfigurationTarget.Global;
}
if (typeof configurationTarget === "boolean") {
return configurationTarget
? ConfigurationTarget.Workspace
: ConfigurationTarget.Global;
}
return configurationTarget;
}
const target = getActualConfigurationTarget();
configuration.update(section, key, value, target);
},
};
},
);
return pkg.contributes.configuration.properties;
})();
// The test settings are all settings in ALL_SETTINGS which don't have any children
// and are also not hidden settings like the codeQL.canary.
const TEST_SETTINGS = ALL_SETTINGS.filter(
(setting) =>
setting.qualifiedName in PKG_CONFIGURATION && !setting.hasChildren,
).map((setting) => new TestSetting(setting));
export const getTestSetting = (
setting: Setting,
): TestSetting<unknown> | undefined => {
return TEST_SETTINGS.find((testSetting) => testSetting.setting === setting);
};
export const jestTestConfigHelper = async () => {
// Read in all current settings
await Promise.all(TEST_SETTINGS.map((setting) => setting.initialSetup()));
beforeEach(async () => {
// Reset the settings to their initial values before each test
await Promise.all(TEST_SETTINGS.map((setting) => setting.setup()));
});
afterAll(async () => {
// Restore all settings to their default values after each test suite
// Only do this outside of CI since the sometimes hangs on CI.
if (process.env.CI !== "true") {
await Promise.all(
TEST_SETTINGS.map((setting) => setting.restoreToInitialValues()),
);
}
});
};

View File

@@ -1,53 +0,0 @@
import { workspace } from "vscode";
type MockConfigurationConfig = {
values: {
[section: string]: {
[scope: string]: any | (() => any);
};
};
};
export function mockConfiguration(config: MockConfigurationConfig) {
const originalGetConfiguration = workspace.getConfiguration;
jest
.spyOn(workspace, "getConfiguration")
.mockImplementation((section, scope) => {
const configuration = originalGetConfiguration(section, scope);
return {
get(key: string, defaultValue?: unknown) {
if (
section &&
config.values[section] &&
config.values[section][key]
) {
const value = config.values[section][key];
return typeof value === "function" ? value() : value;
}
return configuration.get(key, defaultValue);
},
has(key: string) {
return configuration.has(key);
},
inspect(key: string) {
return configuration.inspect(key);
},
update(
key: string,
value: unknown,
configurationTarget?: boolean,
overrideInLanguage?: boolean,
) {
return configuration.update(
key,
value,
configurationTarget,
overrideInLanguage,
);
},
};
});
}