code_coverage icon indicating copy to clipboard operation
code_coverage copied to clipboard

Question for branch coverage

Open Chris7462 opened this issue 3 years ago • 0 comments

Hi, I have a question regarding branch coverage. I have turned on the branch coverage in the config file `/etc/lcovrc'

# Specify if branch coverage data should be collected and processed.
lcov_branch_coverage = 1

The sample code I'm testing does not have any if statement, so I was expecting the branch coverage to be 100%. But, the result is not what I expected.

image image For some reason, some lines that do not belong to the branches are treated as branch statements.

Here's the minimum working example. Testing environment: Ubuntu 20.04.4 LTS, LCOV 1.14, and GCOV 9.04.

The sample_pkg_node subscribes to topic received_topic and publishes to topic sending_topic, both of them having std_msg::String type.

sample_pkg/src/sample_pkg_node.cpp

#include <ros/ros.h>
#include <std_msgs/String.h>

class SamplePkg
{
  public:
    SamplePkg(ros::NodeHandle& node) {
      subscriber_ = node.subscribe("received_topic", 100, &SamplePkg::sub_callback, this);
      publisher_ = node.advertise<std_msgs::String>("sending_topic", 100, true);
    };
    ~SamplePkg() = default;

  private:
    ros::Subscriber subscriber_;
    ros::Publisher publisher_;

    void sub_callback(const std_msgs::String::ConstPtr& input_msg) {
      std_msgs::String output_msg;
      output_msg.data = input_msg->data + " -- output";
      ROS_INFO_STREAM("Publish: " << output_msg.data);
      publisher_.publish(output_msg);
    }
};

int main(int argc, char** argv)
{
  ros::init(argc, argv, "sample_pkg_node");
  ros::NodeHandle node;

  SamplePkg sample_pkg(node);

  ros::spin();

  return 0;
}

The sample_pkg_test_node is the reverse of the sample_pkg_node, which subscribes to sending_topic and publishes to received_topic.

sample_pkg/test/sample_pkg_test.cpp

#include <ros/ros.h>
#include <gtest/gtest.h>
#include <std_msgs/String.h>

class SamplePkgTest: public ::testing::Test
{
  public:
    SamplePkgTest()
      : node_handle_(),
        publisher_(node_handle_.advertise<std_msgs::String>("received_topic", 5, true)),
        subscriber_(node_handle_.subscribe("sending_topic", 5, &SamplePkgTest::Callback, this)),
        message_received_(false)
    {
    }

    /*
     * This is necessary because it takes a while for the node under
     * test to start up.
     */
    void SetUp() override {
      while (!IsNodeReady()) {
        ros::spinOnce();
      }
    }

    void TearDown() override {
    }

    void Publish(std_msgs::String& msg) {
      publisher_.publish(msg);
    }

    /*
     * This is necessary because it takes time for messages from the
     * node under test to reach this node.
     */
    boost::shared_ptr<const std_msgs::String> WaitForMessage() {
      // The second parameter is a timeout duration.
      return ros::topic::waitForMessage<std_msgs::String>(
        subscriber_.getTopic(), node_handle_, ros::Duration(1));
    }

  private:
    bool message_received_;
    ros::NodeHandle node_handle_;
    ros::Publisher publisher_;
    ros::Subscriber subscriber_;

    /*
     * This callback is a no-op because we get the messages from the
     * node under test using WaitForMessage().
     */
    void Callback(const std_msgs::String& event) {
      message_received_ = true;
    }

    /*
     * See SetUp method.
     */
    bool IsNodeReady() {
      return (publisher_.getNumSubscribers() > 0) && (subscriber_.getNumPublishers() > 0);
    }
};

TEST_F(SamplePkgTest, SampleTest) {
  std_msgs::String msg;
  msg.data = "Test";
  Publish(msg);
  auto output = WaitForMessage();
  ASSERT_TRUE(output != NULL);
  EXPECT_EQ("Test -- output", output->data);
}

sample_pkg/test/main_test.cpp

#include <ros/ros.h>
#include <gtest/gtest.h>

int main(int argc, char** argv)
{
  testing::InitGoogleTest(&argc, argv);
  ros::init(argc, argv, "sample_pkg_test_node");

  return RUN_ALL_TESTS();
}

sample_pkg/test/sample_pkg_test.test

<?xml version="1.0"?>

<launch>
  <node name="sample_pkg_node" pkg="sample_pkg" type="sample_pkg_node"/>
  <test test-name="sample_pkg_test_node" pkg="sample_pkg" type="sample_pkg_test_node"/>
</launch>

I have followed the README to setup the CMakeLists.txt as well as the package.xml.

sample_pkg/CMakeLists.txt

cmake_minimum_required(VERSION 3.0.2)
project(sample_pkg)

find_package(catkin REQUIRED COMPONENTS
  roscpp
  std_msgs
)

catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES sample_pkg
  CATKIN_DEPENDS roscpp std_msgs
#  DEPENDS system_lib
)

if(CATKIN_ENABLE_TESTING AND ENABLE_COVERAGE_TESTING)
  find_package(code_coverage REQUIRED)
  # Add compiler flags for coverage instrumentation before defining any targets
  APPEND_COVERAGE_COMPILER_FLAGS()
endif()

include_directories(
  #include
  ${catkin_INCLUDE_DIRS}
)

add_executable(${PROJECT_NAME}_node
  src/sample_pkg_node.cpp
)

target_link_libraries(${PROJECT_NAME}_node
  ${catkin_LIBRARIES}
)

if(CATKIN_ENABLE_TESTING)
  find_package(rostest REQUIRED)

  add_rostest_gtest(sample_pkg_test_node
    test/sample_pkg_test.test
    test/sample_pkg_test.cpp
    test/main_test.cpp
  )

  target_link_libraries(sample_pkg_test_node
    ${catkin_LIBRARIES}
  )

  # Create a target ${PROJECT_NAME}_coverage_report
  if(ENABLE_COVERAGE_TESTING)
    set(COVERAGE_EXCLUDES "*/${PROJECT_NAME}/test*" "*/${PROJECT_NAME}/other_dir_i_dont_care_about*")
    add_code_coverage(
      NAME ${PROJECT_NAME}_coverage_report
      DEPENDENCIES tests
    )
  endif()
endif()

sample_pkg/package.xml

<?xml version="1.0"?>
<package format="2">
  <name>sample_pkg</name>
  <version>0.0.0</version>
  <description>The sample_pkg package</description>

  <maintainer email="[email protected]">yi-chen</maintainer>

  <license>TODO</license>

  <buildtool_depend>catkin</buildtool_depend>
  <build_depend>roscpp</build_depend>
  <build_depend>std_msgs</build_depend>
  <build_export_depend>roscpp</build_export_depend>
  <build_export_depend>std_msgs</build_export_depend>
  <exec_depend>roscpp</exec_depend>
  <exec_depend>std_msgs</exec_depend>

  <test_depend>rostest</test_depend>
  <test_depend>code_coverage</test_depend>

  <!-- The export tag contains other, unspecified, tags -->
  <export>
    <!-- Other tools can request additional information be placed here -->

  </export>
</package>

Then, the package is built by the following commands:

  catkin config --cmake-args -DENABLE_COVERAGE_TESTING=ON -DCMAKE_BUILD_TYPE=Debug
  catkin build sample_pkg
  catkin build sample_pkg -v --no-deps --catkin-make-args sample_pkg_coverage_report 

I don't know which part I was doing it wrong. Any insight would be appreciated. Thank you.

Chris7462 avatar Jul 19 '22 21:07 Chris7462