1111//===----------------------------------------------------------------------===//
1212
1313import ArgumentParser
14+ import Subprocess
15+ #if canImport(System)
16+ import System
17+ #else
18+ import SystemPackage
19+ #endif
1420
1521@_spi ( SwiftPMInternal)
1622import Basics
@@ -342,7 +348,7 @@ public struct SwiftTestCommand: AsyncSwiftCommand {
342348 observabilityScope: swiftCommandState. observabilityScope
343349 )
344350
345- testResults = try runner. run ( tests)
351+ testResults = try await runner. run ( tests)
346352 result = runner. ranSuccessfully ? . success : . failure
347353 }
348354
@@ -538,7 +544,7 @@ public struct SwiftTestCommand: AsyncSwiftCommand {
538544 )
539545
540546 // Finally, run the tests.
541- return runner. test ( outputHandler: {
547+ return await runner. test ( outputHandler: {
542548 // command's result output goes on stdout
543549 // ie "swift test" should output to stdout
544550 print ( $0, terminator: " " )
@@ -837,7 +843,7 @@ extension SwiftTestCommand {
837843 )
838844
839845 // Finally, run the tests.
840- let result = runner. test ( outputHandler: {
846+ let result = await runner. test ( outputHandler: {
841847 // command's result output goes on stdout
842848 // ie "swift test" should output to stdout
843849 print ( $0, terminator: " " )
@@ -911,7 +917,7 @@ final class TestRunner {
911917 // The toolchain to use.
912918 private let toolchain : UserToolchain
913919
914- private let testEnv : Environment
920+ private let testEnv : Basics . Environment
915921
916922 /// ObservabilityScope to emit diagnostics.
917923 private let observabilityScope : ObservabilityScope
@@ -945,7 +951,7 @@ final class TestRunner {
945951 additionalArguments: [ String ] ,
946952 cancellator: Cancellator ,
947953 toolchain: UserToolchain ,
948- testEnv: Environment ,
954+ testEnv: Basics . Environment ,
949955 observabilityScope: ObservabilityScope ,
950956 library: TestingLibrary
951957 ) {
@@ -974,10 +980,10 @@ final class TestRunner {
974980
975981 /// Executes and returns execution status. Prints test output on standard streams if requested
976982 /// - Returns: Result of spawning and running the test process, and the output stream result
977- func test( outputHandler: @escaping ( String ) -> Void ) -> Result {
983+ func test( outputHandler: @escaping ( String ) -> Void ) async -> Result {
978984 var results = [ Result] ( )
979985 for path in self . bundlePaths {
980- let testSuccess = self . test ( at: path, outputHandler: outputHandler)
986+ let testSuccess = await self . test ( at: path, outputHandler: outputHandler)
981987 results. append ( testSuccess)
982988 }
983989 return results. reduce ( )
@@ -1021,33 +1027,36 @@ final class TestRunner {
10211027 return args
10221028 }
10231029
1024- private func test( at path: AbsolutePath , outputHandler: @escaping ( String ) -> Void ) -> Result {
1030+ private func test( at path: AbsolutePath , outputHandler: @escaping ( String ) -> Void ) async -> Result {
10251031 let testObservabilityScope = self . observabilityScope. makeChildScope ( description: " running test at \( path) " )
10261032
10271033 do {
1028- let outputHandler = { ( bytes : [ UInt8 ] ) in
1029- if let output = String ( bytes : bytes , encoding : . utf8 ) {
1030- outputHandler ( output )
1031- }
1034+ let args = try args ( forTestAt : path )
1035+ var env : [ Subprocess . Environment . Key : String ] = [ : ]
1036+ for (key , value ) in self . testEnv {
1037+ env [ . init ( rawValue : key . rawValue ) ! ] = value
10321038 }
1033- let outputRedirection = AsyncProcess . OutputRedirection. stream (
1034- stdout: outputHandler,
1035- stderr: outputHandler
1036- )
1037- let process = AsyncProcess ( arguments: try args ( forTestAt: path) , environment: self . testEnv, outputRedirection: outputRedirection)
1038- guard let terminationKey = self . cancellator. register ( process) else {
1039- return . failure // terminating
1039+ let processConfig = Subprocess . Configuration ( . path( . init( args [ 0 ] ) ) , arguments: . init( Array ( args. dropFirst ( ) ) ) , environment: . custom( env) )
1040+ let result = try await Subprocess . run ( processConfig, input: . none, error: . standardOutput) { execution, outputSequence in
1041+ let token = cancellator. register ( name: " Test Execution " , handler: { _ in
1042+ await execution. teardown ( using: [ . gracefulShutDown( allowedDurationToNextStep: . seconds( 5 ) ) ] )
1043+ } )
1044+ defer {
1045+ token. map { cancellator. deregister ( $0) }
1046+ }
1047+
1048+ for try await line in outputSequence. lines ( ) {
1049+ outputHandler ( line)
1050+ }
10401051 }
1041- defer { self . cancellator. deregister ( terminationKey) }
1042- try process. launch ( )
1043- let result = try process. waitUntilExit ( )
1044- switch result. exitStatus {
1045- case . terminated( code: 0 ) :
1052+
1053+ switch result. terminationStatus {
1054+ case . exited( code: 0 ) :
10461055 return . success
1047- case . terminated ( code: EXIT_NO_TESTS_FOUND) where library == . swiftTesting:
1056+ case . exited ( code: numericCast ( EXIT_NO_TESTS_FOUND) ) where library == . swiftTesting:
10481057 return . noMatchingTests
10491058 #if !os(Windows)
1050- case . signalled ( let signal) where ![ SIGINT, SIGKILL, SIGTERM] . contains ( signal) :
1059+ case . unhandledException ( let signal) where ![ SIGINT, SIGKILL, SIGTERM] . contains ( signal) :
10511060 testObservabilityScope. emit ( error: " Exited with unexpected signal code \( signal) " )
10521061 return . failure
10531062 #endif
@@ -1087,21 +1096,9 @@ final class ParallelTestRunner {
10871096 /// Path to XCTest binaries.
10881097 private let bundlePaths : [ AbsolutePath ]
10891098
1090- /// The queue containing list of tests to run (producer).
1091- private let pendingTests = SynchronizedQueue < UnitTest ? > ( )
1092-
1093- /// The queue containing tests which are finished running.
1094- private let finishedTests = SynchronizedQueue < TestResult ? > ( )
1095-
10961099 /// Instance of a terminal progress animation.
10971100 private let progressAnimation : ProgressAnimationProtocol
10981101
1099- /// Number of tests that will be executed.
1100- private var numTests = 0
1101-
1102- /// Number of the current tests that has been executed.
1103- private var numCurrentTest = 0
1104-
11051102 /// True if all tests executed successfully.
11061103 private( set) var ranSuccessfully = true
11071104
@@ -1160,27 +1157,8 @@ final class ParallelTestRunner {
11601157 assert ( numJobs > 0 , " num jobs should be > 0 " )
11611158 }
11621159
1163- /// Updates the progress bar status.
1164- private func updateProgress( for test: UnitTest ) {
1165- numCurrentTest += 1
1166- progressAnimation. update ( step: numCurrentTest, total: numTests, text: " Testing \( test. specifier) " )
1167- }
1168-
1169- private func enqueueTests( _ tests: [ UnitTest ] ) throws {
1170- // Enqueue all the tests.
1171- for test in tests {
1172- pendingTests. enqueue ( test)
1173- }
1174- self . numTests = tests. count
1175- self . numCurrentTest = 0
1176- // Enqueue the sentinels, we stop a thread when it encounters a sentinel in the queue.
1177- for _ in 0 ..< numJobs {
1178- pendingTests. enqueue ( nil )
1179- }
1180- }
1181-
11821160 /// Executes the tests spawning parallel workers. Blocks calling thread until all workers are finished.
1183- func run( _ tests: [ UnitTest ] ) throws -> [ TestResult ] {
1161+ func run( _ tests: [ UnitTest ] ) async throws -> [ TestResult ] {
11841162 assert ( !tests. isEmpty, " There should be at least one test to execute. " )
11851163
11861164 let testEnv = try TestingSupport . constructTestEnvironment (
@@ -1190,77 +1168,75 @@ final class ParallelTestRunner {
11901168 library: . xctest // swift-testing does not use ParallelTestRunner
11911169 )
11921170
1193- // Enqueue all the tests.
1194- try enqueueTests ( tests)
1195-
1196- // Create the worker threads.
1197- let workers : [ Thread ] = ( 0 ..< numJobs) . map ( { _ in
1198- let thread = Thread {
1199- // Dequeue a specifier and run it till we encounter nil.
1200- while let test = self . pendingTests. dequeue ( ) {
1201- let additionalArguments = TestRunner . xctestArguments ( forTestSpecifiers: CollectionOfOne ( test. specifier) )
1202- let testRunner = TestRunner (
1203- bundlePaths: [ test. productPath] ,
1204- additionalArguments: additionalArguments,
1205- cancellator: self . cancellator,
1206- toolchain: self . toolchain,
1207- testEnv: testEnv,
1208- observabilityScope: self . observabilityScope,
1209- library: . xctest // swift-testing does not use ParallelTestRunner
1210- )
1211- var output = " "
1212- let outputLock = NSLock ( )
1213- let start = DispatchTime . now ( )
1214- let result = testRunner. test ( outputHandler: { _output in outputLock. withLock { output += _output } } )
1215- let duration = start. distance ( to: . now( ) )
1216- if result == . failure {
1217- self . ranSuccessfully = false
1171+ var pendingTests : [ UnitTest ] = tests
1172+ var processedTests : [ TestResult ] = [ ]
1173+ observabilityScope. emit ( error: " wefhfehjef " )
1174+ await withTaskGroup { group in
1175+ func runTest( _ test: UnitTest ) async -> TestResult {
1176+ observabilityScope. emit ( error: " enqueuing \( test. specifier) " )
1177+ let additionalArguments = TestRunner . xctestArguments ( forTestSpecifiers: CollectionOfOne ( test. specifier) )
1178+ let testRunner = TestRunner (
1179+ bundlePaths: [ test. productPath] ,
1180+ additionalArguments: additionalArguments,
1181+ cancellator: self . cancellator,
1182+ toolchain: self . toolchain,
1183+ testEnv: testEnv,
1184+ observabilityScope: self . observabilityScope,
1185+ library: . xctest // swift-testing does not use ParallelTestRunner
1186+ )
1187+ var output = " "
1188+ let outputLock = NSLock ( )
1189+ let start = DispatchTime . now ( )
1190+ let result = await testRunner. test ( outputHandler: { _output in outputLock. withLock { output += _output } } )
1191+ let duration = start. distance ( to: . now( ) )
1192+ if result == . failure {
1193+ self . ranSuccessfully = false
1194+ }
1195+ return TestResult (
1196+ unitTest: test,
1197+ output: output,
1198+ success: result != . failure,
1199+ duration: duration
1200+ )
1201+ }
1202+ for _ in 0 ..< numJobs {
1203+ if !pendingTests. isEmpty {
1204+ let test = pendingTests. removeLast ( )
1205+ group. addTask {
1206+ await runTest ( test)
12181207 }
1219- self . finishedTests. enqueue ( TestResult (
1220- unitTest: test,
1221- output: output,
1222- success: result != . failure,
1223- duration: duration
1224- ) )
12251208 }
12261209 }
1227- thread. start ( )
1228- return thread
1229- } )
1230-
1231- // List of processed tests.
1232- let processedTests = ThreadSafeArrayStore < TestResult > ( )
1233-
1234- // Report (consume) the tests which have finished running.
1235- while let result = finishedTests. dequeue ( ) {
1236- updateProgress ( for: result. unitTest)
1237-
1238- // Store the result.
1239- processedTests. append ( result)
1210+ var completedTests = 0
1211+ while let result = await group. next ( ) {
1212+ completedTests += 1
1213+ progressAnimation. update ( step: completedTests, total: tests. count, text: " Testing \( result. unitTest. specifier) " )
1214+
1215+ if !pendingTests. isEmpty {
1216+ let test = pendingTests. removeLast ( )
1217+ group. addTask ( operation: {
1218+ await runTest ( test)
1219+ } )
1220+ }
12401221
1241- // We can't enqueue a sentinel into finished tests queue because we won't know
1242- // which test is last one so exit this when all the tests have finished running.
1243- if numCurrentTest == numTests {
1244- break
1222+ // Store the result.
1223+ processedTests. append ( result)
12451224 }
12461225 }
12471226
1248- // Wait till all threads finish execution.
1249- workers. forEach { $0. join ( ) }
1250-
12511227 // Report the completion.
1252- progressAnimation. complete ( success: processedTests. get ( ) . contains ( where: { !$0. success } ) )
1228+ progressAnimation. complete ( success: processedTests. contains ( where: { !$0. success } ) )
12531229
12541230 // Print test results.
1255- for test in processedTests. get ( ) {
1231+ for test in processedTests {
12561232 if ( !test. success || shouldOutputSuccess) && !productsBuildParameters. testingParameters. experimentalTestOutput {
12571233 // command's result output goes on stdout
12581234 // ie "swift test" should output to stdout
12591235 print ( test. output)
12601236 }
12611237 }
12621238
1263- return processedTests. get ( )
1239+ return processedTests
12641240 }
12651241}
12661242
0 commit comments