Skip to content

Commit

Permalink
Support cancelling ctest execution (#4141)
Browse files Browse the repository at this point in the history
* Support cancelling ctest execution

* fix wording for progress bar

* fix linter issues
  • Loading branch information
gcampbell-msft authored Oct 25, 2024
1 parent 831c33c commit d0ad11e
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Improvements:

- Fix "Unable to resolve configuration with compilerPath" issue for Swift. [#4097](https://github.com/microsoft/vscode-cmake-tools/issues/4097)
- Ensure that any uses of `proc.spawn` work, especially for .bat and .cmd files, due to VS Code updating to Node 20. [#4037](https://github.com/microsoft/vscode-cmake-tools/issues/4037)
- Ensure that stopping tests actually forces the tests to stop running. [#2095](https://github.com/microsoft/vscode-cmake-tools/issues/2095)

Bug Fixes:

Expand Down
55 changes: 45 additions & 10 deletions src/ctest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,14 +310,25 @@ export class CTestDriver implements vscode.Disposable {
const tests = this.testItemCollectionToArray(testExplorer.items);
const run = testExplorer.createTestRun(new vscode.TestRunRequest());
const ctestArgs = await this.getCTestArgs(driver, customizedTask, testPreset);
const returnCode = await this.runCTestHelper(tests, run, driver, undefined, ctestArgs, undefined, customizedTask, consumer);
const returnCode = await this.runCTestHelper(tests, run, run.token, driver, undefined, ctestArgs, customizedTask, consumer);
run.end();
return returnCode;
} else {
return this.runCTestDirectly(driver, customizedTask, testPreset, consumer);
return vscode.window.withProgress(
{
location: vscode.ProgressLocation.Window,
title: localize('ctest.testing.progress.title', 'Testing'),
cancellable: true
},
async (progress, cancel) => {
progress.report({ message: localize('running.tests', 'Running tests') });
return this.runCTestDirectly(driver, customizedTask, cancel, testPreset, consumer);
}
);
}
}

private async runCTestDirectly(driver: CMakeDriver, customizedTask: boolean = false, testPreset?: TestPreset, consumer?: proc.OutputConsumer): Promise<number> {
private async runCTestDirectly(driver: CMakeDriver, customizedTask: boolean = false, cancellationToken: vscode.CancellationToken, testPreset?: TestPreset, consumer?: proc.OutputConsumer): Promise<number> {
// below code taken from #3032 PR (before changes in how tests are run)
const ctestpath = await this.ws.getCTestPath(driver.cmakePathFromPreset);
if (ctestpath === null) {
Expand All @@ -332,7 +343,7 @@ export class CTestDriver implements vscode.Disposable {
return -3;
}

const testResults = await this.runCTestImpl(driver, ctestpath, ctestArgs, customizedTask, consumer);
const testResults = await this.runCTestImpl(driver, ctestpath, ctestArgs, cancellationToken, customizedTask, consumer);

let returnCode: number = 0;
if (testResults) {
Expand Down Expand Up @@ -384,7 +395,7 @@ export class CTestDriver implements vscode.Disposable {
run.failed(test, message, duration);
}

private async runCTestHelper(tests: vscode.TestItem[], run: vscode.TestRun, driver?: CMakeDriver, ctestPath?: string, ctestArgs?: string[], cancellation?: vscode.CancellationToken, customizedTask: boolean = false, consumer?: proc.OutputConsumer, entryPoint: RunCTestHelperEntryPoint = RunCTestHelperEntryPoint.RunTests): Promise<number> {
private async runCTestHelper(tests: vscode.TestItem[], run: vscode.TestRun, cancellation: vscode.CancellationToken, driver?: CMakeDriver, ctestPath?: string, ctestArgs?: string[], customizedTask: boolean = false, consumer?: proc.OutputConsumer, entryPoint: RunCTestHelperEntryPoint = RunCTestHelperEntryPoint.RunTests): Promise<number> {
let returnCode: number = 0;
const driverMap = new Map<string, { driver: CMakeDriver; ctestPath: string; ctestArgs: string[]; tests: vscode.TestItem[]}>();

Expand Down Expand Up @@ -435,7 +446,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, entryPoint)) {
if (await this.runCTestHelper(children, run, cancellation, _driver, _ctestPath, _ctestArgs, customizedTask, consumer, entryPoint)) {
returnCode = -1;
}
return returnCode;
Expand All @@ -460,7 +471,13 @@ export class CTestDriver implements vscode.Disposable {
run.started(test);

const _ctestArgs = driver.ctestArgs.concat('-R', `^${util.escapeStringForRegex(test.id)}\$`);
const testResults = await this.runCTestImpl(driver.driver, driver.ctestPath, _ctestArgs, customizedTask, consumer);

const testResults = await this.runCTestImpl(driver.driver, driver.ctestPath, _ctestArgs, cancellation, customizedTask, consumer);

if (cancellation && cancellation.isCancellationRequested) {
run.skipped(test);
continue;
}

if (testResults) {
const testResult = testResults.site.testing.test.find(t => t.name === test.id);
Expand Down Expand Up @@ -503,14 +520,20 @@ export class CTestDriver implements vscode.Disposable {
uniqueCtestArgs.push(testsNamesRegex.slice(0, -1)); // Remove the last '|'
}

const testResults = await this.runCTestImpl(uniqueDriver, uniqueCtestPath, uniqueCtestArgs, customizedTask, consumer);
const testResults = await this.runCTestImpl(uniqueDriver, uniqueCtestPath, uniqueCtestArgs, cancellation, customizedTask, consumer);

if (testResults) {
for (let i = 0; i < testResults.site.testing.test.length; i++) {
const _test = driver.tests.find(t => t.id === testResults.site.testing.test[i].name);
if (_test === undefined) {
continue; // This should never happen, we just constructed this list. For now, simply ignore and keep going.
}

if (cancellation && cancellation.isCancellationRequested) {
run.skipped(_test);
continue;
}

returnCode = this.testResultsAnalysis(testResults.site.testing.test[i], _test, returnCode, run);
}
}
Expand Down Expand Up @@ -583,12 +606,24 @@ export class CTestDriver implements vscode.Disposable {
return returnCode;
}

private async runCTestImpl(driver: CMakeDriver, ctestPath: string, ctestArgs: string[], customizedTask: boolean = false, consumer?: proc.OutputConsumer): Promise<CTestResults | undefined> {
private async runCTestImpl(driver: CMakeDriver, ctestPath: string, ctestArgs: string[], cancellationToken: vscode.CancellationToken, customizedTask: boolean = false, consumer?: proc.OutputConsumer): Promise<CTestResults | undefined> {
const child = driver.executeCommand(
ctestPath, ctestArgs,
((customizedTask && consumer) ? consumer : new CTestOutputLogger()),
{ environment: await driver.getCTestCommandEnvironment(), cwd: driver.binaryDir });

const cancellationHandler = cancellationToken.onCancellationRequested(async () => {
if (child.child) {
await util.termProc(child.child);
}
log.info(localize('ctest.run.cancelled', 'CTest run was cancelled'));
});

const res = await child.result;

// Dispose of the cancellation handler so that it doesn't get sticky for other tests.
cancellationHandler?.dispose();

if (res.retc === null) {
log.info(localize('ctest.run.terminated', 'CTest run was terminated'));
} else {
Expand Down Expand Up @@ -856,7 +891,7 @@ export class CTestDriver implements vscode.Disposable {
this.ctestsEnqueued(tests, run);
const buildSucceeded = await this.buildTests(tests, run);
if (buildSucceeded) {
await this.runCTestHelper(tests, run, undefined, undefined, undefined, cancellation, false, undefined, RunCTestHelperEntryPoint.TestExplorer);
await this.runCTestHelper(tests, run, cancellation, undefined, undefined, undefined, false, undefined, RunCTestHelperEntryPoint.TestExplorer);
} else {
log.info(localize('test.skip.run.build.failure', "Not running tests due to build failure."));
}
Expand Down

0 comments on commit d0ad11e

Please sign in to comment.