@@ -37,6 +37,7 @@ export class TestExplorer {
3737 private lspTestDiscovery : LSPTestDiscovery ;
3838 private subscriptions : { dispose ( ) : unknown } [ ] ;
3939 private testFileEdited = true ;
40+ private tokenSource = new vscode . CancellationTokenSource ( ) ;
4041
4142 // Emits after the `vscode.TestController` has been updated.
4243 private onTestItemsDidChangeEmitter = new vscode . EventEmitter < vscode . TestController > ( ) ;
@@ -56,7 +57,7 @@ export class TestExplorer {
5657
5758 this . controller . resolveHandler = async item => {
5859 if ( ! item ) {
59- await this . discoverTestsInWorkspace ( ) ;
60+ await this . discoverTestsInWorkspace ( this . tokenSource . token ) ;
6061 }
6162 } ;
6263
@@ -86,7 +87,7 @@ export class TestExplorer {
8687 this . testFileEdited = false ;
8788 // only run discover tests if the library has tests
8889 if ( this . folderContext . swiftPackage . getTargets ( TargetType . test ) . length > 0 ) {
89- this . discoverTestsInWorkspace ( ) ;
90+ this . discoverTestsInWorkspace ( this . tokenSource . token ) ;
9091 }
9192 }
9293 } ) ;
@@ -99,6 +100,7 @@ export class TestExplorer {
99100 } ) ;
100101
101102 this . subscriptions = [
103+ this . tokenSource ,
102104 fileWatcher ,
103105 onDidEndTask ,
104106 this . controller ,
@@ -110,6 +112,9 @@ export class TestExplorer {
110112 }
111113
112114 dispose ( ) {
115+ this . controller . refreshHandler = undefined ;
116+ this . controller . resolveHandler = undefined ;
117+ this . tokenSource . cancel ( ) ;
113118 this . subscriptions . forEach ( element => element . dispose ( ) ) ;
114119 }
115120
@@ -120,47 +125,64 @@ export class TestExplorer {
120125 * @returns Observer disposable
121126 */
122127 static observeFolders ( workspaceContext : WorkspaceContext ) : vscode . Disposable {
123- return workspaceContext . onDidChangeFolders ( ( { folder, operation, workspace } ) => {
124- switch ( operation ) {
125- case FolderOperation . add :
126- if ( folder ) {
127- if ( folder . swiftPackage . getTargets ( TargetType . test ) . length > 0 ) {
128- folder . addTestExplorer ( ) ;
129- // discover tests in workspace but only if disableAutoResolve is not on.
130- // discover tests will kick off a resolve if required
131- if ( ! configuration . folder ( folder . workspaceFolder ) . disableAutoResolve ) {
132- folder . testExplorer ?. discoverTestsInWorkspace ( ) ;
128+ const tokenSource = new vscode . CancellationTokenSource ( ) ;
129+ const disposable = workspaceContext . onDidChangeFolders (
130+ ( { folder, operation, workspace } ) => {
131+ switch ( operation ) {
132+ case FolderOperation . add :
133+ if ( folder ) {
134+ if ( folder . swiftPackage . getTargets ( TargetType . test ) . length > 0 ) {
135+ folder . addTestExplorer ( ) ;
136+ // discover tests in workspace but only if disableAutoResolve is not on.
137+ // discover tests will kick off a resolve if required
138+ if (
139+ ! configuration . folder ( folder . workspaceFolder ) . disableAutoResolve
140+ ) {
141+ folder . testExplorer ?. discoverTestsInWorkspace (
142+ tokenSource . token
143+ ) ;
144+ }
133145 }
134146 }
135- }
136- break ;
137- case FolderOperation . packageUpdated :
138- if ( folder ) {
139- const hasTestTargets =
140- folder . swiftPackage . getTargets ( TargetType . test ) . length > 0 ;
141- if ( hasTestTargets && ! folder . hasTestExplorer ( ) ) {
142- folder . addTestExplorer ( ) ;
143- // discover tests in workspace but only if disableAutoResolve is not on.
144- // discover tests will kick off a resolve if required
145- if ( ! configuration . folder ( folder . workspaceFolder ) . disableAutoResolve ) {
146- folder . testExplorer ?. discoverTestsInWorkspace ( ) ;
147+ break ;
148+ case FolderOperation . packageUpdated :
149+ if ( folder ) {
150+ const hasTestTargets =
151+ folder . swiftPackage . getTargets ( TargetType . test ) . length > 0 ;
152+ if ( hasTestTargets && ! folder . hasTestExplorer ( ) ) {
153+ folder . addTestExplorer ( ) ;
154+ // discover tests in workspace but only if disableAutoResolve is not on.
155+ // discover tests will kick off a resolve if required
156+ if (
157+ ! configuration . folder ( folder . workspaceFolder ) . disableAutoResolve
158+ ) {
159+ folder . testExplorer ?. discoverTestsInWorkspace (
160+ tokenSource . token
161+ ) ;
162+ }
163+ } else if ( ! hasTestTargets && folder . hasTestExplorer ( ) ) {
164+ folder . removeTestExplorer ( ) ;
165+ } else if ( folder . hasTestExplorer ( ) ) {
166+ folder . refreshTestExplorer ( ) ;
147167 }
148- } else if ( ! hasTestTargets && folder . hasTestExplorer ( ) ) {
149- folder . removeTestExplorer ( ) ;
150- } else if ( folder . hasTestExplorer ( ) ) {
151- folder . refreshTestExplorer ( ) ;
152168 }
153- }
154- break ;
155- case FolderOperation . focus :
156- if ( folder ) {
157- workspace . languageClientManager . documentSymbolWatcher = (
158- document ,
159- symbols
160- ) => TestExplorer . onDocumentSymbols ( folder , document , symbols ) ;
161- }
169+ break ;
170+ case FolderOperation . focus :
171+ if ( folder ) {
172+ workspace . languageClientManager . documentSymbolWatcher = (
173+ document ,
174+ symbols
175+ ) => TestExplorer . onDocumentSymbols ( folder , document , symbols ) ;
176+ }
177+ }
162178 }
163- } ) ;
179+ ) ;
180+ return {
181+ dispose : ( ) => {
182+ tokenSource . dispose ( ) ;
183+ disposable . dispose ( ) ;
184+ } ,
185+ } ;
164186 }
165187
166188 /**
@@ -224,25 +246,25 @@ export class TestExplorer {
224246 /**
225247 * Discover tests
226248 */
227- async discoverTestsInWorkspace ( ) {
249+ async discoverTestsInWorkspace ( token : vscode . CancellationToken ) {
228250 try {
229251 // If the LSP cannot produce a list of tests it throws and
230252 // we fall back to discovering tests with SPM.
231- await this . discoverTestsInWorkspaceLSP ( ) ;
253+ await this . discoverTestsInWorkspaceLSP ( token ) ;
232254 } catch {
233255 this . folderContext . workspaceContext . outputChannel . logDiagnostic (
234256 "workspace/tests LSP request not supported, falling back to SPM to discover tests." ,
235257 "Test Discovery"
236258 ) ;
237- await this . discoverTestsInWorkspaceSPM ( ) ;
259+ await this . discoverTestsInWorkspaceSPM ( token ) ;
238260 }
239261 }
240262
241263 /**
242264 * Discover tests
243265 * Uses `swift test --list-tests` to get the list of tests
244266 */
245- async discoverTestsInWorkspaceSPM ( ) {
267+ async discoverTestsInWorkspaceSPM ( token : vscode . CancellationToken ) {
246268 async function runDiscover ( explorer : TestExplorer , firstTry : boolean ) {
247269 try {
248270 const toolchain = explorer . folderContext . workspaceContext . toolchain ;
@@ -263,6 +285,11 @@ export class TestExplorer {
263285 return ;
264286 }
265287 }
288+
289+ if ( token . isCancellationRequested ) {
290+ return ;
291+ }
292+
266293 // get list of tests from `swift test --list-tests`
267294 let listTestArguments : string [ ] ;
268295 if ( toolchain . swiftVersion . isGreaterThanOrEqual ( new Version ( 5 , 8 , 0 ) ) ) {
@@ -284,7 +311,7 @@ export class TestExplorer {
284311 explorer . updateTests ( explorer . controller , tests ) ;
285312 }
286313 ) ;
287- await explorer . folderContext . taskQueue . queueOperation ( listTestsOperation ) ;
314+ await explorer . folderContext . taskQueue . queueOperation ( listTestsOperation , token ) ;
288315 } catch ( error ) {
289316 // If a test list fails its possible the tests have not been built.
290317 // Build them and try again, and if we still fail then notify the user.
@@ -336,10 +363,14 @@ export class TestExplorer {
336363 /**
337364 * Discover tests
338365 */
339- async discoverTestsInWorkspaceLSP ( ) {
366+ async discoverTestsInWorkspaceLSP ( token : vscode . CancellationToken ) {
340367 const tests = await this . lspTestDiscovery . getWorkspaceTests (
341368 this . folderContext . swiftPackage
342369 ) ;
370+ if ( token . isCancellationRequested ) {
371+ return ;
372+ }
373+
343374 TestDiscovery . updateTestsFromClasses (
344375 this . controller ,
345376 this . folderContext . swiftPackage ,
0 commit comments