@iamsergio/qttest-utils 0.4.5 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -1
- package/a +8 -0
- package/foo +15 -0
- package/out/example.js +33 -3
- package/out/qttest.d.ts +16 -1
- package/out/qttest.js +101 -12
- package/package.json +1 -1
- package/src/example.ts +40 -3
- package/src/qttest.ts +129 -11
- package/test/qt_test/CMakeLists.txt +22 -0
- package/test/qt_test/CMakePresets.json +15 -0
- package/test/qt_test/test1.cpp +18 -0
- package/test/qt_test/test2.cpp +18 -0
- package/test/qt_test/test3.cpp +18 -0
- package/test1.log +31 -0
- package/test1.log1 +17 -0
package/README.md
CHANGED
|
@@ -4,4 +4,14 @@ A nodejs module for listing Qt Test executables and their individual test slots
|
|
|
4
4
|
|
|
5
5
|
To be used by vscode extensions that implement the `Testing API`, but can also be used standalone for whatever reason ;).
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
## Example
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
$ cd test/qt_test
|
|
12
|
+
$ cmake --preset=dev
|
|
13
|
+
$ cmake --build build-dev/
|
|
14
|
+
$ cd ../..
|
|
15
|
+
$ tsc
|
|
16
|
+
$ node out/example.js test/qt_test/build-dev
|
|
17
|
+
```
|
package/a
ADDED
package/foo
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
TAP version 13
|
|
2
|
+
# MyTest
|
|
3
|
+
ok 1 - initTestCase()
|
|
4
|
+
not ok 2 - testF()
|
|
5
|
+
---
|
|
6
|
+
# failed
|
|
7
|
+
at: MyTest::testF() (/data/sources/vscode-test-extension/qttest-utils/test/qt_test/test2.cpp:13)
|
|
8
|
+
file: /data/sources/vscode-test-extension/qttest-utils/test/qt_test/test2.cpp
|
|
9
|
+
line: 13
|
|
10
|
+
...
|
|
11
|
+
ok 3 - cleanupTestCase()
|
|
12
|
+
1..3
|
|
13
|
+
# tests 3
|
|
14
|
+
# pass 2
|
|
15
|
+
# fail 1
|
package/out/example.js
CHANGED
|
@@ -30,16 +30,46 @@ function example() {
|
|
|
30
30
|
process.exit(1);
|
|
31
31
|
}
|
|
32
32
|
let qt = new qttest_1.QtTests();
|
|
33
|
+
qt.setLogFunction((message) => {
|
|
34
|
+
console.log(message);
|
|
35
|
+
});
|
|
33
36
|
// Gather all tests that would be executed by CTest:
|
|
34
37
|
yield qt.discoverViaCMake(buildDirPath);
|
|
35
38
|
// Filter-out the ones that don't link to QtTest (doctests and such)
|
|
36
39
|
yield qt.removeNonLinking();
|
|
37
40
|
// Example of filtering out by regexp:
|
|
38
41
|
qt.removeMatching(/(tst_view|tst_window)/);
|
|
39
|
-
//
|
|
40
|
-
qt.maintainMatching(/(tst_docks|tst_qtwidgets|tst_multisplitter)/);
|
|
42
|
+
// Uncomment to see example of filtering out by regexp (inverted):
|
|
43
|
+
// qt.maintainMatching(/(tst_docks|tst_qtwidgets|tst_multisplitter)/);
|
|
41
44
|
qt.dumpExecutablePaths();
|
|
42
|
-
qt.dumpTestSlots();
|
|
45
|
+
yield qt.dumpTestSlots();
|
|
46
|
+
console.log("\nRunning tests...");
|
|
47
|
+
for (var executable of qt.qtTestExecutables) {
|
|
48
|
+
yield executable.runTest();
|
|
49
|
+
if (executable.lastExitCode === 0)
|
|
50
|
+
console.log(" PASS: " + executable.filename);
|
|
51
|
+
else
|
|
52
|
+
console.log(" FAIL: " + executable.filename + "; code=" + executable.lastExitCode);
|
|
53
|
+
for (let slot of executable.slots) {
|
|
54
|
+
if (slot.lastTestFailure) {
|
|
55
|
+
console.log(" failed slot=" + slot.name + "; path=" + slot.lastTestFailure.filePath + "; line=" + slot.lastTestFailure.lineNumber);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Also run individual slots, just for example purposes:
|
|
60
|
+
console.log("\nRunning single tests...");
|
|
61
|
+
let slot = qt.qtTestExecutables[1].slots[0];
|
|
62
|
+
yield slot.runTest();
|
|
63
|
+
if (slot.lastTestFailure)
|
|
64
|
+
console.log(" FAIL:" + JSON.stringify(slot.lastTestFailure));
|
|
65
|
+
else
|
|
66
|
+
console.log(" PASS:");
|
|
67
|
+
let slot2 = qt.qtTestExecutables[1].slots[2];
|
|
68
|
+
yield slot2.runTest();
|
|
69
|
+
if (slot2.lastTestFailure)
|
|
70
|
+
console.log(" FAIL:" + JSON.stringify(slot2.lastTestFailure));
|
|
71
|
+
else
|
|
72
|
+
console.log(" PASS");
|
|
43
73
|
});
|
|
44
74
|
}
|
|
45
75
|
example();
|
package/out/qttest.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
type LoggerFunction = (arg: string) => void;
|
|
1
2
|
/**
|
|
2
3
|
* Represents a single QtTest executable.
|
|
3
4
|
* Supports listing the individual test slots
|
|
@@ -7,6 +8,7 @@ export declare class QtTest {
|
|
|
7
8
|
readonly buildDirPath: string;
|
|
8
9
|
vscodeTestItem: any | undefined;
|
|
9
10
|
slots: QtTestSlot[] | null;
|
|
11
|
+
lastExitCode: number;
|
|
10
12
|
constructor(filename: string, buildDirPath: string);
|
|
11
13
|
get id(): string;
|
|
12
14
|
get label(): string;
|
|
@@ -23,12 +25,17 @@ export declare class QtTest {
|
|
|
23
25
|
*/
|
|
24
26
|
linksToQtTestLib(): Promise<boolean> | undefined;
|
|
25
27
|
isQtTestViaHelp(): Promise<boolean | undefined>;
|
|
26
|
-
|
|
28
|
+
slotByName(name: string): QtTestSlot | undefined;
|
|
29
|
+
runTest(slot?: QtTestSlot, cwd?: string): Promise<boolean>;
|
|
30
|
+
tapOutputFileName(slot?: QtTestSlot): string;
|
|
31
|
+
txtOutputFileName(slot?: QtTestSlot): string;
|
|
27
32
|
command(): {
|
|
28
33
|
label: string;
|
|
29
34
|
executablePath: string;
|
|
30
35
|
args: string[];
|
|
31
36
|
};
|
|
37
|
+
clearSubTestStates(): void;
|
|
38
|
+
updateSubTestStates(cwdDir: string, slot?: QtTestSlot): Promise<void>;
|
|
32
39
|
}
|
|
33
40
|
/**
|
|
34
41
|
* Represents a single Qt test slot
|
|
@@ -37,6 +44,7 @@ export declare class QtTestSlot {
|
|
|
37
44
|
name: string;
|
|
38
45
|
parentQTest: QtTest;
|
|
39
46
|
vscodeTestItem: any | undefined;
|
|
47
|
+
lastTestFailure: TestFailure | undefined;
|
|
40
48
|
constructor(name: string, parent: QtTest);
|
|
41
49
|
get id(): string;
|
|
42
50
|
get absoluteFilePath(): string;
|
|
@@ -54,9 +62,16 @@ export declare class QtTests {
|
|
|
54
62
|
qtTestExecutables: QtTest[];
|
|
55
63
|
discoverViaCMake(buildDirPath: string): Promise<void>;
|
|
56
64
|
removeNonLinking(): Promise<void>;
|
|
65
|
+
setLogFunction(func: LoggerFunction): void;
|
|
57
66
|
removeByRunningHelp(): Promise<void>;
|
|
58
67
|
removeMatching(regex: RegExp): void;
|
|
59
68
|
maintainMatching(regex: RegExp): void;
|
|
60
69
|
dumpExecutablePaths(): void;
|
|
61
70
|
dumpTestSlots(): Promise<void>;
|
|
62
71
|
}
|
|
72
|
+
export interface TestFailure {
|
|
73
|
+
name: string;
|
|
74
|
+
filePath: string;
|
|
75
|
+
lineNumber: number;
|
|
76
|
+
}
|
|
77
|
+
export {};
|
package/out/qttest.js
CHANGED
|
@@ -43,13 +43,22 @@ const child_process_1 = require("child_process");
|
|
|
43
43
|
const path_1 = __importDefault(require("path"));
|
|
44
44
|
const fs = __importStar(require("fs"));
|
|
45
45
|
const cmake_1 = require("./cmake");
|
|
46
|
+
var gLogFunction;
|
|
47
|
+
function logMessage(message) {
|
|
48
|
+
if (gLogFunction) {
|
|
49
|
+
gLogFunction(message);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
46
52
|
/**
|
|
47
53
|
* Represents a single QtTest executable.
|
|
48
54
|
* Supports listing the individual test slots
|
|
49
55
|
*/
|
|
50
56
|
class QtTest {
|
|
51
57
|
constructor(filename, buildDirPath) {
|
|
58
|
+
/// The list of individual runnable test slots
|
|
52
59
|
this.slots = null;
|
|
60
|
+
/// Set after running
|
|
61
|
+
this.lastExitCode = 0;
|
|
53
62
|
this.filename = filename;
|
|
54
63
|
this.buildDirPath = buildDirPath;
|
|
55
64
|
}
|
|
@@ -159,34 +168,111 @@ class QtTest {
|
|
|
159
168
|
});
|
|
160
169
|
});
|
|
161
170
|
}
|
|
171
|
+
slotByName(name) {
|
|
172
|
+
if (!this.slots)
|
|
173
|
+
return undefined;
|
|
174
|
+
for (let slot of this.slots) {
|
|
175
|
+
if (slot.name == name)
|
|
176
|
+
return slot;
|
|
177
|
+
}
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
162
180
|
/// Runs this test
|
|
163
|
-
runTest(
|
|
181
|
+
runTest(slot, cwd = "") {
|
|
164
182
|
return __awaiter(this, void 0, void 0, function* () {
|
|
165
183
|
let args = [];
|
|
166
|
-
if (
|
|
184
|
+
if (slot) {
|
|
167
185
|
// Runs a single Qt test instead
|
|
168
|
-
args = args.concat(
|
|
186
|
+
args = args.concat(slot.name);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
this.clearSubTestStates();
|
|
169
190
|
}
|
|
191
|
+
// log to file
|
|
192
|
+
args = args.concat("-o").concat(this.tapOutputFileName(slot) + ",tap");
|
|
193
|
+
args = args.concat("-o").concat(this.txtOutputFileName(slot) + ",txt");
|
|
170
194
|
return yield new Promise((resolve, reject) => {
|
|
171
|
-
let
|
|
172
|
-
|
|
173
|
-
child
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
195
|
+
let cwdDir = cwd.length > 0 ? cwd : this.buildDirPath;
|
|
196
|
+
logMessage("Running " + this.filename + " " + args.join(" ") + " with cwd=" + cwdDir);
|
|
197
|
+
const child = (0, child_process_1.spawn)(this.filename, args, { cwd: cwdDir });
|
|
198
|
+
child.on("exit", (code) => __awaiter(this, void 0, void 0, function* () {
|
|
199
|
+
/// We can code even be null ?
|
|
200
|
+
if (code == undefined)
|
|
201
|
+
code = -1;
|
|
202
|
+
if (!slot) {
|
|
203
|
+
this.lastExitCode = code;
|
|
204
|
+
}
|
|
205
|
+
/// When running a QtTest executable, let's check which sub-tests failed
|
|
206
|
+
/// (So VSCode can show some error icon for each fail)
|
|
207
|
+
yield this.updateSubTestStates(cwdDir, slot);
|
|
177
208
|
if (code === 0) {
|
|
178
209
|
resolve(true);
|
|
179
210
|
}
|
|
180
211
|
else {
|
|
181
212
|
resolve(false);
|
|
182
213
|
}
|
|
183
|
-
});
|
|
214
|
+
}));
|
|
184
215
|
});
|
|
185
216
|
});
|
|
186
217
|
}
|
|
218
|
+
/// Using .tap so we don't have to use a separate XML library
|
|
219
|
+
/// .tap is plain text and a single regexp can catch the failing tests and line number
|
|
220
|
+
tapOutputFileName(slot) {
|
|
221
|
+
let slotName = slot ? ("_" + slot.name) : "";
|
|
222
|
+
return this.label + slotName + ".tap";
|
|
223
|
+
}
|
|
224
|
+
txtOutputFileName(slot) {
|
|
225
|
+
let slotName = slot ? ("_" + slot.name) : "";
|
|
226
|
+
return this.label + slotName + ".txt";
|
|
227
|
+
}
|
|
187
228
|
command() {
|
|
188
229
|
return { label: this.label, executablePath: this.filename, args: [] };
|
|
189
230
|
}
|
|
231
|
+
clearSubTestStates() {
|
|
232
|
+
if (this.slots) {
|
|
233
|
+
for (let slot of this.slots) {
|
|
234
|
+
slot.lastTestFailure = undefined;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
updateSubTestStates(cwdDir, slot) {
|
|
239
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
240
|
+
let tapFileName = cwdDir + "/" + this.tapOutputFileName(slot);
|
|
241
|
+
var failures = yield new Promise((resolve, reject) => {
|
|
242
|
+
fs.readFile(tapFileName, "utf8", (error, data) => {
|
|
243
|
+
if (error) {
|
|
244
|
+
logMessage("ERROR: Failed to read log file");
|
|
245
|
+
reject(error);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
// A fail line is something like:
|
|
249
|
+
// at: MyTest::testF() (/some/path/qttest-utils/test/qt_test/test2.cpp:13)
|
|
250
|
+
const pattern = /at:\s+(.+?)::(.+?)\(\)\s+\((.+?):(\d+)\)/gm;
|
|
251
|
+
const matches = Array.from(data.matchAll(pattern));
|
|
252
|
+
const failedResults = matches.map(match => ({
|
|
253
|
+
name: match[2],
|
|
254
|
+
filePath: match[3],
|
|
255
|
+
lineNumber: parseInt(match[4]),
|
|
256
|
+
}));
|
|
257
|
+
resolve(failedResults);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
for (let failure of failures) {
|
|
262
|
+
if (slot && slot.name != failure.name) {
|
|
263
|
+
// We executed a single slot, ignore anything else
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
let failedSlot = this.slotByName(failure.name);
|
|
267
|
+
if (failedSlot) {
|
|
268
|
+
failedSlot.lastTestFailure = failure;
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
logMessage("ERROR: Failed to find slot with name " + failure.name);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
190
276
|
}
|
|
191
277
|
exports.QtTest = QtTest;
|
|
192
278
|
/**
|
|
@@ -205,7 +291,7 @@ class QtTestSlot {
|
|
|
205
291
|
}
|
|
206
292
|
runTest() {
|
|
207
293
|
return __awaiter(this, void 0, void 0, function* () {
|
|
208
|
-
return this.parentQTest.runTest(this
|
|
294
|
+
return this.parentQTest.runTest(this);
|
|
209
295
|
});
|
|
210
296
|
}
|
|
211
297
|
command() {
|
|
@@ -231,7 +317,7 @@ class QtTests {
|
|
|
231
317
|
}
|
|
232
318
|
}
|
|
233
319
|
else {
|
|
234
|
-
|
|
320
|
+
logMessage("ERROR: Failed to retrieve ctests!");
|
|
235
321
|
}
|
|
236
322
|
});
|
|
237
323
|
}
|
|
@@ -255,6 +341,9 @@ class QtTests {
|
|
|
255
341
|
}
|
|
256
342
|
});
|
|
257
343
|
}
|
|
344
|
+
setLogFunction(func) {
|
|
345
|
+
gLogFunction = func;
|
|
346
|
+
}
|
|
258
347
|
removeByRunningHelp() {
|
|
259
348
|
return __awaiter(this, void 0, void 0, function* () {
|
|
260
349
|
this.qtTestExecutables = this.qtTestExecutables.filter((ex) => __awaiter(this, void 0, void 0, function* () { return yield ex.isQtTestViaHelp(); }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iamsergio/qttest-utils",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"description": "API for listing QtTest executables from a build directory and which individual test slots each executable contains. Useful for a Text Explorer VSCode extension.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
package/src/example.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { CMakeTests, CMakeTest } from "./cmake";
|
|
6
6
|
import { QtTest, QtTests } from "./qttest";
|
|
7
7
|
import fs from 'fs';
|
|
8
|
+
import { exec } from "child_process";
|
|
8
9
|
|
|
9
10
|
async function example() {
|
|
10
11
|
const args = process.argv.slice(2)
|
|
@@ -22,6 +23,10 @@ async function example() {
|
|
|
22
23
|
|
|
23
24
|
let qt = new QtTests();
|
|
24
25
|
|
|
26
|
+
qt.setLogFunction((message: string) => {
|
|
27
|
+
console.log(message);
|
|
28
|
+
});
|
|
29
|
+
|
|
25
30
|
// Gather all tests that would be executed by CTest:
|
|
26
31
|
await qt.discoverViaCMake(buildDirPath);
|
|
27
32
|
|
|
@@ -31,11 +36,43 @@ async function example() {
|
|
|
31
36
|
// Example of filtering out by regexp:
|
|
32
37
|
qt.removeMatching(/(tst_view|tst_window)/);
|
|
33
38
|
|
|
34
|
-
//
|
|
35
|
-
qt.maintainMatching(/(tst_docks|tst_qtwidgets|tst_multisplitter)/);
|
|
39
|
+
// Uncomment to see example of filtering out by regexp (inverted):
|
|
40
|
+
// qt.maintainMatching(/(tst_docks|tst_qtwidgets|tst_multisplitter)/);
|
|
36
41
|
|
|
37
42
|
qt.dumpExecutablePaths();
|
|
38
|
-
qt.dumpTestSlots();
|
|
43
|
+
await qt.dumpTestSlots();
|
|
44
|
+
|
|
45
|
+
console.log("\nRunning tests...");
|
|
46
|
+
for (var executable of qt.qtTestExecutables) {
|
|
47
|
+
await executable.runTest();
|
|
48
|
+
if (executable.lastExitCode === 0)
|
|
49
|
+
console.log(" PASS: " + executable.filename);
|
|
50
|
+
else
|
|
51
|
+
console.log(" FAIL: " + executable.filename + "; code=" + executable.lastExitCode);
|
|
52
|
+
for (let slot of executable.slots!) {
|
|
53
|
+
if (slot.lastTestFailure) {
|
|
54
|
+
console.log(" failed slot=" + slot.name + "; path=" + slot.lastTestFailure.filePath + "; line=" + slot.lastTestFailure.lineNumber);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Also run individual slots, just for example purposes:
|
|
60
|
+
|
|
61
|
+
console.log("\nRunning single tests...");
|
|
62
|
+
let slot = qt.qtTestExecutables[1].slots![0];
|
|
63
|
+
await slot.runTest();
|
|
64
|
+
if (slot.lastTestFailure)
|
|
65
|
+
console.log(" FAIL:" + JSON.stringify(slot.lastTestFailure));
|
|
66
|
+
else
|
|
67
|
+
console.log(" PASS:");
|
|
68
|
+
|
|
69
|
+
let slot2 = qt.qtTestExecutables[1].slots![2];
|
|
70
|
+
await slot2.runTest();
|
|
71
|
+
if (slot2.lastTestFailure)
|
|
72
|
+
console.log(" FAIL:" + JSON.stringify(slot2.lastTestFailure));
|
|
73
|
+
else
|
|
74
|
+
console.log(" PASS");
|
|
75
|
+
|
|
39
76
|
}
|
|
40
77
|
|
|
41
78
|
example();
|
package/src/qttest.ts
CHANGED
|
@@ -7,6 +7,16 @@ import path from "path";
|
|
|
7
7
|
import * as fs from 'fs';
|
|
8
8
|
import { CMakeTests } from "./cmake";
|
|
9
9
|
|
|
10
|
+
|
|
11
|
+
type LoggerFunction = (arg: string) => void;
|
|
12
|
+
var gLogFunction: LoggerFunction | undefined;
|
|
13
|
+
|
|
14
|
+
function logMessage(message: string) {
|
|
15
|
+
if (gLogFunction) {
|
|
16
|
+
gLogFunction(message);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
10
20
|
/**
|
|
11
21
|
* Represents a single QtTest executable.
|
|
12
22
|
* Supports listing the individual test slots
|
|
@@ -18,8 +28,12 @@ export class QtTest {
|
|
|
18
28
|
/// Allows vscode extensions to associate with a test item
|
|
19
29
|
vscodeTestItem: any | undefined;
|
|
20
30
|
|
|
31
|
+
/// The list of individual runnable test slots
|
|
21
32
|
slots: QtTestSlot[] | null = null;
|
|
22
33
|
|
|
34
|
+
/// Set after running
|
|
35
|
+
lastExitCode: number = 0;
|
|
36
|
+
|
|
23
37
|
constructor(filename: string, buildDirPath: string) {
|
|
24
38
|
this.filename = filename;
|
|
25
39
|
this.buildDirPath = buildDirPath;
|
|
@@ -138,22 +152,50 @@ export class QtTest {
|
|
|
138
152
|
});
|
|
139
153
|
}
|
|
140
154
|
|
|
155
|
+
public slotByName(name: string): QtTestSlot | undefined {
|
|
156
|
+
if (!this.slots)
|
|
157
|
+
return undefined;
|
|
158
|
+
|
|
159
|
+
for (let slot of this.slots) {
|
|
160
|
+
if (slot.name == name)
|
|
161
|
+
return slot;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
|
|
141
167
|
/// Runs this test
|
|
142
|
-
public async runTest(
|
|
168
|
+
public async runTest(slot?: QtTestSlot, cwd: string = ""): Promise<boolean> {
|
|
143
169
|
let args: string[] = [];
|
|
144
|
-
if (
|
|
170
|
+
if (slot) {
|
|
145
171
|
// Runs a single Qt test instead
|
|
146
|
-
args = args.concat(
|
|
172
|
+
args = args.concat(slot.name);
|
|
173
|
+
} else {
|
|
174
|
+
this.clearSubTestStates();
|
|
147
175
|
}
|
|
148
176
|
|
|
177
|
+
// log to file
|
|
178
|
+
args = args.concat("-o").concat(this.tapOutputFileName(slot) + ",tap");
|
|
179
|
+
args = args.concat("-o").concat(this.txtOutputFileName(slot) + ",txt");
|
|
180
|
+
|
|
149
181
|
return await new Promise((resolve, reject) => {
|
|
150
|
-
let
|
|
151
|
-
|
|
152
|
-
child.
|
|
153
|
-
|
|
154
|
-
|
|
182
|
+
let cwdDir = cwd.length > 0 ? cwd : this.buildDirPath;
|
|
183
|
+
logMessage("Running " + this.filename + " " + args.join(" ") + " with cwd=" + cwdDir);
|
|
184
|
+
const child = spawn(this.filename, args, { cwd: cwdDir });
|
|
185
|
+
|
|
186
|
+
child.on("exit", async (code) => {
|
|
187
|
+
|
|
188
|
+
/// We can code even be null ?
|
|
189
|
+
if (code == undefined) code = -1;
|
|
190
|
+
|
|
191
|
+
if (!slot) {
|
|
192
|
+
this.lastExitCode = code;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/// When running a QtTest executable, let's check which sub-tests failed
|
|
196
|
+
/// (So VSCode can show some error icon for each fail)
|
|
197
|
+
await this.updateSubTestStates(cwdDir, slot);
|
|
155
198
|
|
|
156
|
-
child.on("exit", (code) => {
|
|
157
199
|
if (code === 0) {
|
|
158
200
|
resolve(true);
|
|
159
201
|
} else {
|
|
@@ -163,9 +205,71 @@ export class QtTest {
|
|
|
163
205
|
});
|
|
164
206
|
}
|
|
165
207
|
|
|
208
|
+
/// Using .tap so we don't have to use a separate XML library
|
|
209
|
+
/// .tap is plain text and a single regexp can catch the failing tests and line number
|
|
210
|
+
public tapOutputFileName(slot?: QtTestSlot): string {
|
|
211
|
+
let slotName = slot ? ("_" + slot.name) : "";
|
|
212
|
+
return this.label + slotName + ".tap";
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
public txtOutputFileName(slot?: QtTestSlot): string {
|
|
216
|
+
let slotName = slot ? ("_" + slot.name) : "";
|
|
217
|
+
return this.label + slotName + ".txt";
|
|
218
|
+
}
|
|
219
|
+
|
|
166
220
|
public command(): { label: string, executablePath: string, args: string[] } {
|
|
167
221
|
return { label: this.label, executablePath: this.filename, args: [] };
|
|
168
222
|
}
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
public clearSubTestStates() {
|
|
226
|
+
if (this.slots) {
|
|
227
|
+
for (let slot of this.slots) {
|
|
228
|
+
slot.lastTestFailure = undefined;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
public async updateSubTestStates(cwdDir: string, slot?: QtTestSlot) {
|
|
234
|
+
|
|
235
|
+
let tapFileName: string = cwdDir + "/" + this.tapOutputFileName(slot);
|
|
236
|
+
|
|
237
|
+
var failures = await new Promise<TestFailure[]>((resolve, reject) => {
|
|
238
|
+
fs.readFile(tapFileName, "utf8", (error, data) => {
|
|
239
|
+
if (error) {
|
|
240
|
+
logMessage("ERROR: Failed to read log file");
|
|
241
|
+
reject(error);
|
|
242
|
+
} else {
|
|
243
|
+
// A fail line is something like:
|
|
244
|
+
// at: MyTest::testF() (/some/path/qttest-utils/test/qt_test/test2.cpp:13)
|
|
245
|
+
|
|
246
|
+
const pattern = /at:\s+(.+?)::(.+?)\(\)\s+\((.+?):(\d+)\)/gm;
|
|
247
|
+
const matches = Array.from(data.matchAll(pattern));
|
|
248
|
+
const failedResults = matches.map(match => ({
|
|
249
|
+
name: match[2],
|
|
250
|
+
filePath: match[3],
|
|
251
|
+
lineNumber: parseInt(match[4]),
|
|
252
|
+
}));
|
|
253
|
+
|
|
254
|
+
resolve(failedResults);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
for (let failure of failures) {
|
|
260
|
+
if (slot && slot.name != failure.name) {
|
|
261
|
+
// We executed a single slot, ignore anything else
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
let failedSlot = this.slotByName(failure.name);
|
|
266
|
+
if (failedSlot) {
|
|
267
|
+
failedSlot.lastTestFailure = failure;
|
|
268
|
+
} else {
|
|
269
|
+
logMessage("ERROR: Failed to find slot with name " + failure.name);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
169
273
|
}
|
|
170
274
|
|
|
171
275
|
/**
|
|
@@ -180,6 +284,9 @@ export class QtTestSlot {
|
|
|
180
284
|
/// Allows vscode extensions to associate with a test item
|
|
181
285
|
vscodeTestItem: any | undefined;
|
|
182
286
|
|
|
287
|
+
/// Set after running
|
|
288
|
+
lastTestFailure: TestFailure | undefined;
|
|
289
|
+
|
|
183
290
|
constructor(name: string, parent: QtTest) {
|
|
184
291
|
this.name = name;
|
|
185
292
|
this.parentQTest = parent;
|
|
@@ -194,7 +301,7 @@ export class QtTestSlot {
|
|
|
194
301
|
}
|
|
195
302
|
|
|
196
303
|
public async runTest(): Promise<boolean> {
|
|
197
|
-
return this.parentQTest.runTest(this
|
|
304
|
+
return this.parentQTest.runTest(this);
|
|
198
305
|
}
|
|
199
306
|
|
|
200
307
|
public command(): { label: string, executablePath: string, args: string[] } {
|
|
@@ -217,7 +324,7 @@ export class QtTests {
|
|
|
217
324
|
this.qtTestExecutables.push(qtest);
|
|
218
325
|
}
|
|
219
326
|
} else {
|
|
220
|
-
|
|
327
|
+
logMessage("ERROR: Failed to retrieve ctests!");
|
|
221
328
|
}
|
|
222
329
|
}
|
|
223
330
|
|
|
@@ -239,6 +346,10 @@ export class QtTests {
|
|
|
239
346
|
}
|
|
240
347
|
}
|
|
241
348
|
|
|
349
|
+
public setLogFunction(func: LoggerFunction) {
|
|
350
|
+
gLogFunction = func;
|
|
351
|
+
}
|
|
352
|
+
|
|
242
353
|
public async removeByRunningHelp() {
|
|
243
354
|
this.qtTestExecutables = this.qtTestExecutables.filter(async (ex) => await ex.isQtTestViaHelp());
|
|
244
355
|
}
|
|
@@ -273,3 +384,10 @@ export class QtTests {
|
|
|
273
384
|
}
|
|
274
385
|
}
|
|
275
386
|
}
|
|
387
|
+
|
|
388
|
+
/// Represents a failure location
|
|
389
|
+
export interface TestFailure {
|
|
390
|
+
name: string;
|
|
391
|
+
filePath: string;
|
|
392
|
+
lineNumber: number;
|
|
393
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
|
2
|
+
# Author: Sergio Martins <sergio.martins@kdab.com>
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
|
|
5
|
+
cmake_minimum_required(VERSION 3.12)
|
|
6
|
+
project(qttest)
|
|
7
|
+
|
|
8
|
+
find_package(Qt5Test 5.15 REQUIRED)
|
|
9
|
+
set(CMAKE_AUTOMOC ON)
|
|
10
|
+
|
|
11
|
+
add_executable(test1 test1.cpp)
|
|
12
|
+
add_executable(test2 test2.cpp)
|
|
13
|
+
add_executable(test3 test3.cpp)
|
|
14
|
+
|
|
15
|
+
target_link_libraries(test1 Qt5::Test)
|
|
16
|
+
target_link_libraries(test2 Qt5::Test)
|
|
17
|
+
target_link_libraries(test3 Qt5::Test)
|
|
18
|
+
|
|
19
|
+
enable_testing()
|
|
20
|
+
add_test(NAME test1 COMMAND test1)
|
|
21
|
+
add_test(NAME test2 COMMAND test2)
|
|
22
|
+
add_test(NAME test3 COMMAND test3)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 2,
|
|
3
|
+
"configurePresets": [
|
|
4
|
+
{
|
|
5
|
+
"name": "dev",
|
|
6
|
+
"displayName": "dev",
|
|
7
|
+
"generator": "Ninja",
|
|
8
|
+
"binaryDir": "${sourceDir}/build-dev",
|
|
9
|
+
"cacheVariables": {
|
|
10
|
+
"CMAKE_BUILD_TYPE": "Debug",
|
|
11
|
+
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group
|
|
2
|
+
// company <info@kdab.com> Author: Sergio Martins <sergio.martins@kdab.com>
|
|
3
|
+
// SPDX-License-Identifier: MIT
|
|
4
|
+
|
|
5
|
+
#include <QObject>
|
|
6
|
+
#include <QtTest>
|
|
7
|
+
|
|
8
|
+
class MyTest : public QObject {
|
|
9
|
+
Q_OBJECT
|
|
10
|
+
private Q_SLOTS:
|
|
11
|
+
void testA() {}
|
|
12
|
+
void testB() {}
|
|
13
|
+
void testC() {}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
QTEST_MAIN(MyTest);
|
|
17
|
+
|
|
18
|
+
#include <test1.moc>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group
|
|
2
|
+
// company <info@kdab.com> Author: Sergio Martins <sergio.martins@kdab.com>
|
|
3
|
+
// SPDX-License-Identifier: MIT
|
|
4
|
+
|
|
5
|
+
#include <QObject>
|
|
6
|
+
#include <QtTest>
|
|
7
|
+
|
|
8
|
+
class MyTest : public QObject {
|
|
9
|
+
Q_OBJECT
|
|
10
|
+
private Q_SLOTS:
|
|
11
|
+
void testD() {}
|
|
12
|
+
void testE() {}
|
|
13
|
+
void testF() { QFAIL("failed"); }
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
QTEST_MAIN(MyTest);
|
|
17
|
+
|
|
18
|
+
#include <test2.moc>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group
|
|
2
|
+
// company <info@kdab.com> Author: Sergio Martins <sergio.martins@kdab.com>
|
|
3
|
+
// SPDX-License-Identifier: MIT
|
|
4
|
+
|
|
5
|
+
#include <QObject>
|
|
6
|
+
#include <QtTest>
|
|
7
|
+
|
|
8
|
+
class MyTest : public QObject {
|
|
9
|
+
Q_OBJECT
|
|
10
|
+
private Q_SLOTS:
|
|
11
|
+
void testG() {}
|
|
12
|
+
void testH() {}
|
|
13
|
+
void testI() {}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
QTEST_MAIN(MyTest);
|
|
17
|
+
|
|
18
|
+
#include <test3.moc>
|
package/test1.log
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<TestCase name="MyTest">
|
|
3
|
+
<Environment>
|
|
4
|
+
<QtVersion>5.15.2</QtVersion>
|
|
5
|
+
<QtBuild>Qt 5.15.2 (x86_64-little_endian-lp64 shared (dynamic) release build; by GCC 5.3.1 20160406 (Red Hat 5.3.1-6))</QtBuild>
|
|
6
|
+
<QTestVersion>5.15.2</QTestVersion>
|
|
7
|
+
</Environment>
|
|
8
|
+
<TestFunction name="initTestCase">
|
|
9
|
+
<Incident type="pass" file="" line="0" />
|
|
10
|
+
<Duration msecs="0.312962"/>
|
|
11
|
+
</TestFunction>
|
|
12
|
+
<TestFunction name="testD">
|
|
13
|
+
<Incident type="pass" file="" line="0" />
|
|
14
|
+
<Duration msecs="0.010127"/>
|
|
15
|
+
</TestFunction>
|
|
16
|
+
<TestFunction name="testE">
|
|
17
|
+
<Incident type="pass" file="" line="0" />
|
|
18
|
+
<Duration msecs="0.005239"/>
|
|
19
|
+
</TestFunction>
|
|
20
|
+
<TestFunction name="testF">
|
|
21
|
+
<Incident type="fail" file="/data/sources/vscode-test-extension/qttest-utils/test/qt_test/test2.cpp" line="13">
|
|
22
|
+
<Description><![CDATA[failed]]></Description>
|
|
23
|
+
</Incident>
|
|
24
|
+
<Duration msecs="0.018438"/>
|
|
25
|
+
</TestFunction>
|
|
26
|
+
<TestFunction name="cleanupTestCase">
|
|
27
|
+
<Incident type="pass" file="" line="0" />
|
|
28
|
+
<Duration msecs="0.00426"/>
|
|
29
|
+
</TestFunction>
|
|
30
|
+
<Duration msecs="0.432951"/>
|
|
31
|
+
</TestCase>
|
package/test1.log1
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
TAP version 13
|
|
2
|
+
# MyTest
|
|
3
|
+
ok 1 - initTestCase()
|
|
4
|
+
ok 2 - testD()
|
|
5
|
+
ok 3 - testE()
|
|
6
|
+
not ok 4 - testF()
|
|
7
|
+
---
|
|
8
|
+
# failed
|
|
9
|
+
at: MyTest::testF() (/data/sources/vscode-test-extension/qttest-utils/test/qt_test/test2.cpp:13)
|
|
10
|
+
file: /data/sources/vscode-test-extension/qttest-utils/test/qt_test/test2.cpp
|
|
11
|
+
line: 13
|
|
12
|
+
...
|
|
13
|
+
ok 5 - cleanupTestCase()
|
|
14
|
+
1..5
|
|
15
|
+
# tests 5
|
|
16
|
+
# pass 4
|
|
17
|
+
# fail 1
|