Skip to content

Commit cb93c84

Browse files
author
Dave Bartolomeo
committed
Test tests
1 parent 397b585 commit cb93c84

4 files changed

Lines changed: 409 additions & 159 deletions

File tree

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,6 @@ class WorkspaceFolderHandler extends DisposableObject {
109109
* debugging of tests.
110110
*/
111111
export class TestManager extends TestManagerBase {
112-
private readonly testController: TestController = tests.createTestController(
113-
"codeql",
114-
"Fancy CodeQL Tests",
115-
);
116-
117112
/**
118113
* Maps from each workspace folder being tracked to the `WorkspaceFolderHandler` responsible for
119114
* tracking it.
@@ -127,6 +122,11 @@ export class TestManager extends TestManagerBase {
127122
app: App,
128123
private readonly testRunner: TestRunner,
129124
private readonly cliServer: CodeQLCliServer,
125+
// Having this as a parameter with a default value makes passing in a mock easier.
126+
private readonly testController: TestController = tests.createTestController(
127+
"codeql",
128+
"Fancy CodeQL Tests",
129+
),
130130
) {
131131
super(app);
132132

@@ -245,8 +245,10 @@ export class TestManager extends TestManagerBase {
245245

246246
/**
247247
* Run the tests specified by the `TestRunRequest` parameter.
248+
*
249+
* Public because this is used in unit tests.
248250
*/
249-
private async run(
251+
public async run(
250252
request: TestRunRequest,
251253
token: CancellationToken,
252254
): Promise<void> {
Lines changed: 116 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -1,212 +1,175 @@
1-
import { Uri, WorkspaceFolder } from "vscode";
1+
import {
2+
CancellationTokenSource,
3+
Range,
4+
TestItem,
5+
TestItemCollection,
6+
TestRun,
7+
TestRunRequest,
8+
Uri,
9+
WorkspaceFolder,
10+
tests,
11+
} from "vscode";
212

313
import { QLTestAdapter } from "../../../src/test-adapter";
414
import { CodeQLCliServer } from "../../../src/cli";
5-
import {
6-
DatabaseItem,
7-
DatabaseItemImpl,
8-
DatabaseManager,
9-
FullDatabaseOptions,
10-
} from "../../../src/local-databases";
15+
import { DatabaseManager } from "../../../src/local-databases";
1116
import { mockedObject } from "../utils/mocking.helpers";
1217
import { TestRunner } from "../../../src/test-runner";
18+
import {
19+
createMockCliServerForTestRun,
20+
mockEmptyDatabaseManager,
21+
mockTestsInfo,
22+
} from "./test-runner-helpers";
23+
import { TestManager } from "../../../src/test-manager";
24+
import { createMockApp } from "../../__mocks__/appMock";
1325

14-
jest.mock("fs-extra", () => {
15-
const original = jest.requireActual("fs-extra");
16-
return {
17-
...original,
18-
access: jest.fn(),
19-
};
20-
});
26+
type IdTestItemPair = [id: string, testItem: TestItem];
2127

2228
describe("test-adapter", () => {
2329
let testRunner: TestRunner;
24-
let adapter: QLTestAdapter;
2530
let fakeDatabaseManager: DatabaseManager;
2631
let fakeCliServer: CodeQLCliServer;
27-
let currentDatabaseItem: DatabaseItem | undefined;
28-
let databaseItems: DatabaseItem[] = [];
29-
const openDatabaseSpy = jest.fn();
30-
const removeDatabaseItemSpy = jest.fn();
31-
const renameDatabaseItemSpy = jest.fn();
32-
const setCurrentDatabaseItemSpy = jest.fn();
33-
const runTestsSpy = jest.fn();
34-
const resolveTestsSpy = jest.fn();
35-
const resolveQlpacksSpy = jest.fn();
36-
37-
const preTestDatabaseItem = new DatabaseItemImpl(
38-
Uri.file("/path/to/test/dir/dir.testproj"),
39-
undefined,
40-
mockedObject<FullDatabaseOptions>({ displayName: "custom display name" }),
41-
(_) => {
42-
/* no change event listener */
43-
},
44-
);
45-
const postTestDatabaseItem = new DatabaseItemImpl(
46-
Uri.file("/path/to/test/dir/dir.testproj"),
47-
undefined,
48-
mockedObject<FullDatabaseOptions>({ displayName: "default name" }),
49-
(_) => {
50-
/* no change event listener */
51-
},
52-
);
5332

5433
beforeEach(() => {
55-
mockRunTests();
56-
openDatabaseSpy.mockResolvedValue(postTestDatabaseItem);
57-
removeDatabaseItemSpy.mockResolvedValue(undefined);
58-
renameDatabaseItemSpy.mockResolvedValue(undefined);
59-
setCurrentDatabaseItemSpy.mockResolvedValue(undefined);
60-
resolveQlpacksSpy.mockResolvedValue({});
61-
resolveTestsSpy.mockResolvedValue([]);
62-
fakeDatabaseManager = mockedObject<DatabaseManager>(
63-
{
64-
openDatabase: openDatabaseSpy,
65-
removeDatabaseItem: removeDatabaseItemSpy,
66-
renameDatabaseItem: renameDatabaseItemSpy,
67-
setCurrentDatabaseItem: setCurrentDatabaseItemSpy,
68-
},
69-
{
70-
dynamicProperties: {
71-
currentDatabaseItem: () => currentDatabaseItem,
72-
databaseItems: () => databaseItems,
73-
},
74-
},
75-
);
34+
fakeDatabaseManager = mockEmptyDatabaseManager();
7635

77-
jest.spyOn(preTestDatabaseItem, "isAffectedByTest").mockResolvedValue(true);
78-
79-
fakeCliServer = mockedObject<CodeQLCliServer>({
80-
runTests: runTestsSpy,
81-
resolveQlpacks: resolveQlpacksSpy,
82-
resolveTests: resolveTestsSpy,
83-
});
36+
const mockCli = createMockCliServerForTestRun();
37+
fakeCliServer = mockCli.cliServer;
8438

8539
testRunner = new TestRunner(fakeDatabaseManager, fakeCliServer);
40+
});
8641

87-
adapter = new QLTestAdapter(
42+
it("legacy test adapter should run some tests", async () => {
43+
const adapter = new QLTestAdapter(
8844
mockedObject<WorkspaceFolder>({
8945
name: "ABC",
9046
uri: Uri.parse("file:/ab/c"),
9147
}),
9248
testRunner,
9349
fakeCliServer,
9450
);
95-
});
9651

97-
it("should run some tests", async () => {
9852
const listenerSpy = jest.fn();
9953
adapter.testStates(listenerSpy);
100-
const testsPath = Uri.parse("file:/ab/c").fsPath;
101-
const dPath = Uri.parse("file:/ab/c/d.ql").fsPath;
102-
const gPath = Uri.parse("file:/ab/c/e/f/g.ql").fsPath;
103-
const hPath = Uri.parse("file:/ab/c/e/f/h.ql").fsPath;
104-
105-
await adapter.run([testsPath]);
54+
await adapter.run([mockTestsInfo.testsPath]);
10655

10756
expect(listenerSpy).toBeCalledTimes(5);
10857

10958
expect(listenerSpy).toHaveBeenNthCalledWith(1, {
11059
type: "started",
111-
tests: [testsPath],
60+
tests: [mockTestsInfo.testsPath],
11261
});
11362
expect(listenerSpy).toHaveBeenNthCalledWith(2, {
11463
type: "test",
11564
state: "passed",
116-
test: dPath,
65+
test: mockTestsInfo.dPath,
11766
message: undefined,
11867
decorations: [],
11968
});
12069
expect(listenerSpy).toHaveBeenNthCalledWith(3, {
12170
type: "test",
12271
state: "errored",
123-
test: gPath,
124-
message: `\ncompilation error: ${gPath}\nERROR: abc\n`,
72+
test: mockTestsInfo.gPath,
73+
message: `\ncompilation error: ${mockTestsInfo.gPath}\nERROR: abc\n`,
12574
decorations: [{ line: 1, message: "abc" }],
12675
});
12776
expect(listenerSpy).toHaveBeenNthCalledWith(4, {
12877
type: "test",
12978
state: "failed",
130-
test: hPath,
131-
message: `\nfailed: ${hPath}\njkh\ntuv\n`,
79+
test: mockTestsInfo.hPath,
80+
message: `\nfailed: ${mockTestsInfo.hPath}\njkh\ntuv\n`,
13281
decorations: [],
13382
});
13483
expect(listenerSpy).toHaveBeenNthCalledWith(5, { type: "finished" });
13584
});
13685

137-
it("should reregister testproj databases around test run", async () => {
138-
currentDatabaseItem = preTestDatabaseItem;
139-
databaseItems = [preTestDatabaseItem];
140-
await adapter.run(["/path/to/test/dir"]);
141-
142-
expect(removeDatabaseItemSpy.mock.invocationCallOrder[0]).toBeLessThan(
143-
runTestsSpy.mock.invocationCallOrder[0],
144-
);
145-
expect(openDatabaseSpy.mock.invocationCallOrder[0]).toBeGreaterThan(
146-
runTestsSpy.mock.invocationCallOrder[0],
147-
);
148-
expect(renameDatabaseItemSpy.mock.invocationCallOrder[0]).toBeGreaterThan(
149-
openDatabaseSpy.mock.invocationCallOrder[0],
150-
);
151-
expect(
152-
setCurrentDatabaseItemSpy.mock.invocationCallOrder[0],
153-
).toBeGreaterThan(openDatabaseSpy.mock.invocationCallOrder[0]);
154-
155-
expect(removeDatabaseItemSpy).toBeCalledTimes(1);
156-
expect(removeDatabaseItemSpy).toBeCalledWith(
157-
expect.anything(),
158-
expect.anything(),
159-
preTestDatabaseItem,
86+
it("native test manager should run some tests", async () => {
87+
const enqueuedSpy = jest.fn();
88+
const passedSpy = jest.fn();
89+
const erroredSpy = jest.fn();
90+
const failedSpy = jest.fn();
91+
const endSpy = jest.fn();
92+
93+
const testController = tests.createTestController("codeql", "CodeQL Tests");
94+
testController.createTestRun = jest.fn().mockImplementation(() =>
95+
mockedObject<TestRun>({
96+
enqueued: enqueuedSpy,
97+
passed: passedSpy,
98+
errored: erroredSpy,
99+
failed: failedSpy,
100+
end: endSpy,
101+
}),
160102
);
161-
162-
expect(openDatabaseSpy).toBeCalledTimes(1);
163-
expect(openDatabaseSpy).toBeCalledWith(
164-
expect.anything(),
165-
expect.anything(),
166-
preTestDatabaseItem.databaseUri,
103+
const testManager = new TestManager(
104+
createMockApp({}),
105+
testRunner,
106+
fakeCliServer,
107+
testController,
167108
);
168109

169-
expect(renameDatabaseItemSpy).toBeCalledTimes(1);
170-
expect(renameDatabaseItemSpy).toBeCalledWith(
171-
postTestDatabaseItem,
172-
preTestDatabaseItem.name,
110+
const childItems: TestItem[] = [
111+
{
112+
children: { size: 0 } as TestItemCollection,
113+
id: `test ${mockTestsInfo.dPath}`,
114+
uri: Uri.file(mockTestsInfo.dPath),
115+
} as TestItem,
116+
{
117+
children: { size: 0 } as TestItemCollection,
118+
id: `test ${mockTestsInfo.gPath}`,
119+
uri: Uri.file(mockTestsInfo.gPath),
120+
} as TestItem,
121+
{
122+
children: { size: 0 } as TestItemCollection,
123+
id: `test ${mockTestsInfo.hPath}`,
124+
uri: Uri.file(mockTestsInfo.hPath),
125+
} as TestItem,
126+
];
127+
const childElements: IdTestItemPair[] = childItems.map((childItem) => [
128+
childItem.id,
129+
childItem,
130+
]);
131+
const childIteratorFunc: () => Iterator<IdTestItemPair> = () =>
132+
childElements[Symbol.iterator]();
133+
134+
const rootItem = {
135+
id: `dir ${mockTestsInfo.testsPath}`,
136+
uri: Uri.file(mockTestsInfo.testsPath),
137+
children: {
138+
size: 3,
139+
[Symbol.iterator]: childIteratorFunc,
140+
} as TestItemCollection,
141+
} as TestItem;
142+
143+
const request = new TestRunRequest([rootItem]);
144+
await testManager.run(request, new CancellationTokenSource().token);
145+
146+
expect(enqueuedSpy).toBeCalledTimes(3);
147+
expect(passedSpy).toBeCalledTimes(1);
148+
expect(passedSpy).toHaveBeenCalledWith(childItems[0], 3000);
149+
expect(erroredSpy).toHaveBeenCalledTimes(1);
150+
expect(erroredSpy).toHaveBeenCalledWith(
151+
childItems[1],
152+
[
153+
{
154+
location: {
155+
range: new Range(0, 0, 1, 1),
156+
uri: Uri.file(mockTestsInfo.gPath),
157+
},
158+
message: "abc",
159+
},
160+
],
161+
4000,
173162
);
174-
175-
expect(setCurrentDatabaseItemSpy).toBeCalledTimes(1);
176-
expect(setCurrentDatabaseItemSpy).toBeCalledWith(
177-
postTestDatabaseItem,
178-
true,
163+
expect(failedSpy).toHaveBeenCalledWith(
164+
childItems[2],
165+
[
166+
{
167+
message: "Test failed",
168+
},
169+
],
170+
11000,
179171
);
172+
expect(failedSpy).toBeCalledTimes(1);
173+
expect(endSpy).toBeCalledTimes(1);
180174
});
181-
182-
function mockRunTests() {
183-
// runTests is an async generator function. This is not directly supported in sinon
184-
// However, we can pretend the same thing by just returning an async array.
185-
runTestsSpy.mockReturnValue(
186-
(async function* () {
187-
yield Promise.resolve({
188-
test: Uri.parse("file:/ab/c/d.ql").fsPath,
189-
pass: true,
190-
messages: [],
191-
});
192-
yield Promise.resolve({
193-
test: Uri.parse("file:/ab/c/e/f/g.ql").fsPath,
194-
pass: false,
195-
diff: ["pqr", "xyz"],
196-
// a compile error
197-
failureStage: "COMPILATION",
198-
messages: [
199-
{ position: { line: 1 }, message: "abc", severity: "ERROR" },
200-
],
201-
});
202-
yield Promise.resolve({
203-
test: Uri.parse("file:/ab/c/e/f/h.ql").fsPath,
204-
pass: false,
205-
diff: ["jkh", "tuv"],
206-
failureStage: "RESULT",
207-
messages: [],
208-
});
209-
})(),
210-
);
211-
}
212175
});

0 commit comments

Comments
 (0)