- 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
# 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:
# 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
# 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
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)
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)
--- /dev/null
+#!/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))