Catch2 执行路径过滤完全指南:用 `--section`、`--generator-index` 与 `--path-filter` 精确运行指定 Section 与 Generator
2026/9/13 4:46:22 网站建设 项目流程

Catch2 执行路径过滤完全指南:用--section--generator-index--path-filter精确运行指定 Section 与 Generator

【免费下载链接】Catch2A modern, C++-native, test framework for unit-tests, TDD and BDD - using C++14, C++17 and later (C++11 support is in v2.x branch, and C++03 on the Catch1.x branch)项目地址: https://gitcode.com/GitHub_Trending/ca/Catch2

本文对应的源码级文档为 docs/filtering-execution-path.md,涉及的参数解析实现位于 src/catch2/internal/catch_commandline.cpp,过滤判定核心位于 src/catch2/internal/catch_test_case_tracker.hpp 与其同名实现文件。Generator 与通用路径过滤能力自 Catch2 3.13.0 引入,旧行为(仅-c/--section)同版本被标记为弃用。

Catch2 允许你通过命令行精确选择一条测试用例(TEST_CASE)内部的执行路径:既可以只进入某个 Section,也可以指定 Generator 的某个元素、甚至同时约束多个嵌套层级。本文以官方文档 docs/filtering-execution-path.md 为主体,结合仓库源码,系统讲解三个过滤参数-c/--section-g/--generator-index-p/--path-filter的用法、新旧两套行为模型的差异、断言数量的推算方法,以及底层 Tracker 深度机制的实现原理。读完本文,你将能够精确控制复杂测试用例的执行分支,并准确预判"哪些代码会被执行、会执行几次"。

三个过滤参数与两种行为模型

Catch2 提供了三个命令行动参数,用于在测试用例内部挑选执行路径,每个参数都可以重复使用多次:

-c, --section <section name> -g, --generator-index <index in generator> -p, --path-filter <path filter spec>

它们内部共享一个"过滤器栈"(shared stack of filters)。但根据你使用了哪些参数,Catch2 会进入两种截然不同的行为模型:

  • 旧行为(old behaviour):只使用-c/--section形式指定 Section 过滤器时触发。它完全不涉及 Generator——既不能过滤 Generator,也不会把 Generator 计入过滤深度。
  • 新行为(new behaviour):只要同时使用了-g/--generator-index-p/--path-filter中的任意一个,就会切换为新行为,此时过滤能力扩展到了 Generator 元素。

旧行为在 Catch2 3.13.0 中被标记为弃用(deprecated),新行为在同版本引入。官方弃用说明可参考 docs/deprecations.md。

参数解析的源码实现

从源码层面看,三个参数最终都汇入同一条处理链。在 src/catch2/internal/catch_commandline.cpp 中定义了三个设置回调:

  • setSectionFilter(对应-c):将参数去空白(trim)后,以PathFilter::For::Section类型压入config.pathFilters
  • setGeneratorFilter(对应-g):若参数不是"*",则必须能被解析为无符号整数,否则报错"Could not parse '...' as generator index";随后设置config.useNewPathFilteringBehaviour = true,并以PathFilter::For::Generator类型压入过滤器栈;
  • setPathFilter(对应-p):长度必须至少为 3,必须以g:c:前缀开头,分别委托给上述两个回调(并剥掉前缀),否则分别报错"Path filter '...' is too short""Path filter '...' has unknown type prefix";同时它也会无条件打开新行为开关。

参数注册位于 src/catch2/internal/catch_commandline.cpp:

| Opt( accept_many, setSectionFilter, "section name" ) ["-c"]["--section"] ( "specify section to run" ) | Opt( accept_many, setGeneratorFilter, "index spec" ) ["-g"]["--generator-index"] ( "specify generator elements to try" ) | Opt( accept_many, setPathFilter, "path filter spec" ) ["-p"]["--path-filter"] ( "qualified path filter" )

