Signed-off-by: galeon <dertiellis@gmail.com>
16 KiB
Автотесты
Тесты лежат в tests/ и собираются только по опции BILAUNCHER_BUILD_TESTS. В обычную сборку и в
релизы они не попадают, код лаунчера ради них не менялся.
Как запустить
cmake -S . -B build-tests -G Ninja -DCMAKE_BUILD_TYPE=Debug -DBILAUNCHER_BUILD_TESTS=ON -DCMAKE_PREFIX_PATH=$HOME/Qt/6.11.1/macos
cmake --build build-tests
ctest --test-dir build-tests --output-on-failure -j 6
Отдельный слой — по метке: ctest --test-dir build-tests -L unit (ещё net, backend, qml).
Отдельный тест — напрямую, с фильтром по функции:
build-tests/tests/net/tst_versioninstaller installsModernVersionCompletely
Статические проверки (переводы, qmllint, раздел CHANGELOG для текущей версии):
python3 tests/scripts/static_checks.py build-tests
В Qt Creator проект с тестами открывается как обычно: включите BILAUNCHER_BUILD_TESTS в настройках
CMake, тесты появятся в панели «Тесты».
Что покрыто
| Метка | Тест | Что проверяет |
|---|---|---|
| unit | tst_launcherpaths |
раскладка папки лаунчера, ensureRootExists и её отказ, если на месте папки файл |
| unit | tst_javaruntime |
ключи видов Java, JavaRuntimeStore, какая Java нужна какой версии игры |
| unit | tst_modloader |
ключи и названия модлоадеров |
| unit | tst_localization |
переключение языков, запасной язык, отсутствующий ключ, язык из settings.json |
| unit | tst_minecraftversion |
правила Mojang, загрузка json версии, inheritsFrom, нативы, удаление версий |
| unit | tst_authoffline |
офлайн-UUID как у сервера в offline-mode |
| unit | tst_msaredirect |
разбор адреса возврата после входа Microsoft |
| unit | tst_gamelauncher |
командная строка игры, нативы, запуск, падение и остановка процесса |
| unit | tst_javalocator |
версия java по -version, выбор подходящей, поиск в папке лаунчера |
| unit | tst_zlibreference |
LD_PRELOAD с эталонным zlib на Linux |
| unit | tst_buildarchiveworker |
архив, очистка, восстановление, раскатка пака, zip-slip, отмена |
| unit | tst_buildswitcher |
переключение сборок туда и обратно, продолжение после обрыва, пак |
| unit | tst_gamedirimport |
выбор версии по launcher_profiles.json и versions/, модлоадер по библиотекам и id, папка не меняется |
| net | tst_fakeinternet |
самопроверка перехвата HTTPS (см. ниже) |
| net | tst_versionmanifestservice |
каталог Mojang: разбор, кеш, офлайн, общие запросы |
| net | tst_seasonalbuildservice |
каталог сезонных сборок: проверки полей, формат, кеш по адресу |
| net | tst_modloaderversionservice |
Fabric, Quilt, Forge (с рекомендованными), NeoForge обе схемы |
| net | tst_javaruntimeservice |
Adoptium и Mojang, частичные отказы, кеш, запасной x64 на ARM |
| net | tst_authservice |
Ely.by: пароль, 2FA, продление токена, authlib-injector |
| net | tst_msaauthservice |
вся цепочка Microsoft → Xbox → XSTS → Minecraft, коды ошибок XSTS |
| net | tst_versioninstaller |
установка версии целиком, повторы, контрольные суммы, отмена, очередь |
| net | tst_seasonalpackdownloader |
загрузка пака: sha256, обрыв, отмена |
| net | tst_javainstaller |
Temurin zip и tar.gz, дерево Mojang, zip-slip, отмена |
| net | tst_modloaderinstaller |
Fabric по сети, Forge через подставную java, откат после сбоя |
| backend | tst_launcherbackend |
всё, что зовёт QML: профили, сборки, настройки, миграция, битые файлы, каталог версий, запуск офлайн / Ely.by с 2FA / Microsoft, Java, сезонная сборка от каталога до раскатки обновления |
| qml | tst_qml |
Qt Quick Test: DarkCombo, LabelledField, ProgressPanel, LoaderRow, диалоги с настоящим LauncherBackend |
| qml | tst_qml_smoke |
Main.qml поднимается, как в main.cpp, без единого предупреждения QML, в том числе после смены языка |
Ночью и перед релизом ещё собирается Release, ставится через cmake --install и запускается на
10 секунд (tests/scripts/smoke_installed.sh / .ps1). Это ловит то, чего не видят тесты:
недоложенные деплоем модули Qt и плагины, ссылки на библиотеки вне дерева, неподписанный бандл.
Как тесты изолированы
Файлы. LauncherPaths строит пути от домашней папки. Общий main тестов
(tests/common/testenv.h) до создания приложения направляет BILAUNCHER_HOME (а заодно HOME
и USERPROFILE) на временную папку, и каждый процесс работает в своей, на всех ОС, параллельно.
BILAUNCHER_HOME нужна ради Windows: там QDir::homePath() берёт профиль у системы и HOME
с USERPROFILE не читает. Дочерние процессы наследуют переменную. Если папка лаунчера или папка
игры по умолчанию всё же оказалась вне песочницы, процесс сразу завершается с кодом 2 и ничего не
трогает. Дымовая проверка установленной сборки на Windows (smoke_installed.ps1) тоже запускает
лаунчер с BILAUNCHER_HOME на временную папку.
Сеть. Адреса серверов в сервисах зашиты константами, а свои QNetworkAccessManager они
создают сами. Зато прокси и SSL-настройки у Qt общие на процесс, и код лаунчера их не трогает.
FakeInternet (tests/common/fakeinternet.h) ставит прокси на локальный порт и отключает
проверку сертификата. Каждое https-соединение приходит к нему как CONNECT host:443, переводится
в TLS с тестовым сертификатом из tests/fixtures/tls/ и обслуживается по таблице маршрутов.
Запрос на адрес без маршрута получает 404 и попадает в unmatched(). При старте сервер проверяет
себя сам. Если TLS-бэкенд Qt не умеет серверный режим, сетевые тесты пропускаются с понятным
сообщением, а в CI на всех ОС (BILAUNCHER_REQUIRE_FAKE_INTERNET=1) это ошибка.
QML-тесты также завершаются ошибкой, если стенд не поднялся.
FakeMojang (tests/common/fakemojang.h) публикует через FakeInternet крошечную, но устроенную
как настоящая версию игры: json, client.jar, библиотеки, натив под текущую ОС, индекс ресурсов,
сами ресурсы, конфиг логирования.
Java. fake_java (tests/fake_java/main.cpp, без Qt) копируется в нужную папку как
bin/java (java.exe/javaw.exe). На -version она отвечает версией из fake-java.cfg рядом с
собой, а при обычном запуске пишет аргументы в файл, печатает заданные строки, создаёт файлы
(так изображается установщик Forge) и выходит с нужным кодом либо падает.
Код лаунчера. tests/CMakeLists.txt собирает все .cpp из корня, кроме main.cpp, в
статическую библиотеку-QML-модуль bilauncher_testlib. Новый файл попадёт в тесты сам. QML-файлы
берутся тоже все, ресурсы — из строк RESOURCES корневого CMakeLists.txt. Тестовая библиотека
собирается без Qt WebEngine, так что backend.microsoftAvailable в тестах всегда false.
Найденные проблемы (QEXPECT_FAIL)
Тест, который описывает правильное поведение, но пока падает, помечен QEXPECT_FAIL. В отчёте он
выглядит как XFAIL и прогон не валит. Когда ошибку исправят, тест станет XPASS и прогон упадёт:
тогда пометку нужно снять.
| Тест | Проблема |
|---|---|
tst_versioninstaller::retryOfLastFileIsNotLost |
если временный сбой (503, таймаут) или неверная контрольная сумма пришлись на последний файл, retryOrFail() откладывает повтор на 400 мс, а pump() сразу видит пустую очередь и завершает установку ошибкой «файлы отсутствуют» |
tst_buildarchiveworker::dotFilesSurviveRoundTrip |
QZipReader срезает ведущую точку в именах записей, и после переключения сборок .fabric/, .mixin.out/ возвращаются как fabric/, mixin.out/ |
tst_javainstaller::cancelDuringDownload |
JavaInstaller::cancel() шлёт canceled дважды: abort() синхронно доводит ответ до обработчика, который уже шлёт сигнал |
tst_gamelauncher::extractNatives |
битый jar с нативами не считается ошибкой: QZipReader::isReadable() проверяет только, что файл открылся |
Ещё одна особенность, которую тесты обходят: синглтон Localization запоминает JS-движок, который
его создал. Второй QQmlApplicationEngine после уничтожения первого оставляет этот указатель
висячим, поэтому дымовой тест держит один движок на весь прогон, как и приложение.
CI: .gitea/workflows/tests.yml
Тесты идут на тех же host-раннерах, что собирают релизы. Отдельные машины и Docker не нужны:
Qt, CMake и Ninja там уже есть, пути берутся из QT_ROOT и QT_TOOLS в config.yaml раннера.
| Событие | Что запускается |
|---|---|
| push в любую ветку, pull request | Linux: сборка с тестами, статические проверки, все тесты |
| ночью (02:00 UTC) и вручную (Run workflow) | Linux, Windows и macOS, плюс дымовая проверка установленного дерева на всех трёх |
Windows и macOS на каждый коммит не запускаются: вместимость у раннеров 1, и часовая сборка с
WebEngine задерживала бы релизы. Перед выпуском версии запустите workflow вручную и дождитесь
зелёного прогона. release.yml от тестов не зависит.
Новый push в ту же ветку отменяет незаконченный прогон (concurrency). Отчёт JUnit
(junit.xml), лог qmllint и полный лог ctest прикладываются к прогону как артефакты.
На Linux-раннере полезен ccache: с ним повторная сборка занимает минуты. ci/setup-runner-linux.sh
ставит его вместе с xvfb, который нужен дымовой проверке, если деплой Qt не положил плагин
offscreen.
qmllint. В коде уже есть 53 предупреждения, а код ради тестов не правится. Поэтому проверка
работает как храповик: она падает, только если предупреждений стало больше числа в
tests/qmllint-baseline.txt. Если часть исправили, опустите число.
Как добавить тест
- Создайте
tests/<слой>/tst_<имя>.cppс классом на QtTest и в конце файла напишитеBILAUNCHER_TEST_MAIN(Класс)(илиBILAUNCHER_GUI_TEST_MAIN, если нуженQGuiApplication). Затем#include "tst_<имя>.moc". - Добавьте имя в список в
tests/<слой>/CMakeLists.txt. - В
init()вызывайтеTestEnv::resetLauncherRoot(), папку игры берите изTestEnv::freshDir("game"). - Для сети поднимите
FakeInternetвinitTestCase()черезBILAUNCHER_START_FAKE_INTERNET(m_net)и опишите маршруты:m_net.route("https://host/path", FakeResponse::json(...)). Для префикса поставьте*в конце, для ошибок, задержек и обрывов естьFakeResponse::error,delayMs,hang,drop,truncateTo. Для заданного порядка ответов установитеdeferred = trueи вызовитеreleaseResponse(url)после обработки другого ответа клиентом. - Крупные ответы серверов кладите в
tests/fixtures/. Если в json нужны имя ОС, архитектура или классификатор нативов, используйте подстановки%OS%,%OTHER%,%ARCH%,%NATIVE%иFixtures::readTemplate().
У moc есть ограничение: сырые строки R"(...)" и длинные вложенные инициализаторы {{...}},
разбитые на строки, внутри файла с Q_OBJECT сбивают его разбор («Not a signal or slot
declaration» или пустой .moc). Такие данные выносите в фикстуры или собирайте по шагам.
При создании этого документа использовался ИИ.