Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Improvements:
- No longer convert paths on lowercase on MacOS to enable cpp tools to resolve them. [#4325](https://github.com/microsoft/vscode-cmake-tools/pull/4325) [@tringenbach](https://github.com/tringenbach)
- Speedup & reduce heap allocations in shlex split module function. Significant gains for mid-large compile_commands.json - CompilationDatabase construction. [#4458](https://github.com/microsoft/vscode-cmake-tools/pull/4458) [@borjamunozf](https://github.com/borjamunozf)
- In the Test Explorer, associate CTest tests with outermost function or macro invocation that calls `add_test()` instead of with the `add_test()` call itself. [#4490](https://github.com/microsoft/vscode-cmake-tools/issues/4490) [@malsyned](https://github.com/malsyned)
- When CMake is invoked prior to running tests, build targets required for the test rather than everything. [#4515](https://github.com/microsoft/vscode-cmake-tools/issues/4515) [@epistax](https://github.com/epistax)

Bug Fixes:

Expand Down
99 changes: 74 additions & 25 deletions src/ctest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1275,44 +1275,93 @@ export class CTestDriver implements vscode.Disposable {
return currentTestItem.id;
}

/**
* Determine and build all targets needed to rebuild the selected TestItems. Returns a false if anything goes
* wrong, and returns an early success if build-before-run is disabled.
*/
private async buildTests(tests: vscode.TestItem[], run: vscode.TestRun): Promise<boolean> {
// If buildBeforeRun is set to false, we skip the build step
if (!this.ws.config.buildBeforeRun) {
return true;
}

// Folder => status
const builtFolder = new Map<string, number>();
let status: number = 0;
const foundTarget = new Map<CMakeProject, Map<string, vscode.TestItem[]>>();
for (const test of tests) {
if (!await this.getTestTargets(test, foundTarget, run)) {
return false;
}
}

return this.buildTestTargets(foundTarget, run);
}

/**
* Given the TestItem, determine the owning CMakeProject and build targets to build the tests. If the given
* TestItem is a suite, then recurse. Information is passed back through the foundTarget parameter. A failure to
* identify the project of the test will result in a ctest error and a false value being returned.
*/
private async getTestTargets(test: vscode.TestItem, foundTarget: Map<CMakeProject, Map<string, vscode.TestItem[]>>, run: vscode.TestRun): Promise<boolean> {
if (test.children.size > 0) {
const children = this.testItemCollectionToArray(test.children);
for (const child of children) {
await this.getTestTargets(child, foundTarget, run);
}
} else {
const testProgram = this.testProgram(test.id);
const folder = this.getTestRootFolder(test);
if (!builtFolder.has(folder)) {
const project = await this.projectController?.getProjectForFolder(folder);
if (!project) {
status = 1;
const project = await this.projectController?.getProjectForFolder(folder);
if (!project) {
this.ctestErrored(test, run, { message: localize('no.project.found', 'No project found for folder {0}', folder) });
return false;
}
if (!foundTarget.has(project)) {
foundTarget.set(project, new Map<string, vscode.TestItem[]>([[testProgram, [test]]]));
} else {
const prj = foundTarget.get(project)!;
if (!prj.has(testProgram)) {
prj.set(testProgram, [test]);
} else {
try {
if (extensionManager !== undefined && extensionManager !== null) {
extensionManager.cleanOutputChannel();
}
const buildResult = await project.build(undefined, false, false);
if (buildResult !== 0) {
status = 2;
}
} catch (e) {
status = 2;
}
prj.get(testProgram)!.push(test);
}
}
builtFolder.set(folder, status);
if (status === 1) {
this.ctestErrored(test, run, { message: localize('no.project.found', 'No project found for folder {0}', folder) });
} else if (status === 2) {
this.ctestErrored(test, run, { message: localize('build.failed', 'Build failed') });
}
}
return true;
}

return Array.from(builtFolder.values()).filter(v => v !== 0).length === 0;
/**
* Build the targets provided in foundTarget. CMake will be invoked once per CMakeProject for efficiency. On error,
* the associated tests will be flagged with a ctest error and a false value will be returned.
*/
private async buildTestTargets(foundTarget: Map<CMakeProject, Map<string, vscode.TestItem[]>>, run: vscode.TestRun): Promise<boolean> {
let overallSuccess = true;
for (const [project, targets] of foundTarget) {
const binaryDir = (await project.binaryDir).toString();
const accmulatedTestList: vscode.TestItem[] = [];
const accumulatedTargets: string[] = [];
let success: boolean = true;
for (const [targetName, testList] of targets) {
accumulatedTargets.push(path.relative(binaryDir, targetName));
accmulatedTestList.concat(testList);
}
try {
if (extensionManager !== undefined && extensionManager !== null) {
extensionManager.cleanOutputChannel();
}
const buildResult = await project.build(accumulatedTargets, false, false);
if (buildResult !== 0) {
success = false;
}
} catch (e) {
success = false;
}
if (!success) {
overallSuccess = false;
accmulatedTestList.forEach(test => {
this.ctestErrored(test, run, { message: localize('build.failed', 'Build failed') });
});
}
};
return overallSuccess;
}

/**
Expand Down