coverage-reporter 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/coverage_reporter/cli.rb +3 -4
- data/lib/coverage_reporter/collate_runner.rb +24 -0
- data/lib/coverage_reporter/coverage_collator.rb +31 -13
- data/lib/coverage_reporter/inline_comment_poster.rb +52 -5
- data/lib/coverage_reporter/modified_files_extractor.rb +49 -0
- data/lib/coverage_reporter/options/collate.rb +36 -4
- data/lib/coverage_reporter/pull_request.rb +14 -0
- data/lib/coverage_reporter/{runner.rb → report_runner.rb} +1 -1
- data/lib/coverage_reporter/uncovered_ranges_extractor.rb +39 -4
- data/lib/coverage_reporter/version.rb +1 -1
- data/lib/coverage_reporter.rb +3 -1
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '084368a5d340a21403c9f358fbbd599cd48dc48dd429aec44a40b6eddfec5b4f'
|
|
4
|
+
data.tar.gz: 4f9f847624fe3f1a5692f757d316254f41eee376643bfb8f790edac4f89a228d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e6aca8f9a84ff71d7570967e115ae2e17652e9c0514f18396f21a069222e30f7343ea7b9f11522a932f62edf1373ef081491a232ed6b8ebabe0c87fd868022cb
|
|
7
|
+
data.tar.gz: 1ffb8d0777fce695e3d32833d9b221c6749c8d59951afb45625a167ab755377c16519848f578037fca5f5238d081dd313bbe81edd4ff52050ea55f443e57cfd0
|
|
@@ -9,12 +9,11 @@ module CoverageReporter
|
|
|
9
9
|
when nil
|
|
10
10
|
show_usage_and_exit
|
|
11
11
|
when "report"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
Runner.new(options).run
|
|
12
|
+
report_options = Options::Report.parse(argv[1..])
|
|
13
|
+
ReportRunner.new(report_options).run
|
|
15
14
|
when "collate"
|
|
16
15
|
collate_options = Options::Collate.parse(argv[1..])
|
|
17
|
-
|
|
16
|
+
CollateRunner.new(collate_options).run
|
|
18
17
|
else
|
|
19
18
|
show_unknown_command_error(argv.first)
|
|
20
19
|
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CoverageReporter
|
|
4
|
+
class CollateRunner
|
|
5
|
+
def initialize(options)
|
|
6
|
+
@coverage_dir = options[:coverage_dir]
|
|
7
|
+
@modified_only = options[:modified_only]
|
|
8
|
+
@github_token = options[:github_token]
|
|
9
|
+
@repo = options[:repo]
|
|
10
|
+
@pr_number = options[:pr_number]
|
|
11
|
+
@working_dir = options[:working_dir]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run
|
|
15
|
+
pull_request = PullRequest.new(github_token:, repo:, pr_number:)
|
|
16
|
+
filenames = modified_only ? ModifiedFilesExtractor.new(pull_request.diff).call : []
|
|
17
|
+
CoverageCollator.new(coverage_dir:, filenames:, working_dir:).call
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
attr_reader :coverage_dir, :modified_only, :github_token, :repo, :pr_number, :working_dir
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -4,8 +4,11 @@ module CoverageReporter
|
|
|
4
4
|
class CoverageCollator
|
|
5
5
|
def initialize(options={})
|
|
6
6
|
@coverage_dir = options[:coverage_dir]
|
|
7
|
+
@filenames = options[:filenames]
|
|
8
|
+
@working_dir = options[:working_dir]
|
|
7
9
|
end
|
|
8
10
|
|
|
11
|
+
# rubocop:disable Metrics/AbcSize
|
|
9
12
|
def call
|
|
10
13
|
require "simplecov"
|
|
11
14
|
require "simplecov_json_formatter"
|
|
@@ -13,25 +16,40 @@ module CoverageReporter
|
|
|
13
16
|
require "coverage_reporter/simple_cov/patches/result_hash_formatter_patch"
|
|
14
17
|
|
|
15
18
|
# Collate JSON coverage reports and generate both HTML and JSON outputs
|
|
16
|
-
|
|
17
|
-
abort "No coverage JSON files found to collate" if
|
|
18
|
-
|
|
19
|
-
puts "Collate coverage files: #{
|
|
20
|
-
|
|
21
|
-
::SimpleCov.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
]
|
|
27
|
-
)
|
|
19
|
+
coverage_files = Dir["#{coverage_dir}/resultset-*.json"]
|
|
20
|
+
abort "No coverage JSON files found to collate" if coverage_files.empty?
|
|
21
|
+
|
|
22
|
+
puts "Collate coverage files: #{coverage_files.join(', ')}"
|
|
23
|
+
|
|
24
|
+
::SimpleCov.root(working_dir) if working_dir
|
|
25
|
+
|
|
26
|
+
::SimpleCov.collate(coverage_files) do
|
|
27
|
+
add_filter(build_filter) if filenames.any? && working_dir
|
|
28
|
+
formatter(build_formatter)
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
puts "✅ Coverage merged and report generated."
|
|
31
32
|
end
|
|
33
|
+
# rubocop:enable Metrics/AbcSize
|
|
32
34
|
|
|
33
35
|
private
|
|
34
36
|
|
|
35
|
-
attr_reader :coverage_dir
|
|
37
|
+
attr_reader :coverage_dir, :filenames, :working_dir
|
|
38
|
+
|
|
39
|
+
def build_formatter
|
|
40
|
+
::SimpleCov::Formatter::MultiFormatter.new(
|
|
41
|
+
[
|
|
42
|
+
::SimpleCov::Formatter::JSONFormatter,
|
|
43
|
+
::SimpleCov::Formatter::HypertextFormatter
|
|
44
|
+
]
|
|
45
|
+
)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def build_filter
|
|
49
|
+
lambda do |src_file|
|
|
50
|
+
normalized_filename = src_file.filename.gsub(working_dir, "").gsub(%r{^/}, "")
|
|
51
|
+
filenames.none?(normalized_filename)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
36
54
|
end
|
|
37
55
|
end
|
|
@@ -80,12 +80,59 @@ module CoverageReporter
|
|
|
80
80
|
|
|
81
81
|
def existing_comment_for_path_and_lines(path, start_line, line)
|
|
82
82
|
existing_coverage_comments.values.find do |comment|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
next false unless comment.path == path
|
|
84
|
+
|
|
85
|
+
# Check if line numbers match
|
|
86
|
+
line_numbers_match = if line == start_line
|
|
87
|
+
comment.line == line
|
|
88
|
+
else
|
|
89
|
+
comment.start_line == start_line && comment.line == line
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
next false unless line_numbers_match
|
|
93
|
+
|
|
94
|
+
# Check if the content of the lines has changed
|
|
95
|
+
content_matches?(comment, path, start_line, line)
|
|
88
96
|
end
|
|
89
97
|
end
|
|
98
|
+
|
|
99
|
+
def content_matches?(existing_comment, path, start_line, line)
|
|
100
|
+
# Extract commit SHA from existing comment body
|
|
101
|
+
existing_commit_sha = extract_commit_sha_from_comment(existing_comment.body)
|
|
102
|
+
return false unless existing_commit_sha
|
|
103
|
+
|
|
104
|
+
# If commit SHA matches current commit, content is the same
|
|
105
|
+
return true if existing_commit_sha == commit_sha
|
|
106
|
+
|
|
107
|
+
# Get file content at both commits
|
|
108
|
+
existing_content = pull_request.file_content(path: path, commit_sha: existing_commit_sha)
|
|
109
|
+
current_content = pull_request.file_content(path: path, commit_sha: commit_sha)
|
|
110
|
+
|
|
111
|
+
return false unless existing_content && current_content
|
|
112
|
+
|
|
113
|
+
# Compare the lines at the specified range
|
|
114
|
+
existing_lines = extract_lines(existing_content, start_line, line)
|
|
115
|
+
current_lines = extract_lines(current_content, start_line, line)
|
|
116
|
+
|
|
117
|
+
existing_lines == current_lines
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def extract_commit_sha_from_comment(comment_body)
|
|
121
|
+
return nil unless comment_body
|
|
122
|
+
|
|
123
|
+
# Extract commit SHA from format: "_Commit: abc123_"
|
|
124
|
+
match = comment_body.match(/_Commit:\s*([a-f0-9]+)_/i)
|
|
125
|
+
match ? match[1] : nil
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def extract_lines(content, start_line, line)
|
|
129
|
+
lines = content.lines
|
|
130
|
+
# Convert to 0-based index
|
|
131
|
+
start_idx = start_line - 1
|
|
132
|
+
end_idx = line - 1
|
|
133
|
+
|
|
134
|
+
# Return the lines in the range (inclusive)
|
|
135
|
+
lines[start_idx..end_idx] || []
|
|
136
|
+
end
|
|
90
137
|
end
|
|
91
138
|
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "set"
|
|
4
|
+
|
|
5
|
+
module CoverageReporter
|
|
6
|
+
# Extracts a list of modified files from diff text
|
|
7
|
+
class ModifiedFilesExtractor
|
|
8
|
+
def initialize(diff_text)
|
|
9
|
+
@diff_text = diff_text
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call
|
|
13
|
+
return [] unless @diff_text
|
|
14
|
+
|
|
15
|
+
parse_diff(@diff_text)
|
|
16
|
+
rescue StandardError => e
|
|
17
|
+
puts "Warning: Could not parse diff text: #{e.message}"
|
|
18
|
+
[]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def parse_diff(text)
|
|
24
|
+
modified_files = Set.new
|
|
25
|
+
|
|
26
|
+
text.each_line do |line|
|
|
27
|
+
if file_header_line?(line)
|
|
28
|
+
file_path = parse_file_path(line)
|
|
29
|
+
modified_files.add(file_path) if file_path
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
modified_files.to_a.sort
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def file_header_line?(line)
|
|
37
|
+
line.start_with?("+++ ")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def parse_file_path(line)
|
|
41
|
+
return nil if line.end_with?(File::NULL)
|
|
42
|
+
|
|
43
|
+
line = line.chomp
|
|
44
|
+
if (m = line.match(%r{\A\+\+\+\s[wb]/(.+)\z}))
|
|
45
|
+
m[1]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -7,26 +7,58 @@ module CoverageReporter
|
|
|
7
7
|
class Collate < Base
|
|
8
8
|
def self.defaults
|
|
9
9
|
{
|
|
10
|
-
coverage_dir:
|
|
10
|
+
coverage_dir: "coverage",
|
|
11
|
+
modified_only: false,
|
|
12
|
+
github_token: ENV.fetch("GITHUB_TOKEN", nil),
|
|
13
|
+
repo: normalize_repo(ENV.fetch("REPO", nil)),
|
|
14
|
+
pr_number: ENV.fetch("PR_NUMBER", nil),
|
|
15
|
+
working_dir: nil
|
|
11
16
|
}
|
|
12
17
|
end
|
|
13
18
|
|
|
14
19
|
def self.parse(argv)
|
|
15
20
|
opts = defaults.dup
|
|
21
|
+
parser = build_parser(opts)
|
|
22
|
+
parser.parse!(argv)
|
|
23
|
+
opts
|
|
24
|
+
end
|
|
16
25
|
|
|
17
|
-
|
|
26
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
27
|
+
def self.build_parser(opts)
|
|
28
|
+
OptionParser.new do |o|
|
|
18
29
|
o.banner = "Usage: coverage-reporter collate [options]"
|
|
19
30
|
o.on("--coverage-dir DIR", "Directory containing coverage files (default: coverage)") do |v|
|
|
20
31
|
opts[:coverage_dir] = v
|
|
21
32
|
end
|
|
33
|
+
o.on("--modified-only", "Filter to only modified files") do
|
|
34
|
+
opts[:modified_only] = true
|
|
35
|
+
end
|
|
36
|
+
o.on("--github-token TOKEN", "GitHub token") do |v|
|
|
37
|
+
opts[:github_token] = v
|
|
38
|
+
end
|
|
39
|
+
o.on("--repo REPO", "Repository") do |v|
|
|
40
|
+
opts[:repo] = v
|
|
41
|
+
end
|
|
42
|
+
o.on("--pr-number PR_NUMBER", "Pull request number") do |v|
|
|
43
|
+
opts[:pr_number] = v
|
|
44
|
+
end
|
|
45
|
+
o.on("--working-dir DIR", "Working directory for coverage files") do |v|
|
|
46
|
+
opts[:working_dir] = v
|
|
47
|
+
end
|
|
22
48
|
o.on_tail("-h", "--help", "Show help") do
|
|
23
49
|
puts o
|
|
24
50
|
exit 0
|
|
25
51
|
end
|
|
26
52
|
end
|
|
53
|
+
end
|
|
54
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
|
27
55
|
|
|
28
|
-
|
|
29
|
-
|
|
56
|
+
def self.normalize_repo(repo)
|
|
57
|
+
return repo if repo.nil? || repo.strip.empty?
|
|
58
|
+
|
|
59
|
+
repo.strip
|
|
60
|
+
.gsub(%r{^(https://github\.com/|git@github\.com:)}, "")
|
|
61
|
+
.gsub(/\.git$/, "")
|
|
30
62
|
end
|
|
31
63
|
end
|
|
32
64
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "base64"
|
|
4
|
+
|
|
3
5
|
module CoverageReporter
|
|
4
6
|
class PullRequest
|
|
5
7
|
def initialize(github_token:, repo:, pr_number:)
|
|
@@ -59,6 +61,18 @@ module CoverageReporter
|
|
|
59
61
|
@diff ||= client.pull_request(repo, pr_number, accept: "application/vnd.github.v3.diff")
|
|
60
62
|
end
|
|
61
63
|
|
|
64
|
+
def file_content(path:, commit_sha:)
|
|
65
|
+
content = client.contents(repo, path: path, ref: commit_sha)
|
|
66
|
+
# GitHub API returns file content as base64-encoded string
|
|
67
|
+
if content.encoding == "base64" && content.content
|
|
68
|
+
Base64.decode64(content.content)
|
|
69
|
+
elsif content.content
|
|
70
|
+
content.content
|
|
71
|
+
end
|
|
72
|
+
rescue Octokit::NotFound, Octokit::Error
|
|
73
|
+
nil
|
|
74
|
+
end
|
|
75
|
+
|
|
62
76
|
private
|
|
63
77
|
|
|
64
78
|
attr_reader :client, :repo, :pr_number
|
|
@@ -33,14 +33,49 @@ module CoverageReporter
|
|
|
33
33
|
return [] unless lines.is_a?(Array)
|
|
34
34
|
|
|
35
35
|
uncovered_lines = []
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
i = 0
|
|
37
|
+
|
|
38
|
+
while i < lines.length
|
|
39
|
+
if lines[i] == 0
|
|
40
|
+
i = process_uncovered_range(lines, uncovered_lines, i)
|
|
41
|
+
else
|
|
42
|
+
i += 1
|
|
43
|
+
end
|
|
40
44
|
end
|
|
45
|
+
|
|
41
46
|
convert_to_ranges(uncovered_lines)
|
|
42
47
|
end
|
|
43
48
|
|
|
49
|
+
def process_uncovered_range(lines, uncovered_lines, start_index)
|
|
50
|
+
i = start_index
|
|
51
|
+
# Found an uncovered line, start a range (always starts with 0)
|
|
52
|
+
uncovered_lines << (i + 1)
|
|
53
|
+
i += 1
|
|
54
|
+
|
|
55
|
+
# Continue through consecutive 0s and nils
|
|
56
|
+
# Include nil only if it's immediately followed by an uncovered line (0)
|
|
57
|
+
continue_uncovered_range(lines, uncovered_lines, i)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def continue_uncovered_range(lines, uncovered_lines, start_index)
|
|
61
|
+
i = start_index
|
|
62
|
+
while i < lines.length
|
|
63
|
+
break unless should_continue_range?(lines, i)
|
|
64
|
+
|
|
65
|
+
uncovered_lines << (i + 1)
|
|
66
|
+
i += 1
|
|
67
|
+
end
|
|
68
|
+
i
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def should_continue_range?(lines, index)
|
|
72
|
+
return true if lines[index] == 0
|
|
73
|
+
return false unless lines[index].nil?
|
|
74
|
+
|
|
75
|
+
# Include nil only if it's immediately followed by an uncovered line (0)
|
|
76
|
+
index + 1 < lines.length && lines[index + 1] == 0
|
|
77
|
+
end
|
|
78
|
+
|
|
44
79
|
def convert_to_ranges(lines)
|
|
45
80
|
return [] if lines.empty?
|
|
46
81
|
|
data/lib/coverage_reporter.rb
CHANGED
|
@@ -41,12 +41,14 @@ module CoverageReporter
|
|
|
41
41
|
require_relative "coverage_reporter/inline_comment"
|
|
42
42
|
require_relative "coverage_reporter/inline_comment_factory"
|
|
43
43
|
require_relative "coverage_reporter/inline_comment_poster"
|
|
44
|
+
require_relative "coverage_reporter/modified_files_extractor"
|
|
44
45
|
require_relative "coverage_reporter/modified_ranges_extractor"
|
|
45
46
|
require_relative "coverage_reporter/options/base"
|
|
46
47
|
require_relative "coverage_reporter/options/collate"
|
|
47
48
|
require_relative "coverage_reporter/options/report"
|
|
48
49
|
require_relative "coverage_reporter/pull_request"
|
|
49
|
-
require_relative "coverage_reporter/
|
|
50
|
+
require_relative "coverage_reporter/report_runner"
|
|
51
|
+
require_relative "coverage_reporter/collate_runner"
|
|
50
52
|
require_relative "coverage_reporter/uncovered_ranges_extractor"
|
|
51
53
|
require_relative "coverage_reporter/simple_cov/patches/result_hash_formatter_patch"
|
|
52
54
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: coverage-reporter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gabriel Taylor Russ
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2025-
|
|
10
|
+
date: 2025-11-17 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: octokit
|
|
@@ -77,6 +77,7 @@ files:
|
|
|
77
77
|
- exe/coverage-reporter
|
|
78
78
|
- lib/coverage_reporter.rb
|
|
79
79
|
- lib/coverage_reporter/cli.rb
|
|
80
|
+
- lib/coverage_reporter/collate_runner.rb
|
|
80
81
|
- lib/coverage_reporter/coverage_analyzer.rb
|
|
81
82
|
- lib/coverage_reporter/coverage_collator.rb
|
|
82
83
|
- lib/coverage_reporter/coverage_report_loader.rb
|
|
@@ -85,12 +86,13 @@ files:
|
|
|
85
86
|
- lib/coverage_reporter/inline_comment.rb
|
|
86
87
|
- lib/coverage_reporter/inline_comment_factory.rb
|
|
87
88
|
- lib/coverage_reporter/inline_comment_poster.rb
|
|
89
|
+
- lib/coverage_reporter/modified_files_extractor.rb
|
|
88
90
|
- lib/coverage_reporter/modified_ranges_extractor.rb
|
|
89
91
|
- lib/coverage_reporter/options/base.rb
|
|
90
92
|
- lib/coverage_reporter/options/collate.rb
|
|
91
93
|
- lib/coverage_reporter/options/report.rb
|
|
92
94
|
- lib/coverage_reporter/pull_request.rb
|
|
93
|
-
- lib/coverage_reporter/
|
|
95
|
+
- lib/coverage_reporter/report_runner.rb
|
|
94
96
|
- lib/coverage_reporter/simple_cov/patches/result_hash_formatter_patch.rb
|
|
95
97
|
- lib/coverage_reporter/uncovered_ranges_extractor.rb
|
|
96
98
|
- lib/coverage_reporter/version.rb
|