关于测试自定义查询
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
定义 CLI 将使用哪种语言从存储在此 CodeQL 包中的代码文件创建测试数据库。有关更多信息,请参见“使用 CodeQL 包自定义分析”。
您可能会发现查看 CodeQL 仓库 中查询测试的组织方式很有用。每种语言都有一个src
目录,ql/<language>/ql/src
,其中包含用于分析代码库的库和查询。在src
目录旁边,还有一个test
目录,其中包含这些库和查询的测试。
每个test
目录都配置为一个测试 CodeQL 包,其中包含两个子目录
query-tests
一系列包含存储在src
目录中的查询测试的子目录。每个子目录都包含测试代码和一个 QL 参考文件,该文件指定要测试的查询。library-tests
一系列包含 QL 库文件的测试的子目录。每个子目录都包含作为库单元测试编写的测试代码和查询。
创建qlpack.yml
文件后,需要确保所有依赖项都已下载并可供 CLI 使用。为此,请在与qlpack.yml
文件相同的目录中运行以下命令
codeql pack install
这将生成一个codeql-pack.lock.yml
文件,该文件指定运行此包中查询所需的所有传递依赖项。此文件应检入到源代码控制中。
为查询设置测试文件
对于要测试的每个查询,应在测试 CodeQL 包中创建一个子目录。然后,在运行测试命令之前,将以下文件添加到子目录
-
查询引用文件(
.qlref
文件)定义了要测试的查询位置。该位置相对于包含查询的 CodeQL 包的根目录定义。通常,这是在测试包的dependencies
块中指定的 CodeQL 包。更多信息,请参见“查询引用文件”。如果要测试的查询存储在测试目录中,则无需添加查询引用文件,但通常最好将查询与测试分开存储。唯一的例外是 QL 库的单元测试,这些测试往往存储在测试包中,与生成警报或路径的查询分开。
-
要运行查询的示例代码。这应包含一个或多个包含查询旨在识别的代码示例的文件。
您还可以通过创建扩展名为 .expected
的文件来定义运行查询时针对示例代码所期望看到的结果。或者,您可以让测试命令为您创建 .expected
文件。
有关如何创建和测试查询的示例,请参见下面的示例。
注意
您的 .ql
、.qlref
和 .expected
文件必须具有一致的名称。
- 如果要在测试命令中直接指定
.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
将线程数与逻辑处理器数匹配。
有关测试查询时可以使用的所有选项的完整详细信息,请参见“test run”。
示例
以下示例演示如何为搜索 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
来创建 Java 测试的 CodeQL 包,并更新dependencies
以匹配您的自定义查询 CodeQL 包的名称。以下
qlpack.yml
文件指出my-github-user/my-query-tests
依赖于版本大于或等于 1.2.3 且小于 2.0.0 的my-github-user/my-custom-queries
。它还声明 CLI 在创建测试数据库时应使用 Javaextractor
。tests: .
行声明,当使用--strict-test-discovery
选项运行codeql test run
时,包中的所有.ql
文件都应作为测试运行。通常,测试包不包含version
属性。这可以防止您意外发布它们。name: my-github-user/my-query-tests dependencies: my-github-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-custom-queries
的 CodeQL 包的顶级目录中,该包被声明为my-query-tests
的依赖项。因此,EmptyThen.qlref
应该只包含EmptyThen.ql
。 -
创建要测试的代码片段。以下 Java 代码在第三行包含一个空的
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
目录中找到一个测试。 -
从存储在
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
文件的统一差异。此信息可能足以调试微不足道的测试失败。
对于难以调试的失败,您可以将 EmptyThen.testproj
导入 CodeQL for VS Code,执行 EmptyThen.ql
,并在 Test.java
示例代码中查看结果。更多信息,请参见“管理 CodeQL 数据库”。