Skip to content

Commit 7def55a

Browse files
authored
Merge pull request #844 from ikesyo/process-terminationreason
2 parents 937beeb + 4dd7b98 commit 7def55a

File tree

3 files changed

+46
-6
lines changed

3 files changed

+46
-6
lines changed

Docs/Status.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,10 +271,10 @@ There is no _Complete_ status for test coverage because there are always additio
271271

272272
| Entity Name | Status | Test Coverage | Notes |
273273
|------------------|-----------------|---------------|---------------------------------------------------------------------------------------------------------------------------|
274-
| `FileHandle` | Mostly Complete | Incomplete | `NSCoding`, and background operations remain unimplemented |
274+
| `FileHandle` | Mostly Complete | Incomplete | `NSCoding`, and background operations remain unimplemented |
275275
| `Pipe` | Complete | Incomplete | |
276276
| `FileManager` | Incomplete | Incomplete | URL searches, relationship lookups, item copying, cross-device moving, recursive linking, and others remain unimplemented |
277-
| `Process` | Mostly Complete | Substantial | `interrupt()`, `terminate()`, `suspend()`, `resume()`, and `terminationReason` remain unimplemented |
277+
| `Process` | Mostly Complete | Substantial | `interrupt()`, `terminate()`, `suspend()`, and `resume()` remain unimplemented |
278278
| `Bundle` | Mostly Complete | Incomplete | `allBundles`, `init(for:)`, `unload()`, `classNamed()`, and `principalClass` remain unimplemented |
279279
| `ProcessInfo` | Complete | Substantial | |
280280
| `Thread` | Incomplete | Incomplete | `isMainThread`, `mainThread`, `name`, `callStackReturnAddresses`, and `callStackSymbols` remain unimplemented |

Foundation/Process.swift

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,26 @@ extension Process {
2323
}
2424
}
2525

26-
private func WEXITSTATUS(_ status: CInt) -> CInt {
26+
private func WIFEXITED(_ status: Int32) -> Bool {
27+
return _WSTATUS(status) == 0
28+
}
29+
30+
private func _WSTATUS(_ status: Int32) -> Int32 {
31+
return status & 0x7f
32+
}
33+
34+
private func WIFSIGNALED(_ status: Int32) -> Bool {
35+
return (_WSTATUS(status) != 0) && (_WSTATUS(status) != 0x7f)
36+
}
37+
38+
private func WEXITSTATUS(_ status: Int32) -> Int32 {
2739
return (status >> 8) & 0xff
2840
}
2941

42+
private func WTERMSIG(_ status: Int32) -> Int32 {
43+
return status & 0x7f
44+
}
45+
3046
private var managerThreadRunLoop : RunLoop? = nil
3147
private var managerThreadRunLoopIsRunning = false
3248
private var managerThreadRunLoopIsRunningCondition = NSCondition()
@@ -280,8 +296,15 @@ open class Process: NSObject {
280296
waitResult = waitpid( process.processIdentifier, &exitCode, 0)
281297
#endif
282298
} while ( (waitResult == -1) && (errno == EINTR) )
283-
284-
process.terminationStatus = WEXITSTATUS( exitCode )
299+
300+
if WIFSIGNALED(exitCode) {
301+
process.terminationStatus = WTERMSIG(exitCode)
302+
process.terminationReason = .uncaughtSignal
303+
} else {
304+
assert(WIFEXITED(exitCode))
305+
process.terminationStatus = WEXITSTATUS(exitCode)
306+
process.terminationReason = .exit
307+
}
285308

286309
// If a termination handler has been set, invoke it on a background thread
287310

@@ -429,7 +452,7 @@ open class Process: NSObject {
429452
open private(set) var isRunning: Bool = false
430453

431454
open private(set) var terminationStatus: Int32 = 0
432-
open var terminationReason: TerminationReason { NSUnimplemented() }
455+
open private(set) var terminationReason: TerminationReason = .exit
433456

434457
/*
435458
A block to be invoked when the process underlying the Process terminates. Setting the block to nil is valid, and stops the previous block from being invoked, as long as it hasn't started in any way. The Process is passed as the argument to the block so the block does not have to capture, and thus retain, it. The block is copied when set. Only one termination handler block can be set at any time. The execution context in which the block is invoked is undefined. If the Process has already finished, the block is executed immediately/soon (not necessarily on the current thread). If a terminationHandler is set on an Process, the ProcessDidTerminateNotification notification is not posted for that process. Also note that -waitUntilExit won't wait until the terminationHandler has been fully executed. You cannot use this property in a concrete subclass of Process which hasn't been updated to include an implementation of the storage and use of it.

TestFoundation/TestProcess.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class TestProcess : XCTestCase {
2424
("test_exit100" , test_exit100),
2525
("test_sleep2", test_sleep2),
2626
("test_sleep2_exit1", test_sleep2_exit1),
27+
("test_terminationReason_uncaughtSignal", test_terminationReason_uncaughtSignal),
2728
("test_pipe_stdin", test_pipe_stdin),
2829
("test_pipe_stdout", test_pipe_stdout),
2930
("test_pipe_stderr", test_pipe_stderr),
@@ -47,6 +48,7 @@ class TestProcess : XCTestCase {
4748
process.launch()
4849
process.waitUntilExit()
4950
XCTAssertEqual(process.terminationStatus, 0)
51+
XCTAssertEqual(process.terminationReason, .exit)
5052
}
5153

5254
func test_exit1() {
@@ -59,6 +61,7 @@ class TestProcess : XCTestCase {
5961
process.launch()
6062
process.waitUntilExit()
6163
XCTAssertEqual(process.terminationStatus, 1)
64+
XCTAssertEqual(process.terminationReason, .exit)
6265
}
6366

6467
func test_exit100() {
@@ -71,6 +74,7 @@ class TestProcess : XCTestCase {
7174
process.launch()
7275
process.waitUntilExit()
7376
XCTAssertEqual(process.terminationStatus, 100)
77+
XCTAssertEqual(process.terminationReason, .exit)
7478
}
7579

7680
func test_sleep2() {
@@ -83,6 +87,7 @@ class TestProcess : XCTestCase {
8387
process.launch()
8488
process.waitUntilExit()
8589
XCTAssertEqual(process.terminationStatus, 0)
90+
XCTAssertEqual(process.terminationReason, .exit)
8691
}
8792

8893
func test_sleep2_exit1() {
@@ -95,8 +100,20 @@ class TestProcess : XCTestCase {
95100
process.launch()
96101
process.waitUntilExit()
97102
XCTAssertEqual(process.terminationStatus, 1)
103+
XCTAssertEqual(process.terminationReason, .exit)
98104
}
99105

106+
func test_terminationReason_uncaughtSignal() {
107+
let process = Process()
108+
109+
process.launchPath = "/bin/bash"
110+
process.arguments = ["-c", "kill -TERM $$"]
111+
112+
process.launch()
113+
process.waitUntilExit()
114+
XCTAssertEqual(process.terminationStatus, 15)
115+
XCTAssertEqual(process.terminationReason, .uncaughtSignal)
116+
}
100117

101118
func test_pipe_stdin() {
102119
let process = Process()

0 commit comments

Comments
 (0)