@@ -37,6 +37,10 @@ import var TSCBasic.stdoutStream
3737import  class  TSCBasic. SynchronizedQueue
3838import  class  TSCBasic. Thread
3939
40+ #if os(Windows) 
41+ import  WinSDK // for ERROR_NOT_FOUND
42+ #endif 
43+ 
4044private  enum  TestError :  Swift . Error  { 
4145    case  invalidListTestJSONData( context:  String ,  underlyingError:  Error ? =  nil ) 
4246    case  testsNotFound
@@ -869,13 +873,40 @@ final class TestRunner {
869873
870874    /// Executes and returns execution status. Prints test output on standard streams if requested
871875    /// - 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] ( ) 
874899        for  path  in  self . bundlePaths { 
875900            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
877909        } 
878-         return  success
879910    } 
880911
881912    /// Constructs arguments to execute XCTest.
@@ -899,7 +930,7 @@ final class TestRunner {
899930        return  args
900931    } 
901932
902-     private  func  test( at path:  AbsolutePath ,  outputHandler:  @escaping  ( String )  ->  Void )  ->  Bool  { 
933+     private  func  test( at path:  AbsolutePath ,  outputHandler:  @escaping  ( String )  ->  Void )  ->  Result  { 
903934        let  testObservabilityScope  =  self . observabilityScope. makeChildScope ( description:  " running test at  \( path) " ) 
904935
905936        do  { 
@@ -914,25 +945,27 @@ final class TestRunner {
914945            ) 
915946            let  process  =  AsyncProcess ( arguments:  try args ( forTestAt:  path) ,  environment:  self . testEnv,  outputRedirection:  outputRedirection) 
916947            guard  let  terminationKey =  self . cancellator. register ( process)  else  { 
917-                 return  false  // terminating
948+                 return  . failure  // terminating
918949            } 
919950            defer  {  self . cancellator. deregister ( terminationKey)  } 
920951            try . launch ( ) 
921952            let  result  =  try . waitUntilExit ( ) 
922953            switch  result. exitStatus { 
923954            case  . terminated( code:  0 ) : 
924-                 return  true 
955+                 return  . success
956+             case  . terminated( code:  EXIT_NO_TESTS_FOUND)  where  library ==  . swiftTesting: 
957+                 return  . noMatchingTests
925958            #if !os(Windows) 
926959            case  . signalled( let  signal)  where  ![ SIGINT,  SIGKILL,  SIGTERM] . contains ( signal) : 
927960                testObservabilityScope. emit ( error:  " Exited with unexpected signal code  \( signal) " ) 
928-                 return  false 
961+                 return  . failure 
929962            #endif 
930963            default : 
931-                 return  false 
964+                 return  . failure 
932965            } 
933966        }  catch  { 
934967            testObservabilityScope. emit ( error) 
935-             return  false 
968+             return  . failure 
936969        } 
937970    } 
938971} 
@@ -1399,6 +1432,24 @@ private extension Basics.Diagnostic {
13991432    } 
14001433} 
14011434
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+ 
14021453/// Builds the "test" target if enabled in options.
14031454///
14041455/// - Returns: The paths to the build test products.
0 commit comments