Created
August 6, 2017 09:20
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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