[PATCH 11/17] scripts/code_cov_parse_info: add an internal JSON format

Mauro Carvalho Chehab mauro.chehab at linux.intel.com
Thu Feb 15 10:27:20 UTC 2024


From: Mauro Carvalho Chehab <mchehab at kernel.org>

In order to be able to join data from different JSON files, the
JSON format needs to be different than the standard gcov one,
as:

- it should contain test names;
- it should use hashes instead of arrays for functions and lines.

So, at the end of the day, it needs a different format version.
Add support for read/write on a new format.

Signed-off-by: Mauro Carvalho Chehab <mchehab at kernel.org>
---
 scripts/code_cov_parse_info | 222 +++++++++++++++++++++++++++++-------
 1 file changed, 179 insertions(+), 43 deletions(-)

diff --git a/scripts/code_cov_parse_info b/scripts/code_cov_parse_info
index 97c94a81004f..8180b9c1f82e 100644
--- a/scripts/code_cov_parse_info
+++ b/scripts/code_cov_parse_info
@@ -131,9 +131,9 @@ sub parse_json_gcov_v1($$)
 			# Store the record
 			for my $key (keys %$func_ref) {
 				if ($key eq "execution_count") {
-					$record{$source}{func}{$func}{$key} += $func_ref->{$key};
+					$record{files}{$source}{func}{$func}{$key} += $func_ref->{$key};
 				} else {
-					$record{$source}{func}{$func}{$key} = $func_ref->{$key};
+					$record{files}{$source}{func}{$func}{$key} = $func_ref->{$key};
 				}
 			}
 
@@ -177,9 +177,9 @@ sub parse_json_gcov_v1($$)
 				# Branches will be handled in separate
 				next if ($key eq "branches");
 				if ($key eq "count") {
-					$record{$source}{line}{$ln}{$key} += $line_ref->{$key};
+					$record{files}{$source}{line}{$ln}{$key} += $line_ref->{$key};
 				} else {
-					$record{$source}{line}{$ln}{$key} = $line_ref->{$key};
+					$record{files}{$source}{line}{$ln}{$key} = $line_ref->{$key};
 				}
 			}
 			$all_line{$source}{$ln} += $line_ref->{'count'};
@@ -198,17 +198,17 @@ sub parse_json_gcov_v1($$)
 
 				for my $key (keys %$branch_ref) {
 					if ($key eq "count") {
-						$record{$source}{line}{$ln}{branches}[$i]{$key} += $branch_ref->{$key};
+						$record{files}{$source}{line}{$ln}{branches}[$i]{$key} += $branch_ref->{$key};
 					} else {
-						$record{$source}{line}{$ln}{branches}[$i]{$key} = $branch_ref->{$key};
+						$record{files}{$source}{line}{$ln}{branches}[$i]{$key} = $branch_ref->{$key};
 					}
 				}
 
 				$all_branch{$source}{$where}{count} += $taken;
 				$i++;
 			}
-			if (!defined($record{$source}{line}{$ln}{branches})) {
-				@{$record{$source}{line}{$ln}{branches}} = ();
+			if (!defined($record{files}{$source}{line}{$ln}{branches})) {
+				@{$record{files}{$source}{line}{$ln}{branches}} = ();
 			}
 		}
 	}
@@ -217,6 +217,121 @@ sub parse_json_gcov_v1($$)
 	$record{format_version} = "parse_info v1.0";
 }
 
