1515import * as os from "os" ;
1616import * as path from "path" ;
1717import * as vscode from "vscode" ;
18+ import * as fs from "fs/promises" ;
1819import configuration from "../configuration" ;
1920import { FolderContext } from "../FolderContext" ;
2021import { BuildFlags } from "../toolchain/BuildFlags" ;
@@ -32,35 +33,38 @@ import { TestKind, isDebugging, isRelease } from "../TestExplorer/TestKind";
3233 * and `xcTestConfig` functions to create
3334 */
3435export class TestingDebugConfigurationFactory {
35- public static swiftTestingConfig (
36+ public static async swiftTestingConfig (
3637 ctx : FolderContext ,
3738 fifoPipePath : string ,
3839 testKind : TestKind ,
3940 testList : string [ ] ,
41+ buildStartTime : Date ,
4042 expandEnvVariables = false
41- ) : vscode . DebugConfiguration | null {
43+ ) : Promise < vscode . DebugConfiguration | null > {
4244 return new TestingDebugConfigurationFactory (
4345 ctx ,
4446 fifoPipePath ,
4547 testKind ,
4648 TestLibrary . swiftTesting ,
4749 testList ,
50+ buildStartTime ,
4851 expandEnvVariables
4952 ) . build ( ) ;
5053 }
5154
52- public static xcTestConfig (
55+ public static async xcTestConfig (
5356 ctx : FolderContext ,
5457 testKind : TestKind ,
5558 testList : string [ ] ,
5659 expandEnvVariables = false
57- ) : vscode . DebugConfiguration | null {
60+ ) : Promise < vscode . DebugConfiguration | null > {
5861 return new TestingDebugConfigurationFactory (
5962 ctx ,
6063 "" ,
6164 testKind ,
6265 TestLibrary . xctest ,
6366 testList ,
67+ null ,
6468 expandEnvVariables
6569 ) . build ( ) ;
6670 }
@@ -71,6 +75,7 @@ export class TestingDebugConfigurationFactory {
7175 private testKind : TestKind ,
7276 private testLibrary : TestLibrary ,
7377 private testList : string [ ] ,
78+ private buildStartTime : Date | null ,
7479 private expandEnvVariables = false
7580 ) { }
7681
@@ -82,7 +87,7 @@ export class TestingDebugConfigurationFactory {
8287 * - Test Kind (coverage, debugging)
8388 * - Test Library (XCTest, swift-testing)
8489 */
85- private build ( ) : vscode . DebugConfiguration | null {
90+ private async build ( ) : Promise < vscode . DebugConfiguration | null > {
8691 if ( ! this . hasTestTarget ) {
8792 return null ;
8893 }
@@ -98,7 +103,7 @@ export class TestingDebugConfigurationFactory {
98103 }
99104
100105 /* eslint-disable no-case-declarations */
101- private buildWindowsConfig ( ) : vscode . DebugConfiguration | null {
106+ private async buildWindowsConfig ( ) : Promise < vscode . DebugConfiguration | null > {
102107 if ( isDebugging ( this . testKind ) ) {
103108 const testEnv = {
104109 ...swiftRuntimeEnv ( ) ,
@@ -114,8 +119,8 @@ export class TestingDebugConfigurationFactory {
114119
115120 return {
116121 ...this . baseConfig ,
117- program : this . testExecutableOutputPath ,
118- args : this . debuggingTestExecutableArgs ,
122+ program : await this . testExecutableOutputPath ( ) ,
123+ args : await this . debuggingTestExecutableArgs ( ) ,
119124 env : testEnv ,
120125 } ;
121126 } else {
@@ -124,39 +129,69 @@ export class TestingDebugConfigurationFactory {
124129 }
125130
126131 /* eslint-disable no-case-declarations */
127- private buildLinuxConfig ( ) : vscode . DebugConfiguration | null {
132+ private async buildLinuxConfig ( ) : Promise < vscode . DebugConfiguration | null > {
128133 if ( isDebugging ( this . testKind ) && this . testLibrary === TestLibrary . xctest ) {
129134 return {
130135 ...this . baseConfig ,
131- program : this . testExecutableOutputPath ,
132- args : this . debuggingTestExecutableArgs ,
136+ program : await this . testExecutableOutputPath ( ) ,
137+ args : await this . debuggingTestExecutableArgs ( ) ,
133138 env : {
134139 ...swiftRuntimeEnv ( ) ,
135140 ...configuration . folder ( this . ctx . workspaceFolder ) . testEnvironmentVariables ,
136141 } ,
137142 } ;
138143 } else {
139- return this . buildDarwinConfig ( ) ;
144+ return await this . buildDarwinConfig ( ) ;
140145 }
141146 }
142147
143- private buildDarwinConfig ( ) : vscode . DebugConfiguration | null {
148+ private async buildDarwinConfig ( ) : Promise < vscode . DebugConfiguration | null > {
144149 switch ( this . testLibrary ) {
145150 case TestLibrary . swiftTesting :
146151 switch ( this . testKind ) {
147152 case TestKind . debugRelease :
148153 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
150155 // launch it with LLDB instead of going through `swift test`.
151156 const toolchain = this . ctx . workspaceContext . toolchain ;
152157 const libraryPath = toolchain . swiftTestingLibraryPath ( ) ;
153158 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+
154191 const result = {
155192 ...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 ( ) ,
160195 env : {
161196 ...this . testEnv ,
162197 ...this . sanitizerRuntimeEnvironment ,
@@ -208,7 +243,7 @@ export class TestingDebugConfigurationFactory {
208243 return {
209244 ...this . baseConfig ,
210245 program : path . join ( xcTestPath , "xctest" ) ,
211- args : this . addXCTestExecutableTestsToArgs ( [ this . xcTestOutputPath ] ) ,
246+ args : this . addXCTestExecutableTestsToArgs ( [ this . xcTestOutputPath ( ) ] ) ,
212247 env : {
213248 ...this . testEnv ,
214249 ...this . sanitizerRuntimeEnvironment ,
@@ -315,7 +350,7 @@ export class TestingDebugConfigurationFactory {
315350 targetCreateCommands : [ `file -a ${ arch } ${ xctestPath } /xctest` ] ,
316351 processCreateCommands : [
317352 ...envCommands ,
318- `process launch -w ${ folder } -- ${ testFilterArg } ${ this . xcTestOutputPath } ` ,
353+ `process launch -w ${ folder } -- ${ testFilterArg } ${ this . xcTestOutputPath ( ) } ` ,
319354 ] ,
320355 preLaunchTask : `swift: Build All${ nameSuffix } ` ,
321356 } ;
@@ -387,37 +422,82 @@ export class TestingDebugConfigurationFactory {
387422 return isRelease ( this . testKind ) ? "release" : "debug" ;
388423 }
389424
390- private get xcTestOutputPath ( ) : string {
425+ private xcTestOutputPath ( ) : string {
391426 return path . join (
392427 this . buildDirectory ,
393428 this . artifactFolderForTestKind ,
394- this . ctx . swiftPackage . name + " PackageTests.xctest"
429+ ` ${ this . ctx . swiftPackage . name } PackageTests.xctest`
395430 ) ;
396431 }
397432
398- private get swiftTestingOutputPath ( ) : string {
433+ private swiftTestingOutputPath ( ) : string {
399434 return path . join (
400435 this . buildDirectory ,
401436 this . artifactFolderForTestKind ,
402437 `${ this . ctx . swiftPackage . name } PackageTests.swift-testing`
403438 ) ;
404439 }
405440
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 > {
407479 switch ( this . testLibrary ) {
408480 case TestLibrary . swiftTesting :
409- return this . swiftTestingOutputPath ;
481+ return ( await this . isUnifiedTestingBinary ( ) )
482+ ? this . unifiedTestingOutputPath ( )
483+ : this . swiftTestingOutputPath ( ) ;
410484 case TestLibrary . xctest :
411- return this . xcTestOutputPath ;
485+ return this . xcTestOutputPath ( ) ;
412486 }
413487 }
414488
415- private get debuggingTestExecutableArgs ( ) : string [ ] {
489+ private async debuggingTestExecutableArgs ( ) : Promise < string [ ] > {
416490 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+
418497 return this . addBuildOptionsToArgs (
419- this . addTestsToArgs ( this . addSwiftTestingFlagsArgs ( [ ] ) )
498+ this . addTestsToArgs ( this . addSwiftTestingFlagsArgs ( swiftTestingArgs ) )
420499 ) ;
500+ }
421501 case TestLibrary . xctest :
422502 return [ this . testList . join ( "," ) ] ;
423503 }
0 commit comments