#!/usr/bin/perl # SPDX-License-Identifier: MIT use strict; use warnings; use Getopt::Long; BEGIN { $Pod::Usage::Formatter = 'Pod::Text::Termcap'; } use Pod::Usage; use Pod::Man; my $prefix = qr ".*?(linux)\w*/"; my $title = ""; my %used_func; my %all_func; my %all_branch; my %all_line; my %used_source; my %record; my %files; my @func_regexes; my @func_exclude_regexes; my %test_names; my @src_regexes; my @src_exclude_regexes; my $verbose = 0; my $ignore_unused = 0; my $skip_func = 0; sub is_function_excluded($) { return 0 if (!@func_regexes && !@func_exclude_regexes); my $func = shift; foreach my $r (@func_exclude_regexes) { return 1 if ($func =~ m/$r/); } return 0 if (!@func_regexes); foreach my $r (@func_regexes) { return 0 if ($func =~ m/$r/); } return 1; } sub filter_file($) { my $s = shift; return 0 if (!@src_regexes && !@src_exclude_regexes); foreach my $r (@src_exclude_regexes) { return 1 if ($s =~ m/$r/); } return 0 if (!@src_regexes); foreach my $r (@src_regexes) { return 0 if ($s =~ m/$r/); } return 1; } # Use something that comes before any real function my $before_sf = "!!!!"; sub parse_info_data($) { my $file = shift; my $was_used = 0; my $has_func = 0; my $ignore = 0; my $source = $before_sf; my $func = $before_sf; my $cur_test = ""; # First step: parse data print "reading $file...\n" if ($verbose); open IN, $file or die "can't open $file"; # For details on .info file format, see "man geninfo" # http://ltp.sourceforge.net/coverage/lcov/geninfo.1.php while () { # TN: if (m/^TN:(.*)/) { if ($1 ne $cur_test) { $cur_test = $1; $test_names{$cur_test} = 1; } $source = $before_sf; $func = $before_sf; next; } # SF: if (m/^[SK]F:(.*)/) { $source = $1; $was_used = 0; $has_func = 0; $func = $before_sf; $files{$source} = 1; # Just ignore files explictly set as such $ignore = filter_file($source); next; } # End of record if (m/^end_of_record/) { if (!$source) { print "bad end_of_record field at $file, line $. Ignoring...\n"; next; } my $s = $source; $source = $before_sf; $func = $before_sf; next if ($ignore); next if ($ignore_unused && !$was_used); # Mark that the source was not ignored $used_source{$s} = 1; next; } next if ($ignore); # Function coverage # FN:, if (m/^FN:(-?\d+),(.*)/) { my $ln = $1; $func = $2; $has_func = 1; if (is_function_excluded($func)) { $skip_func = 1; next; } $skip_func = 0; $record{$source}{$func}{fn} = $ln; $all_func{$func}{$source}->{ln} = $ln; next; } # Parse functions that were actually used # FNDA:, if (m/^FNDA:(-?\d+),(.*)/) { my $count = $1; # Negative gcov results are possible, as reported at: # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67937 # Lcov ignores those. So, let's do the same here. next if ($count <= 0); $func = $2; $has_func = 1; if (is_function_excluded($func)) { $skip_func = 1; next; } $skip_func = 0; $was_used = 1; $record{$source}{$func}{fnda} += $count; $used_func{$func}{$source}->{count} += $count; next; } # Ignore data from skipped functions next if ($skip_func); # Ignore DA/BRDA that aren't associated with functions # Those are present on header files (maybe defines?) next if (@func_regexes && !$has_func); # FNF: if (m/^FNF:(-?\d+)/) { $record{$source}{$func}{fnf} = $1; next; } # FNH: if (m/^FNH:(-?\d+)/) { my $hits = $1; if ($record{$source}{$func}{fnh} < $hits) { $record{$source}{$func}{fnh} = $hits; } next; } # Branch coverage # BRDA:,,, if (m/^BRDA:(-?\d+),(-?\d+),(-?\d+),(.*)/) { my $ln = $1; my $block = $2; my $branch = $3; my $taken = $4; my $where = "$ln,$block,$branch"; $taken = 0 if ($taken eq '-'); # Negative gcov results are possible, as reported at: # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67937 # Lcov ignores those. So, let's do the same here. $taken = 0 if ($taken < 0); $was_used = 1 if ($taken > 0); $record{$source}{$func}{brda}{$where} += $taken; $all_branch{$source}{"$where"} += $taken; next; } # BRF: if (m/^BRF:(-?\d+)/) { $record{$source}{brf} = $1; next; } # BRH: if (m/^BRH:(-?\d+)/) { my $hits = $1; if ($record{$source}{$func}{brh} < $hits) { $record{$source}{$func}{brh} = $hits; } next; } # Line coverage # DA:,[,] if (m/^DA:(-?\d+),(-?\d+)(,.*)?/) { my $ln = $1; my $count = $2; # Negative gcov results are possible, as reported at: # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67937 # Lcov ignores those. So, let's do the same here. $count = 0 if ($count < 0); $was_used = 1 if ($count > 0); $record{$source}{$func}{da}{$ln} += $count; $all_line{$source}{"$ln"} += $count; next; } # LF: if (m/^LF:(-?\d+)/) { $record{$source}{$func}{lf} = $1; next; } # LH: if (m/^LH:(-?\d+)/) { my $hits = $1; if ($record{$source}{$func}{lh} < $hits) { $record{$source}{$func}{lh} = $hits; } next; } printf("Warning: invalid line: $_"); } close IN or die; } sub write_filtered_file($) { my $filter = shift; my $filtered = ""; if ($title eq "") { foreach my $testname(sort keys %test_names) { $filtered .= "TN:$testname\n"; } } else { $filtered .= "TN:$title\n"; } # Generates filtered data foreach my $source(sort keys %record) { next if (!$used_source{$source}); if ($source ne $before_sf) { $filtered .= "SF:$source\n"; } foreach my $func(sort keys %{ $record{$source} }) { if ($func ne $before_sf) { my $fn; my $fnda; if (defined($record{$source}{$func}{fn})) { $filtered .= "FN:" . $record{$source}{$func}{fn} . ",$func\n"; } if (defined($record{$source}{$func}{fnda})) { $filtered .= "FNDA:" . $record{$source}{$func}{fnda} . ",$func\n"; } if ($record{$source}{fnf}) { $filtered .= "FNF:". $record{$source}{$func}{fnf} ."\n"; } if ($record{$source}{fnh}) { $filtered .= "FNH:". $record{$source}{$func}{fnh} ."\n"; } } foreach my $ln(sort keys %{ $record{$source}{$func}{da} }) { $filtered .= "DA:$ln," . $record{$source}{$func}{da}{$ln} . "\n"; } foreach my $where(sort keys %{ $record{$source}{$func}{brda} }) { my $taken = $record{$source}{$func}{brda}{$where}; $taken = "-" if (!$taken); $filtered .= "BRDA:$where,$taken\n"; } if ($record{$source}{$func}{brf}) { $filtered .= "BRF:". $record{$source}{$func}{brf} ."\n"; } if ($record{$source}{$func}{brh}) { $filtered .= "BRH:". $record{$source}{$func}{brh} ."\n"; } if ($record{$source}{$func}{lf}) { $filtered .= "LF:". $record{$source}{$func}{lf} ."\n"; } if ($record{$source}{$func}{lh}) { $filtered .= "LH:". $record{$source}{$func}{lh} ."\n"; } } $filtered .= "end_of_record\n"; } open OUT, ">$filter" or die "Can't open $filter"; print OUT $filtered or die "Failed to write to $filter"; close OUT or die "Failed to close to $filter"; } sub print_code_coverage($$$) { my $print_used = shift; my $print_unused = shift; my $show_lines = shift; return if (!$print_used && !$print_unused); my $prev_file = ""; foreach my $func (sort keys(%all_func)) { my @keys = sort keys(%{$all_func{$func}}); foreach my $file (@keys) { my $count = 0; my $name; if ($used_func{$func}) { if ($used_func{$func}->{$file}) { $count = $used_func{$func}->{$file}->{count}; } } if ($show_lines) { my $ln = $all_func{$func}{$file}->{ln}; $file =~ s,$prefix,linux/,; $name = "$func() from $file"; $name .= ":" . $ln if ($ln); } elsif (scalar @keys > 1) { $file =~ s,$prefix,linux/,; $name = "$func() from $file:"; } else { $name = "$func():"; } if ($print_unused) { if (!$count) { print "$name unused\n"; } elsif ($print_used) { print "$name executed $count times\n"; } } elsif ($count) { print "$name executed $count times\n"; } } } } my %stats; sub gen_stats() { # per-line coverage statistics $stats{"line_count"} = 0; $stats{"line_reached"} = 0; foreach my $source (keys(%all_line)) { next if (!$used_source{$source}); foreach my $where (keys(%{$all_line{$source}})) { $stats{"line_count"}++; $stats{"line_reached"}++ if ($all_line{$source}{$where} != 0); } } # per-function coverage statistics $stats{"func_count"} = 0; $stats{"func_used"} = 0; foreach my $func (keys(%all_func)) { foreach my $file (keys(%{$all_func{$func}})) { $stats{"func_count"}++; if ($used_func{$func}) { if ($used_func{$func}->{$file}) { $stats{"func_used"}++; } } } } # per-branch coverage statistics $stats{"branch_count"} = 0; $stats{"branch_reached"} = 0; foreach my $source (keys(%all_branch)) { next if (!$used_source{$source}); foreach my $where (keys(%{$all_branch{$source}})) { $stats{"branch_count"}++; $stats{"branch_reached"}++ if ($all_branch{$source}{$where} != 0); } } # per-file coverage stats $stats{"all_files"} = scalar keys(%files); $stats{"filtered_files"} = scalar keys(%record); $stats{"used_files"} = scalar keys(%used_source); } sub print_summary() { if ($stats{"line_count"}) { my $percent = 100. * $stats{"line_reached"} / $stats{"line_count"}; printf " lines......: %.1f%% (%d of %d lines)\n", $percent, $stats{"line_reached"}, $stats{"line_count"}; } else { print "No line coverage data.\n"; } if ($stats{"func_count"}) { my $percent = 100. * $stats{"func_used"} / $stats{"func_count"}; printf " functions..: %.1f%% (%d of %d functions)\n", $percent, $stats{"func_used"}, $stats{"func_count"}; } else { print "No functions reported. Wrong filters?\n"; return; } if ($stats{"branch_count"}) { my $percent = 100. * $stats{"branch_reached"} / $stats{"branch_count"}; printf " branches...: %.1f%% (%d of %d branches)\n", $percent, $stats{"branch_reached"}, $stats{"branch_count"}; } else { print "No branch coverage data.\n"; } } sub open_filter_file($$$) { my $fname = shift; my $include = shift; my $exclude = shift; my $match_str = ""; my $not_match_str = ""; my $filter = ""; my $i; # Handle regexes that came from command line params for ($i = 0;$i < scalar(@{$include}); $i++) { my $op = @{$include}[$i]; $match_str .= sprintf "m`$op` "; @{$include}[$i] = qr /$op/; } for ($i = 0;$i < scalar(@{$exclude}); $i++) { my $op = @{$exclude}[$i]; $not_match_str .= sprintf "m`$op` "; @{$exclude}[$i] = qr /$op/; } if ($fname) { open IN, $fname or die "Can't open $fname"; while () { s/^\s+//; s/\s+$//; next if (m/^#/ || m/^$/); if (m/^([+\-])\s*(.*)/) { if ($1 eq "+") { $match_str .= sprintf "m`$2` "; push @{$include}, qr /$2/; } else { $not_match_str .= sprintf "m`$2` "; push @{$exclude}, qr /$2/; } } else { $match_str .= sprintf "m`$_` "; push @{$include}, qr /$_/; } } close IN; } $filter .= "not match: $not_match_str" if ($not_match_str); if ($match_str) { $filter .= "and " if ($filter ne ""); $filter .= "match: $match_str"; } $filter =~ s/\s+$//; return $filter; } my $gen_report; my $css_file; my $html_prolog; my $html_epilog; my %report; sub generate_report($) { my $filter_str = shift; my $percent; my $prolog = ""; my $epilog = ""; my @info_files = sort(keys %report); $title = "Code coverage results" if ($title eq ""); if ($html_prolog) { open IN, $html_prolog or die "Can't open prolog file"; $prolog .= $_ while (); close IN; } if ($html_epilog) { open IN, $html_epilog or die "Can't open epilog file"; $epilog .= $_ while (); close IN; } # Re-generate the hashes used to report stats in order to procuce the # Total results %used_func = (); %all_func = (); %all_branch = (); %all_line = (); %used_source = (); %files = (); %test_names = (); foreach my $f (@info_files) { foreach my $source (keys(%{$report{$f}{"all_line"}})) { $used_source{$source} = 1 if ($report{$f}{"used_source"}); foreach my $where (keys(%{$report{$f}{"all_line"}{$source}})) { $all_line{$source}{$where} += $report{$f}{"all_line"}{$source}{$where}; } } foreach my $func (keys(%{$report{$f}{"all_func"}})) { foreach my $file (keys(%{$report{$f}{"all_func"}{$func}})) { $all_func{$func}{$file}->{ln} = $report{$f}{"all_func"}{$func}{$file}->{ln}; $used_func{$func}->{$file} = 1 if ($report{$f}{"used_func"}{$func}->{$file}); } } foreach my $source (keys(%{$report{$f}{"all_branch"}})) { foreach my $where (keys(%{$report{$f}{"all_branch"}{$source}})) { $all_branch{$source}{"$where"} += $report{$f}{"all_branch"}{$source}{$where}; } } for my $source(keys(%{$report{$f}{"files"}})) { $files{$source} = 1; $used_source{$source} = 1 if ($report{$f}{"used_source"}{$source}); } for my $test(keys(%{$report{$f}{"test_names"}})) { $test_names{$test} = 1; } } gen_stats(); # Colors for the html output my $red = "style=\"background-color:#ffb3b3\""; my $yellow = "style=\"background-color:#ffffb3\""; my $green = "style=\"background-color:#d9ffd9\""; # Open report file open OUT, ">$gen_report" or die "Can't open $gen_report"; print OUT "\n"; print OUT "\n\n"; print OUT "\n"; print OUT " \n"; print OUT " $title\n"; print OUT " \n" if ($css_file); print OUT "\n\n\n$prolog"; print OUT "

