[dali_2.1.23] Merge branch 'devel/master'
[platform/core/uifw/dali-toolkit.git] / automated-tests / patch-coverage.pl
1 #!/usr/bin/perl
2 #
3 # Copyright (c) 2020 Samsung Electronics Co., Ltd.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 #
17
18 use strict;
19 use Git;
20 use Getopt::Long;
21 use Error qw(:try);
22 use Pod::Usage;
23 use File::Basename;
24 use File::stat;
25 use Scalar::Util qw /looks_like_number/;
26 use Cwd qw /getcwd/;
27 use Term::ANSIColor qw(:constants);
28 use Data::Dumper;
29
30 # Dali specific program to run lcov and parse output for files in patch
31
32 # A) Generate lcov output from lib & test cases
33 # B) Get patch using git diff
34 # C) Generate patch output with covered/uncovered lines marked in green/red
35 # D) Generate coverage data for changed lines
36 # E) Exit status should be 0 for high coverage (90% line coverage for all new/changed lines)
37 #    or 1 for low coverage
38
39 # Sources for conversion of gcno/gcda files:
40 # /usr/bin/lcov & genhtml
41 # Python git-coverage (From http://stef.thewalter.net/git-coverage-useful-code-coverage.html)
42
43 # From genhtml:
44 sub read_info_file($);
45 sub get_info_entry($);
46 sub set_info_entry($$$$$$$$$;$$$$$$);
47 sub combine_info_entries($$$);
48 sub combine_info_files($$);
49 sub compress_brcount($);
50 sub brcount_to_db($);
51 sub db_to_brcount($;$);
52 sub brcount_db_combine($$$);
53 sub add_counts($$);
54 sub info(@);
55
56 our $repo = Git->repository();
57 our $debug=0;
58 our $pd_debug=0;
59 our $root;
60 our %info_data; # Hash containing all data from .info files
61
62 # Settings from genhtml:
63 our $func_coverage;     # If set, generate function coverage statistics
64 our $no_func_coverage;  # Disable func_coverage
65 our $br_coverage;       # If set, generate branch coverage statistics
66 our $no_br_coverage;    # Disable br_coverage
67 our $sort = 1;          # If set, provide directory listings with sorted entries
68 our $no_sort;           # Disable sort
69
70 # Branch data combination types
71 our $BR_SUB = 0;
72 our $BR_ADD = 1;
73
74 # Command line options
75 our $opt_cached;
76 our $opt_help;
77 our $opt_output;
78 our $opt_quiet;
79 our $opt_verbose;
80
81 my %options = (
82     "cached"       => { "optvar"=>\$opt_cached, "desc"=>"Use index" },
83     "output:s"     => { "optvar"=>\$opt_output, "desc"=>"Generate html output"},
84     "help"         => { "optvar"=>\$opt_help, "desc"=>""},
85     "quiet"        => { "optvar"=>\$opt_quiet, "desc"=>""},
86     "verbose"      => { "optvar"=>\$opt_verbose, "desc"=>"Also output coverage" });
87
88 my %longOptions = map { $_ => $options{$_}->{"optvar"} } keys(%options);
89 GetOptions( %longOptions ) or pod2usage(2);
90 pod2usage(1) if $opt_help;
91
92
93 # From genhtml:
94 #
95 # read_info_file(info_filename)
96 #
97 # Read in the contents of the .info file specified by INFO_FILENAME. Data will
98 # be returned as a reference to a hash containing the following mappings:
99 #
100 # %result: for each filename found in file -> \%data
101 #
102 # %data: "test"    -> \%testdata
103 #        "sum"     -> \%sumcount
104 #        "func"    -> \%funcdata
105 #        "found"   -> $lines_found (number of instrumented lines found in file)
106 #        "hit"     -> $lines_hit (number of executed lines in file)
107 #        "f_found" -> $fn_found (number of instrumented functions found in file)
108 #        "f_hit"   -> $fn_hit (number of executed functions in file)
109 #        "b_found" -> $br_found (number of instrumented branches found in file)
110 #        "b_hit"   -> $br_hit (number of executed branches in file)
111 #        "check"   -> \%checkdata
112 #        "testfnc" -> \%testfncdata
113 #        "sumfnc"  -> \%sumfnccount
114 #        "testbr"  -> \%testbrdata
115 #        "sumbr"   -> \%sumbrcount
116 #
117 # %testdata   : name of test affecting this file -> \%testcount
118 # %testfncdata: name of test affecting this file -> \%testfnccount
119 # %testbrdata:  name of test affecting this file -> \%testbrcount
120 #
121 # %testcount   : line number   -> execution count for a single test
122 # %testfnccount: function name -> execution count for a single test
123 # %testbrcount : line number   -> branch coverage data for a single test
124 # %sumcount    : line number   -> execution count for all tests
125 # %sumfnccount : function name -> execution count for all tests
126 # %sumbrcount  : line number   -> branch coverage data for all tests
127 # %funcdata    : function name -> line number
128 # %checkdata   : line number   -> checksum of source code line
129 # $brdata      : vector of items: block, branch, taken
130 #
131 # Note that .info file sections referring to the same file and test name
132 # will automatically be combined by adding all execution counts.
133 #
134 # Note that if INFO_FILENAME ends with ".gz", it is assumed that the file
135 # is compressed using GZIP. If available, GUNZIP will be used to decompress
136 # this file.
137 #
138 # Die on error.
139 #
140 sub read_info_file($)
141 {
142     my $tracefile = $_[0];      # Name of tracefile
143     my %result;             # Resulting hash: file -> data
144     my $data;           # Data handle for current entry
145     my $testdata;           #       "             "
146     my $testcount;          #       "             "
147     my $sumcount;           #       "             "
148     my $funcdata;           #       "             "
149     my $checkdata;          #       "             "
150     my $testfncdata;
151     my $testfnccount;
152     my $sumfnccount;
153     my $testbrdata;
154     my $testbrcount;
155     my $sumbrcount;
156     my $line;           # Current line read from .info file
157     my $testname;           # Current test name
158     my $filename;           # Current filename
159     my $hitcount;           # Count for lines hit
160     my $count;          # Execution count of current line
161     my $negative;           # If set, warn about negative counts
162     my $changed_testname;       # If set, warn about changed testname
163     my $line_checksum;      # Checksum of current line
164     my $notified_about_relative_paths;
165     local *INFO_HANDLE;         # Filehandle for .info file
166
167     info("Reading data file $tracefile\n");
168
169     # Check if file exists and is readable
170     stat($tracefile);
171     if (!(-r _))
172     {
173         die("ERROR: cannot read file $tracefile!\n");
174     }
175
176     # Check if this is really a plain file
177     if (!(-f _))
178     {
179         die("ERROR: not a plain file: $tracefile!\n");
180     }
181
182     # Check for .gz extension
183     if ($tracefile =~ /\.gz$/)
184     {
185         # Check for availability of GZIP tool
186         system_no_output(1, "gunzip" ,"-h")
187             and die("ERROR: gunzip command not available!\n");
188
189         # Check integrity of compressed file
190         system_no_output(1, "gunzip", "-t", $tracefile)
191             and die("ERROR: integrity check failed for ".
192                     "compressed file $tracefile!\n");
193
194         # Open compressed file
195         open(INFO_HANDLE, "-|", "gunzip -c '$tracefile'")
196             or die("ERROR: cannot start gunzip to decompress ".
197                    "file $tracefile!\n");
198     }
199     else
200     {
201         # Open decompressed file
202         open(INFO_HANDLE, "<", $tracefile)
203             or die("ERROR: cannot read file $tracefile!\n");
204     }
205
206     $testname = "";
207     while (<INFO_HANDLE>)
208     {
209         chomp($_);
210         $line = $_;
211
212         # Switch statement
213         foreach ($line)
214         {
215             /^TN:([^,]*)(,diff)?/ && do
216             {
217                 # Test name information found
218                 $testname = defined($1) ? $1 : "";
219                 if ($testname =~ s/\W/_/g)
220                 {
221                     $changed_testname = 1;
222                 }
223                 $testname .= $2 if (defined($2));
224                 last;
225             };
226
227             /^[SK]F:(.*)/ && do
228             {
229                 # Filename information found
230                 # Retrieve data for new entry
231                 $filename = File::Spec->rel2abs($1, $root);
232
233                 if (!File::Spec->file_name_is_absolute($1) &&
234                     !$notified_about_relative_paths)
235                 {
236                     info("Resolved relative source file ".
237                          "path \"$1\" with CWD to ".
238                          "\"$filename\".\n");
239                     $notified_about_relative_paths = 1;
240                 }
241
242                 $data = $result{$filename};
243                 ($testdata, $sumcount, $funcdata, $checkdata,
244                  $testfncdata, $sumfnccount, $testbrdata,
245                  $sumbrcount) =
246                     get_info_entry($data);
247
248                 if (defined($testname))
249                 {
250                     $testcount = $testdata->{$testname};
251                     $testfnccount = $testfncdata->{$testname};
252                     $testbrcount = $testbrdata->{$testname};
253                 }
254                 else
255                 {
256                     $testcount = {};
257                     $testfnccount = {};
258                     $testbrcount = {};
259                 }
260                 last;
261             };
262
263             /^DA:(\d+),(-?\d+)(,[^,\s]+)?/ && do
264             {
265                 # Fix negative counts
266                 $count = $2 < 0 ? 0 : $2;
267                 if ($2 < 0)
268                 {
269                     $negative = 1;
270                 }
271                 # Execution count found, add to structure
272                 # Add summary counts
273                 $sumcount->{$1} += $count;
274
275                 # Add test-specific counts
276                 if (defined($testname))
277                 {
278                     $testcount->{$1} += $count;
279                 }
280
281                 # Store line checksum if available
282                 if (defined($3))
283                 {
284                     $line_checksum = substr($3, 1);
285
286                     # Does it match a previous definition
287                     if (defined($checkdata->{$1}) &&
288                         ($checkdata->{$1} ne
289                          $line_checksum))
290                     {
291                         die("ERROR: checksum mismatch ".
292                             "at $filename:$1\n");
293                     }
294
295                     $checkdata->{$1} = $line_checksum;
296                 }
297                 last;
298             };
299
300             /^FN:(\d+),([^,]+)/ && do
301             {
302                 last if (!$func_coverage);
303
304                 # Function data found, add to structure
305                 $funcdata->{$2} = $1;
306
307                 # Also initialize function call data
308                 if (!defined($sumfnccount->{$2})) {
309                     $sumfnccount->{$2} = 0;
310                 }
311                 if (defined($testname))
312                 {
313                     if (!defined($testfnccount->{$2})) {
314                         $testfnccount->{$2} = 0;
315                     }
316                 }
317                 last;
318             };
319
320             /^FNDA:(\d+),([^,]+)/ && do
321             {
322                 last if (!$func_coverage);
323                 # Function call count found, add to structure
324                 # Add summary counts
325                 $sumfnccount->{$2} += $1;
326
327                 # Add test-specific counts
328                 if (defined($testname))
329                 {
330                     $testfnccount->{$2} += $1;
331                 }
332                 last;
333             };
334
335             /^BRDA:(\d+),(\d+),(\d+),(\d+|-)/ && do {
336                 # Branch coverage data found
337                 my ($line, $block, $branch, $taken) =
338                     ($1, $2, $3, $4);
339
340                 last if (!$br_coverage);
341                 $sumbrcount->{$line} .=
342                     "$block,$branch,$taken:";
343
344                 # Add test-specific counts
345                 if (defined($testname)) {
346                     $testbrcount->{$line} .=
347                         "$block,$branch,$taken:";
348                 }
349                 last;
350             };
351
352             /^end_of_record/ && do
353             {
354                 # Found end of section marker
355                 if ($filename)
356                 {
357                     # Store current section data
358                     if (defined($testname))
359                     {
360                         $testdata->{$testname} =
361                             $testcount;
362                         $testfncdata->{$testname} =
363                             $testfnccount;
364                         $testbrdata->{$testname} =
365                             $testbrcount;
366                     }
367
368                     set_info_entry($data, $testdata,
369                                    $sumcount, $funcdata,
370                                    $checkdata, $testfncdata,
371                                    $sumfnccount,
372                                    $testbrdata,
373                                    $sumbrcount);
374                     $result{$filename} = $data;
375                     last;
376                 }
377             };
378
379             # default
380             last;
381         }
382     }
383     close(INFO_HANDLE);
384
385     # Calculate lines_found and lines_hit for each file
386     foreach $filename (keys(%result))
387     {
388         $data = $result{$filename};
389
390         ($testdata, $sumcount, undef, undef, $testfncdata,
391          $sumfnccount, $testbrdata, $sumbrcount) =
392             get_info_entry($data);
393
394         # Filter out empty files
395         if (scalar(keys(%{$sumcount})) == 0)
396         {
397             delete($result{$filename});
398             next;
399         }
400         # Filter out empty test cases
401         foreach $testname (keys(%{$testdata}))
402         {
403             if (!defined($testdata->{$testname}) ||
404                 scalar(keys(%{$testdata->{$testname}})) == 0)
405             {
406                 delete($testdata->{$testname});
407                 delete($testfncdata->{$testname});
408             }
409         }
410
411         $data->{"found"} = scalar(keys(%{$sumcount}));
412         $hitcount = 0;
413
414         foreach (keys(%{$sumcount}))
415         {
416             if ($sumcount->{$_} > 0) { $hitcount++; }
417         }
418
419         $data->{"hit"} = $hitcount;
420
421         # Get found/hit values for function call data
422         $data->{"f_found"} = scalar(keys(%{$sumfnccount}));
423         $hitcount = 0;
424
425         foreach (keys(%{$sumfnccount})) {
426             if ($sumfnccount->{$_} > 0) {
427                 $hitcount++;
428             }
429         }
430         $data->{"f_hit"} = $hitcount;
431
432         # Combine branch data for the same branches
433         (undef, $data->{"b_found"}, $data->{"b_hit"}) = compress_brcount($sumbrcount);
434         foreach $testname (keys(%{$testbrdata})) {
435             compress_brcount($testbrdata->{$testname});
436         }
437     }
438
439     if (scalar(keys(%result)) == 0)
440     {
441         die("ERROR: no valid records found in tracefile $tracefile\n");
442     }
443     if ($negative)
444     {
445         warn("WARNING: negative counts found in tracefile ".
446              "$tracefile\n");
447     }
448     if ($changed_testname)
449     {
450         warn("WARNING: invalid characters removed from testname in ".
451              "tracefile $tracefile\n");
452     }
453
454     return(\%result);
455 }
456
457 sub print_simplified_info
458 {
459     for my $key (keys(%info_data))
460     {
461         print "K $key: \n";
462         my $sumcountref = $info_data{$key}->{"sum"};
463         for my $line (sort{$a<=>$b}(keys(%$sumcountref)))
464         {
465             print "L  $line: $sumcountref->{$line}\n";
466         }
467     }
468 }
469
470 # From genhtml:
471 #
472 # get_info_entry(hash_ref)
473 #
474 # Retrieve data from an entry of the structure generated by read_info_file().
475 # Return a list of references to hashes:
476 # (test data hash ref, sum count hash ref, funcdata hash ref, checkdata hash
477 #  ref, testfncdata hash ref, sumfnccount hash ref, lines found, lines hit,
478 #  functions found, functions hit)
479 #
480
481 sub get_info_entry($)
482 {
483     my $testdata_ref = $_[0]->{"test"};
484     my $sumcount_ref = $_[0]->{"sum"};
485     my $funcdata_ref = $_[0]->{"func"};
486     my $checkdata_ref = $_[0]->{"check"};
487     my $testfncdata = $_[0]->{"testfnc"};
488     my $sumfnccount = $_[0]->{"sumfnc"};
489     my $testbrdata = $_[0]->{"testbr"};
490     my $sumbrcount = $_[0]->{"sumbr"};
491     my $lines_found = $_[0]->{"found"};
492     my $lines_hit = $_[0]->{"hit"};
493     my $fn_found = $_[0]->{"f_found"};
494     my $fn_hit = $_[0]->{"f_hit"};
495     my $br_found = $_[0]->{"b_found"};
496     my $br_hit = $_[0]->{"b_hit"};
497
498     return ($testdata_ref, $sumcount_ref, $funcdata_ref, $checkdata_ref,
499             $testfncdata, $sumfnccount, $testbrdata, $sumbrcount,
500             $lines_found, $lines_hit, $fn_found, $fn_hit,
501             $br_found, $br_hit);
502 }
503
504
505 # From genhtml:
506 #
507 # set_info_entry(hash_ref, testdata_ref, sumcount_ref, funcdata_ref,
508 #                checkdata_ref, testfncdata_ref, sumfcncount_ref,
509 #                testbrdata_ref, sumbrcount_ref[,lines_found,
510 #                lines_hit, f_found, f_hit, $b_found, $b_hit])
511 #
512 # Update the hash referenced by HASH_REF with the provided data references.
513 #
514
515 sub set_info_entry($$$$$$$$$;$$$$$$)
516 {
517     my $data_ref = $_[0];
518
519     $data_ref->{"test"} = $_[1];
520     $data_ref->{"sum"} = $_[2];
521     $data_ref->{"func"} = $_[3];
522     $data_ref->{"check"} = $_[4];
523     $data_ref->{"testfnc"} = $_[5];
524     $data_ref->{"sumfnc"} = $_[6];
525     $data_ref->{"testbr"} = $_[7];
526     $data_ref->{"sumbr"} = $_[8];
527
528     if (defined($_[9])) { $data_ref->{"found"} = $_[9]; }
529     if (defined($_[10])) { $data_ref->{"hit"} = $_[10]; }
530     if (defined($_[11])) { $data_ref->{"f_found"} = $_[11]; }
531     if (defined($_[12])) { $data_ref->{"f_hit"} = $_[12]; }
532     if (defined($_[13])) { $data_ref->{"b_found"} = $_[13]; }
533     if (defined($_[14])) { $data_ref->{"b_hit"} = $_[14]; }
534 }
535
536 # From genhtml:
537 #
538 # combine_info_entries(entry_ref1, entry_ref2, filename)
539 #
540 # Combine .info data entry hashes referenced by ENTRY_REF1 and ENTRY_REF2.
541 # Return reference to resulting hash.
542 #
543 sub combine_info_entries($$$)
544 {
545     my $entry1 = $_[0];     # Reference to hash containing first entry
546     my $testdata1;
547     my $sumcount1;
548     my $funcdata1;
549     my $checkdata1;
550     my $testfncdata1;
551     my $sumfnccount1;
552     my $testbrdata1;
553     my $sumbrcount1;
554
555     my $entry2 = $_[1];     # Reference to hash containing second entry
556     my $testdata2;
557     my $sumcount2;
558     my $funcdata2;
559     my $checkdata2;
560     my $testfncdata2;
561     my $sumfnccount2;
562     my $testbrdata2;
563     my $sumbrcount2;
564
565     my %result;         # Hash containing combined entry
566     my %result_testdata;
567     my $result_sumcount = {};
568     my $result_funcdata;
569     my $result_testfncdata;
570     my $result_sumfnccount;
571     my $result_testbrdata;
572     my $result_sumbrcount;
573     my $lines_found;
574     my $lines_hit;
575     my $fn_found;
576     my $fn_hit;
577     my $br_found;
578     my $br_hit;
579
580     my $testname;
581     my $filename = $_[2];
582
583     # Retrieve data
584     ($testdata1, $sumcount1, $funcdata1, $checkdata1, $testfncdata1,
585      $sumfnccount1, $testbrdata1, $sumbrcount1) = get_info_entry($entry1);
586     ($testdata2, $sumcount2, $funcdata2, $checkdata2, $testfncdata2,
587      $sumfnccount2, $testbrdata2, $sumbrcount2) = get_info_entry($entry2);
588
589 #    # Merge checksums
590 #    $checkdata1 = merge_checksums($checkdata1, $checkdata2, $filename);
591 #
592 #    # Combine funcdata
593 #    $result_funcdata = merge_func_data($funcdata1, $funcdata2, $filename);
594 #
595 #    # Combine function call count data
596 #    $result_testfncdata = add_testfncdata($testfncdata1, $testfncdata2);
597 #    ($result_sumfnccount, $fn_found, $fn_hit) =
598 #        add_fnccount($sumfnccount1, $sumfnccount2);
599 #
600 #    # Combine branch coverage data
601 #    $result_testbrdata = add_testbrdata($testbrdata1, $testbrdata2);
602 #    ($result_sumbrcount, $br_found, $br_hit) =
603 #        combine_brcount($sumbrcount1, $sumbrcount2, $BR_ADD);
604 #
605     # Combine testdata
606     foreach $testname (keys(%{$testdata1}))
607     {
608         if (defined($testdata2->{$testname}))
609         {
610             # testname is present in both entries, requires
611             # combination
612             ($result_testdata{$testname}) =
613                 add_counts($testdata1->{$testname},
614                            $testdata2->{$testname});
615         }
616         else
617         {
618             # testname only present in entry1, add to result
619             $result_testdata{$testname} = $testdata1->{$testname};
620         }
621
622         # update sum count hash
623         ($result_sumcount, $lines_found, $lines_hit) =
624             add_counts($result_sumcount,
625                        $result_testdata{$testname});
626     }
627
628     foreach $testname (keys(%{$testdata2}))
629     {
630         # Skip testnames already covered by previous iteration
631         if (defined($testdata1->{$testname})) { next; }
632
633         # testname only present in entry2, add to result hash
634         $result_testdata{$testname} = $testdata2->{$testname};
635
636         # update sum count hash
637         ($result_sumcount, $lines_found, $lines_hit) =
638             add_counts($result_sumcount,
639                        $result_testdata{$testname});
640     }
641
642     # Calculate resulting sumcount
643
644     # Store result
645     set_info_entry(\%result, \%result_testdata, $result_sumcount,
646                    $result_funcdata, $checkdata1, $result_testfncdata,
647                    $result_sumfnccount, $result_testbrdata,
648                    $result_sumbrcount, $lines_found, $lines_hit,
649                    $fn_found, $fn_hit, $br_found, $br_hit);
650
651     return(\%result);
652 }
653
654 # From genhtml:
655 #
656 # combine_info_files(info_ref1, info_ref2)
657 #
658 # Combine .info data in hashes referenced by INFO_REF1 and INFO_REF2. Return
659 # reference to resulting hash.
660 #
661 sub combine_info_files($$)
662 {
663     my %hash1 = %{$_[0]};
664     my %hash2 = %{$_[1]};
665     my $filename;
666
667     foreach $filename (keys(%hash2))
668     {
669         if ($hash1{$filename})
670         {
671             # Entry already exists in hash1, combine them
672             $hash1{$filename} =
673                 combine_info_entries($hash1{$filename},
674                                      $hash2{$filename},
675                                      $filename);
676         }
677         else
678         {
679             # Entry is unique in both hashes, simply add to
680             # resulting hash
681             $hash1{$filename} = $hash2{$filename};
682         }
683     }
684
685     return(\%hash1);
686 }
687
688 # From genhtml:
689 #
690 # add_counts(data1_ref, data2_ref)
691 #
692 # DATA1_REF and DATA2_REF are references to hashes containing a mapping
693 #
694 #   line number -> execution count
695 #
696 # Return a list (RESULT_REF, LINES_FOUND, LINES_HIT) where RESULT_REF
697 # is a reference to a hash containing the combined mapping in which
698 # execution counts are added.
699 #
700 sub add_counts($$)
701 {
702     my $data1_ref = $_[0];  # Hash 1
703     my $data2_ref = $_[1];  # Hash 2
704     my %result;             # Resulting hash
705     my $line;               # Current line iteration scalar
706     my $data1_count;        # Count of line in hash1
707     my $data2_count;        # Count of line in hash2
708     my $found = 0;          # Total number of lines found
709     my $hit = 0;            # Number of lines with a count > 0
710
711     foreach $line (keys(%$data1_ref))
712     {
713         $data1_count = $data1_ref->{$line};
714         $data2_count = $data2_ref->{$line};
715
716         # Add counts if present in both hashes
717         if (defined($data2_count)) { $data1_count += $data2_count; }
718
719         # Store sum in %result
720         $result{$line} = $data1_count;
721
722         $found++;
723         if ($data1_count > 0) { $hit++; }
724     }
725
726     # Add lines unique to data2_ref
727     foreach $line (keys(%$data2_ref))
728     {
729         # Skip lines already in data1_ref
730         if (defined($data1_ref->{$line})) { next; }
731
732         # Copy count from data2_ref
733         $result{$line} = $data2_ref->{$line};
734
735         $found++;
736         if ($result{$line} > 0) { $hit++; }
737     }
738
739     return (\%result, $found, $hit);
740 }
741
742
743 # From genhtml:
744 sub compress_brcount($)
745 {
746     my ($brcount) = @_;
747     my $db;
748
749     $db = brcount_to_db($brcount);
750     return db_to_brcount($db, $brcount);
751 }
752
753 #
754 # brcount_to_db(brcount)
755 #
756 # Convert brcount data to the following format:
757 #
758 # db:          line number    -> block hash
759 # block hash:  block number   -> branch hash
760 # branch hash: branch number  -> taken value
761 #
762
763 sub brcount_to_db($)
764 {
765     my ($brcount) = @_;
766     my $line;
767     my $db;
768
769     # Add branches to database
770     foreach $line (keys(%{$brcount})) {
771         my $brdata = $brcount->{$line};
772
773         foreach my $entry (split(/:/, $brdata)) {
774             my ($block, $branch, $taken) = split(/,/, $entry);
775             my $old = $db->{$line}->{$block}->{$branch};
776
777             if (!defined($old) || $old eq "-") {
778                 $old = $taken;
779             } elsif ($taken ne "-") {
780                 $old += $taken;
781             }
782
783             $db->{$line}->{$block}->{$branch} = $old;
784         }
785     }
786
787     return $db;
788 }
789
790
791 #
792 # db_to_brcount(db[, brcount])
793 #
794 # Convert branch coverage data back to brcount format. If brcount is specified,
795 # the converted data is directly inserted in brcount.
796 #
797
798 sub db_to_brcount($;$)
799 {
800     my ($db, $brcount) = @_;
801     my $line;
802     my $br_found = 0;
803     my $br_hit = 0;
804
805     # Convert database back to brcount format
806     foreach $line (sort({$a <=> $b} keys(%{$db}))) {
807         my $ldata = $db->{$line};
808         my $brdata;
809         my $block;
810
811         foreach $block (sort({$a <=> $b} keys(%{$ldata}))) {
812             my $bdata = $ldata->{$block};
813             my $branch;
814
815             foreach $branch (sort({$a <=> $b} keys(%{$bdata}))) {
816                 my $taken = $bdata->{$branch};
817
818                 $br_found++;
819                 $br_hit++ if ($taken ne "-" && $taken > 0);
820                 $brdata .= "$block,$branch,$taken:";
821             }
822         }
823         $brcount->{$line} = $brdata;
824     }
825
826     return ($brcount, $br_found, $br_hit);
827 }
828
829
830 #
831 # brcount_db_combine(db1, db2, op)
832 #
833 # db1 := db1 op db2, where
834 #   db1, db2: brcount data as returned by brcount_to_db
835 #   op:       one of $BR_ADD and BR_SUB
836 #
837 sub brcount_db_combine($$$)
838 {
839     my ($db1, $db2, $op) = @_;
840
841     foreach my $line (keys(%{$db2})) {
842         my $ldata = $db2->{$line};
843
844         foreach my $block (keys(%{$ldata})) {
845             my $bdata = $ldata->{$block};
846
847             foreach my $branch (keys(%{$bdata})) {
848                 my $taken = $bdata->{$branch};
849                 my $new = $db1->{$line}->{$block}->{$branch};
850
851                 if (!defined($new) || $new eq "-") {
852                     $new = $taken;
853                 } elsif ($taken ne "-") {
854                     if ($op == $BR_ADD) {
855                         $new += $taken;
856                     } elsif ($op == $BR_SUB) {
857                         $new -= $taken;
858                         $new = 0 if ($new < 0);
859                     }
860                 }
861
862                 $db1->{$line}->{$block}->{$branch} = $new;
863             }
864         }
865     }
866 }
867
868 # From genhtml
869 sub info(@)
870 {
871     if($debug)
872     {
873         # Print info string
874         printf(@_);
875     }
876 }
877
878
879 # Format per file, repeated, no linebreak
880 # <diffcmd>
881 # index c1..c2 c3
882 # --- a/<left-hand-side-file>
883 # +++ b/<right-hand-side-file>
884 # <diff hunks>
885 #
886 # Format of each diff hunk, repeated, no linebreak
887 # @@ <ranges> @@ line
888 # 3 lines of context
889 # [-|+]lines removed on left, added on right
890 # 3 lines of context
891 #
892 # output:
893 # <dali-source-file>: source / header files in dali/dali-toolkit
894 # \%filter: <dali-source-file> -> \%filedata
895 # %filedata: "patch"   -> \@checklines
896 #            "b_lines" -> \%b_lines
897 # @checklines: vector of \[start, length] # line numbers of new/modified lines
898 # %b_lines: <line-number> -> patch line in b-file.
899 sub parse_diff
900 {
901     my $patchref = shift;
902     my $file="";
903     my @checklines=();
904     my %b_lines=();
905     my $state = 0;
906     my $store_line=-1;
907     my %files=();
908
909     print "Patch size: ".scalar(@$patchref)."\n" if $pd_debug;
910     for my $line (@$patchref)
911     {
912         if($state == 0)
913         {
914             print "State: $state  $line  \n" if $pd_debug;
915             # Search for a line matching "+++ b/<filename>"
916             if( $line =~ m!^\+\+\+ b/([\w-_\./]*)!)
917             {
918                 $file = $1;
919                 $state = 1 ;
920                 print "Found File: $file\n" if $pd_debug;
921             }
922         }
923         else #elsif($state == 1)
924         {
925             # If we find a line starting with diff, the previous
926             # file's diffs have finished, store them.
927             if( $line =~ /^diff/)
928             {
929                 print "State: $state  $line  \n" if $pd_debug;
930                 $state = 0;
931                 # if the file had changes, store the new/modified line numbers
932                 if( $file && scalar(@checklines))
933                 {
934                     $files{$file}->{"patch"} = [@checklines];
935                     $files{$file}->{"b_lines"} = {%b_lines};
936                     @checklines=();
937                     %b_lines=();
938                 }
939                 print("\n\n") if $pd_debug;
940             }
941             # If we find a line starting with @@, it tells us the line numbers
942             # of the old file and new file for this hunk.
943             elsif( $line =~ /^@@/)
944             {
945                 print "State: $state  $line  \n" if $pd_debug;
946
947                 # Find the lines in the new file (of the form "+<start>[,<length>])
948                 my($start,$space,$length) = ($line =~ /\+([0-9]+)(,| )([0-9]+)?/);
949                 if($length || $space eq " ")
950                 {
951                     if( $space eq " " )
952                     {
953                         $length=1;
954                     }
955                     push(@checklines, [$start, $length]);
956                     $store_line=$start;
957                 }
958                 else
959                 {
960                     $store_line = -1;
961                 }
962                 if($pd_debug)
963                 {
964                     my $last = scalar(@checklines)-1;
965                     if( $last >= 0 )
966                     {
967                         print "Checkline:" . $checklines[$last]->[0] . ", " . $checklines[$last]->[1] . "\n";
968                     }
969                 }
970             }
971             # If we find a line starting with "+", it belongs to the new file's patch
972             elsif( $line =~ /^\+/)
973             {
974                if($store_line >= 0)
975                {
976                    chomp;
977                    $line = substr($line, 1); # Remove leading +
978                    $b_lines{$store_line} = $line;
979                    $store_line++;
980                }
981             }
982         }
983     }
984     # Store the final entry
985     $files{$file}->{"patch"} = [@checklines];
986     $files{$file}->{"b_lines"} = {%b_lines};
987
988     my %filter = map { $_ => $files{$_} } grep {m!^dali(-toolkit|-scene-loader)?/!} (keys(%files));
989
990     if($pd_debug)
991     {
992         print("Filtered files:\n");
993         foreach my $file (keys(%filter))
994         {
995             print("$file: ");
996             $patchref = $filter{$file}->{"patch"};
997             foreach my $lineblock (@$patchref)
998             {
999                 print "$lineblock->[0]($lineblock->[1]) "
1000             }
1001             print ( "\n");
1002         }
1003     }
1004
1005     return {%filter};#copy? - test and fixme
1006 }
1007
1008 sub show_patch_lines
1009 {
1010     my $filesref = shift;
1011     print "\nNumber of files: " . scalar(keys(%$filesref)) . "\n";
1012     for my $file (keys(%$filesref))
1013     {
1014         print("$file:");
1015         my $clref = $filesref->{$file}->{"patch"};
1016         for my $cl (@$clref)
1017         {
1018             print("($cl->[0],$cl->[1]) ");
1019         }
1020         print("\n");
1021     }
1022 }
1023
1024
1025 # Run the git diff command to get the patch
1026 # Output - see parse_diff
1027 sub run_diff
1028 {
1029     my ($fh, $c) = $repo->command_output_pipe(@_);
1030     our @patch=();
1031     while(<$fh>)
1032     {
1033         chomp;
1034         push @patch, $_;
1035     }
1036     $repo->command_close_pipe($fh, $c);
1037
1038     print "Patch size: " . scalar(@patch) . "\n" if $debug;
1039
1040     # @patch has slurped diff for all files...
1041     my $filesref = parse_diff ( \@patch );
1042     show_patch_lines($filesref) if $debug>1;
1043
1044     return $filesref;
1045 }
1046
1047 sub calc_patch_coverage_percentage
1048 {
1049     my $filesref = shift;
1050     my $total_covered_lines = 0;
1051     my $total_uncovered_lines = 0;
1052
1053     foreach my $file (keys(%$filesref))
1054     {
1055         my ($name, $path, $suffix) = fileparse($file, qr{\.[^.]*$});
1056         next if($path !~ /^dali/);
1057
1058         my $covered_lines = 0;
1059         my $uncovered_lines = 0;
1060
1061         my $patchref = $filesref->{$file}->{"patch"};
1062
1063         my $abs_filename = File::Spec->rel2abs($file, $root);
1064         my $sumcountref = $info_data{$abs_filename}->{"sum"};
1065
1066         if($debug>1)
1067         {
1068             print("File:  $abs_filename\n");
1069             print Dumper($info_data{$abs_filename});
1070             print "\n";
1071         }
1072
1073         if( $sumcountref )
1074         {
1075             for my $patch (@$patchref)
1076             {
1077                 for(my $i = 0; $i < $patch->[1]; $i++ )
1078                 {
1079                     my $line = $i + $patch->[0];
1080                     if(exists($sumcountref->{$line}))
1081                     {
1082                         if( $sumcountref->{$line} > 0 )
1083                         {
1084                             $covered_lines++;
1085                             $total_covered_lines++;
1086                         }
1087                         else
1088                         {
1089                             $uncovered_lines++;
1090                             $total_uncovered_lines++;
1091                         }
1092                     }
1093                 }
1094             }
1095             $filesref->{$file}->{"covered_lines"} = $covered_lines;
1096             $filesref->{$file}->{"uncovered_lines"} = $uncovered_lines;
1097             my $total = $covered_lines + $uncovered_lines;
1098             my $percent = 0;
1099             if($total > 0)
1100             {
1101                 $percent = $covered_lines / $total;
1102             }
1103             $filesref->{$file}->{"percent_covered"} = 100 * $percent;
1104         }
1105         else
1106         {
1107             print "Can't find coverage data for $abs_filename\n";
1108         }
1109     }
1110     my $total_exec = $total_covered_lines + $total_uncovered_lines;
1111     my $percent = 0;
1112     if($total_exec > 0) { $percent = 100 * $total_covered_lines / $total_exec; }
1113
1114     return [ $total_exec, $percent, $total_covered_lines ];
1115 }
1116
1117 #
1118 #
1119 sub patch_output
1120 {
1121     my $filesref = shift;
1122     foreach my $file (keys(%$filesref))
1123     {
1124         my ($name, $path, $suffix) = fileparse($file, qr{\.[^.]*$});
1125         next if($path !~ /^dali/);
1126
1127         my $fileref = $filesref->{$file};
1128         my $patchref = $fileref->{"patch"};
1129         my $b_lines_ref = $fileref->{"b_lines"};
1130
1131         my $abs_filename = File::Spec->rel2abs($file, $root);
1132         my $sumcountref = $info_data{$abs_filename}->{"sum"};
1133
1134         print BOLD, "$file  ";
1135
1136         if($fileref)
1137         {
1138             if( $fileref->{"covered_lines"} > 0
1139                 ||
1140                 $fileref->{"uncovered_lines"} > 0 )
1141             {
1142                 print GREEN, "Covered: " . $fileref->{"covered_lines"}, RED, " Uncovered: " . $fileref->{"uncovered_lines"}, RESET;
1143             }
1144         }
1145         else
1146         {
1147             if($suffix eq ".cpp" || $suffix eq ".c" || $suffix eq ".h")
1148             {
1149                 print RED;
1150             }
1151             print "No coverage found";
1152         }
1153         print RESET "\n";
1154
1155         for my $patch (@$patchref)
1156         {
1157             my $hunkstr="Hunk: " . $patch->[0];
1158             if( $patch->[1] > 1 )
1159             {
1160                 $hunkstr .= " - " . ($patch->[0]+$patch->[1]-1);
1161             }
1162             print BOLD, "$hunkstr\n",  RESET;
1163             for(my $i = 0; $i < $patch->[1]; $i++ )
1164             {
1165                 my $line = $i + $patch->[0];
1166                 printf "%-6s  ", $line;
1167
1168                 if($sumcountref)
1169                 {
1170                     my $color;
1171                     if(exists($sumcountref->{$line}))
1172                     {
1173                         if($sumcountref->{$line} > 0)
1174                         {
1175                             $color=GREEN;
1176                         }
1177                         else
1178                         {
1179                             $color=BOLD . RED;
1180                         }
1181                     }
1182                     else
1183                     {
1184                         $color=CYAN;
1185                     }
1186                     my $src = $b_lines_ref->{$line};
1187                     print $color, "$src\n", RESET;
1188                 }
1189                 else
1190                 {
1191                     my $src = $b_lines_ref->{$line};
1192                     print "$src\n";
1193                 }
1194             }
1195         }
1196     }
1197 }
1198
1199 #
1200 #
1201 sub patch_html_output
1202 {
1203     my $filesref = shift;
1204
1205     open( my $filehandle, ">", $opt_output ) || die "Can't open $opt_output for writing:$!\n";
1206
1207     my $OUTPUT_FH = select;
1208     select $filehandle;
1209     print <<EOH;
1210 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
1211 "http://www.w3.org/TR/REC-html40/loose.dtd">
1212 <html>
1213 <head>
1214 <title>Patch Coverage</title>
1215 </head>
1216 <body bgcolor="white">
1217 EOH
1218
1219     foreach my $file (keys(%$filesref))
1220     {
1221         my ($name, $path, $suffix) = fileparse($file, qr{\.[^.]*$});
1222         next if($path !~ /^dali/);
1223
1224         my $fileref = $filesref->{$file};
1225         my $patchref = $fileref->{"patch"};
1226         my $b_lines_ref = $fileref->{"b_lines"};
1227
1228         my $abs_filename = File::Spec->rel2abs($file, $root);
1229         my $sumcountref = $info_data{$abs_filename}->{"sum"};
1230
1231         print "<h2>$file</h2>\n";
1232
1233         if($fileref)
1234         {
1235             if( $fileref->{"covered_lines"} > 0
1236                 ||
1237                 $fileref->{"uncovered_lines"} > 0 )
1238             {
1239                 print "<p style=\"color:green;\">Covered: " .
1240                     $fileref->{"covered_lines"} . "<p>" .
1241                     "<p style=\"color:red;\">Uncovered: " .
1242                     $fileref->{"uncovered_lines"} . "</span></p>";
1243             }
1244         }
1245         else
1246         {
1247             print "<p>";
1248             my $span=0;
1249             if($suffix eq ".cpp" || $suffix eq ".c" || $suffix eq ".h")
1250             {
1251                 print "<span style=\"color:red;\">";
1252                 $span=1;
1253             }
1254             print "No coverage found";
1255             print "</span>" if $span;
1256         }
1257         print "</p>";
1258
1259         for my $patch (@$patchref)
1260         {
1261             my $hunkstr="Hunk: " . $patch->[0];
1262             if( $patch->[1] > 1 )
1263             {
1264                 $hunkstr .= " - " . ($patch->[0]+$patch->[1]-1);
1265             }
1266             print "<p style=\"font-weight:bold;\">" . $hunkstr . "</p>";
1267
1268             print "<pre>";
1269             for(my $i = 0; $i < $patch->[1]; $i++ )
1270             {
1271                 my $line = $i + $patch->[0];
1272                 my $num_line_digits=log($line)/log(10);
1273                 for $i (0..(6-$num_line_digits-1))
1274                 {
1275                     print " ";
1276                 }
1277                 print "$line  ";
1278
1279                 if($sumcountref)
1280                 {
1281                     my $color;
1282                     if(exists($sumcountref->{$line}))
1283                     {
1284                         if($sumcountref->{$line} > 0)
1285                         {
1286                             print("<span style=\"color:green;\">");
1287                         }
1288                         else
1289                         {
1290                             print("<span style=\"color:red;font-weight:bold;\">");
1291                         }
1292                     }
1293                     else
1294                     {
1295                         print("<span style=\"color:black;font-weight:normal;\">");
1296                     }
1297                     my $src=$b_lines_ref->{$line};
1298                     chomp($src);
1299                     print "$src</span>\n";
1300                 }
1301                 else
1302                 {
1303                     my $src = $b_lines_ref->{$line};
1304                     print "$src\n";
1305                 }
1306             }
1307             print "<\pre>\n";
1308         }
1309     }
1310
1311     print $filehandle "<hr>\n</body>\n</html>\n";
1312     close $filehandle;
1313     select $OUTPUT_FH;
1314 }
1315
1316
1317 ################################################################################
1318 ##                                    MAIN                                    ##
1319 ################################################################################
1320
1321
1322 # Generate git diff command
1323 my @cmd=('--no-pager','diff','--no-ext-diff','-U0','--no-color');
1324 my $status = $repo->command("status", "-s");
1325
1326 if( $status eq "" && !scalar(@ARGV))
1327 {
1328     # There are no changes in the index or working tree, and
1329     # no diff arguments to append. Use the last patch instead.
1330     push @cmd, ('HEAD~1','HEAD');
1331 }
1332 else
1333 {
1334     # detect if there are only cached changes or only working tree changes
1335     my $cached = 0;
1336     my $working = 0;
1337     for my $fstat ( split(/\n/, $status) )
1338     {
1339         if(substr( $fstat, 0, 1 ) ne " "){ $cached++; }
1340         if(substr( $fstat, 1, 1 ) ne " "){ $working++; }
1341     }
1342     if($cached > 0 )
1343     {
1344         if($working == 0)
1345         {
1346             push @cmd, "--cached";
1347         }
1348         else
1349         {
1350             die "Error: Both cached & working files - cannot get correct patch from git\nRun git add first.";
1351             # Would have to diff from separate clone.
1352         }
1353     }
1354 }
1355
1356 push @cmd, @ARGV;
1357
1358 # Before executing the diff, run the coverage.sh script. This is done here so that the
1359 # error condition above happens straight away, rather than after spewing out lots of information.
1360
1361 my $cwd = getcwd(); # expect this to be automated-tests folder
1362 # execute coverage.sh, generating build/tizen/dali.info from lib, and
1363 # *.dir/dali.info. Don't generate html
1364 printf("Running coverage.sh\n");
1365 my $coverage_output=`./coverage.sh -n`;
1366 chdir "..";
1367 $root = getcwd();
1368
1369 our %info_data; # Hash of all data from .info files
1370 my @info_files = split(/\n/, `find . -name dali.info`);
1371 my %new_info;
1372
1373 # Read in all specified .info files
1374 foreach (@info_files)
1375 {
1376     %new_info = %{read_info_file($_)};
1377
1378     # Combine %new_info with %info_data
1379     %info_data = %{combine_info_files(\%info_data, \%new_info)};
1380 }
1381
1382
1383 # Execute diff & coverage from root directory
1384 my $filesref = run_diff(@cmd);
1385
1386 chdir $cwd;
1387
1388 # Check how many actual source files there are in the patch
1389 my $filecount = 0;
1390 foreach my $file (keys(%$filesref))
1391 {
1392     my ($name, $path, $suffix) = fileparse($file, qr{\.[^.]*$});
1393     next if($path !~ /^dali/);
1394     next if($suffix ne ".cpp" && $suffix ne ".c" && $suffix ne ".h");
1395     $filecount++;
1396 }
1397 if( $filecount == 0 )
1398 {
1399     print "Warning: No source files found\n";
1400     exit 0;    # Exit with no error.
1401 }
1402
1403 #print_simplified_info() if $debug;
1404 #exit 0;
1405 if($debug > 1)
1406 {
1407     print "Info keys:\n";
1408     for my $key (keys(%info_data))
1409     {
1410         print "$key\n";
1411     }
1412     print "\n\n";
1413 }
1414
1415 my $percentref = calc_patch_coverage_percentage($filesref);
1416 if($percentref->[0] == 0)
1417 {
1418     print "Warning: No coverable lines found\n";
1419     exit 0;
1420 }
1421 my $percent = $percentref->[1];
1422
1423 printf(join("\n", grep { $_ !~ /^Remov/ } split(/\n/,$coverage_output))) if $opt_verbose;
1424 #printf($coverage_output) if $opt_verbose;
1425
1426 my $color=BOLD RED;
1427 if($opt_output)
1428 {
1429     print "Outputing to $opt_output\n" if $debug;
1430     patch_html_output($filesref);
1431 }
1432 elsif( ! $opt_quiet )
1433 {
1434     patch_output($filesref);
1435     if($percent>=90)
1436     {
1437         $color=GREEN;
1438     }
1439     print RESET;
1440 }
1441
1442 printf("\n\n=========================\nPatch coverage output:\n=========================\n");
1443 printf("Line Coverage: %d/%d\n", $percentref->[2], $percentref->[0]);
1444 printf("Percentage of change covered: %5.2f%\n", $percent);
1445
1446 exit($percent<90);
1447
1448
1449 __END__
1450
1451 =head1 NAME
1452
1453 patch-coverage
1454
1455 =head1 SYNOPSIS
1456
1457 patch-coverage.pl - Determine if patch coverage is below 90%
1458
1459 =head1 DESCRIPTION
1460 Calculates how well the most recent patch is covered (either the
1461 patch that is in the index+working directory, or HEAD).
1462
1463 =head1 OPTIONS
1464
1465 =over 28
1466
1467 =item B<-c|--cached>
1468 Use index files if there is nothing in the working tree
1469
1470 =item B<   --help>
1471 This help
1472
1473 =item B<-q|--quiet>
1474 Don't generate any output
1475
1476 =head1 RETURN STATUS
1477 0 if the coverage of source files is > 90%, otherwise 1
1478
1479 =head1 EXAMPLES
1480
1481
1482 =cut