Skip to content

Commit f67c9db

Browse files
committed
Restructure the tree in the Test Explorer View
With this change we display the tree based on the file system not based on ql-packs. We also merge test folders whose only child is another test folder. Resolves #595
1 parent c66fe07 commit f67c9db

4 files changed

Lines changed: 107 additions & 93 deletions

File tree

extensions/ql-vscode/src/cli.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,11 @@ export class CodeQLCliServer implements Disposable {
440440
const subcommandArgs = [
441441
testPath
442442
];
443-
return await this.runJsonCodeQlCliCommand<ResolvedTests>(['resolve', 'tests'], subcommandArgs, 'Resolving tests');
443+
return await this.runJsonCodeQlCliCommand<ResolvedTests>(
444+
['resolve', 'tests', '--strict-test-discovery'],
445+
subcommandArgs,
446+
'Resolving tests'
447+
);
444448
}
445449

446450
/**

extensions/ql-vscode/src/qltest-discovery.ts

Lines changed: 44 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as path from 'path';
2-
import { QLPackDiscovery, QLPack } from './qlpack-discovery';
2+
import { QLPackDiscovery } from './qlpack-discovery';
33
import { Discovery } from './discovery';
4-
import { EventEmitter, Event, Uri, RelativePattern, WorkspaceFolder, env, workspace } from 'vscode';
4+
import { EventEmitter, Event, Uri, RelativePattern, WorkspaceFolder, env } from 'vscode';
55
import { MultiFileSystemWatcher } from './vscode-utils/multi-file-system-watcher';
66
import { CodeQLCliServer } from './cli';
77

@@ -29,9 +29,8 @@ export abstract class QLTestNode {
2929
* A directory containing one or more QL tests or other test directories.
3030
*/
3131
export class QLTestDirectory extends QLTestNode {
32-
private _children: QLTestNode[] = [];
3332

34-
constructor(_path: string, _name: string) {
33+
constructor(_path: string, _name: string, private _children: QLTestNode[] = []) {
3534
super(_path, _name);
3635
}
3736

@@ -55,10 +54,23 @@ export class QLTestDirectory extends QLTestNode {
5554
}
5655

5756
public finish(): void {
57+
// remove empty directories
58+
this._children.filter(child =>
59+
child instanceof QLTestFile || child.children.length > 0
60+
);
5861
this._children.sort((a, b) => a.name.localeCompare(b.name, env.language));
59-
for (const child of this._children) {
62+
this._children.forEach((child, i) => {
6063
child.finish();
61-
}
64+
if (child.children?.length === 1 && child.children[0] instanceof QLTestDirectory) {
65+
// collapse children
66+
const replacement = new QLTestDirectory(
67+
child.children[0].path,
68+
child.name + ' / ' + child.children[0].name,
69+
Array.from(child.children[0].children)
70+
);
71+
this._children[i] = replacement;
72+
}
73+
});
6274
}
6375

6476
private createChildDirectory(name: string): QLTestDirectory {
@@ -98,12 +110,12 @@ interface QLTestDiscoveryResults {
98110
/**
99111
* The root test directory for each QL pack that contains tests.
100112
*/
101-
testDirectories: QLTestDirectory[];
113+
testDirectory: QLTestDirectory | undefined;
102114
/**
103115
* The list of file system paths to watch. If any of these paths changes, the discovery results
104116
* may be out of date.
105117
*/
106-
watchPaths: string[];
118+
watchPath: string;
107119
}
108120

109121
/**
@@ -112,7 +124,7 @@ interface QLTestDiscoveryResults {
112124
export class QLTestDiscovery extends Discovery<QLTestDiscoveryResults> {
113125
private readonly _onDidChangeTests = this.push(new EventEmitter<void>());
114126
private readonly watcher: MultiFileSystemWatcher = this.push(new MultiFileSystemWatcher());
115-
private _testDirectories: QLTestDirectory[] = [];
127+
private _testDirectory: QLTestDirectory | undefined;
116128

117129
constructor(
118130
private readonly qlPackDiscovery: QLPackDiscovery,
@@ -128,12 +140,16 @@ export class QLTestDiscovery extends Discovery<QLTestDiscoveryResults> {
128140
/**
129141
* Event to be fired when the set of discovered tests may have changed.
130142
*/
131-
public get onDidChangeTests(): Event<void> { return this._onDidChangeTests.event; }
143+
public get onDidChangeTests(): Event<void> {
144+
return this._onDidChangeTests.event;
145+
}
132146

133147
/**
134148
* The root test directory for each QL pack that contains tests.
135149
*/
136-
public get testDirectories(): QLTestDirectory[] { return this._testDirectories; }
150+
public get testDirectory(): QLTestDirectory | undefined {
151+
return this._testDirectory;
152+
}
137153

138154
private handleDidChangeQLPacks(): void {
139155
this.refresh();
@@ -146,70 +162,43 @@ export class QLTestDiscovery extends Discovery<QLTestDiscoveryResults> {
146162
}
147163

148164
protected async discover(): Promise<QLTestDiscoveryResults> {
149-
const testDirectories: QLTestDirectory[] = [];
150-
const watchPaths: string[] = [];
151-
const qlPacks = this.qlPackDiscovery.qlPacks;
152-
for (const qlPack of qlPacks) {
153-
//HACK: Assume that only QL packs whose name ends with '-tests' contain tests.
154-
if (this.isRelevantQlPack(qlPack)) {
155-
watchPaths.push(qlPack.uri.fsPath);
156-
const testPackage = await this.discoverTests(qlPack.uri.fsPath, qlPack.name);
157-
if (testPackage !== undefined) {
158-
testDirectories.push(testPackage);
159-
}
160-
}
161-
}
162-
163-
return { testDirectories, watchPaths };
165+
return {
166+
testDirectory: await this.discoverTests(),
167+
watchPath: this.workspaceFolder.uri.fsPath
168+
};
164169
}
165170

166171
protected update(results: QLTestDiscoveryResults): void {
167-
this._testDirectories = results.testDirectories;
172+
this._testDirectory = results.testDirectory;
168173

169174
// Watch for changes to any `.ql` or `.qlref` file in any of the QL packs that contain tests.
170175
this.watcher.clear();
171-
results.watchPaths.forEach(watchPath => {
172-
this.watcher.addWatch(new RelativePattern(watchPath, '**/*.{ql,qlref}'));
173-
});
176+
this.watcher.addWatch(new RelativePattern(results.watchPath, '**/*.{ql,qlref}'));
174177
this._onDidChangeTests.fire();
175178
}
176179

177-
/**
178-
* Only include qlpacks suffixed with '-tests' that are contained
179-
* within the provided workspace folder.
180-
*/
181-
private isRelevantQlPack(qlPack: QLPack): boolean {
182-
return qlPack.name.endsWith('-tests')
183-
&& workspace.getWorkspaceFolder(qlPack.uri)?.index === this.workspaceFolder.index;
184-
}
185-
186180
/**
187181
* Discover all QL tests in the specified directory and its subdirectories.
188-
* @param fullPath The full path of the test directory.
189-
* @param name The display name to use for the returned `TestDirectory` object.
190182
* @returns A `QLTestDirectory` object describing the contents of the directory, or `undefined` if
191183
* no tests were found.
192184
*/
193-
private async discoverTests(fullPath: string, name: string): Promise<QLTestDirectory | undefined> {
185+
private async discoverTests(): Promise<QLTestDirectory> {
186+
const fullPath = this.workspaceFolder.uri.fsPath;
187+
const name = this.workspaceFolder.name;
194188
const resolvedTests = (await this.cliServer.resolveTests(fullPath))
195189
.filter((testPath) => !QLTestDiscovery.ignoreTestPath(testPath));
196190

197-
if (resolvedTests.length === 0) {
198-
return undefined;
191+
const rootDirectory = new QLTestDirectory(fullPath, name);
192+
for (const testPath of resolvedTests) {
193+
const relativePath = path.normalize(path.relative(fullPath, testPath));
194+
const dirName = path.dirname(relativePath);
195+
const parentDirectory = rootDirectory.createDirectory(dirName);
196+
parentDirectory.addChild(new QLTestFile(testPath, path.basename(testPath)));
199197
}
200-
else {
201-
const rootDirectory = new QLTestDirectory(fullPath, name);
202-
for (const testPath of resolvedTests) {
203-
const relativePath = path.normalize(path.relative(fullPath, testPath));
204-
const dirName = path.dirname(relativePath);
205-
const parentDirectory = rootDirectory.createDirectory(dirName);
206-
parentDirectory.addChild(new QLTestFile(testPath, path.basename(testPath)));
207-
}
208198

209-
rootDirectory.finish();
199+
rootDirectory.finish();
210200

211-
return rootDirectory;
212-
}
201+
return rootDirectory;
213202
}
214203

215204
/**

extensions/ql-vscode/src/test-adapter.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -160,22 +160,22 @@ export class QLTestAdapter extends DisposableObject implements TestAdapter {
160160
private discoverTests(): void {
161161
this._tests.fire({ type: 'started' } as TestLoadStartedEvent);
162162

163-
const testDirectories = this.qlTestDiscovery.testDirectories;
164-
const children = testDirectories.map(
165-
testDirectory => QLTestAdapter.createTestSuiteInfo(testDirectory, testDirectory.name)
166-
);
167-
const testSuite: TestSuiteInfo = {
168-
type: 'suite',
169-
label: 'CodeQL',
170-
id: '.',
171-
children
172-
};
173-
163+
const testDirectory = this.qlTestDiscovery.testDirectory;
164+
let testSuite: TestSuiteInfo | undefined;
165+
if (testDirectory?.children.length) {
166+
const children = QLTestAdapter.createTestOrSuiteInfos(testDirectory.children);
167+
testSuite = {
168+
type: 'suite',
169+
label: 'CodeQL',
170+
id: testDirectory.path,
171+
children
172+
};
173+
}
174174
this._tests.fire({
175175
type: 'finished',
176-
suite: children.length > 0 ? testSuite : undefined
176+
suite: testSuite
177177
} as TestLoadFinishedEvent);
178-
}
178+
}
179179

180180
public async run(tests: string[]): Promise<void> {
181181
if (this.runningTask !== undefined) {
Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,61 @@
11
import 'vscode-test';
22
import 'mocha';
3-
import * as path from 'path';
4-
import { Uri, workspace } from 'vscode';
3+
import { Uri, WorkspaceFolder } from 'vscode';
54
import { expect } from 'chai';
65

76
import { QLTestDiscovery } from '../../qltest-discovery';
87

98
describe('qltest-discovery', () => {
10-
describe('isRelevantQlPack', () => {
11-
it('should check if a qlpack is relevant', () => {
12-
const qlTestDiscover: any = new QLTestDiscovery(
9+
describe('discoverTests', () => {
10+
it('should run discovery', async () => {
11+
const baseUri = Uri.parse('file:/a/b');
12+
const baseDir = baseUri.fsPath;
13+
const cDir = Uri.parse('file:/a/b/c').fsPath;
14+
const dFile = Uri.parse('file:/a/b/c/d.ql').fsPath;
15+
const eFile = Uri.parse('file:/a/b/c/e.ql').fsPath;
16+
const hDir = Uri.parse('file:/a/b/c/f/g/h').fsPath;
17+
const iFile = Uri.parse('file:/a/b/c/f/g/h/i.ql').fsPath;
18+
const qlTestDiscover = new QLTestDiscovery(
1319
{ onDidChangeQLPacks: () => ({}) } as any,
14-
{ index: 0 } as any,
15-
{} as any
20+
{
21+
uri: baseUri,
22+
name: 'My tests'
23+
} as unknown as WorkspaceFolder,
24+
{
25+
resolveTests() {
26+
return [
27+
Uri.parse('file:/a/b/c/d.ql').fsPath,
28+
Uri.parse('file:/a/b/c/e.ql').fsPath,
29+
Uri.parse('file:/a/b/c/f/g/h/i.ql').fsPath
30+
];
31+
}
32+
} as any
1633
);
1734

18-
const uri = workspace.workspaceFolders![0].uri;
19-
expect(qlTestDiscover.isRelevantQlPack({
20-
name: '-hucairz',
21-
uri
22-
})).to.be.false;
35+
const result = await (qlTestDiscover as any).discover();
36+
expect(result.watchPath).to.eq(baseDir);
37+
expect(result.testDirectory.path).to.eq(baseDir);
38+
expect(result.testDirectory.name).to.eq('My tests');
2339

24-
expect(qlTestDiscover.isRelevantQlPack({
25-
name: '-tests',
26-
uri: Uri.file('/a/b/')
27-
})).to.be.false;
40+
let children = result.testDirectory.children;
41+
expect(children[0].path).to.eq(cDir);
42+
expect(children[0].name).to.eq('c');
43+
expect(children.length).to.eq(1);
2844

29-
expect(qlTestDiscover.isRelevantQlPack({
30-
name: '-tests',
31-
uri
32-
})).to.be.true;
45+
children = children[0].children;
46+
expect(children[0].path).to.eq(dFile);
47+
expect(children[0].name).to.eq('d.ql');
48+
expect(children[1].path).to.eq(eFile);
49+
expect(children[1].name).to.eq('e.ql');
3350

34-
expect(qlTestDiscover.isRelevantQlPack({
35-
name: '-tests',
36-
uri: Uri.file(path.join(uri.fsPath, 'other'))
37-
})).to.be.true;
51+
// A merged foler
52+
expect(children[2].path).to.eq(hDir);
53+
expect(children[2].name).to.eq('f / g / h');
54+
expect(children.length).to.eq(3);
55+
56+
children = children[2].children;
57+
expect(children[0].path).to.eq(iFile);
58+
expect(children[0].name).to.eq('i.ql');
3859
});
3960
});
4061
});

0 commit comments

Comments
 (0)