-
Notifications
You must be signed in to change notification settings - Fork 227
Expand file tree
/
Copy pathtest-runner.ts
More file actions
135 lines (129 loc) · 4.5 KB
/
test-runner.ts
File metadata and controls
135 lines (129 loc) · 4.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import type { CancellationToken, Uri } from "vscode";
import type { CodeQLCliServer, TestCompleted } from "../codeql-cli/cli";
import type {
DatabaseItem,
DatabaseManager,
} from "../databases/local-databases";
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
import { asError, getErrorMessage } from "../common/helpers-pure";
import { redactableError } from "../common/errors";
import { access } from "fs-extra";
import { extLogger } from "../common/logging/vscode";
import type { BaseLogger } from "../common/logging";
import {
showAndLogExceptionWithTelemetry,
showAndLogWarningMessage,
} from "../common/logging";
import { DisposableObject } from "../common/disposable-object";
import { telemetryListener } from "../common/vscode/telemetry";
async function isFileAccessible(uri: Uri): Promise<boolean> {
try {
await access(uri.fsPath);
return true;
} catch {
return false;
}
}
export class TestRunner extends DisposableObject {
public constructor(
private readonly databaseManager: DatabaseManager,
private readonly cliServer: CodeQLCliServer,
) {
super();
}
public async run(
tests: string[],
logger: BaseLogger,
token: CancellationToken,
eventHandler: (event: TestCompleted) => Promise<void>,
): Promise<void> {
const currentDatabaseUri =
this.databaseManager.currentDatabaseItem?.databaseUri;
const databasesUnderTest: DatabaseItem[] = [];
for (const database of this.databaseManager.databaseItems) {
for (const test of tests) {
if (await database.isAffectedByTest(test)) {
databasesUnderTest.push(database);
break;
}
}
}
await this.removeDatabasesBeforeTests(databasesUnderTest);
try {
const workspacePaths = getOnDiskWorkspaceFolders();
for await (const event of this.cliServer.runTests(tests, workspacePaths, {
cancellationToken: token,
logger,
})) {
await eventHandler(event);
}
} catch {
// CodeQL testing can throw exception even in normal scenarios. For example, if the test run
// produces no output (which is normal), the testing command would throw an exception on
// unexpected EOF during json parsing. So nothing needs to be done here - all the relevant
// error information (if any) should have already been written to the test logger.
} finally {
await this.reopenDatabasesAfterTests(
databasesUnderTest,
currentDatabaseUri,
);
}
}
private async removeDatabasesBeforeTests(
databasesUnderTest: DatabaseItem[],
): Promise<void> {
for (const database of databasesUnderTest) {
try {
await this.databaseManager.removeDatabaseItem(database);
} catch (e) {
// This method is invoked from Test Explorer UI, and testing indicates that Test
// Explorer UI swallows any thrown exception without reporting it to the user.
// So we need to display the error message ourselves and then rethrow.
void showAndLogExceptionWithTelemetry(
extLogger,
telemetryListener,
redactableError(asError(e))`Cannot remove database ${
database.name
}: ${getErrorMessage(e)}`,
);
throw e;
}
}
}
private async reopenDatabasesAfterTests(
databasesUnderTest: DatabaseItem[],
currentDatabaseUri: Uri | undefined,
): Promise<void> {
for (const closedDatabase of databasesUnderTest) {
const uri = closedDatabase.databaseUri;
if (await isFileAccessible(uri)) {
try {
const reopenedDatabase = await this.databaseManager.openDatabase(
uri,
closedDatabase.origin,
false,
);
await this.databaseManager.renameDatabaseItem(
reopenedDatabase,
closedDatabase.name,
);
if (currentDatabaseUri?.toString() === uri.toString()) {
await this.databaseManager.setCurrentDatabaseItem(
reopenedDatabase,
true,
);
}
} catch (e) {
// This method is invoked from Test Explorer UI, and testing indicates that Test
// Explorer UI swallows any thrown exception without reporting it to the user.
// So we need to display the error message ourselves and then rethrow.
void showAndLogWarningMessage(
extLogger,
`Cannot reopen database ${uri.toString()}: ${getErrorMessage(e)}`,
);
throw e;
}
}
}
}
}