Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(388)

Side by Side Diff: third_party/lcov-1.9/bin/genhtml

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

Powered by Google App Engine
This is Rietveld 408576698