OLD | NEW |
| (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/&/&/g; # & -> & | |
2324 $string =~ s/</</g; # < -> < | |
2325 $string =~ s/>/>/g; # > -> > | |
2326 $string =~ s/\"/"/g; # " -> " | |
2327 | |
2328 while ($string =~ /^([^\t]*)(\t)/) | |
2329 { | |
2330 my $replacement = " "x($tab_size - (length($1) % $tab_size)); | |
2331 $string =~ s/^([^\t]*)(\t)/$1$replacement/; | |
2332 } | |
2333 | |
2334 $string =~ s/\n/<br>/g; # \n -> <br> | |
2335 | |
2336 return $string; | |
2337 } | |
2338 | |
2339 | |
2340 # | |
2341 # get_date_string() | |
2342 # | |
2343 # Return the current date in the form: yyyy-mm-dd | |
2344 # | |
2345 | |
2346 sub get_date_string() | |
2347 { | |
2348 my $year; | |
2349 my $month; | |
2350 my $day; | |
2351 | |
2352 ($year, $month, $day) = (localtime())[5, 4, 3]; | |
2353 | |
2354 return sprintf("%d-%02d-%02d", $year+1900, $month+1, $day); | |
2355 } | |
2356 | |
2357 | |
2358 # | |
2359 # create_sub_dir(dir_name) | |
2360 # | |
2361 # Create subdirectory DIR_NAME if it does not already exist, including all its | |
2362 # parent directories. | |
2363 # | |
2364 # Die on error. | |
2365 # | |
2366 | |
2367 sub create_sub_dir($) | |
2368 { | |
2369 my ($dir) = @_; | |
2370 | |
2371 system("mkdir", "-p" ,$dir) | |
2372 and die("ERROR: cannot create directory $dir!\n"); | |
2373 } | |
2374 | |
2375 | |
2376 # | |
2377 # write_description_file(descriptions, overall_found, overall_hit, | |
2378 # total_fn_found, total_fn_hit, total_br_found, | |
2379 # total_br_hit) | |
2380 # | |
2381 # Write HTML file containing all test case descriptions. DESCRIPTIONS is a | |
2382 # reference to a hash containing a mapping | |
2383 # | |
2384 # test case name -> test case description | |
2385 # | |
2386 # Die on error. | |
2387 # | |
2388 | |
2389 sub write_description_file($$$$$$$) | |
2390 { | |
2391 my %description = %{$_[0]}; | |
2392 my $found = $_[1]; | |
2393 my $hit = $_[2]; | |
2394 my $fn_found = $_[3]; | |
2395 my $fn_hit = $_[4]; | |
2396 my $br_found = $_[5]; | |
2397 my $br_hit = $_[6]; | |
2398 my $test_name; | |
2399 local *HTML_HANDLE; | |
2400 | |
2401 html_create(*HTML_HANDLE,"descriptions.$html_ext"); | |
2402 write_html_prolog(*HTML_HANDLE, "", "LCOV - test case descriptions"); | |
2403 write_header(*HTML_HANDLE, 3, "", "", $found, $hit, $fn_found, | |
2404 $fn_hit, $br_found, $br_hit, 0); | |
2405 | |
2406 write_test_table_prolog(*HTML_HANDLE, | |
2407 "Test case descriptions - alphabetical list"); | |
2408 | |
2409 foreach $test_name (sort(keys(%description))) | |
2410 { | |
2411 write_test_table_entry(*HTML_HANDLE, $test_name, | |
2412 escape_html($description{$test_name})); | |
2413 } | |
2414 | |
2415 write_test_table_epilog(*HTML_HANDLE); | |
2416 write_html_epilog(*HTML_HANDLE, ""); | |
2417 | |
2418 close(*HTML_HANDLE); | |
2419 } | |
2420 | |
2421 | |
2422 | |
2423 # | |
2424 # write_png_files() | |
2425 # | |
2426 # Create all necessary .png files for the HTML-output in the current | |
2427 # directory. .png-files are used as bar graphs. | |
2428 # | |
2429 # Die on error. | |
2430 # | |
2431 | |
2432 sub write_png_files() | |
2433 { | |
2434 my %data; | |
2435 local *PNG_HANDLE; | |
2436 | |
2437 $data{"ruby.png"} = | |
2438 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, | |
2439 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, | |
2440 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, | |
2441 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, | |
2442 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x18, 0x10, 0x5d, 0x57, | |
2443 0x34, 0x6e, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, | |
2444 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2, | |
2445 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, | |
2446 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, | |
2447 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0x35, 0x2f, | |
2448 0x00, 0x00, 0x00, 0xd0, 0x33, 0x9a, 0x9d, 0x00, 0x00, 0x00, | |
2449 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00, | |
2450 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00, | |
2451 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, | |
2452 0x82]; | |
2453 $data{"amber.png"} = | |
2454 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, | |
2455 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, | |
2456 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, | |
2457 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, | |
2458 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x28, 0x04, 0x98, 0xcb, | |
2459 0xd6, 0xe0, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, | |
2460 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2, | |
2461 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, | |
2462 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, | |
2463 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xe0, 0x50, | |
2464 0x00, 0x00, 0x00, 0xa2, 0x7a, 0xda, 0x7e, 0x00, 0x00, 0x00, | |
2465 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00, | |
2466 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00, | |
2467 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, | |
2468 0x82]; | |
2469 $data{"emerald.png"} = | |
2470 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, | |
2471 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, | |
2472 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, | |
2473 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, | |
2474 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x22, 0x2b, 0xc9, 0xf5, | |
2475 0x03, 0x33, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, | |
2476 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2, | |
2477 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, | |
2478 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, | |
2479 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0x1b, 0xea, 0x59, | |
2480 0x0a, 0x0a, 0x0a, 0x0f, 0xba, 0x50, 0x83, 0x00, 0x00, 0x00, | |
2481 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00, | |
2482 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00, | |
2483 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, | |
2484 0x82]; | |
2485 $data{"snow.png"} = | |
2486 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, | |
2487 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, | |
2488 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, | |
2489 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, 0x4d, | |
2490 0x45, 0x07, 0xd2, 0x07, 0x11, 0x0f, 0x1e, 0x1d, 0x75, 0xbc, | |
2491 0xef, 0x55, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, | |
2492 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, 0xd2, | |
2493 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, | |
2494 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, | |
2495 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xff, 0xff, | |
2496 0x00, 0x00, 0x00, 0x55, 0xc2, 0xd3, 0x7e, 0x00, 0x00, 0x00, | |
2497 0x0a, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0x60, 0x00, | |
2498 0x00, 0x00, 0x02, 0x00, 0x01, 0xe5, 0x27, 0xde, 0xfc, 0x00, | |
2499 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, | |
2500 0x82]; | |
2501 $data{"glass.png"} = | |
2502 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, | |
2503 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, | |
2504 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x00, 0x00, 0x25, | |
2505 0xdb, 0x56, 0xca, 0x00, 0x00, 0x00, 0x04, 0x67, 0x41, 0x4d, | |
2506 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, | |
2507 0x00, 0x00, 0x06, 0x50, 0x4c, 0x54, 0x45, 0xff, 0xff, 0xff, | |
2508 0x00, 0x00, 0x00, 0x55, 0xc2, 0xd3, 0x7e, 0x00, 0x00, 0x00, | |
2509 0x01, 0x74, 0x52, 0x4e, 0x53, 0x00, 0x40, 0xe6, 0xd8, 0x66, | |
2510 0x00, 0x00, 0x00, 0x01, 0x62, 0x4b, 0x47, 0x44, 0x00, 0x88, | |
2511 0x05, 0x1d, 0x48, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, | |
2512 0x73, 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b, 0x12, 0x01, | |
2513 0xd2, 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x07, 0x74, 0x49, | |
2514 0x4d, 0x45, 0x07, 0xd2, 0x07, 0x13, 0x0f, 0x08, 0x19, 0xc4, | |
2515 0x40, 0x56, 0x10, 0x00, 0x00, 0x00, 0x0a, 0x49, 0x44, 0x41, | |
2516 0x54, 0x78, 0x9c, 0x63, 0x60, 0x00, 0x00, 0x00, 0x02, 0x00, | |
2517 0x01, 0x48, 0xaf, 0xa4, 0x71, 0x00, 0x00, 0x00, 0x00, 0x49, | |
2518 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82]; | |
2519 $data{"updown.png"} = | |
2520 [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, | |
2521 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x0a, | |
2522 0x00, 0x00, 0x00, 0x0e, 0x08, 0x06, 0x00, 0x00, 0x00, 0x16, | |
2523 0xa3, 0x8d, 0xab, 0x00, 0x00, 0x00, 0x3c, 0x49, 0x44, 0x41, | |
2524 0x54, 0x28, 0xcf, 0x63, 0x60, 0x40, 0x03, 0xff, 0xa1, 0x00, | |
2525 0x5d, 0x9c, 0x11, 0x5d, 0x11, 0x8a, 0x24, 0x23, 0x23, 0x23, | |
2526 0x86, 0x42, 0x6c, 0xa6, 0x20, 0x2b, 0x66, 0xc4, 0xa7, 0x08, | |
2527 0x59, 0x31, 0x23, 0x21, 0x45, 0x30, 0xc0, 0xc4, 0x30, 0x60, | |
2528 0x80, 0xfa, 0x6e, 0x24, 0x3e, 0x78, 0x48, 0x0a, 0x70, 0x62, | |
2529 0xa2, 0x90, 0x81, 0xd8, 0x44, 0x01, 0x00, 0xe9, 0x5c, 0x2f, | |
2530 0xf5, 0xe2, 0x9d, 0x0f, 0xf9, 0x00, 0x00, 0x00, 0x00, 0x49, | |
2531 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82] if ($sort); | |
2532 foreach (keys(%data)) | |
2533 { | |
2534 open(PNG_HANDLE, ">".$_) | |
2535 or die("ERROR: cannot create $_!\n"); | |
2536 binmode(PNG_HANDLE); | |
2537 print(PNG_HANDLE map(chr,@{$data{$_}})); | |
2538 close(PNG_HANDLE); | |
2539 } | |
2540 } | |
2541 | |
2542 | |
2543 # | |
2544 # write_htaccess_file() | |
2545 # | |
2546 | |
2547 sub write_htaccess_file() | |
2548 { | |
2549 local *HTACCESS_HANDLE; | |
2550 my $htaccess_data; | |
2551 | |
2552 open(*HTACCESS_HANDLE, ">.htaccess") | |
2553 or die("ERROR: cannot open .htaccess for writing!\n"); | |
2554 | |
2555 $htaccess_data = (<<"END_OF_HTACCESS") | |
2556 AddEncoding x-gzip .html | |
2557 END_OF_HTACCESS | |
2558 ; | |
2559 | |
2560 print(HTACCESS_HANDLE $htaccess_data); | |
2561 close(*HTACCESS_HANDLE); | |
2562 } | |
2563 | |
2564 | |
2565 # | |
2566 # write_css_file() | |
2567 # | |
2568 # Write the cascading style sheet file gcov.css to the current directory. | |
2569 # This file defines basic layout attributes of all generated HTML pages. | |
2570 # | |
2571 | |
2572 sub write_css_file() | |
2573 { | |
2574 local *CSS_HANDLE; | |
2575 | |
2576 # Check for a specified external style sheet file | |
2577 if ($css_filename) | |
2578 { | |
2579 # Simply copy that file | |
2580 system("cp", $css_filename, "gcov.css") | |
2581 and die("ERROR: cannot copy file $css_filename!\n"); | |
2582 return; | |
2583 } | |
2584 | |
2585 open(CSS_HANDLE, ">gcov.css") | |
2586 or die ("ERROR: cannot open gcov.css for writing!\n"); | |
2587 | |
2588 | |
2589 # ************************************************************* | |
2590 | |
2591 my $css_data = ($_=<<"END_OF_CSS") | |
2592 /* All views: initial background and text color */ | |
2593 body | |
2594 { | |
2595 color: #000000; | |
2596 background-color: #FFFFFF; | |
2597 } | |
2598 | |
2599 /* All views: standard link format*/ | |
2600 a:link | |
2601 { | |
2602 color: #284FA8; | |
2603 text-decoration: underline; | |
2604 } | |
2605 | |
2606 /* All views: standard link - visited format */ | |
2607 a:visited | |
2608 { | |
2609 color: #00CB40; | |
2610 text-decoration: underline; | |
2611 } | |
2612 | |
2613 /* All views: standard link - activated format */ | |
2614 a:active | |
2615 { | |
2616 color: #FF0040; | |
2617 text-decoration: underline; | |
2618 } | |
2619 | |
2620 /* All views: main title format */ | |
2621 td.title | |
2622 { | |
2623 text-align: center; | |
2624 padding-bottom: 10px; | |
2625 font-family: sans-serif; | |
2626 font-size: 20pt; | |
2627 font-style: italic; | |
2628 font-weight: bold; | |
2629 } | |
2630 | |
2631 /* All views: header item format */ | |
2632 td.headerItem | |
2633 { | |
2634 text-align: right; | |
2635 padding-right: 6px; | |
2636 font-family: sans-serif; | |
2637 font-weight: bold; | |
2638 vertical-align: top; | |
2639 white-space: nowrap; | |
2640 } | |
2641 | |
2642 /* All views: header item value format */ | |
2643 td.headerValue | |
2644 { | |
2645 text-align: left; | |
2646 color: #284FA8; | |
2647 font-family: sans-serif; | |
2648 font-weight: bold; | |
2649 white-space: nowrap; | |
2650 } | |
2651 | |
2652 /* All views: header item coverage table heading */ | |
2653 td.headerCovTableHead | |
2654 { | |
2655 text-align: center; | |
2656 padding-right: 6px; | |
2657 padding-left: 6px; | |
2658 padding-bottom: 0px; | |
2659 font-family: sans-serif; | |
2660 font-size: 80%; | |
2661 white-space: nowrap; | |
2662 } | |
2663 | |
2664 /* All views: header item coverage table entry */ | |
2665 td.headerCovTableEntry | |
2666 { | |
2667 text-align: right; | |
2668 color: #284FA8; | |
2669 font-family: sans-serif; | |
2670 font-weight: bold; | |
2671 white-space: nowrap; | |
2672 padding-left: 12px; | |
2673 padding-right: 4px; | |
2674 background-color: #DAE7FE; | |
2675 } | |
2676 | |
2677 /* All views: header item coverage table entry for high coverage rate */ | |
2678 td.headerCovTableEntryHi | |
2679 { | |
2680 text-align: right; | |
2681 color: #000000; | |
2682 font-family: sans-serif; | |
2683 font-weight: bold; | |
2684 white-space: nowrap; | |
2685 padding-left: 12px; | |
2686 padding-right: 4px; | |
2687 background-color: #A7FC9D; | |
2688 } | |
2689 | |
2690 /* All views: header item coverage table entry for medium coverage rate
*/ | |
2691 td.headerCovTableEntryMed | |
2692 { | |
2693 text-align: right; | |
2694 color: #000000; | |
2695 font-family: sans-serif; | |
2696 font-weight: bold; | |
2697 white-space: nowrap; | |
2698 padding-left: 12px; | |
2699 padding-right: 4px; | |
2700 background-color: #FFEA20; | |
2701 } | |
2702 | |
2703 /* All views: header item coverage table entry for ow coverage rate */ | |
2704 td.headerCovTableEntryLo | |
2705 { | |
2706 text-align: right; | |
2707 color: #000000; | |
2708 font-family: sans-serif; | |
2709 font-weight: bold; | |
2710 white-space: nowrap; | |
2711 padding-left: 12px; | |
2712 padding-right: 4px; | |
2713 background-color: #FF0000; | |
2714 } | |
2715 | |
2716 /* All views: header legend value for legend entry */ | |
2717 td.headerValueLeg | |
2718 { | |
2719 text-align: left; | |
2720 color: #000000; | |
2721 font-family: sans-serif; | |
2722 font-size: 80%; | |
2723 white-space: nowrap; | |
2724 padding-top: 4px; | |
2725 } | |
2726 | |
2727 /* All views: color of horizontal ruler */ | |
2728 td.ruler | |
2729 { | |
2730 background-color: #6688D4; | |
2731 } | |
2732 | |
2733 /* All views: version string format */ | |
2734 td.versionInfo | |
2735 { | |
2736 text-align: center; | |
2737 padding-top: 2px; | |
2738 font-family: sans-serif; | |
2739 font-style: italic; | |
2740 } | |
2741 | |
2742 /* Directory view/File view (all)/Test case descriptions: | |
2743 table headline format */ | |
2744 td.tableHead | |
2745 { | |
2746 text-align: center; | |
2747 color: #FFFFFF; | |
2748 background-color: #6688D4; | |
2749 font-family: sans-serif; | |
2750 font-size: 120%; | |
2751 font-weight: bold; | |
2752 white-space: nowrap; | |
2753 padding-left: 4px; | |
2754 padding-right: 4px; | |
2755 } | |
2756 | |
2757 span.tableHeadSort | |
2758 { | |
2759 padding-right: 4px; | |
2760 } | |
2761 | |
2762 /* Directory view/File view (all): filename entry format */ | |
2763 td.coverFile | |
2764 { | |
2765 text-align: left; | |
2766 padding-left: 10px; | |
2767 padding-right: 20px; | |
2768 color: #284FA8; | |
2769 background-color: #DAE7FE; | |
2770 font-family: monospace; | |
2771 } | |
2772 | |
2773 /* Directory view/File view (all): bar-graph entry format*/ | |
2774 td.coverBar | |
2775 { | |
2776 padding-left: 10px; | |
2777 padding-right: 10px; | |
2778 background-color: #DAE7FE; | |
2779 } | |
2780 | |
2781 /* Directory view/File view (all): bar-graph outline color */ | |
2782 td.coverBarOutline | |
2783 { | |
2784 background-color: #000000; | |
2785 } | |
2786 | |
2787 /* Directory view/File view (all): percentage entry for files with | |
2788 high coverage rate */ | |
2789 td.coverPerHi | |
2790 { | |
2791 text-align: right; | |
2792 padding-left: 10px; | |
2793 padding-right: 10px; | |
2794 background-color: #A7FC9D; | |
2795 font-weight: bold; | |
2796 font-family: sans-serif; | |
2797 } | |
2798 | |
2799 /* Directory view/File view (all): line count entry for files with | |
2800 high coverage rate */ | |
2801 td.coverNumHi | |
2802 { | |
2803 text-align: right; | |
2804 padding-left: 10px; | |
2805 padding-right: 10px; | |
2806 background-color: #A7FC9D; | |
2807 white-space: nowrap; | |
2808 font-family: sans-serif; | |
2809 } | |
2810 | |
2811 /* Directory view/File view (all): percentage entry for files with | |
2812 medium coverage rate */ | |
2813 td.coverPerMed | |
2814 { | |
2815 text-align: right; | |
2816 padding-left: 10px; | |
2817 padding-right: 10px; | |
2818 background-color: #FFEA20; | |
2819 font-weight: bold; | |
2820 font-family: sans-serif; | |
2821 } | |
2822 | |
2823 /* Directory view/File view (all): line count entry for files with | |
2824 medium coverage rate */ | |
2825 td.coverNumMed | |
2826 { | |
2827 text-align: right; | |
2828 padding-left: 10px; | |
2829 padding-right: 10px; | |
2830 background-color: #FFEA20; | |
2831 white-space: nowrap; | |
2832 font-family: sans-serif; | |
2833 } | |
2834 | |
2835 /* Directory view/File view (all): percentage entry for files with | |
2836 low coverage rate */ | |
2837 td.coverPerLo | |
2838 { | |
2839 text-align: right; | |
2840 padding-left: 10px; | |
2841 padding-right: 10px; | |
2842 background-color: #FF0000; | |
2843 font-weight: bold; | |
2844 font-family: sans-serif; | |
2845 } | |
2846 | |
2847 /* Directory view/File view (all): line count entry for files with | |
2848 low coverage rate */ | |
2849 td.coverNumLo | |
2850 { | |
2851 text-align: right; | |
2852 padding-left: 10px; | |
2853 padding-right: 10px; | |
2854 background-color: #FF0000; | |
2855 white-space: nowrap; | |
2856 font-family: sans-serif; | |
2857 } | |
2858 | |
2859 /* File view (all): "show/hide details" link format */ | |
2860 a.detail:link | |
2861 { | |
2862 color: #B8D0FF; | |
2863 font-size:80%; | |
2864 } | |
2865 | |
2866 /* File view (all): "show/hide details" link - visited format */ | |
2867 a.detail:visited | |
2868 { | |
2869 color: #B8D0FF; | |
2870 font-size:80%; | |
2871 } | |
2872 | |
2873 /* File view (all): "show/hide details" link - activated format */ | |
2874 a.detail:active | |
2875 { | |
2876 color: #FFFFFF; | |
2877 font-size:80%; | |
2878 } | |
2879 | |
2880 /* File view (detail): test name entry */ | |
2881 td.testName | |
2882 { | |
2883 text-align: right; | |
2884 padding-right: 10px; | |
2885 background-color: #DAE7FE; | |
2886 font-family: sans-serif; | |
2887 } | |
2888 | |
2889 /* File view (detail): test percentage entry */ | |
2890 td.testPer | |
2891 { | |
2892 text-align: right; | |
2893 padding-left: 10px; | |
2894 padding-right: 10px; | |
2895 background-color: #DAE7FE; | |
2896 font-family: sans-serif; | |
2897 } | |
2898 | |
2899 /* File view (detail): test lines count entry */ | |
2900 td.testNum | |
2901 { | |
2902 text-align: right; | |
2903 padding-left: 10px; | |
2904 padding-right: 10px; | |
2905 background-color: #DAE7FE; | |
2906 font-family: sans-serif; | |
2907 } | |
2908 | |
2909 /* Test case descriptions: test name format*/ | |
2910 dt | |
2911 { | |
2912 font-family: sans-serif; | |
2913 font-weight: bold; | |
2914 } | |
2915 | |
2916 /* Test case descriptions: description table body */ | |
2917 td.testDescription | |
2918 { | |
2919 padding-top: 10px; | |
2920 padding-left: 30px; | |
2921 padding-bottom: 10px; | |
2922 padding-right: 30px; | |
2923 background-color: #DAE7FE; | |
2924 } | |
2925 | |
2926 /* Source code view: function entry */ | |
2927 td.coverFn | |
2928 { | |
2929 text-align: left; | |
2930 padding-left: 10px; | |
2931 padding-right: 20px; | |
2932 color: #284FA8; | |
2933 background-color: #DAE7FE; | |
2934 font-family: monospace; | |
2935 } | |
2936 | |
2937 /* Source code view: function entry zero count*/ | |
2938 td.coverFnLo | |
2939 { | |
2940 text-align: right; | |
2941 padding-left: 10px; | |
2942 padding-right: 10px; | |
2943 background-color: #FF0000; | |
2944 font-weight: bold; | |
2945 font-family: sans-serif; | |
2946 } | |
2947 | |
2948 /* Source code view: function entry nonzero count*/ | |
2949 td.coverFnHi | |
2950 { | |
2951 text-align: right; | |
2952 padding-left: 10px; | |
2953 padding-right: 10px; | |
2954 background-color: #DAE7FE; | |
2955 font-weight: bold; | |
2956 font-family: sans-serif; | |
2957 } | |
2958 | |
2959 /* Source code view: source code format */ | |
2960 pre.source | |
2961 { | |
2962 font-family: monospace; | |
2963 white-space: pre; | |
2964 margin-top: 2px; | |
2965 } | |
2966 | |
2967 /* Source code view: line number format */ | |
2968 span.lineNum | |
2969 { | |
2970 background-color: #EFE383; | |
2971 } | |
2972 | |
2973 /* Source code view: format for lines which were executed */ | |
2974 td.lineCov, | |
2975 span.lineCov | |
2976 { | |
2977 background-color: #CAD7FE; | |
2978 } | |
2979 | |
2980 /* Source code view: format for Cov legend */ | |
2981 span.coverLegendCov | |
2982 { | |
2983 padding-left: 10px; | |
2984 padding-right: 10px; | |
2985 padding-bottom: 2px; | |
2986 background-color: #CAD7FE; | |
2987 } | |
2988 | |
2989 /* Source code view: format for lines which were not executed */ | |
2990 td.lineNoCov, | |
2991 span.lineNoCov | |
2992 { | |
2993 background-color: #FF6230; | |
2994 } | |
2995 | |
2996 /* Source code view: format for NoCov legend */ | |
2997 span.coverLegendNoCov | |
2998 { | |
2999 padding-left: 10px; | |
3000 padding-right: 10px; | |
3001 padding-bottom: 2px; | |
3002 background-color: #FF6230; | |
3003 } | |
3004 | |
3005 /* Source code view (function table): standard link - visited format */ | |
3006 td.lineNoCov > a:visited, | |
3007 td.lineCov > a:visited | |
3008 { | |
3009 color: black; | |
3010 text-decoration: underline; | |
3011 } | |
3012 | |
3013 /* Source code view: format for lines which were executed only in a | |
3014 previous version */ | |
3015 span.lineDiffCov | |
3016 { | |
3017 background-color: #B5F7AF; | |
3018 } | |
3019 | |
3020 /* Source code view: format for branches which were executed | |
3021 * and taken */ | |
3022 span.branchCov | |
3023 { | |
3024 background-color: #CAD7FE; | |
3025 } | |
3026 | |
3027 /* Source code view: format for branches which were executed | |
3028 * but not taken */ | |
3029 span.branchNoCov | |
3030 { | |
3031 background-color: #FF6230; | |
3032 } | |
3033 | |
3034 /* Source code view: format for branches which were not executed */ | |
3035 span.branchNoExec | |
3036 { | |
3037 background-color: #FF6230; | |
3038 } | |
3039 | |
3040 /* Source code view: format for the source code heading line */ | |
3041 pre.sourceHeading | |
3042 { | |
3043 white-space: pre; | |
3044 font-family: monospace; | |
3045 font-weight: bold; | |
3046 margin: 0px; | |
3047 } | |
3048 | |
3049 /* All views: header legend value for low rate */ | |
3050 td.headerValueLegL | |
3051 { | |
3052 font-family: sans-serif; | |
3053 text-align: center; | |
3054 white-space: nowrap; | |
3055 padding-left: 4px; | |
3056 padding-right: 2px; | |
3057 background-color: #FF0000; | |
3058 font-size: 80%; | |
3059 } | |
3060 | |
3061 /* All views: header legend value for med rate */ | |
3062 td.headerValueLegM | |
3063 { | |
3064 font-family: sans-serif; | |
3065 text-align: center; | |
3066 white-space: nowrap; | |
3067 padding-left: 2px; | |
3068 padding-right: 2px; | |
3069 background-color: #FFEA20; | |
3070 font-size: 80%; | |
3071 } | |
3072 | |
3073 /* All views: header legend value for hi rate */ | |
3074 td.headerValueLegH | |
3075 { | |
3076 font-family: sans-serif; | |
3077 text-align: center; | |
3078 white-space: nowrap; | |
3079 padding-left: 2px; | |
3080 padding-right: 4px; | |
3081 background-color: #A7FC9D; | |
3082 font-size: 80%; | |
3083 } | |
3084 | |
3085 /* All views except source code view: legend format for low coverage */ | |
3086 span.coverLegendCovLo | |
3087 { | |
3088 padding-left: 10px; | |
3089 padding-right: 10px; | |
3090 padding-top: 2px; | |
3091 background-color: #FF0000; | |
3092 } | |
3093 | |
3094 /* All views except source code view: legend format for med coverage */ | |
3095 span.coverLegendCovMed | |
3096 { | |
3097 padding-left: 10px; | |
3098 padding-right: 10px; | |
3099 padding-top: 2px; | |
3100 background-color: #FFEA20; | |
3101 } | |
3102 | |
3103 /* All views except source code view: legend format for hi coverage */ | |
3104 span.coverLegendCovHi | |
3105 { | |
3106 padding-left: 10px; | |
3107 padding-right: 10px; | |
3108 padding-top: 2px; | |
3109 background-color: #A7FC9D; | |
3110 } | |
3111 END_OF_CSS | |
3112 ; | |
3113 | |
3114 # ************************************************************* | |
3115 | |
3116 | |
3117 # Remove leading tab from all lines | |
3118 $css_data =~ s/^\t//gm; | |
3119 | |
3120 print(CSS_HANDLE $css_data); | |
3121 | |
3122 close(CSS_HANDLE); | |
3123 } | |
3124 | |
3125 | |
3126 # | |
3127 # get_bar_graph_code(base_dir, cover_found, cover_hit) | |
3128 # | |
3129 # Return a string containing HTML code which implements a bar graph display | |
3130 # for a coverage rate of cover_hit * 100 / cover_found. | |
3131 # | |
3132 | |
3133 sub get_bar_graph_code($$$) | |
3134 { | |
3135 my $rate; | |
3136 my $alt; | |
3137 my $width; | |
3138 my $remainder; | |
3139 my $png_name; | |
3140 my $graph_code; | |
3141 | |
3142 # Check number of instrumented lines | |
3143 if ($_[1] == 0) { return ""; } | |
3144 | |
3145 $rate = $_[2] * 100 / $_[1]; | |
3146 $alt = sprintf("%.1f", $rate)."%"; | |
3147 $width = sprintf("%.0f", $rate); | |
3148 $remainder = sprintf("%d", 100-$width); | |
3149 | |
3150 # Decide which .png file to use | |
3151 $png_name = $rate_png[classify_rate($_[1], $_[2], $med_limit, | |
3152 $hi_limit)]; | |
3153 | |
3154 if ($width == 0) | |
3155 { | |
3156 # Zero coverage | |
3157 $graph_code = (<<END_OF_HTML) | |
3158 <table border=0 cellspacing=0 cellpadding=1><tr><td class="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 %%", $hit * 100 / $found); | |
3454 $class = $rate_name[classify_rate($found, $hit, | |
3455 $med, $hi)]; | |
3456 } | |
3457 write_html($handle, <<END_OF_HTML); | |
3458 <td class="coverPer$class">$rate</td> | |
3459 <td class="coverNum$class">$hit / $found</td> | |
3460 END_OF_HTML | |
3461 } | |
3462 # End of row | |
3463 write_html($handle, <<END_OF_HTML); | |
3464 </tr> | |
3465 END_OF_HTML | |
3466 } | |
3467 | |
3468 | |
3469 # | |
3470 # write_file_table_detail_entry(filehandle, test_name, ([found, hit], ...)) | |
3471 # | |
3472 # Write entry for detail section in file table. | |
3473 # | |
3474 | |
3475 sub write_file_table_detail_entry(*$@) | |
3476 { | |
3477 my ($handle, $test, @entries) = @_; | |
3478 my $entry; | |
3479 | |
3480 if ($test eq "") { | |
3481 $test = "<span style=\"font-style:italic\"><unnamed></span
>"; | |
3482 } elsif ($test =~ /^(.*),diff$/) { | |
3483 $test = $1." (converted)"; | |
3484 } | |
3485 # Testname | |
3486 write_html($handle, <<END_OF_HTML); | |
3487 <tr> | |
3488 <td class="testName" colspan=2>$test</td> | |
3489 END_OF_HTML | |
3490 # Test data | |
3491 foreach $entry (@entries) { | |
3492 my ($found, $hit) = @{$entry}; | |
3493 my $rate = "-"; | |
3494 | |
3495 if ($found > 0) { | |
3496 $rate = sprintf("%.1f %%", $hit * 100 / $found); | |
3497 } | |
3498 write_html($handle, <<END_OF_HTML); | |
3499 <td class="testPer">$rate</td> | |
3500 <td class="testNum">$hit / $found</td> | |
3501 END_OF_HTML | |
3502 } | |
3503 | |
3504 write_html($handle, <<END_OF_HTML); | |
3505 </tr> | |
3506 | |
3507 END_OF_HTML | |
3508 | |
3509 # ************************************************************* | |
3510 } | |
3511 | |
3512 | |
3513 # | |
3514 # write_file_table_epilog(filehandle) | |
3515 # | |
3516 # Write end of file table HTML code. | |
3517 # | |
3518 | |
3519 sub write_file_table_epilog(*) | |
3520 { | |
3521 # ************************************************************* | |
3522 | |
3523 write_html($_[0], <<END_OF_HTML) | |
3524 </table> | |
3525 </center> | |
3526 <br> | |
3527 | |
3528 END_OF_HTML | |
3529 ; | |
3530 | |
3531 # ************************************************************* | |
3532 } | |
3533 | |
3534 | |
3535 # | |
3536 # write_test_table_prolog(filehandle, table_heading) | |
3537 # | |
3538 # Write heading for test case description table. | |
3539 # | |
3540 | |
3541 sub write_test_table_prolog(*$) | |
3542 { | |
3543 # ************************************************************* | |
3544 | |
3545 write_html($_[0], <<END_OF_HTML) | |
3546 <center> | |
3547 <table width="80%" cellpadding=2 cellspacing=1 border=0> | |
3548 | |
3549 <tr> | |
3550 <td><br></td> | |
3551 </tr> | |
3552 | |
3553 <tr> | |
3554 <td class="tableHead">$_[1]</td> | |
3555 </tr> | |
3556 | |
3557 <tr> | |
3558 <td class="testDescription"> | |
3559 <dl> | |
3560 END_OF_HTML | |
3561 ; | |
3562 | |
3563 # ************************************************************* | |
3564 } | |
3565 | |
3566 | |
3567 # | |
3568 # write_test_table_entry(filehandle, test_name, test_description) | |
3569 # | |
3570 # Write entry for the test table. | |
3571 # | |
3572 | |
3573 sub write_test_table_entry(*$$) | |
3574 { | |
3575 # ************************************************************* | |
3576 | |
3577 write_html($_[0], <<END_OF_HTML) | |
3578 <dt>$_[1]<a name="$_[1]"> </a></dt> | |
3579 <dd>$_[2]<br><br></dd> | |
3580 END_OF_HTML | |
3581 ; | |
3582 | |
3583 # ************************************************************* | |
3584 } | |
3585 | |
3586 | |
3587 # | |
3588 # write_test_table_epilog(filehandle) | |
3589 # | |
3590 # Write end of test description table HTML code. | |
3591 # | |
3592 | |
3593 sub write_test_table_epilog(*) | |
3594 { | |
3595 # ************************************************************* | |
3596 | |
3597 write_html($_[0], <<END_OF_HTML) | |
3598 </dl> | |
3599 </td> | |
3600 </tr> | |
3601 </table> | |
3602 </center> | |
3603 <br> | |
3604 | |
3605 END_OF_HTML | |
3606 ; | |
3607 | |
3608 # ************************************************************* | |
3609 } | |
3610 | |
3611 | |
3612 sub fmt_centered($$) | |
3613 { | |
3614 my ($width, $text) = @_; | |
3615 my $w0 = length($text); | |
3616 my $w1 = int(($width - $w0) / 2); | |
3617 my $w2 = $width - $w0 - $w1; | |
3618 | |
3619 return (" "x$w1).$text.(" "x$w2); | |
3620 } | |
3621 | |
3622 | |
3623 # | |
3624 # write_source_prolog(filehandle) | |
3625 # | |
3626 # Write start of source code table. | |
3627 # | |
3628 | |
3629 sub write_source_prolog(*) | |
3630 { | |
3631 my $lineno_heading = " "; | |
3632 my $branch_heading = ""; | |
3633 my $line_heading = fmt_centered($line_field_width, "Line data"); | |
3634 my $source_heading = " Source code"; | |
3635 | |
3636 if ($br_coverage) { | |
3637 $branch_heading = fmt_centered($br_field_width, "Branch data"). | |
3638 " "; | |
3639 } | |
3640 # ************************************************************* | |
3641 | |
3642 write_html($_[0], <<END_OF_HTML) | |
3643 <table cellpadding=0 cellspacing=0 border=0> | |
3644 <tr> | |
3645 <td><br></td> | |
3646 </tr> | |
3647 <tr> | |
3648 <td> | |
3649 <pre class="sourceHeading">${lineno_heading}${branch_heading}${line_heading} ${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: < $med_limit %</span> | |
4289 <span class="coverLegendCovMed" title="Coverage rates between $med_l
imit % and $hi_limit % are classified as medium">medium: >= $med_limit %</spa
n> | |
4290 <span class="coverLegendCovHi" title="Coverage rates of $hi_limit %
and more are classified as high">high: >= $hi_limit %</span> | |
4291 END_OF_HTML | |
4292 push(@row_left, [[undef, "headerItem", "Legend:"], | |
4293 [undef, "headerValueLeg", $text]]); | |
4294 } | |
4295 if ($type == $HDR_TESTDESC) { | |
4296 push(@row_right, [[ "55%" ]]); | |
4297 } else { | |
4298 push(@row_right, [["15%", undef, undef ], | |
4299 ["10%", "headerCovTableHead", "Hit" ], | |
4300 ["10%", "headerCovTableHead", "Total" ], | |
4301 ["15%", "headerCovTableHead", "Coverage"]]); | |
4302 } | |
4303 # Line coverage | |
4304 $style = $rate_name[classify_rate($lines_found, $lines_hit, | |
4305 $med_limit, $hi_limit)]; | |
4306 $rate = format_rate($lines_found, $lines_hit); | |
4307 push(@row_right, [[undef, "headerItem", "Lines:"], | |
4308 [undef, "headerCovTableEntry", $lines_hit], | |
4309 [undef, "headerCovTableEntry", $lines_found], | |
4310 [undef, "headerCovTableEntry$style", $rate]]) | |
4311 if ($type != $HDR_TESTDESC); | |
4312 # Function coverage | |
4313 if ($func_coverage) { | |
4314 $style = $rate_name[classify_rate($fn_found, $fn_hit, | |
4315 $fn_med_limit, $fn_hi_limit)]; | |
4316 $rate = format_rate($fn_found, $fn_hit); | |
4317 push(@row_right, [[undef, "headerItem", "Functions:"], | |
4318 [undef, "headerCovTableEntry", $fn_hit], | |
4319 [undef, "headerCovTableEntry", $fn_found], | |
4320 [undef, "headerCovTableEntry$style", $rate]]) | |
4321 if ($type != $HDR_TESTDESC); | |
4322 } | |
4323 # Branch coverage | |
4324 if ($br_coverage) { | |
4325 $style = $rate_name[classify_rate($br_found, $br_hit, | |
4326 $br_med_limit, $br_hi_limit)]; | |
4327 $rate = format_rate($br_found, $br_hit); | |
4328 push(@row_right, [[undef, "headerItem", "Branches:"], | |
4329 [undef, "headerCovTableEntry", $br_hit], | |
4330 [undef, "headerCovTableEntry", $br_found], | |
4331 [undef, "headerCovTableEntry$style", $rate]]) | |
4332 if ($type != $HDR_TESTDESC); | |
4333 } | |
4334 | |
4335 # Print rows | |
4336 $num_rows = max(scalar(@row_left), scalar(@row_right)); | |
4337 for ($i = 0; $i < $num_rows; $i++) { | |
4338 my $left = $row_left[$i]; | |
4339 my $right = $row_right[$i]; | |
4340 | |
4341 if (!defined($left)) { | |
4342 $left = [[undef, undef, undef], [undef, undef, undef]]; | |
4343 } | |
4344 if (!defined($right)) { | |
4345 $right = []; | |
4346 } | |
4347 write_header_line(*HTML_HANDLE, @{$left}, | |
4348 [ $i == 0 ? "5%" : undef, undef, undef], | |
4349 @{$right}); | |
4350 } | |
4351 | |
4352 # Fourth line | |
4353 write_header_epilog(*HTML_HANDLE, $base_dir); | |
4354 } | |
4355 | |
4356 | |
4357 # | |
4358 # get_sorted_keys(hash_ref, sort_type) | |
4359 # | |
4360 | |
4361 sub get_sorted_keys($$) | |
4362 { | |
4363 my ($hash, $type) = @_; | |
4364 | |
4365 if ($type == $SORT_FILE) { | |
4366 # Sort by name | |
4367 return sort(keys(%{$hash})); | |
4368 } elsif ($type == $SORT_LINE) { | |
4369 # Sort by line coverage | |
4370 return sort({$hash->{$a}[7] <=> $hash->{$b}[7]} keys(%{$hash})); | |
4371 } elsif ($type == $SORT_FUNC) { | |
4372 # Sort by function coverage; | |
4373 return sort({$hash->{$a}[8] <=> $hash->{$b}[8]} keys(%{$hash})); | |
4374 } elsif ($type == $SORT_BRANCH) { | |
4375 # Sort by br coverage; | |
4376 return sort({$hash->{$a}[9] <=> $hash->{$b}[9]} keys(%{$hash})); | |
4377 } | |
4378 } | |
4379 | |
4380 sub get_sort_code($$$) | |
4381 { | |
4382 my ($link, $alt, $base) = @_; | |
4383 my $png; | |
4384 my $link_start; | |
4385 my $link_end; | |
4386 | |
4387 if (!defined($link)) { | |
4388 $png = "glass.png"; | |
4389 $link_start = ""; | |
4390 $link_end = ""; | |
4391 } else { | |
4392 $png = "updown.png"; | |
4393 $link_start = '<a href="'.$link.'">'; | |
4394 $link_end = "</a>"; | |
4395 } | |
4396 | |
4397 return ' <span class="tableHeadSort">'.$link_start. | |
4398 '<img src="'.$base.$png.'" width=10 height=14 '. | |
4399 'alt="'.$alt.'" title="'.$alt.'" border=0>'.$link_end.'</span>'; | |
4400 } | |
4401 | |
4402 sub get_file_code($$$$) | |
4403 { | |
4404 my ($type, $text, $sort_button, $base) = @_; | |
4405 my $result = $text; | |
4406 my $link; | |
4407 | |
4408 if ($sort_button) { | |
4409 if ($type == $HEAD_NO_DETAIL) { | |
4410 $link = "index.$html_ext"; | |
4411 } else { | |
4412 $link = "index-detail.$html_ext"; | |
4413 } | |
4414 } | |
4415 $result .= get_sort_code($link, "Sort by name", $base); | |
4416 | |
4417 return $result; | |
4418 } | |
4419 | |
4420 sub get_line_code($$$$$) | |
4421 { | |
4422 my ($type, $sort_type, $text, $sort_button, $base) = @_; | |
4423 my $result = $text; | |
4424 my $sort_link; | |
4425 | |
4426 if ($type == $HEAD_NO_DETAIL) { | |
4427 # Just text | |
4428 if ($sort_button) { | |
4429 $sort_link = "index-sort-l.$html_ext"; | |
4430 } | |
4431 } elsif ($type == $HEAD_DETAIL_HIDDEN) { | |
4432 # Text + link to detail view | |
4433 $result .= ' ( <a class="detail" href="index-detail'. | |
4434 $fileview_sortname[$sort_type].'.'.$html_ext. | |
4435 '">show details</a> )'; | |
4436 if ($sort_button) { | |
4437 $sort_link = "index-sort-l.$html_ext"; | |
4438 } | |
4439 } else { | |
4440 # Text + link to standard view | |
4441 $result .= ' ( <a class="detail" href="index'. | |
4442 $fileview_sortname[$sort_type].'.'.$html_ext. | |
4443 '">hide details</a> )'; | |
4444 if ($sort_button) { | |
4445 $sort_link = "index-detail-sort-l.$html_ext"; | |
4446 } | |
4447 } | |
4448 # Add sort button | |
4449 $result .= get_sort_code($sort_link, "Sort by line coverage", $base); | |
4450 | |
4451 return $result; | |
4452 } | |
4453 | |
4454 sub get_func_code($$$$) | |
4455 { | |
4456 my ($type, $text, $sort_button, $base) = @_; | |
4457 my $result = $text; | |
4458 my $link; | |
4459 | |
4460 if ($sort_button) { | |
4461 if ($type == $HEAD_NO_DETAIL) { | |
4462 $link = "index-sort-f.$html_ext"; | |
4463 } else { | |
4464 $link = "index-detail-sort-f.$html_ext"; | |
4465 } | |
4466 } | |
4467 $result .= get_sort_code($link, "Sort by function coverage", $base); | |
4468 return $result; | |
4469 } | |
4470 | |
4471 sub get_br_code($$$$) | |
4472 { | |
4473 my ($type, $text, $sort_button, $base) = @_; | |
4474 my $result = $text; | |
4475 my $link; | |
4476 | |
4477 if ($sort_button) { | |
4478 if ($type == $HEAD_NO_DETAIL) { | |
4479 $link = "index-sort-b.$html_ext"; | |
4480 } else { | |
4481 $link = "index-detail-sort-b.$html_ext"; | |
4482 } | |
4483 } | |
4484 $result .= get_sort_code($link, "Sort by branch coverage", $base); | |
4485 return $result; | |
4486 } | |
4487 | |
4488 # | |
4489 # write_file_table(filehandle, base_dir, overview, testhash, testfnchash, | |
4490 # testbrhash, fileview, sort_type) | |
4491 # | |
4492 # Write a complete file table. OVERVIEW is a reference to a hash containing | |
4493 # the following mapping: | |
4494 # | |
4495 # filename -> "lines_found,lines_hit,funcs_found,funcs_hit,page_link, | |
4496 # func_link" | |
4497 # | |
4498 # TESTHASH is a reference to the following hash: | |
4499 # | |
4500 # filename -> \%testdata | |
4501 # %testdata: name of test affecting this file -> \%testcount | |
4502 # %testcount: line number -> execution count for a single test | |
4503 # | |
4504 # Heading of first column is "Filename" if FILEVIEW is true, "Directory name" | |
4505 # otherwise. | |
4506 # | |
4507 | |
4508 sub write_file_table(*$$$$$$$) | |
4509 { | |
4510 local *HTML_HANDLE = $_[0]; | |
4511 my $base_dir = $_[1]; | |
4512 my $overview = $_[2]; | |
4513 my $testhash = $_[3]; | |
4514 my $testfnchash = $_[4]; | |
4515 my $testbrhash = $_[5]; | |
4516 my $fileview = $_[6]; | |
4517 my $sort_type = $_[7]; | |
4518 my $filename; | |
4519 my $bar_graph; | |
4520 my $hit; | |
4521 my $found; | |
4522 my $fn_found; | |
4523 my $fn_hit; | |
4524 my $br_found; | |
4525 my $br_hit; | |
4526 my $page_link; | |
4527 my $testname; | |
4528 my $testdata; | |
4529 my $testfncdata; | |
4530 my $testbrdata; | |
4531 my %affecting_tests; | |
4532 my $line_code = ""; | |
4533 my $func_code; | |
4534 my $br_code; | |
4535 my $file_code; | |
4536 my @head_columns; | |
4537 | |
4538 # Determine HTML code for column headings | |
4539 if (($base_dir ne "") && $show_details) | |
4540 { | |
4541 my $detailed = keys(%{$testhash}); | |
4542 | |
4543 $file_code = get_file_code($detailed ? $HEAD_DETAIL_HIDDEN : | |
4544 $HEAD_NO_DETAIL, | |
4545 $fileview ? "Filename" : "Directory", | |
4546 $sort && $sort_type != $SORT_FILE, | |
4547 $base_dir); | |
4548 $line_code = get_line_code($detailed ? $HEAD_DETAIL_SHOWN : | |
4549 $HEAD_DETAIL_HIDDEN, | |
4550 $sort_type, | |
4551 "Line Coverage", | |
4552 $sort && $sort_type != $SORT_LINE, | |
4553 $base_dir); | |
4554 $func_code = get_func_code($detailed ? $HEAD_DETAIL_HIDDEN : | |
4555 $HEAD_NO_DETAIL, | |
4556 "Functions", | |
4557 $sort && $sort_type != $SORT_FUNC, | |
4558 $base_dir); | |
4559 $br_code = get_br_code($detailed ? $HEAD_DETAIL_HIDDEN : | |
4560 $HEAD_NO_DETAIL, | |
4561 "Branches", | |
4562 $sort && $sort_type != $SORT_BRANCH, | |
4563 $base_dir); | |
4564 } else { | |
4565 $file_code = get_file_code($HEAD_NO_DETAIL, | |
4566 $fileview ? "Filename" : "Directory", | |
4567 $sort && $sort_type != $SORT_FILE, | |
4568 $base_dir); | |
4569 $line_code = get_line_code($HEAD_NO_DETAIL, $sort_type, "Line 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 } | |
OLD | NEW |