Do not change BlendMode by following whether advanced blend equation is appied or not
[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"=>"" });
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 # NEW STUFF
879
880 ## Format per file, repeated, no linebreak
881 # <diffcmd>
882 # index c1..c2 c3
883 # --- a/<left-hand-side-file>
884 # +++ b/<right-hand-side-file>
885 # <diff hunks>
886
887 # Format of each diff hunk, repeated, no linebreak
888 # @@ <ranges> @@ line
889 # 3 lines of context
890 # [-|+]lines removed on left, added on right
891 # 3 lines of context
892 #
893 # output:
894 # <dali-source-file>: source / header files in dali/dali-toolkit
895 # \%filter: <dali-source-file> -> \%filedata
896 # %filedata: "patch"   -> \@checklines
897 #            "b_lines" -> \%b_lines
898 # @checklines: vector of \[start, length] # line numbers of new/modified lines
899 # %b_lines: <line-number> -> patch line in b-file.
900 sub parse_diff
901 {
902     my $patchref = shift;
903     my $file="";
904     my @checklines=();
905     my %b_lines=();
906     my $state = 0;
907     my $store_line=-1;
908     my %files=();
909
910     print "Patch size: ".scalar(@$patchref)."\n" if $pd_debug;
911     for my $line (@$patchref)
912     {
913         if($state == 0)
914         {
915             print "State: $state  $line  \n" if $pd_debug;
916             # Search for a line matching "+++ b/<filename>"
917             if( $line =~ m!^\+\+\+ b/([\w-_\./]*)!)
918             {
919                 $file = $1;
920                 $state = 1 ;
921                 print "Found File: $file\n" if $pd_debug;
922             }
923         }
924         else #elsif($state == 1)
925         {
926             # If we find a line starting with diff, the previous
927             # file's diffs have finished, store them.
928             if( $line =~ /^diff/)
929             {
930                 print "State: $state  $line  \n" if $pd_debug;
931                 $state = 0;
932                 # if the file had changes, store the new/modified line numbers
933                 if( $file && scalar(@checklines))
934                 {
935                     $files{$file}->{"patch"} = [@checklines];
936                     $files{$file}->{"b_lines"} = {%b_lines};
937                     @checklines=();
938                     %b_lines=();
939                 }
940                 print("\n\n") if $pd_debug;
941             }
942             # If we find a line starting with @@, it tells us the line numbers
943             # of the old file and new file for this hunk.
944             elsif( $line =~ /^@@/)
945             {
946                 print "State: $state  $line  \n" if $pd_debug;
947
948                 # Find the lines in the new file (of the form "+<start>[,<length>])
949                 my($start,$space,$length) = ($line =~ /\+([0-9]+)(,| )([0-9]+)?/);
950                 if($length || $space eq " ")
951                 {
952                     if( $space eq " " )
953                     {
954                         $length=1;
955                     }
956                     push(@checklines, [$start, $length]);
957                     $store_line=$start;
958                 }
959                 else
960                 {
961                     $store_line = -1;
962                 }
963                 if($pd_debug)
964                 {
965                     my $last = scalar(@checklines)-1;
966                     if( $last >= 0 )
967                     {
968                         print "Checkline:" . $checklines[$last]->[0] . ", " . $checklines[$last]->[1] . "\n";
969                     }
970                 }
971             }
972             # If we find a line starting with "+", it belongs to the new file's patch
973             elsif( $line =~ /^\+/)
974             {
975                if($store_line >= 0)
976                {
977                    chomp;
978                    $line = substr($line, 1); # Remove leading +
979                    $b_lines{$store_line} = $line;
980                    $store_line++;
981                }
982             }
983         }
984     }
985     # Store the final entry
986     $files{$file}->{"patch"} = [@checklines];
987     $files{$file}->{"b_lines"} = {%b_lines};
988
989     my %filter = map { $_ => $files{$_} } grep {m!^dali(-toolkit)?/!} (keys(%files));;
990
991     if($pd_debug)
992     {
993         print("Filtered files:\n");
994         foreach my $file (keys(%filter))
995         {
996             print("$file: ");
997             $patchref = $filter{$file}->{"patch"};
998             foreach my $lineblock (@$patchref)
999             {
1000                 print "$lineblock->[0]($lineblock->[1]) "
1001             }
1002             print ( "\n");
1003         }
1004     }
1005
1006     return {%filter};#copy? - test and fixme
1007 }
1008
1009 sub show_patch_lines
1010 {
1011     my $filesref = shift;
1012     print "\nNumber of files: " . scalar(keys(%$filesref)) . "\n";
1013     for my $file (keys(%$filesref))
1014     {
1015         print("$file:");
1016         my $clref = $filesref->{$file}->{"patch"};
1017         for my $cl (@$clref)
1018         {
1019             print("($cl->[0],$cl->[1]) ");
1020         }
1021         print("\n");
1022     }
1023 }
1024
1025
1026 # Run the git diff command to get the patch
1027 # Output - see parse_diff
1028 sub run_diff
1029 {
1030     my ($fh, $c) = $repo->command_output_pipe(@_);
1031     our @patch=();
1032     while(<$fh>)
1033     {
1034         chomp;
1035         push @patch, $_;
1036     }
1037     $repo->command_close_pipe($fh, $c);
1038
1039     print "Patch size: " . scalar(@patch) . "\n" if $debug;
1040
1041     # @patch has slurped diff for all files...
1042     my $filesref = parse_diff ( \@patch );
1043     show_patch_lines($filesref) if $debug>1;
1044
1045     return $filesref;
1046 }
1047
1048 sub calc_patch_coverage_percentage
1049 {
1050     my $filesref = shift;
1051     my $total_covered_lines = 0;
1052     my $total_uncovered_lines = 0;
1053
1054     foreach my $file (keys(%$filesref))
1055     {
1056         my ($name, $path, $suffix) = fileparse($file, qr{\.[^.]*$});
1057         next if($path !~ /^dali/);
1058
1059         my $covered_lines = 0;
1060         my $uncovered_lines = 0;
1061
1062         my $patchref = $filesref->{$file}->{"patch"};
1063
1064         my $abs_filename = File::Spec->rel2abs($file, $root);
1065         my $sumcountref = $info_data{$abs_filename}->{"sum"};
1066
1067         if( $sumcountref )
1068         {
1069             for my $patch (@$patchref)
1070             {
1071                 for(my $i = 0; $i < $patch->[1]; $i++ )
1072                 {
1073                     my $line = $i + $patch->[0];
1074                     if(exists($sumcountref->{$line}))
1075                     {
1076                         if( $sumcountref->{$line} > 0 )
1077                         {
1078                             $covered_lines++;
1079                             $total_covered_lines++;
1080                         }
1081                         else
1082                         {
1083                             $uncovered_lines++;
1084                             $total_uncovered_lines++;
1085                         }
1086                     }
1087                 }
1088             }
1089             $filesref->{$file}->{"covered_lines"} = $covered_lines;
1090             $filesref->{$file}->{"uncovered_lines"} = $uncovered_lines;
1091             my $total = $covered_lines + $uncovered_lines;
1092             my $percent = 0;
1093             if($total > 0)
1094             {
1095                 $percent = $covered_lines / $total;
1096             }
1097             $filesref->{$file}->{"percent_covered"} = 100 * $percent;
1098         }
1099         else
1100         {
1101             print "Can't find coverage data for $abs_filename\n";
1102         }
1103     }
1104     my $total_exec = $total_covered_lines + $total_uncovered_lines;
1105     my $percent = 0;
1106     if($total_exec > 0) { $percent = 100 * $total_covered_lines / $total_exec; }
1107
1108     return [ $total_exec, $percent, $total_covered_lines ];
1109 }
1110
1111 #
1112 #
1113 sub patch_output
1114 {
1115     my $filesref = shift;
1116     foreach my $file (keys(%$filesref))
1117     {
1118         my ($name, $path, $suffix) = fileparse($file, qr{\.[^.]*$});
1119         next if($path !~ /^dali/);
1120
1121         my $fileref = $filesref->{$file};
1122         my $patchref = $fileref->{"patch"};
1123         my $b_lines_ref = $fileref->{"b_lines"};
1124
1125         my $abs_filename = File::Spec->rel2abs($file, $root);
1126         my $sumcountref = $info_data{$abs_filename}->{"sum"};
1127
1128         print BOLD, "$file  ";
1129
1130         if($fileref)
1131         {
1132             if( $fileref->{"covered_lines"} > 0
1133                 ||
1134                 $fileref->{"uncovered_lines"} > 0 )
1135             {
1136                 print GREEN, "Covered: " . $fileref->{"covered_lines"}, RED, " Uncovered: " . $fileref->{"uncovered_lines"}, RESET;
1137             }
1138         }
1139         else
1140         {
1141             if($suffix eq ".cpp" || $suffix eq ".c" || $suffix eq ".h")
1142             {
1143                 print RED;
1144             }
1145             print "No coverage found";
1146         }
1147         print RESET "\n";
1148
1149         for my $patch (@$patchref)
1150         {
1151             my $hunkstr="Hunk: " . $patch->[0];
1152             if( $patch->[1] > 1 )
1153             {
1154                 $hunkstr .= " - " . ($patch->[0]+$patch->[1]-1);
1155             }
1156             print BOLD, "$hunkstr\n",  RESET;
1157             for(my $i = 0; $i < $patch->[1]; $i++ )
1158             {
1159                 my $line = $i + $patch->[0];
1160                 printf "%-6s  ", $line;
1161
1162                 if($sumcountref)
1163                 {
1164                     my $color;
1165                     if(exists($sumcountref->{$line}))
1166                     {
1167                         if($sumcountref->{$line} > 0)
1168                         {
1169                             $color=GREEN;
1170                         }
1171                         else
1172                         {
1173                             $color=BOLD . RED;
1174                         }
1175                     }
1176                     else
1177                     {
1178                         $color=CYAN;
1179                     }
1180                     my $src = $b_lines_ref->{$line};
1181                     print $color, "$src\n", RESET;
1182                 }
1183                 else
1184                 {
1185                     my $src = $b_lines_ref->{$line};
1186                     print "$src\n";
1187                 }
1188             }
1189         }
1190     }
1191 }
1192
1193 #
1194 #
1195 sub patch_html_output
1196 {
1197     my $filesref = shift;
1198
1199     open( my $filehandle, ">", $opt_output ) || die "Can't open $opt_output for writing:$!\n";
1200
1201     my $OUTPUT_FH = select;
1202     select $filehandle;
1203     print <<EOH;
1204 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
1205 "http://www.w3.org/TR/REC-html40/loose.dtd">
1206 <html>
1207 <head>
1208 <title>Patch Coverage</title>
1209 </head>
1210 <body bgcolor="white">
1211 EOH
1212
1213     foreach my $file (keys(%$filesref))
1214     {
1215         my ($name, $path, $suffix) = fileparse($file, qr{\.[^.]*$});
1216         next if($path !~ /^dali/);
1217
1218         my $fileref = $filesref->{$file};
1219         my $patchref = $fileref->{"patch"};
1220         my $b_lines_ref = $fileref->{"b_lines"};
1221
1222         my $abs_filename = File::Spec->rel2abs($file, $root);
1223         my $sumcountref = $info_data{$abs_filename}->{"sum"};
1224
1225         print "<h2>$file</h2>\n";
1226
1227         if($fileref)
1228         {
1229             if( $fileref->{"covered_lines"} > 0
1230                 ||
1231                 $fileref->{"uncovered_lines"} > 0 )
1232             {
1233                 print "<p style=\"color:green;\">Covered: " .
1234                     $fileref->{"covered_lines"} . "<p>" .
1235                     "<p style=\"color:red;\">Uncovered: " .
1236                     $fileref->{"uncovered_lines"} . "</span></p>";
1237             }
1238         }
1239         else
1240         {
1241             print "<p>";
1242             my $span=0;
1243             if($suffix eq ".cpp" || $suffix eq ".c" || $suffix eq ".h")
1244             {
1245                 print "<span style=\"color:red;\">";
1246                 $span=1;
1247             }
1248             print "No coverage found";
1249             print "</span>" if $span;
1250         }
1251         print "</p>";
1252
1253         for my $patch (@$patchref)
1254         {
1255             my $hunkstr="Hunk: " . $patch->[0];
1256             if( $patch->[1] > 1 )
1257             {
1258                 $hunkstr .= " - " . ($patch->[0]+$patch->[1]-1);
1259             }
1260             print "<p style=\"font-weight:bold;\">" . $hunkstr . "</p>";
1261
1262             print "<pre>";
1263             for(my $i = 0; $i < $patch->[1]; $i++ )
1264             {
1265                 my $line = $i + $patch->[0];
1266                 my $num_line_digits=log($line)/log(10);
1267                 for $i (0..(6-$num_line_digits-1))
1268                 {
1269                     print " ";
1270                 }
1271                 print "$line  ";
1272
1273                 if($sumcountref)
1274                 {
1275                     my $color;
1276                     if(exists($sumcountref->{$line}))
1277                     {
1278                         if($sumcountref->{$line} > 0)
1279                         {
1280                             print("<span style=\"color:green;\">");
1281                         }
1282                         else
1283                         {
1284                             print("<span style=\"color:red;font-weight:bold;\">");
1285                         }
1286                     }
1287                     else
1288                     {
1289                         print("<span style=\"color:black;font-weight:normal;\">");
1290                     }
1291                     my $src=$b_lines_ref->{$line};
1292                     chomp($src);
1293                     print "$src</span>\n";
1294                 }
1295                 else
1296                 {
1297                     my $src = $b_lines_ref->{$line};
1298                     print "$src\n";
1299                 }
1300             }
1301             print "<\pre>\n";
1302         }
1303     }
1304
1305     print $filehandle "<hr>\n</body>\n</html>\n";
1306     close $filehandle;
1307     select $OUTPUT_FH;
1308 }
1309
1310
1311 ################################################################################
1312 ##                                    MAIN                                    ##
1313 ################################################################################
1314
1315 my $cwd = getcwd(); # expect this to be automated-tests folder
1316
1317 # execute coverage.sh, generating build/tizen/dali.info from lib, and
1318 # *.dir/dali.info. Don't generate html
1319 print `./coverage.sh -n`;
1320 chdir "..";
1321 $root = getcwd();
1322
1323 our %info_data; # Hash of all data from .info files
1324 my @info_files = split(/\n/, `find . -name dali.info`);
1325 my %new_info;
1326
1327 # Read in all specified .info files
1328 foreach (@info_files)
1329 {
1330     %new_info = %{read_info_file($_)};
1331
1332     # Combine %new_info with %info_data
1333     %info_data = %{combine_info_files(\%info_data, \%new_info)};
1334 }
1335
1336
1337 # Generate git diff command
1338 my @cmd=('--no-pager','diff','--no-ext-diff','-U0','--no-color');
1339 my $status = $repo->command("status", "-s");
1340
1341 if( $status eq "" && !scalar(@ARGV))
1342 {
1343     # There are no changes in the index or working tree, and
1344     # no diff arguments to append. Use the last patch instead.
1345     push @cmd, ('HEAD~1','HEAD');
1346 }
1347 else
1348 {
1349     # detect if there are only cached changes or only working tree changes
1350     my $cached = 0;
1351     my $working = 0;
1352     for my $fstat ( split(/\n/, $status) )
1353     {
1354         if(substr( $fstat, 0, 1 ) ne " "){ $cached++; }
1355         if(substr( $fstat, 1, 1 ) ne " "){ $working++; }
1356     }
1357     if($cached > 0 )
1358     {
1359         if($working == 0)
1360         {
1361             push @cmd, "--cached";
1362         }
1363         else
1364         {
1365             die "Both cached & working files - cannot get correct patch from git\n";
1366             # Would have to diff from separate clone.
1367         }
1368     }
1369 }
1370
1371 push @cmd, @ARGV;
1372
1373 # Execute diff & coverage from root directory
1374 my $filesref = run_diff(@cmd);
1375
1376 chdir $cwd;
1377
1378 # Check how many actual source files there are in the patch
1379 my $filecount = 0;
1380 foreach my $file (keys(%$filesref))
1381 {
1382     my ($name, $path, $suffix) = fileparse($file, qr{\.[^.]*$});
1383     next if($path !~ /^dali/);
1384     next if($suffix ne ".cpp" && $suffix ne ".c" && $suffix ne ".h");
1385     $filecount++;
1386 }
1387 if( $filecount == 0 )
1388 {
1389     print "No source files found\n";
1390     exit 0;    # Exit with no error.
1391 }
1392
1393 #print_simplified_info() if $debug;
1394 #exit 0;
1395
1396 my $percentref = calc_patch_coverage_percentage($filesref);
1397 if($percentref->[0] == 0)
1398 {
1399     print "No coverable lines found\n";
1400     exit 0;
1401 }
1402 my $percent = $percentref->[1];
1403
1404 my $color=BOLD RED;
1405 if($opt_output)
1406 {
1407     print "Outputing to $opt_output\n" if $debug;
1408     patch_html_output($filesref);
1409 }
1410 elsif( ! $opt_quiet )
1411 {
1412     patch_output($filesref);
1413     if($percent>=90)
1414     {
1415         $color=GREEN;
1416     }
1417     print RESET;
1418 }
1419
1420 printf("Line Coverage: %d/%d\n", $percentref->[2], $percentref->[0]);
1421 printf("Percentage of change covered: %5.2f%\n", $percent);
1422
1423 exit($percent<90);
1424
1425
1426 __END__
1427
1428 =head1 NAME
1429
1430 patch-coverage
1431
1432 =head1 SYNOPSIS
1433
1434 patch-coverage.pl - Determine if patch coverage is below 90%
1435
1436 =head1 DESCRIPTION
1437 Calculates how well the most recent patch is covered (either the
1438 patch that is in the index+working directory, or HEAD).
1439
1440 =head1 OPTIONS
1441
1442 =over 28
1443
1444 =item B<-c|--cached>
1445 Use index files if there is nothing in the working tree
1446
1447 =item B<   --help>
1448 This help
1449
1450 =item B<-q|--quiet>
1451 Don't generate any output
1452
1453 =head1 RETURN STATUS
1454 0 if the coverage of source files is > 90%, otherwise 1
1455
1456 =head1 EXAMPLES
1457
1458
1459 =cut