Skip to content

Commit dce4e72

Browse files
committed
Detect when Swift Testing returns EXIT_NO_TESTS_FOUND.
This PR detects when a Swift Testing run returns `EXIT_NO_TESTS_FOUND` and treats it as a successful test run. This change handles the change from swiftlang/swift-testing#536. Separately, the refactor in #7766 stops Swift Package Manager from reporting `"No matching test cases were run"` when XCTest has no tests matching passed `--filter` arguments. A third PR after these two have been merged will restore that functionality by collating results from both XCTest and Swift Testing. This change partially resolves rdar://131704587.
1 parent 125b3eb commit dce4e72

File tree

1 file changed

+57
-10
lines changed

1 file changed

+57
-10
lines changed

Sources/Commands/SwiftTestCommand.swift

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -869,13 +869,40 @@ final class TestRunner {
869869

870870
/// Executes and returns execution status. Prints test output on standard streams if requested
871871
/// - 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]()
874895
for path in self.bundlePaths {
875896
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
877905
}
878-
return success
879906
}
880907

881908
/// Constructs arguments to execute XCTest.
@@ -899,7 +926,7 @@ final class TestRunner {
899926
return args
900927
}
901928

902-
private func test(at path: AbsolutePath, outputHandler: @escaping (String) -> Void) -> Bool {
929+
private func test(at path: AbsolutePath, outputHandler: @escaping (String) -> Void) -> Result {
903930
let testObservabilityScope = self.observabilityScope.makeChildScope(description: "running test at \(path)")
904931

905932
do {
@@ -914,25 +941,27 @@ final class TestRunner {
914941
)
915942
let process = AsyncProcess(arguments: try args(forTestAt: path), environment: self.testEnv, outputRedirection: outputRedirection)
916943
guard let terminationKey = self.cancellator.register(process) else {
917-
return false // terminating
944+
return .failure // terminating
918945
}
919946
defer { self.cancellator.deregister(terminationKey) }
920947
try process.launch()
921948
let result = try process.waitUntilExit()
922949
switch result.exitStatus {
923950
case .terminated(code: 0):
924-
return true
951+
return .success
952+
case .terminated(code: EXIT_NO_TESTS_FOUND) where library == .swiftTesting:
953+
return .noMatchingTests
925954
#if !os(Windows)
926955
case .signalled(let signal) where ![SIGINT, SIGKILL, SIGTERM].contains(signal):
927956
testObservabilityScope.emit(error: "Exited with unexpected signal code \(signal)")
928-
return false
957+
return .failure
929958
#endif
930959
default:
931-
return false
960+
return .failure
932961
}
933962
} catch {
934963
testObservabilityScope.emit(error)
935-
return false
964+
return .failure
936965
}
937966
}
938967
}
@@ -1399,6 +1428,24 @@ private extension Basics.Diagnostic {
13991428
}
14001429
}
14011430

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+
14021449
/// Builds the "test" target if enabled in options.
14031450
///
14041451
/// - Returns: The paths to the build test products.

0 commit comments

Comments
 (0)