CI: Add ability to generate patch coverage reports

This commit is contained in:
Geza Lore 2025-10-04 16:19:29 +01:00
parent f09c30df35
commit 540e042221
3 changed files with 49 additions and 9 deletions

View File

@ -603,9 +603,16 @@ COVERAGE_DIR := obj_coverage
ifeq ($(CFG_WITH_DEV_GCOV),yes)
FASTCOV := nodist/fastcov.py
# Figure out base and head refs for coverage report
COVERAGE_REF_BASE := $(if $(COVERAGE_BASE),$(shell git rev-parse --short $(COVERAGE_BASE)))
COVERAGE_REF_HEAD := $(shell git rev-parse --short HEAD)
override undefine COVERAGE_BASE # Use the above variabels instead
COVERAGE_PATCH_FILE := $(COVERAGE_DIR)/$(if $(COVERAGE_REF_BASE),$(COVERAGE_REF_BASE).patch,.none.pach)
# 'fastcov' setup
FASTCOV := nodist/fastcov.py
FASTCOV_OPT := -j $(shell nproc)
FASTCOV_OPT += --lcov
FASTCOV_OPT += --process-gcno
FASTCOV_OPT += --branch-coverage
FASTCOV_OPT += --dump-statistic
@ -642,7 +649,9 @@ FASTCOV_OPT += BROKEN_BASE_RTN
FASTCOV_OPT += SELF_CHECK
FASTCOV_OPT += 'if (VL_UNCOVERABLE'
FASTCOV_OPT += '} else if (VL_UNCOVERABLE'
FASTCOV_OPT += $(if $(COVERAGE_REF_BASE),--diff-filter $(COVERAGE_PATCH_FILE))
# 'genhtml' setup
GENHTML := genhtml
GENHTML_OPT := -j $(shell nproc)
GENHTML_OPT += --branch-coverage
@ -650,26 +659,43 @@ GENHTML_OPT += --demangle-cpp
GENHTML_OPT += --rc branch_coverage=1
GENHTML_OPT += --rc genhtml_hi_limit=100
GENHTML_OPT += --ignore-errors negative
GENHTML_OPT += --header-title "Verilator code coverage report"
ifeq ($(COVERAGE_REF_BASE),)
GENHTML_OPT += --header-title "Code coverage for Verilator $(shell git describe --dirty)"
else
GENHTML_OPT += --header-title "Patch coverage for Verilator $(COVERAGE_REF_BASE)..$(COVERAGE_REF_HEAD)$(if $(git status --porcelain),,-dirty)"
endif
GENHTML_OPT += --flat
GENHTML_OPT += --precision 2
GENHTML_OPT += --legend
GENHTML_OPT += --show-proportion
GENHTML_OPT += --filter brace,blank,range
# There are loads (~20k combined), but using this seems fine on modern hardware
GCNO_FILES = $(shell find . -name '*.gcno')
GCDA_FILES = $(shell find . -name '*.gcda')
# Patch file to filter coverage with - unused if doing full coverage
$(COVERAGE_PATCH_FILE):
@mkdir -p $(COVERAGE_DIR)
@touch $@
$(if $(COVERAGE_REF_BASE), git diff $(COVERAGE_REF_BASE) > $(COVERAGE_PATCH_FILE))
# Combine all .gcda coverage date files into lcov .info file
$(COVERAGE_DIR)/verilator.info: $(GCNO_FILES) $(GCDA_FILES)
$(COVERAGE_DIR)/verilator.info: $(COVERAGE_PATCH_FILE) $(GCNO_FILES) $(GCDA_FILES)
@echo "####################################################################"
@echo "# fastcov: combining all .gcda files into lcov .info"
@echo "####################################################################"
mkdir -p $(COVERAGE_DIR)
/usr/bin/time -f "That took %E" \
$(FASTCOV) $(FASTCOV_OPT) --lcov --output $@
$(FASTCOV) $(FASTCOV_OPT) --output $@
@# Uncommitted changes not tracked, force rebuild on next run if patch coverage
@$(if $(COVERAGE_REF_BASE),rm $(COVERAGE_PATCH_FILE))
# Build coverage report
$(COVERAGE_DIR)/report/index.html: $(COVERAGE_DIR)/verilator.info
@echo "####################################################################"
@echo "# genhtml: Generating coverage report"
@echo "####################################################################"
@rm -rf $(COVERAGE_DIR)/reprot
/usr/bin/time -f "That took %E" \
$(GENHTML) $(GENHTML_OPT) --output-directory $(COVERAGE_DIR)/report $^

View File

@ -1695,6 +1695,15 @@ Be aware if changing a test, if that test no longer covers some item, the
report will still contain the old coverage. Use ``make coverage-zero`` and
rerun all tests if this is a concern.
It is also possible to generate a 'patch coverage' report, which will only
contain information about lines modified compared to a Git ref specified by the
``COVERAGE_BASE`` Make variable (including uncommitted changes). For example,
to see coverage of changes compared to upstream, use:
.. code:: shell
make coverage-view COVERAGE_BASE=origin/master
Fuzzing
-------

View File

@ -509,6 +509,13 @@ def containsMarker(markers, strBody):
def exclProcessSource(fastcov_sources, source, exclude_branches_sw, include_branches_sw, exclude_line_marker, fallback_encodings, gcov_prefix, gcov_prefix_strip):
source_to_open = processPrefix(source, gcov_prefix, gcov_prefix_strip)
# Before doing any work, check if this file even needs to be processed
if not exclude_branches_sw and not include_branches_sw:
# Ignore unencodable characters
with open(source_to_open, errors="ignore") as f:
if not containsMarker(exclude_line_marker + ["LCOV_EXCL"], f.read()):
return False
# If we've made it this far we have to check every line
start_line = 0
@ -526,10 +533,8 @@ def exclProcessSource(fastcov_sources, source, exclude_branches_sw, include_bran
if del_exclude_br or del_include_br:
del fastcov_data["branches"][i]
lineIsClosingBrace = line.strip() == "}"
# Skip to next line as soon as possible
if not containsMarker(exclude_line_marker + ["LCOV_EXCL"], line) and not lineIsClosingBrace:
if not containsMarker(exclude_line_marker + ["LCOV_EXCL"], line):
continue
# Build line to function dict so can quickly delete by line number
@ -540,7 +545,7 @@ def exclProcessSource(fastcov_sources, source, exclude_branches_sw, include_bran
line_to_func[l] = set()
line_to_func[l].add(f)
if lineIsClosingBrace or any(marker in line for marker in exclude_line_marker):
if any(marker in line for marker in exclude_line_marker):
for key in ["lines", "branches"]:
if i in fastcov_data[key]:
del fastcov_data[key][i]