Модульные тесты (PHPUnit)¶
Модульный пакет - это PHPUnit 10.5 поверх PHP 8.1, минимального уровня PHP, который поддерживает панель. Все, что для этого нужно, находится в репозитории:
| Файл | Роль |
|---|---|
tests/phpunit.phar |
Закрепленный двоичный файл PHPUnit (зафиксирован; CI запускает этот файл) |
tests/phpunit.xml.dist |
Конфигурация: набор XC_VM Unit, memory_limit=512M |
tests/bootstrap.php |
Загружает src/vendor/autoload.php и помощники tests/Support/, определяет MAIN_HOME, PHP_BIN и пути, используемые тестами |
tests/Support/TestDb.php |
База данных двойная, на MariaDB/MySQL |
tests/Support/InstallSchema.php |
Рабочий DDL: таблицы схемы установки и миграции |
tests/Unit/ |
Тесты, по одному <Thing>Test.php на единицу измерения |
Для запуска тестов достаточно зафиксированного значения src/vendor/; make dev-tools - только для PHPStan и phpcs. Не заменяйте PHAR на PHPUnit 11: для этого нужен PHP 8.2.
1. Запустите тесты¶
Вам нужен PHP 8.1 с pdo_mysql и сервер MariaDB/MySQL (раздел 2). На компьютере без сервера:
make test-db # MariaDB in Docker, once per boot
php tests/phpunit.phar -c tests/phpunit.xml.dist # the whole suite
php tests/phpunit.phar -c tests/phpunit.xml.dist --filter SomeTest # one class or method
php tests/phpunit.phar -c tests/phpunit.xml.dist --display-skipped # with each skip's reason
php tests/phpunit.phar -c tests/phpunit.xml.dist --debug --no-progress # name each test as it runs
Выполните php явно, а не ./tests/phpunit.phar: PHAR не содержит интерпретатора и выберет любое значение php, которое будет первым в PATH. Для покрытия требуется pcov или Xdebug: php tests/phpunit.phar -c tests/phpunit.xml.dist --coverage-text.
Что пропускает машина разработчика и как это сделать:
- Redis. About 140 tests start a throwaway
redis-serverof their own and need it inPATH(with theredisandigbinaryPHP extensions):apt install redis-server. - Корень. Некоторые из них проверяют, что делает процесс от имени root (права root, программа обновления, права агента на доступ к файлам), и запускаются только от имени root, как это делается в тестовом контейнере.
- Расширение и реальные компоненты. Раздел 4.
TimeUtilsTest предполагается, что часовой пояс PHP по умолчанию - UTC, как на панельном хосте, так и в CI. Без date.timezone PHP использует системный пояс, поэтому на компьютере, настроенном на другую зону, запустите пакет с TZ=UTC (или -d date.timezone=UTC).
CI (.github/workflows/ci.yml, job PHPUnit) выполняет ту же команду в PHP 8.1 с помощью сервиса mariadb:11.4.
2. База данных¶
Тесты выполняются на MariaDB/MySQL, запускается производство движка; нет SQLite или резервной копии в памяти. TestDb предоставляет каждому экземпляру свою собственную пустую схему (xcvm_t<pid>_<n>), удаляемую вместе с ней, и запускает сеанс в режиме sql_mode, который настраивает установщик (NO_ENGINE_SUBSTITUTION), так что тест видит приведения и усечения, которые видит живая панель.
Соединение происходит из трех переменных окружения:
| Переменная | По умолчанию | Пример (CI) |
|---|---|---|
XCVM_TEST_DB_DSN |
mysql:unix_socket=/run/mysqld/mysqld.sock или mysql:host=127.0.0.1;port=3306 без этого сокета |
mysql:host=127.0.0.1;port=3306 |
XCVM_TEST_DB_USER |
root |
root |
XCVM_TEST_DB_PASS |
(нет) | xcvm-test |
Значения по умолчанию работают как есть на панельном хосте (root через сокет) и при make test-db, который запускает mariadb:11.4, запускается версия CI, при 127.0.0.1:3306 с данными в tmpfs и root без пароля. make test-db-stop удаляет его.
Любой другой сервер работает до тех пор, пока пользователь может создавать и удалять схемы xcvm\_t%; это все, что касается запуска. При первом TestDb запуске удаляются схемы, оставшиеся после аварийного запуска.
3. Написание тестов¶
Тестовый класс имеет значение <Thing>Test в tests/Unit/<Thing>Test.php и расширяет PHPUnit\Framework\TestCase. Назовите его в честь тестируемого класса, укажите допустимые входные данные, недопустимые входные данные, крайние случаи и побочные эффекты и запишите все, что выводит код, чтобы результат выполнения оставался чистым.
База данных¶
Добавьте TestDb к тестируемому коду с помощью DatabaseFactory::set() или setDb() seam класса и создайте в нем таблицы, необходимые для тестирования. Предпочитайте рабочий DDL таблицам, написанным от руки, чтобы тест соответствовал всем столбцам, ключам и типам, которые есть в реальной установке.:
use XcVm\Infrastructure\Database\DatabaseFactory;
use XcVm\Tests\Support\InstallSchema;
protected function setUp(): void {
$this->rDb = new TestDb();
$this->rDb->exec(InstallSchema::table('cluster_reservations')); // from src/bin/install/database.sql
$this->rDb->exec(InstallSchema::migration('029_create_cluster_nodes'));
DatabaseFactory::set($this->rDb);
}
protected function tearDown(): void {
DatabaseFactory::reset();
}
- Написанный от руки DDL - это MySQL DDL (
INTEGER PRIMARY KEY AUTO_INCREMENT, зарезервированные слова в кавычках). Не сокращайте рабочий DDL: его ключи, длины и типы соответствуют требованиям теста. get_rows()возвращает строки с примененным экранированиемDatabase, как это делает производственный процесс чтения. Двоичные столбцы (подписи, ключи) должны считываться с помощьюget_raw_rows(), как это делаетCommandBus: при экранировании байты перезаписываются.information_schema,DATABASE(),JSON_CONTAINS()а остальные принадлежат серверу; не подделывайте их.
Пути¶
Доступ к коду осуществляется через MAIN_HOME (MAIN_HOME . 'migrations/database/up/'), а не через dirname(__DIR__, 2) . '/src/'. В репозитории MAIN_HOME - это src/; на панельном хосте это корень развертывания, который сам содержит tests/, и один и тот же тест работает в обоих случаях.
Дочерние процессы¶
- Запускайте PHP как
PHP_BINARY, а не какphp: у узла панели нетphpвPATH. proc_open()с помощью массива$envзаменяет родительское окружение: добавьтеTestDb::env(), когда дочерний элемент откроет свое собственное соединение, иTestDb::connect($rSchema)достигнет родительской схемы.- Дочерний элемент, который подделывает
\XC_VMвauto_prepend_file, не может начать с реального загруженногоxcvm_core(класс будет существовать дважды). Создайте свою команду изxcvm_test_child_php()(tests/bootstrap.php), которая передаст ему загруженныйphp.iniминусxcvm_core.
Redis и другие сервисы¶
Используйте логику, а не симулируйте \XC_VM: RedisManager::useConnector(static fn() => null) как "Redis не работает" (замените ее на useConnector(null)). Тест, для которого требуется реальное значение redis-server, начинается с PATH и пропускается без него.
Группа skip-on-panel¶
Помечайте тест #[Group('skip-on-panel')], если он не может быть запущен в корневом каталоге развертывания (раздел 5).:
- он считывает файлы репозитория, которые не отправляются из корневого каталога развертывания (
Makefile,lb_configs/,tools/), или сканирует дерево исходных текстов; - он выполняется в отдельном процессе (
#[RunInSeparateProcess],#[RunTestsInSeparateProcesses]).
4. Вступительные тесты¶
Некоторым тестам требуется реальный компонент или конкретная сборка, и они пропускаются без него. Они запускаются в наборе по умолчанию всякий раз, когда установлен их переключатель:
| Переключатель | Бежит | Ценность |
|---|---|---|
XCVM_BENCH=1 |
ClusterCryptoBenchTest: синхронизация криптографии с целями производственного оборудования |
— |
XCVM_TEST_NGINX |
ClusterNginxConfigTest::testRealNginx |
двоичный файл nginx, например /home/xc_vm/bin/nginx/sbin/nginx |
XCVM_TEST_FPM |
ClusterPoolTest::testRealPhpFpm* |
двоичный файл php-fpm, например /home/xc_vm/bin/php/sbin/php-fpm |
XCVM_AGENT_BIN |
LbProvisionClusterTest::testWithTheRealAgent |
двоичный файл xc_agent |
XCVM_TEST_NETNS=1 |
DbAllowlistTest::testInANetworkNamespace (реальные iptables) |
корень, unshare и iptables |
XCVM_CONFIG_DIR |
ClusterExtensionIntegrationTest |
пустая директория во временном каталоге; требуется загрузить тестовые перехватчики xcvm_core |
XCVM_EXT_SO |
ClusterDrTest::testWithTheRealExtension |
путь к тестовым перехватчикам xcvm_core.so |
XCVM_CLUSTER_API_REAL=1 |
ClusterApiTest вместо реального расширения вместо его поддельного (с XCVM_CONFIG_DIR) |
— |
Тестовые перехватчики xcvm_core встроены в репозиторий расширения (XC_VM_CoreExtention) с помощью make build-dev, в зависимости от PHP, в который оно будет загружаться: его php-config и заголовки определяют ABI. Он принимает поддельную лицензию, поэтому никогда не поставляется.
В нескольких тестах описывается узел без xcvm_core или со старым узлом (LicenseGateTest::testFanoutAllowedFailsOpenWithoutExtension, CorePinTest::testAnOldExtensionRefusesCleanly, ...): они пропускают то место, где загружено расширение, а тесты расширения пропускают то, где его нет. Ни один интерпретатор не выполняет оба варианта; в разделе 6 показаны проходы, которые это делают.
5. Запуск на узле панели управления¶
На панельном хостинге пакет работает с производственным стеком: встроенным PHP, собственным php.ini (загрузчик ionCube, OPcache, xcvm_core) и MariaDB для панели. Запустите его из копии корневого каталога развертывания, то есть из src/ с tests/ внутри него, но никогда из самого /home/xc_vm: MAIN_HOME - это будет оперативная установка, и некоторые тесты используют файлы времени выполнения, которые они там находят (например, хранилище одноразовых данных кластерной шины). Сохраняйте копию доступной для чтения каждому пользователю (/opt, а не /root): часть тестируемого кода передается пользователю агента и все равно должна загружать его классы.
mkdir -p /opt/xcvm_flat
rsync -a /path/to/XC_VM/src/ /opt/xcvm_flat/
rsync -a /path/to/XC_VM/tests /opt/xcvm_flat/
cd /opt/xcvm_flat
/home/xc_vm/bin/php/bin/php tests/phpunit.phar -c tests/phpunit.xml.dist --exclude-group skip-on-panel
Группа skip-on-panel содержит то, что не может быть запущено в ней:
- Проверка хранилища. Корень развертывания не содержит
Makefile,lb_configs/илиtools/и смешивает дерево исходных текстов сtests/,backups/иtmp/. - Тесты, изолированные от процесса. PHPUnit выполняет изолированный тест для своего дочернего PHP на stdin, и связанный с PHP segfaults скрипт, считанный из stdin (также на
php -l): ionCube Loader передает дескриптор файла в OPcache (opcache.enable_cli=1), который завершает работу с ошибкой. Это не влияет на именованный файл сценария, который используется в любой производственной точке входа.
Если установщик установил пароль root, предоставьте тестам их собственного пользователя, а не root:
CREATE USER 'xcvm_test'@'localhost' IDENTIFIED BY 'xcvm-test';
GRANT ALL PRIVILEGES ON `xcvm\_t%`.* TO 'xcvm_test'@'localhost';
и запустите с XCVM_TEST_DB_USER=xcvm_test XCVM_TEST_DB_PASS=xcvm-test.
6. Каждый тест, ни один не пропущен¶
Полный запуск занимает несколько проходов, потому что для некоторых тестов требуется расширение, а для других - его отсутствие. На панельном хосте или в контейнере xcvm-test-install, с копией deploy-root в /opt/xcvm_flat, проверкой git в /opt/xcvm_repo и тестовыми перехватчиками xcvm_core.so, созданными для его PHP:
| Проходить | Дерево | php.ini |
Переключатели | Выбор |
|---|---|---|---|---|
| Производственный штабель | развернуть root | производство | XCVM_TEST_NGINX, XCVM_TEST_FPM, XCVM_AGENT_BIN, XCVM_TEST_NETNS=1 |
--exclude-group skip-on-panel |
| Без расширения | развернуть root | производство за вычетом линии xcvm_core |
— | в тестах пропущено "этот PHP загружает xcvm_core..." |
| Реальное расширение | развернуть root | изготовление с использованием тестовых крючков .so вместо xcvm_core.so |
XCVM_CONFIG_DIR=$(mktemp -d) |
--filter ClusterExtensionIntegrationTest |
| Аварийное восстановление | развернуть root | производство за вычетом линии xcvm_core |
XCVM_EXT_SO=<test-hooks .so> |
--filter ClusterDrTest |
| Контрольные показатели | развернуть root | производство | XCVM_BENCH=1 |
--filter ClusterCryptoBenchTest |
| Хранилище и изоляция | проверка git | производство плюс opcache.enable_cli=0 |
— | --group skip-on-panel |
Укажите PHP на вариант с PHPRC=<dir holding php.ini>; дочерние процессы наследуют его, в то время как опция -d не будет доступна для них. Тесты сравниваются с производственным оборудованием, поэтому более медленная машина завершает их без регрессии. Для проверки хранилища требуются make и git в контейнере.
xcvm-test-install содержит сценарий, который выполняет все шесть проходов: