diff --git a/Sources/Backtrace/Backtrace.swift b/Sources/Backtrace/Backtrace.swift index 69d30de..9d08f2e 100644 --- a/Sources/Backtrace/Backtrace.swift +++ b/Sources/Backtrace/Backtrace.swift @@ -67,9 +67,9 @@ private func printBacktrace(signal: CInt) { } public enum Backtrace { - /// Install the backtrace handler on `SIGILL`. + /// Install the backtrace handler on default signals: `SIGILL`, `SIGSEGV`, `SIGBUS`, `SIGFPE`. public static func install() { - Backtrace.install(signals: [SIGILL]) + Backtrace.install(signals: [SIGILL, SIGSEGV, SIGBUS, SIGFPE]) } /// Install the backtrace handler when any of `signals` happen. diff --git a/Sources/Sample/main.swift b/Sources/Sample/main.swift index 2e1879a..b6a0b39 100644 --- a/Sources/Sample/main.swift +++ b/Sources/Sample/main.swift @@ -13,7 +13,28 @@ //===----------------------------------------------------------------------===// import Backtrace +#if canImport(Darwin) +import Darwin +#elseif os(Linux) +import Glibc +#endif -let reason = CommandLine.arguments.count == 2 ? CommandLine.arguments[1] : "unknown" Backtrace.install() -fatalError(reason) + +func raiseSignal(_ signal: Int32) { + raise(signal) +} + +let reason = CommandLine.arguments.count == 2 ? CommandLine.arguments[1] : "unknown" +switch reason.uppercased() { +case "SIGILL": + raiseSignal(SIGILL) +case "SIGSEGV": + raiseSignal(SIGSEGV) +case "SIGBUS": + raiseSignal(SIGBUS) +case "SIGFPE": + raiseSignal(SIGFPE) +default: + fatalError(reason) +} diff --git a/Tests/BacktraceTests/BacktraceTests+XCTest.swift b/Tests/BacktraceTests/BacktraceTests+XCTest.swift index 4c5f604..3e497ed 100644 --- a/Tests/BacktraceTests/BacktraceTests+XCTest.swift +++ b/Tests/BacktraceTests/BacktraceTests+XCTest.swift @@ -25,7 +25,11 @@ import XCTest extension BacktraceTests { public static var allTests: [(String, (BacktraceTests) -> () throws -> Void)] { return [ - ("testBacktrace", testBacktrace), + ("testFatalError", testFatalError), + ("testSIGILL", testSIGILL), + ("testSIGSEGV", testSIGSEGV), + ("testSIGBUS", testSIGBUS), + ("testSIGFPE", testSIGFPE), ] } } diff --git a/Tests/BacktraceTests/BacktraceTests.swift b/Tests/BacktraceTests/BacktraceTests.swift index 2c7db36..dbe3f16 100644 --- a/Tests/BacktraceTests/BacktraceTests.swift +++ b/Tests/BacktraceTests/BacktraceTests.swift @@ -15,22 +15,76 @@ import XCTest public final class BacktraceTests: XCTestCase { - func testBacktrace() { - #if os(Linux) + func testFatalError() throws { + #if !os(Linux) + try XCTSkipIf(true, "test is only supported on Linux") + #endif + let expectedError = UUID().uuidString - let pipe = Pipe() - let process = Process() - process.executableURL = URL(fileURLWithPath: "/usr/bin/swift") - process.arguments = ["run", "Sample", expectedError] - process.standardError = pipe - XCTAssertNoThrow(try process.run()) - if process.isRunning { - process.waitUntilExit() - } - let stderr = String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? "" + let stderr = try runSample(reason: expectedError) print(stderr) + + XCTAssert(stderr.contains("Received signal 4. Backtrace:")) XCTAssert(stderr.contains("Current stack trace:"), "expected stanard error to include backtrace") XCTAssert(stderr.contains("Fatal error: \(expectedError)"), "expected stanard error to include error information") + } + + func testSIGILL() throws { + #if !os(Linux) + try XCTSkipIf(true, "test is only supported on Linux") + #endif + + let stderr = try runSample(reason: "SIGILL") + print(stderr) + + XCTAssert(stderr.contains("Received signal \(SIGILL). Backtrace:")) + XCTAssert(stderr.contains("Sample.raiseSignal")) + } + + func testSIGSEGV() throws { + #if !os(Linux) + try XCTSkipIf(true, "test is only supported on Linux") + #endif + + let stderr = try runSample(reason: "SIGSEGV") + print(stderr) + + XCTAssert(stderr.contains("Received signal \(SIGSEGV). Backtrace:")) + XCTAssert(stderr.contains("Sample.raiseSignal")) + } + + func testSIGBUS() throws { + #if !os(Linux) + try XCTSkipIf(true, "test is only supported on Linux") + #endif + + let stderr = try runSample(reason: "SIGBUS") + print(stderr) + + XCTAssert(stderr.contains("Received signal \(SIGBUS). Backtrace:")) + XCTAssert(stderr.contains("Sample.raiseSignal")) + } + + func testSIGFPE() throws { + #if !os(Linux) + try XCTSkipIf(true, "test is only supported on Linux") #endif + + let stderr = try runSample(reason: "SIGFPE") + print(stderr) + + XCTAssert(stderr.contains("Received signal \(SIGFPE). Backtrace:")) + XCTAssert(stderr.contains("Sample.raiseSignal")) + } + + func runSample(reason: String) throws -> String { + let pipe = Pipe() + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/swift") + process.arguments = ["run", "Sample", reason] + process.standardError = pipe + try process.run() + process.waitUntilExit() + return String(data: pipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? "" } } diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 91ddf2c..1299f6f 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -34,4 +34,4 @@ services: shell: <<: *common - entrypoint: /bin/bash + entrypoint: /bin/bash -l