Skip to content

Commit

Permalink
Merge pull request #538 from danmayer/report_aggregation
Browse files Browse the repository at this point in the history
fixes #467 this is a useful feature for some folks with complex deployments
  • Loading branch information
danmayer authored May 17, 2024
2 parents c92d30a + 7ef8176 commit 59d6d92
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 7 deletions.
4 changes: 3 additions & 1 deletion lib/coverband/adapters/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,9 @@ def merge_expanded_data(new_expanded, old_expanded)

# TODO: This should only be 2 cases get our dup / not dups aligned
def array_add(latest, original)
if Coverband.configuration.use_oneshot_lines_coverage
if latest.empty? && original.empty?
[]
elsif Coverband.configuration.use_oneshot_lines_coverage
latest.map!.with_index { |v, i| ((v + original[i] >= 1) ? 1 : 0) if v && original[i] }
elsif Coverband.configuration.simulate_oneshot_lines_coverage
latest.map.with_index { |v, i| ((v + original[i] >= 1) ? 1 : 0) if v && original[i] }
Expand Down
9 changes: 9 additions & 0 deletions lib/coverband/adapters/redis_store.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ def raw_store
@redis
end

def file_count
data = redis.get type_base_key(Coverband::RUNTIME_TYPE)
JSON.parse(data).keys.length
end

def cached_file_count
@cached_file_count ||= file_count
end

private

attr_reader :redis
Expand Down
38 changes: 36 additions & 2 deletions lib/coverband/reporters/json_report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@
module Coverband
module Reporters
class JSONReport < Base
attr_accessor :filtered_report_files, :options, :page, :as_report, :store, :filename, :base_path
attr_accessor :filtered_report_files, :options, :page, :as_report, :store, :filename, :base_path, :line_coverage,
:for_merged_report

def initialize(store, options = {})
self.options = options
self.page = options.fetch(:page) { nil }
self.filename = options.fetch(:filename) { nil }
self.as_report = options.fetch(:as_report) { false }
self.line_coverage = options.fetch(:line_coverage) { false }
self.for_merged_report = options.fetch(:for_merged_report) { false }
self.base_path = options.fetch(:base_path) { "./" }
self.store = store

Expand All @@ -28,6 +31,33 @@ def report
report_as_json
end

def merge_reports(first_report, second_report, options = {})
merged_data = {}
merged_data[Coverband::RUNTIME_TYPE.to_s] = Coverband::Adapters::Base.new.send(
:merge_reports,
first_report[Coverband::RUNTIME_TYPE.to_s],
second_report[Coverband::RUNTIME_TYPE.to_s],
{skip_expansion: true}
)
if first_report[Coverband::EAGER_TYPE.to_s] && second_report[Coverband::EAGER_TYPE.to_s]
merged_data[Coverband::EAGER_TYPE.to_s] = Coverband::Adapters::Base.new.send(
:merge_reports,
first_report[Coverband::EAGER_TYPE.to_s],
second_report[Coverband::EAGER_TYPE.to_s],
{skip_expansion: true}
)
end
if first_report[Coverband::MERGED_TYPE.to_s] && second_report[Coverband::MERGED_TYPE.to_s]
merged_data[Coverband::MERGED_TYPE.to_s] = Coverband::Adapters::Base.new.send(
:merge_reports,
first_report[Coverband::MERGED_TYPE.to_s],
second_report[Coverband::MERGED_TYPE.to_s],
{skip_expansion: true}
)
end
merged_data
end

private

def coverage_css_class(covered_percent)
Expand All @@ -43,6 +73,8 @@ def coverage_css_class(covered_percent)
end

def report_as_json
return filtered_report_files.to_json if for_merged_report

result = Coverband::Utils::Results.new(filtered_report_files)
source_files = result.source_files

