The SwiftUI app bundles the Go swarm engine through gomobile, so an iPhone is a real peer using the existing PAKE handshake, chunk verification, and relay fallback. XcodeGen builds the project and devicectl installs it on the paired phone.
181 lines
6.1 KiB
Swift
181 lines
6.1 KiB
Swift
import Foundation
|
|
import SwiftUI
|
|
|
|
/// A headless-ish smoke test driven by launch arguments:
|
|
///
|
|
/// xcrun simctl launch <udid> dev.tzgyn.share \
|
|
/// -shareSelfTest -shareSelfTestRelay 127.0.0.1:9009 \
|
|
/// -shareSelfTestCode 1234-selftest-share-apple
|
|
///
|
|
/// The test starts a receive room, waits for one complete file, and writes
|
|
/// `Documents/share-selftest.txt`. Console output is unreliable from a
|
|
/// simulator and impossible to read from a device, so the report on disk is
|
|
/// the source of truth.
|
|
@MainActor
|
|
enum SelfTest {
|
|
private static var started = false
|
|
|
|
static func runIfRequested(model: AppModel) {
|
|
let args = ProcessInfo.processInfo.arguments
|
|
guard !started else { return }
|
|
let relay = value(after: "-shareSelfTestRelay", in: args) ?? "127.0.0.1:9009"
|
|
let code = value(after: "-shareSelfTestCode", in: args)
|
|
?? (args.contains("-shareSelfTestSend")
|
|
? "1234-selftest-send-apple"
|
|
: "1234-selftest-share-apple")
|
|
|
|
if args.contains("-shareSelfTestSend") {
|
|
started = true
|
|
runSend(model: model, relay: relay, code: code)
|
|
} else if args.contains("-shareSelfTest") {
|
|
started = true
|
|
runReceive(model: model, relay: relay, code: code)
|
|
}
|
|
}
|
|
|
|
private static func runReceive(model: AppModel, relay: String, code: String) {
|
|
model.mode = .receive
|
|
model.relay = relay
|
|
model.code = code
|
|
model.startReceive()
|
|
|
|
Task { @MainActor in
|
|
let deadline = Date().addingTimeInterval(120)
|
|
var lastStatus: RoomStatus?
|
|
|
|
while Date() < deadline {
|
|
try? await Task.sleep(nanoseconds: 500_000_000)
|
|
if let status = model.status {
|
|
lastStatus = status
|
|
if status.allComplete {
|
|
writeReport(result: "ok", model: model, status: status, error: nil)
|
|
return
|
|
}
|
|
if !status.running, let error = status.error, !error.isEmpty {
|
|
writeReport(result: "room-error", model: model, status: status, error: error)
|
|
return
|
|
}
|
|
}
|
|
if model.status == nil, let error = model.errorMessage, !error.isEmpty {
|
|
writeReport(result: "start-error", model: model, status: nil, error: error)
|
|
return
|
|
}
|
|
}
|
|
|
|
writeReport(
|
|
result: "timeout",
|
|
model: model,
|
|
status: lastStatus,
|
|
error: "no file completed before the 120s deadline"
|
|
)
|
|
}
|
|
}
|
|
|
|
private static func runSend(model: AppModel, relay: String, code: String) {
|
|
let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
|
let seedDir = documents.appendingPathComponent("Seeds/selftest-send", isDirectory: true)
|
|
let seedURL = seedDir.appendingPathComponent("share-send-selftest.bin")
|
|
do {
|
|
try FileManager.default.createDirectory(at: seedDir, withIntermediateDirectories: true)
|
|
let data = Data(repeating: 0x5A, count: 2 * 1024 * 1024)
|
|
try data.write(to: seedURL)
|
|
} catch {
|
|
writeReport(result: "seed-error", model: model, status: nil, error: error.localizedDescription)
|
|
return
|
|
}
|
|
|
|
model.mode = .send
|
|
model.relay = relay
|
|
model.code = code
|
|
model.pickedFiles = [
|
|
PickedFile(url: seedURL, name: seedURL.lastPathComponent, size: 2 * 1024 * 1024)
|
|
]
|
|
model.startSend()
|
|
|
|
Task { @MainActor in
|
|
let deadline = Date().addingTimeInterval(120)
|
|
var lastStatus: RoomStatus?
|
|
var sawPeer = false
|
|
|
|
while Date() < deadline {
|
|
try? await Task.sleep(nanoseconds: 500_000_000)
|
|
if let status = model.status {
|
|
lastStatus = status
|
|
if status.peers > 0 { sawPeer = true }
|
|
if sawPeer, status.allComplete {
|
|
// Give the receiver a moment to finish the last chunks and
|
|
// disconnect before recording the result.
|
|
try? await Task.sleep(nanoseconds: 1_000_000_000)
|
|
writeReport(result: "ok-seeded", model: model, status: status, error: nil)
|
|
return
|
|
}
|
|
if !status.running, let error = status.error, !error.isEmpty {
|
|
writeReport(result: "room-error", model: model, status: status, error: error)
|
|
return
|
|
}
|
|
}
|
|
if model.status == nil, let error = model.errorMessage, !error.isEmpty {
|
|
writeReport(result: "start-error", model: model, status: nil, error: error)
|
|
return
|
|
}
|
|
}
|
|
|
|
writeReport(
|
|
result: "timeout",
|
|
model: model,
|
|
status: lastStatus,
|
|
error: "no peer completed the seed before the 120s deadline"
|
|
)
|
|
}
|
|
}
|
|
|
|
private static func value(after flag: String, in args: [String]) -> String? {
|
|
guard let index = args.firstIndex(of: flag), index + 1 < args.count else { return nil }
|
|
return args[index + 1]
|
|
}
|
|
|
|
private static func writeReport(
|
|
result: String,
|
|
model: AppModel,
|
|
status: RoomStatus?,
|
|
error: String?
|
|
) {
|
|
let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
|
let url = documents.appendingPathComponent("share-selftest.txt")
|
|
|
|
var lines = [
|
|
"result=\(result)",
|
|
"code=\(status?.code ?? model.code)",
|
|
"relay=\(status?.relay ?? model.relay)",
|
|
"running=\(status?.running ?? false)",
|
|
"peers=\(status?.peers ?? 0)",
|
|
"files=\(status?.files.count ?? 0)",
|
|
]
|
|
if let error, !error.isEmpty {
|
|
lines.append("error=\(error.replacingOccurrences(of: "\n", with: " "))")
|
|
}
|
|
for file in status?.files ?? [] {
|
|
let localURL: URL?
|
|
if model.mode == .send {
|
|
localURL = model.pickedFiles.first(where: { $0.name == file.name })?.url
|
|
} else {
|
|
localURL = model.receivedURL(for: file)
|
|
}
|
|
let exists = localURL.map { FileManager.default.fileExists(atPath: $0.path) } ?? false
|
|
let size = localURL.flatMap {
|
|
try? $0.resourceValues(forKeys: [.fileSizeKey]).fileSize
|
|
} ?? 0
|
|
lines.append(
|
|
"file=\(file.name) size=\(file.size) chunks=\(file.haveChunks)/\(file.totalChunks) complete=\(file.complete) exists=\(exists) diskSize=\(size)"
|
|
)
|
|
}
|
|
|
|
try? lines.joined(separator: "\n").appending("\n").write(
|
|
to: url,
|
|
atomically: true,
|
|
encoding: .utf8
|
|
)
|
|
print("Share self-test \(result): \(url.path)")
|
|
}
|
|
}
|