テスト
機能テスト
機能テストは最もシンプルで便利なテストです。 ClickHouseのほとんどの機能は機能テストでテスト可能であり、この方法でテスト可能なClickHouseのコードに対するすべての変更に必須です。
各機能テストは、実行中のClickHouseサーバーに1つまたは複数のクエリを送信し、その結果を参照と比較します。
テストは queries
ディレクトリにあります。
2つのサブディレクトリがあります: stateless
と stateful
。
- ステートレステストは、事前にロードされたテストデータなしでクエリを実行します - 通常、テスト自体の中で小さな合成データセットをその場で作成します。
- ステートフルテストは、ClickHouseからの事前にロードされたテストデータを必要とし、一般公開されています。
各テストは2つのタイプのいずれかです: .sql
と .sh
。
.sql
テストは、clickhouse-client
にパイプされるシンプルなSQLスクリプトです。.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
を実行するには、リポジトリフォルダに移動し、次のコマンドを実行します。
テスト結果(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
。
また、テストを並列で実行したり、ランダムな順序で実行するオプションもあります。
新しいテストの追加
新しいテストを追加するには、まず queries/0_stateless
ディレクトリに .sql
または .sh
ファイルを作成します。
次に、clickhouse-client < 12345_test.sql > 12345_test.reference
または ./12345_test.sh > ./12345_test.reference
を使用して、対応する .reference
ファイルを生成します。
テストは、必ず事前に自動的に作成されるデータベース test
内のテーブルを作成、削除、選択するべきです。
一時テーブルを使用することは問題ありません。
CIと同じ環境をローカルで設定するには、テスト設定をインストールします(Zookeeperのモック実装を使用し、いくつかの設定を調整します)
テストは以下の要件を満たすべきです
- 最小限であること: 必要最低限のテーブル、カラム、複雑さのみを作成すること。
- 速いこと: 数秒以上かからない(できれば、サブセカンド)。
- 正確で決定的であること: テスト対象の機能が正常に動作しないときのみに失敗すること。
- 隔離されていること / ステートレスであること: 環境やタイミングに依存しないこと。
- 包括的であること: ゼロ、ヌル、空のセット、例外(否定テスト、構文
-- { serverError xyz }
と-- { clientError xyz }
を使用)などのコーナーケースをカバーすること。 - テストの最後にテーブルをクリーンアップすること(残り物がある場合)。
- 他のテストが同じ内容をテストしないようにすること(つまり、最初にgrepする)。
テスト実行の制限
テストには、CIの実行コンテキストでの制限を指定する tags を持つことができます。
.sql
テストのタグは、最初の行にSQLコメントとして配置されます:
.sh
テストのタグは、2行目のコメントとして書かれます:
利用可能なタグのリスト:
タグ名 | 何をするのか | 使用例 |
---|---|---|
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の下で実行されない | テストはFast testで無効になっている MySQL テーブルエンジンを使用 |
no-[asan, tsan, msan, ubsan] | sanitizersを有効にしたビルドでテストを無効にする | テストは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 |
上記の設定に加えて、特定のClickHouse機能の使用を定義するために system.build_options
から USE_*
フラグを使用できます。
たとえば、テストがMySQLテーブルを使用する場合、use-mysql
タグを追加する必要があります。
ランダム設定の制限の指定
テストは、テスト実行中にランダム化できる設定の最小値と最大値を指定できます。
.sh
テストの制限は、タグの隣の行にコメントとして書かれます。タグが指定されていない場合は2行目に書かれます:
.sql
テストは、タグの隣の行または最初の行にSQLコメントとしてタグが配置されます:
1つの制限のみを指定する必要がある場合は、もう1つに None
を使用できます。
テスト名の選択
テストの名前は五桁のプレフィックスで始まり、その後に説明的な名前が続きます。例えば、00422_hash_function_constexpr.sql
のように。
プレフィックスを選択するには、ディレクトリ内に既に存在する最大のプレフィックスを見つけて、1つ増やします。
その間に、同じ数値のプレフィックスを持つ他のテストが追加されることがありますが、これは問題ではなく、後で変更する必要はありません。
発生するべきエラーのチェック
時には、不正なクエリに対してサーバーエラーが発生することを確認したい場合があります。このために、SQLテストでは以下の形式で特別な注釈をサポートしています:
このテストは、サーバーが未知のカラム x
についてエラーコード49を返すことを保証します。
エラーが発生しないか、エラーが異なる場合、テストは失敗します。
クライアント側でエラーが発生することを確認したい場合は、clientError
注釈を使用してください。
エラーメッセージの特定の表現をチェックしないでください。将来的に変更される可能性があり、テストが不要に失敗することになります。 エラーコードのみをチェックしてください。 既存のエラーコードが不十分な場合は、新しいものを追加することを検討してください。
分散クエリのテスト
機能テストで分散クエリを使用する場合、サーバーが自分自身をクエリするために 127.0.0.{1..2}
アドレスを持つ remote
テーブル関数を利用することができます。また、サーバーの設定ファイルにある test_shard_localhost
のような事前定義されたテストクラスターを使用することもできます。
テスト名には shard
または distributed
を追加することを忘れないでください。そうすることで、CI内で正しい設定で実行され、サーバーが分散クエリをサポートするように構成されます。
一時ファイルの操作
シェルテストで一時ファイルをその場で作成する必要がある場合があります。
CIのチェックの中にはテストを並行して実行するものがあるため、スクリプト内で一時ファイルをユニークではない名前で作成または削除すると、FlakyなどのCIチェックが失敗する原因となることがあります。
これを回避するために、環境変数 $CLICKHOUSE_TEST_UNIQUE_NAME
を使用して、実行中のテストにユニークな名前を持つ一時ファイルを付与します。
こうすることで、セットアップ中に作成するファイルやクリーンアップ中に削除するファイルが、そのテストでのみ使用され、並行して実行される他のテストによって影響を受けることがないことを確認できます。
Known Bugs
再現可能なバグがある場合、あらかじめ準備された機能テストを 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チェックを直接実行可能ファイルを呼び出すことで実行できます。例えば:
パフォーマンステスト
パフォーマンステストは、合成クエリに対するClickHouseの一部の性能を測定および比較することを可能にします。
パフォーマンステストは tests/performance/
にあります。
各テストは、テストケースの説明を含む.xml
ファイルで表されます。
テストは、 docker/test/performance-comparison
ツールで実行されます。実行方法についてはreadmeファイルを参照してください。
各テストは、ループ内で1つまたは複数のクエリ(パラメータの組み合わせを含む可能性があります)を実行します。
特定のシナリオにおける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
(または、サーバーを停止するには 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でビルド(推奨されない)
- ライブラリの共有リンクを用いたビルド(推奨されない)
たとえば、システムパッケージを使用してビルドすることは悪い慣習ですが、特定のシステムがどのパッケージのバージョンを持つかは保証できません。 しかし、これはDebianのメンテナーには本当に必要です。 そのため、少なくともこのビルドのバリアントをサポートしなければなりません。 もう一つの例:共有リンクは一般的な問題の原因ですが、一部の愛好者には必要です。
すべてのビルドのバリアントでテストを実行することはできませんが、少なくともさまざまなビルドバリアントが壊れていないことを確認したいと考えています。 その目的のために、ビルドテストを使用します。
また、ビルド時のRAMを多く必要とするような翻訳単位がないことをテストします。
スタックフレームが大きすぎないこともテストします。
プロトコル互換性のテスト
ClickHouseのネットワークプロトコルを拡張するとき、古いclickhouse-clientが新しいclickhouse-serverで動作し、新しいclickhouse-clientが古いclickhouse-serverで動作することを手動でテストします(適切なパッケージからバイナリを単に実行するだけです)。
また、統合テストを通じて一部のケースも自動的にテストしています:
- 古いバージョンのClickHouseによって書き込まれたデータが新しいバージョンで正常に読み取れるか;
- 異なるClickHouseバージョンのクラスターで分散クエリが動作するか。
コンパイラからのヘルプ
メインのClickHouseコード(src
ディレクトリにある)は、-Wall -Wextra -Werror
でビルドされ、いくつかの追加警告が有効になっています。
ただし、これらのオプションはサードパーティのライブラリには有効になっていません。
Clangにはさらに便利な警告があります。-Weverything
を使用してそれらを探し、デフォルトのビルドに何かを選ぶことができます。
ClickHouseは、開発と生産の両方でClangを使用してビルドします。
デバッグモードで自分のマシンでビルドできます(ノートパソコンのバッテリーを節約するために)。ただし、コンパイラは -O3
でより多くの警告を生成できるため、より良い制御フローと手続き間分析が行われます。
デバッグモードでClangでビルドすると、デバッグ版の libc++
が使用され、実行時により多くのエラーを捕まえることができます。
サニタイザー
ローカルで実行中にプロセス(ClickHouseサーバーまたはクライアント)が起動時にクラッシュする場合、アドレス空間レイアウトのランダム化を無効にする必要があるかもしれません: sudo sysctl kernel.randomize_va_space=0
アドレスサニタイザー
我々は、コミットごとにASanの下で機能、統合、ストレス、およびユニットテストを実行します。
スレッドサニタイザー
我々は、コミットごとにTSanの下で機能、統合、ストレス、およびユニットテストを実行します。
メモリサニタイザー
我々は、コミットごとにMSanの下で機能、統合、ストレス、およびユニットテストを実行します。
未定義動作サニタイザー
我々は、コミットごとにUBSanの下で機能、統合、ストレス、およびユニットテストを実行します。 一部のサードパーティライブラリのコードは、UBのためにサニタイズされていないことに注意してください。
Valgrind(メモリチェック)
以前は、Valgrindの下で機能テストを一晩実行していましたが、現在は行っていません。
何時間もかかります。
現在、re2
ライブラリに一つの既知の偽陽性があります。 この記事 を参照してください。
ファジング
ClickHouseのファジングは、libFuzzer とランダムSQLクエリの両方を使用して実装されています。 すべてのファジングテストはサニタイザー(アドレスおよび未定義)で実行されるべきです。
ライブラリコードの孤立したファジングテストには、LibFuzzerが使用されます。
ファジングテストはテストコードの一部として実装され、"_fuzzer" という名前の接尾辞を持ちます。
ファジングテストの例は src/Parsers/fuzzers/lexer_fuzzer.cpp
にあります。
LibFuzzer特有の設定、辞書、およびコーパスは tests/fuzz
に保存されています。
ユーザー入力を処理するすべての機能に対して、ファジングテストを書くことを奨励します。
ファジングテストはデフォルトではビルドされません。
ファジングをビルドするには、-DENABLE_FUZZING=1
と -DENABLE_TESTS=1
オプションの両方を設定する必要があります。
ファジングをビルドする際には、Jemallocを無効にすることをお勧めします。
Google OSS-FuzzにClickHouseファジングを統合するために使用される設定は、docker/fuzz
にあります。
また、サーバーがそれらを実行してもクラッシュしないことを確認するために、ランダムなSQLクエリを生成するシンプルなファジングテストを使用しています。
これは 00746_sql_fuzzy.pl
にあります。
このテストは連続して実行する必要があります(一晩またはそれ以上)。
次に、ASTに基づく洗練されたクエリファジングがあります。これは、多くのコーナーケースを見つけることができます。 クエリAST内のランダムな置換と置換を行います。 前のテストからASTノードを記憶し、ランダムな順序で処理する際にそれらを次のテストのファジングに使用します。 このファジングについての詳細は、このブログ記事 で学ぶことができます。
ストレスタイプ
ストレスタイプはもう一つのファジングのケースです。 すべての機能テストを単一のサーバーでランダムな順序で並行して実行します。 テストの結果はチェックされません。
以下を確認します:
- サーバーがクラッシュしないこと、デバッグやサニタイザートラップがトリガーされないこと;
- デッドロックがないこと;
- データベース構造が整合していること;
- テストの後にサーバーが正常に停止できること、例外なしで再起動できること。
5つのバリアントがあります(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
を使用しています。
強化
デバッグビルドでは、ユーザーレベルのアロケーションのASLRを行うカスタムアロケータを使用しています。
アロケーション後に読み取り専用となることが期待されるメモリ領域を手動で保護しています。
デバッグビルドでは、呼び出されない「有害な」(古い、不正確、安全でない)関数が呼び出されないことを保証するためにlibcのカスタマイズを行っています。
デバッグアサーションは広範囲に使用されています。
デバッグビルドでは、「論理エラー」コード(バグを示唆する)がスローされた場合、プログラムは早期に終了します。 これにより、リリースビルドで例外を使用できるようにし、デバッグビルドではそれをアサーションとすることができます。
デバッグビルドにはjemallocのデバッグ版が使用されます。 デバッグビルドにはlibc++のデバッグ版が使用されます。
実行時の整合性チェック
ディスク上に保存されたデータはチェックサムが取られています。 MergeTree テーブル内のデータは同時に3つの方法でチェックサムが取られています(圧縮データブロック、非圧縮データブロック、ブロック全体のチェックサム)。 クライアントとサーバー間、またはサーバー間で転送されるデータもチェックサムが取られています。 レプリケーションにより、レプリカ間でビット同一のデータが保持されます。
これは、欠陥のあるハードウェア(ストレージメディアのビットロット、サーバーの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回(統合テストの場合)実行します。 1回でもテストが失敗した場合、そのテストはフレーク性があると見なされます。
テスト自動化
GitHub Actionsを使用してテストを実行しています。
ビルドジョブとテストは、コミットごとにサンドボックスで実行されます。 結果として得られたパッケージとテスト結果はGitHubに公開され、直接リンクでダウンロードできます。 アーティファクトは数ヶ月間保存されます。 GitHubでプルリクエストを送信すると、"can be tested"としてタグ付けされ、私たちのCIシステムがClickHouseパッケージ(リリース、デバッグ、アドレスサニタイザなど)をビルドします。