@@ -8,18 +8,16 @@ See http://swift.org/LICENSE.txt for license information
8
8
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
9
9
*/
10
10
11
- import class Foundation. ProcessInfo
12
-
13
11
import ArgumentParser
14
12
import Basics
15
- import TSCBasic
16
- import SPMBuildCore
17
13
import Build
18
- import TSCUtility
14
+ import class Foundation . ProcessInfo
19
15
import PackageGraph
20
- import Workspace
21
-
16
+ import SPMBuildCore
17
+ import TSCBasic
22
18
import func TSCLibc. exit
19
+ import TSCUtility
20
+ import Workspace
23
21
24
22
private enum TestError : Swift . Error {
25
23
case invalidListTestJSONData
@@ -290,18 +288,20 @@ public struct SwiftTestTool: SwiftCommand {
290
288
}
291
289
}
292
290
291
+ let testEnv = try constructTestEnvironment ( toolchain: toolchain, options: swiftOptions, buildParameters: buildParameters)
292
+
293
293
let runner = TestRunner (
294
294
bundlePaths: testProducts. map { $0. bundlePath } ,
295
295
xctestArg: xctestArg,
296
296
processSet: swiftTool. processSet,
297
297
toolchain: toolchain,
298
- diagnostics : ObservabilitySystem . topScope . makeDiagnosticsEngine ( ) ,
299
- options : swiftOptions ,
300
- buildParameters : buildParameters
298
+ testEnv : testEnv ,
299
+ outputStream : swiftTool . outputStream ,
300
+ diagnostics : ObservabilitySystem . topScope . makeDiagnosticsEngine ( )
301
301
)
302
302
303
303
// Finally, run the tests.
304
- let ranSuccessfully : Bool = runner. test ( )
304
+ let ( ranSuccessfully, _ ) = runner. test ( writeToOutputStream : true )
305
305
if !ranSuccessfully {
306
306
swiftTool. executionStatus = . failure
307
307
}
@@ -576,12 +576,13 @@ final class TestRunner {
576
576
// The toolchain to use.
577
577
private let toolchain : UserToolchain
578
578
579
- /// Diagnostics Engine to emit diagnostics.
580
- let diagnostics : DiagnosticsEngine
579
+ private let testEnv : [ String : String ]
581
580
582
- private let options : SwiftToolOptions
581
+ /// Output stream for test results
582
+ private let outputStream : OutputByteStream
583
583
584
- private let buildParameters : BuildParameters
584
+ /// Diagnostics Engine to emit diagnostics.
585
+ private let diagnostics : DiagnosticsEngine
585
586
586
587
/// Creates an instance of TestRunner.
587
588
///
@@ -593,49 +594,37 @@ final class TestRunner {
593
594
xctestArg: String ? = nil ,
594
595
processSet: ProcessSet ,
595
596
toolchain: UserToolchain ,
596
- diagnostics : DiagnosticsEngine ,
597
- options : SwiftToolOptions ,
598
- buildParameters : BuildParameters
597
+ testEnv : [ String : String ] ,
598
+ outputStream : OutputByteStream ,
599
+ diagnostics : DiagnosticsEngine
599
600
) {
600
601
self . bundlePaths = bundlePaths
601
602
self . xctestArg = xctestArg
602
603
self . processSet = processSet
603
604
self . toolchain = toolchain
605
+ self . testEnv = testEnv
606
+ self . outputStream = outputStream
604
607
self . diagnostics = diagnostics
605
- self . options = options
606
- self . buildParameters = buildParameters
607
608
}
608
609
609
- /// Executes the tests without printing anything on standard streams.
610
- ///
611
- /// - Returns: A tuple with first bool member indicating if test execution returned code 0 and second argument
612
- /// containing the output of the execution.
613
- public func test( ) -> ( Bool , String ) {
610
+ /// Executes and returns execution status. Prints test output on standard streams if requested
611
+ /// - Returns: Boolean indicating if test execution returned code 0, and the output stream result
612
+ public func test( writeToOutputStream: Bool ) -> ( Bool , String ) {
614
613
var success = true
615
614
var output = " "
616
- for path in bundlePaths {
617
- let ( testSuccess, testOutput) = test ( testAt : path)
615
+ for path in self . bundlePaths {
616
+ let ( testSuccess, testOutput) = self . test ( at : path, writeToOutputStream : writeToOutputStream )
618
617
success = success && testSuccess
619
618
output += testOutput
620
619
}
621
620
return ( success, output)
622
621
}
623
622
624
- /// Executes and returns execution status. Prints test output on standard streams.
625
- public func test( ) -> Bool {
626
- var success = true
627
- for path in bundlePaths {
628
- let testSuccess : Bool = test ( testAt: path)
629
- success = success && testSuccess
630
- }
631
- return success
632
- }
633
-
634
623
/// Constructs arguments to execute XCTest.
635
624
private func args( forTestAt testPath: AbsolutePath ) throws -> [ String ] {
636
625
var args : [ String ] = [ ]
637
626
#if os(macOS)
638
- guard let xctest = toolchain. xctest else {
627
+ guard let xctest = self . toolchain. xctest else {
639
628
throw TestError . testsExecutableNotFound
640
629
}
641
630
args = [ xctest. pathString]
@@ -652,61 +641,48 @@ final class TestRunner {
652
641
return args
653
642
}
654
643
655
- /// Executes the tests without printing anything on standard streams.
656
- ///
657
- /// - Returns: A tuple with first bool member indicating if test execution returned code 0
658
- /// and second argument containing the output of the execution.
659
- private func test( testAt testPath: AbsolutePath ) -> ( Bool , String ) {
660
- var output = " "
661
- var success = false
662
- do {
663
- // FIXME: The environment will be constructed for every test when using the
664
- // parallel test runner. We should do some kind of caching.
665
- let env = try constructTestEnvironment ( toolchain: toolchain, options: self . options, buildParameters: self . buildParameters)
666
- let process = Process ( arguments: try args ( forTestAt: testPath) , environment: env, outputRedirection: . collect, verbose: false )
667
- try process. launch ( )
668
- let result = try process. waitUntilExit ( )
669
- output = try ( result. utf8Output ( ) + result. utf8stderrOutput ( ) ) . spm_chuzzle ( ) ?? " "
670
- switch result. exitStatus {
671
- case . terminated( code: 0 ) :
672
- success = true
673
- #if !os(Windows)
674
- case . signalled( let signal) :
675
- output += " \n " + exitSignalText( code: signal)
676
- #endif
677
- default : break
678
- }
679
- } catch {
680
- diagnostics. emit ( error)
644
+ private func test( at path: AbsolutePath , writeToOutputStream: Bool ) -> ( Bool , String ) {
645
+ var stdout : [ UInt8 ] = [ ]
646
+ var stderr : [ UInt8 ] = [ ]
647
+
648
+ func makeOutput( ) -> String {
649
+ return String ( bytes: stdout + stderr, encoding: . utf8) ? . spm_chuzzle ( ) ?? " "
681
650
}
682
- return ( success, output)
683
- }
684
651
685
- /// Executes and returns execution status. Prints test output on standard streams.
686
- private func test( testAt testPath: AbsolutePath ) -> Bool {
687
652
do {
688
- let env = try constructTestEnvironment ( toolchain: toolchain, options: self . options, buildParameters: self . buildParameters)
689
- let process = Process ( arguments: try args ( forTestAt: testPath) , environment: env, outputRedirection: . none)
690
- try processSet. add ( process)
653
+ let outputRedirection = Process . OutputRedirection. stream (
654
+ stdout: {
655
+ stdout += $0
656
+ if writeToOutputStream {
657
+ self . outputStream. write ( $0)
658
+ self . outputStream. flush ( )
659
+ }
660
+ } ,
661
+ stderr: {
662
+ stderr += $0
663
+ if writeToOutputStream {
664
+ TSCBasic . stderrStream. write ( $0)
665
+ TSCBasic . stderrStream. flush ( )
666
+ }
667
+ }
668
+ )
669
+ let process = Process ( arguments: try args ( forTestAt: path) , environment: self . testEnv, outputRedirection: outputRedirection, verbose: false )
670
+ try self . processSet. add ( process)
691
671
try process. launch ( )
692
672
let result = try process. waitUntilExit ( )
693
673
switch result. exitStatus {
694
674
case . terminated( code: 0 ) :
695
- return true
696
- #if !os(Windows)
675
+ return ( true , makeOutput ( ) )
676
+ #if !os(Windows)
697
677
case . signalled( let signal) :
698
- print ( exitSignalText ( code: signal) )
699
- #endif
678
+ outputRedirection . outputClosures ? . stdoutClosure ( Array ( " \n Exited with signal code \( signal) " . utf8 ) )
679
+ #endif
700
680
default : break
701
681
}
702
682
} catch {
703
- diagnostics. emit ( error)
683
+ self . diagnostics. emit ( error)
704
684
}
705
- return false
706
- }
707
-
708
- private func exitSignalText( code: Int32 ) -> String {
709
- return " Exited with signal code \( code) "
685
+ return ( false , makeOutput ( ) )
710
686
}
711
687
}
712
688
@@ -740,19 +716,22 @@ final class ParallelTestRunner {
740
716
/// True if all tests executed successfully.
741
717
private( set) var ranSuccessfully = true
742
718
743
- let processSet : ProcessSet
719
+ private let processSet : ProcessSet
744
720
745
- let toolchain : UserToolchain
746
- let xUnitOutput : AbsolutePath ?
721
+ private let toolchain : UserToolchain
722
+ private let xUnitOutput : AbsolutePath ?
747
723
748
- let options : SwiftToolOptions
749
- let buildParameters : BuildParameters
724
+ private let options : SwiftToolOptions
725
+ private let buildParameters : BuildParameters
750
726
751
727
/// Number of tests to execute in parallel.
752
- let numJobs : Int
728
+ private let numJobs : Int
729
+
730
+ /// Output stream for test results
731
+ private let outputStream : OutputByteStream
753
732
754
733
/// Diagnostics Engine to emit diagnostics.
755
- let diagnostics : DiagnosticsEngine
734
+ private let diagnostics : DiagnosticsEngine
756
735
757
736
init (
758
737
bundlePaths: [ AbsolutePath ] ,
@@ -770,6 +749,7 @@ final class ParallelTestRunner {
770
749
self . toolchain = toolchain
771
750
self . xUnitOutput = xUnitOutput
772
751
self . numJobs = numJobs
752
+ self . outputStream = outputStream
773
753
self . diagnostics = diagnostics
774
754
775
755
if ProcessEnv . vars [ " SWIFTPM_TEST_RUNNER_PROGRESS_BAR " ] == " lit " {
@@ -813,6 +793,9 @@ final class ParallelTestRunner {
813
793
/// Executes the tests spawning parallel workers. Blocks calling thread until all workers are finished.
814
794
func run( _ tests: [ UnitTest ] , outputStream: OutputByteStream ) throws {
815
795
assert ( !tests. isEmpty, " There should be at least one test to execute. " )
796
+
797
+ let testEnv = try constructTestEnvironment ( toolchain: self . toolchain, options: self . options, buildParameters: self . buildParameters)
798
+
816
799
// Enqueue all the tests.
817
800
try enqueueTests ( tests)
818
801
@@ -826,11 +809,11 @@ final class ParallelTestRunner {
826
809
xctestArg: test. specifier,
827
810
processSet: self . processSet,
828
811
toolchain: self . toolchain,
829
- diagnostics : self . diagnostics ,
830
- options : self . options ,
831
- buildParameters : self . buildParameters
812
+ testEnv : testEnv ,
813
+ outputStream : self . outputStream ,
814
+ diagnostics : self . diagnostics
832
815
)
833
- let ( success, output) = testRunner. test ( )
816
+ let ( success, output) = testRunner. test ( writeToOutputStream : false )
834
817
if !success {
835
818
self . ranSuccessfully = false
836
819
}
@@ -842,17 +825,14 @@ final class ParallelTestRunner {
842
825
} )
843
826
844
827
// List of processed tests.
845
- var processedTests : [ TestResult ] = [ ]
846
- let processedTestsLock = TSCBasic . Lock ( )
828
+ let processedTests = ThreadSafeArrayStore < TestResult > ( )
847
829
848
830
// Report (consume) the tests which have finished running.
849
831
while let result = finishedTests. dequeue ( ) {
850
832
updateProgress ( for: result. unitTest)
851
833
852
834
// Store the result.
853
- processedTestsLock. withLock {
854
- processedTests. append ( result)
855
- }
835
+ processedTests. append ( result)
856
836
857
837
// We can't enqueue a sentinel into finished tests queue because we won't know
858
838
// which test is last one so exit this when all the tests have finished running.
@@ -865,18 +845,18 @@ final class ParallelTestRunner {
865
845
workers. forEach { $0. join ( ) }
866
846
867
847
// Report the completion.
868
- progressAnimation. complete ( success: processedTests. contains ( where: { !$0. success } ) )
848
+ progressAnimation. complete ( success: processedTests. get ( ) . contains ( where: { !$0. success } ) )
869
849
870
850
// Print test results.
871
- for test in processedTests {
851
+ for test in processedTests. get ( ) {
872
852
if !test. success || shouldOutputSuccess {
873
853
print ( test, outputStream: outputStream)
874
854
}
875
855
}
876
856
877
857
// Generate xUnit file if requested.
878
858
if let xUnitOutput = xUnitOutput {
879
- try XUnitGenerator ( processedTests) . generate ( at: xUnitOutput)
859
+ try XUnitGenerator ( processedTests. get ( ) ) . generate ( at: xUnitOutput)
880
860
}
881
861
}
882
862
@@ -1041,14 +1021,14 @@ fileprivate func constructTestEnvironment(
1041
1021
let codecovProfile = buildParameters. buildPath. appending ( components: " codecov " , " default%m.profraw " )
1042
1022
env [ " LLVM_PROFILE_FILE " ] = codecovProfile. pathString
1043
1023
}
1044
- #if !os(macOS)
1045
- #if os(Windows)
1024
+ #if !os(macOS)
1025
+ #if os(Windows)
1046
1026
if let location = toolchain. configuration. xctestPath {
1047
- env [ " Path " ] = " \( location. pathString) ; \( env [ " Path " ] ?? " " ) "
1027
+ env [ " Path " ] = " \( location. pathString) ; \( env [ " Path " ] ?? " " ) "
1048
1028
}
1049
- #endif
1029
+ #endif
1050
1030
return env
1051
- #else
1031
+ #else
1052
1032
// Fast path when no sanitizers are enabled.
1053
1033
if options. sanitizers. isEmpty {
1054
1034
return env
@@ -1066,7 +1046,7 @@ fileprivate func constructTestEnvironment(
1066
1046
1067
1047
env [ " DYLD_INSERT_LIBRARIES " ] = runtimes. joined ( separator: " : " )
1068
1048
return env
1069
- #endif
1049
+ #endif
1070
1050
}
1071
1051
1072
1052
/// xUnit XML file generator for a swift-test run.
0 commit comments