diff options
author | Jørgen Kvalsvik <j@lambda.is> | 2024-03-29 13:01:37 +0100 |
---|---|---|
committer | Jørgen Kvalsvik <j@lambda.is> | 2024-07-11 09:13:16 +0200 |
commit | 1e43ea7bb3598e3ee19119c54c69a7c4b8745d0f (patch) | |
tree | a386dd6db13eb8ac99c42d53040a9378a7d0b8c8 /gcc/testsuite | |
parent | 2b3fbac8e37384857cd594c0800fccd99e4d39a1 (diff) | |
download | gcc-1e43ea7bb3598e3ee19119c54c69a7c4b8745d0f.zip gcc-1e43ea7bb3598e3ee19119c54c69a7c4b8745d0f.tar.gz gcc-1e43ea7bb3598e3ee19119c54c69a7c4b8745d0f.tar.bz2 |
Add function filtering to gcov
Add the --include and --exclude flags to gcov to control what functions
to report on. This is meant to make gcov more practical as an when
writing test suites or performing other coverage experiments, which
tends to focus on a few functions at the time. This really shines in
combination with the -t/--stdout flag. With support for more expansive
metrics in gcov like modified condition/decision coverage (MC/DC) and
path coverage, output quickly gets overwhelming without filtering.
The approach is quite simple: filters are egrep regexes and are
evaluated left-to-right, and the last filter "wins", that is, if a
function matches an --include and a subsequent --exclude, it should not
be included in the output. All of the output machinery works on the
function table, so by optionally (not) adding function makes the even
the json output work as expected, and only minor changes are needed to
suppress the filtered-out functions.
Demo: math.c
int mul (int a, int b) {
return a * b;
}
int sub (int a, int b) {
return a - b;
}
int sum (int a, int b) {
return a + b;
}
Plain matches:
$ gcov -t math --include=sum
-: 0:Source:math.c
-: 0:Graph:math.gcno
-: 0:Data:-
-: 0:Runs:0
#####: 9:int sum (int a, int b) {
#####: 10: return a + b;
-: 11:}
$ gcov -t math --include=mul
-: 0:Source:math.c
-: 0:Graph:math.gcno
-: 0:Data:-
-: 0:Runs:0
#####: 1:int mul (int a, int b) {
#####: 2: return a * b;
-: 3:}
Regex match:
$ gcov -t math --include=su
-: 0:Source:math.c
-: 0:Graph:math.gcno
-: 0:Data:-
-: 0:Runs:0
#####: 5:int sub (int a, int b) {
#####: 6: return a - b;
-: 7:}
#####: 9:int sum (int a, int b) {
#####: 10: return a + b;
-: 11:}
And similar for exclude:
$ gcov -t math --exclude=sum
-: 0:Source:math.c
-: 0:Graph:math.gcno
-: 0:Data:-
-: 0:Runs:0
#####: 1:int mul (int a, int b) {
#####: 2: return a * b;
-: 3:}
#####: 5:int sub (int a, int b) {
#####: 6: return a - b;
-: 7:}
And json, for good measure:
$ gcov -t math --include=sum --json | jq ".files[].lines[]"
{
"line_number": 9,
"function_name": "sum",
"count": 0,
"unexecuted_block": true,
"block_ids": [],
"branches": [],
"calls": []
}
{
"line_number": 10,
"function_name": "sum",
"count": 0,
"unexecuted_block": true,
"block_ids": [
2
],
"branches": [],
"calls": []
}
Matching generally work well for mangled names, as the mangled names
also have the base symbol name in it. By default, functions are matched
by the mangled name, which means matching on base names always work as
expected. The -M flag makes the matching work on the demangled name
which is quite useful when you only want to report on specific
overloads and can use the full type names.
Why not just use grep? grep is not really sufficient as grep is very
line oriented, and the reports that benefit the most from filtering
often unpredictably span multiple lines based on the state of coverage.
For example, a condition coverage report for 3 terms/6 outcomes only
outputs 1 line when all conditions are covered, and 7 with no lines
covered.
gcc/ChangeLog:
* doc/gcov.texi: Add --include, --exclude, --match-on-demangled
documentation.
* gcov.cc (struct fnfilter): New.
(print_usage): Add --include, --exclude, -M,
--match-on-demangled.
(process_args): Likewise.
(release_structures): Release filters.
(read_graph_file): Only add function_infos matching filters.
(output_lines): Likewise.
gcc/testsuite/ChangeLog:
* lib/gcov.exp: Add filtering test function.
* g++.dg/gcov/gcov-19.C: New test.
* g++.dg/gcov/gcov-20.C: New test.
* g++.dg/gcov/gcov-21.C: New test.
* gcc.misc-tests/gcov-25.c: New test.
* gcc.misc-tests/gcov-26.c: New test.
* gcc.misc-tests/gcov-27.c: New test.
* gcc.misc-tests/gcov-28.c: New test.
Diffstat (limited to 'gcc/testsuite')
-rw-r--r-- | gcc/testsuite/g++.dg/gcov/gcov-19.C | 35 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/gcov/gcov-20.C | 38 | ||||
-rw-r--r-- | gcc/testsuite/g++.dg/gcov/gcov-21.C | 32 | ||||
-rw-r--r-- | gcc/testsuite/gcc.misc-tests/gcov-25.c | 25 | ||||
-rw-r--r-- | gcc/testsuite/gcc.misc-tests/gcov-26.c | 25 | ||||
-rw-r--r-- | gcc/testsuite/gcc.misc-tests/gcov-27.c | 24 | ||||
-rw-r--r-- | gcc/testsuite/gcc.misc-tests/gcov-28.c | 22 | ||||
-rw-r--r-- | gcc/testsuite/lib/gcov.exp | 53 |
8 files changed, 252 insertions, 2 deletions
diff --git a/gcc/testsuite/g++.dg/gcov/gcov-19.C b/gcc/testsuite/g++.dg/gcov/gcov-19.C new file mode 100644 index 0000000..3f898cf --- /dev/null +++ b/gcc/testsuite/g++.dg/gcov/gcov-19.C @@ -0,0 +1,35 @@ +/* { dg-options "-fprofile-arcs -ftest-coverage" } */ +/* { dg-do run { target native } } */ + +/* Filtering on the function base name generally works well, because it becomes + an unadultered part of the symbol. */ + +template <typename T> +T +fn1 (T x) +{ + /* fst */ + return x; +} + +template <typename T> +T +fn2 (T x) +{ + /* snd */ + return 2 * x; +} + +int +main () +{ + fn1 (2); + fn1 (2.0); + fn1 (2.0f); + + fn2 (2); + fn2 (2.0); + fn2 (2.0f); +} + +/* { dg-final { run-gcov { filters { fn1 } { fn2 } } { --include=fn1 gcov-19.C } } } */ diff --git a/gcc/testsuite/g++.dg/gcov/gcov-20.C b/gcc/testsuite/g++.dg/gcov/gcov-20.C new file mode 100644 index 0000000..fb33f7e --- /dev/null +++ b/gcc/testsuite/g++.dg/gcov/gcov-20.C @@ -0,0 +1,38 @@ +/* { dg-options "-fprofile-arcs -ftest-coverage" } */ +/* { dg-do run { target native } } */ + +/* Filtering also works by targeting the mangled symbol directly, but the + subtlety is not really caught by the test framework. Matching on fn1I[df] + prints the "overlapping blocks" of both the float and double instantiation, + but *not* the int instantiation. The extra {} around the --include argument + is quoting for tcl/dejagnu. */ + +template <typename T> +T +fn1 (T x) +{ + /* fst */ + return x; +} + +template <typename T> +T +fn2 (T x) +{ + /* snd */ + return 2 * x; +} + +int +main () +{ + fn1 (2); + fn1 (2.0); + fn1 (2.0f); + + fn2 (2); + fn2 (2.0); + fn2 (2.0f); +} + +/* { dg-final { run-gcov { filters { fst } { snd } } { {--include=fn1I[fd]} gcov-20.C } } } */ diff --git a/gcc/testsuite/g++.dg/gcov/gcov-21.C b/gcc/testsuite/g++.dg/gcov/gcov-21.C new file mode 100644 index 0000000..67f6d79 --- /dev/null +++ b/gcc/testsuite/g++.dg/gcov/gcov-21.C @@ -0,0 +1,32 @@ +/* { dg-options "-fprofile-arcs -ftest-coverage" } */ +/* { dg-do run { target native } } */ + +/* Filters can be applied to demangled names. This support matching on + types and class hierarchies as well as function names. */ + +template <typename T> +T +fn1 (T x) +{ + /* fst */ + return x; +} + +template <typename T> +T +fn2 (T x) +{ + /* snd */ + return 2 * x; +} + +int +main () +{ + fn1 (2); + + fn2 (2.0); + fn2 (2.0f); +} + +/* { dg-final { run-gcov { filters { fst } } { --filter-on-demangled --include int gcov-21.C } } } */ diff --git a/gcc/testsuite/gcc.misc-tests/gcov-25.c b/gcc/testsuite/gcc.misc-tests/gcov-25.c new file mode 100644 index 0000000..658f375 --- /dev/null +++ b/gcc/testsuite/gcc.misc-tests/gcov-25.c @@ -0,0 +1,25 @@ +/* { dg-options "-fprofile-arcs -ftest-coverage" } */ +/* { dg-do run { target native } } */ + +/* Filters are considered in order with latest-wins, so if a function is + included and later excluded it should not show up. */ + +int +fn1 (int x) +{ + /* fst */ + return x; +} + +int +fn2 (int x) +{ + /* snd */ + return x * 2; +} + +int +main () +{} + +/* { dg-final { run-gcov { filters { snd } { fst main } } { --include=fn --exclude=1 gcov-25.c } } } */ diff --git a/gcc/testsuite/gcc.misc-tests/gcov-26.c b/gcc/testsuite/gcc.misc-tests/gcov-26.c new file mode 100644 index 0000000..d204cd3 --- /dev/null +++ b/gcc/testsuite/gcc.misc-tests/gcov-26.c @@ -0,0 +1,25 @@ +/* { dg-options "-fprofile-arcs -ftest-coverage" } */ +/* { dg-do run { target native } } */ + +/* Filters are considered in order with latest-wins, so if a function is + excluded and later included it should show up. */ + +int +fn1 (int x) +{ + /* fst */ + return x; +} + +int +fn2 (int x) +{ + /* snd */ + return x * 2; +} + +int +main () +{} + +/* { dg-final { run-gcov { filters { fst snd } { main } } { --exclude=1 --include=fn gcov-26.c } } } */ diff --git a/gcc/testsuite/gcc.misc-tests/gcov-27.c b/gcc/testsuite/gcc.misc-tests/gcov-27.c new file mode 100644 index 0000000..e01a2c7 --- /dev/null +++ b/gcc/testsuite/gcc.misc-tests/gcov-27.c @@ -0,0 +1,24 @@ +/* { dg-options "-fprofile-arcs -ftest-coverage" } */ +/* { dg-do run { target native } } */ + +/* If only --exclude is used, other functions should be used by default. */ + +int +fn1 (int x) +{ + /* fst */ + return x; +} + +int +fn2 (int x) +{ + /* snd */ + return x * 2; +} + +int +main () +{} + +/* { dg-final { run-gcov { filters { fst snd } { main } } { --exclude=main gcov-27.c } } } */ diff --git a/gcc/testsuite/gcc.misc-tests/gcov-28.c b/gcc/testsuite/gcc.misc-tests/gcov-28.c new file mode 100644 index 0000000..26f5b15 --- /dev/null +++ b/gcc/testsuite/gcc.misc-tests/gcov-28.c @@ -0,0 +1,22 @@ +/* { dg-options "-fprofile-arcs -ftest-coverage" } */ +/* { dg-do run { target native } } */ + +int +once (int x) +{ + /* fst */ + return x; +} + +int +twice (int x) +{ + /* snd */ + return x * 2; +} + +int +main () +{} + +/* { dg-final { run-gcov { filters { fst } { snd main } } { --include=once gcov-28.c } } } */ diff --git a/gcc/testsuite/lib/gcov.exp b/gcc/testsuite/lib/gcov.exp index dd47d66..e49f101 100644 --- a/gcc/testsuite/lib/gcov.exp +++ b/gcc/testsuite/lib/gcov.exp @@ -495,6 +495,44 @@ proc verify-calls { testname testcase file } { return $failed } +# Verify that report filtering includes and excludes the right functions. +proc verify-filters { testname testcase file expected unexpected } { + set fd [open $file r] + + set seen {} + set ex [concat $expected $unexpected] + + while { [gets $fd line] >= 0 } { + foreach sym $ex { + if [regexp "$sym" "$line"] { + lappend seen $sym + } + } + } + + set seen [lsort -unique $seen] + + set expected [lmap key $expected { + if { $key in $seen } continue + set key + }] + set unexpected [lmap key $unexpected { + if { $key ni $seen } continue + set key + }] + + foreach sym $expected { + fail "Did not see expected symbol '$sym'" + } + + foreach sym $unexpected { + fail "Found unexpected symbol '$sym'" + } + + close $fd + return [expr [llength $expected] + [llength $unexpected]] +} + proc gcov-pytest-format-line { args } { global subdir @@ -570,6 +608,7 @@ proc run-gcov { args } { set gcov_verify_conditions 0 set gcov_verify_lines 1 set gcov_verify_intermediate 0 + set gcov_verify_filters 0 set gcov_remove_gcda 0 set xfailed 0 @@ -580,12 +619,17 @@ proc run-gcov { args } { set gcov_verify_branches 1 } elseif { $a == "conditions" } { set gcov_verify_conditions 1 + } elseif { [lindex $a 0] == "filters" } { + set gcov_verify_filters 1 + set verify_filters_expected [lindex $a 1] + set verify_filters_unexpected [lindex $a 2] } elseif { $a == "intermediate" } { set gcov_verify_intermediate 1 set gcov_verify_calls 0 set gcov_verify_branches 0 set gcov_verify_conditions 0 set gcov_verify_lines 0 + set gcov_verify_filters 0 } elseif { $a == "remove-gcda" } { set gcov_remove_gcda 1 } elseif { $gcov_args == "" } { @@ -670,15 +714,20 @@ proc run-gcov { args } { } else { set ifailed 0 } + if { $gcov_verify_filters } { + set ffailed [verify-filters $testname $testcase $testcase.gcov $verify_filters_expected $verify_filters_unexpected] + } else { + set ffailed 0 + } # Report whether the gcov test passed or failed. If there were # multiple failures then the message is a summary. - set tfailed [expr $lfailed + $bfailed + $cdfailed + $cfailed + $ifailed] + set tfailed [expr $lfailed + $bfailed + $cdfailed + $cfailed + $ifailed + $ffailed] if { $xfailed } { setup_xfail "*-*-*" } if { $tfailed > 0 } { - fail "$testname gcov: $lfailed failures in line counts, $bfailed in branch percentages, $cdfailed in condition/decision, $cfailed in return percentages, $ifailed in intermediate format" + fail "$testname gcov: $lfailed failures in line counts, $bfailed in branch percentages, $cdfailed in condition/decision, $cfailed in return percentages, $ifailed in intermediate format, $ffailed failed in filters" if { $xfailed } { clean-gcov $testcase } |