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