@@ -63,31 +63,56 @@ struct SubprocessLinuxTests {
6363 }
6464
6565 @Test func testSuspendResumeProcess( ) async throws {
66- _ = try await Subprocess . run (
66+ let result = try await Subprocess . run (
6767 // This will intentionally hang
6868 . path( " /usr/bin/sleep " ) ,
6969 arguments: [ " infinity " ] ,
70+ output: . discarded,
7071 error: . discarded
71- ) { subprocess, standardOutput in
72- try await tryFinally {
73- // First suspend the process
74- try subprocess. send ( signal: . suspend)
75- try await waitForCondition ( timeout: . seconds( 30 ) ) {
76- let state = try subprocess. state ( )
77- return state == . stopped
72+ ) { subprocess -> Error ? in
73+ do {
74+ try await tryFinally {
75+ // First suspend the process
76+ try subprocess. send ( signal: . suspend)
77+ try await waitForCondition ( timeout: . seconds( 30 ) , comment: " Process did not transition from running to stopped state after $$ " ) {
78+ let state = try subprocess. state ( )
79+ switch state {
80+ case . running:
81+ return false
82+ case . zombie:
83+ throw ProcessStateError ( expectedState: . stopped, actualState: state)
84+ case . stopped, . sleeping, . uninterruptibleWait:
85+ return true
86+ }
87+ }
88+ // Now resume the process
89+ try subprocess. send ( signal: . resume)
90+ try await waitForCondition ( timeout: . seconds( 30 ) , comment: " Process did not transition from stopped to running state after $$ " ) {
91+ let state = try subprocess. state ( )
92+ switch state {
93+ case . running, . sleeping, . uninterruptibleWait:
94+ return true
95+ case . zombie:
96+ throw ProcessStateError ( expectedState: . running, actualState: state)
97+ case . stopped:
98+ return false
99+ }
100+ }
101+ } finally: { error in
102+ // Now kill the process
103+ try subprocess. send ( signal: error != nil ? . kill : . terminate)
78104 }
79- // Now resume the process
80- try subprocess. send ( signal: . resume)
81- try await waitForCondition ( timeout: . seconds( 30 ) ) {
82- let state = try subprocess. state ( )
83- return state == . running
84- }
85- } finally: { error in
86- // Now kill the process
87- try subprocess. send ( signal: error != nil ? . kill : . terminate)
88- for try await _ in standardOutput { }
105+ return nil
106+ } catch {
107+ return error
89108 }
90109 }
110+ if let error = result. value {
111+ #expect( result. terminationStatus == . unhandledException( SIGKILL) )
112+ throw error
113+ } else {
114+ #expect( result. terminationStatus == . unhandledException( SIGTERM) )
115+ }
91116 }
92117}
93118
@@ -99,6 +124,15 @@ fileprivate enum ProcessState: String {
99124 case stopped = " T "
100125}
101126
127+ fileprivate struct ProcessStateError : Error , CustomStringConvertible {
128+ let expectedState : ProcessState
129+ let actualState : ProcessState
130+
131+ var description : String {
132+ " Process did not transition to \( expectedState) state, but was actually \( actualState) "
133+ }
134+ }
135+
102136extension Execution {
103137 fileprivate func state( ) throws -> ProcessState {
104138 let processStatusFile = " /proc/ \( processIdentifier. value) /status "
@@ -124,7 +158,7 @@ extension Execution {
124158 }
125159}
126160
127- func waitForCondition( timeout: Duration , _ evaluateCondition: ( ) throws -> Bool ) async throws {
161+ func waitForCondition( timeout: Duration , comment : Comment , _ evaluateCondition: ( ) throws -> Bool ) async throws {
128162 var currentCondition = try evaluateCondition ( )
129163 let deadline = ContinuousClock . now + timeout
130164 while ContinuousClock . now < deadline {
@@ -135,11 +169,13 @@ func waitForCondition(timeout: Duration, _ evaluateCondition: () throws -> Bool)
135169 currentCondition = try evaluateCondition ( )
136170 }
137171 struct TimeoutError : Error , CustomStringConvertible {
172+ let timeout : Duration
173+ let comment : Comment
138174 var description : String {
139- " Timed out waiting for condition to be true "
175+ comment . description . replacingOccurrences ( of : " $$ " , with : " \( timeout ) " )
140176 }
141177 }
142- throw TimeoutError ( )
178+ throw TimeoutError ( timeout : timeout , comment : comment )
143179}
144180
145181func tryFinally( _ work: ( ) async throws -> ( ) , finally: ( Error ? ) async throws -> ( ) ) async throws {
0 commit comments