Skip to content

Commit 5c86b88

Browse files
committed
Introduce ProcessMonitoringTests
1 parent e6cc238 commit 5c86b88

File tree

7 files changed

+442
-14
lines changed

7 files changed

+442
-14
lines changed

Sources/Subprocess/Error.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ extension SubprocessError: CustomStringConvertible, CustomDebugStringConvertible
111111
case .failedToWriteToSubprocess:
112112
return "Failed to write bytes to the child process."
113113
case .failedToMonitorProcess:
114-
return "Failed to monitor the state of child process with underlying error: \(self.underlyingError!)"
114+
return "Failed to monitor the state of child process with underlying error: \(self.underlyingError == nil ? "nil" : "\(self.underlyingError!)")"
115115
case .streamOutputExceedsLimit(let limit):
116116
return "Failed to create output from current buffer because the output limit (\(limit)) was reached."
117117
case .asyncIOFailed(let reason):

Sources/Subprocess/Platforms/Subprocess+Linux.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ private struct MonitorThreadContext: Sendable {
447447
}
448448
}
449449

450-
private extension siginfo_t {
450+
internal extension siginfo_t {
451451
var si_status: Int32 {
452452
#if canImport(Glibc)
453453
return _sifields._sigchld.si_status
@@ -700,7 +700,7 @@ private let setup: () = {
700700
}()
701701

702702

703-
private func _setupMonitorSignalHandler() {
703+
internal func _setupMonitorSignalHandler() {
704704
// Only executed once
705705
setup
706706
}

Sources/Subprocess/Platforms/Subprocess+Windows.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,7 @@ internal func monitorProcessTermination(
592592
}
593593
}
594594

595-
try? await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, any Error>) in
595+
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, any Error>) in
596596
// Set up a callback that immediately resumes the continuation and does no
597597
// other work.
598598
let context = Unmanaged.passRetained(continuation as AnyObject).toOpaque()

Tests/SubprocessTests/DarwinTests.swift

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ struct SubprocessDarwinTests {
3939
#expect(idResult.terminationStatus == .exited(1234567))
4040
}
4141

42-
@Test func testSubprocessPlatformOptionsPreExecProcessActionAndProcessConfigurator() async throws {
42+
@Test(
43+
.disabled("Constantly fails on macOS 26 and Swift 6.2"),
44+
.bug("https://github.com/swiftlang/swift-subprocess/issues/148")
45+
) func testSubprocessPlatformOptionsPreExecProcessActionAndProcessConfigurator() async throws {
4346
let (readFD, writeFD) = try FileDescriptor.pipe()
4447
try await readFD.closeAfter {
4548
let childPID = try await writeFD.closeAfter {
@@ -165,4 +168,38 @@ struct SubprocessDarwinTests {
165168
}
166169
}
167170

171+
extension FileDescriptor {
172+
internal func readUntilEOF(upToLength maxLength: Int) async throws -> Data {
173+
return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Data, any Error>) in
174+
let dispatchIO = DispatchIO(
175+
type: .stream,
176+
fileDescriptor: self.rawValue,
177+
queue: .global()
178+
) { error in
179+
if error != 0 {
180+
continuation.resume(throwing: POSIXError(.init(rawValue: error) ?? .ENODEV))
181+
}
182+
}
183+
var buffer: Data = Data()
184+
dispatchIO.read(
185+
offset: 0,
186+
length: maxLength,
187+
queue: .global()
188+
) { done, data, error in
189+
guard error == 0 else {
190+
continuation.resume(throwing: POSIXError(.init(rawValue: error) ?? .ENODEV))
191+
return
192+
}
193+
if let data = data {
194+
buffer += Data(data)
195+
}
196+
if done {
197+
dispatchIO.close()
198+
continuation.resume(returning: buffer)
199+
}
200+
}
201+
}
202+
}
203+
}
204+
168205
#endif // canImport(Darwin)

Tests/SubprocessTests/IntegrationTests.swift

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1577,8 +1577,9 @@ extension SubprocessIntegrationTests {
15771577
@Test func testTerminateProcess() async throws {
15781578
#if os(Windows)
15791579
let setup = TestSetup(
1580-
executable: .name("cmd.exe"),
1581-
arguments: ["/c", "timeout /t 99999 >nul"])
1580+
executable: .name("powershell.exe"),
1581+
arguments: ["-Command", "Start-Sleep -Seconds 9999"]
1582+
)
15821583
#else
15831584
let setup = TestSetup(
15841585
executable: .path("/bin/sleep"),
@@ -1789,8 +1790,8 @@ extension SubprocessIntegrationTests {
17891790

17901791
#if os(Windows)
17911792
let setup = TestSetup(
1792-
executable: .name("cmd.exe"),
1793-
arguments: ["/c", "timeout /t 100000 /nobreak"]
1793+
executable: .name("powershell.exe"),
1794+
arguments: ["-Command", "Start-Sleep -Seconds 9999"]
17941795
)
17951796
#else
17961797
let setup = TestSetup(
@@ -2267,3 +2268,29 @@ func _run<Result>(
22672268
)
22682269
}
22692270

2271+
extension FileDescriptor {
2272+
/// Runs a closure and then closes the FileDescriptor, even if an error occurs.
2273+
///
2274+
/// - Parameter body: The closure to run.
2275+
/// If the closure throws an error,
2276+
/// this method closes the file descriptor before it rethrows that error.
2277+
///
2278+
/// - Returns: The value returned by the closure.
2279+
///
2280+
/// If `body` throws an error
2281+
/// or an error occurs while closing the file descriptor,
2282+
/// this method rethrows that error.
2283+
public func closeAfter<R>(_ body: () async throws -> R) async throws -> R {
2284+
// No underscore helper, since the closure's throw isn't necessarily typed.
2285+
let result: R
2286+
do {
2287+
result = try await body()
2288+
} catch {
2289+
_ = try? self.close() // Squash close error and throw closure's
2290+
throw error
2291+
}
2292+
try self.close()
2293+
return result
2294+
}
2295+
}
2296+

0 commit comments

Comments
 (0)