From: Magnus Jacobsson Date: Mon, 19 Apr 2021 15:35:07 +0000 (+0200) Subject: enable lcov branch coverage X-Git-Tag: 2.48.0~4^2~2 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=765536f3ee29ce96e40d5eda61aecfdf508eb670;p=graphviz enable lcov branch coverage --- diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2b7ccf842..277bb6c4a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -236,6 +236,7 @@ portable-source: - ci/install-packages.sh - export GV_VERSION=$( cat VERSION ) - export OS_ID=$( cat OS_ID ) + - ci/test_coverage.py --init - pushd build # FIXME: remove detect_odr_violation=0 when https://gitlab.com/graphviz/graphviz/-/issues/2096 has been fixed - export ASAN_OPTIONS=detect_odr_violation=0 @@ -245,7 +246,10 @@ portable-source: # memory leak detections. Disable those for now. - export ASAN_OPTIONS=detect_leaks=0 - python3 -m pytest --verbose --junitxml=report.xml ci/tests.py tests rtest + - ci/test_coverage.py --analyze artifacts: + paths: + - coverage/** reports: junit: ./report.xml except: @@ -490,7 +494,7 @@ ubuntu21-04-cmake-ASan-build-for-ctest: # fail on any compiler warnings - export CFLAGS=-Werror - export CXXFLAGS=-Werror - - export CMAKE_OPTIONS="-Duse_sanitizers=ON -Dwith_cxx_tests=ON " + - export CMAKE_OPTIONS="-Duse_sanitizers=ON -Duse_coverage=ON -Dwith_cxx_tests=ON " - echo experimental > COLLECTION # override the deb_build_definition artifacts since we need more # files from the build directory when running ctest in the test @@ -507,6 +511,8 @@ ubuntu21-04-cmake-ASan-build-for-ctest: # them. - build/lib/**/lib*.so.* - build/plugin/**/lib*.so.* + # the coverage analysis needs the .gcno files + - build/**/*.gcno # the packages need to be installed for the post-install tests - Packages/*/*/*/*/*/*deb - Metadata/*/*/*/configure.log diff --git a/CMakeLists.txt b/CMakeLists.txt index 1df011e2f..ab611fcfe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ option(with_ortho "ORTHO features in neato layout engine." ON ) option(with_sfdp "sfdp layout engine." ON ) option(with_smyrna "SMYRNA large graph viewer (disabled by default - experimental)" OFF) option(use_sanitizers "enables using address and undefined behavior sanitizer" OFF) +option(use_coverage "enables analyzing code coverage" OFF) option(with_cxx_tests "enables building the C++ tests" OFF) if (enable_ltdl) @@ -220,6 +221,15 @@ if (use_sanitizers) endif() endif() +if (use_coverage) + add_compile_options("-coverage") + if (${CMAKE_VERSION} VERSION_LESS "3.16.0") + link_libraries("-coverage") + else() + add_link_options("-coverage") + endif() +endif() + # ============================ Packaging information =========================== include(InstallRequiredSystemLibraries) include(package_info) diff --git a/ci/test_coverage.py b/ci/test_coverage.py new file mode 100755 index 000000000..74c3b029a --- /dev/null +++ b/ci/test_coverage.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 + +"""Graphviz test coverage analysis script""" + +import argparse +import logging +import os +import subprocess +import sys +from typing import List + +# logging output stream, setup in main() +log = None + + +def main(args: List[str]) -> int: # pylint: disable=C0116 + # setup logging to print to stderr + global log + ch = logging.StreamHandler() + log = logging.getLogger("test_coverage.py") + log.addHandler(ch) + + # parse command line arguments + parser = argparse.ArgumentParser( + description="Graphviz test coverage analysis script") + parser.add_argument("--init", action="store_true", + help="Capture initial zero coverage data before running any test") + parser.add_argument("--analyze", action="store_true", + help="Analyze test coverage after running tests") + options = parser.parse_args(args[1:]) + + if not options.init and not options.analyze: + log.error( + "Must specify --init or --analyze; refusing to run") + return -1 + + cwd = os.getcwd() + + generated_files = [ + f"{cwd}/build/cmd/tools/gmlparse.c", + f"{cwd}/build/cmd/tools/gmlscan.c", + f"{cwd}/build/lib/cgraph/grammar.c", + f"{cwd}/build/lib/cgraph/scan.c", + f"{cwd}/build/lib/common/htmlparse.c", + f"{cwd}/build/lib/expr/y.tab.c", + ] + + excluded_files = generated_files + + exclude_options = [arg for exclude_option in [ + ["--exclude", f] for f in excluded_files] for arg in exclude_option] + + if options.init: + subprocess.check_call( + ["lcov", "--capture", "--initial", "--directory", ".", "--rc", + "lcov_branch_coverage=1", "--no-external"] + exclude_options + + ["--output-file", "app_base.info"]) + + return 0 + + if options.analyze: + # capture test coverage data + subprocess.check_call( + ["lcov", "--capture", "--directory", ".", "--rc", "lcov_branch_coverage=1", + "--no-external"] + exclude_options + ["--output-file", "app_test.info"]) + # combine baseline and test coverage data + subprocess.check_call( + ["lcov", "--rc", "lcov_branch_coverage=1", "--add-tracefile", + "app_base.info", "-add-tracefile", "app_test.info", "--output-file", "app_total.info"]) + # generate coverage html pages using lcov which are nicer than gcovr's + os.makedirs("coverage/lcov", exist_ok=True) + subprocess.check_call( + ["genhtml", "--prefix", cwd, "--rc", "lcov_branch_coverage=1", + "--output-directory", "coverage/lcov", "--show-details", "app_total.info"]) + + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv))