MacroSIP
EnglishРусский中文

测试

三个层面检验 MacroSIP:MacroSIPCore 的 Swift Testing 测试套件、针对 pjsua 的端到端环回通话,以及使用模拟引擎的应用演示启动。三者都在每次推送和拉取请求时在 CI 中运行。只有在 swift build 干净通过、测试通过、scripts/selftest-call.sh 通过并且验证了一次真实通话之后,一个里程碑才算完成(ADR 0006,docs/DEVELOPMENT.md)。

单元与引擎测试:scripts/test.sh

scripts/test.sh                                  # 所有测试套件
scripts/test.sh --filter DialStringTests         # 额外参数传给 swift test

切勿直接运行 swift test:命令行工具附带 Testing 框架,但 SwiftPM 自己找不到它的宏插件。test.sh 显式传入插件路径(-Xswiftc -plugin-path -Xswiftc .../usr/lib/swift/host/plugins/testing)。没有 Xcode 就没有 XCTest,因此每个测试套件都使用 import Testing。

Tests/MacroSIPCoreTests 中的测试套件包含约 210 个测试:

文件 覆盖内容
DialStringTests 解析表(sip:、tel:、普通号码、user@host)
JSONStoreTests、StoreTests、SettingsCompatTests 原子写入、损坏文件恢复、存储往返、旧版 settings.json 和通话记录文件以默认值解码、通话/定时勿扰/录音
KeychainTests 使用测试服务名的封装
ContactTests 号码规范化、匹配、排序、照片、旧字段
AutoAnswerPolicyTests 规则顺序(勿扰、另一路通话、answer-after、模式)
QuickKeyTests 键模型、代接 URI、在线状态注册表
RecordingConverterTests WAV 转 M4A、立体声分离对、头部修复
EngineTests 启动/停止、账户、环回通话(一个引擎呼叫自己)、保持、静音、DTMF、转接、TLS 监听器
EngineMilestone5Tests 会议、单声道和立体声录音、针对 PIDF 存根的在线状态映射、answer-after 信任
EngineAudioTests MOS 表、每路通话和铃声的音量、Opus FEC/DTX、语音处理策略、设备事件去抖
EngineCallOptionsTests 单通话模式、呼叫等待提示音、DTMF 方式、勿扰模式
EngineNetworkTests 注册退避、网络回合、re-INVITE 结果、媒体看门狗、睡眠与唤醒,使用 NetworkStub(一个 UDP 注册服务器/被叫方)
Engine262Tests RTP 端口窗口、rport、使用进程内 DNS 存根的 DNS SRV、拒接规则、附加编解码器
DirectoryParsingTests、DirectoryServiceTests、DirectoryLDAPTests CSV/JSON 源与号码匹配、使用存根提供者的服务、针对进程内 NWListener 服务器的 LDAP

引擎测试使用空音频、绑定到环回地址,并使用 Engine(monitorNetwork: false),因此不依赖主机的网络;makeEngine() 会停止失败测试遗留的引擎,所以一次失败不会级联。TestSupport.swift 提供临时目录。

端到端通话:scripts/selftest-call.sh

scripts/selftest-call.sh                          # 原生
SELFTEST_ARCH=x86_64 scripts/selftest-call.sh     # Rosetta 2 下的 x86_64 切片
SELFTEST_LOG=/tmp/selftest.log SELFTEST_LOG_LEVEL=5 scripts/selftest-call.sh

该脚本从 third_party/pjsip/bin/pjsua 在端口 5080 和 5081 上启动两部 pjsua 电话(空音频、自动接听、循环的 600 Hz 音、5088 和 5089 上的 telnet CLI),构建 macrosip-cli 并运行 macrosip-cli --selftest。自测(Sources/macrosip-cli/SelfTest*.swift)针对一个独立的 PJSIP 实现检查:双向 RTP、保持与恢复(也包括连续操作)、DTMF、单声道录音(可听到音调的 WAV,然后是 M4A)、中间一秒静音的立体声分离录音、通过 pjsua CLI 驱动的在线状态(在线、离开、忙)、带合并和拆分的两路会议以及在两路之间移动的录音、来电(接听、以 486 拒绝)、两部电话之间的盲转接和协商转接、通过 SIP INFO 的 DTMF 以及针对 SIPStub(macrosip-cli 内部一个发出摘要认证挑战的 UDP 注册服务器;每个已授权的 INVITE 必须超过 1300 字节且没有传输切换错误)注册的账户、通话期间和已注册账户下的模拟网络变化、通话质量(必须为 good)、单通话模式、勿扰模式、拒接规则以及仅 GSM 的通话。使用 SELFTEST_ARCH=x86_64 时,CLI 以通用方式构建,CLI 和两部电话都通过 arch -x86_64 运行;pjsua 在 Rosetta 下需要多几秒才能就绪,因此脚本会轮询其 CLI 最多约 15 秒。SELFTEST_CLI=<path> 使用预构建的 CLI。

