LCOV分支覆盖率,在std::map中放置/插入

lcov branch coverage with emplace/insert in std::map

提问人:musnow 提问时间:11/10/2023 最后编辑:musnow 更新时间:11/10/2023 访问量:80

问:

我正在使用 lcov 2.0 检查我的 gtest 分支覆盖率,但我遇到了许多由 STD 引起的分支,例如下面。emplace/insert/operator[]std::map

  • g++ (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
  • gtest 1.12.x
  • lcov/genhtml:LCOV 版本 2.0-1

这是我的源代码

// src/main.hpp
#include <cstdint>
#include <iostream>
#include <map>
#include <set>
#include <stdexcept>
using namespace std;

class mytestint
{
public:
    mytestint() = default;
    mytestint(uint16_t id, uint16_t pr) : _id(id), _pr(pr) {}

    uint16_t get_id() const
    {
        return _id;
    }

private:
    uint16_t _id;
    uint16_t _pr;
};

map<uint16_t, mytestint> _test_map;
map<uint16_t, uint16_t> _test_map_pod;

void test_emplace(uint16_t id, uint16_t pr)
{
    // map with class
    mytestint temp = {id, pr};
    auto t = _test_map.emplace(id, temp);
    auto k = _test_map.emplace(id, mytestint(id,pr));
    auto s = _test_map.insert({pr, temp});
    _test_map[id] = temp;
    auto h = _test_map[pr];
    // map with uint16_t
    _test_map_pod.emplace(id,pr);
    _test_map_pod.insert({pr,id});
    _test_map_pod[id] = pr;
}

这是我的gtest代码

#include "src/main.hpp"
#include <gtest/gtest.h>

TEST(MapTest, TestMap) {
    EXPECT_NO_THROW(test_emplace(1,1));
    EXPECT_NO_THROW(test_emplace(2,2));
    EXPECT_NO_THROW(test_emplace(3,3));
    EXPECT_NO_THROW(test_emplace(2,4));
}

int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

LCOV 将显示对这些函数的所有调用都有一个未处理的分支。

      92                 :           4 : void test_emplace(uint16_t id, uint16_t pr)
      93                 :             : {
      94                 :             :     // map with class
      95                 :           4 :     mytestint temp = {id, pr};
      96         [ +  - ]:           4 :     auto t = _test_map.emplace(id, temp);
      97         [ +  - ]:           4 :     auto k = _test_map.emplace(id, mytestint(id,pr));
      98         [ +  - ]:           4 :     auto s = _test_map.insert({pr, temp});
      99         [ +  - ]:           4 :     _test_map[id] = temp;
     100         [ +  - ]:           4 :     auto h = _test_map[pr];
     101                 :             :     // map with uint16_t
     102         [ +  - ]:           4 :     _test_map_pod.emplace(id,pr);
     103         [ +  - ]:           4 :     _test_map_pod.insert({pr,id});
     104         [ +  - ]:           4 :     _test_map_pod[id] = pr;
     105                 :           4 : }

这是我生成lcov报告的命令。

    g++ -std=c++17 test.cpp -o test -lgtest -lgtest_main -pthread -fprofile-arcs -ftest-coverage -fprofile-update=atomic && \
    ./test && \
    gcov -o . test.cpp && \
    lcov --capture \
         --rc branch_coverage=1 \
         --directory . \
         --output-file coverage_all.info \
         --ignore-errors mismatch && \
    lcov --remove coverage_all.info '*/usr/include/*' '*/usr/lib/*' '*/usr/lib64/*' \
                                    '*/usr/local/include/*' '*/usr/local/lib/*'     \
                                    '*/usr/local/lib64/*'   \
        --rc branch_coverage=1 \
        --output-file coverage.info \
        --ignore-errors unused \
        --ignore-errors mismatch && \
    genhtml coverage.info \
            --rc branch_coverage=1 \
            --output-directory coverage_report 

我想知道这些未被发现的树枝有什么解决方案吗?

这里未覆盖的分支与函数内部的异常之间是否存在关系?我应该如何在这些函数中引发异常?emplace/insert/operator[]


更新但有异常

我尝试在mytestint构造函数中解决异常

    // type of _pr already modify to int
    mytestint(uint16_t id, int pr) : _id(id), _pr(pr)
    {
        if (pr < 0)
        {
            throw std::runtime_error("pr < 0 ");
        }
    }

使用新的测试代码

TEST(MapTest, TestMap) {
    EXPECT_NO_THROW(test_emplace(1,1));

    EXPECT_ANY_THROW(test_emplace(2,-4));
    EXPECT_ANY_THROW(test_emplace(3,-50));
}

TEST(VetorTest, TestEmplace) {
    EXPECT_NO_THROW(test_vector_emplace(1,1));
    EXPECT_ANY_THROW(test_vector_emplace(2,-4));
    EXPECT_ANY_THROW(test_vector_emplace(3,-50));
}

LCOV结果是这样的。

     93                 :             : map<uint16_t, mytestint> _test_map;
      94                 :             : map<uint16_t, uint16_t> _test_map_pod;
      95                 :             : vector<mytestint> _test_vector;
      96                 :             : 
      97                 :             : 
      98                 :           3 : void test_emplace(uint16_t id, int pr)
      99                 :             : {
     100         [ +  - ]:           3 :     printf("%u | %d\n", id, pr);
     101                 :             :     // map with class
     102         [ +  - ]:           3 :     mytestint temp = {id, id};
     103         [ +  - ]:           3 :     auto t = _test_map.emplace(id, temp);
     104   [ +  +  +  - ]:           3 :     auto k = _test_map.emplace(id, mytestint(id, pr));
     105         [ +  - ]:           1 :     auto s = _test_map.insert({pr, temp});
     106         [ +  - ]:           1 :     _test_map[id] = temp;
     107         [ +  - ]:           1 :     auto h = _test_map[pr];
     108                 :             :     // map with uint16_t
     109         [ +  - ]:           1 :     _test_map_pod.emplace(id, pr);
     110         [ +  - ]:           1 :     _test_map_pod.insert({pr, id});
     111         [ +  - ]:           1 :     _test_map_pod[id] = pr;
     112                 :           1 : }
     113                 :             : 
     114                 :           3 : void test_vector_emplace(uint16_t id, int pr)
     115                 :             : {
     116         [ +  - ]:           3 :     mytestint temp = {id, id};
     117         [ +  - ]:           3 :     _test_vector.push_back(temp);
     118         [ +  + ]:           3 :     _test_vector.emplace_back(id, pr);
     119                 :           1 : }

在构造函数中抛出异常可以解决分支覆盖率,但无法解决vector.emplace_backmap.emplace;

我知道这是因为我曾经在 中构造一个匿名对象,而不是直接转发 emplace 的参数来构造 mytestint 对象。因此,此处的异常是在构造匿名 mytestint 对象期间引发的,而不是在 emplace 函数中引发的。mytestint(id,pr)map.emplace

尝试在 mytestint 中添加另一个构造函数

    mytestint(int pr) : _id(0), _pr(pr)
    {
        if (pr < 0)
        {
            throw std::runtime_error("pr < 0 ");
        }
    }

这些分支例外都包括在内!

   112         [ +  + ]:           3 :     auto kk = _test_map.emplace(id, pr); 
   129         [ +  + ]:           3 :     _test_vector.emplace_back(pr);

但我不知道如何抛出异常;看来我只能忽略这些未覆盖的分支。insert/push_back/operator[]insert/push_back/operator[]

C++ GoogleTest LCOV

评论

0赞 Peng Guanwen 11/10/2023
map::operator[]将调用 If Key is not found 的默认构造函数。 如果传递 rValue,将调用 copy 构造函数,如果传递 rValue,则调用 Move 构造函数。因此,您可以在这些构造函数中添加异常。mytestintmap::insert

答:

0赞 Peng Guanwen 11/10/2023 #1

你是对的,未覆盖的树枝是投掷路径。运行 gcov

gcov -b -c -o . test.cpp

您将在文件中看到如下内容.gcov

        4:   12:    auto t = _test_map.emplace(id, temp);
call    0 returned 4
branch  1 taken 4 (fallthrough)
branch  2 taken 0 (throw)

很难在这里手动引发异常。一种可能的解决方案是在 的构造函数中抛出异常。mytestint

评论

0赞 musnow 11/10/2023
谢谢,我在带有 emplace 和 operator[] 的 main.hpp.gcov 文件中得到了这个结果,并带有插入branch 2 taken 0 (throw)branch 3 taken 0 (throw)
0赞 musnow 11/10/2023
我试图在mytestint构造函数中添加一个抛掷,将_pr从uint16_t修改为int,抛出异常时。使用负数 pr 进行测试将添加另一个带有 to 行的分支_pr<0[++]auto k = _test_map.emplace(id, mytestint(id,pr));