Skip to content

Instantly share code, notes, and snippets.

@EricRabil
Last active August 1, 2024 08:32
Show Gist options
  • Save EricRabil/60a40cd5ff50b75ea3e5f08ea9f91ba6 to your computer and use it in GitHub Desktop.
Save EricRabil/60a40cd5ff50b75ea3e5f08ea9f91ba6 to your computer and use it in GitHub Desktop.
XCTest doing absolutely nothing? Lack of documentation driving you insane? Avoiding a DTS ticket? Here's a Swift class that forcibly bootstraps XCTest when running inside of a test host and XCTest is not starting itself.

If you're in an atypical codesigning environment you may run into an issue where xctest fails to bootstrap when running inside of a test host. I have no clue why, nor do I care. I would rather have my tests work with my private entitlements with zero friction. This helper class will do what XCTest normally does when running inside of a test host, which is:

  • Loads your xctest bundle
  • Calls XCTestMain
public class XCTHarness {
private typealias XCTestMainType = @convention(c) (_ something: AnyObject?) -> ()
private static func dlsymcast<T>(_ handle: UnsafeMutableRawPointer!, _ symbol: UnsafePointer<CChar>) -> T! {
dlsym(handle, symbol).map {
if T.self is AnyObject.Type {
return $0 as! T
} else {
return unsafeBitCast($0, to: T.self)
}
}
}
// sanity check for troubleshooting, if this is missing we are most likely being invoked manually and whoever invoked us forgot something
private static let isTestSession = ProcessInfo.processInfo.environment.keys.contains("XCTestSessionIdentifier")
// /Applications/Xcode-13.3.1.app/Contents/Developer/../SharedFrameworks/
private static let libraryPath = ProcessInfo.processInfo.environment["LD_LIBRARY_PATH"].map(URL.init(fileURLWithPath:))
// /Applications/Xcode-13.3.1.app/Contents/Developer/../SharedFrameworks/XCTestCore.framework/Versions/Current/XCTestCore
private static let XCTestCorePath = libraryPath?.appendingPathComponent("XCTestCore.framework/Versions/Current/XCTestCore").path
private static let XCTestCore = XCTestCorePath.flatMap { dlopen($0, RTLD_LAZY) }
// /Applications/Xcode-13.3.1.app/Contents/Developer/../SharedFrameworks/XCTest.framework/Versions/Current/XCTest
private static let XCTestPath = libraryPath?.appendingPathComponent("XCTest.framework/Versions/Current/XCTest").path
private static let XCTest = XCTestPath.flatMap { dlopen($0, RTLD_NOW) }
// dlsym(XCTestCore.framework, _XCTestMain)
private static let XCTestMain = XCTestCore.flatMap { dlsymcast($0, "_XCTestMain") as XCTestMainType? }
// relative path from our bundle path to the .xctest containing the tests we are going to run
private static let XCTestBundlePath = ProcessInfo.processInfo.environment["XCTestBundlePath"]
private static let XCTestBundle = XCTestBundlePath.flatMap { Bundle(url: Bundle.main.bundleURL.appendingPathComponent($0)) }
public static func forceStartTestSession() {
if !XCTHarness.isTestSession {
print("Missing XCTestSessionIdentifier, things might not work. I don't think I'm where I'm supposed to be.")
}
guard let _ = XCTHarness.XCTest else {
guard let XCTestPath = XCTHarness.XCTestPath else {
fatalError("Missing LD_LIBRARY_PATH! Xcode inserts this automatically when running a test host, are you Xcode? What's up buddy?")
}
fatalError("Couldn't find XCTest.framework, expected it at \(XCTestPath)")
}
guard let XCTestBundle = XCTHarness.XCTestBundle else {
guard let XCTestBundlePath = XCTHarness.XCTestBundlePath else {
fatalError("Missing XCTestBundlePath! Xcode inserts this automatically when running a test host, are you Xcode? What's up buddy?")
}
fatalError("Couldn't find XCTestBundle, expected it at \(XCTestBundlePath)")
}
guard XCTestBundle.load() else {
fatalError("Can't load xctest bundle at \(XCTHarness.XCTestBundlePath!)")
}
guard let XCTestMain = XCTHarness.XCTestMain else {
fatalError("Can't find _XCTestMain inside \(XCTestCorePath!), I fear DVT has bamboozled us.")
}
XCTestMain(nil)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment