LinkRecoveryChecks.swift (3294B)
1 // Run without touching hardware: 2 // swiftc Sources/VRWorkstation/{LinkRecovery,MainRunLoop}.swift Tests/LinkRecoveryChecks.swift -o /tmp/vr-recovery-checks-bin 3 // /tmp/vr-recovery-checks-bin 4 import Foundation 5 6 func log(_ text: String) { print(text) } 7 8 @main 9 struct LinkRecoveryChecks { 10 static func attempt(_ recovery: LinkRecovery) -> Bool { 11 var result: Bool? 12 recovery.reset { result = $0 } 13 let deadline = Date().addingTimeInterval(6) 14 while result == nil && Date() < deadline { 15 RunLoop.main.run(until: Date().addingTimeInterval(0.02)) 16 } 17 precondition(result != nil, "recovery did not complete within six seconds") 18 return result! 19 } 20 21 static func main() throws { 22 let directory = FileManager.default.temporaryDirectory.appendingPathComponent("vr-helper-check-\(UUID().uuidString)") 23 try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) 24 defer { try? FileManager.default.removeItem(at: directory) } 25 func helper(_ name: String, _ body: String) throws -> URL { 26 let url = directory.appendingPathComponent(name) 27 try body.write(to: url, atomically: true, encoding: .utf8) 28 try FileManager.default.setAttributes([.posixPermissions: 0o700], ofItemAtPath: url.path) 29 return url 30 } 31 let ok = try helper("ok", "#!/bin/sh\nexit 0\n") 32 let limited = LinkRecovery(helper: ok) 33 precondition((0..<4).map { _ in attempt(limited) } == [true, true, true, false], 34 "helper must run at most three times per minute") 35 36 let stuck = LinkRecovery(helper: try helper("stuck", "#!/bin/sh\nexec /bin/sleep 10\n")) 37 let start = Date() 38 precondition(!attempt(stuck), "a timed-out helper must fail") 39 let seconds = Date().timeIntervalSince(start) 40 precondition(seconds >= 2.9 && seconds < 5, "helper must be killed after about three seconds") 41 42 let missing = LinkRecovery(helper: directory.appendingPathComponent("missing")) 43 precondition(!attempt(missing), "a missing helper must fail") 44 // A headset ADD must cancel startup recovery even before the session binds an ID. 45 let marker = directory.appendingPathComponent("ran") 46 let recording = try helper("recording", "#!/bin/sh\n/usr/bin/touch \(marker.path)\n") 47 let startup = LinkRecovery(helper: recording) 48 startup.requestRecovery() 49 startup.displayAdded(42) 50 Thread.sleep(forTimeInterval: 2.3) // deliberately do not pump main 51 precondition(!FileManager.default.fileExists(atPath: marker.path), "startup ADD must cancel reset") 52 startup.trackDisplay(42) 53 startup.displayRemoved(42) 54 startup.displayAdded(43) 55 Thread.sleep(forTimeInterval: 2.3) 56 precondition(!FileManager.default.fileExists(atPath: marker.path), "changed display ID must cancel reset") 57 startup.displayRemoved(43) 58 Thread.sleep(forTimeInterval: 2.5) 59 precondition(FileManager.default.fileExists(atPath: marker.path), "recovery deadline must fire while main is blocked") 60 startup.stop() 61 print("PASS: three resets/minute; stuck helper stopped in \(seconds)s; missing helper rejected") 62 } 63 }