Skip to content
Merged
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
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2241,6 +2241,12 @@
"description": "%cmake-tools.configuration.cmake.ctest.testExplorerIntegrationEnabled.description%",
"scope": "machine-overridable"
},
"cmake.ctest.testSuiteDelimiter": {
"type": "string",
"default": null,
"markdownDescription": "%cmake-tools.configuration.cmake.ctest.testSuiteDelimiter.markdownDescription%",
"scope": "machine-overridable"
},
"cmake.parseBuildDiagnostics": {
"type": "boolean",
"default": true,
Expand Down
6 changes: 6 additions & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@
},
"cmake-tools.configuration.cmake.ctest.allowParallelJobs.description": "Allows ctests to be run in parallel, however the result output may be garbled as a result and the Test Explorer may not accurately reflect test progress.",
"cmake-tools.configuration.cmake.ctest.testExplorerIntegrationEnabled.description": "Whether or not the integration with the test explorer is enabled. This is helpful to disable if you prefer using a different extension for test integration.",
"cmake-tools.configuration.cmake.ctest.testSuiteDelimiter.markdownDescription": {
"message": "Optional delimiter used to separate test suite names and group tests hierarchically in the Test Explorer. This string is used in a Regular Expression, so some delimiters may need escaping. Examples: `-` ( One delimiter: `-`), `\\.|::` (Two delimiters: `.` or `::`. Note that `.` needs to be escaped.)",
"comment": [
"Markdown text between `` should not be translated or localized (they represent literal text) and the capitalization, spacing, and punctuation (including the ``) should not be altered."
]
},
"cmake-tools.configuration.cmake.parseBuildDiagnostics.description": "Parse compiler output for warnings and errors.",
"cmake-tools.configuration.cmake.enabledOutputParsers.description": {
"message": "Output parsers to use. Supported parsers `cmake`, `gcc`, `gnuld` for GNULD-style linker output, `msvc` for Microsoft Visual C++, `ghs` for the Green Hills compiler with --no_wrap_diagnostics --brief_diagnostics, and `diab` for the Wind River Diab compiler.",
Expand Down
7 changes: 5 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ export interface ExtensionConfigurationSettings {
buildToolArgs: string[];
parallelJobs: number | undefined;
ctestPath: string;
ctest: { parallelJobs: number; allowParallelJobs: boolean; testExplorerIntegrationEnabled: boolean };
ctest: { parallelJobs: number; allowParallelJobs: boolean; testExplorerIntegrationEnabled: boolean; testSuiteDelimiter: string };
parseBuildDiagnostics: boolean;
enabledOutputParsers: string[];
debugConfig: CppDebugConfiguration;
Expand Down Expand Up @@ -375,6 +375,9 @@ export class ConfigurationReader implements vscode.Disposable {
get testExplorerIntegrationEnabled(): boolean {
return this.configData.ctest.testExplorerIntegrationEnabled;
}
get testSuiteDelimiter(): string {
return this.configData.ctest.testSuiteDelimiter;
}
get parseBuildDiagnostics(): boolean {
return !!this.configData.parseBuildDiagnostics;
}
Expand Down Expand Up @@ -571,7 +574,7 @@ export class ConfigurationReader implements vscode.Disposable {
parallelJobs: new vscode.EventEmitter<number>(),
ctestPath: new vscode.EventEmitter<string>(),
cpackPath: new vscode.EventEmitter<string>(),
ctest: new vscode.EventEmitter<{ parallelJobs: number; allowParallelJobs: boolean; testExplorerIntegrationEnabled: boolean }>(),
ctest: new vscode.EventEmitter<{ parallelJobs: number; allowParallelJobs: boolean; testExplorerIntegrationEnabled: boolean; testSuiteDelimiter: string }>(),
parseBuildDiagnostics: new vscode.EventEmitter<boolean>(),
enabledOutputParsers: new vscode.EventEmitter<string[]>(),
debugConfig: new vscode.EventEmitter<CppDebugConfiguration>(),
Expand Down
85 changes: 64 additions & 21 deletions src/ctest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,12 @@ class CTestOutputLogger implements OutputConsumer {
}
}

// A test item and its parent test suite item in the test explorer
interface TestAndParentSuite {
test: vscode.TestItem;
parentSuite: vscode.TestItem;
}

export class CTestDriver implements vscode.Disposable {
/**
* @param projectController Required for test explorer to work properly. Setting as optional to avoid breaking tests.
Expand Down Expand Up @@ -390,7 +396,7 @@ export class CTestDriver implements vscode.Disposable {
if (driver) {
_driver = driver;
} else {
const folder = test.parent ? test.parent.id : test.id;
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) });
Expand Down Expand Up @@ -429,7 +435,7 @@ export class CTestDriver implements vscode.Disposable {
if (test.children.size > 0) {
// Shouldn't reach here now, but not hard to write so keeping it in case we want to have more complicated test hierarchies
const children = this.testItemCollectionToArray(test.children);
if (await this.runCTestHelper(children, run, _driver, _ctestPath, _ctestArgs, cancellation, customizedTask, consumer)) {
if (await this.runCTestHelper(children, run, _driver, _ctestPath, _ctestArgs, cancellation, customizedTask, consumer, entryPoint)) {
returnCode = -1;
}
return returnCode;
Expand Down Expand Up @@ -599,6 +605,30 @@ export class CTestDriver implements vscode.Disposable {
return undefined;
}

private createTestItemAndSuiteTree(testName: string, testExplorerRoot: vscode.TestItem, initializedTestExplorer: vscode.TestController, uri?: vscode.Uri): TestAndParentSuite {
let parentSuiteItem = testExplorerRoot;
let testLabel = testName;

// If a suite delimiter is set, create a suite tree
if (this.ws.config.testSuiteDelimiter) {
const delimiterRegExp = new RegExp(this.ws.config.testSuiteDelimiter);
const parts = testName.split(delimiterRegExp);
testLabel = parts.pop() || testName; // The last part is the test label

// Create a suite item for each suite ID part if it doesn't exist yet at that tree level
for (const suiteId of parts) {
let suiteItem = parentSuiteItem.children.get(suiteId);
if (!suiteItem) {
suiteItem = initializedTestExplorer.createTestItem(suiteId, suiteId);
parentSuiteItem.children.add(suiteItem);
}
parentSuiteItem = suiteItem;
}
}
const testItem = initializedTestExplorer.createTestItem(testName, testLabel, uri);
return { test: testItem, parentSuite: parentSuiteItem };
}

/**
* @brief Refresh the list of CTest tests
* @returns 0 when successful
Expand Down Expand Up @@ -675,7 +705,9 @@ export class CTestDriver implements vscode.Disposable {
this.tests = JSON.parse(result.stdout) ?? undefined;
if (this.tests && this.tests.kind === 'ctestInfo') {
this.tests.tests.forEach(test => {
let testItem: vscode.TestItem | undefined;
let testDefFile: string | undefined;
let testDefLine: number | undefined;

if (test.backtrace !== undefined && this.tests!.backtraceGraph.nodes[test.backtrace] !== undefined) {
// Use DEF_SOURCE_LINE CMake test property to find file and line number
// Property must be set in the test's CMakeLists.txt file or its included modules for this to work
Expand All @@ -684,26 +716,29 @@ export class CTestDriver implements vscode.Disposable {
// Use RegEx to match the format "file_path:line" in value[0]
const match = defSourceLineProperty.value.match(/(.*):(\d+)/);
if (match && match[1] && match[2]) {
const testDefFile = match[1];
const testDefLine = parseInt(match[2]);
if (!isNaN(testDefLine)) {
testItem = initializedTestExplorer.createTestItem(test.name, test.name, vscode.Uri.file(testDefFile));
testItem.range = new vscode.Range(new vscode.Position(testDefLine - 1, 0), new vscode.Position(testDefLine - 1, 0));
testDefFile = match[1];
testDefLine = parseInt(match[2]);
if (isNaN(testDefLine)) {
testDefLine = undefined;
testDefFile = undefined;
}
}
}
if (!testItem) {

if (!testDefFile) {
// Use the backtrace graph to find the file and line number
// This finds the CMake module's file and line number and not the test file and line number
const testDefFile = this.tests!.backtraceGraph.files[this.tests!.backtraceGraph.nodes[test.backtrace].file];
const testDefLine = this.tests!.backtraceGraph.nodes[test.backtrace].line;
testItem = initializedTestExplorer.createTestItem(test.name, test.name, vscode.Uri.file(testDefFile));
if (testDefLine !== undefined) {
testItem.range = new vscode.Range(new vscode.Position(testDefLine - 1, 0), new vscode.Position(testDefLine - 1, 0));
}
testDefFile = this.tests!.backtraceGraph.files[this.tests!.backtraceGraph.nodes[test.backtrace].file];
testDefLine = this.tests!.backtraceGraph.nodes[test.backtrace].line;
}
} else {
testItem = initializedTestExplorer.createTestItem(test.name, test.name);
}

const testAndParentSuite = this.createTestItemAndSuiteTree(test.name, testExplorerRoot, initializedTestExplorer, testDefFile ? vscode.Uri.file(testDefFile) : undefined);
const testItem = testAndParentSuite.test;
const parentSuiteItem = testAndParentSuite.parentSuite;

if (testDefLine !== undefined) {
testItem.range = new vscode.Range(new vscode.Position(testDefLine - 1, 0), new vscode.Position(testDefLine - 1, 0));
}

const testTags: vscode.TestTag[] = [];
Expand All @@ -723,7 +758,7 @@ export class CTestDriver implements vscode.Disposable {
testItem.tags = [...testItem.tags, ...testTags];
}

testExplorerRoot.children.add(testItem);
parentSuiteItem.children.add(testItem);
});
};
}
Expand Down Expand Up @@ -778,7 +813,7 @@ export class CTestDriver implements vscode.Disposable {
let presetMayChange = false;
for (const test of tests) {
if (test.id === testPresetRequired) {
const folder = test.parent ? test.parent.id : test.id;
const folder = this.getTestRootFolder(test);
const project = await this.projectController?.getProjectForFolder(folder);
if (!project) {
log.error(localize('no.project.found', 'No project found for folder {0}', folder));
Expand Down Expand Up @@ -832,7 +867,7 @@ export class CTestDriver implements vscode.Disposable {
continue;
}

const folder = test.parent ? test.parent.id : test.id;
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) });
Expand Down Expand Up @@ -1050,12 +1085,20 @@ export class CTestDriver implements vscode.Disposable {
run.end();
};

private getTestRootFolder(test: vscode.TestItem): string {
let currentTestItem = test;
while (currentTestItem.parent !== undefined) {
currentTestItem = currentTestItem.parent;
}
return currentTestItem.id;
}

private async buildTests(tests: vscode.TestItem[], run: vscode.TestRun): Promise<boolean> {
// Folder => status
const builtFolder = new Map<string, number>();
let status: number = 0;
for (const test of tests) {
const folder = test.parent ? test.parent.id : test.id;
const folder = this.getTestRootFolder(test);
if (!builtFolder.has(folder)) {
const project = await this.projectController?.getProjectForFolder(folder);
if (!project) {
Expand Down
3 changes: 2 additions & 1 deletion test/unit-tests/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ function createConfig(conf: Partial<ExtensionConfigurationSettings>): Configurat
ctest: {
parallelJobs: 0,
allowParallelJobs: false,
testExplorerIntegrationEnabled: true
testExplorerIntegrationEnabled: true,
testSuiteDelimiter: ''
},
parseBuildDiagnostics: true,
enabledOutputParsers: [],
Expand Down