Support cancelling ctest execution (#4141)

* Support cancelling ctest execution

* fix wording for progress bar

* fix linter issues
This commit is contained in:
Garrett Campbell 2024-10-25 09:00:56 -07:00 коммит произвёл GitHub
Родитель 831c33c162
Коммит d0ad11e566
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
2 изменённых файлов: 46 добавлений и 10 удалений

Просмотреть файл

@ -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:

Просмотреть файл

@ -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) {
@ -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) {
@ -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[]}>();
@ -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;
@ -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);
@ -503,7 +520,7 @@ 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++) {
@ -511,6 +528,12 @@ export class CTestDriver implements vscode.Disposable {
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);
}
}
@ -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 {
@ -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."));
}