#if canImport(Testing) import Testing #endif #if false import Foundation import XCTest public final class SwiftPMXCTestObserver: NSObject { public override init() { super.init() XCTestObservationCenter.shared.addTestObserver(self) } } extension SwiftPMXCTestObserver: XCTestObservation { var testOutputPath: String { return "/Users/mattbruce/Documents/Projects/iPhone/Baccarat/Baccarat/CasinoKit/.build/arm64-apple-macosx/debug/testOutput.txt" } private func write(record: any Encodable) { let lock = FileLock(at: URL(fileURLWithPath: self.testOutputPath + ".lock")) _ = try? lock.withLock { self._write(record: record) } } private func _write(record: any Encodable) { if let data = try? JSONEncoder().encode(record) { if let fileHandle = FileHandle(forWritingAtPath: self.testOutputPath) { defer { fileHandle.closeFile() } fileHandle.seekToEndOfFile() fileHandle.write("\n".data(using: .utf8)!) fileHandle.write(data) } else { _ = try? data.write(to: URL(fileURLWithPath: self.testOutputPath)) } } } public func testBundleWillStart(_ testBundle: Bundle) { let record = TestBundleEventRecord(bundle: .init(testBundle), event: .start) write(record: TestEventRecord(bundleEvent: record)) } public func testSuiteWillStart(_ testSuite: XCTestSuite) { let record = TestSuiteEventRecord(suite: .init(testSuite), event: .start) write(record: TestEventRecord(suiteEvent: record)) } public func testCaseWillStart(_ testCase: XCTestCase) { let record = TestCaseEventRecord(testCase: .init(testCase), event: .start) write(record: TestEventRecord(caseEvent: record)) } #if canImport(Darwin) public func testCase(_ testCase: XCTestCase, didRecord issue: XCTIssue) { let record = TestCaseFailureRecord(testCase: .init(testCase), issue: .init(issue), failureKind: .unexpected) write(record: TestEventRecord(caseFailure: record)) } public func testCase(_ testCase: XCTestCase, didRecord expectedFailure: XCTExpectedFailure) { let record = TestCaseFailureRecord(testCase: .init(testCase), issue: .init(expectedFailure.issue), failureKind: .expected(failureReason: expectedFailure.failureReason)) write(record: TestEventRecord(caseFailure: record)) } #else public func testCase(_ testCase: XCTestCase, didFailWithDescription description: String, inFile filePath: String?, atLine lineNumber: Int) { let issue = TestIssue(description: description, inFile: filePath, atLine: lineNumber) let record = TestCaseFailureRecord(testCase: .init(testCase), issue: issue, failureKind: .unexpected) write(record: TestEventRecord(caseFailure: record)) } #endif public func testCaseDidFinish(_ testCase: XCTestCase) { let record = TestCaseEventRecord(testCase: .init(testCase), event: .finish) write(record: TestEventRecord(caseEvent: record)) } #if canImport(Darwin) public func testSuite(_ testSuite: XCTestSuite, didRecord issue: XCTIssue) { let record = TestSuiteFailureRecord(suite: .init(testSuite), issue: .init(issue), failureKind: .unexpected) write(record: TestEventRecord(suiteFailure: record)) } public func testSuite(_ testSuite: XCTestSuite, didRecord expectedFailure: XCTExpectedFailure) { let record = TestSuiteFailureRecord(suite: .init(testSuite), issue: .init(expectedFailure.issue), failureKind: .expected(failureReason: expectedFailure.failureReason)) write(record: TestEventRecord(suiteFailure: record)) } #else public func testSuite(_ testSuite: XCTestSuite, didFailWithDescription description: String, inFile filePath: String?, atLine lineNumber: Int) { let issue = TestIssue(description: description, inFile: filePath, atLine: lineNumber) let record = TestSuiteFailureRecord(suite: .init(testSuite), issue: issue, failureKind: .unexpected) write(record: TestEventRecord(suiteFailure: record)) } #endif public func testSuiteDidFinish(_ testSuite: XCTestSuite) { let record = TestSuiteEventRecord(suite: .init(testSuite), event: .finish) write(record: TestEventRecord(suiteEvent: record)) } public func testBundleDidFinish(_ testBundle: Bundle) { let record = TestBundleEventRecord(bundle: .init(testBundle), event: .finish) write(record: TestEventRecord(bundleEvent: record)) } } // FIXME: Copied from `Lock.swift` in TSCBasic, would be nice if we had a better way #if canImport(Glibc) @_exported import Glibc #elseif canImport(Musl) @_exported import Musl #elseif os(Windows) @_exported import CRT @_exported import WinSDK #elseif os(WASI) @_exported import WASILibc #elseif canImport(Android) @_exported import Android #else @_exported import Darwin.C #endif import Foundation public final class FileLock { #if os(Windows) private var handle: HANDLE? #else private var fileDescriptor: CInt? #endif private let lockFile: URL public init(at lockFile: URL) { self.lockFile = lockFile } public func lock() throws { #if os(Windows) if handle == nil { let h: HANDLE = lockFile.path.withCString(encodedAs: UTF16.self, { CreateFileW( $0, UInt32(GENERIC_READ) | UInt32(GENERIC_WRITE), UInt32(FILE_SHARE_READ) | UInt32(FILE_SHARE_WRITE), nil, DWORD(OPEN_ALWAYS), DWORD(FILE_ATTRIBUTE_NORMAL), nil ) }) if h == INVALID_HANDLE_VALUE { throw FileSystemError(errno: Int32(GetLastError()), lockFile) } self.handle = h } var overlapped = OVERLAPPED() overlapped.Offset = 0 overlapped.OffsetHigh = 0 overlapped.hEvent = nil if !LockFileEx(handle, DWORD(LOCKFILE_EXCLUSIVE_LOCK), 0, UInt32.max, UInt32.max, &overlapped) { throw ProcessLockError.unableToAquireLock(errno: Int32(GetLastError())) } #elseif os(WASI) // WASI doesn't support flock #else if fileDescriptor == nil { let fd = open(lockFile.path, O_WRONLY | O_CREAT | O_CLOEXEC, 0o666) if fd == -1 { fatalError("errno: \(errno), lockFile: \(lockFile)") } self.fileDescriptor = fd } while true { if flock(fileDescriptor!, LOCK_EX) == 0 { break } if errno == EINTR { continue } fatalError("unable to acquire lock, errno: \(errno)") } #endif } public func unlock() { #if os(Windows) var overlapped = OVERLAPPED() overlapped.Offset = 0 overlapped.OffsetHigh = 0 overlapped.hEvent = nil UnlockFileEx(handle, 0, UInt32.max, UInt32.max, &overlapped) #elseif os(WASI) // WASI doesn't support flock #else guard let fd = fileDescriptor else { return } flock(fd, LOCK_UN) #endif } deinit { #if os(Windows) guard let handle = handle else { return } CloseHandle(handle) #elseif os(WASI) // WASI doesn't support flock #else guard let fd = fileDescriptor else { return } close(fd) #endif } public func withLock(_ body: () throws -> T) throws -> T { try lock() defer { unlock() } return try body() } public func withLock(_ body: () async throws -> T) async throws -> T { try lock() defer { unlock() } return try await body() } } // FIXME: Copied from `XCTEvents.swift`, would be nice if we had a better way struct TestEventRecord: Codable { let caseFailure: TestCaseFailureRecord? let suiteFailure: TestSuiteFailureRecord? let bundleEvent: TestBundleEventRecord? let suiteEvent: TestSuiteEventRecord? let caseEvent: TestCaseEventRecord? init( caseFailure: TestCaseFailureRecord? = nil, suiteFailure: TestSuiteFailureRecord? = nil, bundleEvent: TestBundleEventRecord? = nil, suiteEvent: TestSuiteEventRecord? = nil, caseEvent: TestCaseEventRecord? = nil ) { self.caseFailure = caseFailure self.suiteFailure = suiteFailure self.bundleEvent = bundleEvent self.suiteEvent = suiteEvent self.caseEvent = caseEvent } } // MARK: - Records struct TestAttachment: Codable { let name: String? // TODO: Handle `userInfo: [AnyHashable : Any]?` let uniformTypeIdentifier: String let payload: Data? } struct TestBundleEventRecord: Codable { let bundle: TestBundle let event: TestEvent } struct TestCaseEventRecord: Codable { let testCase: TestCase let event: TestEvent } struct TestCaseFailureRecord: Codable, CustomStringConvertible { let testCase: TestCase let issue: TestIssue let failureKind: TestFailureKind var description: String { return "\(issue.sourceCodeContext.description)\(testCase) \(issue.compactDescription)" } } struct TestSuiteEventRecord: Codable { let suite: TestSuiteRecord let event: TestEvent } struct TestSuiteFailureRecord: Codable { let suite: TestSuiteRecord let issue: TestIssue let failureKind: TestFailureKind } // MARK: Primitives struct TestBundle: Codable { let bundleIdentifier: String? let bundlePath: String } struct TestCase: Codable { let name: String } struct TestErrorInfo: Codable { let description: String let type: String } enum TestEvent: Codable { case start case finish } enum TestFailureKind: Codable, Equatable { case unexpected case expected(failureReason: String?) var isExpected: Bool { switch self { case .expected: return true case .unexpected: return false } } } struct TestIssue: Codable { let type: TestIssueType let compactDescription: String let detailedDescription: String? let associatedError: TestErrorInfo? let sourceCodeContext: TestSourceCodeContext let attachments: [TestAttachment] } enum TestIssueType: Codable { case assertionFailure case performanceRegression case system case thrownError case uncaughtException case unmatchedExpectedFailure case unknown } struct TestLocation: Codable, CustomStringConvertible { let file: String let line: Int var description: String { return "\(file):\(line) " } } struct TestSourceCodeContext: Codable, CustomStringConvertible { let callStack: [TestSourceCodeFrame] let location: TestLocation? var description: String { return location?.description ?? "" } } struct TestSourceCodeFrame: Codable { let address: UInt64 let symbolInfo: TestSourceCodeSymbolInfo? let symbolicationError: TestErrorInfo? } struct TestSourceCodeSymbolInfo: Codable { let imageName: String let symbolName: String let location: TestLocation? } struct TestSuiteRecord: Codable { let name: String } // MARK: XCTest compatibility extension TestIssue { init(description: String, inFile filePath: String?, atLine lineNumber: Int) { let location: TestLocation? if let filePath = filePath { location = .init(file: filePath, line: lineNumber) } else { location = nil } self.init(type: .assertionFailure, compactDescription: description, detailedDescription: description, associatedError: nil, sourceCodeContext: .init(callStack: [], location: location), attachments: []) } } import XCTest #if canImport(Darwin) // XCTAttachment is unavailable in swift-corelibs-xctest. extension TestAttachment { init(_ attachment: XCTAttachment) { self.init( name: attachment.name, uniformTypeIdentifier: attachment.uniformTypeIdentifier, payload: attachment.value(forKey: "payload") as? Data ) } } #endif extension TestBundle { init(_ testBundle: Bundle) { self.init( bundleIdentifier: testBundle.bundleIdentifier, bundlePath: testBundle.bundlePath ) } } extension TestCase { init(_ testCase: XCTestCase) { self.init(name: testCase.name) } } extension TestErrorInfo { init(_ error: any Swift.Error) { self.init(description: "\(error)", type: "\(Swift.type(of: error))") } } #if canImport(Darwin) // XCTIssue is unavailable in swift-corelibs-xctest. extension TestIssue { init(_ issue: XCTIssue) { self.init( type: .init(issue.type), compactDescription: issue.compactDescription, detailedDescription: issue.detailedDescription, associatedError: issue.associatedError.map { .init($0) }, sourceCodeContext: .init(issue.sourceCodeContext), attachments: issue.attachments.map { .init($0) } ) } } extension TestIssueType { init(_ type: XCTIssue.IssueType) { switch type { case .assertionFailure: self = .assertionFailure case .thrownError: self = .thrownError case .uncaughtException: self = .uncaughtException case .performanceRegression: self = .performanceRegression case .system: self = .system case .unmatchedExpectedFailure: self = .unmatchedExpectedFailure @unknown default: self = .unknown } } } #endif #if canImport(Darwin) // XCTSourceCodeLocation/XCTSourceCodeContext/XCTSourceCodeFrame/XCTSourceCodeSymbolInfo is unavailable in swift-corelibs-xctest. extension TestLocation { init(_ location: XCTSourceCodeLocation) { self.init( file: location.fileURL.absoluteString, line: location.lineNumber ) } } extension TestSourceCodeContext { init(_ context: XCTSourceCodeContext) { self.init( callStack: context.callStack.map { .init($0) }, location: context.location.map { .init($0) } ) } } extension TestSourceCodeFrame { init(_ frame: XCTSourceCodeFrame) { self.init( address: frame.address, symbolInfo: (try? frame.symbolInfo()).map { .init($0) }, symbolicationError: frame.symbolicationError.map { .init($0) } ) } } extension TestSourceCodeSymbolInfo { init(_ symbolInfo: XCTSourceCodeSymbolInfo) { self.init( imageName: symbolInfo.imageName, symbolName: symbolInfo.symbolName, location: symbolInfo.location.map { .init($0) } ) } } #endif extension TestSuiteRecord { init(_ testSuite: XCTestSuite) { self.init(name: testSuite.name) } } import XCTest #endif @main @available(macOS 10.15, iOS 11, watchOS 4, tvOS 11, *) @available(*, deprecated, message: "Not actually deprecated. Marked as deprecated to allow inclusion of deprecated tests (which test deprecated functionality) without warnings") struct Runner { private static func testingLibrary() -> String { var iterator = CommandLine.arguments.makeIterator() while let argument = iterator.next() { if argument == "--testing-library", let libraryName = iterator.next() { return libraryName.lowercased() } } // Fallback if not specified: run XCTest (legacy behavior) return "xctest" } #if false @_silgen_name("$ss13_runAsyncMainyyyyYaKcF") private static func _runAsyncMain(_ asyncFun: @Sendable @escaping () async throws -> ()) #endif static func main() async { let testingLibrary = Self.testingLibrary() #if canImport(Testing) if testingLibrary == "swift-testing" { #if false _runAsyncMain { await Testing.__swiftPMEntryPoint() as Never } #else await Testing.__swiftPMEntryPoint() as Never #endif } #endif #if false if testingLibrary == "xctest" { XCTMain(__allDiscoveredTests()) as Never } #endif } }