Skip to content

Commit a27b0a4

Browse files
Merge pull request #2447 from github/robertbrignull/QueryDiscovery-tests
Add integration tests of QueryDiscovery
2 parents 7bbba38 + 472b176 commit a27b0a4

2 files changed

Lines changed: 236 additions & 7 deletions

File tree

extensions/ql-vscode/src/queries-panel/query-discovery.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { QueryDiscoverer } from "./query-tree-data-provider";
1212
/**
1313
* The results of discovering queries.
1414
*/
15-
interface QueryDiscoveryResults {
15+
export interface QueryDiscoveryResults {
1616
/**
1717
* A tree of directories and query files.
1818
* May have multiple roots because of multiple workspaces.
@@ -99,22 +99,26 @@ export class QueryDiscovery
9999
): Promise<FileTreeDirectory[]> {
100100
const rootDirectories = [];
101101
for (const workspaceFolder of workspaceFolders) {
102-
rootDirectories.push(
103-
await this.discoverQueriesInWorkspace(workspaceFolder),
104-
);
102+
const root = await this.discoverQueriesInWorkspace(workspaceFolder);
103+
if (root !== undefined) {
104+
rootDirectories.push(root);
105+
}
105106
}
106107
return rootDirectories;
107108
}
108109

109110
private async discoverQueriesInWorkspace(
110111
workspaceFolder: WorkspaceFolder,
111-
): Promise<FileTreeDirectory> {
112+
): Promise<FileTreeDirectory | undefined> {
112113
const fullPath = workspaceFolder.uri.fsPath;
113114
const name = workspaceFolder.name;
114115

115-
const rootDirectory = new FileTreeDirectory(fullPath, name);
116-
117116
const resolvedQueries = await this.cliServer.resolveQueries(fullPath);
117+
if (resolvedQueries.length === 0) {
118+
return undefined;
119+
}
120+
121+
const rootDirectory = new FileTreeDirectory(fullPath, name);
118122
for (const queryPath of resolvedQueries) {
119123
const relativePath = normalize(relative(fullPath, queryPath));
120124
const dirName = dirname(relativePath);
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
import {
2+
EventEmitter,
3+
FileSystemWatcher,
4+
Uri,
5+
WorkspaceFoldersChangeEvent,
6+
workspace,
7+
} from "vscode";
8+
import { CodeQLCliServer } from "../../../../src/codeql-cli/cli";
9+
import {
10+
QueryDiscovery,
11+
QueryDiscoveryResults,
12+
} from "../../../../src/queries-panel/query-discovery";
13+
import { createMockApp } from "../../../__mocks__/appMock";
14+
import { mockedObject } from "../../utils/mocking.helpers";
15+
import { basename, join, sep } from "path";
16+
import { sleep } from "../../../../src/pure/time";
17+
18+
describe("QueryDiscovery", () => {
19+
beforeEach(() => {
20+
expect(workspace.workspaceFolders?.length).toEqual(1);
21+
});
22+
23+
describe("queries", () => {
24+
it("should return empty list when no QL files are present", async () => {
25+
const resolveQueries = jest.fn().mockResolvedValue([]);
26+
const cli = mockedObject<CodeQLCliServer>({
27+
resolveQueries,
28+
});
29+
30+
const discovery = new QueryDiscovery(createMockApp({}), cli);
31+
const results: QueryDiscoveryResults = await (
32+
discovery as any
33+
).discover();
34+
35+
expect(results.queries).toEqual([]);
36+
expect(resolveQueries).toHaveBeenCalledTimes(1);
37+
});
38+
39+
it("should organise query files into directories", async () => {
40+
const workspaceRoot = workspace.workspaceFolders![0].uri.fsPath;
41+
const cli = mockedObject<CodeQLCliServer>({
42+
resolveQueries: jest
43+
.fn()
44+
.mockResolvedValue([
45+
join(workspaceRoot, "dir1/query1.ql"),
46+
join(workspaceRoot, "dir2/query2.ql"),
47+
join(workspaceRoot, "query3.ql"),
48+
]),
49+
});
50+
51+
const discovery = new QueryDiscovery(createMockApp({}), cli);
52+
const results: QueryDiscoveryResults = await (
53+
discovery as any
54+
).discover();
55+
56+
expect(results.queries[0].children.length).toEqual(3);
57+
expect(results.queries[0].children[0].name).toEqual("dir1");
58+
expect(results.queries[0].children[0].children.length).toEqual(1);
59+
expect(results.queries[0].children[0].children[0].name).toEqual(
60+
"query1.ql",
61+
);
62+
expect(results.queries[0].children[1].name).toEqual("dir2");
63+
expect(results.queries[0].children[1].children.length).toEqual(1);
64+
expect(results.queries[0].children[1].children[0].name).toEqual(
65+
"query2.ql",
66+
);
67+
expect(results.queries[0].children[2].name).toEqual("query3.ql");
68+
});
69+
70+
it("should collapse directories containing only a single element", async () => {
71+
const workspaceRoot = workspace.workspaceFolders![0].uri.fsPath;
72+
const cli = mockedObject<CodeQLCliServer>({
73+
resolveQueries: jest
74+
.fn()
75+
.mockResolvedValue([
76+
join(workspaceRoot, "dir1/query1.ql"),
77+
join(workspaceRoot, "dir1/dir2/dir3/dir3/query2.ql"),
78+
]),
79+
});
80+
81+
const discovery = new QueryDiscovery(createMockApp({}), cli);
82+
const results: QueryDiscoveryResults = await (
83+
discovery as any
84+
).discover();
85+
86+
expect(results.queries[0].children.length).toEqual(1);
87+
expect(results.queries[0].children[0].name).toEqual("dir1");
88+
expect(results.queries[0].children[0].children.length).toEqual(2);
89+
expect(results.queries[0].children[0].children[0].name).toEqual(
90+
"dir2 / dir3 / dir3",
91+
);
92+
expect(
93+
results.queries[0].children[0].children[0].children.length,
94+
).toEqual(1);
95+
expect(
96+
results.queries[0].children[0].children[0].children[0].name,
97+
).toEqual("query2.ql");
98+
expect(results.queries[0].children[0].children[1].name).toEqual(
99+
"query1.ql",
100+
);
101+
});
102+
103+
it("calls resolveQueries once for each workspace folder", async () => {
104+
const workspaceRoots = [
105+
`${sep}workspace1`,
106+
`${sep}workspace2`,
107+
`${sep}workspace3`,
108+
];
109+
jest.spyOn(workspace, "workspaceFolders", "get").mockReturnValueOnce(
110+
workspaceRoots.map((root, index) => ({
111+
uri: Uri.file(root),
112+
name: basename(root),
113+
index,
114+
})),
115+
);
116+
117+
const resolveQueries = jest.fn().mockImplementation((queryDir) => {
118+
const workspaceIndex = workspaceRoots.indexOf(queryDir);
119+
if (workspaceIndex === -1) {
120+
throw new Error("Unexpected workspace");
121+
}
122+
return Promise.resolve([
123+
join(queryDir, `query${workspaceIndex + 1}.ql`),
124+
]);
125+
});
126+
const cli = mockedObject<CodeQLCliServer>({
127+
resolveQueries,
128+
});
129+
130+
const discovery = new QueryDiscovery(createMockApp({}), cli);
131+
const results: QueryDiscoveryResults = await (
132+
discovery as any
133+
).discover();
134+
135+
expect(results.queries.length).toEqual(3);
136+
expect(results.queries[0].children[0].name).toEqual("query1.ql");
137+
expect(results.queries[1].children[0].name).toEqual("query2.ql");
138+
expect(results.queries[2].children[0].name).toEqual("query3.ql");
139+
140+
expect(resolveQueries).toHaveBeenCalledTimes(3);
141+
});
142+
});
143+
144+
describe("onDidChangeQueries", () => {
145+
it("should fire onDidChangeQueries when a watcher fires", async () => {
146+
const onWatcherDidChangeEvent = new EventEmitter<Uri>();
147+
const watcher: FileSystemWatcher = {
148+
ignoreCreateEvents: false,
149+
ignoreChangeEvents: false,
150+
ignoreDeleteEvents: false,
151+
onDidCreate: onWatcherDidChangeEvent.event,
152+
onDidChange: onWatcherDidChangeEvent.event,
153+
onDidDelete: onWatcherDidChangeEvent.event,
154+
dispose: () => undefined,
155+
};
156+
const createFileSystemWatcherSpy = jest.spyOn(
157+
workspace,
158+
"createFileSystemWatcher",
159+
);
160+
createFileSystemWatcherSpy.mockReturnValue(watcher);
161+
162+
const workspaceRoot = workspace.workspaceFolders![0].uri.fsPath;
163+
const cli = mockedObject<CodeQLCliServer>({
164+
resolveQueries: jest
165+
.fn()
166+
.mockResolvedValue([join(workspaceRoot, "query1.ql")]),
167+
});
168+
169+
const discovery = new QueryDiscovery(
170+
createMockApp({
171+
createEventEmitter: () => new EventEmitter(),
172+
}),
173+
cli,
174+
);
175+
176+
const onDidChangeQueriesSpy = jest.fn();
177+
discovery.onDidChangeQueries(onDidChangeQueriesSpy);
178+
179+
const results = await (discovery as any).discover();
180+
(discovery as any).update(results);
181+
182+
expect(createFileSystemWatcherSpy).toHaveBeenCalledTimes(2);
183+
expect(onDidChangeQueriesSpy).toHaveBeenCalledTimes(1);
184+
185+
onWatcherDidChangeEvent.fire(workspace.workspaceFolders![0].uri);
186+
187+
// Wait for refresh to finish
188+
await sleep(100);
189+
190+
expect(onDidChangeQueriesSpy).toHaveBeenCalledTimes(2);
191+
});
192+
});
193+
194+
describe("onDidChangeWorkspaceFolders", () => {
195+
it("should refresh when workspace folders change", async () => {
196+
const onDidChangeWorkspaceFoldersEvent =
197+
new EventEmitter<WorkspaceFoldersChangeEvent>();
198+
199+
const discovery = new QueryDiscovery(
200+
createMockApp({
201+
createEventEmitter: () => new EventEmitter(),
202+
onDidChangeWorkspaceFolders: onDidChangeWorkspaceFoldersEvent.event,
203+
}),
204+
mockedObject<CodeQLCliServer>({
205+
resolveQueries: jest.fn().mockResolvedValue([]),
206+
}),
207+
);
208+
209+
const onDidChangeQueriesSpy = jest.fn();
210+
discovery.onDidChangeQueries(onDidChangeQueriesSpy);
211+
212+
const results = await (discovery as any).discover();
213+
(discovery as any).update(results);
214+
215+
expect(onDidChangeQueriesSpy).toHaveBeenCalledTimes(1);
216+
217+
onDidChangeWorkspaceFoldersEvent.fire({ added: [], removed: [] });
218+
219+
// Wait for refresh to finish
220+
await sleep(100);
221+
222+
expect(onDidChangeQueriesSpy).toHaveBeenCalledTimes(2);
223+
});
224+
});
225+
});

0 commit comments

Comments
 (0)