15
15
import * as os from "os" ;
16
16
import * as path from "path" ;
17
17
import * as vscode from "vscode" ;
18
+ import * as fs from "fs/promises" ;
18
19
import configuration from "../configuration" ;
19
20
import { FolderContext } from "../FolderContext" ;
20
21
import { BuildFlags } from "../toolchain/BuildFlags" ;
@@ -32,35 +33,38 @@ import { TestKind, isDebugging, isRelease } from "../TestExplorer/TestKind";
32
33
* and `xcTestConfig` functions to create
33
34
*/
34
35
export class TestingDebugConfigurationFactory {
35
- public static swiftTestingConfig (
36
+ public static async swiftTestingConfig (
36
37
ctx : FolderContext ,
37
38
fifoPipePath : string ,
38
39
testKind : TestKind ,
39
40
testList : string [ ] ,
41
+ buildStartTime : Date ,
40
42
expandEnvVariables = false
41
- ) : vscode . DebugConfiguration | null {
43
+ ) : Promise < vscode . DebugConfiguration | null > {
42
44
return new TestingDebugConfigurationFactory (
43
45
ctx ,
44
46
fifoPipePath ,
45
47
testKind ,
46
48
TestLibrary . swiftTesting ,
47
49
testList ,
50
+ buildStartTime ,
48
51
expandEnvVariables
49
52
) . build ( ) ;
50
53
}
51
54
52
- public static xcTestConfig (
55
+ public static async xcTestConfig (
53
56
ctx : FolderContext ,
54
57
testKind : TestKind ,
55
58
testList : string [ ] ,
56
59
expandEnvVariables = false
57
- ) : vscode . DebugConfiguration | null {
60
+ ) : Promise < vscode . DebugConfiguration | null > {
58
61
return new TestingDebugConfigurationFactory (
59
62
ctx ,
60
63
"" ,
61
64
testKind ,
62
65
TestLibrary . xctest ,
63
66
testList ,
67
+ null ,
64
68
expandEnvVariables
65
69
) . build ( ) ;
66
70
}
@@ -71,6 +75,7 @@ export class TestingDebugConfigurationFactory {
71
75
private testKind : TestKind ,
72
76
private testLibrary : TestLibrary ,
73
77
private testList : string [ ] ,
78
+ private buildStartTime : Date | null ,
74
79
private expandEnvVariables = false
75
80
) { }
76
81
@@ -82,7 +87,7 @@ export class TestingDebugConfigurationFactory {
82
87
* - Test Kind (coverage, debugging)
83
88
* - Test Library (XCTest, swift-testing)
84
89
*/
85
- private build ( ) : vscode . DebugConfiguration | null {
90
+ private async build ( ) : Promise < vscode . DebugConfiguration | null > {
86
91
if ( ! this . hasTestTarget ) {
87
92
return null ;
88
93
}
@@ -98,7 +103,7 @@ export class TestingDebugConfigurationFactory {
98
103
}
99
104
100
105
/* eslint-disable no-case-declarations */
101
- private buildWindowsConfig ( ) : vscode . DebugConfiguration | null {
106
+ private async buildWindowsConfig ( ) : Promise < vscode . DebugConfiguration | null > {
102
107
if ( isDebugging ( this . testKind ) ) {
103
108
const testEnv = {
104
109
...swiftRuntimeEnv ( ) ,
@@ -114,8 +119,8 @@ export class TestingDebugConfigurationFactory {
114
119
115
120
return {
116
121
...this . baseConfig ,
117
- program : this . testExecutableOutputPath ,
118
- args : this . debuggingTestExecutableArgs ,
122
+ program : await this . testExecutableOutputPath ( ) ,
123
+ args : await this . debuggingTestExecutableArgs ( ) ,
119
124
env : testEnv ,
120
125
} ;
121
126
} else {
@@ -124,39 +129,69 @@ export class TestingDebugConfigurationFactory {
124
129
}
125
130
126
131
/* eslint-disable no-case-declarations */
127
- private buildLinuxConfig ( ) : vscode . DebugConfiguration | null {
132
+ private async buildLinuxConfig ( ) : Promise < vscode . DebugConfiguration | null > {
128
133
if ( isDebugging ( this . testKind ) && this . testLibrary === TestLibrary . xctest ) {
129
134
return {
130
135
...this . baseConfig ,
131
- program : this . testExecutableOutputPath ,
132
- args : this . debuggingTestExecutableArgs ,
136
+ program : await this . testExecutableOutputPath ( ) ,
137
+ args : await this . debuggingTestExecutableArgs ( ) ,
133
138
env : {
134
139
...swiftRuntimeEnv ( ) ,
135
140
...configuration . folder ( this . ctx . workspaceFolder ) . testEnvironmentVariables ,
136
141
} ,
137
142
} ;
138
143
} else {
139
- return this . buildDarwinConfig ( ) ;
144
+ return await this . buildDarwinConfig ( ) ;
140
145
}
141
146
}
142
147
143
- private buildDarwinConfig ( ) : vscode . DebugConfiguration | null {
148
+ private async buildDarwinConfig ( ) : Promise < vscode . DebugConfiguration | null > {
144
149
switch ( this . testLibrary ) {
145
150
case TestLibrary . swiftTesting :
146
151
switch ( this . testKind ) {
147
152
case TestKind . debugRelease :
148
153
case TestKind . debug :
149
- // In the debug case we need to build the .swift- testing executable and then
154
+ // In the debug case we need to build the testing executable and then
150
155
// launch it with LLDB instead of going through `swift test`.
151
156
const toolchain = this . ctx . workspaceContext . toolchain ;
152
157
const libraryPath = toolchain . swiftTestingLibraryPath ( ) ;
153
158
const frameworkPath = toolchain . swiftTestingFrameworkPath ( ) ;
159
+ const swiftPMTestingHelperPath = toolchain . swiftPMTestingHelperPath ;
160
+
161
+ // Toolchains that contain https://github.com/swiftlang/swift-package-manager/commit/844bd137070dcd18d0f46dd95885ef7907ea0697
162
+ // produce a single testing binary for both xctest and swift-testing (called <ProductName>.xctest).
163
+ // We can continue to invoke it with the xctest utility, but to run swift-testing tests
164
+ // we need to invoke then using the swiftpm-testing-helper utility. If this helper utility exists
165
+ // then we know we're working with a unified binary.
166
+ if ( swiftPMTestingHelperPath ) {
167
+ const result = {
168
+ ...this . baseConfig ,
169
+ program : swiftPMTestingHelperPath ,
170
+ args : this . addBuildOptionsToArgs (
171
+ this . addTestsToArgs (
172
+ this . addSwiftTestingFlagsArgs ( [
173
+ "--test-bundle-path" ,
174
+ this . unifiedTestingOutputPath ( ) ,
175
+ "--testing-library" ,
176
+ "swift-testing" ,
177
+ ] )
178
+ )
179
+ ) ,
180
+ env : {
181
+ ...this . testEnv ,
182
+ ...this . sanitizerRuntimeEnvironment ,
183
+ DYLD_FRAMEWORK_PATH : frameworkPath ,
184
+ DYLD_LIBRARY_PATH : libraryPath ,
185
+ SWT_SF_SYMBOLS_ENABLED : "0" ,
186
+ } ,
187
+ } ;
188
+ return result ;
189
+ }
190
+
154
191
const result = {
155
192
...this . baseConfig ,
156
- program : this . swiftTestingOutputPath ,
157
- args : this . addBuildOptionsToArgs (
158
- this . addTestsToArgs ( this . addSwiftTestingFlagsArgs ( [ ] ) )
159
- ) ,
193
+ program : await this . testExecutableOutputPath ( ) ,
194
+ args : await this . debuggingTestExecutableArgs ( ) ,
160
195
env : {
161
196
...this . testEnv ,
162
197
...this . sanitizerRuntimeEnvironment ,
@@ -208,7 +243,7 @@ export class TestingDebugConfigurationFactory {
208
243
return {
209
244
...this . baseConfig ,
210
245
program : path . join ( xcTestPath , "xctest" ) ,
211
- args : this . addXCTestExecutableTestsToArgs ( [ this . xcTestOutputPath ] ) ,
246
+ args : this . addXCTestExecutableTestsToArgs ( [ this . xcTestOutputPath ( ) ] ) ,
212
247
env : {
213
248
...this . testEnv ,
214
249
...this . sanitizerRuntimeEnvironment ,
@@ -315,7 +350,7 @@ export class TestingDebugConfigurationFactory {
315
350
targetCreateCommands : [ `file -a ${ arch } ${ xctestPath } /xctest` ] ,
316
351
processCreateCommands : [
317
352
...envCommands ,
318
- `process launch -w ${ folder } -- ${ testFilterArg } ${ this . xcTestOutputPath } ` ,
353
+ `process launch -w ${ folder } -- ${ testFilterArg } ${ this . xcTestOutputPath ( ) } ` ,
319
354
] ,
320
355
preLaunchTask : `swift: Build All${ nameSuffix } ` ,
321
356
} ;
@@ -387,37 +422,82 @@ export class TestingDebugConfigurationFactory {
387
422
return isRelease ( this . testKind ) ? "release" : "debug" ;
388
423
}
389
424
390
- private get xcTestOutputPath ( ) : string {
425
+ private xcTestOutputPath ( ) : string {
391
426
return path . join (
392
427
this . buildDirectory ,
393
428
this . artifactFolderForTestKind ,
394
- this . ctx . swiftPackage . name + " PackageTests.xctest"
429
+ ` ${ this . ctx . swiftPackage . name } PackageTests.xctest`
395
430
) ;
396
431
}
397
432
398
- private get swiftTestingOutputPath ( ) : string {
433
+ private swiftTestingOutputPath ( ) : string {
399
434
return path . join (
400
435
this . buildDirectory ,
401
436
this . artifactFolderForTestKind ,
402
437
`${ this . ctx . swiftPackage . name } PackageTests.swift-testing`
403
438
) ;
404
439
}
405
440
406
- private get testExecutableOutputPath ( ) : string {
441
+ private async isUnifiedTestingBinary ( ) : Promise < boolean > {
442
+ // Toolchains that contain https://github.com/swiftlang/swift-package-manager/commit/844bd137070dcd18d0f46dd95885ef7907ea0697
443
+ // no longer produce a .swift-testing binary, instead we want to use `unifiedTestingOutputPath`.
444
+ // In order to determine if we're working with a unified binary we need to check if the .swift-testing
445
+ // binary _doesn't_ exist, and if it does exist we need to check that it wasn't built by an old toolchain
446
+ // and is still in the .build directory. We do this by checking its mtime and seeing if it is after
447
+ // the `buildStartTime`.
448
+
449
+ // TODO: When Swift 6 is released and enough time has passed that we're sure no one is building the .swift-testing
450
+ // binary anymore this fs.stat workaround can be removed and `swiftTestingPath` can be returned. The
451
+ // `buildStartTime` argument can be removed and the build config generation can be made synchronous again.
452
+
453
+ try {
454
+ const stat = await fs . stat ( this . swiftTestingOutputPath ( ) ) ;
455
+ return ! this . buildStartTime || stat . mtime . getTime ( ) < this . buildStartTime . getTime ( ) ;
456
+ } catch {
457
+ // If the .swift-testing binary doesn't exist then swift-testing tests are in the unified binary.
458
+ return true ;
459
+ }
460
+ }
461
+
462
+ private unifiedTestingOutputPath ( ) : string {
463
+ // The unified binary that contains both swift-testing and XCTests
464
+ // is named the same as the old style .xctest binary. The swiftpm-testing-helper
465
+ // requires the full path to the binary.
466
+ if ( process . platform === "darwin" ) {
467
+ return path . join (
468
+ this . xcTestOutputPath ( ) ,
469
+ "Contents" ,
470
+ "MacOS" ,
471
+ `${ this . ctx . swiftPackage . name } PackageTests`
472
+ ) ;
473
+ } else {
474
+ return this . xcTestOutputPath ( ) ;
475
+ }
476
+ }
477
+
478
+ private async testExecutableOutputPath ( ) : Promise < string > {
407
479
switch ( this . testLibrary ) {
408
480
case TestLibrary . swiftTesting :
409
- return this . swiftTestingOutputPath ;
481
+ return ( await this . isUnifiedTestingBinary ( ) )
482
+ ? this . unifiedTestingOutputPath ( )
483
+ : this . swiftTestingOutputPath ( ) ;
410
484
case TestLibrary . xctest :
411
- return this . xcTestOutputPath ;
485
+ return this . xcTestOutputPath ( ) ;
412
486
}
413
487
}
414
488
415
- private get debuggingTestExecutableArgs ( ) : string [ ] {
489
+ private async debuggingTestExecutableArgs ( ) : Promise < string [ ] > {
416
490
switch ( this . testLibrary ) {
417
- case TestLibrary . swiftTesting :
491
+ case TestLibrary . swiftTesting : {
492
+ const isUnifiedBinary = await this . isUnifiedTestingBinary ( ) ;
493
+ const swiftTestingArgs = isUnifiedBinary
494
+ ? [ "--testing-library" , "swift-testing" ]
495
+ : [ ] ;
496
+
418
497
return this . addBuildOptionsToArgs (
419
- this . addTestsToArgs ( this . addSwiftTestingFlagsArgs ( [ ] ) )
498
+ this . addTestsToArgs ( this . addSwiftTestingFlagsArgs ( swiftTestingArgs ) )
420
499
) ;
500
+ }
421
501
case TestLibrary . xctest :
422
502
return [ this . testList . join ( "," ) ] ;
423
503
}
0 commit comments