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