Перейти к основному содержимому
Перейти к основному содержимому

Тестирование ClickHouse

Функциональные тесты

Функциональные тесты — самые простые и удобные в использовании. Большинство функций ClickHouse можно протестировать с помощью функциональных тестов, и их обязательно следует использовать для каждого изменения в коде ClickHouse, которое можно протестировать таким образом.

Каждый функциональный тест отправляет один или несколько запросов к работающему серверу ClickHouse и сравнивает результат с эталонным.

Тесты расположены в директории queries. В ней есть две поддиректории: stateless и stateful.

  • Stateless тесты выполняют запросы без предзагруженных тестовых данных — они часто создают небольшие синтетические наборы данных на лету в рамках теста.
  • Stateful тесты требуют предзагруженных тестовых данных из ClickHouse, которые доступны широкой публике.

Каждый тест может быть одного из двух типов: .sql и .sh.

  • Тест .sql — это простой SQL-скрипт, который передается clickhouse-client.
  • Тест .sh — это скрипт, который выполняется самостоятельно.

SQL тесты предпочтительнее тестов .sh. Вы должны использовать тесты .sh только когда необходимо протестировать какую-то функцию, которую нельзя протестировать только с помощью SQL, например, передав некоторые входные данные в clickhouse-client или протестировав 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.

Тесты должны только создавать, удалять, выбирать из и т. д. таблицы в базе данных test, которая автоматически создается заранее. Использование временных таблиц допустимо.

Чтобы настроить ту же среду, что и в CI локально, установите конфигурации тестирования (они будут использовать имитацию Zookeeper и настроят некоторые параметры)

примечание

Тесты должны:

  • быть минимальными: только создавать минимально необходимые таблицы, столбцы и сложности,
  • быть быстрыми: не занимать больше нескольких секунд (лучше: доли секунды),
  • быть корректными и детерминированными: давать сбой только тогда, когда протестируемая функция не работает,
  • быть изолированными/stateless: не полагаться на окружение и время,
  • быть исчерпывающими: охватывать крайние случаи, такие как нули, null, пустые множества, исключения (негативные тесты, используйте синтаксис -- { serverError xyz } и -- { clientError xyz } для этого),
  • очищать таблицы в конце теста (в случае остаточных данных),
  • гарантировать, что другие тесты не проверяют то же самое (т. е. сначала используйте grep).

Ограничение запуска тестов

Тест может иметь ноль или более тегов, указывающих ограничения контекстов, в которых тест выполняется в CI.

Для тестов .sql теги помещаются в первую строку как SQL комментарий:

Для тестов .sh теги записываются в комментарий на второй строке:

Список доступных тегов:

Имя тегаЧто он делаетПример использования
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Тест не выполняется в быстром тестеТест использует движок таблицы MySQL, который отключен в быстром тесте
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 теги размещаются в строке, рядом с тегами или в первой строке:

Если вам нужно указать только одно ограничение, вы можете использовать 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 в кластерной конфигурации и взаимодействие 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 и запустить свой собственный с той же конфигурацией, но с логированием в терминал:

Пример с gdb:

Если системный сервер clickhouse уже работает и вы не хотите его останавливать, вы можете изменить номера портов в вашем config.xml (или переопределить их в файле в директории config.d), указать соответствующий путь данных и запустить его.

Исполняемый файл clickhouse почти не имеет зависимостей и работает на широком диапазоне дистрибутивов Linux. Чтобы быстро и в упрощенном виде протестировать ваши изменения на сервере, вы можете просто scp ваш свежесобранный исполняемый файл clickhouse на ваш сервер и затем запустить его, как в примерах выше.

Тесты сборки

Тесты сборки позволяют проверить, что сборка не нарушена при различных альтернативных конфигурациях и на некоторых чуждых системах. Эти тесты также автоматизированы.

Примеры:

  • кросс-компиляция для Darwin x86_64 (macOS)
  • кросс-компиляция для FreeBSD x86_64
  • кросс-компиляция для Linux AArch64
  • сборка на Ubuntu с библиотеками из системных пакетов (не рекомендуется)
  • сборка с использованием общего связывания библиотек (не рекомендуется)

Например, сборка с системными пакетами является плохой практикой, поскольку мы не можем гарантировать, какую точную версию пакетов будет иметь система. Но это действительно необходимо для мейнтейнеров Debian. По этой причине мы, по крайней мере, должны поддерживать этот вариант сборки. Другой пример: общее связывание является распространенным источником проблем, но оно необходимо для некоторых энтузиастов.

Хотя мы не можем запустить все тесты на всех вариантах сборок, мы хотим убедиться, что различные варианты сборки не ломаются. Для этой цели мы используем тесты сборки.

Мы также тестируем, чтобы не было единиц перевода, которые слишком длинны для компиляции или требуют слишком много оперативной памяти.

Мы также проверяем, чтобы не было слишком больших стековых фреймов.

Тестирование совместимости протокола

Когда мы расширяем сетевой протокол ClickHouse, мы вручную тестируем, что старый clickhouse-client работает с новым clickhouse-server и новый clickhouse-client работает со старым clickhouse-server (просто запуская двоичные файлы из соответствующих пакетов).

Мы также автоматически тестируем некоторые случаи с интеграционными тестами:

  • если данные, записанные старой версией ClickHouse, могут быть успешно прочитаны новой версией;
  • работают ли распределенные запросы в кластере с различными версиями ClickHouse.

