@@ -37,6 +37,10 @@ import var TSCBasic.stdoutStream
37
37
import class TSCBasic. SynchronizedQueue
38
38
import class TSCBasic. Thread
39
39
40
+ #if os(Windows)
41
+ import WinSDK // for ERROR_NOT_FOUND
42
+ #endif
43
+
40
44
private enum TestError : Swift . Error {
41
45
case invalidListTestJSONData( context: String , underlyingError: Error ? = nil )
42
46
case testsNotFound
@@ -862,13 +866,40 @@ final class TestRunner {
862
866
863
867
/// Executes and returns execution status. Prints test output on standard streams if requested
864
868
/// - Returns: Boolean indicating if test execution returned code 0, and the output stream result
865
- public func test( outputHandler: @escaping ( String ) -> Void ) -> Bool {
866
- var success = true
869
+ func test( outputHandler: @escaping ( String ) -> Void ) -> Bool {
870
+ ( test ( outputHandler: outputHandler) as Result ) != . failure
871
+ }
872
+
873
+ /// The result of running the test(s).
874
+ enum Result : Equatable {
875
+ /// The test(s) ran successfully.
876
+ case success
877
+
878
+ /// The test(s) failed.
879
+ case failure
880
+
881
+ /// There were no matching tests to run.
882
+ ///
883
+ /// XCTest does not report this result. It is used by Swift Testing only.
884
+ case noMatchingTests
885
+ }
886
+
887
+ /// Executes and returns execution status. Prints test output on standard streams if requested
888
+ /// - Returns: Result of spawning and running the test process, and the output stream result
889
+ @_disfavoredOverload
890
+ func test( outputHandler: @escaping ( String ) -> Void ) -> Result {
891
+ var results = [ Result] ( )
867
892
for path in self . bundlePaths {
868
893
let testSuccess = self . test ( at: path, outputHandler: outputHandler)
869
- success = success && testSuccess
894
+ results. append ( testSuccess)
895
+ }
896
+ if results. contains ( . failure) {
897
+ return . failure
898
+ } else if results. isEmpty || results. contains ( . success) {
899
+ return . success
900
+ } else {
901
+ return . noMatchingTests
870
902
}
871
- return success
872
903
}
873
904
874
905
/// Constructs arguments to execute XCTest.
@@ -892,7 +923,7 @@ final class TestRunner {
892
923
return args
893
924
}
894
925
895
- private func test( at path: AbsolutePath , outputHandler: @escaping ( String ) -> Void ) -> Bool {
926
+ private func test( at path: AbsolutePath , outputHandler: @escaping ( String ) -> Void ) -> Result {
896
927
let testObservabilityScope = self . observabilityScope. makeChildScope ( description: " running test at \( path) " )
897
928
898
929
do {
@@ -907,25 +938,27 @@ final class TestRunner {
907
938
)
908
939
let process = AsyncProcess ( arguments: try args ( forTestAt: path) , environment: self . testEnv, outputRedirection: outputRedirection)
909
940
guard let terminationKey = self . cancellator. register ( process) else {
910
- return false // terminating
941
+ return . failure // terminating
911
942
}
912
943
defer { self . cancellator. deregister ( terminationKey) }
913
944
try process. launch ( )
914
945
let result = try process. waitUntilExit ( )
915
946
switch result. exitStatus {
916
947
case . terminated( code: 0 ) :
917
- return true
948
+ return . success
949
+ case . terminated( code: EXIT_NO_TESTS_FOUND) where library == . swiftTesting:
950
+ return . noMatchingTests
918
951
#if !os(Windows)
919
952
case . signalled( let signal) where ![ SIGINT, SIGKILL, SIGTERM] . contains ( signal) :
920
953
testObservabilityScope. emit ( error: " Exited with unexpected signal code \( signal) " )
921
- return false
954
+ return . failure
922
955
#endif
923
956
default :
924
- return false
957
+ return . failure
925
958
}
926
959
} catch {
927
960
testObservabilityScope. emit ( error)
928
- return false
961
+ return . failure
929
962
}
930
963
}
931
964
}
@@ -1392,6 +1425,24 @@ private extension Basics.Diagnostic {
1392
1425
}
1393
1426
}
1394
1427
1428
+ /// The exit code returned to Swift Package Manager by Swift Testing when no
1429
+ /// tests matched the inputs specified by the developer (or, for the case of
1430
+ /// `swift test list`, when no tests were found.)
1431
+ ///
1432
+ /// Because Swift Package Manager does not directly link to the testing library,
1433
+ /// it duplicates the definition of this constant in its own source. Any changes
1434
+ /// to this constant in either package must be mirrored in the other.
1435
+ private var EXIT_NO_TESTS_FOUND : CInt {
1436
+ #if os(macOS) || os(Linux)
1437
+ EX_UNAVAILABLE
1438
+ #elseif os(Windows)
1439
+ ERROR_NOT_FOUND
1440
+ #else
1441
+ #warning("Platform-specific implementation missing: value for EXIT_NO_TESTS_FOUND unavailable")
1442
+ return 2 // We're assuming that EXIT_SUCCESS = 0 and EXIT_FAILURE = 1.
1443
+ #endif
1444
+ }
1445
+
1395
1446
/// Builds the "test" target if enabled in options.
1396
1447
///
1397
1448
/// - Returns: The paths to the build test products.
0 commit comments