From 3d05d90a65ebbe6adef4e9edbda18db48de31df5 Mon Sep 17 00:00:00 2001 From: Michael Droettboom Date: Wed, 13 Jul 2022 14:11:02 -0400 Subject: [PATCH] Refactor this as a Makefile target --- .github/workflows/c-coverage.yml | 19 +--- .gitignore | 1 + Makefile.pre.in | 44 ++++++++ ...2-07-13-13-24-01.gh-issue-94759.dTLMod.rst | 4 + configure | 100 ++++++++++++++++++ configure.ac | 2 + 6 files changed, 156 insertions(+), 14 deletions(-) create mode 100644 Misc/NEWS.d/next/Build/2022-07-13-13-24-01.gh-issue-94759.dTLMod.rst diff --git a/.github/workflows/c-coverage.yml b/.github/workflows/c-coverage.yml index 2f3718c515c5f1..814e95dfba8a96 100644 --- a/.github/workflows/c-coverage.yml +++ b/.github/workflows/c-coverage.yml @@ -40,26 +40,17 @@ jobs: uses: hendrikmuhs/ccache-action@v1 - name: Configure clang (with coverage) run: | - echo "CC=/usr/lib/ccache/clang-12 -fprofile-instr-generate -fcoverage-mapping" >> $GITHUB_ENV - echo "CXX=/usr/lib/ccache/clang++-12 -fprofile-instr-generate -fcoverage-mapping" >> $GITHUB_ENV + echo "CC=clang" >> $GITHUB_ENV + echo "CXX=clang++" >> $GITHUB_ENV - name: Configure CPython run: ./configure --with-pydebug --with-openssl=$OPENSSL_DIR - - name: Build CPython - run: make -j4 - - name: Collect coverage data - # Specify the LLVM_PROFILE_FILE using %m so multiple shared objects can write - # in parallel. Set the full path to the directory so results aren't written - # into temporary directories created by tests. - # Using "-j 1" is important, or the Github Action runs out of memory - run: LLVM_PROFILE_FILE=${PWD}/python%m.profraw xvfb-run ./python -m test -j 1 - name: Generate coverage report - run: | - llvm-profdata-12 merge -sparse python*.profraw -o python.profdata - llvm-cov-12 show -format=html -output-dir=cpython-coverage -instr-profile=python.profdata -show-branches=count -show-regions python . + # Using "-j1" is important, or the Github Action runs out of memory + run: EXTRATESTOPTS=-j1 xvfb-run make coverage-report-llvm - name: Publish coverage-report uses: JamesIves/github-pages-deploy-action@v4 with: - folder: cpython-coverage + folder: llvm-cov-report repository-name: '' # TODO Destination token: ${{ secrets.COVERAGE_DEPLOY_TOKEN }} # TODO: Use an organization-level token single-commit: true diff --git a/.gitignore b/.gitignore index 0ddfd717d737f8..592324ea731149 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ *.gc?? *.profclang? *.profraw +*.profdata *.dyn .gdb_history .purify diff --git a/Makefile.pre.in b/Makefile.pre.in index 77696fca940787..a5e30deed72b3f 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -51,6 +51,8 @@ PGO_PROF_USE_FLAG=@PGO_PROF_USE_FLAG@ LLVM_PROF_MERGER=@LLVM_PROF_MERGER@ LLVM_PROF_FILE=@LLVM_PROF_FILE@ LLVM_PROF_ERR=@LLVM_PROF_ERR@ +LLVM_PROFDATA=@LLVM_PROFDATA@ +LLVM_COV=@LLVM_COV@ DTRACE= @DTRACE@ DFLAGS= @DFLAGS@ DTRACE_HEADERS= @DTRACE_HEADERS@ @@ -321,6 +323,10 @@ COVERAGE_REPORT=$(abs_builddir)/lcov-report COVERAGE_LCOV_OPTIONS=--rc lcov_branch_coverage=1 COVERAGE_REPORT_OPTIONS=--rc lcov_branch_coverage=1 --branch-coverage --title "CPython $(VERSION) LCOV report [commit $(shell $(GITVERSION))]" +# report files for llvm-cov coverage report +COVERAGE_INFO_LLVM= $(abs_builddir)/coverage.profdata +COVERAGE_REPORT_LLVM=$(abs_builddir)/llvm-cov-report +COVERAGE_REPORT_OPTIONS_LLVM=-show-branches=count -show-regions # === Definitions added by makesetup === @@ -693,6 +699,44 @@ coverage-report: regen-token regen-frozen @ # build lcov report $(MAKE) coverage-lcov +# Compile and calculate coverage with llvm-cov +.PHONY=check-clang coverage-llvm coverage-profdata coverage-report-llvm + +# Check whether the compiler is clang, and if not, error out. +check-clang: + ($(CC) --version | grep clang) || \ + (echo "LLVM coverage only works with clang. Set CC=clang and CXX=clang++ and re-run ./configure"; exit 1) + +coverage-llvm: check-clang + @echo "Building with support for coverage checking:" + $(MAKE) clean + @ # Override CC rather than CFLAGS since these flags must come first + $(MAKE) @DEF_MAKE_RULE@ CC="$(CC) -fprofile-instr-generate -fcoverage-mapping" + +coverage-profdata: + @echo "Creating Coverage HTML report with llvm-profdata/llvm-cov:" + @rm -f $(COVERAGE_INFO_LLVM) + @rm -rf $(COVERAGE_REPORT_LLVM) + @ # Merge coverage results + $(LLVM_PROFDATA) merge -sparse python*.profraw -o $(COVERAGE_INFO_LLVM) + @ # Generate HTML + $(LLVM_COV) show -format=html -output-dir=$(COVERAGE_REPORT_LLVM) -instr-profile=$(COVERAGE_INFO_LLVM) $(COVERAGE_REPORT_OPTIONS_LLVM) python . + @echo + @echo "llvm-cov report at $(COVERAGE_REPORT_LLVM)/index.html" + @echo + +# Force regeneration of parser and importlib +# Specify the LLVM_PROFILE_FILE using %m so multiple shared objects can write +# in parallel. Set the full path to the directory so results aren't written +# into temporary directories created by tests. +coverage-report-llvm: regen-token regen-importlib + @ # build with coverage info + $(MAKE) coverage-llvm + @ # run tests, ignore failures + LLVM_PROFILE_FILE=${PWD}/python%m.profraw $(TESTRUNNER) $(TESTOPTS) || true + @ # build llvm-cov report + $(MAKE) coverage-profdata + # Run "Argument Clinic" over all source files .PHONY=clinic clinic: check-clean-src $(srcdir)/Modules/_blake2/blake2s_impl.c diff --git a/Misc/NEWS.d/next/Build/2022-07-13-13-24-01.gh-issue-94759.dTLMod.rst b/Misc/NEWS.d/next/Build/2022-07-13-13-24-01.gh-issue-94759.dTLMod.rst new file mode 100644 index 00000000000000..74a17daec2dbf9 --- /dev/null +++ b/Misc/NEWS.d/next/Build/2022-07-13-13-24-01.gh-issue-94759.dTLMod.rst @@ -0,0 +1,4 @@ +A new Makefile target ``coverage-report-llvm`` will use ``clang`` and +``llvm-cov`` to generate a coverage report. This provides more details about +branch coverage and subexpressions than the existing ``gcc`` and ``lcov`` +based ``coverage-report``. diff --git a/configure b/configure index bffc30a590fbe4..f2c787b1e63e58 100755 --- a/configure +++ b/configure @@ -882,6 +882,7 @@ CFLAGS_NODIST BASECFLAGS CFLAGS_ALIASING OPT +LLVM_COV LLVM_PROF_FOUND LLVM_PROFDATA LLVM_PROF_ERR @@ -7924,6 +7925,105 @@ case $CC in ;; esac +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}llvm-cov", so it can be a program name with args. +set dummy ${ac_tool_prefix}llvm-cov; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_LLVM_COV+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $LLVM_COV in + [\\/]* | ?:[\\/]*) + ac_cv_path_LLVM_COV="$LLVM_COV" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in ${llvm_path} +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_LLVM_COV="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +LLVM_COV=$ac_cv_path_LLVM_COV +if test -n "$LLVM_COV"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $LLVM_COV" >&5 +$as_echo "$LLVM_COV" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_path_LLVM_COV"; then + ac_pt_LLVM_COV=$LLVM_COV + # Extract the first word of "llvm-cov", so it can be a program name with args. +set dummy llvm-cov; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_path_ac_pt_LLVM_COV+:} false; then : + $as_echo_n "(cached) " >&6 +else + case $ac_pt_LLVM_COV in + [\\/]* | ?:[\\/]*) + ac_cv_path_ac_pt_LLVM_COV="$ac_pt_LLVM_COV" # Let the user override the test with a path. + ;; + *) + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in ${llvm_path} +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_ac_pt_LLVM_COV="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + + ;; +esac +fi +ac_pt_LLVM_COV=$ac_cv_path_ac_pt_LLVM_COV +if test -n "$ac_pt_LLVM_COV"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_pt_LLVM_COV" >&5 +$as_echo "$ac_pt_LLVM_COV" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_pt_LLVM_COV" = x; then + LLVM_COV="''" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + LLVM_COV=$ac_pt_LLVM_COV + fi +else + LLVM_COV="$ac_cv_path_LLVM_COV" +fi + + # XXX Shouldn't the code above that fiddles with BASECFLAGS and OPT be # merged with this chunk of code? diff --git a/configure.ac b/configure.ac index f940c9b2ddf001..b1ec4be8723a81 100644 --- a/configure.ac +++ b/configure.ac @@ -1957,6 +1957,8 @@ case $CC in LLVM_PROF_FILE="" ;; esac +AC_SUBST(LLVM_COV) +AC_PATH_TOOL(LLVM_COV, llvm-cov, '', ${llvm_path}) # XXX Shouldn't the code above that fiddles with BASECFLAGS and OPT be # merged with this chunk of code?