128 lines
5.5 KiB
Swift
128 lines
5.5 KiB
Swift
|
|
// This file is auto-generated by alef — DO NOT EDIT.
|
||
|
|
// alef:hash:4e15143f4af1ae8bafbdb1506ef057da924484c66a19483966333558ad437e75
|
||
|
|
// To regenerate: alef generate
|
||
|
|
// To verify freshness: alef verify --exit-code
|
||
|
|
// Issues & docs: https://github.com/kreuzberg-dev/alef
|
||
|
|
// swift-format-ignore-file
|
||
|
|
|
||
|
|
import Foundation
|
||
|
|
#if canImport(FoundationNetworking)
|
||
|
|
// URLSession, URLRequest, HTTPURLResponse, and URLSessionTaskDelegate live in
|
||
|
|
// the FoundationNetworking submodule on swift-corelibs-foundation (Linux). On
|
||
|
|
// Apple platforms these types remain in plain Foundation and this submodule
|
||
|
|
// does not exist; the canImport guard skips the import there.
|
||
|
|
import FoundationNetworking
|
||
|
|
#endif
|
||
|
|
import RustBridge
|
||
|
|
|
||
|
|
// Make `RustString` print its content in XCTest failure output. Without this,
|
||
|
|
// every error thrown from the swift-bridge layer surfaces as
|
||
|
|
// `caught error: "RustBridge.RustString"` with the actual message hidden
|
||
|
|
// inside the opaque class instance. The `@retroactive` keyword acknowledges
|
||
|
|
// that the conformed-to protocol (`CustomStringConvertible`) and the
|
||
|
|
// conforming type (`RustString`) both live outside this module — required by
|
||
|
|
// Swift 6 to silence the retroactive-conformance warning. swift-bridge does
|
||
|
|
// not give `RustString` a `description` of its own, so there is no conflict.
|
||
|
|
extension RustString: @retroactive CustomStringConvertible {
|
||
|
|
public var description: String { self.toString() }
|
||
|
|
}
|
||
|
|
|
||
|
|
// Spawns the alef mock-server once per test process and exposes its base URL.
|
||
|
|
// SwiftPM/XCTest has no global "before all tests" hook that can inject environment
|
||
|
|
// variables (the JVM-style listener trick used by the Java/Kotlin backends), so the
|
||
|
|
// server is started lazily on first access of `baseURL` and kept alive for the
|
||
|
|
// lifetime of the process. A pre-set `MOCK_SERVER_URL` (e.g. exported by CI) wins.
|
||
|
|
enum AlefE2EMockServer {
|
||
|
|
static let baseURL: String = AlefE2EMockServer.start()
|
||
|
|
|
||
|
|
// Retain the child process so it is not reaped while tests run.
|
||
|
|
nonisolated(unsafe) private static var process: Process?
|
||
|
|
|
||
|
|
private static func start() -> String {
|
||
|
|
if let preset = ProcessInfo.processInfo.environment["MOCK_SERVER_URL"], !preset.isEmpty {
|
||
|
|
return preset
|
||
|
|
}
|
||
|
|
let fileManager = FileManager.default
|
||
|
|
var dir = URL(fileURLWithPath: fileManager.currentDirectoryPath)
|
||
|
|
var fixturesDir: URL?
|
||
|
|
for _ in 0..<16 {
|
||
|
|
let candidate = dir.appendingPathComponent("fixtures")
|
||
|
|
var isDir: ObjCBool = false
|
||
|
|
if fileManager.fileExists(atPath: candidate.path, isDirectory: &isDir), isDir.boolValue {
|
||
|
|
fixturesDir = candidate
|
||
|
|
break
|
||
|
|
}
|
||
|
|
let parent = dir.deletingLastPathComponent()
|
||
|
|
if parent.path == dir.path { break }
|
||
|
|
dir = parent
|
||
|
|
}
|
||
|
|
guard let fixtures = fixturesDir else {
|
||
|
|
fatalError("AlefE2EMockServer: could not locate fixtures/ above \(fileManager.currentDirectoryPath)")
|
||
|
|
}
|
||
|
|
let repoRoot = fixtures.deletingLastPathComponent()
|
||
|
|
let binary = repoRoot.appendingPathComponent("e2e/rust/target/release/mock-server")
|
||
|
|
guard fileManager.fileExists(atPath: binary.path) else {
|
||
|
|
fatalError("AlefE2EMockServer: mock-server binary not found at \(binary.path) — run: cargo build --manifest-path e2e/rust/Cargo.toml --bin mock-server --release")
|
||
|
|
}
|
||
|
|
let proc = Process()
|
||
|
|
proc.executableURL = binary
|
||
|
|
proc.arguments = [fixtures.path]
|
||
|
|
let stdoutPipe = Pipe()
|
||
|
|
proc.standardOutput = stdoutPipe
|
||
|
|
// Keep stdin open so the server does not see EOF and exit immediately.
|
||
|
|
proc.standardInput = Pipe()
|
||
|
|
do {
|
||
|
|
try proc.run()
|
||
|
|
} catch {
|
||
|
|
fatalError("AlefE2EMockServer: failed to start mock-server: \(error)")
|
||
|
|
}
|
||
|
|
process = proc
|
||
|
|
let handle = stdoutPipe.fileHandleForReading
|
||
|
|
var buffer = Data()
|
||
|
|
var resolved: String?
|
||
|
|
for _ in 0..<500 {
|
||
|
|
let chunk = handle.availableData
|
||
|
|
if chunk.isEmpty { break }
|
||
|
|
buffer.append(chunk)
|
||
|
|
if let text = String(data: buffer, encoding: .utf8) {
|
||
|
|
for line in text.split(separator: "\n") {
|
||
|
|
if line.hasPrefix("MOCK_SERVER_URL=") {
|
||
|
|
resolved = String(line.dropFirst("MOCK_SERVER_URL=".count)).trimmingCharacters(in: .whitespacesAndNewlines)
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if resolved != nil { break }
|
||
|
|
}
|
||
|
|
guard let url = resolved else {
|
||
|
|
proc.terminate()
|
||
|
|
fatalError("AlefE2EMockServer: mock-server did not emit MOCK_SERVER_URL")
|
||
|
|
}
|
||
|
|
// Drain remaining stdout in the background so a full pipe never blocks the server.
|
||
|
|
DispatchQueue.global(qos: .background).async {
|
||
|
|
while !handle.availableData.isEmpty {}
|
||
|
|
}
|
||
|
|
return url
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// URLSession that does not follow redirects, so tests can assert on 3xx status codes
|
||
|
|
// and Location headers instead of transparently chasing them to the final response.
|
||
|
|
final class AlefE2ENoRedirectDelegate: NSObject, URLSessionTaskDelegate {
|
||
|
|
func urlSession(
|
||
|
|
_ session: URLSession,
|
||
|
|
task: URLSessionTask,
|
||
|
|
willPerformHTTPRedirection response: HTTPURLResponse,
|
||
|
|
newRequest request: URLRequest,
|
||
|
|
completionHandler: @escaping (URLRequest?) -> Void
|
||
|
|
) {
|
||
|
|
completionHandler(nil)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
let alefE2ESession = URLSession(
|
||
|
|
configuration: .ephemeral,
|
||
|
|
delegate: AlefE2ENoRedirectDelegate(),
|
||
|
|
delegateQueue: nil
|
||
|
|
)
|