$title

\n"; print OUT "

Summary

\n"; # Generates a table containing the code coverage statistics per input print OUT "\n \n"; print OUT " \n"; foreach my $f (@info_files) { print OUT " \n"; } print OUT " \n"; print OUT " \n"; print OUT " \n"; print OUT " \n"; foreach my $f (@info_files) { my %st = %{$report{$f}{"stats"}}; if ($st{"func_count"}) { $percent = 100. * $st{"func_used"} / $st{"func_count"}; printf OUT " \n", $percent; } else { print OUT " \n"; } } if ($stats{"func_count"}) { $percent = 100. * $stats{"func_used"} / $stats{"func_count"}; printf OUT " \n", $percent; } else { print OUT " \n"; } print OUT " "; print OUT " \n"; print OUT " \n"; foreach my $f (@info_files) { my %st = %{$report{$f}{"stats"}}; if ($st{"branch_count"}) { $percent = 100. * $st{"branch_reached"} / $st{"branch_count"}; printf OUT " \n", $percent; } else { print OUT " \n"; } } if ($stats{"branch_count"}) { $percent = 100. * $stats{"branch_reached"} / $stats{"branch_count"}; printf OUT " \n", $percent; } else { print OUT " \n"; } print OUT " "; print OUT " \n"; print OUT " \n"; foreach my $f (@info_files) { my %st = %{$report{$f}{"stats"}}; if ($st{"line_count"}) { $percent = 100. * $st{"line_reached"} / $st{"line_count"}; printf OUT " \n", $percent; } else { print OUT " \n"; } } if ($stats{"line_count"}) { $percent = 100. * $stats{"line_reached"} / $stats{"line_count"}; printf OUT " \n", $percent; } else { print OUT " \n"; } print OUT " "; # If there are more than one tests per file, report them my $total = scalar(keys %test_names); if ($total > 1) { print OUT " \n"; print OUT " \n"; foreach my $f (@info_files) { my $count = scalar(keys %{$report{$f}{"test_names"}}); if ($count == 0) { print OUT " \n"; } elsif ($count < $total) { print OUT " \n"; } else { print OUT " \n"; } } print OUT " \n"; } print OUT " \n
$fTOTALTotal count
Functions%.1f%%N. A.%.1f%%N. A." . $stats{"func_count"} . "
Branches%.1f%%N. A.%.1f%%N. A." . $stats{"branch_count"} . "
Lines%.1f%%N. A.%.1f%%N. A." . $stats{"line_count"} . "
Number of tests$count$count$count$total