Помощь от компилятора

Основной код ClickHouse (который расположен в директории src) собирается с помощью -Wall -Wextra -Werror и с некоторыми дополнительными включенными предупреждениями. Хотя эти опции не включены для сторонних библиотек.

Clang имеет даже больше полезных предупреждений — вы можете их искать с помощью -Weverything и выбрать что-то для сборки по умолчанию.

Мы всегда используем clang для сборки ClickHouse, как для разработки, так и для производства. Вы можете скомпилировать на своем компьютере в режиме отладки (чтобы сэкономить заряд батареи вашего ноутбука), но обратите внимание, что компилятор способен генерировать больше предупреждений с помощью -O3 благодаря лучшему управлению потоком и анализу интер-процедур. При сборке с помощью clang в режиме отладки используется версия libc++, которая позволяет обнаруживать больше ошибок во время выполнения.

Санитайзеры

примечание

Если процесс (сервер ClickHouse или клиент) выдает сбой при запуске локально, вам может потребоваться отключить рандомизацию расположения адресного пространства: sudo sysctl kernel.randomize_va_space=0

Санаайзер адресов

Мы запускаем функциональные, интеграционные, стрессовые и модульные тесты под ASan с учетом каждого коммита.

Санаайзер потоков

Мы запускаем функциональные, интеграционные, стрессовые и модульные тесты под TSan с учетом каждого коммита.

Санаайзер памяти

Мы запускаем функциональные, интеграционные, стрессовые и модульные тесты под MSan с учетом каждого коммита.

Санаайзер неопределенного поведения

Мы запускаем функциональные, интеграционные, стрессовые и модульные тесты под UBSan с учетом каждого коммита. Код некоторых сторонних библиотек не очищен от неопределенного поведения.

Valgrind (Memcheck)

Ранее мы запускали функциональные тесты под Valgrind в течение ночи, но делаем это больше не не делаем. Это занимает несколько часов. В настоящее время имеется один известный ложный положительный результат в библиотеке re2, смотрите эту статью.

Фаззинг

Фаззинг ClickHouse реализован как с использованием libFuzzer, так и случайных SQL запросов. Все тестирование фаззинга должно выполняться с санитайзерами (адресными и неопределенного поведения).

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. Этот тест следует запускать постоянно (в течение ночи и дольше).

Мы также используем сложный фаззер запросов на основе АСТ, который может найти множество крайних случаев. Он выполняет случайные перестановки и подстановки в АСТ запросов. Он запоминает узлы АСТ из предыдущих тестов, чтобы использовать их для фаззинга последующих тестов, обрабатывая их в случайном порядке. Вы можете узнать больше об этом фаззере в этой статье в блоге.

Стресс-тест

Стресс-тесты являются еще одним случаем фаззинга. Он запускает все функциональные тесты параллельно в случайном порядке на одном сервере. Результаты тестов не проверяются.

Проверяется, что:

  • сервер не выдает сбой, не срабатывают отладочные или санитайзирующие ловушки;
  • нет взаимоблокировок;
  • структура базы данных согласованная;
  • сервер может успешно остановиться после теста и запуститься снова без исключений.

Существует пять вариантов (Debug, ASan, TSan, MSan, UBSan).

Фаззер потоков

Фаззер потоков (пожалуйста, не путайте с санитайзером потоков) — это другой вид фаззинга, который позволяет рандомизировать порядок выполнения потоков. Это помогает найти еще больше специальных случаев.

Аудит безопасности

Наша команда безопасности провела некоторый базовый обзор возможностей 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, которая гарантирует, что ни одна "вредная" (устаревшая, незащищенная, не потокобезопасная) функция не будет вызвана.

Отладочные утверждения используются широко.

В сборках отладки, если происходит исключение с кодом "логическая ошибка" (что подразумевает ошибку), программа завершается преждевременно. Это позволяет использовать исключения в сборках релиза, но делает это утверждением в сборках отладки.

Используется отладочная версия jemalloc для сборок отладки. Используется отладочная версия libc++ для сборок отладки.

Проверки целостности времени выполнения

Данные, хранящиеся на диске, имеют контрольные суммы. Данные в таблицах MergeTree имеют контрольные суммы тремя способами одновременно* (сжатые блоки данных, нескомпрессированные блоки данных, общая контрольная сумма по блокам). Данные, передаваемые по сети между клиентом и сервером или между серверами, также имеют контрольные суммы. Репликация обеспечивает битовую идентичность данных на Replica.

Это необходимо для защиты от неисправного оборудования (битовое разрушение на носителе, битовые сбои в 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. Это происходит на ежедневной основе.

Тесты для тестов

Существует автоматическая проверка на флагные тесты. Она запускает все новые тесты 100 раз (для функциональных тестов) или 10 раз (для интеграционных тестов). Если хотя бы один раз тест завершился неудачей, он считается флагным.

Автоматизация тестов

Мы запускаем тесты с помощью GitHub Actions.

Работы по сборке и тесты запускаются в Sandbox с учетом каждого коммита. Результирующие пакеты и результаты тестов публикуются на GitHub и могут быть загружены по прямым ссылкам. Артефакты хранятся в течение нескольких месяцев. Когда вы отправляете запрос на слияние на GitHub, мы помечаем его как "можно тестировать", и наша CI система создаст пакеты ClickHouse (релизный, отладочный, с санитайзером адресов и т. д.) для вас.