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