测试
功能测试
功能测试是最简单且最方便使用的。大多数 ClickHouse 功能都可以通过功能测试进行测试,并且对于 ClickHouse 代码中可以以此方式测试的每个更改,功能测试都是强制性的。
每个功能测试都会向正在运行的 ClickHouse 服务器发送一个或多个查询,并将结果与参考结果进行比较。
测试位于 queries
目录中。其中有两个子目录:stateless
和 stateful
。
- 无状态测试运行查询时没有任何预加载的测试数据 - 它们通常在测试本身中动态创建小型合成数据集。
- 有状态测试需要从 ClickHouse 预加载测试数据,这些数据可供公众使用。
每个测试可以是两种类型之一:.sql
和 .sh
。
.sql
测试是简单的 SQL 脚本,通过管道传递给clickhouse-client
。.sh
测试是由自身运行的脚本。
SQL 测试通常优于 .sh
测试。仅当您必须测试某些无法从纯 SQL 执行的功能时,才应使用 .sh
测试,例如将一些输入数据通过管道传递到 clickhouse-client
或测试 clickhouse-local
。
测试数据类型 DateTime
和 DateTime64
时,一个常见的错误是假设服务器使用特定的时区(例如“UTC”)。情况并非如此,CI 测试运行中的时区是故意随机化的。最简单的解决方法是显式指定测试值的时区,例如 toDateTime64(val, 3, 'Europe/Amsterdam')
。
在本地运行测试
在本地启动 ClickHouse 服务器,监听默认端口 (9000)。要运行例如测试 01428_hash_set_nan_key
,请更改到存储库文件夹并运行以下命令
PATH=<path to clickhouse-client>:$PATH tests/clickhouse-test 01428_hash_set_nan_key
测试结果(stderr
和 stdout
)被写入文件 01428_hash_set_nan_key.[stderr|stdout]
,这些文件位于测试本身旁边(对于 queries/0_stateless/foo.sql
,输出将在 queries/0_stateless/foo.stdout
中)。
有关 clickhouse-test
的所有选项,请参阅 tests/clickhouse-test --help
。您可以通过为测试名称提供过滤器来运行所有测试或运行测试子集:./clickhouse-test substring
。还有一些选项可以并行或随机顺序运行测试。
添加新测试
要添加新测试,首先在 queries/0_stateless
目录中创建一个 .sql
或 .sh
文件。然后使用 clickhouse-client < 12345_test.sql > 12345_test.reference
或 ./12345_test.sh > ./12345_test.reference
生成相应的 .reference
文件。
测试应仅在预先自动创建的数据库 test
中创建、删除、从中选择等表。可以使用临时表。
要在本地设置与 CI 中相同的环境,请安装测试配置(它们将使用 Zookeeper 模拟实现并调整一些设置)
cd <repository>/tests/config
sudo ./install.sh
测试应满足以下条件:
- 最小化:仅创建最少需要的表、列和复杂度,
- 快速:不超过几秒钟(最好:亚秒级),
- 正确且确定性:仅当被测功能不起作用时才失败,
- 隔离/无状态:不依赖于环境和时间
- 详尽:涵盖边缘情况,如零值、空值、空集、异常(负面测试,为此使用语法
-- { serverError xyz }
和-- { clientError xyz }
), - 在测试结束时清理表(以防残留),
- 确保其他测试不测试相同的内容(即首先使用 grep)。
限制测试运行
一个测试可以有零个或多个标签,用于指定在 CI 中运行测试的上下文限制。
对于 .sql
测试,标签放在第一行作为 SQL 注释
-- Tags: no-fasttest, no-replicated-database
-- no-fasttest: <provide_a_reason_for_the_tag_here>
-- no-replicated-database: <provide_a_reason_here>
SELECT 1
对于 .sh
测试,标签写在第二行作为注释
#!/usr/bin/env bash
# Tags: no-fasttest, no-replicated-database
# - no-fasttest: <provide_a_reason_for_the_tag_here>
# - no-replicated-database: <provide_a_reason_here>
可用标签列表
标签名称 | 作用 | 用法示例 |
---|---|---|
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] | 在使用 sanitizers 构建时禁用测试 | 测试在 QEMU 下运行,QEMU 不适用于 sanitizers |
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 |
除了上述设置外,您还可以使用 system.build_options
中的 USE_*
标志来定义特定 ClickHouse 功能的使用。例如,如果您的测试使用 MySQL 表,则应添加标签 use-mysql
。
为随机设置指定限制
测试可以为在测试运行期间可以随机化的设置指定最小和最大允许值。
对于 .sh
测试,限制写在标签旁边的行或未指定标签时的第二行作为注释
#!/usr/bin/env bash
# Tags: no-fasttest
# Random settings limits: max_block_size=(1000, 10000); index_granularity=(100, None)
对于 .sql
测试,标签放在标签旁边的行或第一行作为 SQL 注释
-- Tags: no-fasttest
-- Random settings limits: max_block_size=(1000, 10000); index_granularity=(100, None)
SELECT 1
如果您只需要指定一个限制,则可以对另一个限制使用 None
。
选择测试名称
测试的名称以五位数字前缀开头,后跟描述性名称,例如 00422_hash_function_constexpr.sql
。要选择前缀,请找到目录中已存在的最大前缀,并将其递增一。
ls tests/queries/0_stateless/[0-9]*.reference | tail -n 1
同时,可能会添加一些具有相同数字前缀的其他测试,但这没关系,不会导致任何问题,您不必稍后更改它。
检查必须发生的错误
有时您想测试服务器是否对不正确的查询返回错误。我们支持 SQL 测试中对此的特殊注释,形式如下
select x; -- { serverError 49 }
此测试确保服务器返回关于未知列 x
的代码为 49 的错误。如果没有错误,或者错误不同,则测试将失败。如果您想确保在客户端发生错误,请改用 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,而是一个单独的隔离库或类时,单元测试非常有用。您可以使用 ENABLE_TESTS
CMake 选项启用或禁用测试的构建。单元测试(和其他测试程序)位于代码中的 tests
子目录中。要运行单元测试,请键入 ninja test
。一些测试使用 gtest
,但有些只是在测试失败时返回非零退出代码的程序。
如果代码已经被功能测试覆盖(并且功能测试通常更易于使用),则没有必要进行单元测试。
您可以通过直接调用可执行文件来运行单个 gtest 检查,例如
$ ./src/unit_tests_dbms --gtest_filter=LocalAddress*
性能测试
性能测试允许测量和比较 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 存储库的稳定版本,或者您可以使用 ClickHouse 源代码根目录中的 ./release
为自己构建软件包。然后使用 sudo clickhouse start
启动服务器(或使用 stop 停止服务器)。在 /etc/clickhouse-server/clickhouse-server.log
中查找日志。
当您的系统上已经安装了 ClickHouse 时,您可以构建一个新的 clickhouse
二进制文件并替换现有的二进制文件
$ sudo clickhouse stop
$ sudo cp ./clickhouse /usr/bin/
$ sudo clickhouse start
您还可以停止系统 clickhouse-server 并使用相同的配置但记录到终端来运行您自己的服务器
$ sudo clickhouse stop
$ sudo -u clickhouse /usr/bin/clickhouse server --config-file /etc/clickhouse-server/config.xml
使用 gdb 的示例
$ sudo -u clickhouse gdb --args /usr/bin/clickhouse server --config-file /etc/clickhouse-server/config.xml
如果系统 clickhouse-server 已经在运行并且您不想停止它,您可以在 config.xml
中更改端口号(或在 config.d
目录中的文件中覆盖它们),提供适当的数据路径,然后运行它。
clickhouse
二进制文件几乎没有依赖项,并且可以在各种 Linux 发行版上运行。要在服务器上快速而简陋地测试您的更改,您只需将您新构建的 clickhouse
二进制文件 scp
到您的服务器,然后按照上面的示例运行它。
构建测试
构建测试允许检查在各种替代配置和某些外部系统上构建是否未损坏。这些测试也是自动化的。
示例
- 为 Darwin x86_64 (macOS) 交叉编译
- 为 FreeBSD x86_64 交叉编译
- 为 Linux AArch64 交叉编译
- 使用来自系统软件包的库在 Ubuntu 上构建(不鼓励)
- 使用库的共享链接构建(不鼓励)
例如,使用系统软件包是不良做法,因为我们无法保证系统将具有软件包的确切版本。但这对于 Debian 维护者来说确实是需要的。因此,我们至少必须支持这种构建变体。另一个示例:共享链接是常见问题的来源,但对于某些爱好者来说是需要的。
尽管我们无法在所有构建变体上运行所有测试,但我们至少要检查各种构建变体是否未损坏。为此,我们使用构建测试。
我们还测试是否没有编译时间过长或需要太多 RAM 的翻译单元。
我们还测试是否没有太大的堆栈帧。
协议兼容性测试
当我们扩展 ClickHouse 网络协议时,我们会手动测试旧的 clickhouse-client 是否与新的 clickhouse-server 配合使用,以及新的 clickhouse-client 是否与旧的 clickhouse-server 配合使用(只需运行来自相应软件包的二进制文件)。
我们还使用集成测试自动测试一些案例
- 如果旧版本的 ClickHouse 写入的数据可以被新版本成功读取;
- 分布式查询是否在具有不同 ClickHouse 版本的集群中工作。
来自编译器的帮助
主 ClickHouse 代码(位于 dbms
目录中)是使用 -Wall -Wextra -Werror
以及一些其他启用的警告构建的。尽管这些选项未对第三方库启用。
Clang 具有更多有用的警告 - 您可以使用 -Weverything
查找它们并选择一些用于默认构建。
对于生产构建,使用 clang,但我们也测试 make gcc 构建。对于开发,clang 通常更方便使用。您可以在自己的机器上以调试模式构建(以节省笔记本电脑的电池),但请注意,由于更好的控制流和过程间分析,编译器能够使用 -O3
生成更多警告。在调试模式下使用 clang 构建时,使用 libc++
的调试版本,该版本允许在运行时捕获更多错误。
Sanitizers
如果在本地运行时进程(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
库中有一个已知的误报,请参阅这篇文章。
Fuzzing
ClickHouse fuzzing 是使用 libFuzzer 和随机 SQL 查询实现的。所有模糊测试都应使用 sanitizers(Address 和 Undefined)执行。
LibFuzzer 用于库代码的隔离模糊测试。Fuzzer 作为测试代码的一部分实现,并带有 “_fuzzer” 名称后缀。Fuzzer 示例可以在 src/Parsers/fuzzers/lexer_fuzzer.cpp
中找到。LibFuzzer 特定的配置、字典和语料库存储在 tests/fuzz
中。我们鼓励您为每个处理用户输入的功能编写模糊测试。
Fuzzer 默认不构建。要构建 fuzzer,需要设置 -DENABLE_FUZZING=1
和 -DENABLE_TESTS=1
选项。我们建议在构建 fuzzer 时禁用 Jemalloc。用于将 ClickHouse 模糊测试集成到 Google OSS-Fuzz 的配置可以在 docker/fuzz
中找到。
我们还使用简单的模糊测试来生成随机 SQL 查询,并检查服务器在执行这些查询时不会崩溃。您可以在 00746_sql_fuzzy.pl
中找到它。此测试应持续运行(过夜甚至更长时间)。
我们还使用复杂的基于 AST 的查询 fuzzer,它可以发现大量的边界情况。它在查询 AST 中进行随机排列和替换。它会记住之前测试的 AST 节点,以便在随机顺序处理后续测试时使用它们进行模糊测试。您可以在这篇博客文章中了解更多关于此 fuzzer 的信息。
压力测试
压力测试是模糊测试的另一种情况。它在单个服务器上以随机顺序并行运行所有功能测试。测试结果不进行检查。
检查内容包括:
- 服务器不会崩溃,不会触发调试或 Sanitizer 陷阱;
- 没有死锁;
- 数据库结构是一致的;
- 服务器可以在测试后成功停止,并再次启动而不会出现异常。
有五个变体(Debug, ASan, TSan, MSan, UBSan)。
线程 Fuzzer
线程 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 的自定义,以确保不调用“有害的”(过时的、不安全的、非线程安全的)函数。
广泛使用调试断言。
在调试构建中,如果抛出带有“逻辑错误”代码(意味着错误)的异常,程序将提前终止。这允许在发布构建中使用异常,但在调试构建中将其作为断言。
调试版本的 jemalloc 用于调试构建。调试版本的 libc++ 用于调试构建。
运行时完整性检查
磁盘上存储的数据已进行校验和。MergeTree 表中的数据以三种方式同时进行校验和*(压缩数据块、未压缩数据块、跨块的总校验和)。客户端和服务器之间或服务器之间通过网络传输的数据也进行了校验和。复制确保副本上的数据位相同。
需要防止硬件故障(存储介质上的位衰减、服务器 RAM 中的位翻转、网络控制器 RAM 中的位翻转、网络交换机 RAM 中的位翻转、客户端 RAM 中的位翻转、线路上的位翻转)。请注意,即使对于 ECC RAM 和存在 TCP 校验和的情况下,位翻转也很常见并且可能发生(如果您设法运行数千台服务器,每天处理 PB 级数据)。观看视频(俄语)。
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 中,可以通过直接链接下载。Artifacts 存储数月。当您在 GitHub 上发送拉取请求时,我们会将其标记为“can be tested”,我们的 CI 系统将为您构建 ClickHouse 软件包(release、debug、with address sanitizer 等)。