From 7ef8176d5c2eef446637a034a237701f549a1e29 Mon Sep 17 00:00:00 2001 From: Dan Mayer Date: Thu, 16 May 2024 22:36:42 -0600 Subject: [PATCH] fixes #467 this is a useful feature for some folks with complex deployments --- lib/coverband/adapters/base.rb | 4 ++- lib/coverband/adapters/redis_store.rb | 9 ++++++ lib/coverband/reporters/json_report.rb | 38 ++++++++++++++++++++++-- lib/coverband/utils/dead_methods.rb | 2 +- lib/coverband/utils/tasks.rb | 41 ++++++++++++++++++++++++-- test/coverband/reporters/base_test.rb | 1 - test/coverband/reporters/json_test.rb | 11 +++++++ test/test_helper.rb | 4 +++ 8 files changed, 103 insertions(+), 7 deletions(-) diff --git a/lib/coverband/adapters/base.rb b/lib/coverband/adapters/base.rb index ea41c42c..3349a15c 100644 --- a/lib/coverband/adapters/base.rb +++ b/lib/coverband/adapters/base.rb @@ -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] } diff --git a/lib/coverband/adapters/redis_store.rb b/lib/coverband/adapters/redis_store.rb index 5e3d3165..1c22ce20 100644 --- a/lib/coverband/adapters/redis_store.rb +++ b/lib/coverband/adapters/redis_store.rb @@ -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 diff --git a/lib/coverband/reporters/json_report.rb b/lib/coverband/reporters/json_report.rb index a22afdea..4ee73887 100644 --- a/lib/coverband/reporters/json_report.rb +++ b/lib/coverband/reporters/json_report.rb @@ -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 @@ -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) @@ -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 @@ -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, @@ -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 diff --git a/lib/coverband/utils/dead_methods.rb b/lib/coverband/utils/dead_methods.rb index 213fc179..8dad0cd6 100644 --- a/lib/coverband/utils/dead_methods.rb +++ b/lib/coverband/utils/dead_methods.rb @@ -1,4 +1,4 @@ -# frozen_string_literal: ntrue +# frozen_string_literal: true require "coverband/utils/method_definition_scanner" diff --git a/lib/coverband/utils/tasks.rb b/lib/coverband/utils/tasks.rb index b4cdea16..ef7d1323 100644 --- a/lib/coverband/utils/tasks.rb +++ b/lib/coverband/utils/tasks.rb @@ -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 ### @@ -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") diff --git a/test/coverband/reporters/base_test.rb b/test/coverband/reporters/base_test.rb index 7163e237..9082791c 100644 --- a/test/coverband/reporters/base_test.rb +++ b/test/coverband/reporters/base_test.rb @@ -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') diff --git a/test/coverband/reporters/json_test.rb b/test/coverband/reporters/json_test.rb index 9f9ee32a..a8eee988 100644 --- a/test/coverband/reporters/json_test.rb +++ b/test/coverband/reporters/json_test.rb @@ -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 diff --git a/test/test_helper.rb b/test/test_helper.rb index f0707605..6c7317bf 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -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