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