\n\n"; if ($filter_str ne "") { printf OUT "

Filters: %s.

\n", $filter_str; } else { print OUT "

(unfiltered results)

"; } if ($total > 1) { print OUT "

Tests coverage

\n"; print OUT "\n \n"; print OUT " \n"; foreach my $f (@info_files) { print OUT " \n"; } foreach my $t (sort keys(%test_names)) { print OUT " \n"; printf OUT " \n", $t; foreach my $f (@info_files) { if (%{$report{$f}{"test_names"}}{$t}) { print OUT " \n"; } else { print OUT " \n"; } } } print OUT "
Test name$f
%sYESNO
\n"; } # Generates a table containing per-function detailed data print OUT "

Functions coverage

\n"; print OUT "\n \n"; print OUT " \n"; print OUT " \n"; foreach my $f (@info_files) { print OUT " \n"; } print OUT " \n"; foreach my $func (sort keys(%all_func)) { my @keys = sort keys(%{$all_func{$func}}); foreach my $file (@keys) { print OUT " \n"; print OUT " \n"; if ($used_func{$func}->{$file}) { print OUT " \n"; } else { print OUT " \n"; } foreach my $f (@info_files) { if ($report{$f}{"used_func"}{$func}->{$file}) { print OUT " \n"; } else { print OUT " \n"; } } $file =~ s,$prefix,linux/,; print OUT " \n"; } } print OUT "
FunctionUsed?$fFile
$funcYESNOYESNO$file
\n"; print OUT "$epilog\n"; # Close the file and exit close OUT; } # # Argument handling # my $print_used; my $print_unused; my $stat; my $filter; my $help; my $man; my $func_filters; my $src_filters; my $show_files; my $show_lines; my $only_i915; my $only_drm; GetOptions( "print-coverage|print_coverage|print|p" => \$print_used, "print-unused|u" => \$print_unused, "stat|statistics" => \$stat, "output|o=s" => \$filter, "verbose|v" => \$verbose, "ignore-unused|ignore_unused" => \$ignore_unused, "only-i915|only_i915" => \$only_i915, "only-drm|only_drm" => \$only_drm, "func-filters|f=s" => \$func_filters, "include-func=s" => \@func_regexes, "exclude-func=s" => \@func_exclude_regexes, "source-filters|S=s" => \$src_filters, "include-source=s" => \@src_regexes, "exclude-source=s" => \@src_exclude_regexes, "show-files|show_files" => \$show_files, "show-lines|show_lines" => \$show_lines, "report|r=s" => \$gen_report, "css-file|css|c=s" => \$css_file, "title|t=s" => \$title, "html-prolog|prolog=s" => \$html_prolog, "html-epilog|epilog=s" => \$html_epilog, "help" => \$help, "man" => \$man, ) or pod2usage(2); pod2usage(-verbose => 2) if $man; pod2usage(1) if $help; if ($#ARGV < 0) { print "$0: no input files\n"; pod2usage(1); } # At least one action should be specified pod2usage(1) if (!$print_used && !$filter && !$stat && !$print_unused && !$gen_report); pod2usage(1) if ($gen_report && ($print_used || $filter || $stat || $print_unused)); my $filter_str = ""; my $has_filter; my $str; if ($only_i915) { # Please keep in sync with the documentation push @src_exclude_regexes, "selftest"; push @src_regexes, "drm/i915"; push @src_regexes, "drm/ttm"; push @src_regexes, "drm/vgem"; } if ($only_drm) { # Please keep in sync with the documentation push @src_exclude_regexes, "trace.*\.h"; push @src_exclude_regexes, "drm.*\.h"; } $str = open_filter_file($func_filters, \@func_regexes, \@func_exclude_regexes); if ($str) { $filter_str .= "," if ($filter_str ne ""); $filter_str .= " function regex ($str)"; $has_filter = 1; } $str = open_filter_file($src_filters, \@src_regexes, \@src_exclude_regexes); if ($str) { $filter_str .= "," if ($filter_str ne ""); $filter_str .= " source regex ($str)"; $has_filter = 1; } $ignore_unused = 1 if (@func_regexes || @func_exclude_regexes); if ($ignore_unused) { $filter_str .= "," if ($filter_str ne ""); $filter_str .= " ignored source files where none of its code ran"; $has_filter = 1; } foreach my $f (@ARGV) { parse_info_data($f); if ($gen_report) { $f =~ s,.*/,,; $f =~ s/\.info$//; gen_stats(); $report{$f}{"stats"} = { %stats }; $report{$f}{"all_func"} = { %all_func }; $report{$f}{"used_func"} = { %used_func }; $report{$f}{"all_branch"} = { %all_branch }; $report{$f}{"all_line"} = { %all_line }; $report{$f}{"used_source"} = { %used_source }; $report{$f}{"files"} = { %files }; $report{$f}{"test_names"} = { %test_names }; %used_func = (); %all_func = (); %all_branch = (); %all_line = (); %used_source = (); %files = (); %test_names = (); } } $filter_str =~ s/(.*),/$1 and/ if ($filter_str ne ""); if ($gen_report) { generate_report($filter_str); exit 0; } gen_stats(); die "Nothing counted. Wrong input files?" if (!$stats{"all_files"}); print_code_coverage($print_used, $print_unused, $show_lines); print_summary() if ($stat); if ($has_filter) { my $percent = 100. * $stats{"used_files"} / $stats{"all_files"}; printf "Filters......:%s.\n", $filter_str; printf "Source files.: %.2f%% (%d of %d total)", $percent, $stats{"used_files"}, $stats{"all_files"}; if ($stats{"used_files"} != $stats{"filtered_files"}) { my $percent_filtered = 100. * $stats{"used_files"} / $stats{"filtered_files"}; printf ", %.2f%% (%d of %d filtered)", $percent_filtered, $stats{"used_files"}, $stats{"filtered_files"}; } print "\n"; } else { printf "Source files: %d\n", scalar keys(%files) if($stat); } my $ntests=scalar(%test_names); printf "Number of tests: %d\n", $ntests if ($ntests > 1); if ($show_files) { for my $f(sort keys %used_source) { print "\t$f\n"; } } if ($filter) { write_filtered_file($filter); } __END__ =head1 NAME Parses lcov data from .info files. =head1 SYNOPSIS code_cov_parse_info [input file(s)] At least one of the output options should be used, e g. B<--stat>, B<--print>, B<--print-unused>, B<--report> and/or B<--output>. Also, B<--report> can't be used together with other output options. =head1 OPTIONS =over 8 =item B<--stat> or B<--statistics> Prints code coverage statistics. It displays function, line, branch and file coverage percentage. It also reports when one or more of the filtering parameters are used. The statistics report is affected by the applied filters. =item B<--print-coverage> or B<--print_coverage> or B<--print> or B<-p> Prints the functions that were executed in runtime and how many times they were reached. The function coverage report is affected by the applied filters. =item B<--print-unused> or B<-u> Prints the functions that were never reached. The function coverage report is affected by the applied filters. =item B<--report> B<[output file]> or B<-r> B<[output file]> Generates an html report containing per-test and total statistics. The function coverage report is affected by the applied filters. =item B<--css-file> B<[css file]> or B<--css> B<[css file]> or B<-c> B<[css file] Adds an optional css file to the html report. Used only with B<--report>. =item B<--title> B<[title] or B<-t> B<[title] If used with B<--report>, it defines the title for the for the html report. If used with B<--output>, it replaces the test names with the title. This is useful when merging reports from multiple tests into a summarized file. If not used, the B<[output file]> will contain all test names on its beginning. Used with B<--report> AND B<--output>. =item B<--html-prolog> B<[html file] or B<--prolog> B<[html file] Adds a prolog at the beginning of the body of the html report. Used only with B<--report>. =item B<--html-epilog> B<[html file] or B<--epilog> B<[html file] Adds an epilog before the end of the body of the html report. Used only with B<--report>. =item B<--show-lines> or B<--show_lines> When printing per-function code coverage data, always output the source file and the line number where the function is defined. =item B<--output> B<[output file]> or B<-o> B<[output file]> Produce an output file merging all input files. The generated output file is affected by the applied filters. =item B<--only-drm> or B<--only_drm> Filters out includes outside the DRM subsystem, plus trace files. E. g. it will exclude *.h files that match the following regular expressions: - .*trace.*\.h$ And *.h files that don't match: - drm =item B<--only-i915> or B<--only_i915> Filters out C files and headers outside drm core and drm/i915. E. g. code coverage results will include only the files that that match the following regular expressions: - drm/i915/ - drm/ttm - drm/vgem Excluding files that match: - selftest =item B<--func-filters> B<[filter's file]> or B<-f> B<[filter's file]> Use a file containing regular expressions (regex) to filter functions. Each line at B<[filter's file]> may contain a new regex: =over 4 - Blank lines and lines starting with B<#> will be ignored; - Each line of the file will be handled as a new regex; - If B<+regex> is used, the filter will include B to the matches; - If B<-regex> is used, the filter will exclude B from the matches; - If the line doesn't start with neither B<+> nor B<->, containing just B, the filter will include B to the matches. - Any whitespace/tab before or after B will be ignored. =back When both include and exclude regexes are found, exclude regexes are applied first and any functions that don't match the include regular expressions from the B<[filter's file]> will be ignored. Please notice that, when this filter is used, B<--ignore-unused> will be automaticaly enabled, as the final goal is to report per-function usage. =item B<--include-func> B Include B to the function filter. Can be used multiple times. When used together with B<--func-filters>, regexes here are handled first. Please notice that, when this filter is used, B<--ignore-unused> will be automaticaly enabled, as the final goal is to report per-function usage. =item B<--exclude-func> B Include B to the function filter. Can be used multiple times. When used together with B<--func-filters>, regexes here are handled first. Please notice that, when this filter is used, B<--ignore-unused> will be automaticaly enabled, as the final goal is to report per-function usage. =item B<--source-filters> B<[filter's file]> or B<-S> B<[filter's file]> Use a file containing regular expressions to filter source files. Each line of the file will be handled as a new regular expressions. Blank lines and lines starting with B<#> will be ignored. Each line at B<[filter's file]> may contain a new regex: =over 4 - Blank lines and lines starting with B<#> will be ignored; - Each line of the file will be handled as a new regex; - If B<+regex> is used, the filter will include B to the matches; - If B<-regex> is used, the filter will exclude B from the matches; - If the line doesn't start with neither B<+> nor B<->, containing just B, the filter will include B to the matches. - Any whitespace/tab before or after B will be ignored. =back When both include and exclude regexes are found, exclude regexes are applied first and any functions that don't match the include regular expressions from the B<[filter's file]> will be ignored. =item B<--include-src> B Include B to the sources filter. Can be used multiple times. When used together with B<--src-filters>, regexes here are handled first. =item B<--exclude-src> B Include B to the sources filter. Can be used multiple times. When used together with B<--src-filters>, regexes here are handled first. =item B<--ignore-unused> or B<--ignore_unused> Filters out unused C files and headers from the code coverage results. Sometimes, it is desired to ignore files where none of the functions on it were tested. The rationale is that such files may contain platform-specific drivers and code that will never be used, so, placing them will just bloat the report and decrease the code coverage statistics. This option is automaticaly enabled when B<--func-filters> is used. =back =item B<--show-files> or B<--show_files> Shows the list of files that were used to produce the code coverage results. =item B<--verbose> or B<-v> Prints the name of each parsed file. =item B<--help> Print a brief help message and exits. =item B<--man> Prints the manual page and exits. =back =head1 BUGS Report bugs to Mauro Carvalho Chehab =head1 COPYRIGHT Copyright (c) 2022 Intel Corporation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. =cut