Skip to content

Commit

Permalink
add support for python-based coverage (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikeferguson committed Apr 2, 2020
1 parent 2f1142d commit 2c9cd83
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 12 deletions.
36 changes: 36 additions & 0 deletions README.md
Expand Up @@ -44,3 +44,39 @@ endif()
```

* The output will print where the coverage report is located

## Python rostest Support

While the C++ interface and Python-based unit tests require no
modification to get coverage information, Python-based nodes
run from rostest launch files need a bit of additional
instrumentation turned on:

```
<launch>
<!-- Add an argument to the launch file to turn on coverage -->
<arg name="coverage" default="false"/>
<!-- This fancy line forces nodes to generate coverage -->
<arg name="pythontest_launch_prefix" value="$(eval 'python-coverage run -p' if arg('coverage') else '')"/>
<!-- This node will NOT generate coverage information -->
<node pkg="example_pkg" name="publisher_node" type="publisher_node.py" />
<!-- But this node WILL generate coverage -->
<node pkg="example_pkg" name="subscriber_node" type="subscriber_node.py"
launch-prefix="$(arg pythontest_launch_prefix)" />
<!-- The test can also generate coverage information if you include the launch-prefix -->
<test time-limit="10" test-name="sample_rostest" pkg="example_pkg" type="sample_rostest.py"
launch-prefix="$(arg pythontest_launch_prefix)" />
</launch>
```

In the CMakeLists, you will need to pass this argument:

```
add_rostest(example_rostest.test ARGS coverage:=ENABLE_COVERAGE_TESTING)
```
63 changes: 51 additions & 12 deletions cmake/Modules/CodeCoverage.cmake
Expand Up @@ -126,30 +126,60 @@ function(ADD_CODE_COVERAGE)
message(FATAL_ERROR "genhtml not found! Aborting...")
endif() # NOT GENHTML_PATH

# Setup target
add_custom_target(${Coverage_NAME}_cleanup
# Determine directory to store python coverage files
set(COVERAGE_DIR $ENV{HOME}/.ros)
if(DEFINED ENV{ROS_HOME})
set(COVERAGE_DIR $ENV{ROS_HOME})
endif()

# Cleanup C++ counters
add_custom_target(${Coverage_NAME}_cleanup_cpp
# Cleanup lcov
COMMAND ${LCOV_PATH} --directory . --zerocounters
# Create baseline to make sure untouched files show up in the report
COMMAND ${LCOV_PATH} -c -i -d . -o ${Coverage_NAME}.base
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
DEPENDS ${Coverage_DEPENDENCIES}
COMMENT "Resetting code coverage counters to zero."
COMMENT "Resetting CPP code coverage counters to zero."
)

add_custom_target(${Coverage_NAME}
# Cleanup python counters
add_custom_target(${Coverage_NAME}_cleanup_py
COMMAND python-coverage erase
WORKING_DIRECTORY ${COVERAGE_DIR}
COMMENT "Resetting PYTHON code coverage counters to zero."
)

# Cleanup before we run tests
add_dependencies(_run_tests_${PROJECT_NAME} ${Coverage_NAME}_cleanup_cpp)
add_dependencies(_run_tests_${PROJECT_NAME} ${Coverage_NAME}_cleanup_py)

# Create C++ coverage report
add_custom_target(${Coverage_NAME}_cpp
# Capturing lcov counters and generating report
COMMAND ${LCOV_PATH} --directory . --capture --output-file ${Coverage_NAME}.info
# add baseline counters
COMMAND ${LCOV_PATH} -a ${Coverage_NAME}.base -a ${Coverage_NAME}.info --output-file ${Coverage_NAME}.total
COMMAND ${LCOV_PATH} --remove ${Coverage_NAME}.total ${COVERAGE_EXCLUDES} --output-file ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.removed
COMMAND ${LCOV_PATH} --extract ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.removed "'*/${PROJECT_NAME}/*'" --output-file ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned
COMMAND ${GENHTML_PATH} -o ${Coverage_NAME} ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned
COMMAND ${CMAKE_COMMAND} -E remove ${Coverage_NAME}.base ${Coverage_NAME}.total

COMMAND ${LCOV_PATH} -a ${Coverage_NAME}.base -a ${Coverage_NAME}.info --output-file ${Coverage_NAME}.total || (exit 0)
COMMAND ${LCOV_PATH} --remove ${Coverage_NAME}.total ${COVERAGE_EXCLUDES} --output-file ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.removed || (exit 0)
COMMAND ${LCOV_PATH} --extract ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.removed "'*/${PROJECT_NAME}/*'" --output-file ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned || (exit 0)
COMMAND ${GENHTML_PATH} -o ${Coverage_NAME} ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info.cleaned || (exit 0)
COMMAND ${CMAKE_COMMAND} -E remove ${Coverage_NAME}.base ${Coverage_NAME}.total || (exit 0)
WORKING_DIRECTORY ${PROJECT_BINARY_DIR}
DEPENDS ${Coverage_NAME}_cleanup
DEPENDS _run_tests_${PROJECT_NAME}
)

# Create Python coverage report
add_custom_target(${Coverage_NAME}_py
COMMAND cp ${PROJECT_BINARY_DIR}/.coverage ${COVERAGE_DIR}/.coverage.nosetests || echo "WARNING: No nosetest coverage!"
COMMAND python-coverage combine || echo "WARNING: No python coverage to combine!"
COMMAND python-coverage xml || echo "WARNING: No python xml to output"
WORKING_DIRECTORY ${COVERAGE_DIR}
DEPENDS _run_tests_${PROJECT_NAME}
)

add_custom_target(${Coverage_NAME}
DEPENDS ${Coverage_NAME}_cpp
DEPENDS ${Coverage_NAME}_py
COMMENT "Processing code coverage counters and generating report."
)

Expand All @@ -159,17 +189,26 @@ function(ADD_CODE_COVERAGE)
COMMENT "Lcov code coverage info report saved in ${PROJECT_BINARY_DIR}/${Coverage_NAME}.info."
)

# Show info where to find the report
# Show info where to find the C++ report
add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
COMMAND ;
COMMENT "Open ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html in your browser to view the coverage report."
)

# Show info where to find the Python report
add_custom_command(TARGET ${Coverage_NAME} POST_BUILD
COMMAND ;
COMMENT "Python code coverage info saved in ${COVERAGE_DIR} directory."
)

endfunction() # SETUP_TARGET_FOR_COVERAGE

function(APPEND_COVERAGE_COMPILER_FLAGS)
# Set flags for all C++ builds
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE)
# Turn on coverage in python nosetests (see README for requirements on rostests)
set(ENV{CATKIN_TEST_COVERAGE} "1")
message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}")
endfunction() # APPEND_COVERAGE_COMPILER_FLAGS

Expand Down
2 changes: 2 additions & 0 deletions package.xml
Expand Up @@ -14,6 +14,8 @@
<buildtool_depend>catkin</buildtool_depend>

<build_depend>lcov</build_depend>
<build_depend>python-coverage</build_depend>
<run_depend>lcov</run_depend>
<run_depend>python-coverage</run_depend>

</package>

0 comments on commit 2c9cd83

Please sign in to comment.