From 1c42cf2c535e89d1e07798273ba7eb48eb40d89f Mon Sep 17 00:00:00 2001 From: Valentin Kiselev Date: Mon, 13 Feb 2023 16:46:22 +0300 Subject: [PATCH] feat: add gcov format support --- .ameba.yml | 1 + spec/coverage_reporter/parser_spec.cr | 2 +- .../parsers/gcov_parser_spec.cr | 34 +++++++++ .../parsers/lcov_parser_spec.cr | 30 ++++---- spec/fixtures/gcov/.gitignore | 4 ++ spec/fixtures/gcov/Makefile | 5 ++ spec/fixtures/gcov/main.c | 20 ++++++ spec/fixtures/gcov/main.c.gcov | 24 +++++++ src/coverage_reporter/file_report.cr | 10 +-- src/coverage_reporter/parser.cr | 1 + src/coverage_reporter/parsers/base_parser.cr | 8 +++ src/coverage_reporter/parsers/gcov_parser.cr | 72 +++++++++++++++++++ src/coverage_reporter/parsers/lcov_parser.cr | 1 + .../parsers/simplecov_parser.cr | 1 + 14 files changed, 193 insertions(+), 20 deletions(-) create mode 100644 spec/coverage_reporter/parsers/gcov_parser_spec.cr create mode 100644 spec/fixtures/gcov/.gitignore create mode 100644 spec/fixtures/gcov/Makefile create mode 100644 spec/fixtures/gcov/main.c create mode 100644 spec/fixtures/gcov/main.c.gcov create mode 100644 src/coverage_reporter/parsers/gcov_parser.cr diff --git a/.ameba.yml b/.ameba.yml index 2fa48202..df2f88a1 100644 --- a/.ameba.yml +++ b/.ameba.yml @@ -10,5 +10,6 @@ Metrics/CyclomaticComplexity: MaxComplexity: 10 Excluded: - src/coverage_reporter/parsers/lcov_parser.cr + - src/coverage_reporter/parsers/gcov_parser.cr Enabled: true Severity: Warning diff --git a/spec/coverage_reporter/parser_spec.cr b/spec/coverage_reporter/parser_spec.cr index 7e4c381f..c48d4455 100644 --- a/spec/coverage_reporter/parser_spec.cr +++ b/spec/coverage_reporter/parser_spec.cr @@ -18,7 +18,7 @@ Spectator.describe CoverageReporter::Parser do it "returns reports for all files" do reports = subject.parse - expect(reports.size).to eq 6 + expect(reports.size).to eq 7 end end end diff --git a/spec/coverage_reporter/parsers/gcov_parser_spec.cr b/spec/coverage_reporter/parsers/gcov_parser_spec.cr new file mode 100644 index 00000000..8e1bdf4f --- /dev/null +++ b/spec/coverage_reporter/parsers/gcov_parser_spec.cr @@ -0,0 +1,34 @@ +require "../../spec_helper" + +Spectator.describe CoverageReporter::GcovParser do + subject { described_class.new(base_path) } + + let(base_path) { nil } + + describe "#parse" do + let(filename) { "spec/fixtures/gcov/main.c.gcov" } + + it "parses gcov" do + result = subject.parse(filename) + + expect(result[0].to_h).to eq({ + :name => "main.c", + :coverage => [nil, nil, 2, nil, 2, 1, 1, 1, nil, 0, nil, 2, nil, nil, 1, nil, 1, 1, 1, nil], + }) + end + + context "with base_path" do + let(base_path) { "spec/fixtures/gcov" } + + it "parses gcov with source digest" do + result = subject.parse(filename) + + expect(result[0].to_h).to eq({ + :name => "spec/fixtures/gcov/main.c", + :coverage => [nil, nil, 2, nil, 2, 1, 1, 1, nil, 0, nil, 2, nil, nil, 1, nil, 1, 1, 1, nil], + :source_digest => "da803fdb1b06abe64c3b806d861a5baa", + }) + end + end + end +end diff --git a/spec/coverage_reporter/parsers/lcov_parser_spec.cr b/spec/coverage_reporter/parsers/lcov_parser_spec.cr index 7b8a6db5..7e95e6c4 100644 --- a/spec/coverage_reporter/parsers/lcov_parser_spec.cr +++ b/spec/coverage_reporter/parsers/lcov_parser_spec.cr @@ -20,17 +20,15 @@ Spectator.describe CoverageReporter::LcovParser do describe "#parse" do let(filename) { "spec/fixtures/test.lcov" } let(coverage) do - [ - 1, 1, 1, nil, 1, 66, 66, nil, nil, 1, 323, 63, 63, 63, 60, nil, 3, nil, 63, 32, - nil, 63, 63, 63, 3, nil, 63, 60, nil, 3, nil, 63, 27, 27, 27, nil, nil, 323, nil, - nil, 1, 5, 5, nil, 2, nil, nil, 3, 3, 1, 1, 1, 1, nil, 0, nil, nil, 2, 2, 2, 0, - nil, 2, nil, nil, 2, 2, nil, nil, nil, nil, 1, 1, 1, 1, 1, 1, nil, 1, 1, nil, 1, - 87, 87, 6, 6, 6, 6, 9, 6, 6, nil, 81, nil, nil, nil, 1, 1, nil, nil, 1, 1, nil, - nil, 1, nil, 2, 2, 2, 1, nil, 2, nil, nil, 1, 3, 1, 1, nil, nil, 2, 2, 2, nil, - 1, 1, nil, 2, nil, nil, nil, 1, 1, nil, nil, 1, 50, 50, 50, 50, 20, nil, 50, 50, - 2, nil, 50, 50, 50, 31, nil, 50, 24, nil, 50, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - ] of Int64? + [1, 1, 1, nil, 1, 66, 66, nil, nil, 1, 323, 63, 63, 63, 60, nil, 3, nil, 63, 32, + nil, 63, 63, 63, 3, nil, 63, 60, nil, 3, nil, 63, 27, 27, 27, nil, nil, 323, nil, + nil, 1, 5, 5, nil, 2, nil, nil, 3, 3, 1, 1, 1, 1, nil, 0, nil, nil, 2, 2, 2, 0, + nil, 2, nil, nil, 2, 2, nil, nil, nil, nil, 1, 1, 1, 1, 1, 1, nil, 1, 1, nil, 1, + 87, 87, 6, 6, 6, 6, 9, 6, 6, nil, 81, nil, nil, nil, 1, 1, nil, nil, 1, 1, nil, + nil, 1, nil, 2, 2, 2, 1, nil, 2, nil, nil, 1, 3, 1, 1, nil, nil, 2, 2, 2, nil, 1, + 1, nil, 2, nil, nil, nil, 1, 1, nil, nil, 1, 50, 50, 50, 50, 20, nil, 50, 50, 2, + nil, 50, 50, 50, 31, nil, 50, 24, nil, 50, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, nil] of Int64? end it "parses correctly" do @@ -38,8 +36,9 @@ Spectator.describe CoverageReporter::LcovParser do expect(reports.size).to eq 1 expect(reports[0].to_h).to eq({ - :name => "spec/fixtures/test.js", - :coverage => coverage, + :name => "spec/fixtures/test.js", + :coverage => coverage, + :source_digest => "6e7aea5aa7198489561a44359dc7e1a4", }) end @@ -52,8 +51,9 @@ Spectator.describe CoverageReporter::LcovParser do expect(reports.size).to eq 1 expect(reports[0].to_h).to eq({ - :name => "spec/fixtures/test.js", - :coverage => coverage, + :name => "spec/fixtures/test.js", + :coverage => coverage, + :source_digest => "6e7aea5aa7198489561a44359dc7e1a4", }) end end diff --git a/spec/fixtures/gcov/.gitignore b/spec/fixtures/gcov/.gitignore new file mode 100644 index 00000000..979f7da0 --- /dev/null +++ b/spec/fixtures/gcov/.gitignore @@ -0,0 +1,4 @@ +main +main.o +main.gcda +main.gcno diff --git a/spec/fixtures/gcov/Makefile b/spec/fixtures/gcov/Makefile new file mode 100644 index 00000000..c689d7e3 --- /dev/null +++ b/spec/fixtures/gcov/Makefile @@ -0,0 +1,5 @@ +coverage: + gcc -fPIC -fprofile-arcs -ftest-coverage -c -Wall -Werror main.c + gcc -fPIC -fprofile-arcs -ftest-coverage -o main main.o + ./main + gcov main.c diff --git a/spec/fixtures/gcov/main.c b/spec/fixtures/gcov/main.c new file mode 100644 index 00000000..960a8146 --- /dev/null +++ b/spec/fixtures/gcov/main.c @@ -0,0 +1,20 @@ +#include + +void numbers(int num) +{ + if (num == 1) { + printf("Number is 1\n"); + } else if (num == 2){ + printf("Number is 2\n"); + } else { + printf("Number is %d\n", num); + } +} + + +int main(void) +{ + numbers(1); + numbers(2); + return 0; +} diff --git a/spec/fixtures/gcov/main.c.gcov b/spec/fixtures/gcov/main.c.gcov new file mode 100644 index 00000000..f9de948b --- /dev/null +++ b/spec/fixtures/gcov/main.c.gcov @@ -0,0 +1,24 @@ + -: 0:Source:main.c + -: 0:Graph:main.gcno + -: 0:Data:main.gcda + -: 0:Runs:1 + -: 1:#include + -: 2: + 2: 3:void numbers(int num) + -: 4:{ + 2: 5: if (num == 1) { + 1: 6: printf("Number is 1\n"); + 1: 7: } else if (num == 2){ + 1: 8: printf("Number is 2\n"); + -: 9: } else { + #####: 10: printf("Number is %d\n", num); + -: 11: } + 2: 12:} + -: 13: + -: 14: + 1: 15:int main(void) + -: 16:{ + 1: 17: numbers(1); + 1: 18: numbers(2); + 1: 19: return 0; + -: 20:} diff --git a/src/coverage_reporter/file_report.cr b/src/coverage_reporter/file_report.cr index 911c5b82..51df0c2c 100644 --- a/src/coverage_reporter/file_report.cr +++ b/src/coverage_reporter/file_report.cr @@ -6,15 +6,17 @@ module CoverageReporter def initialize( @name : String, @coverage : Array(Int64?), - @branches : Array(Int64?) | Array(Int64) | Nil = nil + @branches : Array(Int64?) | Array(Int64) | Nil = nil, + @source_digest : String | Nil = nil ) end def to_h : Hash(Symbol, String | Array(Int64?) | Array(Int64)) { - :name => @name, - :coverage => @coverage, - :branches => @branches, + :name => @name, + :coverage => @coverage, + :branches => @branches, + :source_digest => @source_digest, }.compact end end diff --git a/src/coverage_reporter/parser.cr b/src/coverage_reporter/parser.cr index 6ba51d39..c4a0d83c 100644 --- a/src/coverage_reporter/parser.cr +++ b/src/coverage_reporter/parser.cr @@ -16,6 +16,7 @@ module CoverageReporter LcovParser, SimplecovParser, CoberturaParser, + GcovParser, } def initialize(@file : String?, base_path : String?) diff --git a/src/coverage_reporter/parsers/base_parser.cr b/src/coverage_reporter/parsers/base_parser.cr index 3b29e79a..e8b3027f 100644 --- a/src/coverage_reporter/parsers/base_parser.cr +++ b/src/coverage_reporter/parsers/base_parser.cr @@ -1,4 +1,5 @@ require "../file_report" +require "digest" module CoverageReporter # Coverage report parser interface. @@ -35,6 +36,13 @@ module CoverageReporter # # Existing parsers can be used as a reference. abstract class BaseParser + # Returns MD5 hashsum of a file. + def self.file_digest(filename : String) : String | Nil + return unless File.exists?(filename) + + Digest::MD5.hexdigest(File.read(filename)) + end + # Initializes the parser. # # *base_path* can be used to join with all paths in coverage report in order diff --git a/src/coverage_reporter/parsers/gcov_parser.cr b/src/coverage_reporter/parsers/gcov_parser.cr new file mode 100644 index 00000000..e9918d25 --- /dev/null +++ b/src/coverage_reporter/parsers/gcov_parser.cr @@ -0,0 +1,72 @@ +require "./base_parser" +require "digest" + +module CoverageReporter + class GcovParser < BaseParser + # Use *base_path* to join with paths found in reports. + def initialize(@base_path : String?) + end + + def globs : Array(String) + [ + "*.gcov", + "**/*/*.gcov", + ] + end + + def matches?(filename : String) : Bool + filename.ends_with?(".gcov") + end + + def parse(filename : String) : Array(FileReport) + base_path = @base_path + coverage = {} of Int64 => Int64? + name : String? = nil + source_digest : String? = nil + File.each_line(filename, chomp: true) do |line| + match = /^\s*([0-9]+|-|#####):\s*([0-9]+):(.*)/.match(line).try(&.to_a) + next if !match || !match.try(&.size) == 4 + + count, number, text = match[1..3] + next unless number && text && count + + number = number.to_i64 + + if number == 0 + match = /([^:]+):(.*)$/.match(text).try(&.to_a) + next if !match || match.try(&.size) < 2 + + key, val = match[1..2] + if key == "Source" && val + val = base_path ? File.join(base_path, val) : val + name = val.sub(Dir.current, "") + source_digest = BaseParser.file_digest(val) + end + else + coverage[number - 1] = case count.strip + when "-" + nil + when "#####" + if text.strip == "}" + nil + else + 0.to_i64 + end + else + count.to_i64 + end + end + end + + return [] of FileReport unless name + + [ + FileReport.new( + name: name, + coverage: coverage.keys.sort!.map { |i| coverage[i]? }, + source_digest: source_digest, + ), + ] + end + end +end diff --git a/src/coverage_reporter/parsers/lcov_parser.cr b/src/coverage_reporter/parsers/lcov_parser.cr index 4ec52746..69be11f5 100644 --- a/src/coverage_reporter/parsers/lcov_parser.cr +++ b/src/coverage_reporter/parsers/lcov_parser.cr @@ -99,6 +99,7 @@ module CoverageReporter name: filename.sub(Dir.current, ""), coverage: coverage, branches: branches, + source_digest: BaseParser.file_digest(filename), ) end end diff --git a/src/coverage_reporter/parsers/simplecov_parser.cr b/src/coverage_reporter/parsers/simplecov_parser.cr index dd1ae7e8..62f1ce12 100644 --- a/src/coverage_reporter/parsers/simplecov_parser.cr +++ b/src/coverage_reporter/parsers/simplecov_parser.cr @@ -55,6 +55,7 @@ module CoverageReporter name: name.sub(Dir.current, ""), coverage: coverage, branches: branches, + source_digest: BaseParser.file_digest(name), ) ) end