@iamsergio/qttest-utils 2.5.0 → 2.6.1
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/CHANGELOG.md +21 -0
- package/out/cmake.d.ts +1 -0
- package/out/cmake.js +89 -19
- package/out/example.js +24 -10
- package/out/index.js +5 -1
- package/out/qttest.d.ts +3 -0
- package/out/qttest.js +83 -17
- package/out/test.js +67 -26
- package/out/utils.js +3 -1
- package/package.json +9 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.6.1](https://github.com/KDAB/qttest-utils/compare/v2.6.0...v2.6.1) (2026-04-05)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* Improve gtest vs qttest detection ([75b2f57](https://github.com/KDAB/qttest-utils/commit/75b2f575962edc2e78737dc8615c5476bd62e8b2))
|
|
9
|
+
* Improve gtest vs qttest detection ([cb26f31](https://github.com/KDAB/qttest-utils/commit/cb26f316de3e882802e2c81df9c517e6658f9821))
|
|
10
|
+
|
|
11
|
+
## [2.6.0](https://github.com/KDAB/qttest-utils/compare/v2.5.0...v2.6.0) (2026-04-05)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
|
|
16
|
+
* propagate CTest ENVIRONMENT to spawned test processes ([ea24d96](https://github.com/KDAB/qttest-utils/commit/ea24d96545c3ac9d68fc9cfea8ab6b372055b804))
|
|
17
|
+
* propagate CTest ENVIRONMENT to spawned test processes ([6a68cc7](https://github.com/KDAB/qttest-utils/commit/6a68cc7db40f53f3631579477deb780d36b36685))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
* made eslint pass ([6740d61](https://github.com/KDAB/qttest-utils/commit/6740d618843a9a0a55fd73b7cc9e0c9f2412788e))
|
|
23
|
+
|
|
3
24
|
## [2.5.0](https://github.com/KDAB/qttest-utils/compare/v2.4.4...v2.5.0) (2026-04-04)
|
|
4
25
|
|
|
5
26
|
|
package/out/cmake.d.ts
CHANGED
package/out/cmake.js
CHANGED
|
@@ -28,7 +28,7 @@ class CMakeTests {
|
|
|
28
28
|
*/
|
|
29
29
|
async tests() {
|
|
30
30
|
// TODO: Check if folder exists
|
|
31
|
-
if (this.buildDirPath.length
|
|
31
|
+
if (this.buildDirPath.length === 0) {
|
|
32
32
|
console.error("Could not find out cmake build dir");
|
|
33
33
|
return undefined;
|
|
34
34
|
}
|
|
@@ -43,7 +43,7 @@ class CMakeTests {
|
|
|
43
43
|
});
|
|
44
44
|
child.on("close", (code) => {
|
|
45
45
|
if (code === 0) {
|
|
46
|
-
if (output.length
|
|
46
|
+
if (output.length === 0) {
|
|
47
47
|
console.error("ctestJsonToList: Empty json output. Command was ctest --show-only=json-v1 , in " +
|
|
48
48
|
this.buildDirPath);
|
|
49
49
|
reject(new Error("Failed to get ctest JSON output"));
|
|
@@ -68,20 +68,76 @@ class CMakeTests {
|
|
|
68
68
|
let test = new CMakeTest();
|
|
69
69
|
test.command = testJSON.command;
|
|
70
70
|
test.cwd = testJSON.cwd;
|
|
71
|
+
// Extract environment information if present in ctest JSON.
|
|
72
|
+
// ctest may expose environment as `environment` (string or array),
|
|
73
|
+
// or embed it into `properties` either as an object or array.
|
|
74
|
+
try {
|
|
75
|
+
let envArr = undefined;
|
|
76
|
+
if (testJSON.environment) {
|
|
77
|
+
envArr = Array.isArray(testJSON.environment)
|
|
78
|
+
? testJSON.environment
|
|
79
|
+
: [testJSON.environment];
|
|
80
|
+
}
|
|
81
|
+
if (!envArr && testJSON.properties) {
|
|
82
|
+
const props = testJSON.properties;
|
|
83
|
+
// properties might be an object with ENVIRONMENT key or an array of {name,value} entries
|
|
84
|
+
if (!Array.isArray(props) && props.ENVIRONMENT) {
|
|
85
|
+
const val = props.ENVIRONMENT;
|
|
86
|
+
if (typeof val === "string") {
|
|
87
|
+
envArr = [val];
|
|
88
|
+
}
|
|
89
|
+
else if (Array.isArray(val)) {
|
|
90
|
+
envArr = val;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else if (Array.isArray(props)) {
|
|
94
|
+
for (const p of props) {
|
|
95
|
+
if (!p || !p.name) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (p.name === "ENVIRONMENT" || p.name === "Environment") {
|
|
99
|
+
const v = p.value;
|
|
100
|
+
if (typeof v === "string") {
|
|
101
|
+
envArr = [v];
|
|
102
|
+
}
|
|
103
|
+
else if (Array.isArray(v)) {
|
|
104
|
+
envArr = v;
|
|
105
|
+
}
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (envArr) {
|
|
112
|
+
test.environment = envArr;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
try {
|
|
117
|
+
(0, qttest_1.logMessage)("ctest: failed to parse environment for test: " +
|
|
118
|
+
JSON.stringify(testJSON) +
|
|
119
|
+
" error: " +
|
|
120
|
+
e);
|
|
121
|
+
}
|
|
122
|
+
catch (_) { }
|
|
123
|
+
}
|
|
71
124
|
return test;
|
|
72
125
|
});
|
|
73
126
|
// filter invalid tests:
|
|
74
127
|
tests = tests.filter((test) => {
|
|
75
128
|
// pretty print test
|
|
76
|
-
if (!test.command || test.command.length
|
|
129
|
+
if (!test.command || test.command.length === 0) {
|
|
77
130
|
return false;
|
|
131
|
+
}
|
|
78
132
|
let testExecutablePath = test.executablePath();
|
|
79
133
|
let baseName = path_1.default.basename(testExecutablePath).toLowerCase();
|
|
80
|
-
if (baseName.endsWith(".exe"))
|
|
134
|
+
if (baseName.endsWith(".exe")) {
|
|
81
135
|
baseName = baseName.substring(0, baseName.length - 4);
|
|
136
|
+
}
|
|
82
137
|
// People doing complicated things in add_test()
|
|
83
|
-
if (baseName
|
|
138
|
+
if (baseName === "ctest" || baseName === "cmake") {
|
|
84
139
|
return false;
|
|
140
|
+
}
|
|
85
141
|
return true;
|
|
86
142
|
});
|
|
87
143
|
return tests;
|
|
@@ -90,16 +146,19 @@ class CMakeTests {
|
|
|
90
146
|
/// codemodel should have a "projects" key at root.
|
|
91
147
|
targetNameForExecutable(executable, codemodel, workaround = false) {
|
|
92
148
|
let projects = codemodel["projects"];
|
|
93
|
-
if (!projects)
|
|
149
|
+
if (!projects) {
|
|
94
150
|
return undefined;
|
|
151
|
+
}
|
|
95
152
|
for (let project of projects) {
|
|
96
153
|
let targets = project["targets"];
|
|
97
|
-
if (!targets)
|
|
154
|
+
if (!targets) {
|
|
98
155
|
continue;
|
|
156
|
+
}
|
|
99
157
|
for (let target of targets) {
|
|
100
158
|
let artifacts = target["artifacts"];
|
|
101
|
-
if (!artifacts)
|
|
159
|
+
if (!artifacts) {
|
|
102
160
|
continue;
|
|
161
|
+
}
|
|
103
162
|
for (let artifact of artifacts) {
|
|
104
163
|
if (artifact.endsWith(".exe")) {
|
|
105
164
|
artifact = artifact.substring(0, artifact.length - 4);
|
|
@@ -120,18 +179,21 @@ class CMakeTests {
|
|
|
120
179
|
/// If workaround is true, then we workaround microsoft/vscode-cmake-tools-api/issues/7 where
|
|
121
180
|
/// the basename is correct but the path is bogus, and we only compare the basenames
|
|
122
181
|
filenamesAreEqual(file1, file2, workaround = false) {
|
|
123
|
-
if (file1.endsWith(".exe"))
|
|
182
|
+
if (file1.endsWith(".exe")) {
|
|
124
183
|
file1 = file1.substring(0, file1.length - 4);
|
|
125
|
-
|
|
184
|
+
}
|
|
185
|
+
if (file2.endsWith(".exe")) {
|
|
126
186
|
file2 = file2.substring(0, file2.length - 4);
|
|
187
|
+
}
|
|
127
188
|
file1 = file1.replace(/\\/g, "/");
|
|
128
189
|
file2 = file2.replace(/\\/g, "/");
|
|
129
190
|
if (process.platform === "win32") {
|
|
130
191
|
file1 = file1.toLowerCase();
|
|
131
192
|
file2 = file2.toLowerCase();
|
|
132
193
|
}
|
|
133
|
-
if (file1
|
|
194
|
+
if (file1 === file2) {
|
|
134
195
|
return true;
|
|
196
|
+
}
|
|
135
197
|
if (!workaround) {
|
|
136
198
|
// files aren't equal!
|
|
137
199
|
return false;
|
|
@@ -142,7 +204,7 @@ class CMakeTests {
|
|
|
142
204
|
return false;
|
|
143
205
|
}
|
|
144
206
|
/// Compare only basename, since path is bogus
|
|
145
|
-
return path_1.default.basename(file1, ".exe")
|
|
207
|
+
return path_1.default.basename(file1, ".exe") === path_1.default.basename(file2, ".exe");
|
|
146
208
|
}
|
|
147
209
|
/// Returns the list of .cpp files for the specified executable
|
|
148
210
|
/// codemodel is the CMake codemodel JSON object
|
|
@@ -150,20 +212,24 @@ class CMakeTests {
|
|
|
150
212
|
/// @param workaround If true, worksaround https://github.com/microsoft/vscode-cmake-tools-api/issues/7
|
|
151
213
|
cppFilesForExecutable(executable, codemodel, workaround = false) {
|
|
152
214
|
let projects = codemodel["projects"];
|
|
153
|
-
if (!projects)
|
|
215
|
+
if (!projects) {
|
|
154
216
|
return [];
|
|
217
|
+
}
|
|
155
218
|
for (let project of projects) {
|
|
156
219
|
let targets = project["targets"];
|
|
157
|
-
if (!targets)
|
|
220
|
+
if (!targets) {
|
|
158
221
|
continue;
|
|
222
|
+
}
|
|
159
223
|
for (let target of targets) {
|
|
160
224
|
let sourceDir = target["sourceDirectory"];
|
|
161
225
|
let artifacts = target["artifacts"];
|
|
162
|
-
if (!artifacts || !sourceDir)
|
|
226
|
+
if (!artifacts || !sourceDir) {
|
|
163
227
|
continue;
|
|
228
|
+
}
|
|
164
229
|
let targetType = target["type"];
|
|
165
|
-
if (targetType
|
|
230
|
+
if (targetType !== "EXECUTABLE") {
|
|
166
231
|
continue;
|
|
232
|
+
}
|
|
167
233
|
for (let artifact of artifacts) {
|
|
168
234
|
if (artifact.endsWith(".exe")) {
|
|
169
235
|
artifact = artifact.substring(0, artifact.length - 4);
|
|
@@ -172,14 +238,17 @@ class CMakeTests {
|
|
|
172
238
|
artifact = artifact.replace(/\\/g, "/");
|
|
173
239
|
if (this.filenamesAreEqual(executable, artifact, workaround)) {
|
|
174
240
|
let fileGroups = target["fileGroups"];
|
|
175
|
-
if (!fileGroups)
|
|
241
|
+
if (!fileGroups) {
|
|
176
242
|
continue;
|
|
243
|
+
}
|
|
177
244
|
for (let fileGroup of fileGroups) {
|
|
178
|
-
if (fileGroup["language"]
|
|
245
|
+
if (fileGroup["language"] !== "CXX" || fileGroup["isGenerated"]) {
|
|
179
246
|
continue;
|
|
247
|
+
}
|
|
180
248
|
let sources = fileGroup["sources"];
|
|
181
|
-
if (!sources)
|
|
249
|
+
if (!sources) {
|
|
182
250
|
continue;
|
|
251
|
+
}
|
|
183
252
|
let cppFiles = [];
|
|
184
253
|
for (let source of sources) {
|
|
185
254
|
if (!source.endsWith("mocs_compilation.cpp")) {
|
|
@@ -202,6 +271,7 @@ exports.CMakeTests = CMakeTests;
|
|
|
202
271
|
class CMakeTest {
|
|
203
272
|
command = [];
|
|
204
273
|
cwd = "";
|
|
274
|
+
environment = [];
|
|
205
275
|
id() {
|
|
206
276
|
return this.command.join(",");
|
|
207
277
|
}
|
package/out/example.js
CHANGED
|
@@ -10,13 +10,13 @@ const qttest_1 = require("./qttest");
|
|
|
10
10
|
const fs_1 = __importDefault(require("fs"));
|
|
11
11
|
async function example() {
|
|
12
12
|
const args = process.argv.slice(2);
|
|
13
|
-
if (args.length
|
|
13
|
+
if (args.length !== 1) {
|
|
14
14
|
console.error("ERROR: Expected a single argument with the build-dir with cmake tests!");
|
|
15
15
|
process.exit(2);
|
|
16
16
|
}
|
|
17
17
|
let buildDirPath = args[0];
|
|
18
18
|
if (!fs_1.default.existsSync(buildDirPath)) {
|
|
19
|
-
console.error(
|
|
19
|
+
console.error("Directory does not exist!");
|
|
20
20
|
process.exit(1);
|
|
21
21
|
}
|
|
22
22
|
let qt = new qttest_1.QtTests();
|
|
@@ -36,13 +36,23 @@ async function example() {
|
|
|
36
36
|
console.log("\nRunning tests...");
|
|
37
37
|
for (var executable of qt.qtTestExecutables) {
|
|
38
38
|
await executable.runTest();
|
|
39
|
-
if (executable.lastExitCode === 0)
|
|
39
|
+
if (executable.lastExitCode === 0) {
|
|
40
40
|
console.log(" PASS: " + executable.filename);
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
console.log(" FAIL: " +
|
|
44
|
+
executable.filename +
|
|
45
|
+
"; code=" +
|
|
46
|
+
executable.lastExitCode);
|
|
47
|
+
}
|
|
43
48
|
for (let slot of executable.slots) {
|
|
44
49
|
if (slot.lastTestFailure) {
|
|
45
|
-
console.log(" failed slot=" +
|
|
50
|
+
console.log(" failed slot=" +
|
|
51
|
+
slot.name +
|
|
52
|
+
"; path=" +
|
|
53
|
+
slot.lastTestFailure.filePath +
|
|
54
|
+
"; line=" +
|
|
55
|
+
slot.lastTestFailure.lineNumber);
|
|
46
56
|
}
|
|
47
57
|
else {
|
|
48
58
|
console.log(" pass: " + slot.name);
|
|
@@ -53,15 +63,19 @@ async function example() {
|
|
|
53
63
|
console.log("\nRunning single tests...");
|
|
54
64
|
let slot = qt.qtTestExecutables[1].slots[0];
|
|
55
65
|
await slot.runTest();
|
|
56
|
-
if (slot.lastTestFailure)
|
|
66
|
+
if (slot.lastTestFailure) {
|
|
57
67
|
console.log(" FAIL:" + JSON.stringify(slot.lastTestFailure));
|
|
58
|
-
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
59
70
|
console.log(" PASS:");
|
|
71
|
+
}
|
|
60
72
|
let slot2 = qt.qtTestExecutables[1].slots[2];
|
|
61
73
|
await slot2.runTest();
|
|
62
|
-
if (slot2.lastTestFailure)
|
|
74
|
+
if (slot2.lastTestFailure) {
|
|
63
75
|
console.log(" FAIL:" + JSON.stringify(slot2.lastTestFailure));
|
|
64
|
-
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
65
78
|
console.log(" PASS");
|
|
79
|
+
}
|
|
66
80
|
}
|
|
67
81
|
example();
|
package/out/index.js
CHANGED
|
@@ -6,6 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const cmake_1 = require("./cmake");
|
|
7
7
|
const qttest_1 = require("./qttest");
|
|
8
8
|
const qttest = {
|
|
9
|
-
QtTests: qttest_1.QtTests,
|
|
9
|
+
QtTests: qttest_1.QtTests,
|
|
10
|
+
QtTest: qttest_1.QtTest,
|
|
11
|
+
QtTestSlot: qttest_1.QtTestSlot,
|
|
12
|
+
CMakeTests: cmake_1.CMakeTests,
|
|
13
|
+
CMakeTest: cmake_1.CMakeTest,
|
|
10
14
|
};
|
|
11
15
|
exports.default = qttest;
|
package/out/qttest.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export declare class QtTest {
|
|
|
10
10
|
verbose: boolean;
|
|
11
11
|
vscodeTestItem: any | undefined;
|
|
12
12
|
slots: QtTestSlot[] | null;
|
|
13
|
+
environment: string[];
|
|
13
14
|
lastExitCode: number;
|
|
14
15
|
outputFunc: LoggerFunction | undefined;
|
|
15
16
|
constructor(filename: string, buildDirPath: string);
|
|
@@ -17,6 +18,7 @@ export declare class QtTest {
|
|
|
17
18
|
get label(): string;
|
|
18
19
|
relativeFilename(): string;
|
|
19
20
|
filenameWithoutExtension(): string;
|
|
21
|
+
private buildSpawnEnv;
|
|
20
22
|
/**
|
|
21
23
|
* Calls "./yourqttest -functions" and stores the results in the slots property.
|
|
22
24
|
*/
|
|
@@ -30,6 +32,7 @@ export declare class QtTest {
|
|
|
30
32
|
*/
|
|
31
33
|
linksToQtTestLib(): Promise<boolean> | undefined;
|
|
32
34
|
isQtTestViaHelp(): Promise<boolean | undefined>;
|
|
35
|
+
isGTest(): Promise<boolean>;
|
|
33
36
|
slotByName(name: string): QtTestSlot | undefined;
|
|
34
37
|
runTest(slot?: QtTestSlot, cwd?: string): Promise<boolean>;
|
|
35
38
|
tapOutputFileName(slot?: QtTestSlot): string;
|
package/out/qttest.js
CHANGED
|
@@ -65,6 +65,8 @@ class QtTest {
|
|
|
65
65
|
vscodeTestItem;
|
|
66
66
|
/// The list of individual runnable test slots
|
|
67
67
|
slots = null;
|
|
68
|
+
/// Environment variables coming from CTest (array of "VAR=VALUE")
|
|
69
|
+
environment = [];
|
|
68
70
|
/// Set after running
|
|
69
71
|
lastExitCode = 0;
|
|
70
72
|
/// Allows the caller to receive the output of the test process
|
|
@@ -82,8 +84,9 @@ class QtTest {
|
|
|
82
84
|
relativeFilename() {
|
|
83
85
|
let result = path_1.default.relative(process.cwd(), this.filename);
|
|
84
86
|
// strip .exe, as we only use this for tests
|
|
85
|
-
if (result.endsWith(".exe"))
|
|
87
|
+
if (result.endsWith(".exe")) {
|
|
86
88
|
result = result.slice(0, -4);
|
|
89
|
+
}
|
|
87
90
|
// normalize slashes
|
|
88
91
|
result = result.replace(/\\/g, "/");
|
|
89
92
|
return result;
|
|
@@ -91,14 +94,32 @@ class QtTest {
|
|
|
91
94
|
/// returns filename without .exe extension
|
|
92
95
|
filenameWithoutExtension() {
|
|
93
96
|
let result = this.filename;
|
|
94
|
-
if (result.endsWith(".exe"))
|
|
97
|
+
if (result.endsWith(".exe")) {
|
|
95
98
|
result = result.slice(0, -4);
|
|
99
|
+
}
|
|
96
100
|
return result;
|
|
97
101
|
}
|
|
102
|
+
buildSpawnEnv() {
|
|
103
|
+
const env = Object.assign({}, process.env);
|
|
104
|
+
if (this.environment && Array.isArray(this.environment)) {
|
|
105
|
+
for (const kv of this.environment) {
|
|
106
|
+
const idx = kv.indexOf("=");
|
|
107
|
+
if (idx > -1) {
|
|
108
|
+
env[kv.substring(0, idx)] = kv.substring(idx + 1);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return env;
|
|
113
|
+
}
|
|
98
114
|
/**
|
|
99
115
|
* Calls "./yourqttest -functions" and stores the results in the slots property.
|
|
100
116
|
*/
|
|
101
117
|
async parseAvailableSlots() {
|
|
118
|
+
if (await this.isGTest()) {
|
|
119
|
+
logMessage("qttest: Skipping -functions for GTest executable: " + this.filename);
|
|
120
|
+
this.slots = [];
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
102
123
|
let slotNames = [];
|
|
103
124
|
let output = "";
|
|
104
125
|
let err = "";
|
|
@@ -109,6 +130,7 @@ class QtTest {
|
|
|
109
130
|
}
|
|
110
131
|
const child = (0, child_process_1.spawn)(this.filename, ["-functions"], {
|
|
111
132
|
cwd: this.buildDirPath,
|
|
133
|
+
env: this.buildSpawnEnv(),
|
|
112
134
|
});
|
|
113
135
|
child.stdout.on("data", (chunk) => {
|
|
114
136
|
output += chunk.toString();
|
|
@@ -154,8 +176,9 @@ class QtTest {
|
|
|
154
176
|
return undefined;
|
|
155
177
|
}
|
|
156
178
|
return new Promise((resolve, reject) => {
|
|
157
|
-
if (this.verbose)
|
|
179
|
+
if (this.verbose) {
|
|
158
180
|
logMessage("qttest: Running ldd on " + this.filename);
|
|
181
|
+
}
|
|
159
182
|
const child = (0, child_process_1.spawn)("ldd", [this.filename]);
|
|
160
183
|
let output = "";
|
|
161
184
|
let result = false;
|
|
@@ -166,8 +189,9 @@ class QtTest {
|
|
|
166
189
|
result = true;
|
|
167
190
|
}
|
|
168
191
|
}
|
|
169
|
-
if (this.verbose)
|
|
192
|
+
if (this.verbose) {
|
|
170
193
|
logMessage(chunk.toString());
|
|
194
|
+
}
|
|
171
195
|
});
|
|
172
196
|
child.on("exit", (code) => {
|
|
173
197
|
if (code === 0) {
|
|
@@ -183,7 +207,9 @@ class QtTest {
|
|
|
183
207
|
/// Note that if this is not a QtTest it might not run help and instead execute the test itself
|
|
184
208
|
async isQtTestViaHelp() {
|
|
185
209
|
return await new Promise((resolve, reject) => {
|
|
186
|
-
const child = (0, child_process_1.spawn)(this.filename, ["-help"]
|
|
210
|
+
const child = (0, child_process_1.spawn)(this.filename, ["-help"], {
|
|
211
|
+
env: this.buildSpawnEnv(),
|
|
212
|
+
});
|
|
187
213
|
let output = "";
|
|
188
214
|
let result = false;
|
|
189
215
|
child.stdout.on("data", (chunk) => {
|
|
@@ -203,12 +229,41 @@ class QtTest {
|
|
|
203
229
|
});
|
|
204
230
|
});
|
|
205
231
|
}
|
|
232
|
+
/// Returns whether this executable is a Google Test by running it with --help
|
|
233
|
+
/// and checking if the output contains the GTest banner
|
|
234
|
+
async isGTest() {
|
|
235
|
+
return await new Promise((resolve) => {
|
|
236
|
+
if (!fs.existsSync(this.filename)) {
|
|
237
|
+
resolve(false);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const child = (0, child_process_1.spawn)(this.filename, ["--help"], {
|
|
241
|
+
env: this.buildSpawnEnv(),
|
|
242
|
+
});
|
|
243
|
+
let output = "";
|
|
244
|
+
let found = false;
|
|
245
|
+
child.stdout.on("data", (chunk) => {
|
|
246
|
+
if (!found) {
|
|
247
|
+
if (chunk
|
|
248
|
+
.toString()
|
|
249
|
+
.includes("This program contains tests written using Google Test")) {
|
|
250
|
+
found = true;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
child.on("exit", () => {
|
|
255
|
+
resolve(found);
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
}
|
|
206
259
|
slotByName(name) {
|
|
207
|
-
if (!this.slots)
|
|
260
|
+
if (!this.slots) {
|
|
208
261
|
return undefined;
|
|
262
|
+
}
|
|
209
263
|
for (let slot of this.slots) {
|
|
210
|
-
if (slot.name
|
|
264
|
+
if (slot.name === name) {
|
|
211
265
|
return slot;
|
|
266
|
+
}
|
|
212
267
|
}
|
|
213
268
|
return undefined;
|
|
214
269
|
}
|
|
@@ -234,22 +289,28 @@ class QtTest {
|
|
|
234
289
|
args.join(" ") +
|
|
235
290
|
" with cwd=" +
|
|
236
291
|
cwdDir);
|
|
237
|
-
const child = (0, child_process_1.spawn)(this.filename, args, {
|
|
292
|
+
const child = (0, child_process_1.spawn)(this.filename, args, {
|
|
293
|
+
cwd: cwdDir,
|
|
294
|
+
env: this.buildSpawnEnv(),
|
|
295
|
+
});
|
|
238
296
|
if (this.outputFunc) {
|
|
239
297
|
// Callers wants the process output:
|
|
240
298
|
child.stdout.on("data", (chunk) => {
|
|
241
|
-
if (this.outputFunc)
|
|
299
|
+
if (this.outputFunc) {
|
|
242
300
|
this.outputFunc(chunk.toString());
|
|
301
|
+
}
|
|
243
302
|
});
|
|
244
303
|
child.stderr.on("data", (chunk) => {
|
|
245
|
-
if (this.outputFunc)
|
|
304
|
+
if (this.outputFunc) {
|
|
246
305
|
this.outputFunc(chunk.toString());
|
|
306
|
+
}
|
|
247
307
|
});
|
|
248
308
|
}
|
|
249
309
|
child.on("exit", async (code) => {
|
|
250
310
|
/// Can code even be null ?
|
|
251
|
-
if (code
|
|
311
|
+
if (code === undefined || code === null) {
|
|
252
312
|
code = -1;
|
|
313
|
+
}
|
|
253
314
|
if (!slot) {
|
|
254
315
|
this.lastExitCode = code;
|
|
255
316
|
}
|
|
@@ -303,13 +364,15 @@ class QtTest {
|
|
|
303
364
|
else {
|
|
304
365
|
let failedResults = [];
|
|
305
366
|
try {
|
|
306
|
-
const
|
|
307
|
-
for (let event of
|
|
367
|
+
const tapEvents = tap_parser_1.Parser.parse(data);
|
|
368
|
+
for (let event of tapEvents) {
|
|
308
369
|
try {
|
|
309
|
-
if (event.length < 2)
|
|
370
|
+
if (event.length < 2) {
|
|
310
371
|
continue;
|
|
311
|
-
|
|
372
|
+
}
|
|
373
|
+
if (event.at(0) !== "assert") {
|
|
312
374
|
continue;
|
|
375
|
+
}
|
|
313
376
|
var obj = event.at(1);
|
|
314
377
|
let pass = obj["ok"] === true;
|
|
315
378
|
let xfail = !pass && obj["todo"] !== false;
|
|
@@ -348,7 +411,7 @@ class QtTest {
|
|
|
348
411
|
});
|
|
349
412
|
});
|
|
350
413
|
for (let failure of failures) {
|
|
351
|
-
if (slot && slot.name
|
|
414
|
+
if (slot && slot.name !== failure.name) {
|
|
352
415
|
// We executed a single slot, ignore anything else
|
|
353
416
|
continue;
|
|
354
417
|
}
|
|
@@ -407,6 +470,8 @@ class QtTests {
|
|
|
407
470
|
if (ctests) {
|
|
408
471
|
for (let ctest of ctests) {
|
|
409
472
|
let qtest = new QtTest(ctest.executablePath(), buildDirPath);
|
|
473
|
+
// Propagate environment from CTest metadata
|
|
474
|
+
qtest.environment = ctest.environment;
|
|
410
475
|
this.qtTestExecutables.push(qtest);
|
|
411
476
|
}
|
|
412
477
|
}
|
|
@@ -453,8 +518,9 @@ class QtTests {
|
|
|
453
518
|
}
|
|
454
519
|
async dumpTestSlots() {
|
|
455
520
|
for (var ex of this.qtTestExecutables) {
|
|
456
|
-
if (!ex.slots)
|
|
521
|
+
if (!ex.slots) {
|
|
457
522
|
await ex.parseAvailableSlots();
|
|
523
|
+
}
|
|
458
524
|
console.log(ex.filename);
|
|
459
525
|
if (ex.slots) {
|
|
460
526
|
for (let slot of ex.slots) {
|
package/out/test.js
CHANGED
|
@@ -10,23 +10,64 @@ const qttest_1 = require("./qttest");
|
|
|
10
10
|
async function runTests(buildDirPath) {
|
|
11
11
|
let qt = new qttest_1.QtTests();
|
|
12
12
|
await qt.discoverViaCMake(buildDirPath);
|
|
13
|
-
|
|
13
|
+
// Verify that environment properties from CTest were discovered for test1
|
|
14
|
+
const test1Exe = qt.qtTestExecutables.find((e) => e.filenameWithoutExtension().endsWith("test1"));
|
|
15
|
+
if (!test1Exe) {
|
|
16
|
+
console.error("Expected to find test1 executable after discovery");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
if (!test1Exe.environment || !test1Exe.environment.includes("MY_ENV=VALUE")) {
|
|
20
|
+
console.error("Expected test1 to have environment MY_ENV=VALUE, got: " +
|
|
21
|
+
JSON.stringify(test1Exe.environment));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
let expectedExecutables = [
|
|
14
25
|
"test/qt_test/build-dev/test1",
|
|
15
26
|
"test/qt_test/build-dev/test2",
|
|
16
27
|
"test/qt_test/build-dev/test3",
|
|
17
28
|
"test/qt_test/build-dev/non_qttest",
|
|
29
|
+
"test/qt_test/build-dev/test_gtest",
|
|
18
30
|
];
|
|
19
|
-
if (qt.qtTestExecutables.length
|
|
20
|
-
console.error("Expected
|
|
31
|
+
if (qt.qtTestExecutables.length !== expectedExecutables.length) {
|
|
32
|
+
console.error("Expected " +
|
|
33
|
+
expectedExecutables.length +
|
|
34
|
+
" executables, got " +
|
|
35
|
+
qt.qtTestExecutables.length);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
// Verify that test_gtest is detected as a GTest
|
|
39
|
+
const gtestExe = qt.qtTestExecutables.find((e) => e.filenameWithoutExtension().endsWith("test_gtest"));
|
|
40
|
+
if (!gtestExe) {
|
|
41
|
+
console.error("Expected to find test_gtest executable after discovery");
|
|
21
42
|
process.exit(1);
|
|
22
43
|
}
|
|
44
|
+
if (!(await gtestExe.isGTest())) {
|
|
45
|
+
console.error("Expected test_gtest to be detected as a GTest");
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
console.log("PASS: test_gtest detected as GTest");
|
|
49
|
+
// Verify that test_gtest has no slots after parseAvailableSlots (GTest is skipped)
|
|
50
|
+
await gtestExe.parseAvailableSlots();
|
|
51
|
+
if (!gtestExe.slots || gtestExe.slots.length !== 0) {
|
|
52
|
+
console.error("Expected test_gtest to have 0 slots, got " +
|
|
53
|
+
(gtestExe.slots?.length ?? "null"));
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
console.log("PASS: test_gtest has 0 slots (no children)");
|
|
57
|
+
// Verify that a QtTest is not detected as a GTest
|
|
58
|
+
if (await test1Exe.isGTest()) {
|
|
59
|
+
console.error("Expected test1 to NOT be detected as a GTest");
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
console.log("PASS: test1 (QtTest) not misdetected as GTest");
|
|
23
63
|
await qt.removeNonLinking();
|
|
24
64
|
/// On macOS and Windows we don't have ldd or equivalent, so we can't check if the test links to QtTest
|
|
25
65
|
/// Use the help way instead
|
|
26
66
|
await qt.removeByRunningHelp();
|
|
27
|
-
/// Remove the non-qttest
|
|
28
|
-
qt.qtTestExecutables = qt.qtTestExecutables.filter((e) => !e.filenameWithoutExtension().endsWith("non_qttest")
|
|
29
|
-
|
|
67
|
+
/// Remove the non-qttest and gtest executables from qt.qtTestExecutables
|
|
68
|
+
qt.qtTestExecutables = qt.qtTestExecutables.filter((e) => !e.filenameWithoutExtension().endsWith("non_qttest") &&
|
|
69
|
+
!e.filenameWithoutExtension().endsWith("test_gtest"));
|
|
70
|
+
if (qt.qtTestExecutables.length !== 3) {
|
|
30
71
|
console.error("Expected 3 executables, at this point got " +
|
|
31
72
|
qt.qtTestExecutables.length);
|
|
32
73
|
process.exit(1);
|
|
@@ -34,8 +75,8 @@ async function runTests(buildDirPath) {
|
|
|
34
75
|
// 1. Test that the executable test names are correct:
|
|
35
76
|
var i = 0;
|
|
36
77
|
for (var executable of qt.qtTestExecutables) {
|
|
37
|
-
let expected =
|
|
38
|
-
if (executable.relativeFilename()
|
|
78
|
+
let expected = expectedExecutables[i];
|
|
79
|
+
if (executable.relativeFilename() !== expected) {
|
|
39
80
|
console.error("Expected executable " +
|
|
40
81
|
expected +
|
|
41
82
|
", got " +
|
|
@@ -46,7 +87,7 @@ async function runTests(buildDirPath) {
|
|
|
46
87
|
}
|
|
47
88
|
// 2. Test that the discovered slots are correct:
|
|
48
89
|
await qt.dumpTestSlots();
|
|
49
|
-
let
|
|
90
|
+
let expectedSlots = {
|
|
50
91
|
"test/qt_test/build-dev/test1": ["testA", "testB", "testC", "testXFAIL"],
|
|
51
92
|
"test/qt_test/build-dev/test2": [
|
|
52
93
|
"testD",
|
|
@@ -60,25 +101,25 @@ async function runTests(buildDirPath) {
|
|
|
60
101
|
for (var executable of qt.qtTestExecutables) {
|
|
61
102
|
var i = 0;
|
|
62
103
|
for (let slot of executable.slots) {
|
|
63
|
-
let
|
|
64
|
-
if (slot.name
|
|
65
|
-
console.error("Expected slot " +
|
|
104
|
+
let expectedSlot = expectedSlots[executable.relativeFilename()][i];
|
|
105
|
+
if (slot.name !== expectedSlot) {
|
|
106
|
+
console.error("Expected slot " + expectedSlot + ", got " + slot.name);
|
|
66
107
|
process.exit(1);
|
|
67
108
|
}
|
|
68
109
|
i++;
|
|
69
110
|
}
|
|
70
111
|
}
|
|
71
112
|
// 3. Run the tests:
|
|
72
|
-
let
|
|
113
|
+
let expectedSuccess = [true, false, false];
|
|
73
114
|
var i = 0;
|
|
74
115
|
for (var executable of qt.qtTestExecutables) {
|
|
75
116
|
await executable.runTest();
|
|
76
117
|
let wasSuccess = executable.lastExitCode === 0;
|
|
77
|
-
if (wasSuccess && !
|
|
118
|
+
if (wasSuccess && !expectedSuccess[i]) {
|
|
78
119
|
console.error("Expected test to fail: " + executable.filename);
|
|
79
120
|
process.exit(1);
|
|
80
121
|
}
|
|
81
|
-
else if (!wasSuccess &&
|
|
122
|
+
else if (!wasSuccess && expectedSuccess[i]) {
|
|
82
123
|
console.error("Expected test to pass: " + executable.filename);
|
|
83
124
|
process.exit(1);
|
|
84
125
|
}
|
|
@@ -105,7 +146,7 @@ async function runTests(buildDirPath) {
|
|
|
105
146
|
}
|
|
106
147
|
// 5. Test executablesContainingSlot
|
|
107
148
|
let executables = qt.executablesContainingSlot("testB");
|
|
108
|
-
if (executables.length
|
|
149
|
+
if (executables.length !== 1) {
|
|
109
150
|
console.error("Expected 1 executable, got " + executables.length);
|
|
110
151
|
process.exit(1);
|
|
111
152
|
}
|
|
@@ -114,13 +155,13 @@ async function runTests(buildDirPath) {
|
|
|
114
155
|
process.exit(1);
|
|
115
156
|
}
|
|
116
157
|
executables = qt.executablesContainingSlot("non_existing");
|
|
117
|
-
if (executables.length
|
|
158
|
+
if (executables.length !== 0) {
|
|
118
159
|
console.error("Expected 0 executables, got " + executables.length);
|
|
119
160
|
process.exit(1);
|
|
120
161
|
}
|
|
121
162
|
// 6. Run a slot that has XFAIL
|
|
122
163
|
slot = qt.qtTestExecutables[0].slots[3];
|
|
123
|
-
if (slot.name
|
|
164
|
+
if (slot.name !== "testXFAIL") {
|
|
124
165
|
console.error("Expected slot name to be testXFAIL");
|
|
125
166
|
process.exit(1);
|
|
126
167
|
}
|
|
@@ -131,7 +172,7 @@ async function runTests(buildDirPath) {
|
|
|
131
172
|
}
|
|
132
173
|
// 7. Run a slot that has XPASS
|
|
133
174
|
slot = qt.qtTestExecutables[1].slots[3];
|
|
134
|
-
if (slot.name
|
|
175
|
+
if (slot.name !== "testXPASS") {
|
|
135
176
|
console.error("Expected slot name to be testXPASS");
|
|
136
177
|
process.exit(1);
|
|
137
178
|
}
|
|
@@ -142,7 +183,7 @@ async function runTests(buildDirPath) {
|
|
|
142
183
|
}
|
|
143
184
|
// 8. Run a slot that has both XFAIL and FAIL
|
|
144
185
|
slot = qt.qtTestExecutables[1].slots[4];
|
|
145
|
-
if (slot.name
|
|
186
|
+
if (slot.name !== "testMixXFAILWithFAIL") {
|
|
146
187
|
console.error("Expected slot name to be testMixXFAILWithFAIL");
|
|
147
188
|
process.exit(1);
|
|
148
189
|
}
|
|
@@ -158,29 +199,29 @@ async function runCodeModelTests(codeModelFile) {
|
|
|
158
199
|
let codemodelJson = JSON.parse(codemodelStr);
|
|
159
200
|
let cmake = new cmake_1.CMakeTests("random");
|
|
160
201
|
let files = cmake.cppFilesForExecutable("/vscode-qttest/test/qt_test/build-dev/test1", codemodelJson);
|
|
161
|
-
if (files.length
|
|
202
|
+
if (files.length !== 1) {
|
|
162
203
|
console.error("Expected 1 file, got " + files.length);
|
|
163
204
|
process.exit(1);
|
|
164
205
|
}
|
|
165
206
|
let expected = "/vscode-qttest/test/qt_test/test1.cpp";
|
|
166
207
|
let got = files[0].replace(/\\/g, "/");
|
|
167
|
-
if (got
|
|
208
|
+
if (got !== expected) {
|
|
168
209
|
console.error("Expected " + expected + ", got " + got);
|
|
169
210
|
process.exit(1);
|
|
170
211
|
}
|
|
171
212
|
let targetName = cmake.targetNameForExecutable("/vscode-qttest/test/qt_test/build-dev/test1", codemodelJson);
|
|
172
|
-
if (targetName
|
|
213
|
+
if (targetName !== "test1") {
|
|
173
214
|
console.error("Expected test1, got " + targetName);
|
|
174
215
|
process.exit(1);
|
|
175
216
|
}
|
|
176
217
|
// test windows back slashes:
|
|
177
218
|
files = cmake.cppFilesForExecutable("/vscode-qttest/test/qt_test/build-dev/test2", codemodelJson);
|
|
178
|
-
if (files.length
|
|
219
|
+
if (files.length !== 1) {
|
|
179
220
|
console.error("Expected 1 file, got " + files.length);
|
|
180
221
|
process.exit(1);
|
|
181
222
|
}
|
|
182
223
|
targetName = cmake.targetNameForExecutable("/vscode-qttest/test/qt_test/build-dev/test2", codemodelJson);
|
|
183
|
-
if (targetName
|
|
224
|
+
if (targetName !== "test2") {
|
|
184
225
|
console.error("Expected test2, got " + targetName);
|
|
185
226
|
process.exit(1);
|
|
186
227
|
}
|
|
@@ -205,7 +246,7 @@ async function runCodeModelTests(codeModelFile) {
|
|
|
205
246
|
}
|
|
206
247
|
targetName = cmake.targetNameForExecutable("/vscode-qttest/test/qt_test/build-dev/test3", codemodelJson,
|
|
207
248
|
/*workaround=*/ true);
|
|
208
|
-
if (targetName
|
|
249
|
+
if (targetName !== "test3") {
|
|
209
250
|
console.error("Expected null, got " + targetName);
|
|
210
251
|
process.exit(1);
|
|
211
252
|
}
|
package/out/utils.js
CHANGED
|
@@ -87,7 +87,9 @@ function executableFiles(folderPath) {
|
|
|
87
87
|
if (info.isDirectory()) {
|
|
88
88
|
executables = executables.concat(executableFiles(childPath));
|
|
89
89
|
}
|
|
90
|
-
else if (info.isFile() &&
|
|
90
|
+
else if (info.isFile() &&
|
|
91
|
+
!isLibrary(path_1.default.basename(childPath)) &&
|
|
92
|
+
isExecutable(childPath)) {
|
|
91
93
|
executables.push(childPath);
|
|
92
94
|
}
|
|
93
95
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iamsergio/qttest-utils",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.1",
|
|
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",
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"build": "tsc",
|
|
11
|
-
"prepublishOnly": "npm run build"
|
|
11
|
+
"prepublishOnly": "npm run build",
|
|
12
|
+
"format:check": "prettier --check \"src/**/*.ts\"",
|
|
13
|
+
"format:fix": "prettier --write \"src/**/*.ts\""
|
|
12
14
|
},
|
|
13
15
|
"main": "out/index.js",
|
|
14
16
|
"typings": "./out/index.d.ts",
|
|
@@ -30,10 +32,13 @@
|
|
|
30
32
|
},
|
|
31
33
|
"devDependencies": {
|
|
32
34
|
"@types/node": "^22.0.0",
|
|
35
|
+
"eslint": "^10.2.0",
|
|
36
|
+
"prettier": "3.8.1",
|
|
33
37
|
"ts-node": "^10.9.2",
|
|
34
|
-
"typescript": "^
|
|
38
|
+
"typescript": "^6.0.2",
|
|
39
|
+
"typescript-eslint": "^8.0.0"
|
|
35
40
|
},
|
|
36
41
|
"dependencies": {
|
|
37
|
-
"tap-parser": "^
|
|
42
|
+
"tap-parser": "^18.3.0"
|
|
38
43
|
}
|
|
39
44
|
}
|