Expand Down Expand Up @@ -97,7 +129,7 @@ def coverage_totals(source_files)
def coverage_files(result, source_files)
source_files.each_with_object({}) do |source_file, hash|
runtime_coverage = result.file_with_type(source_file, Coverband::RUNTIME_TYPE)&.covered_lines_count || 0
hash[source_file.short_name] = {
data = {
filename: source_file.filename,
hash: Digest::SHA1.hexdigest(source_file.filename),
never_loaded: source_file.never_loaded,
Expand All @@ -109,6 +141,8 @@ def coverage_files(result, source_files)
covered_percent: source_file.covered_percent,
covered_strength: source_file.covered_strength
}
data[:coverage] = source_file.coverage if line_coverage
hash[source_file.short_name] = data
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/coverband/utils/dead_methods.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# frozen_string_literal: ntrue
# frozen_string_literal: true

require "coverband/utils/method_definition_scanner"

Expand Down
41 changes: 39 additions & 2 deletions lib/coverband/utils/tasks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,12 @@
require "coverband/utils/results"
require "coverband/reporters/json_report"

report = Coverband::Reporters::JSONReport.new(Coverband.configuration.store).report
File.write("coverage/coverage.json", report)
report = Coverband::Reporters::JSONReport.new(Coverband.configuration.store, {
for_merged_report: !!ENV["FOR_MERGED_REPORT"],
line_coverage: true
}).report
`mkdir -p coverage`
File.write("coverage/coverage.json.#{Time.now.to_f}", report)
end

###
Expand Down Expand Up @@ -68,6 +72,39 @@
SimpleCov::Formatter::HTMLFormatter.new.format(result)
end

####
# This task can aggregate multiple coverage files into a single coverage report
# * `FOR_MERGED_REPORT=true bundle exec rake coverband:coverage_json` to generate the JSON files
# * collect all the files over time in some system or as artifacts in CI, then run...
# * `bundle exec rake coverband:aggregate_coverage` to merge the files
# * the output will include a timestamp of when it was output...
####
task :aggregate_coverage do |task, args|
require "coverband/utils/result"
require "coverband/utils/file_list"
require "coverband/utils/source_file"
require "coverband/utils/lines_classifier"
require "coverband/utils/results"
require "coverband/reporters/json_report"

directory = "./coverage"
pattern = "coverage.json*"

# Use Dir.glob to find files matching the pattern in the specified directory
files = Dir.glob(File.join(directory, pattern))

report = {}
files.each do |file|
data = JSON.parse(File.read(file))
report = if report.empty?
data
else
Coverband::Reporters::JSONReport.new(Coverband.configuration.store).merge_reports(report, data)
end
end
File.write("coverage/coverage_merged.json.#{Time.now.to_f}", report.to_json)
end

desc "Run a simple rack app to report Coverband code coverage"
task :coverage_server do
if Rake::Task.task_defined?("environment")
Expand Down
1 change: 0 additions & 1 deletion test/coverband/reporters/base_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ def setup
roots = ['/base/[0-9]*/', '/base/78/app/']

Coverband.configuration.stubs(:all_root_paths).returns(roots)
lines_hit = [1, 3, 6]
store.stubs(:merged_coverage).returns(coverage)
File.expects(:exist?).at_least_once
.with('/base/[0-9]*/app/controllers/dashboard_controller.rb')
Expand Down
11 changes: 11 additions & 0 deletions test/coverband/reporters/json_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,15 @@ def setup
assert_equal parsed["files"][file].keys, expected_keys
end
end

test "supports merging" do
@store.send(:save_report, basic_coverage)
first_report = JSON.parse(Coverband::Reporters::JSONReport.new(@store, for_merged_report: true).report)

@store.send(:save_report, increased_basic_coverage)
second_report = JSON.parse(Coverband::Reporters::JSONReport.new(@store, for_merged_report: true).report)
data = Coverband::Reporters::JSONReport.new(@store).merge_reports(first_report, second_report)
assert_equal data[Coverband::RUNTIME_TYPE.to_s]["app_path/dog.rb"]["data"], [0, 4, 10]
assert_equal data[Coverband::MERGED_TYPE.to_s]["app_path/dog.rb"]["data"], [0, 4, 10]
end
end
4 changes: 4 additions & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ def basic_coverage
{"app_path/dog.rb" => example_line}
end

def increased_basic_coverage
{"app_path/dog.rb" => [0, 2, 6]}
end

def basic_coverage_full_path
{basic_coverage_file_full_path => example_line}
end
Expand Down

0 comments on commit 59d6d92

Please sign in to comment.