+sub parse_json_internal_format_v1($$)
+{
+	my $file = shift;
+	my $json = shift;
+
+	my $was_used = 0;
+	my $has_func = 0;
+	my $ignore = 0;
+
+	# Store the common JSON data into the record
+	for my $key (keys %$json) {
+		next if ($key eq "files");
+		next if ($key eq "functions");
+		next if ($key eq "lines");
+		# Store any extra data
+		$record{$key} = $json->{$key};
+	}
+
+	for my $test (keys %{$json->{'tests'}}) {
+		$test_names{$test} = 1;
+	}
+
+	for my $source (keys %{$json->{'files'}}) {
+		$files{$source} = 1;
+		next if is_file_excluded($source);
+
+		my $file_ref = \%{$json->{'files'}{$source}};
+
+		# Parse functions
+		for my $func (keys %{$file_ref->{func}}) {
+			next if is_function_excluded($func);
+
+			my $func_ref = \%{$file_ref->{func}{$func}};
+
+			# Store the record
+			for my $key (keys %$func_ref) {
+				if ($key eq "execution_count") {
+					$record{files}{$source}{func}{$func}{$key} += $func_ref->{$key};
+				} else {
+					$record{files}{$source}{func}{$func}{$key} = $func_ref->{$key};
+				}
+			}
+
+			$all_func{$func}{$source}->{ln} = $func_ref->{'start_line'};
+			$all_func{$func}{$source}->{end_ln} = $func_ref->{'end_line'};
+
+			if ($func_ref->{'execution_count'} > 0) {
+				$used_func{$func}{$source}->{count} += $func_ref->{'execution_count'};
+				$was_used = 1;
+			}
+		}
+		next if ($ignore_unused && !$was_used);
+		$used_source{$source} = 1;
+
+		# Parse lines and branches
+		for my $ln (keys %{$file_ref->{line}}) {
+			my $line_ref = \%{$file_ref->{line}{$ln}};
+			my $func = $line_ref->{'function_name'};
+			if (!$func) {
+				# Ignore DA/BRDA that aren't associated with
+				# functions. Those are present on header files
+				# (maybe defines?)
+				next if (@func_include_regexes);
+
+				# Otherwise place them in separate
+				$func = $before_sf;
+			} else {
+				next if is_function_excluded($func);
+			}
+
+			# Store the record
+			for my $key (keys %$line_ref) {
+				next if ($key eq "line_number");
+
+				# Branches will be handled in separate
+				next if ($key eq "branches");
+				if ($key eq "count") {
+					$record{files}{$source}{line}{$ln}{$key} += $line_ref->{$key};
+				} else {
+					$record{files}{$source}{line}{$ln}{$key} = $line_ref->{$key};
+				}
+			}
+			$all_line{$source}{$ln} += $line_ref->{'count'};
+
+			my $i = 0;
+			for my $branch_ref (@{$line_ref->{'branches'}}) {
+				my $taken = $branch_ref->{'count'};
+				my $where = sprintf "%d,%d,%d", $ln, 0, $i;
+
+				# Negative gcov results are possible, as
+				# reported at:
+				# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67937
+				# Lcov ignores those. So, let's do the same
+				# here.
+				$branch_ref->{'count'} = 0 if ($branch_ref->{'count'} < 0);
+
+				for my $key (keys %$branch_ref) {
+					next if (!$record{files}{$source}{line}{$ln}{branches});
+					if ($key eq "count") {
+						$record{files}{$source}{line}{$ln}{branches}[$i]{$key} += $branch_ref->{$key};
+					} else {
+						$record{files}{$source}{line}{$ln}{branches}[$i]{$key} = $branch_ref->{$key};
+					}
+				}
+
+				$all_branch{$source}{$where}{count} += $taken;
+				$i++;
+			}
+			if (!defined($record{files}{$source}{line}{$ln}{branches})) {
+				@{$record{files}{$source}{line}{$ln}{branches}} = ();
+			}
+		}
+	}
+}
+
 sub read_json($)
 {
 	my $file = shift;
@@ -238,6 +353,8 @@ sub read_json($)
 		}
 		if ($json->{'format_version'} eq '1') {
 			parse_json_gcov_v1($file, $json);
+		} elsif ($json->{'format_version'} eq 'parse_info v1.0') {
+			parse_json_internal_format_v1($file, $json);
 		} else {
 			if ($json->{'format_version'}) {
 				die "Can't parse JSON version %d on file $file\n", $json->{'format_version'};
@@ -331,7 +448,7 @@ sub read_info($)
 
 			$skip_func = 0;
 
-			$record{$source}{func}{$func}{start_line} = $ln;
+			$record{files}{$source}{func}{$func}{start_line} = $ln;
 			$all_func{$func}{$source}->{ln} = $ln;
 			next;
 		}
@@ -359,7 +476,7 @@ sub read_info($)
 			$skip_func = 0;
 			$was_used = 1;
 
-			$record{$source}{func}{$func}{fnda} += $count;
+			$record{files}{$source}{func}{$func}{execution_count} += $count;
 			$used_func{$func}{$source}->{count} += $count;
 			next;
 		}
@@ -428,9 +545,9 @@ sub read_info($)
 
 			$was_used = 1 if ($count > 0);
 
-			$record{$source}{line}{$ln}{count} += $count;
-			if (!defined($record{$source}{line}{$ln}{branches})) {
-				@{$record{$source}{line}{$ln}{branches}} = ();
+			$record{files}{$source}{line}{$ln}{count} += $count;
+			if (!defined($record{files}{$source}{line}{$ln}{branches})) {
+				@{$record{files}{$source}{line}{$ln}{branches}} = ();
 			}
 
 			$all_line{$source}{$ln} += $count;
@@ -469,59 +586,74 @@ sub sort_where($$)
 	return $a[2] <=> $b[2];
 }
 
