Files
Commilitia-Drop/ios/CDrop/Tests/LibWebRtcEngineTests.swift
T
admin 349d94aa8f iOS 原生客户端:libwebrtc 数据面 + UX 改造 + 品牌/字体 + 全平台平均速度
- 原生数据面 libwebrtc(PLAN 决策 F):新增 Engine/LibWebRtcEngine.swift(RTCPeerConnection,端口 engine.go/session.go 线协议 + 水位背压 + ack 完成 + 不冲突落盘);NativeTransfer.swift 的 P2PEventsBridge 改 conform P2PEngineDelegate(去 gomobile);EngineController 4 个 p2p RPC 改调新引擎(completion-based + resolveOnMain)。stasel/WebRTC M149 经 SPM(exactVersion 149.0.0)。环回端到端 20MB 逐字节测过(Tests/)
- UX 改造(PARITY 残余项 1/2):新增 SendComposeSheet(发送预览=文件名 + 选设备,手动多选与分享共用);分享直入预览 + scenePhase .active 兜底(修“分享后必须重启 app”);重写 MessagesView(按设备会话 + 每设备未读红点 + 删单条 / 删整设备历史);EngineController 加 conversations / unread 模型
- 品牌 logo:新增 Shared/Brand.swift(BrandMark / Wordmark / BrandLockup / BrandHero)+ Brand.xcassets/LogoMark;登录页 / 传输空态 / 设置头 / 控制中心控件接 logo;Theme 加 cdropAmber
- 字体(对齐 web,仅主 app):新增 BrandFonts.swift(Orbit Gothic + Maple Mono woff2 运行期 CTFontManager 注册 + UIKit 外观代理 + .oXxx / .maple 助手);Fredoka SemiBold 字标;7 视图 .font sweep。字体不入库——.gitignore 排除 + just ios-fonts 构建前从 CDN / Google Fonts 拉取,见 Sources/Fonts/README.md
- 分享引导:新增 Share/ShareGuideView(吉祥物卡 + “打开 Commilitia Drop”);ShareViewController 健壮 staging(文件 / 数据双路)+ 响应链 IMP 唤起宿主
- 全平台平均速度:传输完成态显平均速度(总字节 / 总耗时)而非末次瞬时——web store/slices/transfer.ts + TransferRow.tsx + iOS RootView 卡片 / 详情;i18n 加 transfer.avgRate
- 工程:project.yml 接 SPM WebRTC + CDropTests + Fredoka UIAppFonts + Share i18n;Justfile 加 ios-sim-build + ios-fonts / ios-fonts-fredoka 配方
- 文档:ios/PLAN.md(决策 F + §10 UX + §11 品牌/字体 + §12 平均速度)、ios/PARITY.md(字体分叉条目更新 + 2026-06-28 漂移行)
2026-06-28 19:33:08 +08:00

