カスタム クエリのテストについて
CodeQL には、クエリの自動回帰テスト用の簡単なテスト フレームワークが用意されています。 クエリをテストして、期待どおりに動作することを確認します。
クエリ テストで、CodeQL は、ユーザーがクエリで生成されると期待する結果と、実際に生成されたものを比較します。 期待される結果と実際の結果が異なる場合、クエリ テストは失敗します。 テストを修正するには、実際の結果と期待される結果が正確に一致するまで、クエリと期待される結果を反復処理する必要があります。 このトピックでは、テスト ファイルを作成し、test run
サブコマンドを使用してそれに対してテストを実行する方法について説明します。
カスタム クエリのテスト CodeQL パックの設定
すべての CodeQL テストは、特別な "テスト" CodeQL パックに格納する必要があります。 つまり、次を定義する qlpack.yml
ファイルを含むテスト ファイルのディレクトリです。
name: <name-of-test-pack>
version: 0.0.0
dependencies:
<codeql-libraries-and-queries-to-test>: "*"
extractor: <language-of-code-to-test>
dependencies
の値によって、テストするクエリを含む CodeQL パックが指定されます。 通常、これらのパックはソースから解決されるため、パックの固定バージョンを指定する必要はありません。 extractor
は、この CodeQL パックに格納されているコード ファイルからテスト データベースを作成するために CLI が使用する言語を定義します。 詳しくは、「CodeQL パックを使った分析のカスタマイズ」をご覧ください。
CodeQL リポジトリでのクエリ テストの編成方法を確認すると役立つ場合があります。 各言語には、コードベースを分析するためのライブラリとクエリを含む src
ディレクトリ、ql/<language>/ql/src
があります。 src
ディレクトリと共に、これらのライブラリとクエリのテストを含む test
ディレクトリがあります。
各 test
ディレクトリは、次の 2 つのサブディレクトリを含むテスト CodeQL パックとして構成されています。
query-tests
はsrc
ディレクトリに格納されているクエリのテストを含む一連のサブディレクトリです。 各サブディレクトリには、テスト コードと、テストするクエリを指定する QL 参照ファイルが含まれています。library-tests
は、QL ライブラリ ファイルのテストを含む一連のサブディレクトリです。 各サブディレクトリには、ライブラリの単体テストとして記述されたテスト コードとクエリが含まれています。
qlpack.yml
ファイルができたら、すべての依存関係がダウンロードされ、CLI で使えるようにする必要があります。 これを行うには、qlpack.yml
ファイルと同じディレクトリで次のコマンドを実行します。
codeql pack install
その結果、このパックでクエリを実行するために必要な一時的依存関係を全部指定する codeql-pack.lock.yml
ファイルが作られます。 このファイルはソース管理にチェックインしてください。
クエリのテスト ファイルの設定
テストするクエリごとに、テスト CodeQL パックにサブディレクトリを作成する必要があります。 次に、test コマンドを実行する前に、次のファイルをサブディレクトリに追加します。
テストするクエリの場所を定義するクエリ参照ファイル (
.qlref
ファイル)。 場所は、クエリを含む CodeQL パックのルートを基準にして定義されます。 通常、これはテスト パックのdependencies
ブロックで指定された CodeQL パックです。 詳しくは、「クエリ参照ファイル」をご覧ください。テストするクエリがテスト ディレクトリに格納されている場合は、クエリ参照ファイルを追加する必要はありませんが、通常はテストとは別にクエリを格納することをお勧めします。 唯一の例外が QL ライブラリの単体テストで、これはアラートやパスを生成するクエリとは別に、テスト パックに格納される傾向があります。
クエリを実行するサンプル コード。 これは、クエリで識別するように設計されたサンプル コードを含む 1 つ以上のファイルで構成されている必要があります。
また、.expected
という拡張子を持つファイルを作成することで、サンプル コードに対してクエリを実行するときに期待される結果を定義することもできます。 または、test コマンドをそのまま使用して .expected
ファイルを作成することもできます。
クエリを作成してテストする方法の例については、次の例をご覧ください。
メモ
.ql
、.qlref
、.expected
のファイルの名前は一貫している必要があります。
- test コマンドで
.ql
ファイル自体を直接指定する場合は、対応する.expected
ファイルと同じベース名が必要です。 たとえば、クエリがMyJavaQuery.ql
の場合、期待される結果ファイルはMyJavaQuery.expected
である必要があります。 - コマンドで
.qlref
ファイルを指定する場合は、対応する.expected
ファイルと同じベース名が必要ですが、クエリ自体の名前が異なるものにできます。 - サンプル コード ファイルの名前は、他のテスト ファイルと一致している必要はありません。
.qlref
(または.ql
) ファイルとサブディレクトリの横にあるすべてのサンプル コード ファイルが、テスト データベースの作成に使用されます。 そのため、わかりやすくするために、互いの先祖であるディレクトリにはテスト ファイルを保存しないことをお勧めします。
実行中 codeql test run
CodeQL クエリ テストは、次のコマンドを実行して実行されます。
codeql test run <test|dir>
<test|dir>
引数は次のいずれかになります。
.ql
ファイルのパス。.ql
ファイルを参照する.qlref
ファイルのパス。.ql
と.qlref
のファイルを再帰的に検索するディレクトリのパス。
次を指定することもできます。
--threads:
必要に応じて、クエリの実行時に使用するスレッドの数。 既定のオプションは1
です。 クエリの実行を高速化するために、より多くのスレッドを指定できます。0
を指定すると、スレッドの数が論理プロセッサの数と照合されます。
クエリのテスト時に使うことができるすべてのオプションについて詳しくは、「テストの実行」を参照してください。
例
次の例は、Java コードで空の then
ブロックを持つ if
ステートメントを検索するクエリのテストを設定する方法を示します。 これには、CodeQL リポジトリのチェックアウト外で CodeQL パックを分離するために、カスタム クエリと対応するテスト ファイルを追加する手順が含まれています。 これにより、CodeQL ライブラリを更新したり、別のブランチをチェックアウトしたりしても、カスタム クエリとテストは上書きされなくなります。
クエリとテスト ファイルを準備する
クエリを開発します。 たとえば、次の単純なクエリでは、Java コード内の空の
then
ブロックが検索されます。import java from IfStmt ifstmt where ifstmt.getThen() instanceof EmptyStmt select ifstmt, "This if statement has an empty then."
他のカスタム クエリを含むクエリを、ディレクトリ内の
EmptyThen.ql
という名前のファイルに保存します。 たとえば、custom-queries/java/queries/EmptyThen.ql
のようにします。カスタム クエリを CodeQL パックにまだ追加していない場合は、ここで CodeQL パックを作成します。 たとえば、カスタム Java クエリが
custom-queries/java/queries
に格納されている場合は、次の内容のqlpack.yml
ファイルをcustom-queries/java/queries
に追加します。name: my-custom-queries dependencies: codeql/java-queries: "*"
CodeQL パックについて詳しくは、「CodeQL パックを使った分析のカスタマイズ」をご覧ください。
次の内容の
qlpack.yml
ファイルをcustom-queries/java/tests
に追加して、カスタム クエリの CodeQL パックの名前と一致するようにdependencies
を更新して、Java テストの CodeQL パックを作成します。次の
qlpack.yml
ファイルは、my--user/my-query-tests
が 1.2.3 以上で 2.0.0 未満のバージョンのmy--user/my-custom-queries
に依存していることを示しています。 また、テスト データベースの作成時に CLI で Javaextractor
を使用する必要があることを宣言します。tests: .
行では、--strict-test-discovery
オプションを指定してcodeql test run
を実行する際に、パック内のすべての.ql
ファイルをテストとして実行する必要があることを宣言します。 通常、テスト パックにversion
プロパティは含まれません。 これにより、それらが誤って発行されるのを防ぐことができます。name: my--user/my-query-tests dependencies: my--user/my-custom-queries: ^1.2.3 extractor: java-kotlin tests: .
テスト ディレクトリのルートで
codeql pack install
を実行します。 その結果、このパックでクエリを実行するために必要な一時的依存関係を全部指定するcodeql-pack.lock.yml
ファイルが作られます。Java テスト パック内で、
EmptyThen.ql
に関連付けられているテスト ファイルを格納するディレクトリを作成します。 たとえば、custom-queries/java/tests/EmptyThen
のようにします。新しいディレクトリで、
EmptyThen.qlref
を作成 してEmptyThen.ql
の場所を定義します。 クエリのパスは、クエリを含む CodeQL パックのルートを基準にして指定する必要があります。 この場合、クエリは、my-query-tests
の依存関係として宣言されているmy-custom-queries
という名前の CodeQL パックの最上位ディレクトリにあります。 したがって、EmptyThen.qlref
にはEmptyThen.ql
のみを含める必要があります。テストするコード スニペットを作成します。 次の Java コードには、3 行目に空の
if
ステートメントが含まれています。 それをcustom-queries/java/tests/EmptyThen/Test.java
に保存します。class Test { public void problem(String arg) { if (arg.isEmpty()) ; { System.out.println("Empty argument"); } } public void good(String arg) { if (arg.isEmpty()) { System.out.println("Empty argument"); } } }
テストを実行する
テストを実行するには、custom-queries
ディレクトリに移動して codeql test run java/tests/EmptyThen
を実行します。
テストを実行すると、次のようになります。
EmptyThen
ディレクトリ内の 1 つのテストが検索されます。EmptyThen
ディレクトリに格納されている.java
ファイルから CodeQL データベースが抽出されます。EmptyThen.qlref
ファイルによって参照されるクエリがコンパイルされます。この手順が失敗した場合、原因は CLI でカスタム CodeQL パックが見つからないためです。 コマンドを再実行し、カスタム CodeQL パックの場所を指定します。次に例を示します。
codeql test run --search-path=java java/tests/EmptyThen
構成の一環として検索パスを保存することに関する詳細については、「CodeQL の構成ファイルでコマンド オプションを指定する」を参照してください。
クエリが実行され、
EmptyThen.actual
結果ファイルが生成されて、テストが実行されます。EmptyThen.expected
ファイルを確認して、.actual
結果ファイルと比較されます。テストの結果 (この場合はエラー
0 tests passed; 1 tests failed:
) が報告されます。 クエリの期待される結果を含むファイルをまだ追加していないため、テストは失敗しました。
クエリ テストの出力を表示する
CodeQL では、EmptyThen
ディレクトリに次のファイルが生成されます。
EmptyThen.actual
。クエリによって生成された実際の結果を含むファイルです。EmptyThen.testproj
。VS Code に読み込み、失敗したテストのデバッグに使用できるテスト データベースです。 テストが正常に完了すると、このデータベースはハウスキープ処理で削除されます。--keep-databases
オプションを使用してtest run
を実行することで、この手順をオーバーライドできます。
この場合、エラーが予想されますが、修正は簡単です。 EmptyThen.actual
ファイルを開くと、テストの結果が表示されます。
| Test.java:3:5:3:22 | stmt | This if statement has an empty then. |
このファイルには、クエリが出力する select
句の各部分の個別の列と共に、結果の場所の列を含むテーブルが含まれています。 結果は期待どおりであるため、ファイル拡張子を更新して、このテストで期待される結果 (EmptyThen.expected
) として定義できます。
ここでテストを再実行すると、出力は似たものになりますが、完了時に All 1 tests passed.
が報告されます。
クエリの結果が変更された場合 (たとえば、クエリの select
ステートメントを変更した場合)、テストは失敗します。 失敗した結果の場合、CLI 出力には EmptyThen.expected
と EmptyThen.actual
のファイルの統合された差分が含まれます。 この情報で、簡単なテスト エラーを十分デバッグできる場合があります。
デバッグが困難なエラーの場合は、VS Code の CodeQL に EmptyThen.testproj
をインポートし、EmptyThen.ql
を実行して、結果を Test.java
サンプル コードで表示できます。 詳しくは、「CodeQL データベースの管理」をご覧ください。