过滤器的存储结构PathFilter定义在 src/catch2/internal/catch_path_filter.hpp:它由枚举For { Section, Generator }(指明这是 Section 过滤器还是 Generator 过滤器)和一个std::string filter组成。所有过滤器收集在ConfigData::pathFiltersstd::vector<PathFilter>)中,是否启用新行为由ConfigData::useNewPathFilteringBehaviourbool)记录,两者都定义在 src/catch2/catch_config.hpp,并通过Config::useNewFilterBehaviour()暴露(见 src/catch2/catch_config.cpp)。

从源码可以确认一个关键点:"新行为"不取决于你写了-c还是-g,而取决于useNewPathFilteringBehaviour开关是否被-g/-p触发。所以即便你只用-p c:xxx添加 Section 过滤器,行为也是"新式的"。

新旧行为共有的三个"反直觉"点

无论旧行为还是新行为,Catch2 的路径过滤都有以下三个容易令人意外的事实(官方文档明确列出):

  1. 被跳过的 Section 之外的代码仍然会执行。例如TEST_CASE中位于各 Section 之外的任何 setup 代码,无论过滤器如何,都会照常执行(且执行次数可能与 Generator 的元素个数有关)。
  2. 路径过滤器只过滤路径的"前缀"。如果你只指定了单个过滤器,它只作用于最顶层(top level)的 Section/Generator,其子 Section/Generator 不受影响、保持全量执行。
  3. 路径过滤与测试用例选择相互独立。Catch2 会在所有被选中的测试用例内部尝试跟随路径过滤器。也就是说,如果你只给了路径过滤器、没给测试用例过滤器,Catch2 会尝试在每个已注册的测试用例内部应用这些路径过滤器。

旧行为:仅-c/--section过滤 Section

-c, --section <section name>

-c/--section的参数可以是任意字符串。当 Catch2 决定是否进入某个 Section 时,会把该 Section 的去除首尾空白后的名称(trimmed name)对应的去除空白后的 Section 过滤器做比较:完全相等则可以打开该 Section,否则跳过它。

从实现看,Section 的比较逻辑位于 src/catch2/internal/catch_test_case_tracker.cpp:SectionTracker::isFilteredImpl()isComplete()中,旧行为取m_sectionOnlyDepth作为过滤器下标,并用m_trimmed_name != StringRef(filter)做精确匹配(注意是精确字符串比较,不是通配符/子串匹配)。SectionTracker在构造时保存了 trim 后的名称,见 src/catch2/internal/catch_test_case_tracker.cpp。

示例一:简单 Section 嵌套

给定如下测试用例:

TEST_CASE( "foo" ) { REQUIRE( true ); SECTION( "A" ) { SECTION( "A1" ) { REQUIRE( true ); } SECTION( "A2" ) { REQUIRE( true ); } } SECTION( "B" ) { SECTION( "B1" ) { REQUIRE( true ); } SECTION( "B2" ) { REQUIRE( true ); } } }
  • ./tests foo -c A:运行 Section "A" 及其两个子 Section,共4 条断言(1 条在外 + "A" 中 1 条 + "A1" 中 1 条 + "A2" 中 1 条)。
  • ./tests foo -c A -c B:两个过滤器构成深度为 2 的栈。顶层匹配 "A",但第二层过滤器是 "B",而 "A" 的子 Section 是 "A1"/"A2",全部不匹配,因此只进入 "A" 而不进入任何子 Section,共1 条断言("A" 之前的REQUIRE(true))。
  • ./tests foo -c A -c A1:进入 "A" 且只进入 "A1" 子 Section,共2 条断言

示例二:包含嵌套 Generator 的 Section

旧行为完全忽略 Generator:它们既不能被过滤,也不会计入过滤深度。给定:

TEST_CASE( "bar" ) { REQUIRE( true ); SECTION( "A" ) { REQUIRE( true ); } SECTION( "B" ) { auto i = GENERATE( 1, 2, 3 ); DYNAMIC_SECTION( "i=" << i ) { REQUIRE( true ); } } }
  • ./tests bar -c A:结果为2 条断言(外层 1 条 + "A" 中 1 条),因为 Section "B" 被跳过,其内部 Generator 根本不会运行。
  • ./tests bar -c B -c i=2:结果为4 条断言。过滤器第二层是 "i=2",但 Generator 在旧行为下被完全忽略——它不在过滤深度内,所以 Section "B" 内的整个 Generator 必须全部跑完(3 个元素 × 每次进入测试用例的外层REQUIRE(true))。只有当 Generator 返回i == 2时动态 Section 才被进入:外层断言 3 次 + "B" 中 1 次 = 4 条。
  • ./tests bar -c B -c i=4:结果为3 条断言。Section 外的断言在每次进入测试用例时都执行;Generator 迫使测试用例重跑 3 次才耗尽,即使动态 Section 永远不会被进入(i只可能是 1、2、3),因此 3 条全部来自外层REQUIRE(true)

示例三:带兄弟 Generator 的 Section

当 Section 与 Generator 是"兄弟"(sibling,同级)关系时,过滤结果会更出人意料:

TEST_CASE( "qux" ) { REQUIRE( true ); SECTION( "A" ) { REQUIRE( true ); } auto i = GENERATE( 1, 2, 3 ); DYNAMIC_SECTION( "i=" << i ) { REQUIRE( true ); } }
  • ./tests qux -c A:结果为4 条断言。Section "A" 只进入 1 次,但兄弟 Generator 必须被耗尽(3 个元素),而外层第一条断言在每次 Generator 元素重跑时都会执行一次(1 + 3 = 4)。
  • ./tests qux -c i=2:同样结果为4 条断言。因为旧行为忽略 Generator,过滤器 "i=2" 对 Generator 无效,Generator 必须完整跑完 3 个元素;动态 Section 只进入一次。即外层断言 3 次 + 动态 Section 中 1 次 = 4 条。

新行为:-g/--generator-index-p/--path-filter

-g, --generator-index <index in generator> -p, --path-filter <path filter spec>

-g/--generator-index的参数必须是以下两者之一:

  • 一个非负整数,表示 Generator 中目标元素的索引(index,从 0 开始);
  • "*",表示允许 Generator 的全部元素。

索引超出 Generator 的元素范围属于错误(会导致该次断言失败)。

-p/--path-filter 的参数必须以c:(Section 过滤器)或g:(Generator 过滤器)开头,冒号之后的部分才被解析为具体的 Section 名或 Generator 索引。注意:-p会把g:之后的文本原样委托给 Generator 过滤器解析,因此-p g:*等价于-g *

一个重要的提醒(官方文档明确强调):只要使用了-p/--path-filter,即使只用来添加 Section 过滤器,也会启用新的过滤行为

Section 与 Generator 的过滤语义差异

Section 和 Generator 在过滤失败时的处理方式有本质区别:

  • 一个 Section 可以"不进入"(left un-entered),测试用例继续走其他分支;
  • 但 Generator总是必须处于活动状态(a generator always has to be active)。因此,如果 Generator 在某层深度上未能通过过滤器(例如该深度对应的过滤器是 Section 过滤器而非 Generator 过滤器),它就无法继续执行,只能终止测试用例的执行。目前 Catch2 是通过SKIP()的等价机制实现的,使该 Section 被判定为"跳过"(skipped)。

这一设计直接导致下文示例中"带兄弟 Generator 的 Section"会出现 skipped 测试用例。

示例一:嵌套 Generator