-sub write_filtered_file($)
+sub write_json_file($)
 {
-	my $filter = shift;
+	my $fname = shift;
 
-	my $filtered = "";
+	if (!$has_json_support) {
+		die "Can't parse json as system doesn't have Cpanel::JSON::XS.\n";
+	}
+
+	my $data = eval {
+		Cpanel::JSON::XS::encode_json(\%record)
+	};
+
+	open OUT, ">$fname" or die "Can't open $fname";
+	print OUT $data or die "Failed to write to $fname";
+	close OUT or die "Failed to close to $fname";
+}
+
+sub write_info_file($)
+{
+	my $fname = shift;
+	my $data = "";
 
 	if ($title eq "") {
 		foreach my $testname(sort keys %test_names) {
-			$filtered .= "TN:$testname\n";
+			$data .= "TN:$testname\n";
 		}
 	} else {
-		$filtered .= "TN:$title\n";
+		$data .= "TN:$title\n";
 	}
 
-	# Generates filtered data
-	foreach my $source(sort keys %record) {
+	# Fills $data with the contents to be stored at the file
+	foreach my $source(sort keys %{$record{files}}) {
 		next if (!$used_source{$source});
 
 		if ($source ne $before_sf) {
-			$filtered .= "SF:$source\n";
+			$data .= "SF:$source\n";
 		}
 
-		foreach my $func(sort keys %{ $record{$source}{func} }) {
+		foreach my $func(sort keys %{ $record{files}{$source}{func} }) {
 			if ($func ne $before_sf) {
 				my $fn;
 				my $fnda;
 
-				if (defined($record{$source}{func}{$func}{start_line})) {
-					$filtered .= "FN:" . $record{$source}{func}{$func}{start_line} . ",$func\n";
+				if (defined($record{files}{$source}{func}{$func}{start_line})) {
+					$data .= "FN:" . $record{files}{$source}{func}{$func}{start_line} . ",$func\n";
 				}
-				if (defined($record{$source}{func}{$func}{fnda})) {
-					$filtered .= "FNDA:" . $record{$source}{func}{$func}{fnda} . ",$func\n";
+				if (defined($record{files}{$source}{func}{$func}{execution_count})) {
+					$data .= "FNDA:" . $record{files}{$source}{func}{$func}{execution_count} . ",$func\n";
 				}
 
 			}
 		}
 
-		foreach my $ln(sort { $a <=> $b } keys %{ $record{$source}{line} }) {
-			$filtered .= "DA:$ln," . $record{$source}{line}{$ln}{count} . "\n";
-
-			my $i = 0;
-			for ($i = 0, $i < scalar(@{$record{$source}{line}{$ln}{branches}}), $i++) {
-				my $taken = $record{$source}{line}{$ln}{branches}[$i]{count};
+		foreach my $ln(sort { $a <=> $b } keys %{ $record{files}{$source}{line} }) {
+			$data .= "DA:$ln," . $record{files}{$source}{line}{$ln}{count} . "\n";
+			next if (!$record{files}{$source}{line}{$ln}{branches});
+			for (my $i = 0; $i < scalar @{$record{files}{$source}{line}{$ln}{branches}}; $i++) {
+				my $taken = $record{files}{$source}{line}{$ln}{branches}[$i]{count};
 				$taken = "-" if (!$taken);
-				$filtered .= "BRDA:$ln,0,$i,$taken\n";
+				$data .= "BRDA:$ln,0,$i,$taken\n";
 			}
 		}
 
-		$filtered .= "end_of_record\n";
+		$data .= "end_of_record\n";
 	}
-	open OUT, ">$filter" or die "Can't open $filter";
-	print OUT $filtered or die "Failed to write to $filter";
-	close OUT or die "Failed to close to $filter";
+	open OUT, ">$fname" or die "Can't open $fname";
+	print OUT $data or die "Failed to write to $fname";
+	close OUT or die "Failed to close to $fname";
 }
 
 sub print_code_coverage($$$)
@@ -1120,7 +1252,7 @@ sub check_source_branches()
 my $print_used;
 my $print_unused;
 my $stat;
-my $filter;
+my $output_file;
 my $help;
 my $man;
 my $func_filters;
@@ -1136,7 +1268,7 @@ GetOptions(
 	"print-coverage|print_coverage|print|p" => \$print_used,
 	"print-unused|u" => \$print_unused,
 	"stat|statistics" => \$stat,
-	"output|o=s" => \$filter,
+	"output|o=s" => \$output_file,
 	"verbose|v" => \$verbose,
 	"ignore-unused|ignore_unused" => \$ignore_unused,
 	"only-i915|only_i915" => \$only_i915,
@@ -1169,9 +1301,9 @@ if ($#ARGV < 0) {
 }
 
 # At least one action should be specified
-pod2usage(1) if (!$print_used && !$filter && !$stat && !$print_unused && !$gen_report && !$check_branches);
+pod2usage(1) if (!$print_used && !$output_file && !$stat && !$print_unused && !$gen_report && !$check_branches);
 
-pod2usage(1) if ($gen_report && ($print_used || $filter || $stat || $print_unused));
+pod2usage(1) if ($gen_report && ($print_used || $output_file || $stat || $print_unused));
 
 my $filter_str = "";
 my $has_filter;
@@ -1302,8 +1434,12 @@ if ($show_files) {
 	}
 }
 
-if ($filter) {
-	write_filtered_file($filter);
+if ($output_file) {
+	if ($output_file =~ /.json$/) {
+		write_json_file($output_file);
+	} else {
+		write_info_file($output_file);
+	}
 }
 
 __END__
-- 
2.43.0



More information about the igt-dev mailing list