1

最近我了解了GENERATECatch2 中的宏(来自此视频)。现在我很好奇它是如何在内部工作的。

天真地认为,对于带有生成器的测试用例k(通过生成器,我的意思是一个GENERATE调用站点),Catch2 只运行每个测试用例n1 * n2 * ... * nk时间,其中nii第 -th 生成器中元素的数量,每次指定不同的值组合从那些k发电机。事实上,这个幼稚的规范似乎适用于一个简单的测试用例:

TEST_CASE("Naive") {
    auto x = GENERATE(0, 1);
    auto y = GENERATE(2, 3);
    std::cout << "x = " << x << ", y = " << y << std::endl;
}

正如预期的那样,输出是:

x = 0, y = 2
x = 0, y = 3
x = 1, y = 2
x = 1, y = 3

这表明测试用例运行了2 * 2 == 4多次。

但是,catch 似乎并没有天真地实现它,如下例所示:

TEST_CASE("Depends on if") {
    auto choice = GENERATE(0, 1);
    int x = -1, y = -1;
    if (choice == 0) {
        x = GENERATE(2, 3);
    } else {
        y = GENERATE(4, 5);
    }
    std::cout << "choice = " << choice << ", x = " << x << ", y = " << y << std::endl;
}

在上述情况下,实际调用(不是调用点)GENERATE取决于choice. 如果逻辑实现得天真,人们会期望有 8 行输出(因为2 * 2 * 2 == 8):

choice = 0, x = 2, y = -1
choice = 0, x = 2, y = -1
choice = 0, x = 3, y = -1
choice = 0, x = 3, y = -1
choice = 1, x = -1, y = 4
choice = 1, x = -1, y = 4
choice = 1, x = -1, y = 5
choice = 1, x = -1, y = 5

请注意重复的行:即使未实际调用生成器,天真的置换仍然会置换生成器的值。例如,y = GENERATE(4, 5)仅在 时调用choice == 1,但是,即使在 时choice != 1,实现仍会置换值 4 和 5,即使未使用这些值。

但是,实际输出是:

choice = 0, x = 2, y = -1
choice = 0, x = 3, y = -1
choice = 1, x = -1, y = 4
choice = 1, x = -1, y = 5

没有重复的行。这让我怀疑 Catch 在内部使用堆栈来跟踪调用的生成器及其最近调用的顺序。每次测试用例完成一次迭代时,它会以相反的顺序遍历调用的生成器,并推进每个生成器的值。如果这种推进失败(即生成器内的值序列完成),则该生成器将重置为其初始状态(即准备好按顺序发出第一个值);否则(推进成功),遍历退出。

在伪代码中,它看起来像:

for each generator that is invoked in reverse order of latest invocation:
  bool success = generator.moveNext();
  if success: break;
  generator.reset();

这完美地解释了前面的案例。但它并没有解释这个(相当模糊的)一个:

TEST_CASE("Non structured generators") {
    int x = -1, y = -1;
    for (int i = 0; i <= 1; ++i) {
        x = GENERATE(0, 1);
        if (i == 1) break;
        y = GENERATE(2, 3);
    }
    std::cout << x << "," << y << std::endl;
}

人们会期望它运行4 == 2 * 2时间,并且输出是:

x = 0, y = 2
x = 1, y = 2
x = 0, y = 3
x = 1, y = 3

x之前的更改yx = GENERATE(0, 1)最后一次调用的生成器)

然而,这不是 catch实际所做的,这是现实中发生的:

x = 0, y = 2
x = 1, y = 2
x = 0, y = 3
x = 1, y = 3
x = 0, y = 2
x = 1, y = 2
x = 0, y = 3
x = 1, y = 3

8行输出,也就是前四行重复了两次。

所以我的问题是,GENERATECatch2 究竟是如何实现的?我并不是特别在寻找详细的代码,而是一个可以解释我在前面的示例中看到的内容的高级描述。

4

1 回答 1

1

也许您可以尝试使用 GCC 中的 -E 选项查看预处理器后生成的代码。

交流:

GENERATE(0,1)

gcc -E -CC a.c

如何让 G++ 预处理器在宏中输出换行符?

于 2021-02-07T18:34:09.167 回答