Skip to content

Commit bf7c770

Browse files
authored
Prompt for cancellation when starting test run while one is in flight (#1774)
* Prompt for cancellation when starting test run while one is in flight If a test run is already in progress and another one is started VS Code will queue it and start the second run once the first has finished. Implement cancellation logic that prompts the user to cancel the active test run if one is in flight. If they chose to cancel the dialog, the second test run is not queued. If they do chose to cancel the active test run it is stopped and the new one starts immediately. Issue: #1748
1 parent 30fe1e1 commit bf7c770

File tree

7 files changed

+316
-34
lines changed

7 files changed

+316
-34
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- New `swift.outputChannelLogLevel` setting to control the verbosity of the `Swift` output channel ([#1746](https://github.com/swiftlang/vscode-swift/pull/1746))
99
- New `swift.debugTestsMultipleTimes` and `swift.debugTestsUntilFailure` commands for debugging tests over multiple runs ([#1763](https://github.com/swiftlang/vscode-swift/pull/1763))
1010
- Optionally include LLDB DAP logs in the Swift diagnostics bundle ([#1768](https://github.com/swiftlang/vscode-swift/pull/1758))
11+
- Prompt to cancel and replace the active test run if one is in flight ([#1774](https://github.com/swiftlang/vscode-swift/pull/1774))
1112

1213
### Changed
1314
- Added log levels and improved Swift extension logging so a logfile is produced in addition to logging messages to the existing `Swift` output channel. Deprecated the `swift.diagnostics` setting in favour of the new `swift.outputChannelLogLevel` setting ([#1746](https://github.com/swiftlang/vscode-swift/pull/1746))

src/FolderContext.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,22 @@ import { LinuxMain } from "./LinuxMain";
1818
import { PackageWatcher } from "./PackageWatcher";
1919
import { SwiftPackage, Target, TargetType } from "./SwiftPackage";
2020
import { TestExplorer } from "./TestExplorer/TestExplorer";
21+
import { TestRunManager } from "./TestExplorer/TestRunManager";
2122
import { WorkspaceContext, FolderOperation } from "./WorkspaceContext";
2223
import { BackgroundCompilation } from "./BackgroundCompilation";
2324
import { TaskQueue } from "./tasks/TaskQueue";
2425
import { isPathInsidePath } from "./utilities/filesystem";
2526
import { SwiftToolchain } from "./toolchain/toolchain";
2627
import { SwiftLogger } from "./logging/SwiftLogger";
28+
import { TestRunProxy } from "./TestExplorer/TestRunner";
2729

2830
export class FolderContext implements vscode.Disposable {
29-
private packageWatcher: PackageWatcher;
3031
public backgroundCompilation: BackgroundCompilation;
3132
public hasResolveErrors = false;
3233
public testExplorer?: TestExplorer;
3334
public taskQueue: TaskQueue;
35+
private packageWatcher: PackageWatcher;
36+
private testRunManager: TestRunManager;
3437

3538
/**
3639
* FolderContext constructor
@@ -49,6 +52,7 @@ export class FolderContext implements vscode.Disposable {
4952
this.packageWatcher = new PackageWatcher(this, workspaceContext);
5053
this.backgroundCompilation = new BackgroundCompilation(this);
5154
this.taskQueue = new TaskQueue(this);
55+
this.testRunManager = new TestRunManager();
5256
}
5357

5458
/** dispose of any thing FolderContext holds */
@@ -212,6 +216,34 @@ export class FolderContext implements vscode.Disposable {
212216
return target;
213217
}
214218

219+
/**
220+
* Register a new test run
221+
* @param testRun The test run to register
222+
* @param folder The folder context
223+
* @param testKind The kind of test run
224+
* @param tokenSource The cancellation token source
225+
*/
226+
public registerTestRun(testRun: TestRunProxy, tokenSource: vscode.CancellationTokenSource) {
227+
this.testRunManager.registerTestRun(testRun, this, tokenSource);
228+
}
229+
230+
/**
231+
* Returns true if there is an active test run for the given test kind
232+
* @param testKind The kind of test
233+
* @returns True if there is an active test run, false otherwise
234+
*/
235+
hasActiveTestRun() {
236+
return this.testRunManager.getActiveTestRun(this) !== undefined;
237+
}
238+
239+
/**
240+
* Cancels the active test run for the given test kind
241+
* @param testKind The kind of test run
242+
*/
243+
cancelTestRun() {
244+
this.testRunManager.cancelTestRun(this);
245+
}
246+
215247
/**
216248
* Called whenever we have new document symbols
217249
*/

src/TestExplorer/TestRunManager.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the VS Code Swift open source project
4+
//
5+
// Copyright (c) 2021-2025 the VS Code Swift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import * as vscode from "vscode";
16+
import { TestRunProxy } from "./TestRunner";
17+
import { FolderContext } from "../FolderContext";
18+
19+
/**
20+
* Manages active test runs and provides functionality to check if a test run is in progress
21+
* and to cancel test runs.
22+
*/
23+
export class TestRunManager {
24+
private activeTestRuns = new Map<
25+
string,
26+
{ testRun: TestRunProxy; tokenSource: vscode.CancellationTokenSource }
27+
>();
28+
29+
/**
30+
* Register a new test run
31+
* @param testRun The test run to register
32+
* @param folder The folder context
33+
* @param tokenSource The cancellation token source
34+
*/
35+
public registerTestRun(
36+
testRun: TestRunProxy,
37+
folder: FolderContext,
38+
tokenSource: vscode.CancellationTokenSource
39+
) {
40+
const key = this.getTestRunKey(folder);
41+
this.activeTestRuns.set(key, { testRun, tokenSource });
42+
43+
// When the test run completes, remove it from active test runs
44+
testRun.onTestRunComplete(() => {
45+
this.activeTestRuns.delete(key);
46+
});
47+
}
48+
49+
/**
50+
* Cancel an active test run
51+
* @param folder The folder context
52+
*/
53+
public cancelTestRun(folder: FolderContext) {
54+
const key = this.getTestRunKey(folder);
55+
const activeRun = this.activeTestRuns.get(key);
56+
if (activeRun) {
57+
activeRun.tokenSource.cancel();
58+
}
59+
}
60+
61+
/**
62+
* Check if a test run is already in progress for the given folder and test kind
63+
* @param folder The folder context
64+
* @returns The active test run if one exists, undefined otherwise
65+
*/
66+
public getActiveTestRun(folder: FolderContext) {
67+
const key = this.getTestRunKey(folder);
68+
const activeRun = this.activeTestRuns.get(key);
69+
return activeRun?.testRun;
70+
}
71+
72+
/**
73+
* Generate a unique key for a test run based on folder and test kind
74+
* @param folder The folder context
75+
* @returns A unique key
76+
*/
77+
private getTestRunKey(folder: FolderContext) {
78+
return folder.folder.fsPath;
79+
}
80+
}

0 commit comments

Comments
 (0)