@@ -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
@@ -869,13 +873,40 @@ final class TestRunner {
869
873
870
874
/// Executes and returns execution status. Prints test output on standard streams if requested
871
875
/// - Returns: Boolean indicating if test execution returned code 0, and the output stream result
872
- public func test( outputHandler: @escaping ( String ) -> Void ) -> Bool {
873
- var success = true
876
+ func test( outputHandler: @escaping ( String ) -> Void ) -> Bool {
877
+ ( test ( outputHandler: outputHandler) as Result ) != . failure
878
+ }
879
+
880
+ /// The result of running the test(s).
881
+ enum Result : Equatable {
882
+ /// The test(s) ran successfully.
883
+ case success
884
+
885
+ /// The test(s) failed.
886
+ case failure
887
+
888
+ /// There were no matching tests to run.
889
+ ///
890
+ /// XCTest does not report this result. It is used by Swift Testing only.
891
+ case noMatchingTests
892
+ }
893
+
894
+ /// Executes and returns execution status. Prints test output on standard streams if requested
895
+ /// - Returns: Result of spawning and running the test process, and the output stream result
896
+ @_disfavoredOverload
897
+ func test( outputHandler: @escaping ( String ) -> Void ) -> Result {
898
+ var results = [ Result] ( )
874
899
for path in self . bundlePaths {
875
900
let testSuccess = self . test ( at: path, outputHandler: outputHandler)
876
- success = success && testSuccess
901
+ results. append ( testSuccess)
902
+ }
903
+ if results. contains ( . failure) {
904
+ return . failure
905
+ } else if results. isEmpty || results. contains ( . success) {
906
+ return . success
907
+ } else {
908
+ return . noMatchingTests
877
909
}
878
- return success
879
910
}
880
911
881
912
/// Constructs arguments to execute XCTest.
@@ -899,7 +930,7 @@ final class TestRunner {
899
930
return args
900
931
}
901
932
902
- private func test( at path: AbsolutePath , outputHandler: @escaping ( String ) -> Void ) -> Bool {
933
+ private func test( at path: AbsolutePath , outputHandler: @escaping ( String ) -> Void ) -> Result {
903
934
let testObservabilityScope = self . observabilityScope. makeChildScope ( description: " running test at \( path) " )
904
935
905
936
do {
@@ -914,25 +945,27 @@ final class TestRunner {
914
945
)
915
946
let process = AsyncProcess ( arguments: try args ( forTestAt: path) , environment: self . testEnv, outputRedirection: outputRedirection)
916
947
guard let terminationKey = self . cancellator. register ( process) else {
917
- return false // terminating
948
+ return . failure // terminating
918
949
}
919
950
defer { self . cancellator. deregister ( terminationKey) }
920
951
try process. launch ( )
921
952
let result = try process. waitUntilExit ( )
922
953
switch result. exitStatus {
923
954
case . terminated( code: 0 ) :
924
- return true
955
+ return . success
956
+ case . terminated( code: EXIT_NO_TESTS_FOUND) where library == . swiftTesting:
957
+ return . noMatchingTests
925
958
#if !os(Windows)
926
959
case . signalled( let signal) where ![ SIGINT, SIGKILL, SIGTERM] . contains ( signal) :
927
960
testObservabilityScope. emit ( error: " Exited with unexpected signal code \( signal) " )
928
- return false
961
+ return . failure
929
962
#endif
930
963
default :
931
- return false
964
+ return . failure
932
965
}
933
966
} catch {
934
967
testObservabilityScope. emit ( error)
935
- return false
968
+ return . failure
936
969
}
937
970
}
938
971
}
@@ -1399,6 +1432,24 @@ private extension Basics.Diagnostic {
1399
1432
}
1400
1433
}
1401
1434
1435
+ /// The exit code returned to Swift Package Manager by Swift Testing when no
1436
+ /// tests matched the inputs specified by the developer (or, for the case of
1437
+ /// `swift test list`, when no tests were found.)
1438
+ ///
1439
+ /// Because Swift Package Manager does not directly link to the testing library,
1440
+ /// it duplicates the definition of this constant in its own source. Any changes
1441
+ /// to this constant in either package must be mirrored in the other.
1442
+ private var EXIT_NO_TESTS_FOUND : CInt {
1443
+ #if os(macOS) || os(Linux)
1444
+ EX_UNAVAILABLE
1445
+ #elseif os(Windows)
1446
+ ERROR_NOT_FOUND
1447
+ #else
1448
+ #warning("Platform-specific implementation missing: value for EXIT_NO_TESTS_FOUND unavailable")
1449
+ return 2 // We're assuming that EXIT_SUCCESS = 0 and EXIT_FAILURE = 1.
1450
+ #endif
1451
+ }
1452
+
1402
1453
/// Builds the "test" target if enabled in options.
1403
1454
///
1404
1455
/// - Returns: The paths to the build test products.
0 commit comments