Skip to content

Commit 21b6adb

Browse files
authored
Merge pull request #1575 from github/koesie10/reset-config
Reset VSCode configuration between tests
2 parents 7bdd452 + 726feb1 commit 21b6adb

File tree

4 files changed

+138
-37
lines changed

4 files changed

+138
-37
lines changed

extensions/ql-vscode/src/config.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { DistributionManager } from './distribution';
44
import { logger } from './logging';
55
import { ONE_DAY_IN_MS } from './pure/time';
66

7+
export const ALL_SETTINGS: Setting[] = [];
8+
79
/** Helper class to look up a labelled (and possibly nested) setting. */
810
export class Setting {
911
name: string;
@@ -12,6 +14,7 @@ export class Setting {
1214
constructor(name: string, parent?: Setting) {
1315
this.name = name;
1416
this.parent = parent;
17+
ALL_SETTINGS.push(this);
1518
}
1619

1720
get qualifiedName(): string {
@@ -36,6 +39,18 @@ export class Setting {
3639
return workspace.getConfiguration(this.parent.qualifiedName).update(this.name, value, target);
3740
}
3841

42+
inspect<T>(): InspectionResult<T> | undefined {
43+
if (this.parent === undefined) {
44+
throw new Error('Cannot update the value of a root setting.');
45+
}
46+
return workspace.getConfiguration(this.parent.qualifiedName).inspect(this.name);
47+
}
48+
}
49+
50+
export interface InspectionResult<T> {
51+
globalValue?: T;
52+
workspaceValue?: T,
53+
workspaceFolderValue?: T,
3954
}
4055

4156
const ROOT_SETTING = new Setting('codeQL');

extensions/ql-vscode/src/vscode-tests/cli-integration/global.helper.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import * as fs from 'fs-extra';
44
import fetch from 'node-fetch';
55

66
import { fail } from 'assert';
7-
import { commands, ConfigurationTarget, extensions, workspace } from 'vscode';
7+
import { commands, extensions, workspace } from 'vscode';
88
import { CodeQLExtensionInterface } from '../../extension';
99
import { DatabaseManager } from '../../databases';
10+
import { getTestSetting } from '../test-config';
11+
import { CUSTOM_CODEQL_PATH_SETTING } from '../../config';
1012

1113
// This file contains helpers shared between actual tests.
1214

@@ -58,7 +60,7 @@ export default function(mocha: Mocha) {
5860
// Set the CLI version here before activation to ensure we don't accidentally try to download a cli
5961
(mocha.options as any).globalSetup.push(
6062
async () => {
61-
await workspace.getConfiguration().update('codeQL.cli.executablePath', process.env.CLI_PATH, ConfigurationTarget.Global);
63+
await getTestSetting(CUSTOM_CODEQL_PATH_SETTING)?.setInitialTestValue(process.env.CLI_PATH);
6264
}
6365
);
6466

extensions/ql-vscode/src/vscode-tests/index-template.ts

Lines changed: 34 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import * as path from 'path';
22
import * as Mocha from 'mocha';
3-
import * as glob from 'glob';
3+
import * as glob from 'glob-promise';
44
import { ensureCli } from './ensureCli';
55
import { env } from 'vscode';
6+
import { testConfigHelper } from './test-config';
67

78

89
// Use this handler to avoid swallowing unhandled rejections.
@@ -57,44 +58,42 @@ export async function runTestsInDirectory(testsRoot: string, useCli = false): Pr
5758

5859
await ensureCli(useCli);
5960

60-
return new Promise((resolve, reject) => {
61-
console.log(`Adding test cases and helpers from ${testsRoot}`);
62-
glob('**/**.js', { cwd: testsRoot }, (err, files) => {
63-
if (err) {
64-
return reject(err);
65-
}
61+
console.log(`Adding test cases and helpers from ${testsRoot}`);
62+
63+
const files = await glob('**/**.js', { cwd: testsRoot });
64+
65+
// Add test files to the test suite
66+
files
67+
.filter(f => f.endsWith('.test.js'))
68+
.forEach(f => {
69+
console.log(`Adding test file ${f}`);
70+
mocha.addFile(path.resolve(testsRoot, f));
71+
});
6672

67-
try {
68-
// Add test files to the test suite
69-
files
70-
.filter(f => f.endsWith('.test.js'))
71-
.forEach(f => {
72-
console.log(`Adding test file ${f}`);
73-
mocha.addFile(path.resolve(testsRoot, f));
74-
});
73+
// Setup the config helper. This needs to run before other helpers so any config they setup
74+
// is restored.
75+
await testConfigHelper(mocha);
7576

76-
// Add helpers. Helper files add global setup and teardown blocks
77-
// for a test run.
78-
files
79-
.filter(f => f.endsWith('.helper.js'))
80-
.forEach(f => {
81-
console.log(`Executing helper ${f}`);
82-
// eslint-disable-next-line @typescript-eslint/no-var-requires
83-
const helper = require(path.resolve(testsRoot, f)).default;
84-
helper(mocha);
85-
});
77+
// Add helpers. Helper files add global setup and teardown blocks
78+
// for a test run.
79+
files
80+
.filter(f => f.endsWith('.helper.js'))
81+
.forEach(f => {
82+
console.log(`Executing helper ${f}`);
83+
// eslint-disable-next-line @typescript-eslint/no-var-requires
84+
const helper = require(path.resolve(testsRoot, f)).default;
85+
helper(mocha);
86+
});
8687

87-
// Run the mocha test
88-
mocha.run(failures => {
89-
if (failures > 0) {
90-
reject(new Error(`${failures} tests failed.`));
91-
} else {
92-
resolve();
93-
}
94-
});
95-
} catch (err) {
96-
reject(err);
88+
return new Promise((resolve, reject) => {
89+
// Run the mocha test
90+
mocha.run(failures => {
91+
if (failures > 0) {
92+
reject(new Error(`${failures} tests failed.`));
93+
return;
9794
}
95+
96+
resolve();
9897
});
9998
});
10099
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { ConfigurationTarget } from 'vscode';
2+
import { ALL_SETTINGS, InspectionResult, Setting } from '../config';
3+
4+
class TestSetting<T> {
5+
private initialSettingState: InspectionResult<T> | undefined;
6+
7+
constructor(
8+
public readonly setting: Setting,
9+
private initialTestValue: T | undefined = undefined
10+
) { }
11+
12+
public async get(): Promise<T | undefined> {
13+
return this.setting.getValue();
14+
}
15+
16+
public async set(value: T | undefined, target: ConfigurationTarget = ConfigurationTarget.Global): Promise<void> {
17+
await this.setting.updateValue(value, target);
18+
}
19+
20+
public async setInitialTestValue(value: T | undefined) {
21+
this.initialTestValue = value;
22+
}
23+
24+
public async initialSetup() {
25+
this.initialSettingState = this.setting.inspect();
26+
27+
// Unfortunately it's not well-documented how to check whether we can write to a workspace
28+
// configuration. This is the best I could come up with. It only fails for initial test values
29+
// which are not undefined.
30+
if (this.initialSettingState?.workspaceValue !== undefined) {
31+
await this.set(this.initialTestValue, ConfigurationTarget.Workspace);
32+
}
33+
if (this.initialSettingState?.workspaceFolderValue !== undefined) {
34+
await this.set(this.initialTestValue, ConfigurationTarget.WorkspaceFolder);
35+
}
36+
37+
await this.setup();
38+
}
39+
40+
public async setup() {
41+
await this.set(this.initialTestValue, ConfigurationTarget.Global);
42+
}
43+
44+
public async restoreToInitialValues() {
45+
const state = this.setting.inspect();
46+
47+
// We need to check the state of the setting before we restore it. This is less important for the global
48+
// configuration target, but the workspace/workspace folder configuration might not even exist. If they
49+
// don't exist, VSCode will error when trying to write the new value (even if that value is undefined).
50+
if (state?.globalValue !== this.initialSettingState?.globalValue) {
51+
await this.set(this.initialSettingState?.globalValue, ConfigurationTarget.Global);
52+
}
53+
if (state?.workspaceValue !== this.initialSettingState?.workspaceValue) {
54+
await this.set(this.initialSettingState?.workspaceValue, ConfigurationTarget.Workspace);
55+
}
56+
if (state?.workspaceFolderValue !== this.initialSettingState?.workspaceFolderValue) {
57+
await this.set(this.initialSettingState?.workspaceFolderValue, ConfigurationTarget.WorkspaceFolder);
58+
}
59+
}
60+
}
61+
62+
// The test settings are all settings in ALL_SETTINGS which don't have any children
63+
const TEST_SETTINGS = ALL_SETTINGS
64+
.filter(setting => ALL_SETTINGS.filter(s => s.parent === setting).length === 0)
65+
.map(setting => new TestSetting(setting));
66+
67+
export const getTestSetting = (setting: Setting): TestSetting<unknown> | undefined => {
68+
return TEST_SETTINGS.find(testSetting => testSetting.setting === setting);
69+
};
70+
71+
export const testConfigHelper = async (mocha: Mocha) => {
72+
// Read in all current settings
73+
await Promise.all(TEST_SETTINGS.map(setting => setting.initialSetup()));
74+
75+
mocha.rootHooks({
76+
async beforeEach() {
77+
// Reset the settings to their initial values before each test
78+
await Promise.all(TEST_SETTINGS.map(setting => setting.setup()));
79+
},
80+
async afterAll() {
81+
// Restore all settings to their default values after each test suite
82+
await Promise.all(TEST_SETTINGS.map(setting => setting.restoreToInitialValues()));
83+
}
84+
});
85+
};

0 commit comments

Comments
 (0)