Skip to content

[Process] Implement terminationReason #844

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 30, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Docs/Status.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,10 +271,10 @@ There is no _Complete_ status for test coverage because there are always additio

| Entity Name | Status | Test Coverage | Notes |
|------------------|-----------------|---------------|---------------------------------------------------------------------------------------------------------------------------|
| `FileHandle` | Mostly Complete | Incomplete | `NSCoding`, and background operations remain unimplemented |
| `FileHandle` | Mostly Complete | Incomplete | `NSCoding`, and background operations remain unimplemented |
| `Pipe` | Complete | Incomplete | |
| `FileManager` | Incomplete | Incomplete | URL searches, relationship lookups, item copying, cross-device moving, recursive linking, and others remain unimplemented |
| `Process` | Mostly Complete | Substantial | `interrupt()`, `terminate()`, `suspend()`, `resume()`, and `terminationReason` remain unimplemented |
| `Process` | Mostly Complete | Substantial | `interrupt()`, `terminate()`, `suspend()`, and `resume()` remain unimplemented |
| `Bundle` | Mostly Complete | Incomplete | `allBundles`, `init(for:)`, `unload()`, `classNamed()`, and `principalClass` remain unimplemented |
| `ProcessInfo` | Complete | Substantial | |
| `Thread` | Incomplete | Incomplete | `isMainThread`, `mainThread`, `name`, `callStackReturnAddresses`, and `callStackSymbols` remain unimplemented |
Expand Down
31 changes: 27 additions & 4 deletions Foundation/Process.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,26 @@ extension Process {
}
}

private func WEXITSTATUS(_ status: CInt) -> CInt {
private func WIFEXITED(_ status: Int32) -> Bool {
return _WSTATUS(status) == 0
}

private func _WSTATUS(_ status: Int32) -> Int32 {
return status & 0x7f
}

private func WIFSIGNALED(_ status: Int32) -> Bool {
return (_WSTATUS(status) != 0) && (_WSTATUS(status) != 0x7f)
}

private func WEXITSTATUS(_ status: Int32) -> Int32 {
return (status >> 8) & 0xff
}

private func WTERMSIG(_ status: Int32) -> Int32 {
return status & 0x7f
}

private var managerThreadRunLoop : RunLoop? = nil
private var managerThreadRunLoopIsRunning = false
private var managerThreadRunLoopIsRunningCondition = NSCondition()
Expand Down Expand Up @@ -280,8 +296,15 @@ open class Process: NSObject {
waitResult = waitpid( process.processIdentifier, &exitCode, 0)
#endif
} while ( (waitResult == -1) && (errno == EINTR) )

process.terminationStatus = WEXITSTATUS( exitCode )

if WIFSIGNALED(exitCode) {
process.terminationStatus = WTERMSIG(exitCode)
process.terminationReason = .uncaughtSignal
} else {
assert(WIFEXITED(exitCode))
process.terminationStatus = WEXITSTATUS(exitCode)
process.terminationReason = .exit
}

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

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

open private(set) var terminationStatus: Int32 = 0
open var terminationReason: TerminationReason { NSUnimplemented() }
open private(set) var terminationReason: TerminationReason = .exit

/*
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.
Expand Down
17 changes: 17 additions & 0 deletions TestFoundation/TestProcess.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class TestProcess : XCTestCase {
("test_exit100" , test_exit100),
("test_sleep2", test_sleep2),
("test_sleep2_exit1", test_sleep2_exit1),
("test_terminationReason_uncaughtSignal", test_terminationReason_uncaughtSignal),
("test_pipe_stdin", test_pipe_stdin),
("test_pipe_stdout", test_pipe_stdout),
("test_pipe_stderr", test_pipe_stderr),
Expand All @@ -47,6 +48,7 @@ class TestProcess : XCTestCase {
process.launch()
process.waitUntilExit()
XCTAssertEqual(process.terminationStatus, 0)
XCTAssertEqual(process.terminationReason, .exit)
}

func test_exit1() {
Expand All @@ -59,6 +61,7 @@ class TestProcess : XCTestCase {
process.launch()
process.waitUntilExit()
XCTAssertEqual(process.terminationStatus, 1)
XCTAssertEqual(process.terminationReason, .exit)
}

func test_exit100() {
Expand All @@ -71,6 +74,7 @@ class TestProcess : XCTestCase {
process.launch()
process.waitUntilExit()
XCTAssertEqual(process.terminationStatus, 100)
XCTAssertEqual(process.terminationReason, .exit)
}

func test_sleep2() {
Expand All @@ -83,6 +87,7 @@ class TestProcess : XCTestCase {
process.launch()
process.waitUntilExit()
XCTAssertEqual(process.terminationStatus, 0)
XCTAssertEqual(process.terminationReason, .exit)
}

func test_sleep2_exit1() {
Expand All @@ -95,8 +100,20 @@ class TestProcess : XCTestCase {
process.launch()
process.waitUntilExit()
XCTAssertEqual(process.terminationStatus, 1)
XCTAssertEqual(process.terminationReason, .exit)
}

func test_terminationReason_uncaughtSignal() {
let process = Process()

process.launchPath = "/bin/bash"
process.arguments = ["-c", "kill -TERM $$"]

process.launch()
process.waitUntilExit()
XCTAssertEqual(process.terminationStatus, 15)
XCTAssertEqual(process.terminationReason, .uncaughtSignal)
}

func test_pipe_stdin() {
let process = Process()
Expand Down