Skip to content
Open
Show file tree
Hide file tree
Changes from 10 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 @@ -48,6 +48,7 @@ Improvements:
- Add `cmake.removeStaleKitsOnScan` setting to optionally remove stale compiler kits from the kit picker after a "Scan for Kits" when they are no longer rediscovered. This is useful after compiler upgrades that leave older versions outside `PATH`. Set `"keep": true` in a kit entry to prevent automatic removal. [#3852](https://github.com/microsoft/vscode-cmake-tools/issues/3852)
- Add `pr-readiness` Copilot skill to verify PRs have a descriptive title, meaningful description, and a properly formatted CHANGELOG entry. [#4862](https://github.com/microsoft/vscode-cmake-tools/pull/4862)
- Updated IntelliSense tooltips with changes from CMake 4.3.1. [#4872](https://github.com/microsoft/vscode-cmake-tools/pull/4872)
- 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)
Comment thread
hanniavalera marked this conversation as resolved.
Outdated

Bug Fixes:
- Fix stale C/C++ custom-configuration entries persisting after reconfigure/preset switches, which could cause Go to Definition/IntelliSense to surface symbols from inactive sources in the same folder. [#4472](https://github.com/microsoft/vscode-cmake-tools/issues/4472)
Expand Down
83 changes: 82 additions & 1 deletion src/ctest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1832,6 +1832,10 @@ 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.
Comment thread
hanniavalera marked this conversation as resolved.
*/
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) {
Expand All @@ -1841,7 +1845,11 @@ export class CTestDriver implements vscode.Disposable {
// 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;
}
const folder = this.getTestRootFolder(test);
if (!builtFolder.has(folder)) {
const project = await this.projectController?.getProjectForFolder(folder);
Expand Down Expand Up @@ -1869,7 +1877,80 @@ export class CTestDriver implements vscode.Disposable {
}
}

return Array.from(builtFolder.values()).filter(v => v !== 0).length === 0;
return this.buildTestTargets(foundTarget, run);
Comment thread
hanniavalera marked this conversation as resolved.
Comment thread
hanniavalera marked this conversation as resolved.
}

/**
* 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);
Comment thread
mcodilla marked this conversation as resolved.
Outdated
}
} else {
const testProgram = this.testProgram(test.id);
Comment thread
mcodilla marked this conversation as resolved.
const folder = this.getTestRootFolder(test);
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 {
prj.get(testProgram)!.push(test);
}
}
}
return true;
}

/**
* 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;
Comment thread
hanniavalera marked this conversation as resolved.
for (const [project, targets] of foundTarget) {
const execTargets = await project.executableTargets;
const accmulatedTestList: vscode.TestItem[] = [];
Comment thread
mcodilla marked this conversation as resolved.
Outdated
const accumulatedTargets: string[] = [];
let success: boolean = true;
for (const [targetPath, testList] of targets) {
// Look up the CMake target name from executable targets by matching
// the executable path. Using path.relative() uses the executable instead of the target name
const normalizedTargetPath = util.platformNormalizePath(targetPath);
const execTarget = execTargets.find(t => util.platformNormalizePath(t.path) === normalizedTargetPath);
Comment thread
mcodilla marked this conversation as resolved.
Outdated
accumulatedTargets.push(execTarget ? execTarget.name : path.parse(targetPath).name);
accmulatedTestList.push(...testList);
}
Comment thread
mcodilla marked this conversation as resolved.
Outdated
try {
if (extensionManager !== undefined && extensionManager !== null) {
extensionManager.cleanOutputChannel();
}
const buildResult = await project.build(accumulatedTargets, false, false);
Comment thread
hanniavalera marked this conversation as resolved.
if (buildResult.exitCode !== 0) {
success = false;
}
} catch (e) {
success = false;
}
Comment on lines +1947 to +1953
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The catch (e) here suppresses the underlying build error details. Please log the error (or include relevant information such as the target list and exit code) so users can diagnose why build-before-test failed.

Copilot uses AI. Check for mistakes.
if (!success) {
overallSuccess = false;
accmulatedTestList.forEach(test => {
this.ctestErrored(test, run, { message: localize('build.failed', 'Build failed') });
});
}
};
Comment thread
hanniavalera marked this conversation as resolved.
return overallSuccess;
}

/**
Expand Down
Loading