Проверка ClickHouse
Функциональные тесты
Функциональные тесты — наиболее простые и удобные в использовании. Большинство возможностей ClickHouse можно протестировать с их помощью, и их обязательно использовать для каждого изменения в коде ClickHouse, которое может быть протестировано таким образом.
Каждый функциональный тест отправляет один или несколько запросов на запущенный сервер ClickHouse и сравнивает результат с эталонным.
Тесты расположены в каталоге ./tests/queries.
Каждый тест может быть одного из двух типов: .sql и .sh.
- Тест
.sql— это простой SQL‑скрипт, который подаётся на входclickhouse-client. - Тест
.sh— это скрипт, который запускается самостоятельно.
Тесты на SQL обычно предпочтительнее тестов .sh.
Используйте тесты .sh только в тех случаях, когда нужно проверить функциональность, которую невозможно покрыть чистым SQL, например, подачу входных данных в clickhouse-client через pipe или тестирование clickhouse-local.
Распространённая ошибка при тестировании типов данных DateTime и DateTime64 — предполагать, что сервер использует конкретный часовой пояс (например, «UTC»). Это не так: часовые пояса в CI‑запусках тестов преднамеренно выбираются случайным образом. Самое простое решение — явно указывать часовой пояс для тестовых значений, например: toDateTime64(val, 3, 'Europe/Amsterdam').
Запуск теста локально
Запустите сервер ClickHouse локально, прослушивающий порт по умолчанию (9000).
Чтобы запустить, например, тест 01428_hash_set_nan_key, перейдите в каталог репозитория и выполните следующую команду:
Результаты тестов (stderr и stdout) записываются в файлы 01428_hash_set_nan_key.[stderr|stdout], которые находятся рядом с самим тестом (для queries/0_stateless/foo.sql вывод будет в queries/0_stateless/foo.stdout).
См. tests/clickhouse-test --help для просмотра всех параметров clickhouse-test.
Вы можете запустить все тесты или только их подмножество, указав фильтр по именам тестов: ./clickhouse-test substring.
Также есть параметры для запуска тестов параллельно или в случайном порядке.
Добавление нового теста
Чтобы добавить новый тест, сначала создайте файл .sql или .sh в директории queries/0_stateless.
Затем сгенерируйте соответствующий файл .reference, используя clickhouse-client < 12345_test.sql > 12345_test.reference или ./12345_test.sh > ./12345_test.reference.
Тесты должны только создавать, удалять, выполнять SELECT и т.п. над таблицами в базе данных test, которая создаётся автоматически заранее.
Допускается использование временных таблиц.
Чтобы локально настроить такое же окружение, как в CI, установите тестовые конфигурации (они будут использовать mock-реализацию Zookeeper и скорректируют некоторые настройки).
Тесты должны:
- быть минимальными: создавать только минимально необходимые таблицы, столбцы и минимальную необходимую сложность,
- быть быстрыми: выполняться не дольше нескольких секунд (лучше — доли секунды),
- быть корректными и детерминированными: падать тогда и только тогда, когда тестируемая функциональность не работает,
- быть изолированными/без сохранения состояния: не полагаться на окружение и время выполнения,
- быть исчерпывающими: покрывать крайние случаи, такие как нули,
null, пустые множества, исключения (отрицательные тесты, для этого используйте синтаксис-- { serverError xyz }и-- { clientError xyz }), - очищать таблицы в конце теста (на случай оставшихся данных),
- удостоверяться, что другие тесты не проверяют то же самое (т.е. сначала выполните grep).
Ограничение запусков тестов
Тест может иметь ноль или более тегов, задающих ограничения, в каких контекстах тест запускается в CI.
Для тестов .sql теги размещаются в первой строке в виде комментария SQL:
Для тестов .sh теги указываются в комментарии на второй строке:
Список доступных тегов:
| Tag name | Назначение | Пример использования |
|---|---|---|
disabled | Тест не выполняется | |
long | Время выполнения теста увеличивается с 1 до 10 минут | |
deadlock | Тест запускается в цикле в течение длительного времени | |
race | То же, что и deadlock. Предпочтителен deadlock | |
shard | Требуется, чтобы сервер слушал 127.0.0.* | |
distributed | То же, что и shard. Предпочтителен shard | |
global | То же, что и shard. Предпочтителен shard | |
zookeeper | Для выполнения теста требуется Zookeeper или ClickHouse Keeper | Тест использует ReplicatedMergeTree |
replica | То же, что и zookeeper. Предпочтителен zookeeper | |
no-fasttest | Тест не выполняется в режиме Fast test | Тест использует движок таблиц MySQL, который отключён в Fast test |
fasttest-only | Тест выполняется только в режиме Fast test | |
no-[asan, tsan, msan, ubsan] | Отключает тесты в сборке с санитайзерами | Тест выполняется под QEMU, который не работает с санитайзерами |
no-replicated-database | ||
no-ordinary-database | ||
no-parallel | Отключает параллельный запуск других тестов вместе с этим | Тест читает из таблиц system, и инварианты могут быть нарушены |
no-parallel-replicas | ||
no-debug | ||
no-stress | ||
no-polymorphic-parts | ||
no-random-settings | ||
no-random-merge-tree-settings | ||
no-backward-compatibility-check | ||
no-cpu-x86_64 | ||
no-cpu-aarch64 | ||
no-cpu-ppc64le | ||
no-s3-storage |
В дополнение к приведённым выше настройкам вы можете использовать флаги USE_* из system.build_options для указания использования отдельных возможностей ClickHouse.
Например, если ваш тест использует таблицу MySQL, вам следует добавить тег use-mysql.
Указание ограничений для случайных настроек
Тест может задавать минимальные и максимальные допустимые значения для настроек, которые могут случайным образом изменяться во время выполнения теста.
Для .sh‑тестов ограничения записываются в виде комментария в строке рядом с тегами или во второй строке, если теги не указаны:
Для .sql-тестов теги размещаются как SQL-комментарий в строке рядом с тестом или в первой строке:
Если вам нужно указать только одно ограничение, вы можете использовать None для второго.
Выбор имени теста
Имя теста начинается с пятизначного префикса, за которым следует описательное имя, например 00422_hash_function_constexpr.sql.
Чтобы выбрать префикс, найдите наибольший префикс, уже присутствующий в каталоге, и увеличьте его на единицу.
Параллельно могут быть добавлены другие тесты с тем же числовым префиксом, но это допустимо и не приводит к каким-либо проблемам; впоследствии ничего менять не нужно.
Проверка ошибки, которая должна произойти
Иногда требуется протестировать, что для некорректного запроса возникает ошибка сервера. В SQL-тестах для этого поддерживаются специальные аннотации следующего вида:
Этот тест проверяет, что сервер возвращает ошибку с кодом 49 о неизвестном столбце x.
Если ошибки нет или код ошибки другой, тест завершится неуспешно.
Если вы хотите убедиться, что ошибка возникает на стороне клиента, вместо этого используйте аннотацию clientError.
Не проверяйте конкретную формулировку сообщения об ошибке — она может измениться в будущем, и тест будет без необходимости ломаться. Проверяйте только код ошибки. Если существующий код ошибки недостаточно точен для ваших нужд, рассмотрите возможность добавления нового.
Тестирование распределённого запроса
Если вы хотите использовать распределённые запросы в функциональных тестах, вы можете воспользоваться табличной функцией remote с адресами 127.0.0.{1..2}, чтобы сервер выполнял запрос к самому себе; либо вы можете использовать предопределённые тестовые кластеры в конфигурационном файле сервера, такие как test_shard_localhost.
Не забудьте добавить слова shard или distributed в имя теста, чтобы он запускался в CI в корректных конфигурациях, где сервер настроен на поддержку распределённых запросов.
Работа с временными файлами
Иногда в shell-тесте может потребоваться на лету создать файл для работы.
Имейте в виду, что некоторые проверки в CI запускают тесты параллельно, поэтому если вы создаёте или удаляете временный файл в своём скрипте без уникального имени, это может привести к сбоям некоторых проверок CI, таких как Flaky.
Чтобы избежать этого, следует использовать переменную окружения $CLICKHOUSE_TEST_UNIQUE_NAME, чтобы давать временным файлам имя, уникальное для выполняющегося теста.
Таким образом вы можете быть уверены, что файл, который вы создаёте при подготовке или удаляете при очистке, используется только этим тестом, а не каким-либо другим тестом, выполняющимся параллельно.
Известные ошибки
Если нам известны ошибки, которые можно легко воспроизвести функциональными тестами, мы помещаем соответствующие функциональные тесты в директорию tests/queries/bugs.
После исправления ошибок эти тесты переносятся в tests/queries/0_stateless.
Интеграционные тесты
Интеграционные тесты позволяют проверять работу ClickHouse в кластерной конфигурации и его взаимодействие с другими серверами, такими как MySQL, Postgres, MongoDB. Они полезны для имитации сетевых разрывов, потерь пакетов и т. д. Эти тесты запускаются в Docker и создают несколько контейнеров с различным программным обеспечением.
См. tests/integration/README.md для получения информации о запуске этих тестов.
Обратите внимание, что интеграция ClickHouse со сторонними драйверами не тестируется. Также в настоящее время у нас нет интеграционных тестов для наших JDBC- и ODBC-драйверов.
Модульные тесты
Модульные тесты полезны, когда вы хотите протестировать не ClickHouse целиком, а отдельную изолированную библиотеку или класс.
Сборку тестов можно включить или отключить с помощью опции CMake ENABLE_TESTS.
Модульные тесты (и другие тестовые программы) расположены в подкаталогах tests в различных частях исходного кода.
Чтобы запустить модульные тесты, выполните ninja test.
Некоторые тесты используют gtest, но некоторые представляют собой просто программы, которые возвращают ненулевой код возврата при сбое теста.
Наличие модульных тестов не является обязательным, если код уже покрыт функциональными тестами (а функциональные тесты, как правило, гораздо проще в использовании).
Вы можете запускать отдельные проверки gtest, вызывая соответствующий исполняемый файл напрямую, например:
Тесты производительности
Тесты производительности позволяют измерять и сравнивать производительность отдельных компонентов ClickHouse на синтетических запросах.
Тесты производительности находятся в tests/performance/.
Каждый тест представлен файлом .xml с описанием тестового сценария.
Тесты запускаются с помощью утилиты docker/test/performance-comparison. См. файл readme для примеров запуска.
Каждый тест выполняет один или несколько запросов (возможно, с различными сочетаниями параметров) в цикле.
Если вы хотите улучшить производительность ClickHouse в каком-либо сценарии и эти улучшения можно наблюдать на простых запросах, настоятельно рекомендуется написать тест производительности.
Также рекомендуется писать тесты производительности при добавлении или изменении SQL-функций, которые являются относительно изолированными и не слишком специфичными.
Всегда имеет смысл использовать perf top или другие инструменты perf во время тестов.
Тестовые инструменты и скрипты
Некоторые программы в каталоге tests не являются готовыми тестами, а представляют собой вспомогательные тестовые инструменты.
Например, для Lexer есть инструмент src/Parsers/tests/lexer, который просто выполняет токенизацию stdin и выводит результат с подсветкой в stdout.
Вы можете использовать такие инструменты как примеры кода, а также для изучения и ручного тестирования.
Прочие тесты
Есть тесты для моделей машинного обучения в tests/external_models.
Эти тесты не поддерживаются и должны быть перенесены в интеграционные тесты.
Есть отдельный тест для кворумных вставок.
Он поднимает кластер ClickHouse на отдельных серверах и эмулирует различные варианты отказов: разделение сети, потерю пакетов (между узлами ClickHouse, между ClickHouse и ZooKeeper, между сервером ClickHouse и клиентом и т.д.), kill -9, kill -STOP и kill -CONT, аналогично Jepsen. Затем тест проверяет, что все подтверждённые вставки были записаны, а все отклонённые — нет.
Кворумный тест был написан отдельной командой до того, как ClickHouse стал открытым исходным кодом. Эта команда больше не работает с ClickHouse. Тест был случайно написан на Java. По этим причинам кворумный тест должен быть переписан и перенесён в интеграционные тесты.
Ручное тестирование
Когда вы разрабатываете новую функциональность, имеет смысл также протестировать её вручную. Вы можете сделать это, выполнив следующие шаги:
Соберите ClickHouse. Запустите ClickHouse из терминала: перейдите в каталог programs/clickhouse-server и выполните ./clickhouse-server. По умолчанию он будет использовать конфигурацию (config.xml, users.xml и файлы в каталогах config.d и users.d) из текущего каталога. Чтобы подключиться к серверу ClickHouse, выполните programs/clickhouse-client/clickhouse-client.
Обратите внимание, что все утилиты ClickHouse (сервер, клиент и т. д.) представляют собой символьные ссылки на единый исполняемый файл с именем clickhouse.
Вы можете найти этот исполняемый файл в programs/clickhouse.
Все утилиты также можно запускать как clickhouse tool вместо clickhouse-tool.
В качестве альтернативы вы можете установить пакет ClickHouse: либо стабильный релиз из репозитория ClickHouse, либо собрать пакет самостоятельно с помощью ./release в корневом каталоге исходников ClickHouse.
Затем запустите сервер командой sudo clickhouse start (или stop, чтобы остановить сервер).
Журналы работы сервера находятся в /etc/clickhouse-server/clickhouse-server.log.
Когда ClickHouse уже установлен в вашей системе, вы можете собрать новый исполняемый файл clickhouse и заменить существующий файл:
Также вы можете остановить системную службу clickhouse-server и запустить экземпляр с той же конфигурацией, но с выводом логов в терминал:
Пример с gdb:
Если системный clickhouse-server уже запущен и вы не хотите его останавливать, вы можете изменить номера портов в своём config.xml (или переопределить их в файле в директории config.d), указать соответствующий путь к данным и запустить его.
Исполняемый файл clickhouse практически не имеет зависимостей и работает на широком спектре дистрибутивов Linux.
Для быстрого чернового тестирования своих изменений на сервере вы можете просто передать на него свежесобранный исполняемый файл clickhouse с помощью scp, а затем запустить его, как показано в примерах выше.
Тесты сборки
Тесты сборки позволяют проверить, что сборка не ломается на различных альтернативных конфигурациях и на некоторых сторонних системах. Эти тесты также автоматизированы.
Примеры:
- кросс-компиляция для Darwin x86_64 (macOS)
- кросс-компиляция для FreeBSD x86_64
- кросс-компиляция для Linux AArch64
- сборка на Ubuntu с библиотеками из системных пакетов (не рекомендуется)
- сборка с динамическим (shared) связыванием библиотек (не рекомендуется)
Например, сборка с использованием системных пакетов — плохая практика, потому что мы не можем гарантировать, какая именно версия пакетов будет установлена в системе. Но это действительно необходимо мейнтейнерам Debian. По этой причине мы как минимум должны поддерживать этот вариант сборки. Другой пример: динамическое связывание — распространённый источник проблем, но оно необходимо для некоторых энтузиастов.
Хотя мы не можем запускать все тесты на всех вариантах сборки, мы хотим по крайней мере убедиться, что различные варианты сборки не сломаны. Для этого мы используем тесты сборки.
Мы также проверяем, что нет единиц трансляции, которые слишком долго компилируются или требуют слишком много оперативной памяти.
Мы также проверяем, что нет слишком больших кадров стека.
Тестирование совместимости протокола
Когда мы расширяем сетевой протокол ClickHouse, мы вручную проверяем, что старый clickhouse-client работает с новым clickhouse-server и новый clickhouse-client работает со старым clickhouse-server (просто запуская исполняемые файлы из соответствующих пакетов).
Мы также автоматически проверяем ряд сценариев с помощью интеграционных тестов:
- могут ли данные, записанные старой версией ClickHouse, быть успешно прочитаны новой версией;
- работают ли распределённые запросы в кластере с разными версиями ClickHouse.
Помощь от компилятора
Основная часть кода ClickHouse (расположенная в каталоге src) собирается с флагами -Wall -Wextra -Werror и с некоторыми дополнительными включёнными предупреждениями.
Однако эти опции не включены для сторонних библиотек.
В Clang есть ещё больше полезных предупреждений — вы можете просмотреть их с помощью -Weverything и выбрать некоторые из них для использования по умолчанию при сборке.
Мы всегда используем clang для сборки ClickHouse как при разработке, так и в продакшене.
Вы можете собирать ClickHouse на своей машине в отладочном режиме (чтобы экономить заряд батареи ноутбука), но обратите внимание, что компилятор способен генерировать больше предупреждений при сборке с оптимизацией -O3 благодаря более качественному анализу потока управления и межпроцедурному анализу.
При сборке с clang в отладочном режиме используется отладочная версия libc++, которая позволяет отлавливать больше ошибок во время выполнения.
Санитайзеры
Если процесс (сервер или клиент ClickHouse) падает при локальном запуске, возможно, вам нужно отключить рандомизацию расположения адресного пространства: sudo sysctl kernel.randomize_va_space=0
Address sanitizer
Мы запускаем функциональные, интеграционные, стрессовые и модульные тесты под ASan для каждого коммита.
Thread sanitizer
Мы запускаем функциональные, интеграционные, стрессовые и модульные тесты под TSan для каждого коммита.
Memory sanitizer
Мы запускаем функциональные, интеграционные, стрессовые и модульные тесты под MSan для каждого коммита.
Undefined behaviour sanitizer
Мы запускаем функциональные, интеграционные, стрессовые и модульные тесты под UBSan для каждого коммита. Код некоторых сторонних библиотек не проверяется на неопределённое поведение (UB).
Valgrind (memcheck)
Раньше мы запускали функциональные тесты под Valgrind каждую ночь, но сейчас больше этого не делаем.
Это занимает несколько часов.
В настоящее время есть одно известное ложноположительное срабатывание в библиотеке re2, см. эту статью.
Фаззинг
Фаззинг ClickHouse реализован как с использованием libFuzzer, так и с помощью случайных SQL‑запросов. Все фазз‑тесты должны выполняться с санитайзерами (Address и Undefined).
LibFuzzer используется для изолированного фазз‑тестирования библиотечного кода.
Фаззеры реализованы как часть тестового кода и имеют суффикс имени "_fuzzer".
Пример фаззера можно найти в src/Parsers/fuzzers/lexer_fuzzer.cpp.
Специальные для LibFuzzer конфигурации, словари и корпус хранятся в tests/fuzz.
Мы рекомендуем писать фазз‑тесты для каждой функции, обрабатывающей пользовательский ввод.
Фаззеры по умолчанию не собираются.
Для сборки фаззеров должны быть установлены оба параметра: -DENABLE_FUZZING=1 и -DENABLE_TESTS=1.
Мы рекомендуем отключить Jemalloc при сборке фаззеров.
Конфигурацию, используемую для интеграции фаззинга ClickHouse с
Google OSS-Fuzz, можно найти в docker/fuzz.
Мы также используем простой фазз‑тест для генерации случайных SQL‑запросов и проверки, что сервер не падает при их выполнении.
Вы можете найти его в 00746_sql_fuzzy.pl.
Этот тест следует запускать непрерывно (на ночь и дольше).
Мы также используем продвинутый AST‑ориентированный фаззер запросов, который способен находить огромное количество граничных случаев. Он выполняет случайные перестановки и подстановки в AST запросов. Он запоминает узлы AST из предыдущих тестов, чтобы использовать их для фаззинга последующих тестов, обрабатывая их в случайном порядке. Вы можете узнать больше об этом фаззере в этой статье блога.
Стресс-тест
Стресс-тесты — это ещё один вид фаззинга. Он выполняет все функциональные тесты параллельно в случайном порядке на одном сервере. Результаты тестов не проверяются.
Проверяется, что:
- сервер не падает, не срабатывают отладочные ловушки или ловушки санитайзеров;
- нет взаимоблокировок;
- структура базы данных остаётся консистентной;
- сервер может успешно остановиться после теста и снова запуститься без исключений.
Существует пять вариантов (Debug, ASan, TSan, MSan, UBSan).
Thread fuzzer
Thread Fuzzer (пожалуйста, не путайте с Thread Sanitizer) — это ещё один вид фаззинга, который позволяет случайным образом менять порядок выполнения потоков. Он помогает обнаружить ещё больше краевых случаев.
Аудит безопасности
Наша команда по безопасности провела первичный обзор возможностей ClickHouse в области обеспечения безопасности.
Статические анализаторы
Мы запускаем clang-tidy при каждом коммите.
Проверки clang-static-analyzer также включены.
clang-tidy также используется для части проверок стиля.
Мы протестировали clang-tidy, Coverity, cppcheck, PVS-Studio, tscancode, CodeQL.
Инструкции по использованию вы найдёте в каталоге tests/instructions/.
Если вы используете CLion в качестве IDE, вы можете сразу воспользоваться частью проверок clang-tidy из коробки.
Мы также используем shellcheck для статического анализа shell-скриптов.
Повышение защищённости
В отладочной сборке используется собственный аллокатор, выполняющий ASLR пользовательских выделений памяти.
Мы также вручную защищаем области памяти, которые после выделения должны быть только для чтения.
В отладочной сборке мы дополнительно используем модифицированную libc, которая гарантирует, что не вызываются «вредные» (устаревшие, небезопасные, не потокобезопасные) функции.
Отладочные проверки (assertions) используются повсеместно.
В отладочной сборке, если выбрасывается исключение с кодом «logical error» (что подразумевает наличие ошибки в коде), программа принудительно завершается. Это позволяет использовать исключения в релизной сборке, но трактовать их как assertion в отладочной сборке.
Отладочная версия jemalloc используется для отладочных сборок. Отладочная версия libc++ используется для отладочных сборок.
Проверки целостности во время работы
Данные, хранящиеся на диске, защищены с помощью контрольных сумм. Данные в таблицах MergeTree одновременно* проверяются тремя способами (для сжатых блоков данных, для несжатых блоков данных и общей контрольной суммой по всем блокам). Данные, передаваемые по сети между клиентом и сервером или между серверами, также защищены контрольными суммами. Репликация обеспечивает побитовое совпадение данных на репликах.
Это необходимо для защиты от неисправного оборудования (битовой коррозии (bit rot) на носителях, переворотов битов в RAM на сервере, переворотов битов в RAM сетевого контроллера, переворотов битов в RAM сетевого коммутатора, переворотов битов в RAM клиента, переворотов битов в линии связи). Обратите внимание, что перевороты битов — обычное явление и с высокой вероятностью будут происходить даже при использовании ECC RAM и при наличии контрольных сумм TCP (если вы эксплуатируете тысячи серверов, обрабатывающих петабайты данных каждый день). См. видео (на русском).
ClickHouse предоставляет средства диагностики, которые помогут инженерам по эксплуатации найти неисправное оборудование.
* и это не замедляет работу.
Стиль кода
Правила стиля кода описаны здесь.
Чтобы проверить некоторые распространённые нарушения стиля, вы можете использовать скрипт utils/check-style.
Чтобы принудительно привести ваш код к правильному стилю, вы можете использовать clang-format.
Файл .clang-format находится в корне исходников.
Он в основном соответствует нашему фактическому стилю кода.
Но не рекомендуется применять clang-format к уже существующим файлам, так как это может ухудшить форматирование.
Вы можете использовать инструмент clang-format-diff, который можно найти в репозитории исходного кода clang.
В качестве альтернативы вы можете попробовать инструмент uncrustify для переформатирования кода.
Конфигурация находится в uncrustify.cfg в корне исходников.
Он менее тщательно протестирован, чем clang-format.
В CLion есть собственный форматтер кода, который необходимо настроить под наш стиль кода.
Мы также используем codespell для поиска опечаток в коде.
Эта проверка также автоматизирована.
Покрытие тестами
Мы также отслеживаем покрытие тестами, но только для функциональных тестов clickhouse-server. Измерение покрытия выполняется ежедневно.
Тесты для тестов
Выполняется автоматическая проверка на нестабильные (flaky) тесты. Она запускает все новые тесты 100 раз (для функциональных тестов) или 10 раз (для интеграционных тестов). Если хотя бы один раз тест завершился с ошибкой, он считается нестабильным.
Автоматизация тестирования
Мы запускаем тесты с помощью GitHub Actions.
Задания сборки и тесты выполняются в среде Sandbox на каждый коммит. Полученные пакеты и результаты тестов публикуются в GitHub и их можно скачать по прямым ссылкам. Артефакты хранятся в течение нескольких месяцев. Когда вы отправляете pull request на GitHub, мы помечаем его как «can be tested», и наша CI‑система собирает для вас пакеты ClickHouse (release, debug, с address sanitizer и т. д.).