演示启动与模拟引擎

debug 应用(scripts/make-app.sh debug)接受 Sources/MacroSIPApp/Model/LaunchOptions.swift 中的启动选项;release 应用忽略它们。

选项 效果
--mock-engine 用 MockEngine 代替 PJSIP:注册在 1 秒后成功(密码 wrong 或包含 fail 的域给出 403),外拨在 2 秒后接通
--demo-state <name> 植入示例账户、联系人和记录条目,隐含 --mock-engine 和一个一次性数据目录;绝不触碰钥匙串
--data-dir <path> JSON 存储放在 <path> 而非 Application Support
--snapshot <prefix> 在 --snapshot-after 秒(默认 6)后为每个窗口写出 <prefix>-<window>.png,然后退出;无需显示器
--appearance light|dark 强制主题
-AppleLanguages "(ru)" 标准的 macOS 参数;以俄语运行应用
--log-level 0-6 PJSIP 详细程度(所有构建)

按领域划分的演示状态(完整列表在 Model/DemoState.swift 的头部):

演示自测。--demo-state selftest 像视图那样通过模拟引擎驱动模型,并为每项检查记录一行 PASS 或 FAIL:注册状态(一个账户已注册,一个以 403 失败)、呼叫中和已接通状态、从联系人解析出的来电者姓名、静音、保持与恢复、两路通话的保持切换、协商转接、录音回放、账户测试、状态摘要等。结果输出到 stdout,使用 --snapshot 时输出到 <prefix>-selftest.txt,然后应用退出。--demo-state directory-selftest 只运行目录部分:号码栏中的建议、来电者查询、导入与合并。

timeout 60 dist/MacroSIP.app/Contents/MacOS/MacroSIP --demo-state selftest
timeout 60 dist/MacroSIP.app/Contents/MacOS/MacroSIP --demo-state directory-selftest

字符串:scripts/check-strings.sh

检查 Model/Strings.swift 中使用的每个键都存在于 en.lproj 和 ru.lproj(Localizable.strings 或 Localizable.stringsdict)中,没有文件包含代码未使用的键,没有键同时出现在两个文件中,复数类别完整(英语为 one/other,俄语为 one/few/many/other),并且俄语值没有使用英语值所没有的占位符。每次更改字符串后都要运行它。

截图

docs/screenshots/{light,dark} 和 docs/screenshots/ru/{light,dark} 中的图像来自带 --snapshot 的演示启动;没有单独的截图脚本。俄语深色变体的典型截图:

timeout 30 dist/MacroSIP.app/Contents/MacOS/MacroSIP --demo-state contacts \
  --appearance dark -AppleLanguages "(ru)" --snapshot /tmp/shots/contacts --snapshot-after 3

有来电响铃或打开了表单的状态永不自行退出,因此需要 timeout。俄语文本大约长 25 %,所以添加字符串后请检查状态栏、通话面板按钮和账户弹出菜单。模拟中的 Google 通讯录在 4 秒后应答;用 --snapshot-after 3 捕捉“正在搜索…”行。

CI

.github/workflows/ci.yml 在对 main 的推送和拉取请求时运行,每个 ref 同时只有一个进行中的运行。

作业 运行器 步骤
build macos-15(arm64) 恢复 third_party/pjsip 和 third_party/deps 的缓存(键:build-pjsip.sh 和 config_site.h 的哈希)、build-pjsip.sh、swift build、test.sh、原生以及在 Rosetta 2(按需安装)下以 SELFTEST_ARCH=x86_64 运行 selftest-call.sh、make-app.sh release、对二进制文件执行 lipo -info;将应用(14 天)和通用 PJSIP 构建(3 天)作为产物上传,失败时上传构建日志
intel macos-15-intel(x86_64),在 build 之后 下载通用库和应用(Intel 主机无法运行 build-pjsip.sh),验证签名,用 --data-dir 和 MACROSIP_LOG_DIR 启动应用 15 秒,然后原生运行 swift build、test.sh 和 selftest-call.sh

release.yml 在构建 DMG 之前重复 arm64 的步骤(见 发布)。

另见 构建 和 参与贡献。

其他页面:首页 · Home (English)