ClickHouse 测试
功能测试
功能测试是最简单方便的测试方法。大多数 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
中)。
有关更多选项,请参阅 tests/clickhouse-test --help
。您可以简单地运行所有测试,也可以运行按测试名称中的子字符串过滤的测试子集:./clickhouse-test substring
。还有一些选项可以并行运行测试或以随机顺序运行测试。
添加新测试
要添加新的测试,请在 queries/0_stateless
目录中创建一个 .sql
或 .sh
文件,手动检查它,然后以以下方式生成 .reference
文件:clickhouse-client < 00000_test.sql > 00000_test.reference
或 ./00000_test.sh > ./00000_test.reference
。
测试应该仅使用 (创建、删除等) 预先创建的 test
数据库中的表;测试还可以使用临时表。
限制测试运行
测试可以有零个或多个测试标签,指定测试运行的限制。
对于 .sh
测试,标签作为第二行的注释写入
#!/usr/bin/env bash
# Tags: no-fasttest
对于 .sql
测试,标签作为 SQL 注释放置在第一行
-- Tags: no-fasttest
SELECT 1
标签名称 | 作用 | 使用示例 |
---|---|---|
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
。要选择前缀,请查找目录中已存在的最大前缀,并将它加 1。同时,可能会添加一些其他测试,它们具有相同数字前缀,但这是可以的,不会导致任何问题,您以后不必更改它。
检查必须发生的错误
有时您想测试服务器错误是否会针对不正确的查询发生。我们支持在 SQL 测试中以以下形式使用特殊注释来实现这一点
select x; -- { serverError 49 }
此测试确保服务器返回代码为 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 的某个特定库或类而不是整个 ClickHouse 时很有用。你可以使用 ENABLE_TESTS
CMake 选项来启用或禁用测试的构建。单元测试(和其他测试程序)位于代码中的 tests
子目录中。要运行单元测试,请键入 ninja test
。一些测试使用 gtest
,但有些只是在测试失败时返回非零退出代码的程序。
如果代码已经被功能测试覆盖(并且功能测试通常更易于使用),那么没有必要进行单元测试。
你可以通过直接调用可执行文件来运行单个 gtest 检查,例如
$ ./src/unit_tests_dbms --gtest_filter=LocalAddress*
性能测试
性能测试允许衡量和比较 ClickHouse 在合成查询上的某些隔离部分的性能。性能测试位于 tests/performance/
。每个测试都由一个 .xml
文件表示,其中包含测试用例的描述。测试使用 docker/test/performance-comparison
工具运行。请参阅自述文件以了解调用方法。
每个测试在一个循环中运行一个或多个查询(可能包含参数组合)。
如果你想提高 ClickHouse 在某些场景下的性能,并且可以在简单查询上观察到改进,强烈建议你编写性能测试。此外,建议你在添加或修改相对独立且不那么模糊的 SQL 函数时编写性能测试。在你的测试中使用 perf top
或其他 perf
工具总是有意义的。
测试工具和脚本
tests
目录中的一些程序不是准备好的测试,而是测试工具。例如,对于 Lexer
,有一个工具 src/Parsers/tests/lexer
,它只是对标准输入进行标记化,并将彩色结果写入标准输出。你可以使用这类工具作为代码示例,并进行探索和手动测试。
其他测试
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
启动服务器(或使用 sudo clickhouse 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++
的调试版本,该版本允许在运行时捕获更多错误。
消毒器
如果进程(ClickHouse 服务器或客户端)在本地运行时启动时崩溃,你可能需要禁用地址空间布局随机化:sudo sysctl kernel.randomize_va_space=0
地址消毒器
我们在每次提交时都在 ASan 下运行功能、集成、压力和单元测试。
线程消毒器
我们在每次提交时都在 TSan 下运行功能、集成、压力和单元测试。
内存消毒器
我们在每次提交时都在 MSan 下运行功能、集成、压力和单元测试。
未定义行为消毒器
我们在每次提交时都在 UBSan 下运行功能、集成、压力和单元测试。一些第三方库的代码没有针对 UB 进行消毒。
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
中找到它。此测试应持续运行(整夜或更长时间)。
我们还使用复杂的基于AST的查询模糊测试器,它能够找到大量边缘情况。它在查询AST中进行随机排列和替换。它会记住先前测试的AST节点,并在随机顺序处理时将其用于后续测试的模糊测试。您可以在这篇博客文章中了解有关此模糊测试器的更多信息。
压力测试
压力测试是模糊测试的另一种情况。它在随机顺序中并行运行所有功能测试,并使用单个服务器。测试结果不会被检查。
检查以下内容
- 服务器没有崩溃,没有触发调试或sanitizer陷阱;
- 没有死锁;
- 数据库结构一致;
- 服务器可以在测试后成功停止,并在没有异常的情况下重新启动。
有五种变体(Debug、ASan、TSan、MSan、UBSan)。
线程模糊测试器
线程模糊测试器(请勿与线程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运行测试。
构建作业和测试在沙盒中按每次提交的基础运行。生成的包和测试结果发布在GitHub上,并且可以通过直接链接下载。工件保存几个月。当您在GitHub上发送拉取请求时,我们将将其标记为“可测试”,我们的CI系统将为您构建ClickHouse包(发布、调试、使用地址sanitizer等)。
由于时间和计算能力的限制,我们不使用Travis CI。我们不使用Jenkins。它以前使用过,现在我们很高兴不再使用Jenkins。