Skip to content

Instantly share code, notes, and snippets.

@spevans
Created August 6, 2017 09:20
Show Gist options
  • Save spevans/9c5b8c97f9c6a6dbb5d78c588b03a7d2 to your computer and use it in GitHub Desktop.
Save spevans/9c5b8c97f9c6a6dbb5d78c588b03a7d2 to your computer and use it in GitHub Desktop.
Attempts to catch fatalError() in swift for use in XCTestCase, not robust at all
//
// fatalTest.swift
// fatalTest
//
// Created by Simon Evans on 04/08/2017.
// Copyright © 2017 Simon Evans. All rights reserved.
//
import Foundation
import Darwin
func parseFatalErrorOutput(data: Data) -> (String, Int, String)? {
guard let text = String(data: data, encoding: .ascii) else {
return nil
}
let pattern = ".*fatal error: (.*): file (.*), line ([0-9]+)$"
if let regex = try? NSRegularExpression(pattern: pattern,
options: .dotMatchesLineSeparators) {
if let match = regex.matches(in: text, range: NSRange(text.startIndex...,
in: text)).first,
match.numberOfRanges == 4 {
let exitMessage = text[Range(match.range(at: 1), in: text)!]
let fileName = text[Range(match.range(at: 2), in: text)!]
if let lineNumber = Int(text[Range(match.range(at: 3), in: text)!]) {
return (String(fileName), lineNumber, String(exitMessage))
}
}
}
return nil
}
func readData(fd: Int32, timeout: Int32) -> Data? {
func pollInput(fd: Int32, timeout: Int32) -> Bool {
let pollin = Int16(truncatingIfNeeded: POLLIN)
var pollFD = pollfd(fd: fd, events: pollin, revents: 0)
if poll(&pollFD, 1, timeout) == 1 {
return (pollFD.revents & pollin) != 0
} else {
return false
}
}
guard pollInput(fd: fd, timeout: timeout) else {
return nil
}
let fh = FileHandle(fileDescriptor: fd, closeOnDealloc: false)
let data = fh.availableData
guard data.count > 0 else {
return nil
}
return data
}
public func isFatalError(_ expression: @escaping @autoclosure () throws -> Any?) -> Bool {
let oldStderr = dup(STDERR_FILENO)
let pipe = Pipe()
dup2(pipe.fileHandleForWriting.fileDescriptor, STDERR_FILENO)
let oldHandler = signal(SIGILL, { arg in
print("Caught a SIGILL, arg:", arg)
Thread.exit()
})
let thread = Thread(block: {
_ = try? expression()
})
thread.start()
var output = Data()
let startTime = Date()
repeat {
let fd = pipe.fileHandleForReading.fileDescriptor
if let data = readData(fd: fd, timeout: 10) {
output.append(data)
}
let runTime = Date().timeIntervalSince(startTime)
if runTime > 2 {
print("Thread overran")
break
}
} while(thread.isExecuting)
// Restore STDERR and fatalError() SIGILL
dup2(oldStderr, STDERR_FILENO)
close(oldStderr)
signal(SIGILL, oldHandler)
if thread.isExecuting {
fatalError("subThread overran")
}
if let (_, _, _) = parseFatalErrorOutput(data: output) {
// print("File:", fileName)
// print("line:", line)
// print("msg:", msg)
return true
}
return false
}
func someFatalFunc() {
print("someFatalFunc: isMainThread:", Thread.isMainThread)
fatalError("Time to go")
}
func someNormalFunc() {
print("someNormalFunc: isMainThread:", Thread.isMainThread)
}
print("isFatal1:", isFatalError(someFatalFunc()))
print("\nisFatal2:", isFatalError(someNormalFunc()))
print("\nFinished")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment