A device runs no tool-hosted test bundle, so the package's tests run on one hosted in TestHost/: a minimal application (an empty window) and an Xcode project whose unit-test target compiles the same test files against this package by its path, with the corpus in its resources and local networking allowed for the loopback origin and relay. The package itself is unchanged for consumers. The tests import WaiSink plainly (they use only its public interface) and read the corpus from Bundle.module in the package, from their own bundle in the host. Co-Authored-By: Claude Opus 5.5 <[email protected]>
86 lines
3.6 KiB
Swift
86 lines
3.6 KiB
Swift
// An origin for the tests: an HTTP/1.1 server on the loopback interface serving fixed bodies by
|
|
// path. Nothing here leaves the host.
|
|
import Foundation
|
|
import Network
|
|
|
|
final class TestOrigin: @unchecked Sendable {
|
|
private let queue = DispatchQueue(label: "wai.sink.test-origin")
|
|
private var listener: NWListener?
|
|
private let lock = NSLock()
|
|
private var files: [String: (Data, String)] = [:]
|
|
private(set) var port: UInt16 = 0
|
|
private(set) var requests: [String] = []
|
|
|
|
func put(_ path: String, _ body: Data, type: String = "application/octet-stream") {
|
|
lock.lock(); files[path] = (body, type); lock.unlock()
|
|
}
|
|
|
|
func url(_ path: String) -> URL { URL(string: "http://127.0.0.1:\(port)\(path)")! }
|
|
|
|
func start() throws {
|
|
let params = NWParameters.tcp
|
|
params.requiredLocalEndpoint = NWEndpoint.hostPort(host: .ipv4(.loopback), port: .any)
|
|
let l = try NWListener(using: params)
|
|
let ready = DispatchSemaphore(value: 0)
|
|
l.stateUpdateHandler = { s in if case .ready = s { ready.signal() } }
|
|
l.newConnectionHandler = { [weak self] c in self?.serve(c) }
|
|
l.start(queue: queue)
|
|
_ = ready.wait(timeout: .now() + 5)
|
|
port = l.port?.rawValue ?? 0
|
|
listener = l
|
|
}
|
|
|
|
func stop() { listener?.cancel() }
|
|
|
|
private func serve(_ c: NWConnection) {
|
|
c.start(queue: queue)
|
|
c.receive(minimumIncompleteLength: 1, maximumLength: 65_536) { [weak self] d, _, _, _ in
|
|
guard let self, let d else { return c.cancel() }
|
|
let line = String(decoding: d, as: UTF8.self).components(separatedBy: "\r\n").first ?? ""
|
|
let path = line.split(separator: " ").dropFirst().first.map(String.init) ?? "/"
|
|
self.lock.lock()
|
|
self.requests.append(path)
|
|
let f = self.files[path]
|
|
self.lock.unlock()
|
|
let (status, body, type) = f.map { (200, $0.0, $0.1) } ?? (404, Data(), "text/plain")
|
|
var out = Data("HTTP/1.1 \(status) \(status == 200 ? "OK" : "Not Found")\r\nContent-Type: \(type)\r\nContent-Length: \(body.count)\r\nConnection: close\r\n\r\n".utf8)
|
|
out.append(body)
|
|
c.send(content: out, completion: .contentProcessed { _ in c.cancel() })
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The repository's directory `wai/`, found from this file's path.
|
|
func waiDir() -> URL {
|
|
URL(fileURLWithPath: #filePath).deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent()
|
|
}
|
|
|
|
func hexData(_ s: String) -> Data {
|
|
var d = Data(capacity: s.count / 2)
|
|
var i = s.startIndex
|
|
while i < s.endIndex {
|
|
let j = s.index(i, offsetBy: 2)
|
|
d.append(UInt8(s[i..<j], radix: 16)!)
|
|
i = j
|
|
}
|
|
return d
|
|
}
|
|
|
|
func canonical(_ v: Any) -> String {
|
|
String(decoding: try! JSONSerialization.data(withJSONObject: v, options: [.sortedKeys, .fragmentsAllowed]), as: UTF8.self)
|
|
}
|
|
|
|
/// The test bundle, where the tests are built outside the package (the device host, TestHost/).
|
|
final class TestBundleMarker {}
|
|
|
|
/// The native sink corpus: the copy staged into the test bundle's resources (a device has no
|
|
/// repository), else the repository's.
|
|
func corpus() throws -> [String: Any] {
|
|
#if SWIFT_PACKAGE
|
|
let staged = Bundle.module.url(forResource: "vectors", withExtension: "json", subdirectory: "Resources")
|
|
#else
|
|
let staged = Bundle(for: TestBundleMarker.self).url(forResource: "vectors", withExtension: "json")
|
|
#endif
|
|
let d = try Data(contentsOf: staged ?? waiDir().appendingPathComponent("live-conformance/native-sink/vectors.json"))
|
|
return try JSONSerialization.jsonObject(with: d) as! [String: Any]
|
|
}
|