@@ -27,15 +27,16 @@ import { TargetType } from "../SwiftPackage";
27
27
import { parseTestsFromSwiftTestListOutput } from "./SPMTestDiscovery" ;
28
28
import { parseTestsFromDocumentSymbols } from "./DocumentSymbolTestDiscovery" ;
29
29
import { flattenTestItemCollection } from "./TestUtils" ;
30
+ import { TestCodeLensProvider } from "./TestCodeLensProvider" ;
30
31
31
32
/** Build test explorer UI */
32
33
export class TestExplorer {
33
34
static errorTestItemId = "#Error#" ;
34
35
public controller : vscode . TestController ;
35
36
public testRunProfiles : vscode . TestRunProfile [ ] ;
37
+
36
38
private lspTestDiscovery : LSPTestDiscovery ;
37
39
private subscriptions : { dispose ( ) : unknown } [ ] ;
38
- private testFileEdited = true ;
39
40
private tokenSource = new vscode . CancellationTokenSource ( ) ;
40
41
41
42
// Emits after the `vscode.TestController` has been updated.
@@ -45,9 +46,13 @@ export class TestExplorer {
45
46
public onDidCreateTestRunEmitter = new vscode . EventEmitter < TestRunProxy > ( ) ;
46
47
public onCreateTestRun : vscode . Event < TestRunProxy > ;
47
48
49
+ private codeLensProvider ?: TestCodeLensProvider ;
50
+
48
51
constructor ( public folderContext : FolderContext ) {
49
52
this . onTestItemsDidChange = this . onTestItemsDidChangeEmitter . event ;
50
53
this . onCreateTestRun = this . onDidCreateTestRunEmitter . event ;
54
+ this . lspTestDiscovery = this . configureLSPTestDiscovery ( folderContext ) ;
55
+ this . codeLensProvider = new TestCodeLensProvider ( this ) ;
51
56
52
57
this . controller = vscode . tests . createTestController (
53
58
folderContext . name ,
@@ -66,55 +71,109 @@ export class TestExplorer {
66
71
this . onDidCreateTestRunEmitter
67
72
) ;
68
73
74
+ this . subscriptions = [
75
+ this . tokenSource ,
76
+ this . controller ,
77
+ this . onTestItemsDidChangeEmitter ,
78
+ this . onDidCreateTestRunEmitter ,
79
+ ...this . testRunProfiles ,
80
+ this . onTestItemsDidChange ( ( ) => this . updateSwiftTestContext ( ) ) ,
81
+ this . discoverUpdatedTestsAfterBuild ( folderContext ) ,
82
+ ] ;
83
+ }
84
+
85
+ /**
86
+ * Query the LSP for tests in the document. If the LSP is not available
87
+ * this method will fallback to the legacy method of parsing document symbols,
88
+ * but only for XCTests.
89
+ * @param folder The folder context.
90
+ * @param uri The document URI. If the document is not part of a test target, this method will do nothing.
91
+ * @param symbols The document symbols.
92
+ * @returns A promise that resolves when the tests have been retrieved.
93
+ */
94
+ public async getDocumentTests (
95
+ folder : FolderContext ,
96
+ uri : vscode . Uri ,
97
+ symbols : vscode . DocumentSymbol [ ]
98
+ ) : Promise < void > {
99
+ const target = await folder . swiftPackage . getTarget ( uri . fsPath ) ;
100
+ if ( ! target || target . type !== "test" ) {
101
+ return ;
102
+ }
103
+
104
+ try {
105
+ const tests = await this . lspTestDiscovery . getDocumentTests ( folder . swiftPackage , uri ) ;
106
+ TestDiscovery . updateTestsForTarget (
107
+ this . controller ,
108
+ { id : target . c99name , label : target . name } ,
109
+ tests ,
110
+ uri
111
+ ) ;
112
+ this . onTestItemsDidChangeEmitter . fire ( this . controller ) ;
113
+ } catch {
114
+ // Fallback to parsing document symbols for XCTests only
115
+ const tests = parseTestsFromDocumentSymbols ( target . name , symbols , uri ) ;
116
+ this . updateTests ( this . controller , tests , uri ) ;
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Creates an LSPTestDiscovery client for the given folder context.
122
+ */
123
+ private configureLSPTestDiscovery ( folderContext : FolderContext ) : LSPTestDiscovery {
69
124
const workspaceContext = folderContext . workspaceContext ;
70
125
const languageClientManager = workspaceContext . languageClientManager . get ( folderContext ) ;
71
- this . lspTestDiscovery = new LSPTestDiscovery ( languageClientManager ) ;
126
+ return new LSPTestDiscovery ( languageClientManager ) ;
127
+ }
72
128
73
- // add end of task handler to be called whenever a build task has finished. If
74
- // it is the build task for this folder then update the tests
75
- const onDidEndTask = folderContext . workspaceContext . tasks . onDidEndTaskProcess ( event => {
76
- const task = event . execution . task ;
77
- const execution = task . execution as vscode . ProcessExecution ;
78
- if (
79
- task . scope === this . folderContext . workspaceFolder &&
80
- task . group === vscode . TaskGroup . Build &&
81
- execution ?. options ?. cwd === this . folderContext . folder . fsPath &&
82
- event . exitCode === 0 &&
83
- task . definition . dontTriggerTestDiscovery !== true &&
84
- this . testFileEdited
85
- ) {
86
- this . testFileEdited = false ;
129
+ /**
130
+ * Configure test discovery for updated tests after a build task has completed.
131
+ */
132
+ private discoverUpdatedTestsAfterBuild ( folderContext : FolderContext ) : vscode . Disposable {
133
+ let testFileEdited = true ;
134
+ const endProcessDisposable = folderContext . workspaceContext . tasks . onDidEndTaskProcess (
135
+ event => {
136
+ const task = event . execution . task ;
137
+ const execution = task . execution as vscode . ProcessExecution ;
138
+ if (
139
+ task . scope === folderContext . workspaceFolder &&
140
+ task . group === vscode . TaskGroup . Build &&
141
+ execution ?. options ?. cwd === folderContext . folder . fsPath &&
142
+ event . exitCode === 0 &&
143
+ task . definition . dontTriggerTestDiscovery !== true &&
144
+ testFileEdited
145
+ ) {
146
+ testFileEdited = false ;
87
147
88
- // only run discover tests if the library has tests
89
- void this . folderContext . swiftPackage . getTargets ( TargetType . test ) . then ( targets => {
90
- if ( targets . length > 0 ) {
91
- void this . discoverTestsInWorkspace ( this . tokenSource . token ) ;
92
- }
93
- } ) ;
148
+ // only run discover tests if the library has tests
149
+ void folderContext . swiftPackage . getTargets ( TargetType . test ) . then ( targets => {
150
+ if ( targets . length > 0 ) {
151
+ void this . discoverTestsInWorkspace ( this . tokenSource . token ) ;
152
+ }
153
+ } ) ;
154
+ }
94
155
}
95
- } ) ;
156
+ ) ;
96
157
97
158
// add file watcher to catch changes to swift test files
98
- const fileWatcher = this . folderContext . workspaceContext . onDidChangeSwiftFiles ( ( { uri } ) => {
99
- if ( this . testFileEdited === false ) {
100
- void this . folderContext . getTestTarget ( uri ) . then ( target => {
101
- if ( target ) {
102
- this . testFileEdited = true ;
103
- }
104
- } ) ;
159
+ const didChangeSwiftFileDisposable = folderContext . workspaceContext . onDidChangeSwiftFiles (
160
+ ( { uri } ) => {
161
+ if ( testFileEdited === false ) {
162
+ void folderContext . getTestTarget ( uri ) . then ( target => {
163
+ if ( target ) {
164
+ testFileEdited = true ;
165
+ }
166
+ } ) ;
167
+ }
105
168
}
106
- } ) ;
169
+ ) ;
107
170
108
- this . subscriptions = [
109
- this . tokenSource ,
110
- fileWatcher ,
111
- onDidEndTask ,
112
- this . controller ,
113
- this . onTestItemsDidChangeEmitter ,
114
- this . onDidCreateTestRunEmitter ,
115
- ...this . testRunProfiles ,
116
- this . onTestItemsDidChange ( ( ) => this . updateSwiftTestContext ( ) ) ,
117
- ] ;
171
+ return {
172
+ dispose : ( ) => {
173
+ endProcessDisposable . dispose ( ) ;
174
+ didChangeSwiftFileDisposable . dispose ( ) ;
175
+ } ,
176
+ } ;
118
177
}
119
178
120
179
dispose ( ) {
@@ -193,32 +252,6 @@ export class TestExplorer {
193
252
} ) ;
194
253
}
195
254
196
- async getDocumentTests (
197
- folder : FolderContext ,
198
- uri : vscode . Uri ,
199
- symbols : vscode . DocumentSymbol [ ]
200
- ) : Promise < void > {
201
- const target = await folder . swiftPackage . getTarget ( uri . fsPath ) ;
202
- if ( ! target || target . type !== "test" ) {
203
- return ;
204
- }
205
-
206
- try {
207
- const tests = await this . lspTestDiscovery . getDocumentTests ( folder . swiftPackage , uri ) ;
208
- TestDiscovery . updateTestsForTarget (
209
- this . controller ,
210
- { id : target . c99name , label : target . name } ,
211
- tests ,
212
- uri
213
- ) ;
214
- this . onTestItemsDidChangeEmitter . fire ( this . controller ) ;
215
- } catch {
216
- // Fallback to parsing document symbols for XCTests only
217
- const tests = parseTestsFromDocumentSymbols ( target . name , symbols , uri ) ;
218
- this . updateTests ( this . controller , tests , uri ) ;
219
- }
220
- }
221
-
222
255
private updateTests (
223
256
controller : vscode . TestController ,
224
257
tests : TestDiscovery . TestClass [ ] ,
@@ -231,7 +264,7 @@ export class TestExplorer {
231
264
/**
232
265
* Discover tests
233
266
*/
234
- async discoverTestsInWorkspace ( token : vscode . CancellationToken ) {
267
+ private async discoverTestsInWorkspace ( token : vscode . CancellationToken ) {
235
268
try {
236
269
// If the LSP cannot produce a list of tests it throws and
237
270
// we fall back to discovering tests with SPM.
@@ -249,7 +282,7 @@ export class TestExplorer {
249
282
* Discover tests
250
283
* Uses `swift test --list-tests` to get the list of tests
251
284
*/
252
- async discoverTestsInWorkspaceSPM ( token : vscode . CancellationToken ) {
285
+ private async discoverTestsInWorkspaceSPM ( token : vscode . CancellationToken ) {
253
286
async function runDiscover ( explorer : TestExplorer , firstTry : boolean ) {
254
287
try {
255
288
// we depend on sourcekit-lsp to detect swift-testing tests so let the user know
@@ -379,7 +412,7 @@ export class TestExplorer {
379
412
/**
380
413
* Discover tests
381
414
*/
382
- async discoverTestsInWorkspaceLSP ( token : vscode . CancellationToken ) {
415
+ private async discoverTestsInWorkspaceLSP ( token : vscode . CancellationToken ) {
383
416
const tests = await this . lspTestDiscovery . getWorkspaceTests (
384
417
this . folderContext . swiftPackage
385
418
) ;
0 commit comments