TEST_CASE( "waldo" ) { auto i = GENERATE( 1, 10, 100 ); auto j = GENERATE( 2, 20, 200 ); CAPTURE( i, j ); REQUIRE( true ); }
  • ./tests waldo -g 1:结果为3 条断言,且i := 10。因为只有第一层 Generator 被过滤,第二层嵌套 Generator 未过滤、全量运行。
  • ./tests waldo -g 1 -g 2:结果为1 条断言i := 10, j := 200
  • ./tests waldo -g * -g 2:结果为3 条断言,且j恒为 200(i取全部 3 个值)。
  • ./tests waldo -g 1 -g *:结果为3 条断言,且i恒为 10(j取全部 3 个值)。
  • ./tests waldo -g 3:结果为1 条失败断言。第一个 Generator 没有第 3 个元素(索引 0、1、2 之外)。
  • ./tests waldo -g * -g 3:结果为3 条失败断言。第二个 Generator 没有第 3 个元素,但我们仍必须耗尽第一个 Generator 的 3 个元素。

示例二:Generator 内嵌动态 Section

TEST_CASE( "grault" ) { REQUIRE( true ); auto i = GENERATE( 1, 2, 3 ); DYNAMIC_SECTION( "i=" << i ) { REQUIRE( true ); } }
  • ./tests grault -p g:1:结果为2 条断言。动态 Section 上没有任何过滤器,因此全量进入。
  • ./tests grault -p g:1 -p c:i=2:结果为2 条断言。动态 Section 的过滤器i=2与 Generator 给出的元素匹配。
  • ./tests grault -p g:1 -p c:i=3:结果为1 条断言。Generator 被限制为只尝试i := 2(索引 1 对应第 2 个元素),而动态 Section 过滤器i=3与之不匹配、被过滤掉,只剩下外层 1 条断言。

示例三:带兄弟 Generator 的 Section(新行为)

由于 Generator 未通过过滤器时必须停止测试用例执行,因此"只运行带兄弟 Generator 的某个 Section"在新行为下是不可能的——必然触发测试用例跳过。仍用前文的qux用例:

TEST_CASE( "qux" ) { REQUIRE( true ); SECTION( "A" ) { REQUIRE( true ); } auto i = GENERATE( 1, 2, 3 ); DYNAMIC_SECTION( "i=" << i ) { REQUIRE( true ); } }
  • ./tests qux -p g:1:结果为2 条断言,动态 Section 只进入一次。
  • ./tests qux -p g:1 -p c:i=1:结果为1 条断言。动态 Section 的过滤器i=1与 Generator 过滤器(索引 1 →i := 2)不兼容。
  • ./tests qux -p c:A:结果为2 条断言,且测试用例被跳过(skipped)。因为 Generator 与 Section "A" 是兄弟,读取的是同一个过滤器栈,但该过滤器是 Section 过滤器,Generator 不是 Section、无法继续。
  • ./tests qux -p c:i=2:结果为1 条断言,且测试用例被跳过。同样地,过滤器栈的第一层是 Section 过滤器,Generator 无法继续。

对比旧行为下的./tests qux -c i=2(结果为 4 条断言、Generator 完整跑完所有元素),可以看到新行为下"过滤失败 = 跳过",这是两者最直观的行为差异。

源码级原理:Tracker 深度机制与过滤判定

理解新旧行为的差异,关键在于ITracker中维护的两套深度计数器(见 src/catch2/internal/catch_test_case_tracker.hpp):

  • m_allTrackerDepth所有tracker(包括 Section 与 Generator)都会在构造时自增(m_parent->m_allTrackerDepth + 1,见 catch_test_case_tracker.cpp),用于决定新风格过滤器作用在哪一层;
  • m_sectionOnlyDepth只有 SectionTracker在构造时自增(++m_sectionOnlyDepth,见 catch_test_case_tracker.cpp),Generator 不改变它,用于决定旧风格过滤器作用在哪一层。

