@@ -63,31 +63,56 @@ struct SubprocessLinuxTests {
63
63
}
64
64
65
65
@Test func testSuspendResumeProcess( ) async throws {
66
- _ = try await Subprocess . run (
66
+ let result = try await Subprocess . run (
67
67
// This will intentionally hang
68
68
. path( " /usr/bin/sleep " ) ,
69
69
arguments: [ " infinity " ] ,
70
+ output: . discarded,
70
71
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)
78
104
}
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
89
108
}
90
109
}
110
+ if let error = result. value {
111
+ #expect( result. terminationStatus == . unhandledException( SIGKILL) )
112
+ throw error
113
+ } else {
114
+ #expect( result. terminationStatus == . unhandledException( SIGTERM) )
115
+ }
91
116
}
92
117
}
93
118
@@ -99,6 +124,15 @@ fileprivate enum ProcessState: String {
99
124
case stopped = " T "
100
125
}
101
126
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
+
102
136
extension Execution {
103
137
fileprivate func state( ) throws -> ProcessState {
104
138
let processStatusFile = " /proc/ \( processIdentifier. value) /status "
@@ -124,7 +158,7 @@ extension Execution {
124
158
}
125
159
}
126
160
127
- func waitForCondition( timeout: Duration , _ evaluateCondition: ( ) throws -> Bool ) async throws {
161
+ func waitForCondition( timeout: Duration , comment : Comment , _ evaluateCondition: ( ) throws -> Bool ) async throws {
128
162
var currentCondition = try evaluateCondition ( )
129
163
let deadline = ContinuousClock . now + timeout
130
164
while ContinuousClock . now < deadline {
@@ -135,11 +169,13 @@ func waitForCondition(timeout: Duration, _ evaluateCondition: () throws -> Bool)
135
169
currentCondition = try evaluateCondition ( )
136
170
}
137
171
struct TimeoutError : Error , CustomStringConvertible {
172
+ let timeout : Duration
173
+ let comment : Comment
138
174
var description : String {
139
- " Timed out waiting for condition to be true "
175
+ comment . description . replacingOccurrences ( of : " $$ " , with : " \( timeout ) " )
140
176
}
141
177
}
142
- throw TimeoutError ( )
178
+ throw TimeoutError ( timeout : timeout , comment : comment )
143
179
}
144
180
145
181
func tryFinally( _ work: ( ) async throws -> ( ) , finally: ( Error ? ) async throws -> ( ) ) async throws {
0 commit comments