zeerd's blog         Search     Categories     Tags     Feed

闲来生雅趣,无事乐逍遥。对窗相望雪,一盏茶香飘。

在非编译环境中运行测试程序并获取正确的覆盖率报告

#GCC #Gcov @Program


Contents:

本文针对GCOV_PREFIXGCOV_PREFIX_STRIP的使用进行一些记录。

首先,准备一个简单的 C 语言程序作为测试目标。

#include <stdio.h>
#include <stdlib.h>

int foo(int a)
{
    return a * a;
}

int main(int argc, char const *argv[])
{
    printf("%d\n", foo(atoi(argv[1])));
    return 0;
}

在最简单的情况下,我们的情况会类似于下面的脚本。 即编译环境和运行环境都在一起。

这种情况下,只要参数设置没有问题,就可以简单的获取对应的覆盖率报告。

#!/bin/bash

gcc main.c -o test -lgcov -coverage
./test 2

lcov -c -d $(pwd) -o test.info
genhtml test.info -o doc

但是,很多情况下,我们都需要将测试程序移动到其他环境中运行。 比如下面的脚本模拟了在Docker中运行测试程序的情况。

简单的运行一下,我们会发现,覆盖率报告生成失败了。 原因是, lcov 找不到 .gcda 文件。

#!/bin/bash

gcc main.c -o test -lgcov -coverage

docker run -v $(pwd):/home/test \
    ubuntu:20.04 /home/test/test 2

lcov -c -d $(pwd) -o test.info
genhtml test.info -o doc

解决的方法也很简单。在目标环境中设置GCOV_PREFIXGCOV_PREFIX_STRIP这两个环境变量。 其中:

  • GCOV_PREFIX 是测试程序运行的位置。
  • GCOV_PREFIX_STRIP 是编译测试程序时,源代码所在路径的深度。
#!/bin/bash

gcc main.c -o test -lgcov -coverage

echo "#!/bin/bash" > startup.sh
echo "export GCOV_PREFIX=/home/test" >> startup.sh
echo "export GCOV_PREFIX_STRIP=\$(echo $(pwd) | awk -F\"/\" '{print NF-1}')" \
     >> startup.sh
echo "/home/test/test 2" >> startup.sh
chmod +x startup.sh
docker run -v $(pwd):/home/test -w /home/test \
    ubuntu:20.04 /home/test/startup.sh

lcov -c -d $(pwd) -o test.info
genhtml test.info -o doc

上述例子其实还不够精准的描述更加复杂的情况。 例如,当我们需要在多个Docker中并行运行同一段代码时(这种情况常见于动态链接库), 同时运行的程序会竞争.gcda文件。引发写冲突破坏.gcda文件。 为了避免类似的问题,我们可能需要将测试程序拷贝多份。

参照下面的脚本,我们将编译出来的测试程序移动到了源代码以外的路径(new),去执行 (脚本并没有模拟并行运行的情况,毕竟测试程序太简单了,一瞬间就退出了)。

这种情况下,直接执行测试程序,就会由于缺少信息而无法生成覆盖率报告。 这些信息其实是保存在.gcno文件中。

参照下面的脚本,将main.gcno同样复制到新的路径下,然后再次运行测试程序。 覆盖率报告可以顺利的生成了。

#!/bin/bash

gcc main.c -o test -lgcov -coverage
install -d new
install test new

echo "#!/bin/bash" > new/startup.sh
echo "export GCOV_PREFIX=/home/test" >> new/startup.sh
echo "export GCOV_PREFIX_STRIP=\$(echo $(pwd) | awk -F\"/\" '{print NF-1}')" \
     >> new/startup.sh
echo "/home/test/test 2" >> new/startup.sh
chmod +x new/startup.sh

cp main.gcno new/
docker run -v $(pwd)/new:/home/test/ -w /home/test \
    ubuntu:20.04 /home/test/startup.sh

lcov -c -d $(pwd)/new -o test.info
genhtml test.info -o doc

简单总结一下。

某工程代码的存放路径为/path/to/proj,其中有一个源文件存放在a/b.c。 编译生成的a.out转移到目标系统的/usr/bin下运行。

此时,需要进行如下工作:

  • /path/to/proj/a/b.gcno复制到目标环境的/usr/bin/a/b.gcno
  • 设置环境变量GCOV_PREFIX=/usr/bin
  • 设置环境变量GCOV_PREFIX_STRIP=3。(/path/to/proj是三层)。

运行时,a.out会根据调试信息去寻找/path/to/proj/a/b.gcno。 但是,由于设置了GCOV_PREFIX,这个目标会调整为/usr/bin/path/to/proj/a/b.gcno。 然后,由于设置了GCOV_PREFIX_STRIP,将/usr/bin/后面的三级路径删除,变成了 /usr/bin/a/b.gcno。 在这里,a.out找到了我们提前复制进去的b.gcno文件。 并在同样的位置生成/usr/bin/a/b.gcda文件。

有了.gcno.gcda文件,就可以生成测试报告了。