1 #!/usr/bin/perl -w 2 # 3 # Copyright (c) International Business Machines Corp., 2002,2012 4 # 5 # This program is free software; you can redistribute it and/or modify 6 # it under the terms of the GNU General Public License as published by 7 # the Free Software Foundation; either version 2 of the License, or (at 8 # your option) any later version. 9 # 10 # This program is distributed in the hope that it will be useful, but 11 # WITHOUT ANY WARRANTY; without even the implied warranty of 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 # General Public License for more details. 14 # 15 # You should have received a copy of the GNU General Public License 16 # along with this program; if not, write to the Free Software 17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 # 19 # 20 # genhtml 21 # 22 # This script generates HTML output from .info files as created by the 23 # geninfo script. Call it with --help and refer to the genhtml man page 24 # to get information on usage and available options. 25 # 26 # 27 # History: 28 # 2002-08-23 created by Peter Oberparleiter <Peter.Oberparleiter (at] de.ibm.com> 29 # IBM Lab Boeblingen 30 # based on code by Manoj Iyer <manjo (at] mail.utexas.edu> and 31 # Megan Bock <mbock (at] us.ibm.com> 32 # IBM Austin 33 # 2002-08-27 / Peter Oberparleiter: implemented frame view 34 # 2002-08-29 / Peter Oberparleiter: implemented test description filtering 35 # so that by default only descriptions for test cases which 36 # actually hit some source lines are kept 37 # 2002-09-05 / Peter Oberparleiter: implemented --no-sourceview 38 # 2002-09-05 / Mike Kobler: One of my source file paths includes a "+" in 39 # the directory name. I found that genhtml.pl died when it 40 # encountered it. I was able to fix the problem by modifying 41 # the string with the escape character before parsing it. 42 # 2002-10-26 / Peter Oberparleiter: implemented --num-spaces 43 # 2003-04-07 / Peter Oberparleiter: fixed bug which resulted in an error 44 # when trying to combine .info files containing data without 45 # a test name 46 # 2003-04-10 / Peter Oberparleiter: extended fix by Mike to also cover 47 # other special characters 48 # 2003-04-30 / Peter Oberparleiter: made info write to STDERR, not STDOUT 49 # 2003-07-10 / Peter Oberparleiter: added line checksum support 50 # 2004-08-09 / Peter Oberparleiter: added configuration file support 51 # 2005-03-04 / Cal Pierog: added legend to HTML output, fixed coloring of 52 # "good coverage" background 53 # 2006-03-18 / Marcus Boerger: added --custom-intro, --custom-outro and 54 # overwrite --no-prefix if --prefix is present 55 # 2006-03-20 / Peter Oberparleiter: changes to custom_* function (rename 56 # to html_prolog/_epilog, minor modifications to implementation), 57 # changed prefix/noprefix handling to be consistent with current 58 # logic 59 # 2006-03-20 / Peter Oberparleiter: added --html-extension option 60 # 2008-07-14 / Tom Zoerner: added --function-coverage command line option; 61 # added function table to source file page 62 # 2008-08-13 / Peter Oberparleiter: modified function coverage 63 # implementation (now enabled per default), 64 # introduced sorting option (enabled per default) 65 # 66 67 use strict; 68 use File::Basename; 69 use Getopt::Long; 70 use Digest::MD5 qw(md5_base64); 71 72 73 # Global constants 74 our $title = "LCOV - code coverage report"; 75 our $lcov_version = 'LCOV version 1.10'; 76 our $lcov_url = "http://ltp.sourceforge.net/coverage/lcov.php"; 77 our $tool_name = basename($0); 78 79 # Specify coverage rate limits (in %) for classifying file entries 80 # HI: $hi_limit <= rate <= 100 graph color: green 81 # MED: $med_limit <= rate < $hi_limit graph color: orange 82 # LO: 0 <= rate < $med_limit graph color: red 83 84 # For line coverage/all coverage types if not specified 85 our $hi_limit = 90; 86 our $med_limit = 75; 87 88 # For function coverage 89 our $fn_hi_limit; 90 our $fn_med_limit; 91 92 # For branch coverage 93 our $br_hi_limit; 94 our $br_med_limit; 95 96 # Width of overview image 97 our $overview_width = 80; 98 99 # Resolution of overview navigation: this number specifies the maximum 100 # difference in lines between the position a user selected from the overview 101 # and the position the source code window is scrolled to. 102 our $nav_resolution = 4; 103 104 # Clicking a line in the overview image should show the source code view at 105 # a position a bit further up so that the requested line is not the first 106 # line in the window. This number specifies that offset in lines. 107 our $nav_offset = 10; 108 109 # Clicking on a function name should show the source code at a position a 110 # few lines before the first line of code of that function. This number 111 # specifies that offset in lines. 112 our $func_offset = 2; 113 114 our $overview_title = "top level"; 115 116 # Width for line coverage information in the source code view 117 our $line_field_width = 12; 118 119 # Width for branch coverage information in the source code view 120 our $br_field_width = 16; 121 122 # Internal Constants 123 124 # Header types 125 our $HDR_DIR = 0; 126 our $HDR_FILE = 1; 127 our $HDR_SOURCE = 2; 128 our $HDR_TESTDESC = 3; 129 our $HDR_FUNC = 4; 130 131 # Sort types 132 our $SORT_FILE = 0; 133 our $SORT_LINE = 1; 134 our $SORT_FUNC = 2; 135 our $SORT_BRANCH = 3; 136 137 # Fileview heading types 138 our $HEAD_NO_DETAIL = 1; 139 our $HEAD_DETAIL_HIDDEN = 2; 140 our $HEAD_DETAIL_SHOWN = 3; 141 142 # Offsets for storing branch coverage data in vectors 143 our $BR_BLOCK = 0; 144 our $BR_BRANCH = 1; 145 our $BR_TAKEN = 2; 146 our $BR_VEC_ENTRIES = 3; 147 our $BR_VEC_WIDTH = 32; 148 149 # Additional offsets used when converting branch coverage data to HTML 150 our $BR_LEN = 3; 151 our $BR_OPEN = 4; 152 our $BR_CLOSE = 5; 153 154 # Branch data combination types 155 our $BR_SUB = 0; 156 our $BR_ADD = 1; 157 158 # Error classes which users may specify to ignore during processing 159 our $ERROR_SOURCE = 0; 160 our %ERROR_ID = ( 161 "source" => $ERROR_SOURCE, 162 ); 163 164 # Data related prototypes 165 sub print_usage(*); 166 sub gen_html(); 167 sub html_create($$); 168 sub process_dir($); 169 sub process_file($$$); 170 sub info(@); 171 sub read_info_file($); 172 sub get_info_entry($); 173 sub set_info_entry($$$$$$$$$;$$$$$$); 174 sub get_prefix($@); 175 sub shorten_prefix($); 176 sub get_dir_list(@); 177 sub get_relative_base_path($); 178 sub read_testfile($); 179 sub get_date_string(); 180 sub create_sub_dir($); 181 sub subtract_counts($$); 182 sub add_counts($$); 183 sub apply_baseline($$); 184 sub remove_unused_descriptions(); 185 sub get_found_and_hit($); 186 sub get_affecting_tests($$$); 187 sub combine_info_files($$); 188 sub merge_checksums($$$); 189 sub combine_info_entries($$$); 190 sub apply_prefix($$); 191 sub system_no_output($@); 192 sub read_config($); 193 sub apply_config($); 194 sub get_html_prolog($); 195 sub get_html_epilog($); 196 sub write_dir_page($$$$$$$$$$$$$$$$$); 197 sub classify_rate($$$$); 198 sub br_taken_add($$); 199 sub br_taken_sub($$); 200 sub br_ivec_len($); 201 sub br_ivec_get($$); 202 sub br_ivec_push($$$$); 203 sub combine_brcount($$$); 204 sub get_br_found_and_hit($); 205 sub warn_handler($); 206 sub die_handler($); 207 sub parse_ignore_errors(@); 208 sub rate($$;$$$); 209 210 211 # HTML related prototypes 212 sub escape_html($); 213 sub get_bar_graph_code($$$); 214 215 sub write_png_files(); 216 sub write_htaccess_file(); 217 sub write_css_file(); 218 sub write_description_file($$$$$$$); 219 sub write_function_table(*$$$$$$$$$$); 220 221 sub write_html(*$); 222 sub write_html_prolog(*$$); 223 sub write_html_epilog(*$;$); 224 225 sub write_header(*$$$$$$$$$$); 226 sub write_header_prolog(*$); 227 sub write_header_line(*@); 228 sub write_header_epilog(*$); 229 230 sub write_file_table(*$$$$$$$); 231 sub write_file_table_prolog(*$@); 232 sub write_file_table_entry(*$$$@); 233 sub write_file_table_detail_entry(*$@); 234 sub write_file_table_epilog(*); 235 236 sub write_test_table_prolog(*$); 237 sub write_test_table_entry(*$$); 238 sub write_test_table_epilog(*); 239 240 sub write_source($$$$$$$); 241 sub write_source_prolog(*); 242 sub write_source_line(*$$$$$$); 243 sub write_source_epilog(*); 244 245 sub write_frameset(*$$$); 246 sub write_overview_line(*$$$); 247 sub write_overview(*$$$$); 248 249 # External prototype (defined in genpng) 250 sub gen_png($$$@); 251 252 253 # Global variables & initialization 254 our %info_data; # Hash containing all data from .info file 255 our $dir_prefix; # Prefix to remove from all sub directories 256 our %test_description; # Hash containing test descriptions if available 257 our $date = get_date_string(); 258 259 our @info_filenames; # List of .info files to use as data source 260 our $test_title; # Title for output as written to each page header 261 our $output_directory; # Name of directory in which to store output 262 our $base_filename; # Optional name of file containing baseline data 263 our $desc_filename; # Name of file containing test descriptions 264 our $css_filename; # Optional name of external stylesheet file to use 265 our $quiet; # If set, suppress information messages 266 our $help; # Help option flag 267 our $version; # Version option flag 268 our $show_details; # If set, generate detailed directory view 269 our $no_prefix; # If set, do not remove filename prefix 270 our $func_coverage; # If set, generate function coverage statistics 271 our $no_func_coverage; # Disable func_coverage 272 our $br_coverage; # If set, generate branch coverage statistics 273 our $no_br_coverage; # Disable br_coverage 274 our $sort = 1; # If set, provide directory listings with sorted entries 275 our $no_sort; # Disable sort 276 our $frames; # If set, use frames for source code view 277 our $keep_descriptions; # If set, do not remove unused test case descriptions 278 our $no_sourceview; # If set, do not create a source code view for each file 279 our $highlight; # If set, highlight lines covered by converted data only 280 our $legend; # If set, include legend in output 281 our $tab_size = 8; # Number of spaces to use in place of tab 282 our $config; # Configuration file contents 283 our $html_prolog_file; # Custom HTML prolog file (up to and including <body>) 284 our $html_epilog_file; # Custom HTML epilog file (from </body> onwards) 285 our $html_prolog; # Actual HTML prolog 286 our $html_epilog; # Actual HTML epilog 287 our $html_ext = "html"; # Extension for generated HTML files 288 our $html_gzip = 0; # Compress with gzip 289 our $demangle_cpp = 0; # Demangle C++ function names 290 our @opt_ignore_errors; # Ignore certain error classes during processing 291 our @ignore; 292 our $opt_config_file; # User-specified configuration file location 293 our %opt_rc; 294 our $charset = "UTF-8"; # Default charset for HTML pages 295 our @fileview_sortlist; 296 our @fileview_sortname = ("", "-sort-l", "-sort-f", "-sort-b"); 297 our @funcview_sortlist; 298 our @rate_name = ("Lo", "Med", "Hi"); 299 our @rate_png = ("ruby.png", "amber.png", "emerald.png"); 300 our $lcov_func_coverage = 1; 301 our $lcov_branch_coverage = 0; 302 303 our $cwd = `pwd`; # Current working directory 304 chomp($cwd); 305 our $tool_dir = dirname($0); # Directory where genhtml tool is installed 306 307 308 # 309 # Code entry point 310 # 311 312 $SIG{__WARN__} = \&warn_handler; 313 $SIG{__DIE__} = \&die_handler; 314 315 # Prettify version string 316 $lcov_version =~ s/\$\s*Revision\s*:?\s*(\S+)\s*\$/$1/; 317 318 # Add current working directory if $tool_dir is not already an absolute path 319 if (! ($tool_dir =~ /^\/(.*)$/)) 320 { 321 $tool_dir = "$cwd/$tool_dir"; 322 } 323 324 # Check command line for a configuration file name 325 Getopt::Long::Configure("pass_through", "no_auto_abbrev"); 326 GetOptions("config-file=s" => \$opt_config_file, 327 "rc=s%" => \%opt_rc); 328 Getopt::Long::Configure("default"); 329 330 # Read configuration file if available 331 if (defined($opt_config_file)) { 332 $config = read_config($opt_config_file); 333 } elsif (defined($ENV{"HOME"}) && (-r $ENV{"HOME"}."/.lcovrc")) 334 { 335 $config = read_config($ENV{"HOME"}."/.lcovrc"); 336 } 337 elsif (-r "/etc/lcovrc") 338 { 339 $config = read_config("/etc/lcovrc"); 340 } 341 342 if ($config || %opt_rc) 343 { 344 # Copy configuration file and --rc values to variables 345 apply_config({ 346 "genhtml_css_file" => \$css_filename, 347 "genhtml_hi_limit" => \$hi_limit, 348 "genhtml_med_limit" => \$med_limit, 349 "genhtml_line_field_width" => \$line_field_width, 350 "genhtml_overview_width" => \$overview_width, 351 "genhtml_nav_resolution" => \$nav_resolution, 352 "genhtml_nav_offset" => \$nav_offset, 353 "genhtml_keep_descriptions" => \$keep_descriptions, 354 "genhtml_no_prefix" => \$no_prefix, 355 "genhtml_no_source" => \$no_sourceview, 356 "genhtml_num_spaces" => \$tab_size, 357 "genhtml_highlight" => \$highlight, 358 "genhtml_legend" => \$legend, 359 "genhtml_html_prolog" => \$html_prolog_file, 360 "genhtml_html_epilog" => \$html_epilog_file, 361 "genhtml_html_extension" => \$html_ext, 362 "genhtml_html_gzip" => \$html_gzip, 363 "genhtml_function_hi_limit" => \$fn_hi_limit, 364 "genhtml_function_med_limit" => \$fn_med_limit, 365 "genhtml_function_coverage" => \$func_coverage, 366 "genhtml_branch_hi_limit" => \$br_hi_limit, 367 "genhtml_branch_med_limit" => \$br_med_limit, 368 "genhtml_branch_coverage" => \$br_coverage, 369 "genhtml_branch_field_width" => \$br_field_width, 370 "genhtml_sort" => \$sort, 371 "genhtml_charset" => \$charset, 372 "lcov_function_coverage" => \$lcov_func_coverage, 373 "lcov_branch_coverage" => \$lcov_branch_coverage, 374 }); 375 } 376 377 # Copy related values if not specified 378 $fn_hi_limit = $hi_limit if (!defined($fn_hi_limit)); 379 $fn_med_limit = $med_limit if (!defined($fn_med_limit)); 380 $br_hi_limit = $hi_limit if (!defined($br_hi_limit)); 381 $br_med_limit = $med_limit if (!defined($br_med_limit)); 382 $func_coverage = $lcov_func_coverage if (!defined($func_coverage)); 383 $br_coverage = $lcov_branch_coverage if (!defined($br_coverage)); 384 385 # Parse command line options 386 if (!GetOptions("output-directory|o=s" => \$output_directory, 387 "title|t=s" => \$test_title, 388 "description-file|d=s" => \$desc_filename, 389 "keep-descriptions|k" => \$keep_descriptions, 390 "css-file|c=s" => \$css_filename, 391 "baseline-file|b=s" => \$base_filename, 392 "prefix|p=s" => \$dir_prefix, 393 "num-spaces=i" => \$tab_size, 394 "no-prefix" => \$no_prefix, 395 "no-sourceview" => \$no_sourceview, 396 "show-details|s" => \$show_details, 397 "frames|f" => \$frames, 398 "highlight" => \$highlight, 399 "legend" => \$legend, 400 "quiet|q" => \$quiet, 401 "help|h|?" => \$help, 402 "version|v" => \$version, 403 "html-prolog=s" => \$html_prolog_file, 404 "html-epilog=s" => \$html_epilog_file, 405 "html-extension=s" => \$html_ext, 406 "html-gzip" => \$html_gzip, 407 "function-coverage" => \$func_coverage, 408 "no-function-coverage" => \$no_func_coverage, 409 "branch-coverage" => \$br_coverage, 410 "no-branch-coverage" => \$no_br_coverage, 411 "sort" => \$sort, 412 "no-sort" => \$no_sort, 413 "demangle-cpp" => \$demangle_cpp, 414 "ignore-errors=s" => \@opt_ignore_errors, 415 "config-file=s" => \$opt_config_file, 416 "rc=s%" => \%opt_rc, 417 )) 418 { 419 print(STDERR "Use $tool_name --help to get usage information\n"); 420 exit(1); 421 } else { 422 # Merge options 423 if ($no_func_coverage) { 424 $func_coverage = 0; 425 } 426 if ($no_br_coverage) { 427 $br_coverage = 0; 428 } 429 430 # Merge sort options 431 if ($no_sort) { 432 $sort = 0; 433 } 434 } 435 436 @info_filenames = @ARGV; 437 438 # Check for help option 439 if ($help) 440 { 441 print_usage(*STDOUT); 442 exit(0); 443 } 444 445 # Check for version option 446 if ($version) 447 { 448 print("$tool_name: $lcov_version\n"); 449 exit(0); 450 } 451 452 # Determine which errors the user wants us to ignore 453 parse_ignore_errors(@opt_ignore_errors); 454 455 # Check for info filename 456 if (!@info_filenames) 457 { 458 die("No filename specified\n". 459 "Use $tool_name --help to get usage information\n"); 460 } 461 462 # Generate a title if none is specified 463 if (!$test_title) 464 { 465 if (scalar(@info_filenames) == 1) 466 { 467 # Only one filename specified, use it as title 468 $test_title = basename($info_filenames[0]); 469 } 470 else 471 { 472 # More than one filename specified, used default title 473 $test_title = "unnamed"; 474 } 475 } 476 477 # Make sure css_filename is an absolute path (in case we're changing 478 # directories) 479 if ($css_filename) 480 { 481 if (!($css_filename =~ /^\/(.*)$/)) 482 { 483 $css_filename = $cwd."/".$css_filename; 484 } 485 } 486 487 # Make sure tab_size is within valid range 488 if ($tab_size < 1) 489 { 490 print(STDERR "ERROR: invalid number of spaces specified: ". 491 "$tab_size!\n"); 492 exit(1); 493 } 494 495 # Get HTML prolog and epilog 496 $html_prolog = get_html_prolog($html_prolog_file); 497 $html_epilog = get_html_epilog($html_epilog_file); 498 499 # Issue a warning if --no-sourceview is enabled together with --frames 500 if ($no_sourceview && defined($frames)) 501 { 502 warn("WARNING: option --frames disabled because --no-sourceview ". 503 "was specified!\n"); 504 $frames = undef; 505 } 506 507 # Issue a warning if --no-prefix is enabled together with --prefix 508 if ($no_prefix && defined($dir_prefix)) 509 { 510 warn("WARNING: option --prefix disabled because --no-prefix was ". 511 "specified!\n"); 512 $dir_prefix = undef; 513 } 514 515 @fileview_sortlist = ($SORT_FILE); 516 @funcview_sortlist = ($SORT_FILE); 517 518 if ($sort) { 519 push(@fileview_sortlist, $SORT_LINE); 520 push(@fileview_sortlist, $SORT_FUNC) if ($func_coverage); 521 push(@fileview_sortlist, $SORT_BRANCH) if ($br_coverage); 522 push(@funcview_sortlist, $SORT_LINE); 523 } 524 525 if ($frames) 526 { 527 # Include genpng code needed for overview image generation 528 do("$tool_dir/genpng"); 529 } 530 531 # Ensure that the c++filt tool is available when using --demangle-cpp 532 if ($demangle_cpp) 533 { 534 if (system_no_output(3, "c++filt", "--version")) { 535 die("ERROR: could not find c++filt tool needed for ". 536 "--demangle-cpp\n"); 537 } 538 } 539 540 # Make sure output_directory exists, create it if necessary 541 if ($output_directory) 542 { 543 stat($output_directory); 544 545 if (! -e _) 546 { 547 create_sub_dir($output_directory); 548 } 549 } 550 551 # Do something 552 gen_html(); 553 554 exit(0); 555 556 557 558 # 559 # print_usage(handle) 560 # 561 # Print usage information. 562 # 563 564 sub print_usage(*) 565 { 566 local *HANDLE = $_[0]; 567 568 print(HANDLE <<END_OF_USAGE); 569 Usage: $tool_name [OPTIONS] INFOFILE(S) 570 571 Create HTML output for coverage data found in INFOFILE. Note that INFOFILE 572 may also be a list of filenames. 573 574 Misc: 575 -h, --help Print this help, then exit 576 -v, --version Print version number, then exit 577 -q, --quiet Do not print progress messages 578 --config-file FILENAME Specify configuration file location 579 --rc SETTING=VALUE Override configuration file setting 580 --ignore-errors ERRORS Continue after ERRORS (source) 581 582 Operation: 583 -o, --output-directory OUTDIR Write HTML output to OUTDIR 584 -s, --show-details Generate detailed directory view 585 -d, --description-file DESCFILE Read test case descriptions from DESCFILE 586 -k, --keep-descriptions Do not remove unused test descriptions 587 -b, --baseline-file BASEFILE Use BASEFILE as baseline file 588 -p, --prefix PREFIX Remove PREFIX from all directory names 589 --no-prefix Do not remove prefix from directory names 590 --(no-)function-coverage Enable (disable) function coverage display 591 --(no-)branch-coverage Enable (disable) branch coverage display 592 593 HTML output: 594 -f, --frames Use HTML frames for source code view 595 -t, --title TITLE Display TITLE in header of all pages 596 -c, --css-file CSSFILE Use external style sheet file CSSFILE 597 --no-source Do not create source code view 598 --num-spaces NUM Replace tabs with NUM spaces in source view 599 --highlight Highlight lines with converted-only data 600 --legend Include color legend in HTML output 601 --html-prolog FILE Use FILE as HTML prolog for generated pages 602 --html-epilog FILE Use FILE as HTML epilog for generated pages 603 --html-extension EXT Use EXT as filename extension for pages 604 --html-gzip Use gzip to compress HTML 605 --(no-)sort Enable (disable) sorted coverage views 606 --demangle-cpp Demangle C++ function names 607 608 For more information see: $lcov_url 609 END_OF_USAGE 610 ; 611 } 612 613 614 # 615 # get_rate(found, hit) 616 # 617 # Return a relative value for the specified found&hit values 618 # which is used for sorting the corresponding entries in a 619 # file list. 620 # 621 622 sub get_rate($$) 623 { 624 my ($found, $hit) = @_; 625 626 if ($found == 0) { 627 return 10000; 628 } 629 return int($hit * 1000 / $found) * 10 + 2 - (1 / $found); 630 } 631 632 633 # 634 # get_overall_line(found, hit, name_singular, name_plural) 635 # 636 # Return a string containing overall information for the specified 637 # found/hit data. 638 # 639 640 sub get_overall_line($$$$) 641 { 642 my ($found, $hit, $name_sn, $name_pl) = @_; 643 my $name; 644 645 return "no data found" if (!defined($found) || $found == 0); 646 $name = ($found == 1) ? $name_sn : $name_pl; 647 return rate($hit, $found, "% ($hit of $found $name)"); 648 } 649 650 651 # 652 # print_overall_rate(ln_do, ln_found, ln_hit, fn_do, fn_found, fn_hit, br_do 653 # br_found, br_hit) 654 # 655 # Print overall coverage rates for the specified coverage types. 656 # 657 658 sub print_overall_rate($$$$$$$$$) 659 { 660 my ($ln_do, $ln_found, $ln_hit, $fn_do, $fn_found, $fn_hit, 661 $br_do, $br_found, $br_hit) = @_; 662 663 info("Overall coverage rate:\n"); 664 info(" lines......: %s\n", 665 get_overall_line($ln_found, $ln_hit, "line", "lines")) 666 if ($ln_do); 667 info(" functions..: %s\n", 668 get_overall_line($fn_found, $fn_hit, "function", "functions")) 669 if ($fn_do); 670 info(" branches...: %s\n", 671 get_overall_line($br_found, $br_hit, "branch", "branches")) 672 if ($br_do); 673 } 674 675 676 # 677 # gen_html() 678 # 679 # Generate a set of HTML pages from contents of .info file INFO_FILENAME. 680 # Files will be written to the current directory. If provided, test case 681 # descriptions will be read from .tests file TEST_FILENAME and included 682 # in ouput. 683 # 684 # Die on error. 685 # 686 687 sub gen_html() 688 { 689 local *HTML_HANDLE; 690 my %overview; 691 my %base_data; 692 my $lines_found; 693 my $lines_hit; 694 my $fn_found; 695 my $fn_hit; 696 my $br_found; 697 my $br_hit; 698 my $overall_found = 0; 699 my $overall_hit = 0; 700 my $total_fn_found = 0; 701 my $total_fn_hit = 0; 702 my $total_br_found = 0; 703 my $total_br_hit = 0; 704 my $dir_name; 705 my $link_name; 706 my @dir_list; 707 my %new_info; 708 709 # Read in all specified .info files 710 foreach (@info_filenames) 711 { 712 %new_info = %{read_info_file($_)}; 713 714 # Combine %new_info with %info_data 715 %info_data = %{combine_info_files(\%info_data, \%new_info)}; 716 } 717 718 info("Found %d entries.\n", scalar(keys(%info_data))); 719 720 # Read and apply baseline data if specified 721 if ($base_filename) 722 { 723 # Read baseline file 724 info("Reading baseline file $base_filename\n"); 725 %base_data = %{read_info_file($base_filename)}; 726 info("Found %d entries.\n", scalar(keys(%base_data))); 727 728 # Apply baseline 729 info("Subtracting baseline data.\n"); 730 %info_data = %{apply_baseline(\%info_data, \%base_data)}; 731 } 732 733 @dir_list = get_dir_list(keys(%info_data)); 734 735 if ($no_prefix) 736 { 737 # User requested that we leave filenames alone 738 info("User asked not to remove filename prefix\n"); 739 } 740 elsif (!defined($dir_prefix)) 741 { 742 # Get prefix common to most directories in list 743 $dir_prefix = get_prefix(1, keys(%info_data)); 744 745 if ($dir_prefix) 746 { 747 info("Found common filename prefix \"$dir_prefix\"\n"); 748 } 749 else 750 { 751 info("No common filename prefix found!\n"); 752 $no_prefix=1; 753 } 754 } 755 else 756 { 757 info("Using user-specified filename prefix \"". 758 "$dir_prefix\"\n"); 759 } 760 761 # Read in test description file if specified 762 if ($desc_filename) 763 { 764 info("Reading test description file $desc_filename\n"); 765 %test_description = %{read_testfile($desc_filename)}; 766 767 # Remove test descriptions which are not referenced 768 # from %info_data if user didn't tell us otherwise 769 if (!$keep_descriptions) 770 { 771 remove_unused_descriptions(); 772 } 773 } 774 775 # Change to output directory if specified 776 if ($output_directory) 777 { 778 chdir($output_directory) 779 or die("ERROR: cannot change to directory ". 780 "$output_directory!\n"); 781 } 782 783 info("Writing .css and .png files.\n"); 784 write_css_file(); 785 write_png_files(); 786 787 if ($html_gzip) 788 { 789 info("Writing .htaccess file.\n"); 790 write_htaccess_file(); 791 } 792 793 info("Generating output.\n"); 794 795 # Process each subdirectory and collect overview information 796 foreach $dir_name (@dir_list) 797 { 798 ($lines_found, $lines_hit, $fn_found, $fn_hit, 799 $br_found, $br_hit) 800 = process_dir($dir_name); 801 802 # Handle files in root directory gracefully 803 $dir_name = "root" if ($dir_name eq ""); 804 805 # Remove prefix if applicable 806 if (!$no_prefix && $dir_prefix) 807 { 808 # Match directory names beginning with $dir_prefix 809 $dir_name = apply_prefix($dir_name, $dir_prefix); 810 } 811 812 # Generate name for directory overview HTML page 813 if ($dir_name =~ /^\/(.*)$/) 814 { 815 $link_name = substr($dir_name, 1)."/index.$html_ext"; 816 } 817 else 818 { 819 $link_name = $dir_name."/index.$html_ext"; 820 } 821 822 $overview{$dir_name} = [$lines_found, $lines_hit, $fn_found, 823 $fn_hit, $br_found, $br_hit, $link_name, 824 get_rate($lines_found, $lines_hit), 825 get_rate($fn_found, $fn_hit), 826 get_rate($br_found, $br_hit)]; 827 $overall_found += $lines_found; 828 $overall_hit += $lines_hit; 829 $total_fn_found += $fn_found; 830 $total_fn_hit += $fn_hit; 831 $total_br_found += $br_found; 832 $total_br_hit += $br_hit; 833 } 834 835 # Generate overview page 836 info("Writing directory view page.\n"); 837 838 # Create sorted pages 839 foreach (@fileview_sortlist) { 840 write_dir_page($fileview_sortname[$_], ".", "", $test_title, 841 undef, $overall_found, $overall_hit, 842 $total_fn_found, $total_fn_hit, $total_br_found, 843 $total_br_hit, \%overview, {}, {}, {}, 0, $_); 844 } 845 846 # Check if there are any test case descriptions to write out 847 if (%test_description) 848 { 849 info("Writing test case description file.\n"); 850 write_description_file( \%test_description, 851 $overall_found, $overall_hit, 852 $total_fn_found, $total_fn_hit, 853 $total_br_found, $total_br_hit); 854 } 855 856 print_overall_rate(1, $overall_found, $overall_hit, 857 $func_coverage, $total_fn_found, $total_fn_hit, 858 $br_coverage, $total_br_found, $total_br_hit); 859 860 chdir($cwd); 861 } 862 863 # 864 # html_create(handle, filename) 865 # 866 867 sub html_create($$) 868 { 869 my $handle = $_[0]; 870 my $filename = $_[1]; 871 872 if ($html_gzip) 873 { 874 open($handle, "|-", "gzip -c >'$filename'") 875 or die("ERROR: cannot open $filename for writing ". 876 "(gzip)!\n"); 877 } 878 else 879 { 880 open($handle, ">", $filename) 881 or die("ERROR: cannot open $filename for writing!\n"); 882 } 883 } 884 885 sub write_dir_page($$$$$$$$$$$$$$$$$) 886 { 887 my ($name, $rel_dir, $base_dir, $title, $trunc_dir, $overall_found, 888 $overall_hit, $total_fn_found, $total_fn_hit, $total_br_found, 889 $total_br_hit, $overview, $testhash, $testfnchash, $testbrhash, 890 $view_type, $sort_type) = @_; 891 892 # Generate directory overview page including details 893 html_create(*HTML_HANDLE, "$rel_dir/index$name.$html_ext"); 894 if (!defined($trunc_dir)) { 895 $trunc_dir = ""; 896 } 897 $title .= " - " if ($trunc_dir ne ""); 898 write_html_prolog(*HTML_HANDLE, $base_dir, "LCOV - $title$trunc_dir"); 899 write_header(*HTML_HANDLE, $view_type, $trunc_dir, $rel_dir, 900 $overall_found, $overall_hit, $total_fn_found, 901 $total_fn_hit, $total_br_found, $total_br_hit, $sort_type); 902 write_file_table(*HTML_HANDLE, $base_dir, $overview, $testhash, 903 $testfnchash, $testbrhash, $view_type, $sort_type); 904 write_html_epilog(*HTML_HANDLE, $base_dir); 905 close(*HTML_HANDLE); 906 } 907 908 909 # 910 # process_dir(dir_name) 911 # 912 913 sub process_dir($) 914 { 915 my $abs_dir = $_[0]; 916 my $trunc_dir; 917 my $rel_dir = $abs_dir; 918 my $base_dir; 919 my $filename; 920 my %overview; 921 my $lines_found; 922 my $lines_hit; 923 my $fn_found; 924 my $fn_hit; 925 my $br_found; 926 my $br_hit; 927 my $overall_found=0; 928 my $overall_hit=0; 929 my $total_fn_found=0; 930 my $total_fn_hit=0; 931 my $total_br_found = 0; 932 my $total_br_hit = 0; 933 my $base_name; 934 my $extension; 935 my $testdata; 936 my %testhash; 937 my $testfncdata; 938 my %testfnchash; 939 my $testbrdata; 940 my %testbrhash; 941 my @sort_list; 942 local *HTML_HANDLE; 943 944 # Remove prefix if applicable 945 if (!$no_prefix) 946 { 947 # Match directory name beginning with $dir_prefix 948 $rel_dir = apply_prefix($rel_dir, $dir_prefix); 949 } 950 951 $trunc_dir = $rel_dir; 952 953 # Remove leading / 954 if ($rel_dir =~ /^\/(.*)$/) 955 { 956 $rel_dir = substr($rel_dir, 1); 957 } 958 959 # Handle files in root directory gracefully 960 $rel_dir = "root" if ($rel_dir eq ""); 961 $trunc_dir = "root" if ($trunc_dir eq ""); 962 963 $base_dir = get_relative_base_path($rel_dir); 964 965 create_sub_dir($rel_dir); 966 967 # Match filenames which specify files in this directory, not including 968 # sub-directories 969 foreach $filename (grep(/^\Q$abs_dir\E\/[^\/]*$/,keys(%info_data))) 970 { 971 my $page_link; 972 my $func_link; 973 974 ($lines_found, $lines_hit, $fn_found, $fn_hit, $br_found, 975 $br_hit, $testdata, $testfncdata, $testbrdata) = 976 process_file($trunc_dir, $rel_dir, $filename); 977 978 $base_name = basename($filename); 979 980 if ($no_sourceview) { 981 $page_link = ""; 982 } elsif ($frames) { 983 # Link to frameset page 984 $page_link = "$base_name.gcov.frameset.$html_ext"; 985 } else { 986 # Link directory to source code view page 987 $page_link = "$base_name.gcov.$html_ext"; 988 } 989 $overview{$base_name} = [$lines_found, $lines_hit, $fn_found, 990 $fn_hit, $br_found, $br_hit, 991 $page_link, 992 get_rate($lines_found, $lines_hit), 993 get_rate($fn_found, $fn_hit), 994 get_rate($br_found, $br_hit)]; 995 996 $testhash{$base_name} = $testdata; 997 $testfnchash{$base_name} = $testfncdata; 998 $testbrhash{$base_name} = $testbrdata; 999 1000 $overall_found += $lines_found; 1001 $overall_hit += $lines_hit; 1002 1003 $total_fn_found += $fn_found; 1004 $total_fn_hit += $fn_hit; 1005 1006 $total_br_found += $br_found; 1007 $total_br_hit += $br_hit; 1008 } 1009 1010 # Create sorted pages 1011 foreach (@fileview_sortlist) { 1012 # Generate directory overview page (without details) 1013 write_dir_page($fileview_sortname[$_], $rel_dir, $base_dir, 1014 $test_title, $trunc_dir, $overall_found, 1015 $overall_hit, $total_fn_found, $total_fn_hit, 1016 $total_br_found, $total_br_hit, \%overview, {}, 1017 {}, {}, 1, $_); 1018 if (!$show_details) { 1019 next; 1020 } 1021 # Generate directory overview page including details 1022 write_dir_page("-detail".$fileview_sortname[$_], $rel_dir, 1023 $base_dir, $test_title, $trunc_dir, 1024 $overall_found, $overall_hit, $total_fn_found, 1025 $total_fn_hit, $total_br_found, $total_br_hit, 1026 \%overview, \%testhash, \%testfnchash, 1027 \%testbrhash, 1, $_); 1028 } 1029 1030 # Calculate resulting line counts 1031 return ($overall_found, $overall_hit, $total_fn_found, $total_fn_hit, 1032 $total_br_found, $total_br_hit); 1033 } 1034 1035 1036 # 1037 # get_converted_lines(testdata) 1038 # 1039 # Return hash of line numbers of those lines which were only covered in 1040 # converted data sets. 1041 # 1042 1043 sub get_converted_lines($) 1044 { 1045 my $testdata = $_[0]; 1046 my $testcount; 1047 my %converted; 1048 my %nonconverted; 1049 my $hash; 1050 my $testcase; 1051 my $line; 1052 my %result; 1053 1054 1055 # Get a hash containing line numbers with positive counts both for 1056 # converted and original data sets 1057 foreach $testcase (keys(%{$testdata})) 1058 { 1059 # Check to see if this is a converted data set 1060 if ($testcase =~ /,diff$/) 1061 { 1062 $hash = \%converted; 1063 } 1064 else 1065 { 1066 $hash = \%nonconverted; 1067 } 1068 1069 $testcount = $testdata->{$testcase}; 1070 # Add lines with a positive count to hash 1071 foreach $line (keys%{$testcount}) 1072 { 1073 if ($testcount->{$line} > 0) 1074 { 1075 $hash->{$line} = 1; 1076 } 1077 } 1078 } 1079 1080 # Combine both hashes to resulting list 1081 foreach $line (keys(%converted)) 1082 { 1083 if (!defined($nonconverted{$line})) 1084 { 1085 $result{$line} = 1; 1086 } 1087 } 1088 1089 return \%result; 1090 } 1091 1092 1093 sub write_function_page($$$$$$$$$$$$$$$$$$) 1094 { 1095 my ($base_dir, $rel_dir, $trunc_dir, $base_name, $title, 1096 $lines_found, $lines_hit, $fn_found, $fn_hit, $br_found, $br_hit, 1097 $sumcount, $funcdata, $sumfnccount, $testfncdata, $sumbrcount, 1098 $testbrdata, $sort_type) = @_; 1099 my $pagetitle; 1100 my $filename; 1101 1102 # Generate function table for this file 1103 if ($sort_type == 0) { 1104 $filename = "$rel_dir/$base_name.func.$html_ext"; 1105 } else { 1106 $filename = "$rel_dir/$base_name.func-sort-c.$html_ext"; 1107 } 1108 html_create(*HTML_HANDLE, $filename); 1109 $pagetitle = "LCOV - $title - $trunc_dir/$base_name - functions"; 1110 write_html_prolog(*HTML_HANDLE, $base_dir, $pagetitle); 1111 write_header(*HTML_HANDLE, 4, "$trunc_dir/$base_name", 1112 "$rel_dir/$base_name", $lines_found, $lines_hit, 1113 $fn_found, $fn_hit, $br_found, $br_hit, $sort_type); 1114 write_function_table(*HTML_HANDLE, "$base_name.gcov.$html_ext", 1115 $sumcount, $funcdata, 1116 $sumfnccount, $testfncdata, $sumbrcount, 1117 $testbrdata, $base_name, 1118 $base_dir, $sort_type); 1119 write_html_epilog(*HTML_HANDLE, $base_dir, 1); 1120 close(*HTML_HANDLE); 1121 } 1122 1123 1124 # 1125 # process_file(trunc_dir, rel_dir, filename) 1126 # 1127 1128 sub process_file($$$) 1129 { 1130 info("Processing file ".apply_prefix($_[2], $dir_prefix)."\n"); 1131 1132 my $trunc_dir = $_[0]; 1133 my $rel_dir = $_[1]; 1134 my $filename = $_[2]; 1135 my $base_name = basename($filename); 1136 my $base_dir = get_relative_base_path($rel_dir); 1137 my $testdata; 1138 my $testcount; 1139 my $sumcount; 1140 my $funcdata; 1141 my $checkdata; 1142 my $testfncdata; 1143 my $sumfnccount; 1144 my $testbrdata; 1145 my $sumbrcount; 1146 my $lines_found; 1147 my $lines_hit; 1148 my $fn_found; 1149 my $fn_hit; 1150 my $br_found; 1151 my $br_hit; 1152 my $converted; 1153 my @source; 1154 my $pagetitle; 1155 local *HTML_HANDLE; 1156 1157 ($testdata, $sumcount, $funcdata, $checkdata, $testfncdata, 1158 $sumfnccount, $testbrdata, $sumbrcount, $lines_found, $lines_hit, 1159 $fn_found, $fn_hit, $br_found, $br_hit) 1160 = get_info_entry($info_data{$filename}); 1161 1162 # Return after this point in case user asked us not to generate 1163 # source code view 1164 if ($no_sourceview) 1165 { 1166 return ($lines_found, $lines_hit, $fn_found, $fn_hit, 1167 $br_found, $br_hit, $testdata, $testfncdata, 1168 $testbrdata); 1169 } 1170 1171 $converted = get_converted_lines($testdata); 1172 # Generate source code view for this file 1173 html_create(*HTML_HANDLE, "$rel_dir/$base_name.gcov.$html_ext"); 1174 $pagetitle = "LCOV - $test_title - $trunc_dir/$base_name"; 1175 write_html_prolog(*HTML_HANDLE, $base_dir, $pagetitle); 1176 write_header(*HTML_HANDLE, 2, "$trunc_dir/$base_name", 1177 "$rel_dir/$base_name", $lines_found, $lines_hit, 1178 $fn_found, $fn_hit, $br_found, $br_hit, 0); 1179 @source = write_source(*HTML_HANDLE, $filename, $sumcount, $checkdata, 1180 $converted, $funcdata, $sumbrcount); 1181 1182 write_html_epilog(*HTML_HANDLE, $base_dir, 1); 1183 close(*HTML_HANDLE); 1184 1185 if ($func_coverage) { 1186 # Create function tables 1187 foreach (@funcview_sortlist) { 1188 write_function_page($base_dir, $rel_dir, $trunc_dir, 1189 $base_name, $test_title, 1190 $lines_found, $lines_hit, 1191 $fn_found, $fn_hit, $br_found, 1192 $br_hit, $sumcount, 1193 $funcdata, $sumfnccount, 1194 $testfncdata, $sumbrcount, 1195 $testbrdata, $_); 1196 } 1197 } 1198 1199 # Additional files are needed in case of frame output 1200 if (!$frames) 1201 { 1202 return ($lines_found, $lines_hit, $fn_found, $fn_hit, 1203 $br_found, $br_hit, $testdata, $testfncdata, 1204 $testbrdata); 1205 } 1206 1207 # Create overview png file 1208 gen_png("$rel_dir/$base_name.gcov.png", $overview_width, $tab_size, 1209 @source); 1210 1211 # Create frameset page 1212 html_create(*HTML_HANDLE, 1213 "$rel_dir/$base_name.gcov.frameset.$html_ext"); 1214 write_frameset(*HTML_HANDLE, $base_dir, $base_name, $pagetitle); 1215 close(*HTML_HANDLE); 1216 1217 # Write overview frame 1218 html_create(*HTML_HANDLE, 1219 "$rel_dir/$base_name.gcov.overview.$html_ext"); 1220 write_overview(*HTML_HANDLE, $base_dir, $base_name, $pagetitle, 1221 scalar(@source)); 1222 close(*HTML_HANDLE); 1223 1224 return ($lines_found, $lines_hit, $fn_found, $fn_hit, $br_found, 1225 $br_hit, $testdata, $testfncdata, $testbrdata); 1226 } 1227 1228 1229 # 1230 # read_info_file(info_filename) 1231 # 1232 # Read in the contents of the .info file specified by INFO_FILENAME. Data will 1233 # be returned as a reference to a hash containing the following mappings: 1234 # 1235 # %result: for each filename found in file -> \%data 1236 # 1237 # %data: "test" -> \%testdata 1238 # "sum" -> \%sumcount 1239 # "func" -> \%funcdata 1240 # "found" -> $lines_found (number of instrumented lines found in file) 1241 # "hit" -> $lines_hit (number of executed lines in file) 1242 # "check" -> \%checkdata 1243 # "testfnc" -> \%testfncdata 1244 # "sumfnc" -> \%sumfnccount 1245 # "testbr" -> \%testbrdata 1246 # "sumbr" -> \%sumbrcount 1247 # 1248 # %testdata : name of test affecting this file -> \%testcount 1249 # %testfncdata: name of test affecting this file -> \%testfnccount 1250 # %testbrdata: name of test affecting this file -> \%testbrcount 1251 # 1252 # %testcount : line number -> execution count for a single test 1253 # %testfnccount: function name -> execution count for a single test 1254 # %testbrcount : line number -> branch coverage data for a single test 1255 # %sumcount : line number -> execution count for all tests 1256 # %sumfnccount : function name -> execution count for all tests 1257 # %sumbrcount : line number -> branch coverage data for all tests 1258 # %funcdata : function name -> line number 1259 # %checkdata : line number -> checksum of source code line 1260 # $brdata : vector of items: block, branch, taken 1261 # 1262 # Note that .info file sections referring to the same file and test name 1263 # will automatically be combined by adding all execution counts. 1264 # 1265 # Note that if INFO_FILENAME ends with ".gz", it is assumed that the file 1266 # is compressed using GZIP. If available, GUNZIP will be used to decompress 1267 # this file. 1268 # 1269 # Die on error. 1270 # 1271 1272 sub read_info_file($) 1273 { 1274 my $tracefile = $_[0]; # Name of tracefile 1275 my %result; # Resulting hash: file -> data 1276 my $data; # Data handle for current entry 1277 my $testdata; # " " 1278 my $testcount; # " " 1279 my $sumcount; # " " 1280 my $funcdata; # " " 1281 my $checkdata; # " " 1282 my $testfncdata; 1283 my $testfnccount; 1284 my $sumfnccount; 1285 my $testbrdata; 1286 my $testbrcount; 1287 my $sumbrcount; 1288 my $line; # Current line read from .info file 1289 my $testname; # Current test name 1290 my $filename; # Current filename 1291 my $hitcount; # Count for lines hit 1292 my $count; # Execution count of current line 1293 my $negative; # If set, warn about negative counts 1294 my $changed_testname; # If set, warn about changed testname 1295 my $line_checksum; # Checksum of current line 1296 my $br_found; 1297 my $br_hit; 1298 local *INFO_HANDLE; # Filehandle for .info file 1299 1300 info("Reading data file $tracefile\n"); 1301 1302 # Check if file exists and is readable 1303 stat($_[0]); 1304 if (!(-r _)) 1305 { 1306 die("ERROR: cannot read file $_[0]!\n"); 1307 } 1308 1309 # Check if this is really a plain file 1310 if (!(-f _)) 1311 { 1312 die("ERROR: not a plain file: $_[0]!\n"); 1313 } 1314 1315 # Check for .gz extension 1316 if ($_[0] =~ /\.gz$/) 1317 { 1318 # Check for availability of GZIP tool 1319 system_no_output(1, "gunzip" ,"-h") 1320 and die("ERROR: gunzip command not available!\n"); 1321 1322 # Check integrity of compressed file 1323 system_no_output(1, "gunzip", "-t", $_[0]) 1324 and die("ERROR: integrity check failed for ". 1325 "compressed file $_[0]!\n"); 1326 1327 # Open compressed file 1328 open(INFO_HANDLE, "-|", "gunzip -c '$_[0]'") 1329 or die("ERROR: cannot start gunzip to decompress ". 1330 "file $_[0]!\n"); 1331 } 1332 else 1333 { 1334 # Open decompressed file 1335 open(INFO_HANDLE, "<", $_[0]) 1336 or die("ERROR: cannot read file $_[0]!\n"); 1337 } 1338 1339 $testname = ""; 1340 while (<INFO_HANDLE>) 1341 { 1342 chomp($_); 1343 $line = $_; 1344 1345 # Switch statement 1346 foreach ($line) 1347 { 1348 /^TN:([^,]*)(,diff)?/ && do 1349 { 1350 # Test name information found 1351 $testname = defined($1) ? $1 : ""; 1352 if ($testname =~ s/\W/_/g) 1353 { 1354 $changed_testname = 1; 1355 } 1356 $testname .= $2 if (defined($2)); 1357 last; 1358 }; 1359 1360 /^[SK]F:(.*)/ && do 1361 { 1362 # Filename information found 1363 # Retrieve data for new entry 1364 $filename = $1; 1365 1366 $data = $result{$filename}; 1367 ($testdata, $sumcount, $funcdata, $checkdata, 1368 $testfncdata, $sumfnccount, $testbrdata, 1369 $sumbrcount) = 1370 get_info_entry($data); 1371 1372 if (defined($testname)) 1373 { 1374 $testcount = $testdata->{$testname}; 1375 $testfnccount = $testfncdata->{$testname}; 1376 $testbrcount = $testbrdata->{$testname}; 1377 } 1378 else 1379 { 1380 $testcount = {}; 1381 $testfnccount = {}; 1382 $testbrcount = {}; 1383 } 1384 last; 1385 }; 1386 1387 /^DA:(\d+),(-?\d+)(,[^,\s]+)?/ && do 1388 { 1389 # Fix negative counts 1390 $count = $2 < 0 ? 0 : $2; 1391 if ($2 < 0) 1392 { 1393 $negative = 1; 1394 } 1395 # Execution count found, add to structure 1396 # Add summary counts 1397 $sumcount->{$1} += $count; 1398 1399 # Add test-specific counts 1400 if (defined($testname)) 1401 { 1402 $testcount->{$1} += $count; 1403 } 1404 1405 # Store line checksum if available 1406 if (defined($3)) 1407 { 1408 $line_checksum = substr($3, 1); 1409 1410 # Does it match a previous definition 1411 if (defined($checkdata->{$1}) && 1412 ($checkdata->{$1} ne 1413 $line_checksum)) 1414 { 1415 die("ERROR: checksum mismatch ". 1416 "at $filename:$1\n"); 1417 } 1418 1419 $checkdata->{$1} = $line_checksum; 1420 } 1421 last; 1422 }; 1423 1424 /^FN:(\d+),([^,]+)/ && do 1425 { 1426 last if (!$func_coverage); 1427 1428 # Function data found, add to structure 1429 $funcdata->{$2} = $1; 1430 1431 # Also initialize function call data 1432 if (!defined($sumfnccount->{$2})) { 1433 $sumfnccount->{$2} = 0; 1434 } 1435 if (defined($testname)) 1436 { 1437 if (!defined($testfnccount->{$2})) { 1438 $testfnccount->{$2} = 0; 1439 } 1440 } 1441 last; 1442 }; 1443 1444 /^FNDA:(\d+),([^,]+)/ && do 1445 { 1446 last if (!$func_coverage); 1447 # Function call count found, add to structure 1448 # Add summary counts 1449 $sumfnccount->{$2} += $1; 1450 1451 # Add test-specific counts 1452 if (defined($testname)) 1453 { 1454 $testfnccount->{$2} += $1; 1455 } 1456 last; 1457 }; 1458 1459 /^BRDA:(\d+),(\d+),(\d+),(\d+|-)/ && do { 1460 # Branch coverage data found 1461 my ($line, $block, $branch, $taken) = 1462 ($1, $2, $3, $4); 1463 1464 last if (!$br_coverage); 1465 $sumbrcount->{$line} = 1466 br_ivec_push($sumbrcount->{$line}, 1467 $block, $branch, $taken); 1468 1469 # Add test-specific counts 1470 if (defined($testname)) { 1471 $testbrcount->{$line} = 1472 br_ivec_push( 1473 $testbrcount->{$line}, 1474 $block, $branch, 1475 $taken); 1476 } 1477 last; 1478 }; 1479 1480 /^end_of_record/ && do 1481 { 1482 # Found end of section marker 1483 if ($filename) 1484 { 1485 # Store current section data 1486 if (defined($testname)) 1487 { 1488 $testdata->{$testname} = 1489 $testcount; 1490 $testfncdata->{$testname} = 1491 $testfnccount; 1492 $testbrdata->{$testname} = 1493 $testbrcount; 1494 } 1495 1496 set_info_entry($data, $testdata, 1497 $sumcount, $funcdata, 1498 $checkdata, $testfncdata, 1499 $sumfnccount, 1500 $testbrdata, 1501 $sumbrcount); 1502 $result{$filename} = $data; 1503 last; 1504 } 1505 }; 1506 1507 # default 1508 last; 1509 } 1510 } 1511 close(INFO_HANDLE); 1512 1513 # Calculate lines_found and lines_hit for each file 1514 foreach $filename (keys(%result)) 1515 { 1516 $data = $result{$filename}; 1517 1518 ($testdata, $sumcount, undef, undef, $testfncdata, 1519 $sumfnccount, $testbrdata, $sumbrcount) = 1520 get_info_entry($data); 1521 1522 # Filter out empty files 1523 if (scalar(keys(%{$sumcount})) == 0) 1524 { 1525 delete($result{$filename}); 1526 next; 1527 } 1528 # Filter out empty test cases 1529 foreach $testname (keys(%{$testdata})) 1530 { 1531 if (!defined($testdata->{$testname}) || 1532 scalar(keys(%{$testdata->{$testname}})) == 0) 1533 { 1534 delete($testdata->{$testname}); 1535 delete($testfncdata->{$testname}); 1536 } 1537 } 1538 1539 $data->{"found"} = scalar(keys(%{$sumcount})); 1540 $hitcount = 0; 1541 1542 foreach (keys(%{$sumcount})) 1543 { 1544 if ($sumcount->{$_} > 0) { $hitcount++; } 1545 } 1546 1547 $data->{"hit"} = $hitcount; 1548 1549 # Get found/hit values for function call data 1550 $data->{"f_found"} = scalar(keys(%{$sumfnccount})); 1551 $hitcount = 0; 1552 1553 foreach (keys(%{$sumfnccount})) { 1554 if ($sumfnccount->{$_} > 0) { 1555 $hitcount++; 1556 } 1557 } 1558 $data->{"f_hit"} = $hitcount; 1559 1560 # Get found/hit values for branch data 1561 ($br_found, $br_hit) = get_br_found_and_hit($sumbrcount); 1562 1563 $data->{"b_found"} = $br_found; 1564 $data->{"b_hit"} = $br_hit; 1565 } 1566 1567 if (scalar(keys(%result)) == 0) 1568 { 1569 die("ERROR: no valid records found in tracefile $tracefile\n"); 1570 } 1571 if ($negative) 1572 { 1573 warn("WARNING: negative counts found in tracefile ". 1574 "$tracefile\n"); 1575 } 1576 if ($changed_testname) 1577 { 1578 warn("WARNING: invalid characters removed from testname in ". 1579 "tracefile $tracefile\n"); 1580 } 1581 1582 return(\%result); 1583 } 1584 1585 1586 # 1587 # get_info_entry(hash_ref) 1588 # 1589 # Retrieve data from an entry of the structure generated by read_info_file(). 1590 # Return a list of references to hashes: 1591 # (test data hash ref, sum count hash ref, funcdata hash ref, checkdata hash 1592 # ref, testfncdata hash ref, sumfnccount hash ref, lines found, lines hit, 1593 # functions found, functions hit) 1594 # 1595 1596 sub get_info_entry($) 1597 { 1598 my $testdata_ref = $_[0]->{"test"}; 1599 my $sumcount_ref = $_[0]->{"sum"}; 1600 my $funcdata_ref = $_[0]->{"func"}; 1601 my $checkdata_ref = $_[0]->{"check"}; 1602 my $testfncdata = $_[0]->{"testfnc"}; 1603 my $sumfnccount = $_[0]->{"sumfnc"}; 1604 my $testbrdata = $_[0]->{"testbr"}; 1605 my $sumbrcount = $_[0]->{"sumbr"}; 1606 my $lines_found = $_[0]->{"found"}; 1607 my $lines_hit = $_[0]->{"hit"}; 1608 my $fn_found = $_[0]->{"f_found"}; 1609 my $fn_hit = $_[0]->{"f_hit"}; 1610 my $br_found = $_[0]->{"b_found"}; 1611 my $br_hit = $_[0]->{"b_hit"}; 1612 1613 return ($testdata_ref, $sumcount_ref, $funcdata_ref, $checkdata_ref, 1614 $testfncdata, $sumfnccount, $testbrdata, $sumbrcount, 1615 $lines_found, $lines_hit, $fn_found, $fn_hit, 1616 $br_found, $br_hit); 1617 } 1618 1619 1620 # 1621 # set_info_entry(hash_ref, testdata_ref, sumcount_ref, funcdata_ref, 1622 # checkdata_ref, testfncdata_ref, sumfcncount_ref, 1623 # testbrdata_ref, sumbrcount_ref[,lines_found, 1624 # lines_hit, f_found, f_hit, $b_found, $b_hit]) 1625 # 1626 # Update the hash referenced by HASH_REF with the provided data references. 1627 # 1628 1629 sub set_info_entry($$$$$$$$$;$$$$$$) 1630 { 1631 my $data_ref = $_[0]; 1632 1633 $data_ref->{"test"} = $_[1]; 1634 $data_ref->{"sum"} = $_[2]; 1635 $data_ref->{"func"} = $_[3]; 1636 $data_ref->{"check"} = $_[4]; 1637 $data_ref->{"testfnc"} = $_[5]; 1638 $data_ref->{"sumfnc"} = $_[6]; 1639 $data_ref->{"testbr"} = $_[7]; 1640 $data_ref->{"sumbr"} = $_[8]; 1641 1642 if (defined($_[9])) { $data_ref->{"found"} = $_[9]; } 1643 if (defined($_[10])) { $data_ref->{"hit"} = $_[10]; } 1644 if (defined($_[11])) { $data_ref->{"f_found"} = $_[11]; } 1645 if (defined($_[12])) { $data_ref->{"f_hit"} = $_[12]; } 1646 if (defined($_[13])) { $data_ref->{"b_found"} = $_[13]; } 1647 if (defined($_[14])) { $data_ref->{"b_hit"} = $_[14]; } 1648 } 1649 1650 1651 # 1652 # add_counts(data1_ref, data2_ref) 1653 # 1654 # DATA1_REF and DATA2_REF are references to hashes containing a mapping 1655 # 1656 # line number -> execution count 1657 # 1658 # Return a list (RESULT_REF, LINES_FOUND, LINES_HIT) where RESULT_REF 1659 # is a reference to a hash containing the combined mapping in which 1660 # execution counts are added. 1661 # 1662 1663 sub add_counts($$) 1664 { 1665 my %data1 = %{$_[0]}; # Hash 1 1666 my %data2 = %{$_[1]}; # Hash 2 1667 my %result; # Resulting hash 1668 my $line; # Current line iteration scalar 1669 my $data1_count; # Count of line in hash1 1670 my $data2_count; # Count of line in hash2 1671 my $found = 0; # Total number of lines found 1672 my $hit = 0; # Number of lines with a count > 0 1673 1674 foreach $line (keys(%data1)) 1675 { 1676 $data1_count = $data1{$line}; 1677 $data2_count = $data2{$line}; 1678 1679 # Add counts if present in both hashes 1680 if (defined($data2_count)) { $data1_count += $data2_count; } 1681 1682 # Store sum in %result 1683 $result{$line} = $data1_count; 1684 1685 $found++; 1686 if ($data1_count > 0) { $hit++; } 1687 } 1688 1689 # Add lines unique to data2 1690 foreach $line (keys(%data2)) 1691 { 1692 # Skip lines already in data1 1693 if (defined($data1{$line})) { next; } 1694 1695 # Copy count from data2 1696 $result{$line} = $data2{$line}; 1697 1698 $found++; 1699 if ($result{$line} > 0) { $hit++; } 1700 } 1701 1702 return (\%result, $found, $hit); 1703 } 1704 1705 1706 # 1707 # merge_checksums(ref1, ref2, filename) 1708 # 1709 # REF1 and REF2 are references to hashes containing a mapping 1710 # 1711 # line number -> checksum 1712 # 1713 # Merge checksum lists defined in REF1 and REF2 and return reference to 1714 # resulting hash. Die if a checksum for a line is defined in both hashes 1715 # but does not match. 1716 # 1717 1718 sub merge_checksums($$$) 1719 { 1720 my $ref1 = $_[0]; 1721 my $ref2 = $_[1]; 1722 my $filename = $_[2]; 1723 my %result; 1724 my $line; 1725 1726 foreach $line (keys(%{$ref1})) 1727 { 1728 if (defined($ref2->{$line}) && 1729 ($ref1->{$line} ne $ref2->{$line})) 1730 { 1731 die("ERROR: checksum mismatch at $filename:$line\n"); 1732 } 1733 $result{$line} = $ref1->{$line}; 1734 } 1735 1736 foreach $line (keys(%{$ref2})) 1737 { 1738 $result{$line} = $ref2->{$line}; 1739 } 1740 1741 return \%result; 1742 } 1743 1744 1745 # 1746 # merge_func_data(funcdata1, funcdata2, filename) 1747 # 1748 1749 sub merge_func_data($$$) 1750 { 1751 my ($funcdata1, $funcdata2, $filename) = @_; 1752 my %result; 1753 my $func; 1754 1755 if (defined($funcdata1)) { 1756 %result = %{$funcdata1}; 1757 } 1758 1759 foreach $func (keys(%{$funcdata2})) { 1760 my $line1 = $result{$func}; 1761 my $line2 = $funcdata2->{$func}; 1762 1763 if (defined($line1) && ($line1 != $line2)) { 1764 warn("WARNING: function data mismatch at ". 1765 "$filename:$line2\n"); 1766 next; 1767 } 1768 $result{$func} = $line2; 1769 } 1770 1771 return \%result; 1772 } 1773 1774 1775 # 1776 # add_fnccount(fnccount1, fnccount2) 1777 # 1778 # Add function call count data. Return list (fnccount_added, f_found, f_hit) 1779 # 1780 1781 sub add_fnccount($$) 1782 { 1783 my ($fnccount1, $fnccount2) = @_; 1784 my %result; 1785 my $fn_found; 1786 my $fn_hit; 1787 my $function; 1788 1789 if (defined($fnccount1)) { 1790 %result = %{$fnccount1}; 1791 } 1792 foreach $function (keys(%{$fnccount2})) { 1793 $result{$function} += $fnccount2->{$function}; 1794 } 1795 $fn_found = scalar(keys(%result)); 1796 $fn_hit = 0; 1797 foreach $function (keys(%result)) { 1798 if ($result{$function} > 0) { 1799 $fn_hit++; 1800 } 1801 } 1802 1803 return (\%result, $fn_found, $fn_hit); 1804 } 1805 1806 # 1807 # add_testfncdata(testfncdata1, testfncdata2) 1808 # 1809 # Add function call count data for several tests. Return reference to 1810 # added_testfncdata. 1811 # 1812 1813 sub add_testfncdata($$) 1814 { 1815 my ($testfncdata1, $testfncdata2) = @_; 1816 my %result; 1817 my $testname; 1818 1819 foreach $testname (keys(%{$testfncdata1})) { 1820 if (defined($testfncdata2->{$testname})) { 1821 my $fnccount; 1822 1823 # Function call count data for this testname exists 1824 # in both data sets: add 1825 ($fnccount) = add_fnccount( 1826 $testfncdata1->{$testname}, 1827 $testfncdata2->{$testname}); 1828 $result{$testname} = $fnccount; 1829 next; 1830 } 1831 # Function call count data for this testname is unique to 1832 # data set 1: copy 1833 $result{$testname} = $testfncdata1->{$testname}; 1834 } 1835 1836 # Add count data for testnames unique to data set 2 1837 foreach $testname (keys(%{$testfncdata2})) { 1838 if (!defined($result{$testname})) { 1839 $result{$testname} = $testfncdata2->{$testname}; 1840 } 1841 } 1842 return \%result; 1843 } 1844 1845 1846 # 1847 # brcount_to_db(brcount) 1848 # 1849 # Convert brcount data to the following format: 1850 # 1851 # db: line number -> block hash 1852 # block hash: block number -> branch hash 1853 # branch hash: branch number -> taken value 1854 # 1855 1856 sub brcount_to_db($) 1857 { 1858 my ($brcount) = @_; 1859 my $line; 1860 my $db; 1861 1862 # Add branches from first count to database 1863 foreach $line (keys(%{$brcount})) { 1864 my $brdata = $brcount->{$line}; 1865 my $i; 1866 my $num = br_ivec_len($brdata); 1867 1868 for ($i = 0; $i < $num; $i++) { 1869 my ($block, $branch, $taken) = br_ivec_get($brdata, $i); 1870 1871 $db->{$line}->{$block}->{$branch} = $taken; 1872 } 1873 } 1874 1875 return $db; 1876 } 1877 1878 1879 # 1880 # db_to_brcount(db) 1881 # 1882 # Convert branch coverage data back to brcount format. 1883 # 1884 1885 sub db_to_brcount($) 1886 { 1887 my ($db) = @_; 1888 my $line; 1889 my $brcount = {}; 1890 my $br_found = 0; 1891 my $br_hit = 0; 1892 1893 # Convert database back to brcount format 1894 foreach $line (sort({$a <=> $b} keys(%{$db}))) { 1895 my $ldata = $db->{$line}; 1896 my $brdata; 1897 my $block; 1898 1899 foreach $block (sort({$a <=> $b} keys(%{$ldata}))) { 1900 my $bdata = $ldata->{$block}; 1901 my $branch; 1902 1903 foreach $branch (sort({$a <=> $b} keys(%{$bdata}))) { 1904 my $taken = $bdata->{$branch}; 1905 1906 $br_found++; 1907 $br_hit++ if ($taken ne "-" && $taken > 0); 1908 $brdata = br_ivec_push($brdata, $block, 1909 $branch, $taken); 1910 } 1911 } 1912 $brcount->{$line} = $brdata; 1913 } 1914 1915 return ($brcount, $br_found, $br_hit); 1916 } 1917 1918 1919 # 1920 # combine_brcount(brcount1, brcount2, type) 1921 # 1922 # If add is BR_ADD, add branch coverage data and return list (brcount_added, 1923 # br_found, br_hit). If add is BR_SUB, subtract the taken values of brcount2 1924 # from brcount1 and return (brcount_sub, br_found, br_hit). 1925 # 1926 1927 sub combine_brcount($$$) 1928 { 1929 my ($brcount1, $brcount2, $type) = @_; 1930 my $line; 1931 my $block; 1932 my $branch; 1933 my $taken; 1934 my $db; 1935 my $br_found = 0; 1936 my $br_hit = 0; 1937 my $result; 1938 1939 # Convert branches from first count to database 1940 $db = brcount_to_db($brcount1); 1941 # Combine values from database and second count 1942 foreach $line (keys(%{$brcount2})) { 1943 my $brdata = $brcount2->{$line}; 1944 my $num = br_ivec_len($brdata); 1945 my $i; 1946 1947 for ($i = 0; $i < $num; $i++) { 1948 ($block, $branch, $taken) = br_ivec_get($brdata, $i); 1949 my $new_taken = $db->{$line}->{$block}->{$branch}; 1950 1951 if ($type == $BR_ADD) { 1952 $new_taken = br_taken_add($new_taken, $taken); 1953 } elsif ($type == $BR_SUB) { 1954 $new_taken = br_taken_sub($new_taken, $taken); 1955 } 1956 $db->{$line}->{$block}->{$branch} = $new_taken 1957 if (defined($new_taken)); 1958 } 1959 } 1960 # Convert database back to brcount format 1961 ($result, $br_found, $br_hit) = db_to_brcount($db); 1962 1963 return ($result, $br_found, $br_hit); 1964 } 1965 1966 1967 # 1968 # add_testbrdata(testbrdata1, testbrdata2) 1969 # 1970 # Add branch coverage data for several tests. Return reference to 1971 # added_testbrdata. 1972 # 1973 1974 sub add_testbrdata($$) 1975 { 1976 my ($testbrdata1, $testbrdata2) = @_; 1977 my %result; 1978 my $testname; 1979 1980 foreach $testname (keys(%{$testbrdata1})) { 1981 if (defined($testbrdata2->{$testname})) { 1982 my $brcount; 1983 1984 # Branch coverage data for this testname exists 1985 # in both data sets: add 1986 ($brcount) = combine_brcount($testbrdata1->{$testname}, 1987 $testbrdata2->{$testname}, $BR_ADD); 1988 $result{$testname} = $brcount; 1989 next; 1990 } 1991 # Branch coverage data for this testname is unique to 1992 # data set 1: copy 1993 $result{$testname} = $testbrdata1->{$testname}; 1994 } 1995 1996 # Add count data for testnames unique to data set 2 1997 foreach $testname (keys(%{$testbrdata2})) { 1998 if (!defined($result{$testname})) { 1999 $result{$testname} = $testbrdata2->{$testname}; 2000 } 2001 } 2002 return \%result; 2003 } 2004 2005 2006 # 2007 # combine_info_entries(entry_ref1, entry_ref2, filename) 2008 # 2009 # Combine .info data entry hashes referenced by ENTRY_REF1 and ENTRY_REF2. 2010 # Return reference to resulting hash. 2011 # 2012 2013 sub combine_info_entries($$$) 2014 { 2015 my $entry1 = $_[0]; # Reference to hash containing first entry 2016 my $testdata1; 2017 my $sumcount1; 2018 my $funcdata1; 2019 my $checkdata1; 2020 my $testfncdata1; 2021 my $sumfnccount1; 2022 my $testbrdata1; 2023 my $sumbrcount1; 2024 2025 my $entry2 = $_[1]; # Reference to hash containing second entry 2026 my $testdata2; 2027 my $sumcount2; 2028 my $funcdata2; 2029 my $checkdata2; 2030 my $testfncdata2; 2031 my $sumfnccount2; 2032 my $testbrdata2; 2033 my $sumbrcount2; 2034 2035 my %result; # Hash containing combined entry 2036 my %result_testdata; 2037 my $result_sumcount = {}; 2038 my $result_funcdata; 2039 my $result_testfncdata; 2040 my $result_sumfnccount; 2041 my $result_testbrdata; 2042 my $result_sumbrcount; 2043 my $lines_found; 2044 my $lines_hit; 2045 my $fn_found; 2046 my $fn_hit; 2047 my $br_found; 2048 my $br_hit; 2049 2050 my $testname; 2051 my $filename = $_[2]; 2052 2053 # Retrieve data 2054 ($testdata1, $sumcount1, $funcdata1, $checkdata1, $testfncdata1, 2055 $sumfnccount1, $testbrdata1, $sumbrcount1) = get_info_entry($entry1); 2056 ($testdata2, $sumcount2, $funcdata2, $checkdata2, $testfncdata2, 2057 $sumfnccount2, $testbrdata2, $sumbrcount2) = get_info_entry($entry2); 2058 2059 # Merge checksums 2060 $checkdata1 = merge_checksums($checkdata1, $checkdata2, $filename); 2061 2062 # Combine funcdata 2063 $result_funcdata = merge_func_data($funcdata1, $funcdata2, $filename); 2064 2065 # Combine function call count data 2066 $result_testfncdata = add_testfncdata($testfncdata1, $testfncdata2); 2067 ($result_sumfnccount, $fn_found, $fn_hit) = 2068 add_fnccount($sumfnccount1, $sumfnccount2); 2069 2070 # Combine branch coverage data 2071 $result_testbrdata = add_testbrdata($testbrdata1, $testbrdata2); 2072 ($result_sumbrcount, $br_found, $br_hit) = 2073 combine_brcount($sumbrcount1, $sumbrcount2, $BR_ADD); 2074 2075 # Combine testdata 2076 foreach $testname (keys(%{$testdata1})) 2077 { 2078 if (defined($testdata2->{$testname})) 2079 { 2080 # testname is present in both entries, requires 2081 # combination 2082 ($result_testdata{$testname}) = 2083 add_counts($testdata1->{$testname}, 2084 $testdata2->{$testname}); 2085 } 2086 else 2087 { 2088 # testname only present in entry1, add to result 2089 $result_testdata{$testname} = $testdata1->{$testname}; 2090 } 2091 2092 # update sum count hash 2093 ($result_sumcount, $lines_found, $lines_hit) = 2094 add_counts($result_sumcount, 2095 $result_testdata{$testname}); 2096 } 2097 2098 foreach $testname (keys(%{$testdata2})) 2099 { 2100 # Skip testnames already covered by previous iteration 2101 if (defined($testdata1->{$testname})) { next; } 2102 2103 # testname only present in entry2, add to result hash 2104 $result_testdata{$testname} = $testdata2->{$testname}; 2105 2106 # update sum count hash 2107 ($result_sumcount, $lines_found, $lines_hit) = 2108 add_counts($result_sumcount, 2109 $result_testdata{$testname}); 2110 } 2111 2112 # Calculate resulting sumcount 2113 2114 # Store result 2115 set_info_entry(\%result, \%result_testdata, $result_sumcount, 2116 $result_funcdata, $checkdata1, $result_testfncdata, 2117 $result_sumfnccount, $result_testbrdata, 2118 $result_sumbrcount, $lines_found, $lines_hit, 2119 $fn_found, $fn_hit, $br_found, $br_hit); 2120 2121 return(\%result); 2122 } 2123 2124 2125 # 2126 # combine_info_files(info_ref1, info_ref2) 2127 # 2128 # Combine .info data in hashes referenced by INFO_REF1 and INFO_REF2. Return 2129 # reference to resulting hash. 2130 # 2131 2132 sub combine_info_files($$) 2133 { 2134 my %hash1 = %{$_[0]}; 2135 my %hash2 = %{$_[1]}; 2136 my $filename; 2137 2138 foreach $filename (keys(%hash2)) 2139 { 2140 if ($hash1{$filename}) 2141 { 2142 # Entry already exists in hash1, combine them 2143 $hash1{$filename} = 2144 combine_info_entries($hash1{$filename}, 2145 $hash2{$filename}, 2146 $filename); 2147 } 2148 else 2149 { 2150 # Entry is unique in both hashes, simply add to 2151 # resulting hash 2152 $hash1{$filename} = $hash2{$filename}; 2153 } 2154 } 2155 2156 return(\%hash1); 2157 } 2158 2159 2160 # 2161 # get_prefix(min_dir, filename_list) 2162 # 2163 # Search FILENAME_LIST for a directory prefix which is common to as many 2164 # list entries as possible, so that removing this prefix will minimize the 2165 # sum of the lengths of all resulting shortened filenames while observing 2166 # that no filename has less than MIN_DIR parent directories. 2167 # 2168 2169 sub get_prefix($@) 2170 { 2171 my ($min_dir, @filename_list) = @_; 2172 my %prefix; # mapping: prefix -> sum of lengths 2173 my $current; # Temporary iteration variable 2174 2175 # Find list of prefixes 2176 foreach (@filename_list) 2177 { 2178 # Need explicit assignment to get a copy of $_ so that 2179 # shortening the contained prefix does not affect the list 2180 $current = $_; 2181 while ($current = shorten_prefix($current)) 2182 { 2183 $current .= "/"; 2184 2185 # Skip rest if the remaining prefix has already been 2186 # added to hash 2187 if (exists($prefix{$current})) { last; } 2188 2189 # Initialize with 0 2190 $prefix{$current}="0"; 2191 } 2192 2193 } 2194 2195 # Remove all prefixes that would cause filenames to have less than 2196 # the minimum number of parent directories 2197 foreach my $filename (@filename_list) { 2198 my $dir = dirname($filename); 2199 2200 for (my $i = 0; $i < $min_dir; $i++) { 2201 delete($prefix{$dir."/"}); 2202 $dir = shorten_prefix($dir); 2203 } 2204 } 2205 2206 # Check if any prefix remains 2207 return undef if (!%prefix); 2208 2209 # Calculate sum of lengths for all prefixes 2210 foreach $current (keys(%prefix)) 2211 { 2212 foreach (@filename_list) 2213 { 2214 # Add original length 2215 $prefix{$current} += length($_); 2216 2217 # Check whether prefix matches 2218 if (substr($_, 0, length($current)) eq $current) 2219 { 2220 # Subtract prefix length for this filename 2221 $prefix{$current} -= length($current); 2222 } 2223 } 2224 } 2225 2226 # Find and return prefix with minimal sum 2227 $current = (keys(%prefix))[0]; 2228 2229 foreach (keys(%prefix)) 2230 { 2231 if ($prefix{$_} < $prefix{$current}) 2232 { 2233 $current = $_; 2234 } 2235 } 2236 2237 $current =~ s/\/$//; 2238 2239 return($current); 2240 } 2241 2242 2243 # 2244 # shorten_prefix(prefix) 2245 # 2246 # Return PREFIX shortened by last directory component. 2247 # 2248 2249 sub shorten_prefix($) 2250 { 2251 my @list = split("/", $_[0]); 2252 2253 pop(@list); 2254 return join("/", @list); 2255 } 2256 2257 2258 2259 # 2260 # get_dir_list(filename_list) 2261 # 2262 # Return sorted list of directories for each entry in given FILENAME_LIST. 2263 # 2264 2265 sub get_dir_list(@) 2266 { 2267 my %result; 2268 2269 foreach (@_) 2270 { 2271 $result{shorten_prefix($_)} = ""; 2272 } 2273 2274 return(sort(keys(%result))); 2275 } 2276 2277 2278 # 2279 # get_relative_base_path(subdirectory) 2280 # 2281 # Return a relative path string which references the base path when applied 2282 # in SUBDIRECTORY. 2283 # 2284 # Example: get_relative_base_path("fs/mm") -> "../../" 2285 # 2286 2287 sub get_relative_base_path($) 2288 { 2289 my $result = ""; 2290 my $index; 2291 2292 # Make an empty directory path a special case 2293 if (!$_[0]) { return(""); } 2294 2295 # Count number of /s in path 2296 $index = ($_[0] =~ s/\//\//g); 2297 2298 # Add a ../ to $result for each / in the directory path + 1 2299 for (; $index>=0; $index--) 2300 { 2301 $result .= "../"; 2302 } 2303 2304 return $result; 2305 } 2306 2307 2308 # 2309 # read_testfile(test_filename) 2310 # 2311 # Read in file TEST_FILENAME which contains test descriptions in the format: 2312 # 2313 # TN:<whitespace><test name> 2314 # TD:<whitespace><test description> 2315 # 2316 # for each test case. Return a reference to a hash containing a mapping 2317 # 2318 # test name -> test description. 2319 # 2320 # Die on error. 2321 # 2322 2323 sub read_testfile($) 2324 { 2325 my %result; 2326 my $test_name; 2327 my $changed_testname; 2328 local *TEST_HANDLE; 2329 2330 open(TEST_HANDLE, "<", $_[0]) 2331 or die("ERROR: cannot open $_[0]!\n"); 2332 2333 while (<TEST_HANDLE>) 2334 { 2335 chomp($_); 2336 2337 # Match lines beginning with TN:<whitespace(s)> 2338 if (/^TN:\s+(.*?)\s*$/) 2339 { 2340 # Store name for later use 2341 $test_name = $1; 2342 if ($test_name =~ s/\W/_/g) 2343 { 2344 $changed_testname = 1; 2345 } 2346 } 2347 2348 # Match lines beginning with TD:<whitespace(s)> 2349 if (/^TD:\s+(.*?)\s*$/) 2350 { 2351 # Check for empty line 2352 if ($1) 2353 { 2354 # Add description to hash 2355 $result{$test_name} .= " $1"; 2356 } 2357 else 2358 { 2359 # Add empty line 2360 $result{$test_name} .= "\n\n"; 2361 } 2362 } 2363 } 2364 2365 close(TEST_HANDLE); 2366 2367 if ($changed_testname) 2368 { 2369 warn("WARNING: invalid characters removed from testname in ". 2370 "descriptions file $_[0]\n"); 2371 } 2372 2373 return \%result; 2374 } 2375 2376 2377 # 2378 # escape_html(STRING) 2379 # 2380 # Return a copy of STRING in which all occurrences of HTML special characters 2381 # are escaped. 2382 # 2383 2384 sub escape_html($) 2385 { 2386 my $string = $_[0]; 2387 2388 if (!$string) { return ""; } 2389 2390 $string =~ s/&/&/g; # & -> & 2391 $string =~ s/</</g; # < -> < 2392 $string =~ s/>/>/g; # > -> > 2393 $string =~ s/\"/"/g; # " -> " 2394 2395 while ($string =~ /^([^\t]*)(\t)/) 2396 { 2397 my $replacement = " "x($tab_size - (length($1) % $tab_size)); 2398 $string =~ s/^([^\t]*)(\t)/$1$replacement/; 2399 } 2400 2401 $string =~ s/\n/<br>/g; # \n -> <br> 2402 2403 return $string; 2404 } 2405 2406 2407 # 2408 # get_date_string() 2409 # 2410 # Return the current date in the form: yyyy-mm-dd 2411 # 2412 2413 sub get_date_string() 2414 { 2415 my $year; 2416 my $month; 2417 my $day; 2418 2419 ($year, $month, $day) = (localtime())[5, 4, 3]; 2420 2421 return sprintf("%d-%02d-%02d", $year+1900, $month+1, $day); 2422 } 2423 2424 2425 # 2426 # create_sub_dir(dir_name) 2427 # 2428 # Create subdirectory DIR_NAME if it does not already exist, including all its 2429 # parent directories. 2430 # 2431 # Die on error. 2432 # 2433 2434 sub create_sub_dir($) 2435 { 2436 my ($dir) = @_; 2437 2438 system("mkdir", "-p" ,$dir) 2439 and die("ERROR: cannot create directory $dir!\n"); 2440 } 2441 2442 2443 # 2444 # write_description_file(descriptions, overall_found, overall_hit, 2445 # total_fn_found, total_fn_hit, total_br_found, 2446 # total_br_hit) 2447 # 2448 # Write HTML file containing all test case descriptions. DESCRIPTIONS is a 2449 # reference to a hash containing a mapping 2450 # 2451 # test case name -> test case description 2452 # 2453 # Die on error. 2454 # 2455 2456 sub write_description_file($$$$$$$) 2457 { 2458 my %description = %{$_[0]}; 2459 my $found = $_[1]; 2460 my $hit = $_[2]; 2461 my $fn_found = $_[3]; 2462 my $fn_hit = $_[4]; 2463 my $br_found = $_[5]; 2464 my $br_hit = $_[6]; 2465 my $test_name; 2466 local *HTML_HANDLE; 2467 2468 html_create(*HTML_HANDLE,"descriptions.$html_ext"); 2469 write_html_prolog(*HTML_HANDLE, "", "LCOV - test case descriptions"); 2470 write_header(*HTML_HANDLE, 3, "", "", $found, $hit, $fn_found, 2471 $fn_hit, $br_found, $br_hit, 0); 2472 2473 write_test_table_prolog(*HTML_HANDLE, 2474 "Test case descriptions - alphabetical list"); 2475 2476 foreach $test_name (sort(keys(%description))) 2477 { 2478 write_test_table_entry(*HTML_HANDLE, $test_name, 2479 escape_html($description{$test_name})); 2480 } 2481 2482 write_test_table_epilog(*HTML_HANDLE); 2483 write_html_epilog(*HTML_HANDLE, ""); 2484 2485 close(*HTML_HANDLE); 2486 } 2487 2488 2489 2490 # 2491 # write_png_files() 2492 # 2493 # Create all necessary .png files for the HTML-output in the current 2494 # directory. .png-files are used as bar graphs. 2495 # 2496 # Die on error. 2497 # 2498 2499 sub write_png_files() 2500 { 2501 my %data; 2502 local *PNG_HANDLE; 2503 2504 $data{"ruby.png"} = 2505 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 2506 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 2507 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, 2508 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, 2509 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x18, 0x10, 0x5d, 0x57, 2510 0x34, 0x6e, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 2511 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2, 2512 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, 2513 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, 2514 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0x35, 0x2f, 2515 0x00, 0x00, 0x00, 0xd0, 0x33, 0x9a, 0x9d, 0x00, 0x00, 0x00, 2516 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00, 2517 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00, 2518 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 2519 0x82]; 2520 $data{"amber.png"} = 2521 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 2522 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 2523 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, 2524 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, 2525 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x28, 0x04, 0x98, 0xcb, 2526 0xd6, 0xe0, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 2527 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2, 2528 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, 2529 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, 2530 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xe0, 0x50, 2531 0x00, 0x00, 0x00, 0xa2, 0x7a, 0xda, 0x7e, 0x00, 0x00, 0x00, 2532 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00, 2533 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00, 2534 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 2535 0x82]; 2536 $data{"emerald.png"} = 2537 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 2538 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 2539 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, 2540 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, 2541 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x22, 0x2b, 0xc9, 0xf5, 2542 0x03, 0x33, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 2543 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2, 2544 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, 2545 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, 2546 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0x1b, 0xea, 0x59, 2547 0x0a, 0x0a, 0x0a, 0x0f, 0xba, 0x50, 0x83, 0x00, 0x00, 0x00, 2548 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00, 2549 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00, 2550 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 2551 0x82]; 2552 $data{"snow.png"} = 2553 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 2554 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 2555 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, 2556 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, 2557 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x1e, 0x1d, 0x75, 0xbc, 2558 0xef, 0x55, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 2559 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2, 2560 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, 2561 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, 2562 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xff, 0xff, 2563 0x00, 0x00, 0x00, 0x55, 0xc2, 0xd3, 0x7e, 0x00, 0x00, 0x00, 2564 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00, 2565 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00, 2566 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 2567 0x82]; 2568 $data{"glass.png"} = 2569 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 2570 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 2571 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, 2572 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, 2573 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, 2574 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xff, 0xff, 2575 0x00, 0x00, 0x00, 0x55, 0xc2, 0xd3, 0x7e, 0x00, 0x00, 0x00, 2576 0x01, 0x74, 0x52, 0x4e, 0x53, 0x00, 0x40, 0xe6, 0xd8, 0x66, 2577 0x00, 0x00, 0x00, 0x01, 0x62, 0x4b, 0x47, 0x44, 0x00, 0x88, 2578 0x05, 0x1d, 0x48, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 2579 0x73, 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 2580 0xd2, 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 2581 0x4d, 0x45, 0x07, 0xd2, 0x07, 0x13, 0x0f, 0x08, 0x19, 0xc4, 2582 0x40, 0x56, 0x10, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41, 2583 0x54, 0x78, 0x9c, 0x63, 0x60, 0x00, 0x00, 0x00, 0x02, 0x00, 2584 0x01, 0x48, 0xaf, 0xa4, 0x71, 0x00, 0x00, 0x00, 0x00, 0x49, 2585 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82]; 2586 $data{"updown.png"} = 2587 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 2588 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x0a, 2589 0x00, 0x00, 0x00, 0x0e, 0x08, 0x06, 0x00, 0x00, 0x00, 0x16, 2590 0xa3, 0x8d, 0xab, 0x00, 0x00, 0x00, 0x3c, 0x49, 0x44, 0x41, 2591 0x54, 0x28, 0xcf, 0x63, 0x60, 0x40, 0x03, 0xff, 0xa1, 0x00, 2592 0x5d, 0x9c, 0x11, 0x5d, 0x11, 0x8a, 0x24, 0x23, 0x23, 0x23, 2593 0x86, 0x42, 0x6c, 0xa6, 0x20, 0x2b, 0x66, 0xc4, 0xa7, 0x08, 2594 0x59, 0x31, 0x23, 0x21, 0x45, 0x30, 0xc0, 0xc4, 0x30, 0x60, 2595 0x80, 0xfa, 0x6e, 0x24, 0x3e, 0x78, 0x48, 0x0a, 0x70, 0x62, 2596 0xa2, 0x90, 0x81, 0xd8, 0x44, 0x01, 0x00, 0xe9, 0x5c, 0x2f, 2597 0xf5, 0xe2, 0x9d, 0x0f, 0xf9, 0x00, 0x00, 0x00, 0x00, 0x49, 2598 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82] if ($sort); 2599 foreach (keys(%data)) 2600 { 2601 open(PNG_HANDLE, ">", $_) 2602 or die("ERROR: cannot create $_!\n"); 2603 binmode(PNG_HANDLE); 2604 print(PNG_HANDLE map(chr,@{$data{$_}})); 2605 close(PNG_HANDLE); 2606 } 2607 } 2608 2609 2610 # 2611 # write_htaccess_file() 2612 # 2613 2614 sub write_htaccess_file() 2615 { 2616 local *HTACCESS_HANDLE; 2617 my $htaccess_data; 2618 2619 open(*HTACCESS_HANDLE, ">", ".htaccess") 2620 or die("ERROR: cannot open .htaccess for writing!\n"); 2621 2622 $htaccess_data = (<<"END_OF_HTACCESS") 2623 AddEncoding x-gzip .html 2624 END_OF_HTACCESS 2625 ; 2626 2627 print(HTACCESS_HANDLE $htaccess_data); 2628 close(*HTACCESS_HANDLE); 2629 } 2630 2631 2632 # 2633 # write_css_file() 2634 # 2635 # Write the cascading style sheet file gcov.css to the current directory. 2636 # This file defines basic layout attributes of all generated HTML pages. 2637 # 2638 2639 sub write_css_file() 2640 { 2641 local *CSS_HANDLE; 2642 2643 # Check for a specified external style sheet file 2644 if ($css_filename) 2645 { 2646 # Simply copy that file 2647 system("cp", $css_filename, "gcov.css") 2648 and die("ERROR: cannot copy file $css_filename!\n"); 2649 return; 2650 } 2651 2652 open(CSS_HANDLE, ">", "gcov.css") 2653 or die ("ERROR: cannot open gcov.css for writing!\n"); 2654 2655 2656 # ************************************************************* 2657 2658 my $css_data = ($_=<<"END_OF_CSS") 2659 /* All views: initial background and text color */ 2660 body 2661 { 2662 color: #000000; 2663 background-color: #FFFFFF; 2664 } 2665 2666 /* All views: standard link format*/ 2667 a:link 2668 { 2669 color: #284FA8; 2670 text-decoration: underline; 2671 } 2672 2673 /* All views: standard link - visited format */ 2674 a:visited 2675 { 2676 color: #00CB40; 2677 text-decoration: underline; 2678 } 2679 2680 /* All views: standard link - activated format */ 2681 a:active 2682 { 2683 color: #FF0040; 2684 text-decoration: underline; 2685 } 2686 2687 /* All views: main title format */ 2688 td.title 2689 { 2690 text-align: center; 2691 padding-bottom: 10px; 2692 font-family: sans-serif; 2693 font-size: 20pt; 2694 font-style: italic; 2695 font-weight: bold; 2696 } 2697 2698 /* All views: header item format */ 2699 td.headerItem 2700 { 2701 text-align: right; 2702 padding-right: 6px; 2703 font-family: sans-serif; 2704 font-weight: bold; 2705 vertical-align: top; 2706 white-space: nowrap; 2707 } 2708 2709 /* All views: header item value format */ 2710 td.headerValue 2711 { 2712 text-align: left; 2713 color: #284FA8; 2714 font-family: sans-serif; 2715 font-weight: bold; 2716 white-space: nowrap; 2717 } 2718 2719 /* All views: header item coverage table heading */ 2720 td.headerCovTableHead 2721 { 2722 text-align: center; 2723 padding-right: 6px; 2724 padding-left: 6px; 2725 padding-bottom: 0px; 2726 font-family: sans-serif; 2727 font-size: 80%; 2728 white-space: nowrap; 2729 } 2730 2731 /* All views: header item coverage table entry */ 2732 td.headerCovTableEntry 2733 { 2734 text-align: right; 2735 color: #284FA8; 2736 font-family: sans-serif; 2737 font-weight: bold; 2738 white-space: nowrap; 2739 padding-left: 12px; 2740 padding-right: 4px; 2741 background-color: #DAE7FE; 2742 } 2743 2744 /* All views: header item coverage table entry for high coverage rate */ 2745 td.headerCovTableEntryHi 2746 { 2747 text-align: right; 2748 color: #000000; 2749 font-family: sans-serif; 2750 font-weight: bold; 2751 white-space: nowrap; 2752 padding-left: 12px; 2753 padding-right: 4px; 2754 background-color: #A7FC9D; 2755 } 2756 2757 /* All views: header item coverage table entry for medium coverage rate */ 2758 td.headerCovTableEntryMed 2759 { 2760 text-align: right; 2761 color: #000000; 2762 font-family: sans-serif; 2763 font-weight: bold; 2764 white-space: nowrap; 2765 padding-left: 12px; 2766 padding-right: 4px; 2767 background-color: #FFEA20; 2768 } 2769 2770 /* All views: header item coverage table entry for ow coverage rate */ 2771 td.headerCovTableEntryLo 2772 { 2773 text-align: right; 2774 color: #000000; 2775 font-family: sans-serif; 2776 font-weight: bold; 2777 white-space: nowrap; 2778 padding-left: 12px; 2779 padding-right: 4px; 2780 background-color: #FF0000; 2781 } 2782 2783 /* All views: header legend value for legend entry */ 2784 td.headerValueLeg 2785 { 2786 text-align: left; 2787 color: #000000; 2788 font-family: sans-serif; 2789 font-size: 80%; 2790 white-space: nowrap; 2791 padding-top: 4px; 2792 } 2793 2794 /* All views: color of horizontal ruler */ 2795 td.ruler 2796 { 2797 background-color: #6688D4; 2798 } 2799 2800 /* All views: version string format */ 2801 td.versionInfo 2802 { 2803 text-align: center; 2804 padding-top: 2px; 2805 font-family: sans-serif; 2806 font-style: italic; 2807 } 2808 2809 /* Directory view/File view (all)/Test case descriptions: 2810 table headline format */ 2811 td.tableHead 2812 { 2813 text-align: center; 2814 color: #FFFFFF; 2815 background-color: #6688D4; 2816 font-family: sans-serif; 2817 font-size: 120%; 2818 font-weight: bold; 2819 white-space: nowrap; 2820 padding-left: 4px; 2821 padding-right: 4px; 2822 } 2823 2824 span.tableHeadSort 2825 { 2826 padding-right: 4px; 2827 } 2828 2829 /* Directory view/File view (all): filename entry format */ 2830 td.coverFile 2831 { 2832 text-align: left; 2833 padding-left: 10px; 2834 padding-right: 20px; 2835 color: #284FA8; 2836 background-color: #DAE7FE; 2837 font-family: monospace; 2838 } 2839 2840 /* Directory view/File view (all): bar-graph entry format*/ 2841 td.coverBar 2842 { 2843 padding-left: 10px; 2844 padding-right: 10px; 2845 background-color: #DAE7FE; 2846 } 2847 2848 /* Directory view/File view (all): bar-graph outline color */ 2849 td.coverBarOutline 2850 { 2851 background-color: #000000; 2852 } 2853 2854 /* Directory view/File view (all): percentage entry for files with 2855 high coverage rate */ 2856 td.coverPerHi 2857 { 2858 text-align: right; 2859 padding-left: 10px; 2860 padding-right: 10px; 2861 background-color: #A7FC9D; 2862 font-weight: bold; 2863 font-family: sans-serif; 2864 } 2865 2866 /* Directory view/File view (all): line count entry for files with 2867 high coverage rate */ 2868 td.coverNumHi 2869 { 2870 text-align: right; 2871 padding-left: 10px; 2872 padding-right: 10px; 2873 background-color: #A7FC9D; 2874 white-space: nowrap; 2875 font-family: sans-serif; 2876 } 2877 2878 /* Directory view/File view (all): percentage entry for files with 2879 medium coverage rate */ 2880 td.coverPerMed 2881 { 2882 text-align: right; 2883 padding-left: 10px; 2884 padding-right: 10px; 2885 background-color: #FFEA20; 2886 font-weight: bold; 2887 font-family: sans-serif; 2888 } 2889 2890 /* Directory view/File view (all): line count entry for files with 2891 medium coverage rate */ 2892 td.coverNumMed 2893 { 2894 text-align: right; 2895 padding-left: 10px; 2896 padding-right: 10px; 2897 background-color: #FFEA20; 2898 white-space: nowrap; 2899 font-family: sans-serif; 2900 } 2901 2902 /* Directory view/File view (all): percentage entry for files with 2903 low coverage rate */ 2904 td.coverPerLo 2905 { 2906 text-align: right; 2907 padding-left: 10px; 2908 padding-right: 10px; 2909 background-color: #FF0000; 2910 font-weight: bold; 2911 font-family: sans-serif; 2912 } 2913 2914 /* Directory view/File view (all): line count entry for files with 2915 low coverage rate */ 2916 td.coverNumLo 2917 { 2918 text-align: right; 2919 padding-left: 10px; 2920 padding-right: 10px; 2921 background-color: #FF0000; 2922 white-space: nowrap; 2923 font-family: sans-serif; 2924 } 2925 2926 /* File view (all): "show/hide details" link format */ 2927 a.detail:link 2928 { 2929 color: #B8D0FF; 2930 font-size:80%; 2931 } 2932 2933 /* File view (all): "show/hide details" link - visited format */ 2934 a.detail:visited 2935 { 2936 color: #B8D0FF; 2937 font-size:80%; 2938 } 2939 2940 /* File view (all): "show/hide details" link - activated format */ 2941 a.detail:active 2942 { 2943 color: #FFFFFF; 2944 font-size:80%; 2945 } 2946 2947 /* File view (detail): test name entry */ 2948 td.testName 2949 { 2950 text-align: right; 2951 padding-right: 10px; 2952 background-color: #DAE7FE; 2953 font-family: sans-serif; 2954 } 2955 2956 /* File view (detail): test percentage entry */ 2957 td.testPer 2958 { 2959 text-align: right; 2960 padding-left: 10px; 2961 padding-right: 10px; 2962 background-color: #DAE7FE; 2963 font-family: sans-serif; 2964 } 2965 2966 /* File view (detail): test lines count entry */ 2967 td.testNum 2968 { 2969 text-align: right; 2970 padding-left: 10px; 2971 padding-right: 10px; 2972 background-color: #DAE7FE; 2973 font-family: sans-serif; 2974 } 2975 2976 /* Test case descriptions: test name format*/ 2977 dt 2978 { 2979 font-family: sans-serif; 2980 font-weight: bold; 2981 } 2982 2983 /* Test case descriptions: description table body */ 2984 td.testDescription 2985 { 2986 padding-top: 10px; 2987 padding-left: 30px; 2988 padding-bottom: 10px; 2989 padding-right: 30px; 2990 background-color: #DAE7FE; 2991 } 2992 2993 /* Source code view: function entry */ 2994 td.coverFn 2995 { 2996 text-align: left; 2997 padding-left: 10px; 2998 padding-right: 20px; 2999 color: #284FA8; 3000 background-color: #DAE7FE; 3001 font-family: monospace; 3002 } 3003 3004 /* Source code view: function entry zero count*/ 3005 td.coverFnLo 3006 { 3007 text-align: right; 3008 padding-left: 10px; 3009 padding-right: 10px; 3010 background-color: #FF0000; 3011 font-weight: bold; 3012 font-family: sans-serif; 3013 } 3014 3015 /* Source code view: function entry nonzero count*/ 3016 td.coverFnHi 3017 { 3018 text-align: right; 3019 padding-left: 10px; 3020 padding-right: 10px; 3021 background-color: #DAE7FE; 3022 font-weight: bold; 3023 font-family: sans-serif; 3024 } 3025 3026 /* Source code view: source code format */ 3027 pre.source 3028 { 3029 font-family: monospace; 3030 white-space: pre; 3031 margin-top: 2px; 3032 } 3033 3034 /* Source code view: line number format */ 3035 span.lineNum 3036 { 3037 background-color: #EFE383; 3038 } 3039 3040 /* Source code view: format for lines which were executed */ 3041 td.lineCov, 3042 span.lineCov 3043 { 3044 background-color: #CAD7FE; 3045 } 3046 3047 /* Source code view: format for Cov legend */ 3048 span.coverLegendCov 3049 { 3050 padding-left: 10px; 3051 padding-right: 10px; 3052 padding-bottom: 2px; 3053 background-color: #CAD7FE; 3054 } 3055 3056 /* Source code view: format for lines which were not executed */ 3057 td.lineNoCov, 3058 span.lineNoCov 3059 { 3060 background-color: #FF6230; 3061 } 3062 3063 /* Source code view: format for NoCov legend */ 3064 span.coverLegendNoCov 3065 { 3066 padding-left: 10px; 3067 padding-right: 10px; 3068 padding-bottom: 2px; 3069 background-color: #FF6230; 3070 } 3071 3072 /* Source code view (function table): standard link - visited format */ 3073 td.lineNoCov > a:visited, 3074 td.lineCov > a:visited 3075 { 3076 color: black; 3077 text-decoration: underline; 3078 } 3079 3080 /* Source code view: format for lines which were executed only in a 3081 previous version */ 3082 span.lineDiffCov 3083 { 3084 background-color: #B5F7AF; 3085 } 3086 3087 /* Source code view: format for branches which were executed 3088 * and taken */ 3089 span.branchCov 3090 { 3091 background-color: #CAD7FE; 3092 } 3093 3094 /* Source code view: format for branches which were executed 3095 * but not taken */ 3096 span.branchNoCov 3097 { 3098 background-color: #FF6230; 3099 } 3100 3101 /* Source code view: format for branches which were not executed */ 3102 span.branchNoExec 3103 { 3104 background-color: #FF6230; 3105 } 3106 3107 /* Source code view: format for the source code heading line */ 3108 pre.sourceHeading 3109 { 3110 white-space: pre; 3111 font-family: monospace; 3112 font-weight: bold; 3113 margin: 0px; 3114 } 3115 3116 /* All views: header legend value for low rate */ 3117 td.headerValueLegL 3118 { 3119 font-family: sans-serif; 3120 text-align: center; 3121 white-space: nowrap; 3122 padding-left: 4px; 3123 padding-right: 2px; 3124 background-color: #FF0000; 3125 font-size: 80%; 3126 } 3127 3128 /* All views: header legend value for med rate */ 3129 td.headerValueLegM 3130 { 3131 font-family: sans-serif; 3132 text-align: center; 3133 white-space: nowrap; 3134 padding-left: 2px; 3135 padding-right: 2px; 3136 background-color: #FFEA20; 3137 font-size: 80%; 3138 } 3139 3140 /* All views: header legend value for hi rate */ 3141 td.headerValueLegH 3142 { 3143 font-family: sans-serif; 3144 text-align: center; 3145 white-space: nowrap; 3146 padding-left: 2px; 3147 padding-right: 4px; 3148 background-color: #A7FC9D; 3149 font-size: 80%; 3150 } 3151 3152 /* All views except source code view: legend format for low coverage */ 3153 span.coverLegendCovLo 3154 { 3155 padding-left: 10px; 3156 padding-right: 10px; 3157 padding-top: 2px; 3158 background-color: #FF0000; 3159 } 3160 3161 /* All views except source code view: legend format for med coverage */ 3162 span.coverLegendCovMed 3163 { 3164 padding-left: 10px; 3165 padding-right: 10px; 3166 padding-top: 2px; 3167 background-color: #FFEA20; 3168 } 3169 3170 /* All views except source code view: legend format for hi coverage */ 3171 span.coverLegendCovHi 3172 { 3173 padding-left: 10px; 3174 padding-right: 10px; 3175 padding-top: 2px; 3176 background-color: #A7FC9D; 3177 } 3178 END_OF_CSS 3179 ; 3180 3181 # ************************************************************* 3182 3183 3184 # Remove leading tab from all lines 3185 $css_data =~ s/^\t//gm; 3186 3187 print(CSS_HANDLE $css_data); 3188 3189 close(CSS_HANDLE); 3190 } 3191 3192 3193 # 3194 # get_bar_graph_code(base_dir, cover_found, cover_hit) 3195 # 3196 # Return a string containing HTML code which implements a bar graph display 3197 # for a coverage rate of cover_hit * 100 / cover_found. 3198 # 3199 3200 sub get_bar_graph_code($$$) 3201 { 3202 my ($base_dir, $found, $hit) = @_; 3203 my $rate; 3204 my $alt; 3205 my $width; 3206 my $remainder; 3207 my $png_name; 3208 my $graph_code; 3209 3210 # Check number of instrumented lines 3211 if ($_[1] == 0) { return ""; } 3212 3213 $alt = rate($hit, $found, "%"); 3214 $width = rate($hit, $found, undef, 0); 3215 $remainder = 100 - $width; 3216 3217 # Decide which .png file to use 3218 $png_name = $rate_png[classify_rate($found, $hit, $med_limit, 3219 $hi_limit)]; 3220 3221 if ($width == 0) 3222 { 3223 # Zero coverage 3224 $graph_code = (<<END_OF_HTML) 3225 <table border=0 cellspacing=0 cellpadding=1><tr><td class="coverBarOutline"><img src="$_[0]snow.png" width=100 height=10 alt="$alt"></td></tr></table> 3226 END_OF_HTML 3227 ; 3228 } 3229 elsif ($width == 100) 3230 { 3231 # Full coverage 3232 $graph_code = (<<END_OF_HTML) 3233 <table border=0 cellspacing=0 cellpadding=1><tr><td class="coverBarOutline"><img src="$_[0]$png_name" width=100 height=10 alt="$alt"></td></tr></table> 3234 END_OF_HTML 3235 ; 3236 } 3237 else 3238 { 3239 # Positive coverage 3240 $graph_code = (<<END_OF_HTML) 3241 <table border=0 cellspacing=0 cellpadding=1><tr><td class="coverBarOutline"><img src="$_[0]$png_name" width=$width height=10 alt="$alt"><img src="$_[0]snow.png" width=$remainder height=10 alt="$alt"></td></tr></table> 3242 END_OF_HTML 3243 ; 3244 } 3245 3246 # Remove leading tabs from all lines 3247 $graph_code =~ s/^\t+//gm; 3248 chomp($graph_code); 3249 3250 return($graph_code); 3251 } 3252 3253 # 3254 # sub classify_rate(found, hit, med_limit, high_limit) 3255 # 3256 # Return 0 for low rate, 1 for medium rate and 2 for hi rate. 3257 # 3258 3259 sub classify_rate($$$$) 3260 { 3261 my ($found, $hit, $med, $hi) = @_; 3262 my $rate; 3263 3264 if ($found == 0) { 3265 return 2; 3266 } 3267 $rate = rate($hit, $found); 3268 if ($rate < $med) { 3269 return 0; 3270 } elsif ($rate < $hi) { 3271 return 1; 3272 } 3273 return 2; 3274 } 3275 3276 3277 # 3278 # write_html(filehandle, html_code) 3279 # 3280 # Write out HTML_CODE to FILEHANDLE while removing a leading tabulator mark 3281 # in each line of HTML_CODE. 3282 # 3283 3284 sub write_html(*$) 3285 { 3286 local *HTML_HANDLE = $_[0]; 3287 my $html_code = $_[1]; 3288 3289 # Remove leading tab from all lines 3290 $html_code =~ s/^\t//gm; 3291 3292 print(HTML_HANDLE $html_code) 3293 or die("ERROR: cannot write HTML data ($!)\n"); 3294 } 3295 3296 3297 # 3298 # write_html_prolog(filehandle, base_dir, pagetitle) 3299 # 3300 # Write an HTML prolog common to all HTML files to FILEHANDLE. PAGETITLE will 3301 # be used as HTML page title. BASE_DIR contains a relative path which points 3302 # to the base directory. 3303 # 3304 3305 sub write_html_prolog(*$$) 3306 { 3307 my $basedir = $_[1]; 3308 my $pagetitle = $_[2]; 3309 my $prolog; 3310 3311 $prolog = $html_prolog; 3312 $prolog =~ s/\@pagetitle\@/$pagetitle/g; 3313 $prolog =~ s/\@basedir\@/$basedir/g; 3314 3315 write_html($_[0], $prolog); 3316 } 3317 3318 3319 # 3320 # write_header_prolog(filehandle, base_dir) 3321 # 3322 # Write beginning of page header HTML code. 3323 # 3324 3325 sub write_header_prolog(*$) 3326 { 3327 # ************************************************************* 3328 3329 write_html($_[0], <<END_OF_HTML) 3330 <table width="100%" border=0 cellspacing=0 cellpadding=0> 3331 <tr><td class="title">$title</td></tr> 3332 <tr><td class="ruler"><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr> 3333 3334 <tr> 3335 <td width="100%"> 3336 <table cellpadding=1 border=0 width="100%"> 3337 END_OF_HTML 3338 ; 3339 3340 # ************************************************************* 3341 } 3342 3343 3344 # 3345 # write_header_line(handle, content) 3346 # 3347 # Write a header line with the specified table contents. 3348 # 3349 3350 sub write_header_line(*@) 3351 { 3352 my ($handle, @content) = @_; 3353 my $entry; 3354 3355 write_html($handle, " <tr>\n"); 3356 foreach $entry (@content) { 3357 my ($width, $class, $text, $colspan) = @{$entry}; 3358 3359 if (defined($width)) { 3360 $width = " width=\"$width\""; 3361 } else { 3362 $width = ""; 3363 } 3364 if (defined($class)) { 3365 $class = " class=\"$class\""; 3366 } else { 3367 $class = ""; 3368 } 3369 if (defined($colspan)) { 3370 $colspan = " colspan=\"$colspan\""; 3371 } else { 3372 $colspan = ""; 3373 } 3374 $text = "" if (!defined($text)); 3375 write_html($handle, 3376 " <td$width$class$colspan>$text</td>\n"); 3377 } 3378 write_html($handle, " </tr>\n"); 3379 } 3380 3381 3382 # 3383 # write_header_epilog(filehandle, base_dir) 3384 # 3385 # Write end of page header HTML code. 3386 # 3387 3388 sub write_header_epilog(*$) 3389 { 3390 # ************************************************************* 3391 3392 write_html($_[0], <<END_OF_HTML) 3393 <tr><td><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr> 3394 </table> 3395 </td> 3396 </tr> 3397 3398 <tr><td class="ruler"><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr> 3399 </table> 3400 3401 END_OF_HTML 3402 ; 3403 3404 # ************************************************************* 3405 } 3406 3407 3408 # 3409 # write_file_table_prolog(handle, file_heading, ([heading, num_cols], ...)) 3410 # 3411 # Write heading for file table. 3412 # 3413 3414 sub write_file_table_prolog(*$@) 3415 { 3416 my ($handle, $file_heading, @columns) = @_; 3417 my $num_columns = 0; 3418 my $file_width; 3419 my $col; 3420 my $width; 3421 3422 $width = 20 if (scalar(@columns) == 1); 3423 $width = 10 if (scalar(@columns) == 2); 3424 $width = 8 if (scalar(@columns) > 2); 3425 3426 foreach $col (@columns) { 3427 my ($heading, $cols) = @{$col}; 3428 3429 $num_columns += $cols; 3430 } 3431 $file_width = 100 - $num_columns * $width; 3432 3433 # Table definition 3434 write_html($handle, <<END_OF_HTML); 3435 <center> 3436 <table width="80%" cellpadding=1 cellspacing=1 border=0> 3437 3438 <tr> 3439 <td width="$file_width%"><br></td> 3440 END_OF_HTML 3441 # Empty first row 3442 foreach $col (@columns) { 3443 my ($heading, $cols) = @{$col}; 3444 3445 while ($cols-- > 0) { 3446 write_html($handle, <<END_OF_HTML); 3447 <td width="$width%"></td> 3448 END_OF_HTML 3449 } 3450 } 3451 # Next row 3452 write_html($handle, <<END_OF_HTML); 3453 </tr> 3454 3455 <tr> 3456 <td class="tableHead">$file_heading</td> 3457 END_OF_HTML 3458 # Heading row 3459 foreach $col (@columns) { 3460 my ($heading, $cols) = @{$col}; 3461 my $colspan = ""; 3462 3463 $colspan = " colspan=$cols" if ($cols > 1); 3464 write_html($handle, <<END_OF_HTML); 3465 <td class="tableHead"$colspan>$heading</td> 3466 END_OF_HTML 3467 } 3468 write_html($handle, <<END_OF_HTML); 3469 </tr> 3470 END_OF_HTML 3471 } 3472 3473 3474 # write_file_table_entry(handle, base_dir, filename, page_link, 3475 # ([ found, hit, med_limit, hi_limit, graph ], ..) 3476 # 3477 # Write an entry of the file table. 3478 # 3479 3480 sub write_file_table_entry(*$$$@) 3481 { 3482 my ($handle, $base_dir, $filename, $page_link, @entries) = @_; 3483 my $file_code; 3484 my $entry; 3485 my $esc_filename = escape_html($filename); 3486 3487 # Add link to source if provided 3488 if (defined($page_link) && $page_link ne "") { 3489 $file_code = "<a href=\"$page_link\">$esc_filename</a>"; 3490 } else { 3491 $file_code = $esc_filename; 3492 } 3493 3494 # First column: filename 3495 write_html($handle, <<END_OF_HTML); 3496 <tr> 3497 <td class="coverFile">$file_code</td> 3498 END_OF_HTML 3499 # Columns as defined 3500 foreach $entry (@entries) { 3501 my ($found, $hit, $med, $hi, $graph) = @{$entry}; 3502 my $bar_graph; 3503 my $class; 3504 my $rate; 3505 3506 # Generate bar graph if requested 3507 if ($graph) { 3508 $bar_graph = get_bar_graph_code($base_dir, $found, 3509 $hit); 3510 write_html($handle, <<END_OF_HTML); 3511 <td class="coverBar" align="center"> 3512 $bar_graph 3513 </td> 3514 END_OF_HTML 3515 } 3516 # Get rate color and text 3517 if ($found == 0) { 3518 $rate = "-"; 3519 $class = "Hi"; 3520 } else { 3521 $rate = rate($hit, $found, " %"); 3522 $class = $rate_name[classify_rate($found, $hit, 3523 $med, $hi)]; 3524 } 3525 write_html($handle, <<END_OF_HTML); 3526 <td class="coverPer$class">$rate</td> 3527 <td class="coverNum$class">$hit / $found</td> 3528 END_OF_HTML 3529 } 3530 # End of row 3531 write_html($handle, <<END_OF_HTML); 3532 </tr> 3533 END_OF_HTML 3534 } 3535 3536 3537 # 3538 # write_file_table_detail_entry(filehandle, test_name, ([found, hit], ...)) 3539 # 3540 # Write entry for detail section in file table. 3541 # 3542 3543 sub write_file_table_detail_entry(*$@) 3544 { 3545 my ($handle, $test, @entries) = @_; 3546 my $entry; 3547 3548 if ($test eq "") { 3549 $test = "<span style=\"font-style:italic\"><unnamed></span>"; 3550 } elsif ($test =~ /^(.*),diff$/) { 3551 $test = $1." (converted)"; 3552 } 3553 # Testname 3554 write_html($handle, <<END_OF_HTML); 3555 <tr> 3556 <td class="testName" colspan=2>$test</td> 3557 END_OF_HTML 3558 # Test data 3559 foreach $entry (@entries) { 3560 my ($found, $hit) = @{$entry}; 3561 my $rate = rate($hit, $found, " %"); 3562 3563 write_html($handle, <<END_OF_HTML); 3564 <td class="testPer">$rate</td> 3565 <td class="testNum">$hit / $found</td> 3566 END_OF_HTML 3567 } 3568 3569 write_html($handle, <<END_OF_HTML); 3570 </tr> 3571 3572 END_OF_HTML 3573 3574 # ************************************************************* 3575 } 3576 3577 3578 # 3579 # write_file_table_epilog(filehandle) 3580 # 3581 # Write end of file table HTML code. 3582 # 3583 3584 sub write_file_table_epilog(*) 3585 { 3586 # ************************************************************* 3587 3588 write_html($_[0], <<END_OF_HTML) 3589 </table> 3590 </center> 3591 <br> 3592 3593 END_OF_HTML 3594 ; 3595 3596 # ************************************************************* 3597 } 3598 3599 3600 # 3601 # write_test_table_prolog(filehandle, table_heading) 3602 # 3603 # Write heading for test case description table. 3604 # 3605 3606 sub write_test_table_prolog(*$) 3607 { 3608 # ************************************************************* 3609 3610 write_html($_[0], <<END_OF_HTML) 3611 <center> 3612 <table width="80%" cellpadding=2 cellspacing=1 border=0> 3613 3614 <tr> 3615 <td><br></td> 3616 </tr> 3617 3618 <tr> 3619 <td class="tableHead">$_[1]</td> 3620 </tr> 3621 3622 <tr> 3623 <td class="testDescription"> 3624 <dl> 3625 END_OF_HTML 3626 ; 3627 3628 # ************************************************************* 3629 } 3630 3631 3632 # 3633 # write_test_table_entry(filehandle, test_name, test_description) 3634 # 3635 # Write entry for the test table. 3636 # 3637 3638 sub write_test_table_entry(*$$) 3639 { 3640 # ************************************************************* 3641 3642 write_html($_[0], <<END_OF_HTML) 3643 <dt>$_[1]<a name="$_[1]"> </a></dt> 3644 <dd>$_[2]<br><br></dd> 3645 END_OF_HTML 3646 ; 3647 3648 # ************************************************************* 3649 } 3650 3651 3652 # 3653 # write_test_table_epilog(filehandle) 3654 # 3655 # Write end of test description table HTML code. 3656 # 3657 3658 sub write_test_table_epilog(*) 3659 { 3660 # ************************************************************* 3661 3662 write_html($_[0], <<END_OF_HTML) 3663 </dl> 3664 </td> 3665 </tr> 3666 </table> 3667 </center> 3668 <br> 3669 3670 END_OF_HTML 3671 ; 3672 3673 # ************************************************************* 3674 } 3675 3676 3677 sub fmt_centered($$) 3678 { 3679 my ($width, $text) = @_; 3680 my $w0 = length($text); 3681 my $w1 = int(($width - $w0) / 2); 3682 my $w2 = $width - $w0 - $w1; 3683 3684 return (" "x$w1).$text.(" "x$w2); 3685 } 3686 3687 3688 # 3689 # write_source_prolog(filehandle) 3690 # 3691 # Write start of source code table. 3692 # 3693 3694 sub write_source_prolog(*) 3695 { 3696 my $lineno_heading = " "; 3697 my $branch_heading = ""; 3698 my $line_heading = fmt_centered($line_field_width, "Line data"); 3699 my $source_heading = " Source code"; 3700 3701 if ($br_coverage) { 3702 $branch_heading = fmt_centered($br_field_width, "Branch data"). 3703 " "; 3704 } 3705 # ************************************************************* 3706 3707 write_html($_[0], <<END_OF_HTML) 3708 <table cellpadding=0 cellspacing=0 border=0> 3709 <tr> 3710 <td><br></td> 3711 </tr> 3712 <tr> 3713 <td> 3714 <pre class="sourceHeading">${lineno_heading}${branch_heading}${line_heading} ${source_heading}</pre> 3715 <pre class="source"> 3716 END_OF_HTML 3717 ; 3718 3719 # ************************************************************* 3720 } 3721 3722 3723 # 3724 # get_branch_blocks(brdata) 3725 # 3726 # Group branches that belong to the same basic block. 3727 # 3728 # Returns: [block1, block2, ...] 3729 # block: [branch1, branch2, ...] 3730 # branch: [block_num, branch_num, taken_count, text_length, open, close] 3731 # 3732 3733 sub get_branch_blocks($) 3734 { 3735 my ($brdata) = @_; 3736 my $last_block_num; 3737 my $block = []; 3738 my @blocks; 3739 my $i; 3740 my $num = br_ivec_len($brdata); 3741 3742 # Group branches 3743 for ($i = 0; $i < $num; $i++) { 3744 my ($block_num, $branch, $taken) = br_ivec_get($brdata, $i); 3745 my $br; 3746 3747 if (defined($last_block_num) && $block_num != $last_block_num) { 3748 push(@blocks, $block); 3749 $block = []; 3750 } 3751 $br = [$block_num, $branch, $taken, 3, 0, 0]; 3752 push(@{$block}, $br); 3753 $last_block_num = $block_num; 3754 } 3755 push(@blocks, $block) if (scalar(@{$block}) > 0); 3756 3757 # Add braces to first and last branch in group 3758 foreach $block (@blocks) { 3759 $block->[0]->[$BR_OPEN] = 1; 3760 $block->[0]->[$BR_LEN]++; 3761 $block->[scalar(@{$block}) - 1]->[$BR_CLOSE] = 1; 3762 $block->[scalar(@{$block}) - 1]->[$BR_LEN]++; 3763 } 3764 3765 return @blocks; 3766 } 3767 3768 # 3769 # get_block_len(block) 3770 # 3771 # Calculate total text length of all branches in a block of branches. 3772 # 3773 3774 sub get_block_len($) 3775 { 3776 my ($block) = @_; 3777 my $len = 0; 3778 my $branch; 3779 3780 foreach $branch (@{$block}) { 3781 $len += $branch->[$BR_LEN]; 3782 } 3783 3784 return $len; 3785 } 3786 3787 3788 # 3789 # get_branch_html(brdata) 3790 # 3791 # Return a list of HTML lines which represent the specified branch coverage 3792 # data in source code view. 3793 # 3794 3795 sub get_branch_html($) 3796 { 3797 my ($brdata) = @_; 3798 my @blocks = get_branch_blocks($brdata); 3799 my $block; 3800 my $branch; 3801 my $line_len = 0; 3802 my $line = []; # [branch2|" ", branch|" ", ...] 3803 my @lines; # [line1, line2, ...] 3804 my @result; 3805 3806 # Distribute blocks to lines 3807 foreach $block (@blocks) { 3808 my $block_len = get_block_len($block); 3809 3810 # Does this block fit into the current line? 3811 if ($line_len + $block_len <= $br_field_width) { 3812 # Add it 3813 $line_len += $block_len; 3814 push(@{$line}, @{$block}); 3815 next; 3816 } elsif ($block_len <= $br_field_width) { 3817 # It would fit if the line was empty - add it to new 3818 # line 3819 push(@lines, $line); 3820 $line_len = $block_len; 3821 $line = [ @{$block} ]; 3822 next; 3823 } 3824 # Split the block into several lines 3825 foreach $branch (@{$block}) { 3826 if ($line_len + $branch->[$BR_LEN] >= $br_field_width) { 3827 # Start a new line 3828 if (($line_len + 1 <= $br_field_width) && 3829 scalar(@{$line}) > 0 && 3830 !$line->[scalar(@$line) - 1]->[$BR_CLOSE]) { 3831 # Try to align branch symbols to be in 3832 # one # row 3833 push(@{$line}, " "); 3834 } 3835 push(@lines, $line); 3836 $line_len = 0; 3837 $line = []; 3838 } 3839 push(@{$line}, $branch); 3840 $line_len += $branch->[$BR_LEN]; 3841 } 3842 } 3843 push(@lines, $line); 3844 3845 # Convert to HTML 3846 foreach $line (@lines) { 3847 my $current = ""; 3848 my $current_len = 0; 3849 3850 foreach $branch (@$line) { 3851 # Skip alignment space 3852 if ($branch eq " ") { 3853 $current .= " "; 3854 $current_len++; 3855 next; 3856 } 3857 3858 my ($block_num, $br_num, $taken, $len, $open, $close) = 3859 @{$branch}; 3860 my $class; 3861 my $title; 3862 my $text; 3863 3864 if ($taken eq '-') { 3865 $class = "branchNoExec"; 3866 $text = " # "; 3867 $title = "Branch $br_num was not executed"; 3868 } elsif ($taken == 0) { 3869 $class = "branchNoCov"; 3870 $text = " - "; 3871 $title = "Branch $br_num was not taken"; 3872 } else { 3873 $class = "branchCov"; 3874 $text = " + "; 3875 $title = "Branch $br_num was taken $taken ". 3876 "time"; 3877 $title .= "s" if ($taken > 1); 3878 } 3879 $current .= "[" if ($open); 3880 $current .= "<span class=\"$class\" title=\"$title\">"; 3881 $current .= $text."</span>"; 3882 $current .= "]" if ($close); 3883 $current_len += $len; 3884 } 3885 3886 # Right-align result text 3887 if ($current_len < $br_field_width) { 3888 $current = (" "x($br_field_width - $current_len)). 3889 $current; 3890 } 3891 push(@result, $current); 3892 } 3893 3894 return @result; 3895 } 3896 3897 3898 # 3899 # format_count(count, width) 3900 # 3901 # Return a right-aligned representation of count that fits in width characters. 3902 # 3903 3904 sub format_count($$) 3905 { 3906 my ($count, $width) = @_; 3907 my $result; 3908 my $exp; 3909 3910 $result = sprintf("%*.0f", $width, $count); 3911 while (length($result) > $width) { 3912 last if ($count < 10); 3913 $exp++; 3914 $count = int($count/10); 3915 $result = sprintf("%*s", $width, ">$count*10^$exp"); 3916 } 3917 return $result; 3918 } 3919 3920 # 3921 # write_source_line(filehandle, line_num, source, hit_count, converted, 3922 # brdata, add_anchor) 3923 # 3924 # Write formatted source code line. Return a line in a format as needed 3925 # by gen_png() 3926 # 3927 3928 sub write_source_line(*$$$$$$) 3929 { 3930 my ($handle, $line, $source, $count, $converted, $brdata, 3931 $add_anchor) = @_; 3932 my $source_format; 3933 my $count_format; 3934 my $result; 3935 my $anchor_start = ""; 3936 my $anchor_end = ""; 3937 my $count_field_width = $line_field_width - 1; 3938 my @br_html; 3939 my $html; 3940 3941 # Get branch HTML data for this line 3942 @br_html = get_branch_html($brdata) if ($br_coverage); 3943 3944 if (!defined($count)) { 3945 $result = ""; 3946 $source_format = ""; 3947 $count_format = " "x$count_field_width; 3948 } 3949 elsif ($count == 0) { 3950 $result = $count; 3951 $source_format = '<span class="lineNoCov">'; 3952 $count_format = format_count($count, $count_field_width); 3953 } 3954 elsif ($converted && defined($highlight)) { 3955 $result = "*".$count; 3956 $source_format = '<span class="lineDiffCov">'; 3957 $count_format = format_count($count, $count_field_width); 3958 } 3959 else { 3960 $result = $count; 3961 $source_format = '<span class="lineCov">'; 3962 $count_format = format_count($count, $count_field_width); 3963 } 3964 $result .= ":".$source; 3965 3966 # Write out a line number navigation anchor every $nav_resolution 3967 # lines if necessary 3968 if ($add_anchor) 3969 { 3970 $anchor_start = "<a name=\"$_[1]\">"; 3971 $anchor_end = "</a>"; 3972 } 3973 3974 3975 # ************************************************************* 3976 3977 $html = $anchor_start; 3978 $html .= "<span class=\"lineNum\">".sprintf("%8d", $line)." </span>"; 3979 $html .= shift(@br_html).":" if ($br_coverage); 3980 $html .= "$source_format$count_format : "; 3981 $html .= escape_html($source); 3982 $html .= "</span>" if ($source_format); 3983 $html .= $anchor_end."\n"; 3984 3985 write_html($handle, $html); 3986 3987 if ($br_coverage) { 3988 # Add lines for overlong branch information 3989 foreach (@br_html) { 3990 write_html($handle, "<span class=\"lineNum\">". 3991 " </span>$_\n"); 3992 } 3993 } 3994 # ************************************************************* 3995 3996 return($result); 3997 } 3998 3999 4000 # 4001 # write_source_epilog(filehandle) 4002 # 4003 # Write end of source code table. 4004 # 4005 4006 sub write_source_epilog(*) 4007 { 4008 # ************************************************************* 4009 4010 write_html($_[0], <<END_OF_HTML) 4011 </pre> 4012 </td> 4013 </tr> 4014 </table> 4015 <br> 4016 4017 END_OF_HTML 4018 ; 4019 4020 # ************************************************************* 4021 } 4022 4023 4024 # 4025 # write_html_epilog(filehandle, base_dir[, break_frames]) 4026 # 4027 # Write HTML page footer to FILEHANDLE. BREAK_FRAMES should be set when 4028 # this page is embedded in a frameset, clicking the URL link will then 4029 # break this frameset. 4030 # 4031 4032 sub write_html_epilog(*$;$) 4033 { 4034 my $basedir = $_[1]; 4035 my $break_code = ""; 4036 my $epilog; 4037 4038 if (defined($_[2])) 4039 { 4040 $break_code = " target=\"_parent\""; 4041 } 4042 4043 # ************************************************************* 4044 4045 write_html($_[0], <<END_OF_HTML) 4046 <table width="100%" border=0 cellspacing=0 cellpadding=0> 4047 <tr><td class="ruler"><img src="$_[1]glass.png" width=3 height=3 alt=""></td></tr> 4048 <tr><td class="versionInfo">Generated by: <a href="$lcov_url"$break_code>$lcov_version</a></td></tr> 4049 </table> 4050 <br> 4051 END_OF_HTML 4052 ; 4053 4054 $epilog = $html_epilog; 4055 $epilog =~ s/\@basedir\@/$basedir/g; 4056 4057 write_html($_[0], $epilog); 4058 } 4059 4060 4061 # 4062 # write_frameset(filehandle, basedir, basename, pagetitle) 4063 # 4064 # 4065 4066 sub write_frameset(*$$$) 4067 { 4068 my $frame_width = $overview_width + 40; 4069 4070 # ************************************************************* 4071 4072 write_html($_[0], <<END_OF_HTML) 4073 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN"> 4074 4075 <html lang="en"> 4076 4077 <head> 4078 <meta http-equiv="Content-Type" content="text/html; charset=$charset"> 4079 <title>$_[3]</title> 4080 <link rel="stylesheet" type="text/css" href="$_[1]gcov.css"> 4081 </head> 4082 4083 <frameset cols="$frame_width,*"> 4084 <frame src="$_[2].gcov.overview.$html_ext" name="overview"> 4085 <frame src="$_[2].gcov.$html_ext" name="source"> 4086 <noframes> 4087 <center>Frames not supported by your browser!<br></center> 4088 </noframes> 4089 </frameset> 4090 4091 </html> 4092 END_OF_HTML 4093 ; 4094 4095 # ************************************************************* 4096 } 4097 4098 4099 # 4100 # sub write_overview_line(filehandle, basename, line, link) 4101 # 4102 # 4103 4104 sub write_overview_line(*$$$) 4105 { 4106 my $y1 = $_[2] - 1; 4107 my $y2 = $y1 + $nav_resolution - 1; 4108 my $x2 = $overview_width - 1; 4109 4110 # ************************************************************* 4111 4112 write_html($_[0], <<END_OF_HTML) 4113 <area shape="rect" coords="0,$y1,$x2,$y2" href="$_[1].gcov.$html_ext#$_[3]" target="source" alt="overview"> 4114 END_OF_HTML 4115 ; 4116 4117 # ************************************************************* 4118 } 4119 4120 4121 # 4122 # write_overview(filehandle, basedir, basename, pagetitle, lines) 4123 # 4124 # 4125 4126 sub write_overview(*$$$$) 4127 { 4128 my $index; 4129 my $max_line = $_[4] - 1; 4130 my $offset; 4131 4132 # ************************************************************* 4133 4134 write_html($_[0], <<END_OF_HTML) 4135 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 4136 4137 <html lang="en"> 4138 4139 <head> 4140 <title>$_[3]</title> 4141 <meta http-equiv="Content-Type" content="text/html; charset=$charset"> 4142 <link rel="stylesheet" type="text/css" href="$_[1]gcov.css"> 4143 </head> 4144 4145 <body> 4146 <map name="overview"> 4147 END_OF_HTML 4148 ; 4149 4150 # ************************************************************* 4151 4152 # Make $offset the next higher multiple of $nav_resolution 4153 $offset = ($nav_offset + $nav_resolution - 1) / $nav_resolution; 4154 $offset = sprintf("%d", $offset ) * $nav_resolution; 4155 4156 # Create image map for overview image 4157 for ($index = 1; $index <= $_[4]; $index += $nav_resolution) 4158 { 4159 # Enforce nav_offset 4160 if ($index < $offset + 1) 4161 { 4162 write_overview_line($_[0], $_[2], $index, 1); 4163 } 4164 else 4165 { 4166 write_overview_line($_[0], $_[2], $index, $index - $offset); 4167 } 4168 } 4169 4170 # ************************************************************* 4171 4172 write_html($_[0], <<END_OF_HTML) 4173 </map> 4174 4175 <center> 4176 <a href="$_[2].gcov.$html_ext#top" target="source">Top</a><br><br> 4177 <img src="$_[2].gcov.png" width=$overview_width height=$max_line alt="Overview" border=0 usemap="#overview"> 4178 </center> 4179 </body> 4180 </html> 4181 END_OF_HTML 4182 ; 4183 4184 # ************************************************************* 4185 } 4186 4187 4188 sub max($$) 4189 { 4190 my ($a, $b) = @_; 4191 4192 return $a if ($a > $b); 4193 return $b; 4194 } 4195 4196 4197 # 4198 # write_header(filehandle, type, trunc_file_name, rel_file_name, lines_found, 4199 # lines_hit, funcs_found, funcs_hit, sort_type) 4200 # 4201 # Write a complete standard page header. TYPE may be (0, 1, 2, 3, 4) 4202 # corresponding to (directory view header, file view header, source view 4203 # header, test case description header, function view header) 4204 # 4205 4206 sub write_header(*$$$$$$$$$$) 4207 { 4208 local *HTML_HANDLE = $_[0]; 4209 my $type = $_[1]; 4210 my $trunc_name = $_[2]; 4211 my $rel_filename = $_[3]; 4212 my $lines_found = $_[4]; 4213 my $lines_hit = $_[5]; 4214 my $fn_found = $_[6]; 4215 my $fn_hit = $_[7]; 4216 my $br_found = $_[8]; 4217 my $br_hit = $_[9]; 4218 my $sort_type = $_[10]; 4219 my $base_dir; 4220 my $view; 4221 my $test; 4222 my $base_name; 4223 my $style; 4224 my $rate; 4225 my @row_left; 4226 my @row_right; 4227 my $num_rows; 4228 my $i; 4229 my $esc_trunc_name = escape_html($trunc_name); 4230 4231 $base_name = basename($rel_filename); 4232 4233 # Prepare text for "current view" field 4234 if ($type == $HDR_DIR) 4235 { 4236 # Main overview 4237 $base_dir = ""; 4238 $view = $overview_title; 4239 } 4240 elsif ($type == $HDR_FILE) 4241 { 4242 # Directory overview 4243 $base_dir = get_relative_base_path($rel_filename); 4244 $view = "<a href=\"$base_dir"."index.$html_ext\">". 4245 "$overview_title</a> - $esc_trunc_name"; 4246 } 4247 elsif ($type == $HDR_SOURCE || $type == $HDR_FUNC) 4248 { 4249 # File view 4250 my $dir_name = dirname($rel_filename); 4251 my $esc_base_name = escape_html($base_name); 4252 my $esc_dir_name = escape_html($dir_name); 4253 4254 $base_dir = get_relative_base_path($dir_name); 4255 if ($frames) 4256 { 4257 # Need to break frameset when clicking any of these 4258 # links 4259 $view = "<a href=\"$base_dir"."index.$html_ext\" ". 4260 "target=\"_parent\">$overview_title</a> - ". 4261 "<a href=\"index.$html_ext\" target=\"_parent\">". 4262 "$esc_dir_name</a> - $esc_base_name"; 4263 } 4264 else 4265 { 4266 $view = "<a href=\"$base_dir"."index.$html_ext\">". 4267 "$overview_title</a> - ". 4268 "<a href=\"index.$html_ext\">". 4269 "$esc_dir_name</a> - $esc_base_name"; 4270 } 4271 4272 # Add function suffix 4273 if ($func_coverage) { 4274 $view .= "<span style=\"font-size: 80%;\">"; 4275 if ($type == $HDR_SOURCE) { 4276 $view .= " (source / <a href=\"$base_name.func.$html_ext\">functions</a>)"; 4277 } elsif ($type == $HDR_FUNC) { 4278 $view .= " (<a href=\"$base_name.gcov.$html_ext\">source</a> / functions)"; 4279 } 4280 $view .= "</span>"; 4281 } 4282 } 4283 elsif ($type == $HDR_TESTDESC) 4284 { 4285 # Test description header 4286 $base_dir = ""; 4287 $view = "<a href=\"$base_dir"."index.$html_ext\">". 4288 "$overview_title</a> - test case descriptions"; 4289 } 4290 4291 # Prepare text for "test" field 4292 $test = escape_html($test_title); 4293 4294 # Append link to test description page if available 4295 if (%test_description && ($type != $HDR_TESTDESC)) 4296 { 4297 if ($frames && ($type == $HDR_SOURCE || $type == $HDR_FUNC)) 4298 { 4299 # Need to break frameset when clicking this link 4300 $test .= " ( <span style=\"font-size:80%;\">". 4301 "<a href=\"$base_dir". 4302 "descriptions.$html_ext\" target=\"_parent\">". 4303 "view descriptions</a></span> )"; 4304 } 4305 else 4306 { 4307 $test .= " ( <span style=\"font-size:80%;\">". 4308 "<a href=\"$base_dir". 4309 "descriptions.$html_ext\">". 4310 "view descriptions</a></span> )"; 4311 } 4312 } 4313 4314 # Write header 4315 write_header_prolog(*HTML_HANDLE, $base_dir); 4316 4317 # Left row 4318 push(@row_left, [[ "10%", "headerItem", "Current view:" ], 4319 [ "35%", "headerValue", $view ]]); 4320 push(@row_left, [[undef, "headerItem", "Test:"], 4321 [undef, "headerValue", $test]]); 4322 push(@row_left, [[undef, "headerItem", "Date:"], 4323 [undef, "headerValue", $date]]); 4324 4325 # Right row 4326 if ($legend && ($type == $HDR_SOURCE || $type == $HDR_FUNC)) { 4327 my $text = <<END_OF_HTML; 4328 Lines: 4329 <span class="coverLegendCov">hit</span> 4330 <span class="coverLegendNoCov">not hit</span> 4331 END_OF_HTML 4332 if ($br_coverage) { 4333 $text .= <<END_OF_HTML; 4334 | Branches: 4335 <span class="coverLegendCov">+</span> taken 4336 <span class="coverLegendNoCov">-</span> not taken 4337 <span class="coverLegendNoCov">#</span> not executed 4338 END_OF_HTML 4339 } 4340 push(@row_left, [[undef, "headerItem", "Legend:"], 4341 [undef, "headerValueLeg", $text]]); 4342 } elsif ($legend && ($type != $HDR_TESTDESC)) { 4343 my $text = <<END_OF_HTML; 4344 Rating: 4345 <span class="coverLegendCovLo" title="Coverage rates below $med_limit % are classified as low">low: < $med_limit %</span> 4346 <span class="coverLegendCovMed" title="Coverage rates between $med_limit % and $hi_limit % are classified as medium">medium: >= $med_limit %</span> 4347 <span class="coverLegendCovHi" title="Coverage rates of $hi_limit % and more are classified as high">high: >= $hi_limit %</span> 4348 END_OF_HTML 4349 push(@row_left, [[undef, "headerItem", "Legend:"], 4350 [undef, "headerValueLeg", $text]]); 4351 } 4352 if ($type == $HDR_TESTDESC) { 4353 push(@row_right, [[ "55%" ]]); 4354 } else { 4355 push(@row_right, [["15%", undef, undef ], 4356 ["10%", "headerCovTableHead", "Hit" ], 4357 ["10%", "headerCovTableHead", "Total" ], 4358 ["15%", "headerCovTableHead", "Coverage"]]); 4359 } 4360 # Line coverage 4361 $style = $rate_name[classify_rate($lines_found, $lines_hit, 4362 $med_limit, $hi_limit)]; 4363 $rate = rate($lines_hit, $lines_found, " %"); 4364 push(@row_right, [[undef, "headerItem", "Lines:"], 4365 [undef, "headerCovTableEntry", $lines_hit], 4366 [undef, "headerCovTableEntry", $lines_found], 4367 [undef, "headerCovTableEntry$style", $rate]]) 4368 if ($type != $HDR_TESTDESC); 4369 # Function coverage 4370 if ($func_coverage) { 4371 $style = $rate_name[classify_rate($fn_found, $fn_hit, 4372 $fn_med_limit, $fn_hi_limit)]; 4373 $rate = rate($fn_hit, $fn_found, " %"); 4374 push(@row_right, [[undef, "headerItem", "Functions:"], 4375 [undef, "headerCovTableEntry", $fn_hit], 4376 [undef, "headerCovTableEntry", $fn_found], 4377 [undef, "headerCovTableEntry$style", $rate]]) 4378 if ($type != $HDR_TESTDESC); 4379 } 4380 # Branch coverage 4381 if ($br_coverage) { 4382 $style = $rate_name[classify_rate($br_found, $br_hit, 4383 $br_med_limit, $br_hi_limit)]; 4384 $rate = rate($br_hit, $br_found, " %"); 4385 push(@row_right, [[undef, "headerItem", "Branches:"], 4386 [undef, "headerCovTableEntry", $br_hit], 4387 [undef, "headerCovTableEntry", $br_found], 4388 [undef, "headerCovTableEntry$style", $rate]]) 4389 if ($type != $HDR_TESTDESC); 4390 } 4391 4392 # Print rows 4393 $num_rows = max(scalar(@row_left), scalar(@row_right)); 4394 for ($i = 0; $i < $num_rows; $i++) { 4395 my $left = $row_left[$i]; 4396 my $right = $row_right[$i]; 4397 4398 if (!defined($left)) { 4399 $left = [[undef, undef, undef], [undef, undef, undef]]; 4400 } 4401 if (!defined($right)) { 4402 $right = []; 4403 } 4404 write_header_line(*HTML_HANDLE, @{$left}, 4405 [ $i == 0 ? "5%" : undef, undef, undef], 4406 @{$right}); 4407 } 4408 4409 # Fourth line 4410 write_header_epilog(*HTML_HANDLE, $base_dir); 4411 } 4412 4413 4414 # 4415 # get_sorted_keys(hash_ref, sort_type) 4416 # 4417 4418 sub get_sorted_keys($$) 4419 { 4420 my ($hash, $type) = @_; 4421 4422 if ($type == $SORT_FILE) { 4423 # Sort by name 4424 return sort(keys(%{$hash})); 4425 } elsif ($type == $SORT_LINE) { 4426 # Sort by line coverage 4427 return sort({$hash->{$a}[7] <=> $hash->{$b}[7]} keys(%{$hash})); 4428 } elsif ($type == $SORT_FUNC) { 4429 # Sort by function coverage; 4430 return sort({$hash->{$a}[8] <=> $hash->{$b}[8]} keys(%{$hash})); 4431 } elsif ($type == $SORT_BRANCH) { 4432 # Sort by br coverage; 4433 return sort({$hash->{$a}[9] <=> $hash->{$b}[9]} keys(%{$hash})); 4434 } 4435 } 4436 4437 sub get_sort_code($$$) 4438 { 4439 my ($link, $alt, $base) = @_; 4440 my $png; 4441 my $link_start; 4442 my $link_end; 4443 4444 if (!defined($link)) { 4445 $png = "glass.png"; 4446 $link_start = ""; 4447 $link_end = ""; 4448 } else { 4449 $png = "updown.png"; 4450 $link_start = '<a href="'.$link.'">'; 4451 $link_end = "</a>"; 4452 } 4453 4454 return ' <span class="tableHeadSort">'.$link_start. 4455 '<img src="'.$base.$png.'" width=10 height=14 '. 4456 'alt="'.$alt.'" title="'.$alt.'" border=0>'.$link_end.'</span>'; 4457 } 4458 4459 sub get_file_code($$$$) 4460 { 4461 my ($type, $text, $sort_button, $base) = @_; 4462 my $result = $text; 4463 my $link; 4464 4465 if ($sort_button) { 4466 if ($type == $HEAD_NO_DETAIL) { 4467 $link = "index.$html_ext"; 4468 } else { 4469 $link = "index-detail.$html_ext"; 4470 } 4471 } 4472 $result .= get_sort_code($link, "Sort by name", $base); 4473 4474 return $result; 4475 } 4476 4477 sub get_line_code($$$$$) 4478 { 4479 my ($type, $sort_type, $text, $sort_button, $base) = @_; 4480 my $result = $text; 4481 my $sort_link; 4482 4483 if ($type == $HEAD_NO_DETAIL) { 4484 # Just text 4485 if ($sort_button) { 4486 $sort_link = "index-sort-l.$html_ext"; 4487 } 4488 } elsif ($type == $HEAD_DETAIL_HIDDEN) { 4489 # Text + link to detail view 4490 $result .= ' ( <a class="detail" href="index-detail'. 4491 $fileview_sortname[$sort_type].'.'.$html_ext. 4492 '">show details</a> )'; 4493 if ($sort_button) { 4494 $sort_link = "index-sort-l.$html_ext"; 4495 } 4496 } else { 4497 # Text + link to standard view 4498 $result .= ' ( <a class="detail" href="index'. 4499 $fileview_sortname[$sort_type].'.'.$html_ext. 4500 '">hide details</a> )'; 4501 if ($sort_button) { 4502 $sort_link = "index-detail-sort-l.$html_ext"; 4503 } 4504 } 4505 # Add sort button 4506 $result .= get_sort_code($sort_link, "Sort by line coverage", $base); 4507 4508 return $result; 4509 } 4510 4511 sub get_func_code($$$$) 4512 { 4513 my ($type, $text, $sort_button, $base) = @_; 4514 my $result = $text; 4515 my $link; 4516 4517 if ($sort_button) { 4518 if ($type == $HEAD_NO_DETAIL) { 4519 $link = "index-sort-f.$html_ext"; 4520 } else { 4521 $link = "index-detail-sort-f.$html_ext"; 4522 } 4523 } 4524 $result .= get_sort_code($link, "Sort by function coverage", $base); 4525 return $result; 4526 } 4527 4528 sub get_br_code($$$$) 4529 { 4530 my ($type, $text, $sort_button, $base) = @_; 4531 my $result = $text; 4532 my $link; 4533 4534 if ($sort_button) { 4535 if ($type == $HEAD_NO_DETAIL) { 4536 $link = "index-sort-b.$html_ext"; 4537 } else { 4538 $link = "index-detail-sort-b.$html_ext"; 4539 } 4540 } 4541 $result .= get_sort_code($link, "Sort by branch coverage", $base); 4542 return $result; 4543 } 4544 4545 # 4546 # write_file_table(filehandle, base_dir, overview, testhash, testfnchash, 4547 # testbrhash, fileview, sort_type) 4548 # 4549 # Write a complete file table. OVERVIEW is a reference to a hash containing 4550 # the following mapping: 4551 # 4552 # filename -> "lines_found,lines_hit,funcs_found,funcs_hit,page_link, 4553 # func_link" 4554 # 4555 # TESTHASH is a reference to the following hash: 4556 # 4557 # filename -> \%testdata 4558 # %testdata: name of test affecting this file -> \%testcount 4559 # %testcount: line number -> execution count for a single test 4560 # 4561 # Heading of first column is "Filename" if FILEVIEW is true, "Directory name" 4562 # otherwise. 4563 # 4564 4565 sub write_file_table(*$$$$$$$) 4566 { 4567 local *HTML_HANDLE = $_[0]; 4568 my $base_dir = $_[1]; 4569 my $overview = $_[2]; 4570 my $testhash = $_[3]; 4571 my $testfnchash = $_[4]; 4572 my $testbrhash = $_[5]; 4573 my $fileview = $_[6]; 4574 my $sort_type = $_[7]; 4575 my $filename; 4576 my $bar_graph; 4577 my $hit; 4578 my $found; 4579 my $fn_found; 4580 my $fn_hit; 4581 my $br_found; 4582 my $br_hit; 4583 my $page_link; 4584 my $testname; 4585 my $testdata; 4586 my $testfncdata; 4587 my $testbrdata; 4588 my %affecting_tests; 4589 my $line_code = ""; 4590 my $func_code; 4591 my $br_code; 4592 my $file_code; 4593 my @head_columns; 4594 4595 # Determine HTML code for column headings 4596 if (($base_dir ne "") && $show_details) 4597 { 4598 my $detailed = keys(%{$testhash}); 4599 4600 $file_code = get_file_code($detailed ? $HEAD_DETAIL_HIDDEN : 4601 $HEAD_NO_DETAIL, 4602 $fileview ? "Filename" : "Directory", 4603 $sort && $sort_type != $SORT_FILE, 4604 $base_dir); 4605 $line_code = get_line_code($detailed ? $HEAD_DETAIL_SHOWN : 4606 $HEAD_DETAIL_HIDDEN, 4607 $sort_type, 4608 "Line Coverage", 4609 $sort && $sort_type != $SORT_LINE, 4610 $base_dir); 4611 $func_code = get_func_code($detailed ? $HEAD_DETAIL_HIDDEN : 4612 $HEAD_NO_DETAIL, 4613 "Functions", 4614 $sort && $sort_type != $SORT_FUNC, 4615 $base_dir); 4616 $br_code = get_br_code($detailed ? $HEAD_DETAIL_HIDDEN : 4617 $HEAD_NO_DETAIL, 4618 "Branches", 4619 $sort && $sort_type != $SORT_BRANCH, 4620 $base_dir); 4621 } else { 4622 $file_code = get_file_code($HEAD_NO_DETAIL, 4623 $fileview ? "Filename" : "Directory", 4624 $sort && $sort_type != $SORT_FILE, 4625 $base_dir); 4626 $line_code = get_line_code($HEAD_NO_DETAIL, $sort_type, "Line Coverage", 4627 $sort && $sort_type != $SORT_LINE, 4628 $base_dir); 4629 $func_code = get_func_code($HEAD_NO_DETAIL, "Functions", 4630 $sort && $sort_type != $SORT_FUNC, 4631 $base_dir); 4632 $br_code = get_br_code($HEAD_NO_DETAIL, "Branches", 4633 $sort && $sort_type != $SORT_BRANCH, 4634 $base_dir); 4635 } 4636 push(@head_columns, [ $line_code, 3 ]); 4637 push(@head_columns, [ $func_code, 2]) if ($func_coverage); 4638 push(@head_columns, [ $br_code, 2]) if ($br_coverage); 4639 4640 write_file_table_prolog(*HTML_HANDLE, $file_code, @head_columns); 4641 4642 foreach $filename (get_sorted_keys($overview, $sort_type)) 4643 { 4644 my @columns; 4645 ($found, $hit, $fn_found, $fn_hit, $br_found, $br_hit, 4646 $page_link) = @{$overview->{$filename}}; 4647 4648 # Line coverage 4649 push(@columns, [$found, $hit, $med_limit, $hi_limit, 1]); 4650 # Function coverage 4651 if ($func_coverage) { 4652 push(@columns, [$fn_found, $fn_hit, $fn_med_limit, 4653 $fn_hi_limit, 0]); 4654 } 4655 # Branch coverage 4656 if ($br_coverage) { 4657 push(@columns, [$br_found, $br_hit, $br_med_limit, 4658 $br_hi_limit, 0]); 4659 } 4660 write_file_table_entry(*HTML_HANDLE, $base_dir, $filename, 4661 $page_link, @columns); 4662 4663 $testdata = $testhash->{$filename}; 4664 $testfncdata = $testfnchash->{$filename}; 4665 $testbrdata = $testbrhash->{$filename}; 4666 4667 # Check whether we should write test specific coverage 4668 # as well 4669 if (!($show_details && $testdata)) { next; } 4670 4671 # Filter out those tests that actually affect this file 4672 %affecting_tests = %{ get_affecting_tests($testdata, 4673 $testfncdata, $testbrdata) }; 4674 4675 # Does any of the tests affect this file at all? 4676 if (!%affecting_tests) { next; } 4677 4678 foreach $testname (keys(%affecting_tests)) 4679 { 4680 my @results; 4681 ($found, $hit, $fn_found, $fn_hit, $br_found, $br_hit) = 4682 split(",", $affecting_tests{$testname}); 4683 4684 # Insert link to description of available 4685 if ($test_description{$testname}) 4686 { 4687 $testname = "<a href=\"$base_dir". 4688 "descriptions.$html_ext#$testname\">". 4689 "$testname</a>"; 4690 } 4691 4692 push(@results, [$found, $hit]); 4693 push(@results, [$fn_found, $fn_hit]) if ($func_coverage); 4694 push(@results, [$br_found, $br_hit]) if ($br_coverage); 4695 write_file_table_detail_entry(*HTML_HANDLE, $testname, 4696 @results); 4697 } 4698 } 4699 4700 write_file_table_epilog(*HTML_HANDLE); 4701 } 4702 4703 4704 # 4705 # get_found_and_hit(hash) 4706 # 4707 # Return the count for entries (found) and entries with an execution count 4708 # greater than zero (hit) in a hash (linenumber -> execution count) as 4709 # a list (found, hit) 4710 # 4711 4712 sub get_found_and_hit($) 4713 { 4714 my %hash = %{$_[0]}; 4715 my $found = 0; 4716 my $hit = 0; 4717 4718 # Calculate sum 4719 $found = 0; 4720 $hit = 0; 4721 4722 foreach (keys(%hash)) 4723 { 4724 $found++; 4725 if ($hash{$_}>0) { $hit++; } 4726 } 4727 4728 return ($found, $hit); 4729 } 4730 4731 4732 # 4733 # get_func_found_and_hit(sumfnccount) 4734 # 4735 # Return (f_found, f_hit) for sumfnccount 4736 # 4737 4738 sub get_func_found_and_hit($) 4739 { 4740 my ($sumfnccount) = @_; 4741 my $function; 4742 my $fn_found; 4743 my $fn_hit; 4744 4745 $fn_found = scalar(keys(%{$sumfnccount})); 4746 $fn_hit = 0; 4747 foreach $function (keys(%{$sumfnccount})) { 4748 if ($sumfnccount->{$function} > 0) { 4749 $fn_hit++; 4750 } 4751 } 4752 return ($fn_found, $fn_hit); 4753 } 4754 4755 4756 # 4757 # br_taken_to_num(taken) 4758 # 4759 # Convert a branch taken value .info format to number format. 4760 # 4761 4762 sub br_taken_to_num($) 4763 { 4764 my ($taken) = @_; 4765 4766 return 0 if ($taken eq '-'); 4767 return $taken + 1; 4768 } 4769 4770 4771 # 4772 # br_num_to_taken(taken) 4773 # 4774 # Convert a branch taken value in number format to .info format. 4775 # 4776 4777 sub br_num_to_taken($) 4778 { 4779 my ($taken) = @_; 4780 4781 return '-' if ($taken == 0); 4782 return $taken - 1; 4783 } 4784 4785 4786 # 4787 # br_taken_add(taken1, taken2) 4788 # 4789 # Return the result of taken1 + taken2 for 'branch taken' values. 4790 # 4791 4792 sub br_taken_add($$) 4793 { 4794 my ($t1, $t2) = @_; 4795 4796 return $t1 if (!defined($t2)); 4797 return $t2 if (!defined($t1)); 4798 return $t1 if ($t2 eq '-'); 4799 return $t2 if ($t1 eq '-'); 4800 return $t1 + $t2; 4801 } 4802 4803 4804 # 4805 # br_taken_sub(taken1, taken2) 4806 # 4807 # Return the result of taken1 - taken2 for 'branch taken' values. Return 0 4808 # if the result would become negative. 4809 # 4810 4811 sub br_taken_sub($$) 4812 { 4813 my ($t1, $t2) = @_; 4814 4815 return $t1 if (!defined($t2)); 4816 return undef if (!defined($t1)); 4817 return $t1 if ($t1 eq '-'); 4818 return $t1 if ($t2 eq '-'); 4819 return 0 if $t2 > $t1; 4820 return $t1 - $t2; 4821 } 4822 4823 4824 # 4825 # br_ivec_len(vector) 4826 # 4827 # Return the number of entries in the branch coverage vector. 4828 # 4829 4830 sub br_ivec_len($) 4831 { 4832 my ($vec) = @_; 4833 4834 return 0 if (!defined($vec)); 4835 return (length($vec) * 8 / $BR_VEC_WIDTH) / $BR_VEC_ENTRIES; 4836 } 4837 4838 4839 # 4840 # br_ivec_get(vector, number) 4841 # 4842 # Return an entry from the branch coverage vector. 4843 # 4844 4845 sub br_ivec_get($$) 4846 { 4847 my ($vec, $num) = @_; 4848 my $block; 4849 my $branch; 4850 my $taken; 4851 my $offset = $num * $BR_VEC_ENTRIES; 4852 4853 # Retrieve data from vector 4854 $block = vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH); 4855 $branch = vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH); 4856 $taken = vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH); 4857 4858 # Decode taken value from an integer 4859 $taken = br_num_to_taken($taken); 4860 4861 return ($block, $branch, $taken); 4862 } 4863 4864 4865 # 4866 # br_ivec_push(vector, block, branch, taken) 4867 # 4868 # Add an entry to the branch coverage vector. If an entry with the same 4869 # branch ID already exists, add the corresponding taken values. 4870 # 4871 4872 sub br_ivec_push($$$$) 4873 { 4874 my ($vec, $block, $branch, $taken) = @_; 4875 my $offset; 4876 my $num = br_ivec_len($vec); 4877 my $i; 4878 4879 $vec = "" if (!defined($vec)); 4880 4881 # Check if branch already exists in vector 4882 for ($i = 0; $i < $num; $i++) { 4883 my ($v_block, $v_branch, $v_taken) = br_ivec_get($vec, $i); 4884 4885 next if ($v_block != $block || $v_branch != $branch); 4886 4887 # Add taken counts 4888 $taken = br_taken_add($taken, $v_taken); 4889 last; 4890 } 4891 4892 $offset = $i * $BR_VEC_ENTRIES; 4893 $taken = br_taken_to_num($taken); 4894 4895 # Add to vector 4896 vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH) = $block; 4897 vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH) = $branch; 4898 vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH) = $taken; 4899 4900 return $vec; 4901 } 4902 4903 4904 # 4905 # get_br_found_and_hit(sumbrcount) 4906 # 4907 # Return (br_found, br_hit) for sumbrcount 4908 # 4909 4910 sub get_br_found_and_hit($) 4911 { 4912 my ($sumbrcount) = @_; 4913 my $line; 4914 my $br_found = 0; 4915 my $br_hit = 0; 4916 4917 foreach $line (keys(%{$sumbrcount})) { 4918 my $brdata = $sumbrcount->{$line}; 4919 my $i; 4920 my $num = br_ivec_len($brdata); 4921 4922 for ($i = 0; $i < $num; $i++) { 4923 my $taken; 4924 4925 (undef, undef, $taken) = br_ivec_get($brdata, $i); 4926 4927 $br_found++; 4928 $br_hit++ if ($taken ne "-" && $taken > 0); 4929 } 4930 } 4931 4932 return ($br_found, $br_hit); 4933 } 4934 4935 4936 # 4937 # get_affecting_tests(testdata, testfncdata, testbrdata) 4938 # 4939 # HASHREF contains a mapping filename -> (linenumber -> exec count). Return 4940 # a hash containing mapping filename -> "lines found, lines hit" for each 4941 # filename which has a nonzero hit count. 4942 # 4943 4944 sub get_affecting_tests($$$) 4945 { 4946 my ($testdata, $testfncdata, $testbrdata) = @_; 4947 my $testname; 4948 my $testcount; 4949 my $testfnccount; 4950 my $testbrcount; 4951 my %result; 4952 my $found; 4953 my $hit; 4954 my $fn_found; 4955 my $fn_hit; 4956 my $br_found; 4957 my $br_hit; 4958 4959 foreach $testname (keys(%{$testdata})) 4960 { 4961 # Get (line number -> count) hash for this test case 4962 $testcount = $testdata->{$testname}; 4963 $testfnccount = $testfncdata->{$testname}; 4964 $testbrcount = $testbrdata->{$testname}; 4965 4966 # Calculate sum 4967 ($found, $hit) = get_found_and_hit($testcount); 4968 ($fn_found, $fn_hit) = get_func_found_and_hit($testfnccount); 4969 ($br_found, $br_hit) = get_br_found_and_hit($testbrcount); 4970 4971 if ($hit>0) 4972 { 4973 $result{$testname} = "$found,$hit,$fn_found,$fn_hit,". 4974 "$br_found,$br_hit"; 4975 } 4976 } 4977 4978 return(\%result); 4979 } 4980 4981 4982 sub get_hash_reverse($) 4983 { 4984 my ($hash) = @_; 4985 my %result; 4986 4987 foreach (keys(%{$hash})) { 4988 $result{$hash->{$_}} = $_; 4989 } 4990 4991 return \%result; 4992 } 4993 4994 # 4995 # write_source(filehandle, source_filename, count_data, checksum_data, 4996 # converted_data, func_data, sumbrcount) 4997 # 4998 # Write an HTML view of a source code file. Returns a list containing 4999 # data as needed by gen_png(). 5000 # 5001 # Die on error. 5002 # 5003 5004 sub write_source($$$$$$$) 5005 { 5006 local *HTML_HANDLE = $_[0]; 5007 local *SOURCE_HANDLE; 5008 my $source_filename = $_[1]; 5009 my %count_data; 5010 my $line_number; 5011 my @result; 5012 my $checkdata = $_[3]; 5013 my $converted = $_[4]; 5014 my $funcdata = $_[5]; 5015 my $sumbrcount = $_[6]; 5016 my $datafunc = get_hash_reverse($funcdata); 5017 my $add_anchor; 5018 my @file; 5019 5020 if ($_[2]) 5021 { 5022 %count_data = %{$_[2]}; 5023 } 5024 5025 if (!open(SOURCE_HANDLE, "<", $source_filename)) { 5026 my @lines; 5027 my $last_line = 0; 5028 5029 if (!$ignore[$ERROR_SOURCE]) { 5030 die("ERROR: cannot read $source_filename\n"); 5031 } 5032 5033 # Continue without source file 5034 warn("WARNING: cannot read $source_filename!\n"); 5035 5036 @lines = sort( { $a <=> $b } keys(%count_data)); 5037 if (@lines) { 5038 $last_line = $lines[scalar(@lines) - 1]; 5039 } 5040 return ( ":" ) if ($last_line < 1); 5041 5042 # Simulate gcov behavior 5043 for ($line_number = 1; $line_number <= $last_line; 5044 $line_number++) { 5045 push(@file, "/* EOF */"); 5046 } 5047 } else { 5048 @file = <SOURCE_HANDLE>; 5049 } 5050 5051 write_source_prolog(*HTML_HANDLE); 5052 $line_number = 0; 5053 foreach (@file) { 5054 $line_number++; 5055 chomp($_); 5056 5057 # Also remove CR from line-end 5058 s/\015$//; 5059 5060 # Source code matches coverage data? 5061 if (defined($checkdata->{$line_number}) && 5062 ($checkdata->{$line_number} ne md5_base64($_))) 5063 { 5064 die("ERROR: checksum mismatch at $source_filename:". 5065 "$line_number\n"); 5066 } 5067 5068 $add_anchor = 0; 5069 if ($frames) { 5070 if (($line_number - 1) % $nav_resolution == 0) { 5071 $add_anchor = 1; 5072 } 5073 } 5074 if ($func_coverage) { 5075 if ($line_number == 1) { 5076 $add_anchor = 1; 5077 } elsif (defined($datafunc->{$line_number + 5078 $func_offset})) { 5079 $add_anchor = 1; 5080 } 5081 } 5082 push (@result, 5083 write_source_line(HTML_HANDLE, $line_number, 5084 $_, $count_data{$line_number}, 5085 $converted->{$line_number}, 5086 $sumbrcount->{$line_number}, $add_anchor)); 5087 } 5088 5089 close(SOURCE_HANDLE); 5090 write_source_epilog(*HTML_HANDLE); 5091 return(@result); 5092 } 5093 5094 5095 sub funcview_get_func_code($$$) 5096 { 5097 my ($name, $base, $type) = @_; 5098 my $result; 5099 my $link; 5100 5101 if ($sort && $type == 1) { 5102 $link = "$name.func.$html_ext"; 5103 } 5104 $result = "Function Name"; 5105 $result .= get_sort_code($link, "Sort by function name", $base); 5106 5107 return $result; 5108 } 5109 5110 sub funcview_get_count_code($$$) 5111 { 5112 my ($name, $base, $type) = @_; 5113 my $result; 5114 my $link; 5115 5116 if ($sort && $type == 0) { 5117 $link = "$name.func-sort-c.$html_ext"; 5118 } 5119 $result = "Hit count"; 5120 $result .= get_sort_code($link, "Sort by hit count", $base); 5121 5122 return $result; 5123 } 5124 5125 # 5126 # funcview_get_sorted(funcdata, sumfncdata, sort_type) 5127 # 5128 # Depending on the value of sort_type, return a list of functions sorted 5129 # by name (type 0) or by the associated call count (type 1). 5130 # 5131 5132 sub funcview_get_sorted($$$) 5133 { 5134 my ($funcdata, $sumfncdata, $type) = @_; 5135 5136 if ($type == 0) { 5137 return sort(keys(%{$funcdata})); 5138 } 5139 return sort({$sumfncdata->{$b} <=> $sumfncdata->{$a}} 5140 keys(%{$sumfncdata})); 5141 } 5142 5143 # 5144 # write_function_table(filehandle, source_file, sumcount, funcdata, 5145 # sumfnccount, testfncdata, sumbrcount, testbrdata, 5146 # base_name, base_dir, sort_type) 5147 # 5148 # Write an HTML table listing all functions in a source file, including 5149 # also function call counts and line coverages inside of each function. 5150 # 5151 # Die on error. 5152 # 5153 5154 sub write_function_table(*$$$$$$$$$$) 5155 { 5156 local *HTML_HANDLE = $_[0]; 5157 my $source = $_[1]; 5158 my $sumcount = $_[2]; 5159 my $funcdata = $_[3]; 5160 my $sumfncdata = $_[4]; 5161 my $testfncdata = $_[5]; 5162 my $sumbrcount = $_[6]; 5163 my $testbrdata = $_[7]; 5164 my $name = $_[8]; 5165 my $base = $_[9]; 5166 my $type = $_[10]; 5167 my $func; 5168 my $func_code; 5169 my $count_code; 5170 5171 # Get HTML code for headings 5172 $func_code = funcview_get_func_code($name, $base, $type); 5173 $count_code = funcview_get_count_code($name, $base, $type); 5174 write_html(*HTML_HANDLE, <<END_OF_HTML) 5175 <center> 5176 <table width="60%" cellpadding=1 cellspacing=1 border=0> 5177 <tr><td><br></td></tr> 5178 <tr> 5179 <td width="80%" class="tableHead">$func_code</td> 5180 <td width="20%" class="tableHead">$count_code</td> 5181 </tr> 5182 END_OF_HTML 5183 ; 5184 5185 # Get a sorted table 5186 foreach $func (funcview_get_sorted($funcdata, $sumfncdata, $type)) { 5187 if (!defined($funcdata->{$func})) 5188 { 5189 next; 5190 } 5191 5192 my $startline = $funcdata->{$func} - $func_offset; 5193 my $name = $func; 5194 my $count = $sumfncdata->{$name}; 5195 my $countstyle; 5196 5197 # Demangle C++ function names if requested 5198 if ($demangle_cpp) { 5199 $name = `c++filt "$name"`; 5200 chomp($name); 5201 } 5202 # Escape any remaining special characters 5203 $name = escape_html($name); 5204 if ($startline < 1) { 5205 $startline = 1; 5206 } 5207 if ($count == 0) { 5208 $countstyle = "coverFnLo"; 5209 } else { 5210 $countstyle = "coverFnHi"; 5211 } 5212 5213 write_html(*HTML_HANDLE, <<END_OF_HTML) 5214 <tr> 5215 <td class="coverFn"><a href="$source#$startline">$name</a></td> 5216 <td class="$countstyle">$count</td> 5217 </tr> 5218 END_OF_HTML 5219 ; 5220 } 5221 write_html(*HTML_HANDLE, <<END_OF_HTML) 5222 </table> 5223 <br> 5224 </center> 5225 END_OF_HTML 5226 ; 5227 } 5228 5229 5230 # 5231 # info(printf_parameter) 5232 # 5233 # Use printf to write PRINTF_PARAMETER to stdout only when the $quiet flag 5234 # is not set. 5235 # 5236 5237 sub info(@) 5238 { 5239 if (!$quiet) 5240 { 5241 # Print info string 5242 printf(@_); 5243 } 5244 } 5245 5246 5247 # 5248 # subtract_counts(data_ref, base_ref) 5249 # 5250 5251 sub subtract_counts($$) 5252 { 5253 my %data = %{$_[0]}; 5254 my %base = %{$_[1]}; 5255 my $line; 5256 my $data_count; 5257 my $base_count; 5258 my $hit = 0; 5259 my $found = 0; 5260 5261 foreach $line (keys(%data)) 5262 { 5263 $found++; 5264 $data_count = $data{$line}; 5265 $base_count = $base{$line}; 5266 5267 if (defined($base_count)) 5268 { 5269 $data_count -= $base_count; 5270 5271 # Make sure we don't get negative numbers 5272 if ($data_count<0) { $data_count = 0; } 5273 } 5274 5275 $data{$line} = $data_count; 5276 if ($data_count > 0) { $hit++; } 5277 } 5278 5279 return (\%data, $found, $hit); 5280 } 5281 5282 5283 # 5284 # subtract_fnccounts(data, base) 5285 # 5286 # Subtract function call counts found in base from those in data. 5287 # Return (data, f_found, f_hit). 5288 # 5289 5290 sub subtract_fnccounts($$) 5291 { 5292 my %data; 5293 my %base; 5294 my $func; 5295 my $data_count; 5296 my $base_count; 5297 my $fn_hit = 0; 5298 my $fn_found = 0; 5299 5300 %data = %{$_[0]} if (defined($_[0])); 5301 %base = %{$_[1]} if (defined($_[1])); 5302 foreach $func (keys(%data)) { 5303 $fn_found++; 5304 $data_count = $data{$func}; 5305 $base_count = $base{$func}; 5306 5307 if (defined($base_count)) { 5308 $data_count -= $base_count; 5309 5310 # Make sure we don't get negative numbers 5311 if ($data_count < 0) { 5312 $data_count = 0; 5313 } 5314 } 5315 5316 $data{$func} = $data_count; 5317 if ($data_count > 0) { 5318 $fn_hit++; 5319 } 5320 } 5321 5322 return (\%data, $fn_found, $fn_hit); 5323 } 5324 5325 5326 # 5327 # apply_baseline(data_ref, baseline_ref) 5328 # 5329 # Subtract the execution counts found in the baseline hash referenced by 5330 # BASELINE_REF from actual data in DATA_REF. 5331 # 5332 5333 sub apply_baseline($$) 5334 { 5335 my %data_hash = %{$_[0]}; 5336 my %base_hash = %{$_[1]}; 5337 my $filename; 5338 my $testname; 5339 my $data; 5340 my $data_testdata; 5341 my $data_funcdata; 5342 my $data_checkdata; 5343 my $data_testfncdata; 5344 my $data_testbrdata; 5345 my $data_count; 5346 my $data_testfnccount; 5347 my $data_testbrcount; 5348 my $base; 5349 my $base_checkdata; 5350 my $base_sumfnccount; 5351 my $base_sumbrcount; 5352 my $base_count; 5353 my $sumcount; 5354 my $sumfnccount; 5355 my $sumbrcount; 5356 my $found; 5357 my $hit; 5358 my $fn_found; 5359 my $fn_hit; 5360 my $br_found; 5361 my $br_hit; 5362 5363 foreach $filename (keys(%data_hash)) 5364 { 5365 # Get data set for data and baseline 5366 $data = $data_hash{$filename}; 5367 $base = $base_hash{$filename}; 5368 5369 # Skip data entries for which no base entry exists 5370 if (!defined($base)) 5371 { 5372 next; 5373 } 5374 5375 # Get set entries for data and baseline 5376 ($data_testdata, undef, $data_funcdata, $data_checkdata, 5377 $data_testfncdata, undef, $data_testbrdata) = 5378 get_info_entry($data); 5379 (undef, $base_count, undef, $base_checkdata, undef, 5380 $base_sumfnccount, undef, $base_sumbrcount) = 5381 get_info_entry($base); 5382 5383 # Check for compatible checksums 5384 merge_checksums($data_checkdata, $base_checkdata, $filename); 5385 5386 # sumcount has to be calculated anew 5387 $sumcount = {}; 5388 $sumfnccount = {}; 5389 $sumbrcount = {}; 5390 5391 # For each test case, subtract test specific counts 5392 foreach $testname (keys(%{$data_testdata})) 5393 { 5394 # Get counts of both data and baseline 5395 $data_count = $data_testdata->{$testname}; 5396 $data_testfnccount = $data_testfncdata->{$testname}; 5397 $data_testbrcount = $data_testbrdata->{$testname}; 5398 5399 ($data_count, undef, $hit) = 5400 subtract_counts($data_count, $base_count); 5401 ($data_testfnccount) = 5402 subtract_fnccounts($data_testfnccount, 5403 $base_sumfnccount); 5404 ($data_testbrcount) = 5405 combine_brcount($data_testbrcount, 5406 $base_sumbrcount, $BR_SUB); 5407 5408 5409 # Check whether this test case did hit any line at all 5410 if ($hit > 0) 5411 { 5412 # Write back resulting hash 5413 $data_testdata->{$testname} = $data_count; 5414 $data_testfncdata->{$testname} = 5415 $data_testfnccount; 5416 $data_testbrdata->{$testname} = 5417 $data_testbrcount; 5418 } 5419 else 5420 { 5421 # Delete test case which did not impact this 5422 # file 5423 delete($data_testdata->{$testname}); 5424 delete($data_testfncdata->{$testname}); 5425 delete($data_testbrdata->{$testname}); 5426 } 5427 5428 # Add counts to sum of counts 5429 ($sumcount, $found, $hit) = 5430 add_counts($sumcount, $data_count); 5431 ($sumfnccount, $fn_found, $fn_hit) = 5432 add_fnccount($sumfnccount, $data_testfnccount); 5433 ($sumbrcount, $br_found, $br_hit) = 5434 combine_brcount($sumbrcount, $data_testbrcount, 5435 $BR_ADD); 5436 } 5437 5438 # Write back resulting entry 5439 set_info_entry($data, $data_testdata, $sumcount, $data_funcdata, 5440 $data_checkdata, $data_testfncdata, $sumfnccount, 5441 $data_testbrdata, $sumbrcount, $found, $hit, 5442 $fn_found, $fn_hit, $br_found, $br_hit); 5443 5444 $data_hash{$filename} = $data; 5445 } 5446 5447 return (\%data_hash); 5448 } 5449 5450 5451 # 5452 # remove_unused_descriptions() 5453 # 5454 # Removes all test descriptions from the global hash %test_description which 5455 # are not present in %info_data. 5456 # 5457 5458 sub remove_unused_descriptions() 5459 { 5460 my $filename; # The current filename 5461 my %test_list; # Hash containing found test names 5462 my $test_data; # Reference to hash test_name -> count_data 5463 my $before; # Initial number of descriptions 5464 my $after; # Remaining number of descriptions 5465 5466 $before = scalar(keys(%test_description)); 5467 5468 foreach $filename (keys(%info_data)) 5469 { 5470 ($test_data) = get_info_entry($info_data{$filename}); 5471 foreach (keys(%{$test_data})) 5472 { 5473 $test_list{$_} = ""; 5474 } 5475 } 5476 5477 # Remove descriptions for tests which are not in our list 5478 foreach (keys(%test_description)) 5479 { 5480 if (!defined($test_list{$_})) 5481 { 5482 delete($test_description{$_}); 5483 } 5484 } 5485 5486 $after = scalar(keys(%test_description)); 5487 if ($after < $before) 5488 { 5489 info("Removed ".($before - $after). 5490 " unused descriptions, $after remaining.\n"); 5491 } 5492 } 5493 5494 5495 # 5496 # apply_prefix(filename, prefix) 5497 # 5498 # If FILENAME begins with PREFIX, remove PREFIX from FILENAME and return 5499 # resulting string, otherwise return FILENAME. 5500 # 5501 5502 sub apply_prefix($$) 5503 { 5504 my $filename = $_[0]; 5505 my $prefix = $_[1]; 5506 5507 if (defined($prefix) && ($prefix ne "")) 5508 { 5509 if ($filename =~ /^\Q$prefix\E\/(.*)$/) 5510 { 5511 return substr($filename, length($prefix) + 1); 5512 } 5513 } 5514 5515 return $filename; 5516 } 5517 5518 5519 # 5520 # system_no_output(mode, parameters) 5521 # 5522 # Call an external program using PARAMETERS while suppressing depending on 5523 # the value of MODE: 5524 # 5525 # MODE & 1: suppress STDOUT 5526 # MODE & 2: suppress STDERR 5527 # 5528 # Return 0 on success, non-zero otherwise. 5529 # 5530 5531 sub system_no_output($@) 5532 { 5533 my $mode = shift; 5534 my $result; 5535 local *OLD_STDERR; 5536 local *OLD_STDOUT; 5537 5538 # Save old stdout and stderr handles 5539 ($mode & 1) && open(OLD_STDOUT, ">>&", "STDOUT"); 5540 ($mode & 2) && open(OLD_STDERR, ">>&", "STDERR"); 5541 5542 # Redirect to /dev/null 5543 ($mode & 1) && open(STDOUT, ">", "/dev/null"); 5544 ($mode & 2) && open(STDERR, ">", "/dev/null"); 5545 5546 system(@_); 5547 $result = $?; 5548 5549 # Close redirected handles 5550 ($mode & 1) && close(STDOUT); 5551 ($mode & 2) && close(STDERR); 5552 5553 # Restore old handles 5554 ($mode & 1) && open(STDOUT, ">>&", "OLD_STDOUT"); 5555 ($mode & 2) && open(STDERR, ">>&", "OLD_STDERR"); 5556 5557 return $result; 5558 } 5559 5560 5561 # 5562 # read_config(filename) 5563 # 5564 # Read configuration file FILENAME and return a reference to a hash containing 5565 # all valid key=value pairs found. 5566 # 5567 5568 sub read_config($) 5569 { 5570 my $filename = $_[0]; 5571 my %result; 5572 my $key; 5573 my $value; 5574 local *HANDLE; 5575 5576 if (!open(HANDLE, "<", $filename)) 5577 { 5578 warn("WARNING: cannot read configuration file $filename\n"); 5579 return undef; 5580 } 5581 while (<HANDLE>) 5582 { 5583 chomp; 5584 # Skip comments 5585 s/#.*//; 5586 # Remove leading blanks 5587 s/^\s+//; 5588 # Remove trailing blanks 5589 s/\s+$//; 5590 next unless length; 5591 ($key, $value) = split(/\s*=\s*/, $_, 2); 5592 if (defined($key) && defined($value)) 5593 { 5594 $result{$key} = $value; 5595 } 5596 else 5597 { 5598 warn("WARNING: malformed statement in line $. ". 5599 "of configuration file $filename\n"); 5600 } 5601 } 5602 close(HANDLE); 5603 return \%result; 5604 } 5605 5606 5607 # 5608 # apply_config(REF) 5609 # 5610 # REF is a reference to a hash containing the following mapping: 5611 # 5612 # key_string => var_ref 5613 # 5614 # where KEY_STRING is a keyword and VAR_REF is a reference to an associated 5615 # variable. If the global configuration hashes CONFIG or OPT_RC contain a value 5616 # for keyword KEY_STRING, VAR_REF will be assigned the value for that keyword. 5617 # 5618 5619 sub apply_config($) 5620 { 5621 my $ref = $_[0]; 5622 5623 foreach (keys(%{$ref})) 5624 { 5625 if (defined($opt_rc{$_})) { 5626 ${$ref->{$_}} = $opt_rc{$_}; 5627 } elsif (defined($config->{$_})) { 5628 ${$ref->{$_}} = $config->{$_}; 5629 } 5630 } 5631 } 5632 5633 5634 # 5635 # get_html_prolog(FILENAME) 5636 # 5637 # If FILENAME is defined, return contents of file. Otherwise return default 5638 # HTML prolog. Die on error. 5639 # 5640 5641 sub get_html_prolog($) 5642 { 5643 my $filename = $_[0]; 5644 my $result = ""; 5645 5646 if (defined($filename)) 5647 { 5648 local *HANDLE; 5649 5650 open(HANDLE, "<", $filename) 5651 or die("ERROR: cannot open html prolog $filename!\n"); 5652 while (<HANDLE>) 5653 { 5654 $result .= $_; 5655 } 5656 close(HANDLE); 5657 } 5658 else 5659 { 5660 $result = <<END_OF_HTML 5661 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 5662 5663 <html lang="en"> 5664 5665 <head> 5666 <meta http-equiv="Content-Type" content="text/html; charset=$charset"> 5667 <title>\@pagetitle\@</title> 5668 <link rel="stylesheet" type="text/css" href="\@basedir\@gcov.css"> 5669 </head> 5670 5671 <body> 5672 5673 END_OF_HTML 5674 ; 5675 } 5676 5677 return $result; 5678 } 5679 5680 5681 # 5682 # get_html_epilog(FILENAME) 5683 # 5684 # If FILENAME is defined, return contents of file. Otherwise return default 5685 # HTML epilog. Die on error. 5686 # 5687 sub get_html_epilog($) 5688 { 5689 my $filename = $_[0]; 5690 my $result = ""; 5691 5692 if (defined($filename)) 5693 { 5694 local *HANDLE; 5695 5696 open(HANDLE, "<", $filename) 5697 or die("ERROR: cannot open html epilog $filename!\n"); 5698 while (<HANDLE>) 5699 { 5700 $result .= $_; 5701 } 5702 close(HANDLE); 5703 } 5704 else 5705 { 5706 $result = <<END_OF_HTML 5707 5708 </body> 5709 </html> 5710 END_OF_HTML 5711 ; 5712 } 5713 5714 return $result; 5715 5716 } 5717 5718 sub warn_handler($) 5719 { 5720 my ($msg) = @_; 5721 5722 warn("$tool_name: $msg"); 5723 } 5724 5725 sub die_handler($) 5726 { 5727 my ($msg) = @_; 5728 5729 die("$tool_name: $msg"); 5730 } 5731 5732 # 5733 # parse_ignore_errors(@ignore_errors) 5734 # 5735 # Parse user input about which errors to ignore. 5736 # 5737 5738 sub parse_ignore_errors(@) 5739 { 5740 my (@ignore_errors) = @_; 5741 my @items; 5742 my $item; 5743 5744 return if (!@ignore_errors); 5745 5746 foreach $item (@ignore_errors) { 5747 $item =~ s/\s//g; 5748 if ($item =~ /,/) { 5749 # Split and add comma-separated parameters 5750 push(@items, split(/,/, $item)); 5751 } else { 5752 # Add single parameter 5753 push(@items, $item); 5754 } 5755 } 5756 foreach $item (@items) { 5757 my $item_id = $ERROR_ID{lc($item)}; 5758 5759 if (!defined($item_id)) { 5760 die("ERROR: unknown argument for --ignore-errors: ". 5761 "$item\n"); 5762 } 5763 $ignore[$item_id] = 1; 5764 } 5765 } 5766 5767 # 5768 # rate(hit, found[, suffix, precision, width]) 5769 # 5770 # Return the coverage rate [0..100] for HIT and FOUND values. 0 is only 5771 # returned when HIT is 0. 100 is only returned when HIT equals FOUND. 5772 # PRECISION specifies the precision of the result. SUFFIX defines a 5773 # string that is appended to the result if FOUND is non-zero. Spaces 5774 # are added to the start of the resulting string until it is at least WIDTH 5775 # characters wide. 5776 # 5777 5778 sub rate($$;$$$) 5779 { 5780 my ($hit, $found, $suffix, $precision, $width) = @_; 5781 my $rate; 5782 5783 # Assign defaults if necessary 5784 $precision = 1 if (!defined($precision)); 5785 $suffix = "" if (!defined($suffix)); 5786 $width = 0 if (!defined($width)); 5787 5788 return sprintf("%*s", $width, "-") if (!defined($found) || $found == 0); 5789 $rate = sprintf("%.*f", $precision, $hit * 100 / $found); 5790 5791 # Adjust rates if necessary 5792 if ($rate == 0 && $hit > 0) { 5793 $rate = sprintf("%.*f", $precision, 1 / 10 ** $precision); 5794 } elsif ($rate == 100 && $hit != $found) { 5795 $rate = sprintf("%.*f", $precision, 100 - 1 / 10 ** $precision); 5796 } 5797 5798 return sprintf("%*s", $width, $rate.$suffix); 5799 } 5800