此外,代码注释还揭示了一个细节:在遇到第一个"真实" Section tracker 之前,存在两个 dummy tracker(root 与 test-case),因此两个深度计数器都从-2起步,让第一个真实 Section 的深度恰好从 0 开始。这正是"旧行为完全忽略 Generator"的根源——Generator 只增加m_allTrackerDepth,从不影响m_sectionOnlyDepth

过滤判定的快速路径在 catch_test_case_tracker.hpp 的ITracker::isFiltered():先按m_newStyleFilters ? m_allTrackerDepth : m_sectionOnlyDepth计算过滤器下标,若过滤器栈长度不大于该深度,说明该层没有过滤器、直接放行;否则进入各 tracker 自己的isFilteredImpl()慢路径。SectionTracker::isFilteredImpl()(catch_test_case_tracker.cpp)中,新风格过滤器必须显式针对 Section(PathFilter::For::Section),否则直接视为被过滤;随后做 trimmed 名称的精确比较。

Generator 一侧的过滤语义("必须始终活动、失败即跳过")与 tracker 的状态机强相关:ITracker::CycleState枚举(NotStartedExecutingExecutingChildrenNeedsAnotherRunCompletedSuccessfullyFailed,见 catch_test_case_tracker.hpp)驱动着测试用例的多轮重跑;Generator 耗尽前会不断让测试用例NeedsAnotherRun。而过滤器栈与"新/旧风格"开关是在运行开始时通过rootTracker.setFilters(&m_config->getPathFilters(), ...)注入的(见 src/catch2/internal/catch_run_context.cpp 及 catch_test_case_tracker.hpp)。

由此可以推断:新旧行为在架构上共用同一套 tracker/过滤器基础设施,区别只在于"深度按什么计数"以及"过滤器类型(Section/Generator)是否参与匹配"。

测试验证

仓库自带的测试充分覆盖了这些行为:

  • tests/SelfTest/IntrospectiveTests/CmdLine.tests.cpp:验证-c/-g/-p三个参数解析后config.pathFilters的内容与useNewPathFilteringBehaviour开关是否正确设置;
  • tests/SelfTest/IntrospectiveTests/PartTracker.tests.cpp:直接针对TestCaseTracking的 tracker 行为进行验证,包括过滤深度、Generator 处理等;
  • tests/TestScripts/testSectionFiltering.py:脚本层面驱动编译出的测试二进制,用真实命令行参数组合检查 Section 过滤的实际运行结果;
  • 基线文件如 tests/SelfTest/Baselines/console.sw.approved.txt 中保留了config.useNewPathFilteringBehaviour的相关断言输出。

如果你需要快速体验这些示例,可先按 docs/cmake-integration.md 配置构建出测试二进制,然后对tests/SelfTest中类似结构的测试用例直接传入上述命令行动参数运行观察。

小结

参数参数格式触发的行为过滤对象
-c, --section任意字符串(精确匹配 trimmed 名称)单独使用 = 旧行为;与-g/-p同用 = 新行为仅 Section,忽略 Generator
-g, --generator-index非负整数或*新行为Generator 元素(按索引)
-p, --path-filterc:<section>g:<index>新行为(即使只用于 Section)按路径层级混合过滤 Section 与 Generator

使用路径过滤时请始终牢记三条准则:被跳过 Section 之外的代码仍会执行;过滤器只作用于路径前缀;路径过滤独立于测试用例选择、会应用到所有选中测试用例。若 Generator 在某深度未通过过滤器,新行为下测试用例将被SKIP()跳过而非简单不进入——这是新旧行为最核心的语义分水岭。相关命令行的完整参数清单可参考 docs/command-line.md,Section 与 Generator 的基础用法见 docs/test-cases-and-sections.md 与 docs/generators.md。

【免费下载链接】Catch2A modern, C++-native, test framework for unit-tests, TDD and BDD - using C++14, C++17 and later (C++11 support is in v2.x branch, and C++03 on the Catch1.x branch)项目地址: https://gitcode.com/GitHub_Trending/ca/Catch2

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询