Тесты
MacroSIP проверяют три уровня: наборы Swift Testing для MacroSIPCore, сквозной звонок по loopback с pjsua и демонстрационные запуски приложения с имитацией движка. Все три выполняются в CI при каждом push и pull request. Веха считается закрытой, только когда swift build проходит чисто, тесты зелёные, scripts/selftest-call.sh проходит и проверен настоящий звонок (ADR 0006, docs/DEVELOPMENT.md).
Модульные тесты и тесты движка: scripts/test.sh
scripts/test.sh # all suites
scripts/test.sh --filter DialStringTests # extra arguments go to swift testНикогда не запускайте голый swift test: Command Line Tools поставляют фреймворк Testing, но SwiftPM сам не находит его плагин макросов. test.sh передаёт путь к плагину явно (-Xswiftc -plugin-path -Xswiftc .../usr/lib/swift/host/plugins/testing). XCTest без Xcode недоступен, поэтому каждый набор использует 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 |
запуск/остановку, аккаунты, loopback-звонки (движок звонит сам себе), удержание, выключение звука, DTMF, перевод, слушателей TLS |
EngineMilestone5Tests |
конференцию, запись в моно и стерео, отображение статусов на заглушке PIDF, доверие к answer-after |
EngineAudioTests |
таблицу MOS, громкость звонка и мелодии, Opus FEC/DTX, политику обработки голоса, подавление дребезга событий устройств |
EngineCallOptionsTests |
режим одного звонка, сигнал ожидания вызова, способы DTMF, «Не беспокоить» |
EngineNetworkTests |
повтор регистрации с нарастающей паузой, сетевые раунды, исходы re-INVITE, сторожа медиа, сон и пробуждение, с NetworkStub (UDP-регистратор/вызываемый) |
Engine262Tests |
окно портов RTP, rport, DNS SRV с DNS-заглушкой в процессе, правила отклонения, дополнительные кодеки |
DirectoryParsingTests, DirectoryServiceTests, DirectoryLDAPTests |
ленты CSV/JSON и сопоставление номеров, сервис с провайдером-заглушкой, LDAP с сервером NWListener в процессе |
Тесты движка работают с null-аудио, привязаны к loopback и используют Engine(monitorNetwork: false), чтобы не зависеть от сети хоста; makeEngine() останавливает движок, оставшийся от упавшего теста, так что один сбой не тянет за собой остальные. TestSupport.swift предоставляет временные каталоги.
Сквозной звонок: scripts/selftest-call.sh
scripts/selftest-call.sh # native
SELFTEST_ARCH=x86_64 scripts/selftest-call.sh # the x86_64 slice under Rosetta 2
SELFTEST_LOG=/tmp/selftest.log SELFTEST_LOG_LEVEL=5 scripts/selftest-call.shСкрипт запускает два телефона pjsua из third_party/pjsip/bin/pjsua на портах 5080 и 5081 (null-аудио, автоответ, зацикленный тон 600 Гц, telnet-консоли на 5088 и 5089), собирает macrosip-cli и выполняет macrosip-cli --selftest. Самопроверка (Sources/macrosip-cli/SelfTest*.swift) проверяет на независимой реализации PJSIP: двусторонний RTP, удержание и возобновление (в том числе подряд), DTMF, запись в моно (WAV со слышимым тоном, затем M4A), стереозапись с разделением и заглушённой средней секундой, статусы, управляемые через консоль pjsua (доступен, отошёл, занят), конференцию из двух веток с объединением и разделением и записью, переходящей между ветками, входящие звонки (ответ, отклонение с 486), перевод сразу и с консультацией между двумя телефонами, DTMF через SIP INFO и зарегистрированный аккаунт на SIPStub (UDP-регистратор с digest-вызовом внутри macrosip-cli; каждый авторизованный INVITE должен быть больше 1300 байт без ошибки смены транспорта), имитацию смены сети во время звонка и с зарегистрированным аккаунтом, качество звонка (должно быть good), режим одного звонка, «Не беспокоить», правила отклонения и звонок только с GSM. При SELFTEST_ARCH=x86_64 CLI собирается универсальным, а CLI и оба телефона запускаются через arch -x86_64; pjsua под Rosetta поднимается на несколько секунд дольше, поэтому скрипт опрашивает его консоль до ~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> |
пишет <prefix>-<window>.png каждого окна через --snapshot-after секунд (по умолчанию 6), затем завершается; работает без дисплея |
--appearance light|dark |
принудительно задаёт тему |
-AppleLanguages "(ru)" |
стандартный аргумент macOS; запускает приложение на русском |
--log-level 0-6 |
подробность PJSIP (все сборки) |
Демонстрационные состояния по областям (полный список в заголовке Model/DemoState.swift):
- Главное окно и звонки:
first-launch,dialpad,contacts,calllog,connected,calls,conference,recording,recording-long,incoming,incoming-hidden,autoAnswer,suggest,keypad,transfer-banner,account-popup,account-popup-live,many-accounts. - Звук и сеть:
quality-good,quality-fair,quality-poor,quality-poor-calls,call-volume(также пишет<prefix>-popover.png),headset,headset-back,network-offline,reconnecting. - Звонки и «Не беспокоить»:
timed-dnd(пишет пункты строки меню в<prefix>-menu.txt),call-log-playback,busy-mode. - Настройки:
settings-accounts,account-editor,account-editor-basic,account-editor-open,editor-first-launch,settings-general,settings-general-advanced,settings-audio,settings-codecs,settings-network,settings-recording,settings-autoanswer,settings-calls,settings-directory,many-accounts-settings,deny-incoming. - Быстрые клавиши:
quick-keys,quick-keys-bottom(пишет-main.pngи-settings.png). - Карточка контакта:
contact-card,contact-card-new(пишет-card.png). - Справочник (
MockDirectory):directory,directory-done,directory-incoming,directory-sheet,directory-sheet-url,directory-sheet-google,directory-sheet-macos,directory-import,directory-selftest. - Сообщения (имитация движка отвечает через 0,6 с):
messages,messages-conversation,messages-failed,messages-new,messages-empty,messages-many,messages-selftest. Каждое состояние включает сообщения в демонстрационных настройках;messages-selftestзапускает только проверки сообщений, которые входят и вselftest(выключено: отбрасывается, вкладки нет; включено: сохраняется; снова выключено: скрыто). - Служебные:
selftest,quit(проверяет, что завершение не зависает).
Демонстрационная самопроверка. --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 выполняется при push и pull request в 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, selftest-call.sh нативно и с SELFTEST_ARCH=x86_64 под Rosetta 2 (устанавливается при необходимости), make-app.sh release, lipo -info на бинарнике; выгружает приложение (14 дней) и универсальную сборку PJSIP (3 дня) как артефакты, а при сбое — журналы сборки |
intel |
macos-15-intel (x86_64), после build |
скачивает универсальные библиотеки и приложение (хост Intel не может выполнить build-pjsip.sh), проверяет подпись, запускает приложение на 15 с с --data-dir и MACROSIP_LOG_DIR, затем swift build, test.sh и selftest-call.sh нативно |
release.yml повторяет шаги arm64 перед сборкой DMG (см. Релизы).