141 lines
6.4 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import XCTest
@testable import CDrop
// LibWebRtcEngine 线
// offer/answer/ICE + meta//done + ack +
// 线 / / ack / finalize libwebrtc
// / host STUN/TURN /
final class LibWebRtcEngineTests: XCTestCase
{
// delegate LibWebRtcEngine Peer
final class NullDelegate: P2PEngineDelegate
{
static let shared = NullDelegate()
func engineDidProgress(sessionId: String, bytes: Int64) {}
func engineDidChangeState(sessionId: String, state: String) {}
func engineDidEmitSignal(sessionId: String, toPeer: String, payloadJSON: String) {}
func engineDidSave(sessionId: String, path: String) {}
func engineDidSelectIcePair(sessionId: String, local: String, remote: String) {}
func engineDidLog(_ line: String) {}
}
// 宿 / handleSignal
// p2pIos.ts offer/answer trickle ICE
// addIceCandidate remote description
final class Peer: P2PEngineDelegate
{
let name: String // handleSignal fromPeer
let engine: LibWebRtcEngine
weak var other: Peer?
private let signalQueue: DispatchQueue
var onState: ((String) -> Void)?
var onSaved: ((String) -> Void)?
init(name: String, downloadDir: String)
{
self.name = name
signalQueue = DispatchQueue(label: "test.signal.\(name)")
engine = LibWebRtcEngine(downloadDir: downloadDir, delegate: NullDelegate.shared)
engine.delegate = self // self
}
func engineDidProgress(sessionId: String, bytes: Int64) {}
func engineDidChangeState(sessionId: String, state: String)
{
onState?(state)
}
//
func engineDidEmitSignal(sessionId: String, toPeer: String, payloadJSON: String)
{
guard let other else { return }
let fromPeer = name
other.signalQueue.async { [weak other] in
guard let other else { return }
let sem = DispatchSemaphore(value: 0)
other.engine.handleSignal(fromPeer: fromPeer, payloadJSON: payloadJSON) { _ in sem.signal() }
sem.wait()
}
}
func engineDidSave(sessionId: String, path: String)
{
onSaved?(path)
}
func engineDidSelectIcePair(sessionId: String, local: String, remote: String) {}
func engineDidLog(_ line: String) {}
}
// 20 MB 64KB + HIGH/LOW
func testLoopbackTransferIntegrity() throws
{
let fm = FileManager.default
let dirA = fm.temporaryDirectory.appendingPathComponent("cdropTestA-\(UUID().uuidString)")
let dirB = fm.temporaryDirectory.appendingPathComponent("cdropTestB-\(UUID().uuidString)")
try fm.createDirectory(at: dirA, withIntermediateDirectories: true)
try fm.createDirectory(at: dirB, withIntermediateDirectories: true)
defer { try? fm.removeItem(at: dirA); try? fm.removeItem(at: dirB) }
// 线便
let size = 20 * 1024 * 1024
var payload = Data(count: size)
payload.withUnsafeMutableBytes
{ raw in
let p = raw.bindMemory(to: UInt8.self)
var s: UInt64 = 0x9E37_79B9_7F4A_7C15
for i in 0 ..< size
{
s = s &* 6_364_136_223_846_793_005 &+ 1_442_695_040_888_963_407
p[i] = UInt8(truncatingIfNeeded: s >> 33)
}
}
let srcURL = dirA.appendingPathComponent("payload.bin")
try payload.write(to: srcURL)
let senderName = "DeviceA"
let receiverName = "DeviceB"
let peerA = Peer(name: senderName, downloadDir: dirA.path) // engineB senderName
let peerB = Peer(name: receiverName, downloadDir: dirB.path) // engineA receiverName
peerA.other = peerB
peerB.other = peerA
let senderDone = expectation(description: "sender completed")
let receiverSaved = expectation(description: "receiver saved")
var savedPath: String?
peerA.onState =
{ state in
if state == "completed" { senderDone.fulfill() }
else if state == "failed" { XCTFail("sender entered failed state") }
}
peerB.onSaved =
{ path in
savedPath = path
receiverSaved.fulfill()
}
peerB.onState =
{ state in
if state == "failed" { XCTFail("receiver entered failed state") }
}
let sid = "loopback-session-1"
let ice = "[]" // host STUN/TURN
// PeerConnection offer transfer:incoming offer
peerB.engine.startIncoming(sessionId: sid, peerName: senderName, iceServersJSON: ice)
{ err in XCTAssertNil(err, "startIncoming error: \(String(describing: err))") }
peerA.engine.startOutgoing(sessionId: sid, peerName: receiverName,
filePath: srcURL.path, iceServersJSON: ice)
{ err in XCTAssertNil(err, "startOutgoing error: \(String(describing: err))") }
wait(for: [senderDone, receiverSaved], timeout: 120)
let saved = try XCTUnwrap(savedPath, "receiver never reported a saved path")
let got = try Data(contentsOf: URL(fileURLWithPath: saved))
XCTAssertEqual(got.count, size, "received size mismatch")
XCTAssertEqual(got, payload, "received bytes differ from source")
}
}