169 lines
15 KiB
Markdown
169 lines
15 KiB
Markdown
# Автотесты
|
||||
|
|
|
|||
|
|
Тесты лежат в `tests/` и собираются только по опции `BILAUNCHER_BUILD_TESTS`. В обычную сборку и в
|
|||
|
|
релизы они не попадают, код лаунчера ради них не менялся.
|
|||
|
|
|
|||
|
|
## Как запустить
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cmake -S . -B build-tests -G Ninja -DCMAKE_BUILD_TYPE=Debug -DBILAUNCHER_BUILD_TESTS=ON -DCMAKE_PREFIX_PATH=$HOME/Qt/6.11.1/macos
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cmake --build build-tests
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
ctest --test-dir build-tests --output-on-failure -j 6
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Отдельный слой — по метке: `ctest --test-dir build-tests -L unit` (ещё `net`, `backend`, `qml`).
|
|||
|
|
Отдельный тест — напрямую, с фильтром по функции:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
build-tests/tests/net/tst_versioninstaller installsModernVersionCompletely
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Статические проверки (переводы, qmllint, раздел CHANGELOG для текущей версии):
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
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` | переключение сборок туда и обратно, продолжение после обрыва, пак |
|
|||
|
|
| 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` строит пути от `QDir::homePath()`. Общий `main` тестов
|
|||
|
|
(`tests/common/testenv.h`) до создания приложения подменяет `HOME` на временную папку, и каждый
|
|||
|
|
процесс работает в своей. Если путь лаунчера всё же оказался вне песочницы, процесс сразу
|
|||
|
|
завершается. На Windows `QDir::homePath()` переменные окружения не читает, поэтому там тесты идут
|
|||
|
|
в настоящей `%APPDATA%\BiLauncher` учётной записи раннера, строго по одному, а за собой папку
|
|||
|
|
стирают. Если папка уже есть и создана не тестами (нет метки `.bilauncher-test-sandbox`), тесты
|
|||
|
|
отказываются запускаться.
|
|||
|
|
|
|||
|
|
**Сеть.** Адреса серверов в сервисах зашиты константами, а свои `QNetworkAccessManager` они
|
|||
|
|
создают сами. Зато прокси и SSL-настройки у Qt общие на процесс, и код лаунчера их не трогает.
|
|||
|
|
`FakeInternet` (`tests/common/fakeinternet.h`) ставит прокси на локальный порт и отключает
|
|||
|
|
проверку сертификата. Каждое https-соединение приходит к нему как `CONNECT host:443`, переводится
|
|||
|
|
в TLS с тестовым сертификатом из `tests/fixtures/tls/` и обслуживается по таблице маршрутов.
|
|||
|
|
Запрос на адрес без маршрута получает 404 и попадает в `unmatched()`. При старте сервер проверяет
|
|||
|
|
себя сам. Если TLS-бэкенд Qt не умеет серверный режим, сетевые тесты пропускаются с понятным
|
|||
|
|
сообщением, а в CI на Linux (`BILAUNCHER_REQUIRE_FAKE_INTERNET=1`) это ошибка.
|
|||
|
|
|
|||
|
|
`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_modloaderversionservice::neoForgeLegacyArtifactArrivesFirst` | если ответ старого артефакта `net.neoforged:forge` приходит раньше основного, основной затирает слитые записи (`state->merged = parsed`), и версии NeoForge для 1.20.1 пропадают |
|
|||
|
|
| `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`. Если часть исправили, опустите число.
|
|||
|
|
|
|||
|
|
## Как добавить тест
|
|||
|
|
|
|||
|
|
1. Создайте `tests/<слой>/tst_<имя>.cpp` с классом на QtTest и в конце файла напишите
|
|||
|
|
`BILAUNCHER_TEST_MAIN(Класс)` (или `BILAUNCHER_GUI_TEST_MAIN`, если нужен `QGuiApplication`).
|
|||
|
|
Затем `#include "tst_<имя>.moc"`.
|
|||
|
|
2. Добавьте имя в список в `tests/<слой>/CMakeLists.txt`.
|
|||
|
|
3. В `init()` вызывайте `TestEnv::resetLauncherRoot()`, папку игры берите из
|
|||
|
|
`TestEnv::freshDir("game")`.
|
|||
|
|
4. Для сети поднимите `FakeInternet` в `initTestCase()` через `BILAUNCHER_START_FAKE_INTERNET(m_net)`
|
|||
|
|
и опишите маршруты: `m_net.route("https://host/path", FakeResponse::json(...))`. Для префикса
|
|||
|
|
поставьте `*` в конце, для ошибок, задержек и обрывов есть `FakeResponse::error`, `delayMs`,
|
|||
|
|
`hang`, `drop`, `truncateTo`.
|
|||
|
|
5. Крупные ответы серверов кладите в `tests/fixtures/`. Если в json нужны имя ОС, архитектура или
|
|||
|
|
классификатор нативов, используйте подстановки `%OS%`, `%OTHER%`, `%ARCH%`, `%NATIVE%` и
|
|||
|
|
`Fixtures::readTemplate()`.
|
|||
|
|
|
|||
|
|
У moc есть ограничение: сырые строки `R"(...)"` и длинные вложенные инициализаторы `{{...}}`,
|
|||
|
|
разбитые на строки, внутри файла с `Q_OBJECT` сбивают его разбор («Not a signal or slot
|
|||
|
|
declaration» или пустой `.moc`). Такие данные выносите в фикстуры или собирайте по шагам.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
При создании этого документа использовался ИИ.
|