From 19cc5da07a3cc10e9f9b4c499dde236fc32013d5 Mon Sep 17 00:00:00 2001 From: Dan Mayer Date: Wed, 28 Feb 2024 00:15:14 -0700 Subject: [PATCH 01/18] support paging for the hash redis store --- coverband.gemspec | 1 + lib/coverband.rb | 16 +++++ lib/coverband/adapters/base.rb | 10 ++-- lib/coverband/adapters/file_store.rb | 2 +- lib/coverband/adapters/hash_redis_store.rb | 68 +++++++++++++++++++-- lib/coverband/adapters/null_store.rb | 2 +- lib/coverband/adapters/stdout_store.rb | 2 +- lib/coverband/reporters/base.rb | 9 +-- lib/coverband/reporters/html_report.rb | 16 +++-- lib/coverband/reporters/json_report.rb | 46 ++++++++++++-- lib/coverband/reporters/web.rb | 13 ++++ lib/coverband/reporters/web_pager.rb | 28 +++++++++ lib/coverband/utils/html_formatter.rb | 3 +- lib/coverband/utils/source_file.rb | 2 +- lib/coverband/version.rb | 2 +- public/application.js | 35 +++++++++++ public/dependencies.js | 2 +- test/coverband/reporters/json_test.rb | 2 +- views/file_list.erb | 70 ++++++++++++---------- views/layout.erb | 6 +- 20 files changed, 269 insertions(+), 66 deletions(-) create mode 100644 lib/coverband/reporters/web_pager.rb diff --git a/coverband.gemspec b/coverband.gemspec index 6c3ea108..bed113c0 100644 --- a/coverband.gemspec +++ b/coverband.gemspec @@ -39,6 +39,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency "minitest-fork_executor" spec.add_development_dependency "minitest-stub-const" spec.add_development_dependency "mocha", "~> 1.7.0" + # spec.add_development_dependency "spy" spec.add_development_dependency "rack" spec.add_development_dependency "rack-test" spec.add_development_dependency "rake" diff --git a/lib/coverband.rb b/lib/coverband.rb index e171b014..44243222 100644 --- a/lib/coverband.rb +++ b/lib/coverband.rb @@ -130,6 +130,7 @@ class Web ### def initialize require "coverband/reporters/web" + require "coverband/reporters/web_pager" require "coverband/utils/html_formatter" require "coverband/utils/result" require "coverband/utils/file_list" @@ -147,4 +148,19 @@ def self.call(env) end end end + + module Reporters + class WebPager < Web + def initialize + require "coverband/reporters/web" + require "coverband/reporters/web_pager" + super + end + + def self.call(env) + @app ||= new + @app.call(env) + end + end + end end diff --git a/lib/coverband/adapters/base.rb b/lib/coverband/adapters/base.rb index df0903d3..df5504a8 100644 --- a/lib/coverband/adapters/base.rb +++ b/lib/coverband/adapters/base.rb @@ -35,7 +35,7 @@ def save_coverage raise ABSTRACT_KEY end - def coverage(_local_type = nil) + def coverage(_local_type = nil, opts = {}) raise ABSTRACT_KEY end @@ -51,9 +51,9 @@ def save_report(_report) raise "abstract" end - def get_coverage_report + def get_coverage_report(options = {}) coverage_cache = {} - data = Coverband.configuration.store.split_coverage(Coverband::TYPES, coverage_cache) + data = Coverband.configuration.store.split_coverage(Coverband::TYPES, coverage_cache, options) data.merge(Coverband::MERGED_TYPE => Coverband.configuration.store.merged_coverage(Coverband::TYPES, coverage_cache)) end @@ -67,12 +67,12 @@ def raw_store protected - def split_coverage(types, coverage_cache) + def split_coverage(types, coverage_cache, options = {}) types.reduce({}) do |data, type| if type == Coverband::RUNTIME_TYPE && Coverband.configuration.simulate_oneshot_lines_coverage data.update(type => coverage_cache[type] ||= simulated_runtime_coverage) else - data.update(type => coverage_cache[type] ||= coverage(type)) + data.update(type => coverage_cache[type] ||= coverage(type, options)) end end end diff --git a/lib/coverband/adapters/file_store.rb b/lib/coverband/adapters/file_store.rb index 62b8e429..5b08db27 100644 --- a/lib/coverband/adapters/file_store.rb +++ b/lib/coverband/adapters/file_store.rb @@ -52,7 +52,7 @@ def migrate! raise NotImplementedError, "FileStore doesn't support migrations" end - def coverage(_local_type = nil) + def coverage(_local_type = nil, opts = {}) if merge_mode data = {} Dir[path.sub(/\.\d+/, ".*")].each do |path| diff --git a/lib/coverband/adapters/hash_redis_store.rb b/lib/coverband/adapters/hash_redis_store.rb index cc6aaf6d..43d05fe5 100644 --- a/lib/coverband/adapters/hash_redis_store.rb +++ b/lib/coverband/adapters/hash_redis_store.rb @@ -51,12 +51,22 @@ def clear!(local_types = Coverband::TYPES) end end + protected + + def split_coverage(types, coverage_cache, options = {}) + if types.is_a?(Array) + coverage_for_types(types, options) + else + super + end + end + private # sleep in between to avoid holding other redis commands.. # with a small random offset so runtime and eager types can be processed "at the same time" def deferred_time - rand(3.0..4.0) + rand(2.0..3.0) end def del(local_type) @@ -107,7 +117,7 @@ def initialize(redis, opts = {}) @relative_file_converter = opts[:relative_file_converter] || Utils::RelativeFileConverter @get_coverage_cache = if opts[:get_coverage_cache] - key_prefix = [REDIS_STORAGE_FORMAT_VERSION, @redis_namespace].compact.join(".") + key_prefix = [REDIS_STORAGE_FORMAT_VERSION, @redis_namespace, "v2"].compact.join(".") GetCoverageRedisCacheStore.new(redis, key_prefix) else GetCoverageNullCacheStore @@ -128,6 +138,7 @@ def clear! file_keys = files_set @redis.del(*file_keys) if file_keys.any? @redis.del(files_key) + @redis.del(files_key(type)) @get_coverage_cache.clear!(type) end self.type = old_type @@ -171,12 +182,19 @@ def save_report(report) @redis.sadd(files_key, keys) if keys.any? end - def coverage(local_type = nil) + # TODO: refactor this and the method below and consider removing all the cached results stuff + def coverage(local_type = nil, opts = {}) + page_size = opts[:page_size] || 250 cached_results = @get_coverage_cache.fetch(local_type || type) do |sleep_time| - files_set = files_set(local_type) - + files_set = if opts[:page] + files_set(local_type).each_slice(page_size).to_a[opts[:page] - 1] || {} + elsif opts[:filename] + files_set(local_type).select { |filepath| filepath == opts[:filename] } || {} + else + files_set(local_type) + end # use batches with a sleep in between to avoid overloading redis - files_set.each_slice(250).flat_map do |key_batch| + files_set.each_slice(page_size).flat_map do |key_batch| sleep sleep_time @redis.pipelined do |pipeline| key_batch.each do |key| @@ -191,6 +209,44 @@ def coverage(local_type = nil) end end + # NOTE: when using paging we need to ensure we have the same set of files per page in runtime and eager + def coverage_for_types(types, opts = {}) + page_size = opts[:page_size] || 250 + + local_type = Coverband::RUNTIME_TYPE + hash_data = {} + + runtime_file_set = if opts[:page] + files_set(local_type).each_slice(page_size).to_a[opts[:page] - 1] || {} + elsif opts[:filename] + files_set(local_type).select { |filepath| filepath == opts[:filename] } || {} + else + files_set(local_type) + end + hash_data[Coverband::RUNTIME_TYPE] = runtime_file_set.each_slice(page_size).flat_map do |key_batch| + @redis.pipelined do |pipeline| + key_batch.each do |key| + pipeline.hgetall(key) + end + end + end + + matched_file_set = files_set(Coverband::EAGER_TYPE) + .select { |filepath| runtime_file_set.include?(filepath) } || {} + hash_data[Coverband::EAGER_TYPE] = matched_file_set.each_slice(page_size).flat_map do |key_batch| + @redis.pipelined do |pipeline| + key_batch.each do |key| + pipeline.hgetall(key) + end + end + end + hash_data + end + + def file_count(local_type = nil) + files_set(local_type).count { |filename| !Coverband.configuration.ignore.any? { |i| filename.match(i) } } + end + def raw_store @redis end diff --git a/lib/coverband/adapters/null_store.rb b/lib/coverband/adapters/null_store.rb index 9f12411a..bfcf9614 100644 --- a/lib/coverband/adapters/null_store.rb +++ b/lib/coverband/adapters/null_store.rb @@ -26,7 +26,7 @@ def migrate! raise NotImplementedError, "NullStore doesn't support migrations" end - def coverage(_local_type = nil) + def coverage(_local_type = nil, opts = {}) {} end diff --git a/lib/coverband/adapters/stdout_store.rb b/lib/coverband/adapters/stdout_store.rb index 3f4d9d68..7a27b6fb 100644 --- a/lib/coverband/adapters/stdout_store.rb +++ b/lib/coverband/adapters/stdout_store.rb @@ -25,7 +25,7 @@ def migrate! raise NotImplementedError, "StdoutStore doesn't support migrations" end - def coverage(_local_type = nil) + def coverage(_local_type = nil, opts = {}) {} end diff --git a/lib/coverband/reporters/base.rb b/lib/coverband/reporters/base.rb index c7eac9bb..a626051c 100644 --- a/lib/coverband/reporters/base.rb +++ b/lib/coverband/reporters/base.rb @@ -10,9 +10,9 @@ class Base class << self DATA_KEY = "data" - def report(store, _options = {}) + def report(store, options = {}) all_roots = Coverband.configuration.all_root_paths - get_current_scov_data_imp(store, all_roots) + get_current_scov_data_imp(store, all_roots, options) # These are extremelhy verbose but useful during coverband development, not generally for users # Only available by uncommenting this mode is never released @@ -85,12 +85,13 @@ def merge_arrays(first, second) # why do we need to merge covered files data? # basically because paths on machines or deployed hosts could be different, so # two different keys could point to the same filename or `line_key` + # this happens when deployment has a dynmaic path or the path change during deployment (hot code reload) # TODO: think we are filtering based on ignore while sending to the store # and as we also pull it out here ### - def get_current_scov_data_imp(store, roots) + def get_current_scov_data_imp(store, roots, options = {}) scov_style_report = {} - store.get_coverage_report.each_pair do |name, data| + store.get_coverage_report(options).each_pair do |name, data| data.each_pair do |key, line_data| next if Coverband.configuration.ignore.any? { |i| key.match(i) } next unless line_data diff --git a/lib/coverband/reporters/html_report.rb b/lib/coverband/reporters/html_report.rb index 20831cb8..b89494f9 100644 --- a/lib/coverband/reporters/html_report.rb +++ b/lib/coverband/reporters/html_report.rb @@ -4,17 +4,23 @@ module Coverband module Reporters class HTMLReport < Base attr_accessor :filtered_report_files, :open_report, :notice, - :base_path, :filename + :base_path, :filename, :page def initialize(store, options = {}) - coverband_reports = Coverband::Reporters::Base.report(store, options) + self.page = options.fetch(:page) { nil } self.open_report = options.fetch(:open_report) { true } # TODO: refactor notice out to top level of web only self.notice = options.fetch(:notice) { nil } self.base_path = options.fetch(:base_path) { "./" } self.filename = options.fetch(:filename) { nil } - self.filtered_report_files = self.class.fix_reports(coverband_reports) + coverband_reports = Coverband::Reporters::Base.report(store, options) + # NOTE: at the moment the optimization around paging and filenames only works for hash redis store + self.filtered_report_files = if (page || filename) && store.is_a?(Coverband::Adapters::HashRedisStore) + coverband_reports + else + self.class.fix_reports(coverband_reports) + end end def file_details @@ -36,12 +42,14 @@ def report_data def report_dynamic_html Coverband::Utils::HTMLFormatter.new(filtered_report_files, base_path: base_path, - notice: notice).format_dynamic_html! + notice: notice, + page: page).format_dynamic_html! end def report_dynamic_data Coverband::Utils::HTMLFormatter.new(filtered_report_files, base_path: base_path, + page: page, notice: notice).format_dynamic_data! end end diff --git a/lib/coverband/reporters/json_report.rb b/lib/coverband/reporters/json_report.rb index 09d7a1e6..014fd8de 100644 --- a/lib/coverband/reporters/json_report.rb +++ b/lib/coverband/reporters/json_report.rb @@ -5,11 +5,22 @@ module Coverband module Reporters class JSONReport < Base - attr_accessor :filtered_report_files + attr_accessor :filtered_report_files, :options, :page, :as_report, :store, :filename 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.store = store + coverband_reports = Coverband::Reporters::Base.report(store, options) - self.filtered_report_files = self.class.fix_reports(coverband_reports) + # NOTE: paged reports can't find and add in files that has never been loaded + self.filtered_report_files = if page || filename + coverband_reports + else + self.class.fix_reports(coverband_reports) + end end def report @@ -21,10 +32,35 @@ def report def report_as_json result = Coverband::Utils::Results.new(filtered_report_files) source_files = result.source_files - { + + data = { **coverage_totals(source_files), files: coverage_files(result, source_files) - }.to_json + } + + if as_report + row_data = [] + data[:files].each_pair do |key, data| + row_data << [ + key, + data[:covered_percent].to_s, + data[:runtime_percentage].to_s, + data[:lines_of_code].to_s, + (data[:lines_covered] + data[:lines_missed]).to_s, + data[:lines_covered].to_s, + data[:lines_runtime].to_s, + data[:lines_missed].to_s, + data[:covered_strength].to_s + ] + end + filesreported = store.file_count(:runtime) + data["iTotalRecords"] = filesreported + data["iTotalDisplayRecords"] = filesreported + data["aaData"] = row_data + data.delete(:files) + data = data.as_json + end + data.to_json end def coverage_totals(source_files) @@ -41,11 +77,13 @@ def coverage_totals(source_files) # Using a hash indexed by file name for quick lookups 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] = { never_loaded: source_file.never_loaded, runtime_percentage: result.runtime_relevant_coverage(source_file), lines_of_code: source_file.lines.count, lines_covered: source_file.covered_lines.count, + lines_runtime: runtime_coverage, lines_missed: source_file.missed_lines.count, covered_percent: source_file.covered_percent, covered_strength: source_file.covered_strength diff --git a/lib/coverband/reporters/web.rb b/lib/coverband/reporters/web.rb index 73a9f8f4..62e5a1ca 100644 --- a/lib/coverband/reporters/web.rb +++ b/lib/coverband/reporters/web.rb @@ -98,6 +98,8 @@ def call(env) [200, coverband_headers(content_type: "text/json"), [load_file_details]] when %r{\/json} [200, coverband_headers(content_type: "text/json"), [json]] + when %r{\/report_json} + [200, coverband_headers(content_type: "text/json"), [report_json]] when %r{\/$} [200, coverband_headers, [index]] else @@ -121,6 +123,17 @@ def json Coverband::Reporters::JSONReport.new(Coverband.configuration.store).report end + def report_json + report_options = { + as_report: true + } + report_options[:page] = (request.params["page"] || 1).to_i if request.params["page"] + Coverband::Reporters::JSONReport.new( + Coverband.configuration.store, + report_options + ).report + end + def settings Coverband::Utils::HTMLFormatter.new(nil, base_path: base_path).format_settings! end diff --git a/lib/coverband/reporters/web_pager.rb b/lib/coverband/reporters/web_pager.rb new file mode 100644 index 00000000..c91bc89b --- /dev/null +++ b/lib/coverband/reporters/web_pager.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require "base64" +require "coverband" + +begin + require "rack" +rescue LoadError + puts "error loading Coverband web reporter as Rack is not available" +end + +module Coverband + module Reporters + class WebPager < Web + def index + notice = "Notice: #{Rack::Utils.escape_html(request.params["notice"])}
" + notice = request.params["notice"] ? notice : "" + # TODO: remove the call to the store render empty table + Coverband::Reporters::HTMLReport.new(Coverband.configuration.store, + page: (request.params["page"] || 1).to_i, + static: false, + base_path: base_path, + notice: notice, + open_report: false).report + end + end + end +end diff --git a/lib/coverband/utils/html_formatter.rb b/lib/coverband/utils/html_formatter.rb index c2c217a4..697df7b3 100644 --- a/lib/coverband/utils/html_formatter.rb +++ b/lib/coverband/utils/html_formatter.rb @@ -13,12 +13,13 @@ module Coverband module Utils class HTMLFormatter - attr_reader :notice, :base_path, :tracker + attr_reader :notice, :base_path, :tracker, :page def initialize(report, options = {}) @notice = options.fetch(:notice, nil) @base_path = options.fetch(:base_path, "./") @tracker = options.fetch(:tracker, nil) + @page = options.fetch(:page, nil) @coverage_result = Coverband::Utils::Results.new(report) if report end diff --git a/lib/coverband/utils/source_file.rb b/lib/coverband/utils/source_file.rb index 60f3d7fb..e87d290d 100644 --- a/lib/coverband/utils/source_file.rb +++ b/lib/coverband/utils/source_file.rb @@ -162,7 +162,7 @@ def covered_percent return 0.0 if relevant_lines.zero? # handle edge case where runtime in dev can go over 100% - [Float(covered_lines.size * 100.0 / relevant_lines.to_f), 100.0].min + [Float(covered_lines.size * 100.0 / relevant_lines.to_f), 100.0].min&.round(2) end def formatted_covered_percent diff --git a/lib/coverband/version.rb b/lib/coverband/version.rb index d11bfb33..604ce70a 100644 --- a/lib/coverband/version.rb +++ b/lib/coverband/version.rb @@ -5,5 +5,5 @@ # use format "4.2.1.rc.1" ~> 4.2.1.rc to prerelease versions like v4.2.1.rc.2 and v4.2.1.rc.3 ### module Coverband - VERSION = "6.0.2" + VERSION = "6.0.3.rc.1" end diff --git a/public/application.js b/public/application.js index 64ed2397..13e5c50f 100644 --- a/public/application.js +++ b/public/application.js @@ -33,6 +33,41 @@ $(document).ready(function() { ] }); + // TODO: add support for searching... + // hmm should I just use manual paging? or load more... + if ($(".file_list.unsorted").length == 1) { + var current_rows = 0; + var total_rows = 0; + var page = 1; + + // write a function to get a page of data and add it to the table + function get_page(page) { + $.ajax({ + url: `/coverage/report_json?page=${page}`, + type: 'GET', + dataType: 'json', + success: function(data) { + console.log(data); + total_rows = data["iTotalRecords"]; + // NOTE: we request 250 at a time, but we seem to have some files that we have as a list but 0 coverage, + // so we don't get back 250 per page... to ensure we we need to account for filtered out and empty files + // this 250 at the moment is synced to the 250 in the hash redis store + current_rows += 250; //data["aaData"].length; + console.log(current_rows); + console.log(total_rows); + $(".file_list.unsorted").dataTable().fnAddData(data["aaData"]); + page += 1; + // the page less than 100 is to stop infinite loop in case of folks never clearing out old coverage reports + if (page < 100 && current_rows < total_rows) { + get_page(page); + } + } + }); + } + get_page(page); + } + + // Syntax highlight all files up front - deactivated // $('.source_table pre code').each(function(i, e) {hljs.highlightBlock(e, ' ')}); diff --git a/public/dependencies.js b/public/dependencies.js index 7d48e5b7..d50bb61a 100644 --- a/public/dependencies.js +++ b/public/dependencies.js @@ -1147,7 +1147,7 @@ this._iRecordsTotal:this.aiDisplayMaster.length};this.fnRecordsDisplay=function( bSort:true,bInfo:true,bAutoWidth:true,bProcessing:false,bSortClasses:true,bStateSave:false,bServerSide:false};this.oScroll={sX:"",sXInner:"",sY:"",bCollapse:false,iBarWidth:0};this.aanFeatures=[];this.oLanguage={sProcessing:"Processing...",sLengthMenu:"Show _MENU_ entries",sZeroRecords:"No matching records found",sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)", sInfoPostFix:"",sSearch:"Search:",sUrl:"",oPaginate:{sFirst:"First",sPrevious:"Previous",sNext:"Next",sLast:"Last"}};this.aoData=[];this.aiDisplay=[];this.aiDisplayMaster=[];this.aoColumns=[];this.iNextId=0;this.asDataSearch=[];this.oPreviousSearch={sSearch:"",bRegex:false,bSmart:true};this.aoPreSearchCols=[];this.aaSorting=[[0,"asc",0]];this.aaSortingFixed=null;this.asStripClasses=[];this.asDestoryStrips=[];this.fnFooterCallback=this.fnHeaderCallback=this.fnRowCallback=null;this.aoDrawCallback=[]; this.fnInitComplete=null;this.sTableId="";this.nTableWrapper=this.nTBody=this.nTFoot=this.nTHead=this.nTable=null;this.iDefaultSortIndex=0;this.bInitialised=false;this.aoOpenRows=[];this.sDom="lfrtip";this.sPaginationType="two_button";this.iCookieDuration=7200;this.sCookiePrefix="SpryMedia_DataTables_";this.sAjaxSource=null;this.bAjaxDataGet=true;this.fnServerData=function(a,b,c){j.ajax({url:a,data:b,success:c,dataType:"json",cache:false,error:function(){alert("DataTables warning: JSON data from server failed to load or be parsed. This is most likely to be caused by a JSON formatting error.")}})}; -this.fnFormatNumber=function(a){if(a<1E3)return a;else{var b=a+"";a=b.split("");var c="";b=b.length;for(var d=0;d

<%= title %> - (<%= source_files.covered_percent.round(2) %>% - covered at - - - <%= source_files.covered_strength.round(2) %> - - hits/line) + <% unless page %> + (<%= source_files.covered_percent.round(2) %>% + covered at + + + <%= source_files.covered_strength.round(2) %> + + hits/line) + <% end %>

<% end %>
- <%= source_files.length %> files in total. - <%= source_files.lines_of_code %> relevant lines. - <%= source_files.covered_lines %> lines covered and - <%= source_files.missed_lines %> lines missed + <% unless page %> + <%= source_files.length %> files in total. + <%= source_files.lines_of_code %> relevant lines. + <%= source_files.covered_lines %> lines covered and + <%= source_files.missed_lines %> lines missed + <% end %>
- +
"> @@ -33,26 +37,28 @@ - <% source_files.each do |source_file| %> - - <% source_class = source_file.never_loaded ? 'strong red' : 'strong'%> - - - <% runtime_percentage = result.runtime_relevant_coverage(source_file) %> - - - - - - - - + <% unless page %> + <% source_files.each do |source_file| %> + + <% source_class = source_file.never_loaded ? 'strong red' : 'strong'%> + + + <% runtime_percentage = result.runtime_relevant_coverage(source_file) %> + + + + + + + + + <% end %> <% end %>
File
- <%= link_to_source_file(source_file) %> - <%= source_file.covered_percent.round(2).to_s %> % strong"> - <%= "#{runtime_percentage || '0'} %" %> - <%= source_file.lines.count %><%= source_file.covered_lines.count + source_file.missed_lines.count %><%= source_file.covered_lines.count %> - <%= result.file_with_type(source_file, Coverband::RUNTIME_TYPE)&.covered_lines_count || 0 %> - <%= source_file.missed_lines.count %><%= source_file.covered_strength %>
+ <%= link_to_source_file(source_file) %> + <%= source_file.covered_percent.round(2).to_s %> % strong"> + <%= "#{runtime_percentage || '0'} %" %> + <%= source_file.lines.count %><%= source_file.covered_lines.count + source_file.missed_lines.count %><%= source_file.covered_lines.count %> + <%= result.file_with_type(source_file, Coverband::RUNTIME_TYPE)&.covered_lines_count || 0 %> + <%= source_file.missed_lines.count %><%= source_file.covered_strength %>
diff --git a/views/layout.erb b/views/layout.erb index 144eb694..d6098c37 100644 --- a/views/layout.erb +++ b/views/layout.erb @@ -26,9 +26,9 @@
- <% result.source_files.each do |source_file| %> - <%= formatted_source_file_loader(result, source_file) %> - <% end %> + <% result.source_files.each do |source_file| %> + <%= formatted_source_file_loader(result, source_file) %> + <% end %>
From a9372ee67c0da314d74b5977b26bd7e89a4b33d2 Mon Sep 17 00:00:00 2001 From: Dan Mayer Date: Mon, 4 Mar 2024 18:39:06 -0700 Subject: [PATCH 02/18] add pager endpoint based on base path --- lib/coverband/version.rb | 2 +- public/application.js | 5 +++-- views/file_list.erb | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/coverband/version.rb b/lib/coverband/version.rb index 604ce70a..e3309343 100644 --- a/lib/coverband/version.rb +++ b/lib/coverband/version.rb @@ -5,5 +5,5 @@ # use format "4.2.1.rc.1" ~> 4.2.1.rc to prerelease versions like v4.2.1.rc.2 and v4.2.1.rc.3 ### module Coverband - VERSION = "6.0.3.rc.1" + VERSION = "6.0.3.rc.2" end diff --git a/public/application.js b/public/application.js index 13e5c50f..cae00116 100644 --- a/public/application.js +++ b/public/application.js @@ -35,6 +35,8 @@ $(document).ready(function() { // TODO: add support for searching... // hmm should I just use manual paging? or load more... + // best docs on our version of datatables 1.7 https://datatables.net/beta/1.7/examples/server_side/server_side.html + // TODO: fix bug where we hardcoded /coverage we need to pull it from the path it is mounted on if ($(".file_list.unsorted").length == 1) { var current_rows = 0; var total_rows = 0; @@ -43,11 +45,10 @@ $(document).ready(function() { // write a function to get a page of data and add it to the table function get_page(page) { $.ajax({ - url: `/coverage/report_json?page=${page}`, + url: `${$(".file_list").data("coverageurl")}/report_json?page=${page}`, type: 'GET', dataType: 'json', success: function(data) { - console.log(data); total_rows = data["iTotalRecords"]; // NOTE: we request 250 at a time, but we seem to have some files that we have as a list but 0 coverage, // so we don't get back 250 per page... to ensure we we need to account for filtered out and empty files diff --git a/views/file_list.erb b/views/file_list.erb index 5b7dc3c0..ba5c95d8 100644 --- a/views/file_list.erb +++ b/views/file_list.erb @@ -22,7 +22,7 @@ <%= source_files.missed_lines %> lines missed <% end %> - "> +
" data-coverageurl="<%= base_path %>"> From e3bbb8e0a26895e14fa49b7d18cc1ccd5197a51c Mon Sep 17 00:00:00 2001 From: Dan Mayer Date: Tue, 12 Mar 2024 20:20:57 -0600 Subject: [PATCH 03/18] fixed links and JS for using dynamically added links --- lib/coverband/adapters/hash_redis_store.rb | 45 ++++++++++++----- lib/coverband/reporters/json_report.rb | 13 ++++- lib/coverband/reporters/web.rb | 3 +- lib/coverband/version.rb | 2 +- public/application.js | 57 +++++++++++++--------- views/layout.erb | 10 ++-- 6 files changed, 88 insertions(+), 42 deletions(-) diff --git a/lib/coverband/adapters/hash_redis_store.rb b/lib/coverband/adapters/hash_redis_store.rb index 43d05fe5..ab99a0ba 100644 --- a/lib/coverband/adapters/hash_redis_store.rb +++ b/lib/coverband/adapters/hash_redis_store.rb @@ -51,16 +51,6 @@ def clear!(local_types = Coverband::TYPES) end end - protected - - def split_coverage(types, coverage_cache, options = {}) - if types.is_a?(Array) - coverage_for_types(types, options) - else - super - end - end - private # sleep in between to avoid holding other redis commands.. @@ -189,7 +179,8 @@ def coverage(local_type = nil, opts = {}) files_set = if opts[:page] files_set(local_type).each_slice(page_size).to_a[opts[:page] - 1] || {} elsif opts[:filename] - files_set(local_type).select { |filepath| filepath == opts[:filename] } || {} + # TODO: this probably needs to be an exact match of the parsed cache key section + files_set(local_type).select{ |cache_key| cache_key.match(short_name(opts[:filename])) } || {} else files_set(local_type) end @@ -209,7 +200,17 @@ def coverage(local_type = nil, opts = {}) end end + # TODO: fix this before shipping main line release + # def split_coverage(types, coverage_cache, options = {}) + # if types.is_a?(Array) + # coverage_for_types(types, options) + # else + # super + # end + # end + # NOTE: when using paging we need to ensure we have the same set of files per page in runtime and eager + # TODO: This merge of eager and runtime isn't working fix later... def coverage_for_types(types, opts = {}) page_size = opts[:page_size] || 250 @@ -219,10 +220,13 @@ def coverage_for_types(types, opts = {}) runtime_file_set = if opts[:page] files_set(local_type).each_slice(page_size).to_a[opts[:page] - 1] || {} elsif opts[:filename] - files_set(local_type).select { |filepath| filepath == opts[:filename] } || {} + # TODO: this probably needs to be an exact match of the parsed cache key section + # match is a hack that will only kind of work + files_set(local_type).select{ |cache_key| cache_key.match(short_name(opts[:filename])) } || {} else files_set(local_type) end + hash_data[Coverband::RUNTIME_TYPE] = runtime_file_set.each_slice(page_size).flat_map do |key_batch| @redis.pipelined do |pipeline| key_batch.each do |key| @@ -231,8 +235,12 @@ def coverage_for_types(types, opts = {}) end end + # TODO: debug the set isn't just paths it has other key details including coverage type so below probalby fails + # match is a hack that will work a sometimes... fix this but it will prove out if this solves the perf issue matched_file_set = files_set(Coverband::EAGER_TYPE) - .select { |filepath| runtime_file_set.include?(filepath) } || {} + .select { |eager_key, val| runtime_file_set.any?{ |runtime_key| + (eager_key.match(/\.\.(.*).rb/) && eager_key.match(/\.\.(.*).rb/)[0]==runtime_key.match(/\.\.(.*).rb/)[0]) } + } || {} hash_data[Coverband::EAGER_TYPE] = matched_file_set.each_slice(page_size).flat_map do |key_batch| @redis.pipelined do |pipeline| key_batch.each do |key| @@ -240,9 +248,20 @@ def coverage_for_types(types, opts = {}) end end end + hash_data[Coverband::RUNTIME_TYPE] = hash_data[Coverband::RUNTIME_TYPE].each_with_object({}) do |data_from_redis, hash| + add_coverage_for_file(data_from_redis, hash) + end + hash_data[Coverband::EAGER_TYPE] = hash_data[Coverband::EAGER_TYPE].each_with_object({}) do |data_from_redis, hash| + add_coverage_for_file(data_from_redis, hash) + end hash_data end + def short_name(filename) + filename.sub(/^#{Coverband.configuration.root}/, ".") + .gsub(%r{^\.\/}, "") + end + def file_count(local_type = nil) files_set(local_type).count { |filename| !Coverband.configuration.ignore.any? { |i| filename.match(i) } } end diff --git a/lib/coverband/reporters/json_report.rb b/lib/coverband/reporters/json_report.rb index 014fd8de..6cfa262e 100644 --- a/lib/coverband/reporters/json_report.rb +++ b/lib/coverband/reporters/json_report.rb @@ -5,13 +5,14 @@ module Coverband module Reporters class JSONReport < Base - attr_accessor :filtered_report_files, :options, :page, :as_report, :store, :filename + attr_accessor :filtered_report_files, :options, :page, :as_report, :store, :filename, :base_path 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.base_path = options.fetch(:base_path) { "./" } self.store = store coverband_reports = Coverband::Reporters::Base.report(store, options) @@ -41,8 +42,14 @@ def report_as_json if as_report row_data = [] data[:files].each_pair do |key, data| + source_class = data[:never_loaded] ? 'strong red' : 'strong' + data_loader_url="#{base_path}load_file_details?filename=#{data[:filename]}" + # class=\"src_link cboxElement\ + link = "#{key}" + # Started GET "/config/coverage/load_file_details?filename=/home/danmayer/projects/coverband_rails_example/app/jobs/application_job.rb" for ::1 at 2024-03-05 16:02:33 -0700 + # class="<%= coverage_css_class(source_file.covered_percent) %> strong" row_data << [ - key, + link, data[:covered_percent].to_s, data[:runtime_percentage].to_s, data[:lines_of_code].to_s, @@ -79,6 +86,8 @@ 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] = { + filename: source_file.filename, + hash: Digest::SHA1.hexdigest(source_file.filename), never_loaded: source_file.never_loaded, runtime_percentage: result.runtime_relevant_coverage(source_file), lines_of_code: source_file.lines.count, diff --git a/lib/coverband/reporters/web.rb b/lib/coverband/reporters/web.rb index 62e5a1ca..6c65af34 100644 --- a/lib/coverband/reporters/web.rb +++ b/lib/coverband/reporters/web.rb @@ -125,7 +125,8 @@ def json def report_json report_options = { - as_report: true + as_report: true, + base_path: base_path } report_options[:page] = (request.params["page"] || 1).to_i if request.params["page"] Coverband::Reporters::JSONReport.new( diff --git a/lib/coverband/version.rb b/lib/coverband/version.rb index e3309343..6cede387 100644 --- a/lib/coverband/version.rb +++ b/lib/coverband/version.rb @@ -5,5 +5,5 @@ # use format "4.2.1.rc.1" ~> 4.2.1.rc to prerelease versions like v4.2.1.rc.2 and v4.2.1.rc.3 ### module Coverband - VERSION = "6.0.3.rc.2" + VERSION = "6.0.3.rc.3" end diff --git a/public/application.js b/public/application.js index cae00116..3246ebde 100644 --- a/public/application.js +++ b/public/application.js @@ -71,35 +71,36 @@ $(document).ready(function() { // Syntax highlight all files up front - deactivated // $('.source_table pre code').each(function(i, e) {hljs.highlightBlock(e, ' ')}); + src_link_click = (trigger_element) => { + // Get the source file element that corresponds to the clicked element + var source_table = $(".shared_source_table"); + var loader_url = $(trigger_element).attr("data-loader-url"); + $(trigger_element).colorbox(jQuery.extend(colorbox_options, { href: loader_url})); + + // If not highlighted yet, do it! + if (!source_table.hasClass("highlighted")) { + source_table.find("pre code").each(function(i, e) { + hljs.highlightBlock(e, " "); + }); + source_table.addClass("highlighted"); + } + }; + window.src_link_click = src_link_click; // Syntax highlight source files on first toggle of the file view popup - $("a.src_link").click(function() { - // Get the source file element that corresponds to the clicked element - var source_table = $($(this).attr("href")); - var loader_url = $(source_table).attr("data-loader-url"); - - $(source_table).load(loader_url); - - // If not highlighted yet, do it! - if (!source_table.hasClass("highlighted")) { - source_table.find("pre code").each(function(i, e) { - hljs.highlightBlock(e, " "); - }); - source_table.addClass("highlighted"); - } - }); + $("a.src_link").click(src_link_click(this)); var prev_anchor; var curr_anchor; - - // Set-up of popup for source file views - $("a.src_link").colorbox({ + var colorbox_options = { + open: true, transition: "none", - inline: true, + // inline: true, opacity: 1, width: "95%", height: "95%", onLoad: function() { + // TODO: move source highlighting here prev_anchor = curr_anchor ? curr_anchor : jQuery.url.attr("anchor"); curr_anchor = this.href.split("#")[1]; window.location.hash = curr_anchor; @@ -115,7 +116,16 @@ $(document).ready(function() { } window.location.hash = curr_anchor; } - }); + } + + src_link_colorbox = (trigger_element) => { + $(trigger_element).colorbox(colorbox_options); + }; + window.src_link_colorbox = src_link_colorbox; + + // Set-up of popup for source file views + // TODO: drop the static source view even for not paged coverband, then delete all this + $("a.src_link").colorbox(colorbox_options); window.onpopstate = function(event) { if (location.hash.substring(0, 2) == "#_") { @@ -123,7 +133,9 @@ $(document).ready(function() { curr_anchor = jQuery.url.attr("anchor"); } else { if ($("#colorbox").is(":hidden")) { - $('a.src_link[href="' + location.hash + '"]').colorbox({ open: true }); + console.log("pop"); + // $('a.src_link[href="' + location.hash + '"]').colorbox({ open: true }); + $('.shared_source_table').colorbox({ open: true }); } } }; @@ -207,7 +219,8 @@ $(document).ready(function() { var anchor = jQuery.url.attr("anchor"); // source file hash if (anchor.length == 40) { - $("a.src_link[href=#" + anchor + "]").click(); + console.log("I need to fix deep links to source, the click call wont work anymore"); + // $("a.src_link[href=#" + anchor + "]").click(); } else { if ($(".group_tabs a." + anchor.replace("_", "")).length > 0) { $(".group_tabs a." + anchor.replace("_", "")).click(); diff --git a/views/layout.erb b/views/layout.erb index d6098c37..3bbd9f21 100644 --- a/views/layout.erb +++ b/views/layout.erb @@ -26,9 +26,13 @@
- <% result.source_files.each do |source_file| %> - <%= formatted_source_file_loader(result, source_file) %> - <% end %> +
+
+ loading source data... +
+ loading +
+
From 99a220c7a72f465faf780e351e4a53f13b906df2 Mon Sep 17 00:00:00 2001 From: Dan Mayer Date: Mon, 18 Mar 2024 20:55:43 -0600 Subject: [PATCH 04/18] fix test --- test/coverband/reporters/json_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/coverband/reporters/json_test.rb b/test/coverband/reporters/json_test.rb index cc0618cb..9f9ee32a 100644 --- a/test/coverband/reporters/json_test.rb +++ b/test/coverband/reporters/json_test.rb @@ -38,7 +38,7 @@ def setup json = Coverband::Reporters::JSONReport.new(@store).report parsed = JSON.parse(json) - expected_keys = ["never_loaded", "runtime_percentage", "lines_of_code", "lines_covered", "lines_runtime", "lines_missed", "covered_percent", "covered_strength"] + expected_keys = ["filename", "hash", "never_loaded", "runtime_percentage", "lines_of_code", "lines_covered", "lines_runtime", "lines_missed", "covered_percent", "covered_strength"] assert_equal parsed["files"].length, 2 parsed["files"].keys.each do |file| From 555475585f6db06ec7721811e3cbcc987da89a65 Mon Sep 17 00:00:00 2001 From: Dan Mayer Date: Mon, 18 Mar 2024 22:08:13 -0600 Subject: [PATCH 05/18] add loading UI --- lib/coverband/reporters/json_report.rb | 2 +- public/application.js | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/coverband/reporters/json_report.rb b/lib/coverband/reporters/json_report.rb index 6cfa262e..35b297c2 100644 --- a/lib/coverband/reporters/json_report.rb +++ b/lib/coverband/reporters/json_report.rb @@ -43,7 +43,7 @@ def report_as_json row_data = [] data[:files].each_pair do |key, data| source_class = data[:never_loaded] ? 'strong red' : 'strong' - data_loader_url="#{base_path}load_file_details?filename=#{data[:filename]}" + data_loader_url = "#{base_path}load_file_details?filename=#{data[:filename]}" # class=\"src_link cboxElement\ link = "#{key}" # Started GET "/config/coverage/load_file_details?filename=/home/danmayer/projects/coverband_rails_example/app/jobs/application_job.rb" for ::1 at 2024-03-05 16:02:33 -0700 diff --git a/public/application.js b/public/application.js index 3246ebde..10e409d5 100644 --- a/public/application.js +++ b/public/application.js @@ -33,16 +33,19 @@ $(document).ready(function() { ] }); - // TODO: add support for searching... - // hmm should I just use manual paging? or load more... + // TODO: add support for searching on server side // best docs on our version of datatables 1.7 https://datatables.net/beta/1.7/examples/server_side/server_side.html - // TODO: fix bug where we hardcoded /coverage we need to pull it from the path it is mounted on if ($(".file_list.unsorted").length == 1) { + $(".dataTables_empty").html("loading..."); var current_rows = 0; var total_rows = 0; var page = 1; + + // load and render page content before we start the loop + setTimeout(() => { + get_page(page); + }, 10); - // write a function to get a page of data and add it to the table function get_page(page) { $.ajax({ url: `${$(".file_list").data("coverageurl")}/report_json?page=${page}`, @@ -65,7 +68,6 @@ $(document).ready(function() { } }); } - get_page(page); } From b23b9356914d978e181268e6e53fa2cd11cef212 Mon Sep 17 00:00:00 2001 From: Dan Mayer Date: Mon, 18 Mar 2024 23:37:01 -0600 Subject: [PATCH 06/18] add back deep links and consolidate JS approach for ajax paging and single page --- lib/coverband/utils/html_formatter.rb | 3 +- public/application.js | 118 +++++++++++--------------- 2 files changed, 52 insertions(+), 69 deletions(-) diff --git a/lib/coverband/utils/html_formatter.rb b/lib/coverband/utils/html_formatter.rb index 697df7b3..88d1ff4d 100644 --- a/lib/coverband/utils/html_formatter.rb +++ b/lib/coverband/utils/html_formatter.rb @@ -175,7 +175,8 @@ def shortened_filename(source_file) end def link_to_source_file(source_file) - %(#{shortened_filename source_file}) + data_loader_url = "#{base_path}load_file_details?filename=#{source_file.filename}" + %(#{shortened_filename source_file}) end end end diff --git a/public/application.js b/public/application.js index 10e409d5..7791dc6a 100644 --- a/public/application.js +++ b/public/application.js @@ -57,43 +57,31 @@ $(document).ready(function() { // so we don't get back 250 per page... to ensure we we need to account for filtered out and empty files // this 250 at the moment is synced to the 250 in the hash redis store current_rows += 250; //data["aaData"].length; - console.log(current_rows); - console.log(total_rows); $(".file_list.unsorted").dataTable().fnAddData(data["aaData"]); page += 1; // the page less than 100 is to stop infinite loop in case of folks never clearing out old coverage reports if (page < 100 && current_rows < total_rows) { get_page(page); } + // allow rendering to complete before we click the anchor + setTimeout(() => { + if (window.auto_click_anchor && $(window.auto_click_anchor).length > 0) { + console.log("found and click"); + $(window.auto_click_anchor).click(); + } + }, 20); } }); } } - - // Syntax highlight all files up front - deactivated - // $('.source_table pre code').each(function(i, e) {hljs.highlightBlock(e, ' ')}); src_link_click = (trigger_element) => { - // Get the source file element that corresponds to the clicked element - var source_table = $(".shared_source_table"); var loader_url = $(trigger_element).attr("data-loader-url"); + auto_click_anchor = null; $(trigger_element).colorbox(jQuery.extend(colorbox_options, { href: loader_url})); - - // If not highlighted yet, do it! - if (!source_table.hasClass("highlighted")) { - source_table.find("pre code").each(function(i, e) { - hljs.highlightBlock(e, " "); - }); - source_table.addClass("highlighted"); - } }; window.src_link_click = src_link_click; - // Syntax highlight source files on first toggle of the file view popup - $("a.src_link").click(src_link_click(this)); - - var prev_anchor; - var curr_anchor; var colorbox_options = { open: true, transition: "none", @@ -102,46 +90,21 @@ $(document).ready(function() { width: "95%", height: "95%", onLoad: function() { - // TODO: move source highlighting here - prev_anchor = curr_anchor ? curr_anchor : jQuery.url.attr("anchor"); - curr_anchor = this.href.split("#")[1]; - window.location.hash = curr_anchor; + // If not highlighted yet, do it! + var source_table = $(".shared_source_table"); + if (!source_table.hasClass("highlighted")) { + source_table.find("pre code").each(function(i, e) { + hljs.highlightBlock(e, " "); + }); + source_table.addClass("highlighted"); + } + window.location.hash = this.href.split("#")[1]; }, onCleanup: function() { - if (prev_anchor && prev_anchor != curr_anchor) { - $('a[href="#' + prev_anchor + '"]').click(); - curr_anchor = prev_anchor; - } else { - $(".group_tabs a:first").click(); - prev_anchor = curr_anchor; - curr_anchor = $(".group_tabs a:first").attr("href"); - } - window.location.hash = curr_anchor; + window.location.hash = $(".group_tabs a:first").attr("href"); } } - src_link_colorbox = (trigger_element) => { - $(trigger_element).colorbox(colorbox_options); - }; - window.src_link_colorbox = src_link_colorbox; - - // Set-up of popup for source file views - // TODO: drop the static source view even for not paged coverband, then delete all this - $("a.src_link").colorbox(colorbox_options); - - window.onpopstate = function(event) { - if (location.hash.substring(0, 2) == "#_") { - $.colorbox.close(); - curr_anchor = jQuery.url.attr("anchor"); - } else { - if ($("#colorbox").is(":hidden")) { - console.log("pop"); - // $('a.src_link[href="' + location.hash + '"]').colorbox({ open: true }); - $('.shared_source_table').colorbox({ open: true }); - } - } - }; - // Hide src files and file list container after load $(".source_files").hide(); $(".file_list_container").hide(); @@ -159,15 +122,20 @@ $(document).ready(function() { .find(".covered_percent") .first() .html(); + if (covered_percent) { + covered_percent = "(" + covered_percent + ")"; + } else { + covered_percent = ""; + } $(".group_tabs").append( '
  • ' + group_name + - " (" + + " " + covered_percent + - ")
  • " + "" ); }); @@ -199,15 +167,26 @@ $(document).ready(function() { .addClass("active"); } $(".file_list_container").hide(); - $(".file_list_container" + $(this).attr("href")).show(); - window.location.href = - window.location.href.split("#")[0] + - $(this) - .attr("href") - .replace("#", "#_"); - + $(".file_list_container" + $(this).attr("href")).show(function() { + // If we have an anchor to click, click it + // allow rendering to complete before we click the anchor + setTimeout(() => { + if (window.auto_click_anchor && $(window.auto_click_anchor).length > 0) { + $(window.auto_click_anchor).click(); + } + }, 20); + }); + // Below the #_ is a hack to show we have processed the hash change + if (!window.auto_click_anchor) { + window.location.href = + window.location.href.split("#")[0] + + $(this) + .attr("href") + .replace("#", "#_"); + } + // Force favicon reload - otherwise the location change containing anchor would drop the favicon... - // Works only on firefox, but still... - Anyone know a better solution to force favicon on local file? + // Works only on firefox, but still... - Anyone know a better solution to force favicon on local relative file path? $('link[rel="shortcut icon"]').remove(); $("head").append( ' 0) { $(".group_tabs a." + anchor.replace("_", "")).click(); } } } else { + // No anchor, so click the first navigation tab $(".group_tabs a:first").click(); } From b9e99a4666025f2ef5bc92d1988d7f4754130d49 Mon Sep 17 00:00:00 2001 From: Dan Mayer Date: Tue, 19 Mar 2024 21:32:18 -0600 Subject: [PATCH 07/18] remove webpager in favor of option --- lib/coverband.rb | 16 --------------- lib/coverband/configuration.rb | 2 +- lib/coverband/reporters/json_report.rb | 2 +- lib/coverband/reporters/web.rb | 20 ++++++++++++++---- lib/coverband/reporters/web_pager.rb | 28 -------------------------- public/application.js | 3 ++- 6 files changed, 20 insertions(+), 51 deletions(-) delete mode 100644 lib/coverband/reporters/web_pager.rb diff --git a/lib/coverband.rb b/lib/coverband.rb index 44243222..e171b014 100644 --- a/lib/coverband.rb +++ b/lib/coverband.rb @@ -130,7 +130,6 @@ class Web ### def initialize require "coverband/reporters/web" - require "coverband/reporters/web_pager" require "coverband/utils/html_formatter" require "coverband/utils/result" require "coverband/utils/file_list" @@ -148,19 +147,4 @@ def self.call(env) end end end - - module Reporters - class WebPager < Web - def initialize - require "coverband/reporters/web" - require "coverband/reporters/web_pager" - super - end - - def self.call(env) - @app ||= new - @app.call(env) - end - end - end end diff --git a/lib/coverband/configuration.rb b/lib/coverband/configuration.rb index fa348fff..6b19d202 100644 --- a/lib/coverband/configuration.rb +++ b/lib/coverband/configuration.rb @@ -11,7 +11,7 @@ class Configuration :view_tracker, :defer_eager_loading_data, :track_routes, :route_tracker, :track_translations, :translations_tracker, - :trackers, :csp_policy + :trackers, :csp_policy, :paged_reporting attr_writer :logger, :s3_region, :s3_bucket, :s3_access_key_id, :s3_secret_access_key, :password, :api_key, :service_url, :coverband_timeout, :service_dev_mode, diff --git a/lib/coverband/reporters/json_report.rb b/lib/coverband/reporters/json_report.rb index 35b297c2..0c4fc7bd 100644 --- a/lib/coverband/reporters/json_report.rb +++ b/lib/coverband/reporters/json_report.rb @@ -45,7 +45,7 @@ def report_as_json source_class = data[:never_loaded] ? 'strong red' : 'strong' data_loader_url = "#{base_path}load_file_details?filename=#{data[:filename]}" # class=\"src_link cboxElement\ - link = "#{key}" + link = "#{key}" # Started GET "/config/coverage/load_file_details?filename=/home/danmayer/projects/coverband_rails_example/app/jobs/application_job.rb" for ::1 at 2024-03-05 16:02:33 -0700 # class="<%= coverage_css_class(source_file.covered_percent) %> strong" row_data << [ diff --git a/lib/coverband/reporters/web.rb b/lib/coverband/reporters/web.rb index 6c65af34..dd3d0c62 100644 --- a/lib/coverband/reporters/web.rb +++ b/lib/coverband/reporters/web.rb @@ -112,11 +112,17 @@ def call(env) def index notice = "Notice: #{Rack::Utils.escape_html(request.params["notice"])}
    " notice = request.params["notice"] ? notice : "" - Coverband::Reporters::HTMLReport.new(Coverband.configuration.store, + page = (request.params["page"] || 1).to_i + options = { static: false, base_path: base_path, notice: notice, - open_report: false).report + open_report: false + } + options[:page] = page if Coverband.configuration.paged_reporting == true + Coverband::Reporters::HTMLReport.new(Coverband.configuration.store, + options + ).report end def json @@ -142,10 +148,16 @@ def settings def display_abstract_tracker(tracker) notice = "Notice: #{Rack::Utils.escape_html(request.params["notice"])}
    " notice = request.params["notice"] ? notice : "" - Coverband::Utils::HTMLFormatter.new(nil, + page = (request.params["page"] || 1).to_i + options = { tracker: tracker, notice: notice, - base_path: base_path).format_abstract_tracker! + base_path: base_path + } + options[:page] = page if Coverband.configuration.paged_reporting == true + Coverband::Utils::HTMLFormatter.new(nil, + options + ).format_abstract_tracker! end def view_tracker_data diff --git a/lib/coverband/reporters/web_pager.rb b/lib/coverband/reporters/web_pager.rb deleted file mode 100644 index c91bc89b..00000000 --- a/lib/coverband/reporters/web_pager.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -require "base64" -require "coverband" - -begin - require "rack" -rescue LoadError - puts "error loading Coverband web reporter as Rack is not available" -end - -module Coverband - module Reporters - class WebPager < Web - def index - notice = "Notice: #{Rack::Utils.escape_html(request.params["notice"])}
    " - notice = request.params["notice"] ? notice : "" - # TODO: remove the call to the store render empty table - Coverband::Reporters::HTMLReport.new(Coverband.configuration.store, - page: (request.params["page"] || 1).to_i, - static: false, - base_path: base_path, - notice: notice, - open_report: false).report - end - end - end -end diff --git a/public/application.js b/public/application.js index 7791dc6a..fee6d983 100644 --- a/public/application.js +++ b/public/application.js @@ -65,11 +65,12 @@ $(document).ready(function() { } // allow rendering to complete before we click the anchor setTimeout(() => { + console.log("auto_click_anchor", window.auto_click_anchor); if (window.auto_click_anchor && $(window.auto_click_anchor).length > 0) { console.log("found and click"); $(window.auto_click_anchor).click(); } - }, 20); + }, 25); } }); } From 5f2a8448d75765792c4a862b9cae60eab6301e56 Mon Sep 17 00:00:00 2001 From: Dan Mayer Date: Wed, 20 Mar 2024 21:07:26 -0600 Subject: [PATCH 08/18] add back file and runtime css highlighting --- lib/coverband/reporters/json_report.rb | 17 +++++++++++++++-- public/application.css | 13 ++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/lib/coverband/reporters/json_report.rb b/lib/coverband/reporters/json_report.rb index 0c4fc7bd..00adfe00 100644 --- a/lib/coverband/reporters/json_report.rb +++ b/lib/coverband/reporters/json_report.rb @@ -30,6 +30,18 @@ def report private + def coverage_css_class(covered_percent) + if covered_percent.nil? + "" + elsif covered_percent > 90 + "green" + elsif covered_percent > 80 + "yellow" + else + "red" + end + end + def report_as_json result = Coverband::Utils::Results.new(filtered_report_files) source_files = result.source_files @@ -45,13 +57,14 @@ def report_as_json source_class = data[:never_loaded] ? 'strong red' : 'strong' data_loader_url = "#{base_path}load_file_details?filename=#{data[:filename]}" # class=\"src_link cboxElement\ - link = "#{key}" + link = "#{key}" # Started GET "/config/coverage/load_file_details?filename=/home/danmayer/projects/coverband_rails_example/app/jobs/application_job.rb" for ::1 at 2024-03-05 16:02:33 -0700 # class="<%= coverage_css_class(source_file.covered_percent) %> strong" + runtime_percentage = "#{data[:runtime_percentage]}" row_data << [ link, data[:covered_percent].to_s, - data[:runtime_percentage].to_s, + runtime_percentage, data[:lines_of_code].to_s, (data[:lines_covered] + data[:lines_missed]).to_s, data[:lines_covered].to_s, diff --git a/public/application.css b/public/application.css index e07b31e7..20561086 100644 --- a/public/application.css +++ b/public/application.css @@ -687,9 +687,17 @@ a.src_link { background: url("./magnify.png") no-repeat left 50%; padding-left: 18px; } +a.src_link.strong { + font-weight: bold; +} + .red a.src_link { color: #990000; - } +} + +a.src_link.red { + color: #990000; +} tr, td { margin: 0; @@ -817,6 +825,9 @@ td { .red { color: #990000; } +span.red { + color: #990000; } + .yellow { color: #ddaa00; } From 3782e649c47de1f66439fd45d1e73cbfa7c14c41 Mon Sep 17 00:00:00 2001 From: Dan Mayer Date: Thu, 21 Mar 2024 21:19:27 -0600 Subject: [PATCH 09/18] add covered percentage css --- lib/coverband/reporters/json_report.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/coverband/reporters/json_report.rb b/lib/coverband/reporters/json_report.rb index 00adfe00..1f1f2e07 100644 --- a/lib/coverband/reporters/json_report.rb +++ b/lib/coverband/reporters/json_report.rb @@ -56,14 +56,12 @@ def report_as_json data[:files].each_pair do |key, data| source_class = data[:never_loaded] ? 'strong red' : 'strong' data_loader_url = "#{base_path}load_file_details?filename=#{data[:filename]}" - # class=\"src_link cboxElement\ link = "#{key}" - # Started GET "/config/coverage/load_file_details?filename=/home/danmayer/projects/coverband_rails_example/app/jobs/application_job.rb" for ::1 at 2024-03-05 16:02:33 -0700 - # class="<%= coverage_css_class(source_file.covered_percent) %> strong" runtime_percentage = "#{data[:runtime_percentage]}" + covered_percent = "#{data[:covered_percent]}" row_data << [ link, - data[:covered_percent].to_s, + covered_percent, runtime_percentage, data[:lines_of_code].to_s, (data[:lines_covered] + data[:lines_missed]).to_s, From 241a04563dc343d59632a362359880eff0ce7e5c Mon Sep 17 00:00:00 2001 From: Dan Mayer Date: Mon, 1 Apr 2024 20:33:09 -0600 Subject: [PATCH 10/18] remove the spy gem as that related code will be in another PR --- coverband.gemspec | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/coverband.gemspec b/coverband.gemspec index bed113c0..69a5e066 100644 --- a/coverband.gemspec +++ b/coverband.gemspec @@ -34,12 +34,13 @@ Gem::Specification.new do |spec| spec.add_development_dependency "capybara" spec.add_development_dependency "m" spec.add_development_dependency "memory_profiler" - # breaking change in minitest and mocha + # breaking change in minitest and mocha... + # note: we are also adding 'spy' as mocha doesn't want us to spy on redis calls... + # ^^^ probably need a large test cleanup refactor spec.add_development_dependency "minitest", "= 5.18.1" spec.add_development_dependency "minitest-fork_executor" spec.add_development_dependency "minitest-stub-const" spec.add_development_dependency "mocha", "~> 1.7.0" - # spec.add_development_dependency "spy" spec.add_development_dependency "rack" spec.add_development_dependency "rack-test" spec.add_development_dependency "rake" From a759388975245fcdf66cb53b023b917beb21053d Mon Sep 17 00:00:00 2001 From: Dan Mayer Date: Tue, 2 Apr 2024 22:55:10 -0600 Subject: [PATCH 11/18] fix eager loading --- lib/coverband/adapters/hash_redis_store.rb | 46 +++++++++------------- lib/coverband/configuration.rb | 8 +++- lib/coverband/reporters/web.rb | 4 +- lib/coverband/utils/results.rb | 2 + 4 files changed, 29 insertions(+), 31 deletions(-) diff --git a/lib/coverband/adapters/hash_redis_store.rb b/lib/coverband/adapters/hash_redis_store.rb index ab99a0ba..6cb09a5d 100644 --- a/lib/coverband/adapters/hash_redis_store.rb +++ b/lib/coverband/adapters/hash_redis_store.rb @@ -172,7 +172,7 @@ def save_report(report) @redis.sadd(files_key, keys) if keys.any? end - # TODO: refactor this and the method below and consider removing all the cached results stuff + # TODO: refactor this and the coverage_for_types method below and consider removing all the cached results stuff def coverage(local_type = nil, opts = {}) page_size = opts[:page_size] || 250 cached_results = @get_coverage_cache.fetch(local_type || type) do |sleep_time| @@ -184,7 +184,7 @@ def coverage(local_type = nil, opts = {}) else files_set(local_type) end - # use batches with a sleep in between to avoid overloading redis + # below uses batches with a sleep in between to avoid overloading redis files_set.each_slice(page_size).flat_map do |key_batch| sleep sleep_time @redis.pipelined do |pipeline| @@ -200,33 +200,25 @@ def coverage(local_type = nil, opts = {}) end end - # TODO: fix this before shipping main line release - # def split_coverage(types, coverage_cache, options = {}) - # if types.is_a?(Array) - # coverage_for_types(types, options) - # else - # super - # end - # end - - # NOTE: when using paging we need to ensure we have the same set of files per page in runtime and eager - # TODO: This merge of eager and runtime isn't working fix later... + def split_coverage(types, coverage_cache, options = {}) + if types.is_a?(Array) && !options[:filename] && options[:page] + data = coverage_for_types(types, options) + coverage_cache[Coverband::RUNTIME_TYPE] = data[Coverband::RUNTIME_TYPE] + coverage_cache[Coverband::EAGER_TYPE] = data[Coverband::EAGER_TYPE] + data + else + super + end + end + def coverage_for_types(types, opts = {}) page_size = opts[:page_size] || 250 local_type = Coverband::RUNTIME_TYPE hash_data = {} - runtime_file_set = if opts[:page] - files_set(local_type).each_slice(page_size).to_a[opts[:page] - 1] || {} - elsif opts[:filename] - # TODO: this probably needs to be an exact match of the parsed cache key section - # match is a hack that will only kind of work - files_set(local_type).select{ |cache_key| cache_key.match(short_name(opts[:filename])) } || {} - else - files_set(local_type) - end - + runtime_file_set = files_set(local_type).each_slice(page_size).to_a[opts[:page] - 1] || [] + hash_data[Coverband::RUNTIME_TYPE] = runtime_file_set.each_slice(page_size).flat_map do |key_batch| @redis.pipelined do |pipeline| key_batch.each do |key| @@ -235,12 +227,12 @@ def coverage_for_types(types, opts = {}) end end - # TODO: debug the set isn't just paths it has other key details including coverage type so below probalby fails - # match is a hack that will work a sometimes... fix this but it will prove out if this solves the perf issue + eager_key_pre = key_prefix(Coverband::EAGER_TYPE) + runtime_key_pre = key_prefix(Coverband::RUNTIME_TYPE) matched_file_set = files_set(Coverband::EAGER_TYPE) .select { |eager_key, val| runtime_file_set.any?{ |runtime_key| - (eager_key.match(/\.\.(.*).rb/) && eager_key.match(/\.\.(.*).rb/)[0]==runtime_key.match(/\.\.(.*).rb/)[0]) } - } || {} + (eager_key.sub(eager_key_pre, "") == runtime_key.sub(runtime_key_pre, "")) } + } || [] hash_data[Coverband::EAGER_TYPE] = matched_file_set.each_slice(page_size).flat_map do |key_batch| @redis.pipelined do |pipeline| key_batch.each do |key| diff --git a/lib/coverband/configuration.rb b/lib/coverband/configuration.rb index 6b19d202..0f5f00ee 100644 --- a/lib/coverband/configuration.rb +++ b/lib/coverband/configuration.rb @@ -11,13 +11,13 @@ class Configuration :view_tracker, :defer_eager_loading_data, :track_routes, :route_tracker, :track_translations, :translations_tracker, - :trackers, :csp_policy, :paged_reporting + :trackers, :csp_policy attr_writer :logger, :s3_region, :s3_bucket, :s3_access_key_id, :s3_secret_access_key, :password, :api_key, :service_url, :coverband_timeout, :service_dev_mode, :service_test_mode, :process_type, :track_views, :redis_url, :background_reporting_sleep_seconds, :reporting_wiggle, - :send_deferred_eager_loading_data + :send_deferred_eager_loading_data, :paged_reporting attr_reader :track_gems, :ignore, :use_oneshot_lines_coverage @@ -293,6 +293,10 @@ def send_deferred_eager_loading_data? @send_deferred_eager_loading_data end + def paged_reporting + !!@paged_reporting + end + def service_disabled_dev_test_env? return false unless service? diff --git a/lib/coverband/reporters/web.rb b/lib/coverband/reporters/web.rb index dd3d0c62..e3a69082 100644 --- a/lib/coverband/reporters/web.rb +++ b/lib/coverband/reporters/web.rb @@ -119,7 +119,7 @@ def index notice: notice, open_report: false } - options[:page] = page if Coverband.configuration.paged_reporting == true + options[:page] = page if Coverband.configuration.paged_reporting Coverband::Reporters::HTMLReport.new(Coverband.configuration.store, options ).report @@ -154,7 +154,7 @@ def display_abstract_tracker(tracker) notice: notice, base_path: base_path } - options[:page] = page if Coverband.configuration.paged_reporting == true + options[:page] = page if Coverband.configuration.paged_reporting Coverband::Utils::HTMLFormatter.new(nil, options ).format_abstract_tracker! diff --git a/lib/coverband/utils/results.rb b/lib/coverband/utils/results.rb index 15d97b7e..aff0fd1e 100644 --- a/lib/coverband/utils/results.rb +++ b/lib/coverband/utils/results.rb @@ -44,6 +44,8 @@ def runtime_relavent_lines(source_file) eager_file = get_eager_file(source_file) runtime_file = get_runtime_file(source_file) + return 0.0 unless runtime_file + return runtime_file.covered_lines_count unless eager_file eager_file.relevant_lines - eager_file.covered_lines_count From 1d3b6293030772b7dbc59722eb693cbe5bc034a5 Mon Sep 17 00:00:00 2001 From: Dan Mayer Date: Wed, 3 Apr 2024 21:21:02 -0600 Subject: [PATCH 12/18] fix standard and add spec coverage --- lib/coverband.rb | 4 ++-- lib/coverband/adapters/hash_redis_store.rb | 8 +++++--- lib/coverband/reporters/json_report.rb | 2 +- lib/coverband/reporters/web.rb | 6 ++---- lib/coverband/utils/results.rb | 2 +- test/coverband/utils/results_test.rb | 19 +++++++++++++++++-- 6 files changed, 28 insertions(+), 13 deletions(-) diff --git a/lib/coverband.rb b/lib/coverband.rb index e171b014..62e8a317 100644 --- a/lib/coverband.rb +++ b/lib/coverband.rb @@ -91,8 +91,8 @@ def self.eager_loading_coverage! coverage_instance.eager_loading! end - def self.eager_loading_coverage(&block) - coverage_instance.eager_loading(&block) + def self.eager_loading_coverage(...) + coverage_instance.eager_loading(...) end def self.runtime_coverage! diff --git a/lib/coverband/adapters/hash_redis_store.rb b/lib/coverband/adapters/hash_redis_store.rb index 6cb09a5d..4390f701 100644 --- a/lib/coverband/adapters/hash_redis_store.rb +++ b/lib/coverband/adapters/hash_redis_store.rb @@ -180,7 +180,7 @@ def coverage(local_type = nil, opts = {}) files_set(local_type).each_slice(page_size).to_a[opts[:page] - 1] || {} elsif opts[:filename] # TODO: this probably needs to be an exact match of the parsed cache key section - files_set(local_type).select{ |cache_key| cache_key.match(short_name(opts[:filename])) } || {} + files_set(local_type).select { |cache_key| cache_key.match(short_name(opts[:filename])) } || {} else files_set(local_type) end @@ -230,8 +230,10 @@ def coverage_for_types(types, opts = {}) eager_key_pre = key_prefix(Coverband::EAGER_TYPE) runtime_key_pre = key_prefix(Coverband::RUNTIME_TYPE) matched_file_set = files_set(Coverband::EAGER_TYPE) - .select { |eager_key, val| runtime_file_set.any?{ |runtime_key| - (eager_key.sub(eager_key_pre, "") == runtime_key.sub(runtime_key_pre, "")) } + .select { |eager_key, val| + runtime_file_set.any? { |runtime_key| + (eager_key.sub(eager_key_pre, "") == runtime_key.sub(runtime_key_pre, "")) + } } || [] hash_data[Coverband::EAGER_TYPE] = matched_file_set.each_slice(page_size).flat_map do |key_batch| @redis.pipelined do |pipeline| diff --git a/lib/coverband/reporters/json_report.rb b/lib/coverband/reporters/json_report.rb index 1f1f2e07..6d47e380 100644 --- a/lib/coverband/reporters/json_report.rb +++ b/lib/coverband/reporters/json_report.rb @@ -54,7 +54,7 @@ def report_as_json if as_report row_data = [] data[:files].each_pair do |key, data| - source_class = data[:never_loaded] ? 'strong red' : 'strong' + source_class = data[:never_loaded] ? "strong red" : "strong" data_loader_url = "#{base_path}load_file_details?filename=#{data[:filename]}" link = "#{key}" runtime_percentage = "#{data[:runtime_percentage]}" diff --git a/lib/coverband/reporters/web.rb b/lib/coverband/reporters/web.rb index e3a69082..de74531c 100644 --- a/lib/coverband/reporters/web.rb +++ b/lib/coverband/reporters/web.rb @@ -121,8 +121,7 @@ def index } options[:page] = page if Coverband.configuration.paged_reporting Coverband::Reporters::HTMLReport.new(Coverband.configuration.store, - options - ).report + options).report end def json @@ -156,8 +155,7 @@ def display_abstract_tracker(tracker) } options[:page] = page if Coverband.configuration.paged_reporting Coverband::Utils::HTMLFormatter.new(nil, - options - ).format_abstract_tracker! + options).format_abstract_tracker! end def view_tracker_data diff --git a/lib/coverband/utils/results.rb b/lib/coverband/utils/results.rb index aff0fd1e..a3c97042 100644 --- a/lib/coverband/utils/results.rb +++ b/lib/coverband/utils/results.rb @@ -44,7 +44,7 @@ def runtime_relavent_lines(source_file) eager_file = get_eager_file(source_file) runtime_file = get_runtime_file(source_file) - return 0.0 unless runtime_file + return 0 unless runtime_file return runtime_file.covered_lines_count unless eager_file diff --git a/test/coverband/utils/results_test.rb b/test/coverband/utils/results_test.rb index a3939768..9489be4a 100644 --- a/test/coverband/utils/results_test.rb +++ b/test/coverband/utils/results_test.rb @@ -7,12 +7,14 @@ let(:source_file) { Coverband::Utils::SourceFile.new(source_fixture("app/models/user.rb"), run_lines) } let(:eager_lines) { [nil, 1, 1, 0, nil, nil, 1, 0, nil, nil] } let(:run_lines) { [nil, nil, nil, 1, nil, nil, nil, nil, nil, nil] } + let(:missing_run_coverage_file) { false } let(:original_result) do orig = { Coverband::MERGED_TYPE => {source_fixture("app/models/user.rb") => eager_lines} } orig[Coverband::EAGER_TYPE] = {source_fixture("app/models/user.rb") => eager_lines} if eager_lines orig[Coverband::RUNTIME_TYPE] = {source_fixture("app/models/user.rb") => run_lines} if run_lines + orig[Coverband::RUNTIME_TYPE] = {"random.rb" => [nil, 1, nil]} if missing_run_coverage_file orig end subject { Coverband::Utils::Results.new(original_result) } @@ -27,15 +29,28 @@ end end - describe "runtime relevant lines when no runtime coverage exists" do + describe "runtime relevant lines when no runtime coverage file matches" do let(:run_lines) { nil } + it "has correct runtime relevant coverage" do + assert_equal 0, subject.runtime_relevant_coverage(source_file) + end + it "has correct runtime relevant lines" do + assert_equal 0, subject.runtime_relavent_lines(source_file) + end + end + + describe "runtime relevant lines when no runtime coverage exists" do + let(:run_lines) { nil } + let(:missing_run_coverage_file) { true } + + it "has correct runtime relevant coverage" do assert_equal 0.0, subject.runtime_relevant_coverage(source_file) end it "has correct runtime relevant lines" do - assert_equal 2, subject.runtime_relavent_lines(source_file) + assert_equal 0, subject.runtime_relavent_lines(source_file) end end From 8f3dfc06ce54108061fcb1e8f0e2646043ea501b Mon Sep 17 00:00:00 2001 From: Dan Mayer Date: Thu, 4 Apr 2024 22:08:10 -0600 Subject: [PATCH 13/18] reduce todos and duplication, fix issue on rendering during ajax loading, remove consoles --- lib/coverband/adapters/hash_redis_store.rb | 10 ++++++---- public/application.js | 21 +++++++++++---------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/lib/coverband/adapters/hash_redis_store.rb b/lib/coverband/adapters/hash_redis_store.rb index 4390f701..522a4345 100644 --- a/lib/coverband/adapters/hash_redis_store.rb +++ b/lib/coverband/adapters/hash_redis_store.rb @@ -172,15 +172,17 @@ def save_report(report) @redis.sadd(files_key, keys) if keys.any? end - # TODO: refactor this and the coverage_for_types method below and consider removing all the cached results stuff + # NOTE: This method should be used for full coverage or filename coverage look ups + # When paging code should use coverage_for_types and pull eager and runtime together as matched pairs def coverage(local_type = nil, opts = {}) page_size = opts[:page_size] || 250 cached_results = @get_coverage_cache.fetch(local_type || type) do |sleep_time| files_set = if opts[:page] - files_set(local_type).each_slice(page_size).to_a[opts[:page] - 1] || {} + raise "call coverage_for_types with paging" elsif opts[:filename] - # TODO: this probably needs to be an exact match of the parsed cache key section - files_set(local_type).select { |cache_key| cache_key.match(short_name(opts[:filename])) } || {} + type_key_prefix = key_prefix(local_type) + # NOTE: a better way to extract filename from key would be better + files_set(local_type).select { |cache_key| cache_key.sub(type_key_prefix, "").match(short_name(opts[:filename])) } || {} else files_set(local_type) end diff --git a/public/application.js b/public/application.js index fee6d983..e0ab1753 100644 --- a/public/application.js +++ b/public/application.js @@ -30,7 +30,7 @@ $(document).ready(function() { null, null, null - ] + ], }); // TODO: add support for searching on server side @@ -42,9 +42,10 @@ $(document).ready(function() { var page = 1; // load and render page content before we start the loop + // perhaps move this into a datatable ready event setTimeout(() => { get_page(page); - }, 10); + }, 1250); function get_page(page) { $.ajax({ @@ -59,18 +60,18 @@ $(document).ready(function() { current_rows += 250; //data["aaData"].length; $(".file_list.unsorted").dataTable().fnAddData(data["aaData"]); page += 1; - // the page less than 100 is to stop infinite loop in case of folks never clearing out old coverage reports - if (page < 100 && current_rows < total_rows) { - get_page(page); - } // allow rendering to complete before we click the anchor setTimeout(() => { - console.log("auto_click_anchor", window.auto_click_anchor); if (window.auto_click_anchor && $(window.auto_click_anchor).length > 0) { - console.log("found and click"); $(window.auto_click_anchor).click(); } - }, 25); + }, 50); + // the page less than 100 is to stop infinite loop in case of folks never clearing out old coverage reports + if (page < 100 && current_rows < total_rows) { + setTimeout(() => { + get_page(page); + }, 200); + } } }); } @@ -175,7 +176,7 @@ $(document).ready(function() { if (window.auto_click_anchor && $(window.auto_click_anchor).length > 0) { $(window.auto_click_anchor).click(); } - }, 20); + }, 30); }); // Below the #_ is a hack to show we have processed the hash change if (!window.auto_click_anchor) { From c01de46daa75cef59175cc1b911141f0e9bf0d54 Mon Sep 17 00:00:00 2001 From: Dan Mayer Date: Sun, 7 Apr 2024 22:47:38 -0600 Subject: [PATCH 14/18] fix double loading of source files --- public/application.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/public/application.js b/public/application.js index e0ab1753..57b364ec 100644 --- a/public/application.js +++ b/public/application.js @@ -82,12 +82,9 @@ $(document).ready(function() { auto_click_anchor = null; $(trigger_element).colorbox(jQuery.extend(colorbox_options, { href: loader_url})); }; - window.src_link_click = src_link_click; var colorbox_options = { - open: true, transition: "none", - // inline: true, opacity: 1, width: "95%", height: "95%", From ffce1a618c6e8fc53afca8ba9644b68da1596bf7 Mon Sep 17 00:00:00 2001 From: Dan Mayer Date: Mon, 8 Apr 2024 23:00:42 -0600 Subject: [PATCH 15/18] prepare next RC release --- lib/coverband/adapters/hash_redis_store.rb | 4 ++-- lib/coverband/reporters/json_report.rb | 5 +++-- lib/coverband/reporters/web.rb | 5 +---- lib/coverband/utils/html_formatter.rb | 7 ------- lib/coverband/version.rb | 2 +- public/application.css | 12 ++++++++++++ public/application.js | 6 ++++-- views/source_file_loader.erb | 7 ------- 8 files changed, 23 insertions(+), 25 deletions(-) delete mode 100644 views/source_file_loader.erb diff --git a/lib/coverband/adapters/hash_redis_store.rb b/lib/coverband/adapters/hash_redis_store.rb index 522a4345..f40f68f7 100644 --- a/lib/coverband/adapters/hash_redis_store.rb +++ b/lib/coverband/adapters/hash_redis_store.rb @@ -89,7 +89,7 @@ def unlock!(local_type) # used to store data to redis. It is changed only when breaking changes to our # redis format are required. ### - REDIS_STORAGE_FORMAT_VERSION = "coverband_hash_3_3" + REDIS_STORAGE_FORMAT_VERSION = "coverband_hash_4_0" JSON_PAYLOAD_EXPIRATION = 5 * 60 @@ -107,7 +107,7 @@ def initialize(redis, opts = {}) @relative_file_converter = opts[:relative_file_converter] || Utils::RelativeFileConverter @get_coverage_cache = if opts[:get_coverage_cache] - key_prefix = [REDIS_STORAGE_FORMAT_VERSION, @redis_namespace, "v2"].compact.join(".") + key_prefix = [REDIS_STORAGE_FORMAT_VERSION, @redis_namespace].compact.join(".") GetCoverageRedisCacheStore.new(redis, key_prefix) else GetCoverageNullCacheStore diff --git a/lib/coverband/reporters/json_report.rb b/lib/coverband/reporters/json_report.rb index 6d47e380..c900d96a 100644 --- a/lib/coverband/reporters/json_report.rb +++ b/lib/coverband/reporters/json_report.rb @@ -57,8 +57,9 @@ def report_as_json source_class = data[:never_loaded] ? "strong red" : "strong" data_loader_url = "#{base_path}load_file_details?filename=#{data[:filename]}" link = "#{key}" - runtime_percentage = "#{data[:runtime_percentage]}" - covered_percent = "#{data[:covered_percent]}" + # Hack to ensure the sorting works on percentage columns, the span is hidden but colors the cell and the text is used for sorting + covered_percent = "#{data[:covered_percent]}  " + runtime_percentage = "#{data[:runtime_percentage]} " row_data << [ link, covered_percent, diff --git a/lib/coverband/reporters/web.rb b/lib/coverband/reporters/web.rb index de74531c..314e2c9c 100644 --- a/lib/coverband/reporters/web.rb +++ b/lib/coverband/reporters/web.rb @@ -147,15 +147,12 @@ def settings def display_abstract_tracker(tracker) notice = "Notice: #{Rack::Utils.escape_html(request.params["notice"])}
    " notice = request.params["notice"] ? notice : "" - page = (request.params["page"] || 1).to_i options = { tracker: tracker, notice: notice, base_path: base_path } - options[:page] = page if Coverband.configuration.paged_reporting - Coverband::Utils::HTMLFormatter.new(nil, - options).format_abstract_tracker! + Coverband::Utils::HTMLFormatter.new(nil, options).format_abstract_tracker! end def view_tracker_data diff --git a/lib/coverband/utils/html_formatter.rb b/lib/coverband/utils/html_formatter.rb index 88d1ff4d..ae34ae16 100644 --- a/lib/coverband/utils/html_formatter.rb +++ b/lib/coverband/utils/html_formatter.rb @@ -117,13 +117,6 @@ def formatted_source_file(result, source_file) puts "Encoding error file:#{source_file.filename} Coverband/ERB error #{e.message}." end - # Returns the html to ajax load a given source_file - def formatted_source_file_loader(result, source_file) - template("source_file_loader").result(binding) - rescue Encoding::CompatibilityError => e - puts "Encoding error file:#{source_file.filename} Coverband/ERB error #{e.message}." - end - # Returns a table containing the given source files def formatted_file_list(title, result, source_files, options = {}) title_id = title.gsub(/^[^a-zA-Z]+/, "").gsub(/[^a-zA-Z0-9\-\_]/, "") diff --git a/lib/coverband/version.rb b/lib/coverband/version.rb index 6cede387..eb847d4a 100644 --- a/lib/coverband/version.rb +++ b/lib/coverband/version.rb @@ -5,5 +5,5 @@ # use format "4.2.1.rc.1" ~> 4.2.1.rc to prerelease versions like v4.2.1.rc.2 and v4.2.1.rc.3 ### module Coverband - VERSION = "6.0.3.rc.3" + VERSION = "6.0.3.rc.4" end diff --git a/public/application.css b/public/application.css index 20561086..fed4baab 100644 --- a/public/application.css +++ b/public/application.css @@ -828,6 +828,18 @@ td { span.red { color: #990000; } +td:has(> span.red) { + color: #990000; +} + +td:has(> span.yellow) { + color: #ddaa00; +} + +td:has(> span.green) { + color: #009900; +} + .yellow { color: #ddaa00; } diff --git a/public/application.js b/public/application.js index 57b364ec..f7b5bbca 100644 --- a/public/application.js +++ b/public/application.js @@ -16,7 +16,7 @@ $(document).ready(function() { }); // Configuration for fancy sortable tables for source file groups - $(".file_list").dataTable({ + var tableOptions = { aaSorting: [[1, "asc"]], bPaginate: false, bJQueryUI: true, @@ -31,7 +31,9 @@ $(document).ready(function() { null, null ], - }); + } + + $(".file_list").dataTable(tableOptions); // TODO: add support for searching on server side // best docs on our version of datatables 1.7 https://datatables.net/beta/1.7/examples/server_side/server_side.html diff --git a/views/source_file_loader.erb b/views/source_file_loader.erb deleted file mode 100644 index c98fb1b2..00000000 --- a/views/source_file_loader.erb +++ /dev/null @@ -1,7 +0,0 @@ -
    -
    - loading source data... -
    - loading -
    -
    From 66db31667db894affcfea24dbdce3f2ca1522d8c Mon Sep 17 00:00:00 2001 From: Dan Mayer Date: Sat, 20 Apr 2024 16:22:43 -0600 Subject: [PATCH 16/18] better UX experience when loading many pages --- lib/coverband/adapters/hash_redis_store.rb | 115 +++++++------- lib/coverband/configuration.rb | 176 +++++++++++---------- lib/coverband/reporters/json_report.rb | 28 ++-- public/application.js | 31 ++-- 4 files changed, 186 insertions(+), 164 deletions(-) diff --git a/lib/coverband/adapters/hash_redis_store.rb b/lib/coverband/adapters/hash_redis_store.rb index f40f68f7..fc5b1caf 100644 --- a/lib/coverband/adapters/hash_redis_store.rb +++ b/lib/coverband/adapters/hash_redis_store.rb @@ -1,13 +1,12 @@ # frozen_string_literal: true -require "securerandom" +require 'securerandom' module Coverband module Adapters class HashRedisStore < Base class GetCoverageNullCacheStore - def self.clear!(*_local_types) - end + def self.clear!(*_local_types); end def self.fetch(_local_type) yield(0) @@ -19,7 +18,7 @@ class GetCoverageRedisCacheStore def initialize(redis, key_prefix) @redis = redis - @key_prefix = [key_prefix, "get-coverage"].join(".") + @key_prefix = [key_prefix, 'get-coverage'].join('.') end def fetch(local_type) @@ -73,7 +72,7 @@ def set(local_type, value) # lock for at most 60 minutes def lock!(local_type) - @redis.set("#{@key_prefix}.lock.#{local_type}", "1", nx: true, ex: LOCK_LIMIT) + @redis.set("#{@key_prefix}.lock.#{local_type}", '1', nx: true, ex: LOCK_LIMIT) end def unlock!(local_type) @@ -81,15 +80,15 @@ def unlock!(local_type) end end - FILE_KEY = "file" - FILE_LENGTH_KEY = "file_length" + FILE_KEY = 'file' + FILE_LENGTH_KEY = 'file_length' META_DATA_KEYS = [DATA_KEY, FIRST_UPDATED_KEY, LAST_UPDATED_KEY, FILE_HASH].freeze ### # This key isn't related to the coverband version, but to the internal format # used to store data to redis. It is changed only when breaking changes to our # redis format are required. ### - REDIS_STORAGE_FORMAT_VERSION = "coverband_hash_4_0" + REDIS_STORAGE_FORMAT_VERSION = 'coverband_hash_4_0' JSON_PAYLOAD_EXPIRATION = 5 * 60 @@ -101,24 +100,24 @@ def initialize(redis, opts = {}) @save_report_batch_size = opts[:save_report_batch_size] || 100 @format_version = REDIS_STORAGE_FORMAT_VERSION @redis = redis - raise "HashRedisStore requires redis >= 2.6.0" unless supported? + raise 'HashRedisStore requires redis >= 2.6.0' unless supported? @ttl = opts[:ttl] @relative_file_converter = opts[:relative_file_converter] || Utils::RelativeFileConverter @get_coverage_cache = if opts[:get_coverage_cache] - key_prefix = [REDIS_STORAGE_FORMAT_VERSION, @redis_namespace].compact.join(".") - GetCoverageRedisCacheStore.new(redis, key_prefix) - else - GetCoverageNullCacheStore - end + key_prefix = [REDIS_STORAGE_FORMAT_VERSION, @redis_namespace].compact.join('.') + GetCoverageRedisCacheStore.new(redis, key_prefix) + else + GetCoverageNullCacheStore + end end def supported? - Gem::Version.new(@redis.info["redis_version"]) >= Gem::Version.new("2.6.0") - rescue Redis::CannotConnectError => error - Coverband.configuration.logger.info "Redis is not available (#{error}), Coverband not configured" - Coverband.configuration.logger.info "If this is a setup task like assets:precompile feel free to ignore" + Gem::Version.new(@redis.info['redis_version']) >= Gem::Version.new('2.6.0') + rescue Redis::CannotConnectError => e + Coverband.configuration.logger.info "Redis is not available (#{e}), Coverband not configured" + Coverband.configuration.logger.info 'If this is a setup task like assets:precompile feel free to ignore' end def clear! @@ -146,10 +145,10 @@ def clear_file!(file) def save_report(report) report_time = Time.now.to_i - updated_time = (type == Coverband::EAGER_TYPE) ? nil : report_time + updated_time = type == Coverband::EAGER_TYPE ? nil : report_time keys = [] report.each_slice(@save_report_batch_size) do |slice| - files_data = slice.map { |(file, data)| + files_data = slice.map do |(file, data)| relative_file = @relative_file_converter.convert(file) file_hash = file_hash(relative_file) key = key(relative_file, file_hash: file_hash) @@ -162,11 +161,11 @@ def save_report(report) report_time: report_time, updated_time: updated_time ) - } + end next unless files_data.any? - arguments_key = [@redis_namespace, SecureRandom.uuid].compact.join(".") - @redis.set(arguments_key, {ttl: @ttl, files_data: files_data}.to_json, ex: JSON_PAYLOAD_EXPIRATION) + arguments_key = [@redis_namespace, SecureRandom.uuid].compact.join('.') + @redis.set(arguments_key, { ttl: @ttl, files_data: files_data }.to_json, ex: JSON_PAYLOAD_EXPIRATION) @redis.evalsha(hash_incr_script, [arguments_key]) end @redis.sadd(files_key, keys) if keys.any? @@ -178,14 +177,16 @@ def coverage(local_type = nil, opts = {}) page_size = opts[:page_size] || 250 cached_results = @get_coverage_cache.fetch(local_type || type) do |sleep_time| files_set = if opts[:page] - raise "call coverage_for_types with paging" - elsif opts[:filename] - type_key_prefix = key_prefix(local_type) - # NOTE: a better way to extract filename from key would be better - files_set(local_type).select { |cache_key| cache_key.sub(type_key_prefix, "").match(short_name(opts[:filename])) } || {} - else - files_set(local_type) - end + raise 'call coverage_for_types with paging' + elsif opts[:filename] + type_key_prefix = key_prefix(local_type) + # NOTE: a better way to extract filename from key would be better + files_set(local_type).select do |cache_key| + cache_key.sub(type_key_prefix, '').match(short_name(opts[:filename])) + end || {} + else + files_set(local_type) + end # below uses batches with a sleep in between to avoid overloading redis files_set.each_slice(page_size).flat_map do |key_batch| sleep sleep_time @@ -213,13 +214,13 @@ def split_coverage(types, coverage_cache, options = {}) end end - def coverage_for_types(types, opts = {}) + def coverage_for_types(_types, opts = {}) page_size = opts[:page_size] || 250 - - local_type = Coverband::RUNTIME_TYPE hash_data = {} - runtime_file_set = files_set(local_type).each_slice(page_size).to_a[opts[:page] - 1] || [] + runtime_file_set = files_set(Coverband::RUNTIME_TYPE) + @cached_file_count = runtime_file_set.length + runtime_file_set = runtime_file_set.each_slice(page_size).to_a[opts[:page] - 1] || [] hash_data[Coverband::RUNTIME_TYPE] = runtime_file_set.each_slice(page_size).flat_map do |key_batch| @redis.pipelined do |pipeline| @@ -232,11 +233,11 @@ def coverage_for_types(types, opts = {}) eager_key_pre = key_prefix(Coverband::EAGER_TYPE) runtime_key_pre = key_prefix(Coverband::RUNTIME_TYPE) matched_file_set = files_set(Coverband::EAGER_TYPE) - .select { |eager_key, val| - runtime_file_set.any? { |runtime_key| - (eager_key.sub(eager_key_pre, "") == runtime_key.sub(runtime_key_pre, "")) - } - } || [] + .select do |eager_key, _val| + runtime_file_set.any? do |runtime_key| + (eager_key.sub(eager_key_pre, '') == runtime_key.sub(runtime_key_pre, '')) + end + end || [] hash_data[Coverband::EAGER_TYPE] = matched_file_set.each_slice(page_size).flat_map do |key_batch| @redis.pipelined do |pipeline| key_batch.each do |key| @@ -254,24 +255,28 @@ def coverage_for_types(types, opts = {}) end def short_name(filename) - filename.sub(/^#{Coverband.configuration.root}/, ".") - .gsub(%r{^\.\/}, "") + filename.sub(/^#{Coverband.configuration.root}/, '.') + .gsub(%r{^\./}, '') end def file_count(local_type = nil) files_set(local_type).count { |filename| !Coverband.configuration.ignore.any? { |i| filename.match(i) } } end + def cached_file_count + @cached_file_count ||= file_count(Coverband::RUNTIME_TYPE) + end + def raw_store @redis end def size - "not available" + 'not available' end def size_in_mib - "not available" + 'not available' end private @@ -283,9 +288,13 @@ def add_coverage_for_file(data_from_redis, hash) return unless file_hash(file) == data_from_redis[FILE_HASH] data = coverage_data_from_redis(data_from_redis) - hash[file] = data_from_redis.select { |meta_data_key, _value| META_DATA_KEYS.include?(meta_data_key) }.merge!("data" => data) - hash[file][LAST_UPDATED_KEY] = (hash[file][LAST_UPDATED_KEY].nil? || hash[file][LAST_UPDATED_KEY] == "") ? nil : hash[file][LAST_UPDATED_KEY].to_i - hash[file].merge!(LAST_UPDATED_KEY => hash[file][LAST_UPDATED_KEY], FIRST_UPDATED_KEY => hash[file][FIRST_UPDATED_KEY].to_i) + hash[file] = data_from_redis.select do |meta_data_key, _value| + META_DATA_KEYS.include?(meta_data_key) + end.merge!('data' => data) + hash[file][LAST_UPDATED_KEY] = + hash[file][LAST_UPDATED_KEY].nil? || hash[file][LAST_UPDATED_KEY] == '' ? nil : hash[file][LAST_UPDATED_KEY].to_i + hash[file].merge!(LAST_UPDATED_KEY => hash[file][LAST_UPDATED_KEY], + FIRST_UPDATED_KEY => hash[file][FIRST_UPDATED_KEY].to_i) end def coverage_data_from_redis(data_from_redis) @@ -297,9 +306,9 @@ def coverage_data_from_redis(data_from_redis) end def script_input(key:, file:, file_hash:, data:, report_time:, updated_time:) - coverage_data = data.each_with_index.each_with_object({}) { |(coverage, index), hash| + coverage_data = data.each_with_index.each_with_object({}) do |(coverage, index), hash| hash[index] = coverage if coverage - } + end meta = { first_updated_at: report_time, file: file, @@ -321,8 +330,8 @@ def hash_incr_script def lua_script_content File.read(File.join( - File.dirname(__FILE__), "../../../lua/lib/persist-coverage.lua" - )) + File.dirname(__FILE__), '../../../lua/lib/persist-coverage.lua' + )) end def values_from_redis(local_type, files) @@ -346,12 +355,12 @@ def files_key(local_type = nil) end def key(file, local_type = nil, file_hash:) - [key_prefix(local_type), file, file_hash].join(".") + [key_prefix(local_type), file, file_hash].join('.') end def key_prefix(local_type = nil) local_type ||= type - [@format_version, @redis_namespace, local_type].compact.join(".") + [@format_version, @redis_namespace, local_type].compact.join('.') end end end diff --git a/lib/coverband/configuration.rb b/lib/coverband/configuration.rb index 0f5f00ee..08ccbdb3 100644 --- a/lib/coverband/configuration.rb +++ b/lib/coverband/configuration.rb @@ -3,21 +3,21 @@ module Coverband class Configuration attr_accessor :root_paths, :root, - :verbose, - :reporter, :redis_namespace, :redis_ttl, - :background_reporting_enabled, - :test_env, :web_enable_clear, :gem_details, :web_debug, :report_on_exit, - :simulate_oneshot_lines_coverage, - :view_tracker, :defer_eager_loading_data, - :track_routes, :route_tracker, - :track_translations, :translations_tracker, - :trackers, :csp_policy + :verbose, + :reporter, :redis_namespace, :redis_ttl, + :background_reporting_enabled, + :test_env, :web_enable_clear, :gem_details, :web_debug, :report_on_exit, + :simulate_oneshot_lines_coverage, + :view_tracker, :defer_eager_loading_data, + :track_routes, :route_tracker, + :track_translations, :translations_tracker, + :trackers, :csp_policy attr_writer :logger, :s3_region, :s3_bucket, :s3_access_key_id, - :s3_secret_access_key, :password, :api_key, :service_url, :coverband_timeout, :service_dev_mode, - :service_test_mode, :process_type, :track_views, :redis_url, - :background_reporting_sleep_seconds, :reporting_wiggle, - :send_deferred_eager_loading_data, :paged_reporting + :s3_secret_access_key, :password, :api_key, :service_url, :coverband_timeout, :service_dev_mode, + :service_test_mode, :process_type, :track_views, :redis_url, + :background_reporting_sleep_seconds, :reporting_wiggle, + :send_deferred_eager_loading_data, :paged_reporting attr_reader :track_gems, :ignore, :use_oneshot_lines_coverage @@ -27,21 +27,21 @@ class Configuration # # * Perhaps detect heroku deployment ENV var opposed to tasks? ##### - IGNORE_TASKS = ["coverband:clear", - "coverband:coverage", - "coverband:coverage_server", - "coverband:migrate", - "assets:precompile", - "webpacker:compile", - "db:version", - "db:create", - "db:drop", - "db:seed", - "db:setup", - "db:test:prepare", - "db:structure:dump", - "db:structure:load", - "db:version"] + IGNORE_TASKS = ['coverband:clear', + 'coverband:coverage', + 'coverband:coverage_server', + 'coverband:migrate', + 'assets:precompile', + 'webpacker:compile', + 'db:version', + 'db:create', + 'db:drop', + 'db:seed', + 'db:setup', + 'db:test:prepare', + 'db:structure:dump', + 'db:structure:load', + 'db:version'] # Heroku when building assets runs code from a dynamic directory # /tmp was added to avoid coverage from /tmp/build directories during @@ -62,7 +62,7 @@ def reset @ignore = IGNORE_DEFAULTS.dup @search_paths = TRACKED_DEFAULT_PATHS.dup @verbose = false - @reporter = "scov" + @reporter = 'scov' @logger = nil @store = nil @background_reporting_enabled = true @@ -79,8 +79,8 @@ def reset @translations_tracker = nil @web_debug = false @report_on_exit = true - @use_oneshot_lines_coverage = ENV["ONESHOT"] || false - @simulate_oneshot_lines_coverage = ENV["SIMULATE_ONESHOT"] || false + @use_oneshot_lines_coverage = ENV['ONESHOT'] || false + @simulate_oneshot_lines_coverage = ENV['SIMULATE_ONESHOT'] || false @current_root = nil @all_root_paths = nil @all_root_patterns = nil @@ -124,28 +124,28 @@ def railtie! if Coverband.configuration.track_views Coverband.configuration.view_tracker = if Coverband.coverband_service? - Coverband::Collectors::ViewTrackerService.new - else - Coverband::Collectors::ViewTracker.new - end + Coverband::Collectors::ViewTrackerService.new + else + Coverband::Collectors::ViewTracker.new + end trackers << Coverband.configuration.view_tracker end trackers.each { |tracker| tracker.railtie! } - rescue Redis::CannotConnectError => error - Coverband.configuration.logger.info "Redis is not available (#{error}), Coverband not configured" - Coverband.configuration.logger.info "If this is a setup task like assets:precompile feel free to ignore" + rescue Redis::CannotConnectError => e + Coverband.configuration.logger.info "Redis is not available (#{e}), Coverband not configured" + Coverband.configuration.logger.info 'If this is a setup task like assets:precompile feel free to ignore' end def logger @logger ||= if defined?(Rails.logger) && Rails.logger - Rails.logger - else - Logger.new(STDOUT) - end + Rails.logger + else + Logger.new(STDOUT) + end end def password - @password || ENV["COVERBAND_PASSWORD"] + @password || ENV['COVERBAND_PASSWORD'] end # The adjustments here either protect the redis or service from being overloaded @@ -153,14 +153,14 @@ def password # if running your own redis increasing this number reduces load on the redis CPU def background_reporting_sleep_seconds @background_reporting_sleep_seconds ||= if service? - # default to 10m for service - (Coverband.configuration.coverband_env == "production") ? 600 : 60 - elsif store.is_a?(Coverband::Adapters::HashRedisStore) - # Default to 5 minutes if using the hash redis store - 300 - else - 60 - end + # default to 10m for service + Coverband.configuration.coverband_env == 'production' ? 600 : 60 + elsif store.is_a?(Coverband::Adapters::HashRedisStore) + # Default to 5 minutes if using the hash redis store + 300 + else + 60 + end end def reporting_wiggle @@ -169,18 +169,27 @@ def reporting_wiggle def store @store ||= if service? - raise "invalid configuration: unclear default store coverband expects either api_key or redis_url" if ENV["COVERBAND_REDIS_URL"] - require "coverband/adapters/web_service_store" - Coverband::Adapters::WebServiceStore.new(service_url) - else - Coverband::Adapters::RedisStore.new(Redis.new(url: redis_url), redis_store_options) - end + if ENV['COVERBAND_REDIS_URL'] + raise 'invalid configuration: unclear default store coverband expects either api_key or redis_url' + end + + require 'coverband/adapters/web_service_store' + Coverband::Adapters::WebServiceStore.new(service_url) + else + Coverband::Adapters::RedisStore.new(Redis.new(url: redis_url), redis_store_options) + end end def store=(store) - raise "Pass in an instance of Coverband::Adapters" unless store.is_a?(Coverband::Adapters::Base) - raise "invalid configuration: only coverband service expects an API Key" if api_key && store.class.to_s != "Coverband::Adapters::WebServiceStore" - raise "invalid configuration: coverband service shouldn't have redis url set" if ENV["COVERBAND_REDIS_URL"] && store.instance_of?(::Coverband::Adapters::WebServiceStore) + raise 'Pass in an instance of Coverband::Adapters' unless store.is_a?(Coverband::Adapters::Base) + if api_key && store.class.to_s != 'Coverband::Adapters::WebServiceStore' + raise 'invalid configuration: only coverband service expects an API Key' + end + if ENV['COVERBAND_REDIS_URL'] && + defined?(::Coverband::Adapters::WebServiceStore) && + store.instance_of?(::Coverband::Adapters::WebServiceStore) + raise "invalid configuration: coverband service shouldn't have redis url set" + end @store = store end @@ -195,7 +204,7 @@ def track_views # Search Paths ### def tracked_search_paths - "#{Coverband.configuration.current_root}/{#{@search_paths.join(",")}}/**/*.{rb}" + "#{Coverband.configuration.current_root}/{#{@search_paths.join(',')}}/**/*.{rb}" end ### @@ -212,7 +221,7 @@ def ignore=(ignored_array) ignored_array.map { |ignore_str| Regexp.new(ignore_str) } @ignore = (@ignore + ignored_array).uniq rescue RegexpError - logger.error "an invalid regular expression was passed in, ensure string are valid regex patterns #{ignored_array.join(",")}" + logger.error "an invalid regular expression was passed in, ensure string are valid regex patterns #{ignored_array.join(',')}" end def current_root @@ -235,50 +244,53 @@ def all_root_patterns def to_h instance_variables .each_with_object({}) do |var, hash| - hash[var.to_s.delete("@")] = instance_variable_get(var) unless SKIPPED_SETTINGS.include?(var.to_s) + hash[var.to_s.delete('@')] = instance_variable_get(var) unless SKIPPED_SETTINGS.include?(var.to_s) end end def use_oneshot_lines_coverage=(value) - raise(StandardError, "One shot line coverage is only available in ruby >= 2.6") unless one_shot_coverage_implemented_in_ruby_version? || !value + unless one_shot_coverage_implemented_in_ruby_version? || !value + raise(StandardError, + 'One shot line coverage is only available in ruby >= 2.6') + end @use_oneshot_lines_coverage = value end def one_shot_coverage_implemented_in_ruby_version? - Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.6.0") + Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.6.0') end def redis_url - @redis_url ||= ENV["COVERBAND_REDIS_URL"] || ENV["REDIS_URL"] + @redis_url ||= ENV['COVERBAND_REDIS_URL'] || ENV['REDIS_URL'] end def api_key - @api_key ||= ENV["COVERBAND_API_KEY"] + @api_key ||= ENV['COVERBAND_API_KEY'] end def service_url - @service_url ||= ENV["COVERBAND_URL"] || "https://coverband.io" + @service_url ||= ENV['COVERBAND_URL'] || 'https://coverband.io' end def coverband_env - ENV["RACK_ENV"] || ENV["RAILS_ENV"] || ((defined?(Rails) && Rails.respond_to?(:env)) ? Rails.env : "unknown") + ENV['RACK_ENV'] || ENV['RAILS_ENV'] || (defined?(Rails) && Rails.respond_to?(:env) ? Rails.env : 'unknown') end def coverband_timeout - @coverband_timeout ||= (coverband_env == "development") ? 5 : 2 + @coverband_timeout ||= coverband_env == 'development' ? 5 : 2 end def service_dev_mode - @service_dev_mode ||= ENV["COVERBAND_ENABLE_DEV_MODE"] || false + @service_dev_mode ||= ENV['COVERBAND_ENABLE_DEV_MODE'] || false end def service_test_mode - @service_test_mode ||= ENV["COVERBAND_ENABLE_TEST_MODE"] || false + @service_test_mode ||= ENV['COVERBAND_ENABLE_TEST_MODE'] || false end def process_type - @process_type ||= ENV["PROCESS_TYPE"] || "unknown" + @process_type ||= ENV['PROCESS_TYPE'] || 'unknown' end def service? @@ -300,35 +312,35 @@ def paged_reporting def service_disabled_dev_test_env? return false unless service? - (coverband_env == "test" && !Coverband.configuration.service_test_mode) || - (coverband_env == "development" && !Coverband.configuration.service_dev_mode) + (coverband_env == 'test' && !Coverband.configuration.service_test_mode) || + (coverband_env == 'development' && !Coverband.configuration.service_dev_mode) end def s3_bucket - puts "deprecated, s3 is no longer support" + puts 'deprecated, s3 is no longer support' end def s3_region - puts "deprecated, s3 is no longer support" + puts 'deprecated, s3 is no longer support' end def s3_access_key_id - puts "deprecated, s3 is no longer support" + puts 'deprecated, s3 is no longer support' end def s3_secret_access_key - puts "deprecated, s3 is no longer support" + puts 'deprecated, s3 is no longer support' end def track_gems=(_value) - puts "gem tracking is deprecated, setting this will be ignored" + puts 'gem tracking is deprecated, setting this will be ignored' end private def redis_store_options - {ttl: Coverband.configuration.redis_ttl, - redis_namespace: Coverband.configuration.redis_namespace} + { ttl: Coverband.configuration.redis_ttl, + redis_namespace: Coverband.configuration.redis_namespace } end end end diff --git a/lib/coverband/reporters/json_report.rb b/lib/coverband/reporters/json_report.rb index c900d96a..bd4be553 100644 --- a/lib/coverband/reporters/json_report.rb +++ b/lib/coverband/reporters/json_report.rb @@ -12,16 +12,16 @@ def initialize(store, options = {}) self.page = options.fetch(:page) { nil } self.filename = options.fetch(:filename) { nil } self.as_report = options.fetch(:as_report) { false } - self.base_path = options.fetch(:base_path) { "./" } + self.base_path = options.fetch(:base_path) { './' } self.store = store coverband_reports = Coverband::Reporters::Base.report(store, options) # NOTE: paged reports can't find and add in files that has never been loaded self.filtered_report_files = if page || filename - coverband_reports - else - self.class.fix_reports(coverband_reports) - end + coverband_reports + else + self.class.fix_reports(coverband_reports) + end end def report @@ -32,13 +32,13 @@ def report def coverage_css_class(covered_percent) if covered_percent.nil? - "" + '' elsif covered_percent > 90 - "green" + 'green' elsif covered_percent > 80 - "yellow" + 'yellow' else - "red" + 'red' end end @@ -54,7 +54,7 @@ def report_as_json if as_report row_data = [] data[:files].each_pair do |key, data| - source_class = data[:never_loaded] ? "strong red" : "strong" + source_class = data[:never_loaded] ? 'strong red' : 'strong' data_loader_url = "#{base_path}load_file_details?filename=#{data[:filename]}" link = "#{key}" # Hack to ensure the sorting works on percentage columns, the span is hidden but colors the cell and the text is used for sorting @@ -72,10 +72,10 @@ def report_as_json data[:covered_strength].to_s ] end - filesreported = store.file_count(:runtime) - data["iTotalRecords"] = filesreported - data["iTotalDisplayRecords"] = filesreported - data["aaData"] = row_data + filesreported = store.cached_file_count + data['iTotalRecords'] = filesreported + data['iTotalDisplayRecords'] = filesreported + data['aaData'] = row_data data.delete(:files) data = data.as_json end diff --git a/public/application.js b/public/application.js index f7b5bbca..8f8551d9 100644 --- a/public/application.js +++ b/public/application.js @@ -39,40 +39,41 @@ $(document).ready(function() { // best docs on our version of datatables 1.7 https://datatables.net/beta/1.7/examples/server_side/server_side.html if ($(".file_list.unsorted").length == 1) { $(".dataTables_empty").html("loading..."); - var current_rows = 0; var total_rows = 0; var page = 1; + var all_data = []; // load and render page content before we start the loop // perhaps move this into a datatable ready event + $(".dataTables_empty").html("loading..."); setTimeout(() => { get_page(page); - }, 1250); + }, 1200); function get_page(page) { + $(".dataTables_empty").html("loading... current page: " + page); $.ajax({ url: `${$(".file_list").data("coverageurl")}/report_json?page=${page}`, type: 'GET', dataType: 'json', success: function(data) { total_rows = data["iTotalRecords"]; - // NOTE: we request 250 at a time, but we seem to have some files that we have as a list but 0 coverage, - // so we don't get back 250 per page... to ensure we we need to account for filtered out and empty files - // this 250 at the moment is synced to the 250 in the hash redis store - current_rows += 250; //data["aaData"].length; - $(".file_list.unsorted").dataTable().fnAddData(data["aaData"]); + all_data = all_data.concat(data["aaData"]); page += 1; - // allow rendering to complete before we click the anchor - setTimeout(() => { - if (window.auto_click_anchor && $(window.auto_click_anchor).length > 0) { - $(window.auto_click_anchor).click(); - } - }, 50); +; // the page less than 100 is to stop infinite loop in case of folks never clearing out old coverage reports - if (page < 100 && current_rows < total_rows) { + if (page < 50 && all_data.length < total_rows) { setTimeout(() => { get_page(page); - }, 200); + }, 10); + } else { + $(".file_list.unsorted").dataTable().fnAddData(all_data); + // allow rendering to complete before we click the anchor + setTimeout(() => { + if (window.auto_click_anchor && $(window.auto_click_anchor).length > 0) { + $(window.auto_click_anchor).click(); + } + }, 50) } } }); From 23af6d54b16980f1323b62127ea1166427f84d09 Mon Sep 17 00:00:00 2001 From: Dan Mayer Date: Sat, 20 Apr 2024 16:24:50 -0600 Subject: [PATCH 17/18] better loading comment --- public/application.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/application.js b/public/application.js index 8f8551d9..d889b26f 100644 --- a/public/application.js +++ b/public/application.js @@ -51,7 +51,6 @@ $(document).ready(function() { }, 1200); function get_page(page) { - $(".dataTables_empty").html("loading... current page: " + page); $.ajax({ url: `${$(".file_list").data("coverageurl")}/report_json?page=${page}`, type: 'GET', @@ -59,6 +58,7 @@ $(document).ready(function() { success: function(data) { total_rows = data["iTotalRecords"]; all_data = all_data.concat(data["aaData"]); + $(".dataTables_empty").html("loading... on " + all_data.length + " of " + total_rows + " files"); page += 1; ; // the page less than 100 is to stop infinite loop in case of folks never clearing out old coverage reports From 7a183da267f343bde0a7ec2add80164a08e1a875 Mon Sep 17 00:00:00 2001 From: Dan Mayer Date: Sat, 20 Apr 2024 16:46:18 -0600 Subject: [PATCH 18/18] re-apply standardrb, my editor applied rubocop ;( --- lib/coverband/adapters/hash_redis_store.rb | 83 +++++----- lib/coverband/configuration.rb | 173 +++++++++++---------- lib/coverband/reporters/json_report.rb | 26 ++-- 3 files changed, 143 insertions(+), 139 deletions(-) diff --git a/lib/coverband/adapters/hash_redis_store.rb b/lib/coverband/adapters/hash_redis_store.rb index fc5b1caf..503b9bd3 100644 --- a/lib/coverband/adapters/hash_redis_store.rb +++ b/lib/coverband/adapters/hash_redis_store.rb @@ -1,12 +1,13 @@ # frozen_string_literal: true -require 'securerandom' +require "securerandom" module Coverband module Adapters class HashRedisStore < Base class GetCoverageNullCacheStore - def self.clear!(*_local_types); end + def self.clear!(*_local_types) + end def self.fetch(_local_type) yield(0) @@ -18,7 +19,7 @@ class GetCoverageRedisCacheStore def initialize(redis, key_prefix) @redis = redis - @key_prefix = [key_prefix, 'get-coverage'].join('.') + @key_prefix = [key_prefix, "get-coverage"].join(".") end def fetch(local_type) @@ -72,7 +73,7 @@ def set(local_type, value) # lock for at most 60 minutes def lock!(local_type) - @redis.set("#{@key_prefix}.lock.#{local_type}", '1', nx: true, ex: LOCK_LIMIT) + @redis.set("#{@key_prefix}.lock.#{local_type}", "1", nx: true, ex: LOCK_LIMIT) end def unlock!(local_type) @@ -80,15 +81,15 @@ def unlock!(local_type) end end - FILE_KEY = 'file' - FILE_LENGTH_KEY = 'file_length' + FILE_KEY = "file" + FILE_LENGTH_KEY = "file_length" META_DATA_KEYS = [DATA_KEY, FIRST_UPDATED_KEY, LAST_UPDATED_KEY, FILE_HASH].freeze ### # This key isn't related to the coverband version, but to the internal format # used to store data to redis. It is changed only when breaking changes to our # redis format are required. ### - REDIS_STORAGE_FORMAT_VERSION = 'coverband_hash_4_0' + REDIS_STORAGE_FORMAT_VERSION = "coverband_hash_4_0" JSON_PAYLOAD_EXPIRATION = 5 * 60 @@ -100,24 +101,24 @@ def initialize(redis, opts = {}) @save_report_batch_size = opts[:save_report_batch_size] || 100 @format_version = REDIS_STORAGE_FORMAT_VERSION @redis = redis - raise 'HashRedisStore requires redis >= 2.6.0' unless supported? + raise "HashRedisStore requires redis >= 2.6.0" unless supported? @ttl = opts[:ttl] @relative_file_converter = opts[:relative_file_converter] || Utils::RelativeFileConverter @get_coverage_cache = if opts[:get_coverage_cache] - key_prefix = [REDIS_STORAGE_FORMAT_VERSION, @redis_namespace].compact.join('.') - GetCoverageRedisCacheStore.new(redis, key_prefix) - else - GetCoverageNullCacheStore - end + key_prefix = [REDIS_STORAGE_FORMAT_VERSION, @redis_namespace].compact.join(".") + GetCoverageRedisCacheStore.new(redis, key_prefix) + else + GetCoverageNullCacheStore + end end def supported? - Gem::Version.new(@redis.info['redis_version']) >= Gem::Version.new('2.6.0') + Gem::Version.new(@redis.info["redis_version"]) >= Gem::Version.new("2.6.0") rescue Redis::CannotConnectError => e Coverband.configuration.logger.info "Redis is not available (#{e}), Coverband not configured" - Coverband.configuration.logger.info 'If this is a setup task like assets:precompile feel free to ignore' + Coverband.configuration.logger.info "If this is a setup task like assets:precompile feel free to ignore" end def clear! @@ -145,7 +146,7 @@ def clear_file!(file) def save_report(report) report_time = Time.now.to_i - updated_time = type == Coverband::EAGER_TYPE ? nil : report_time + updated_time = (type == Coverband::EAGER_TYPE) ? nil : report_time keys = [] report.each_slice(@save_report_batch_size) do |slice| files_data = slice.map do |(file, data)| @@ -164,8 +165,8 @@ def save_report(report) end next unless files_data.any? - arguments_key = [@redis_namespace, SecureRandom.uuid].compact.join('.') - @redis.set(arguments_key, { ttl: @ttl, files_data: files_data }.to_json, ex: JSON_PAYLOAD_EXPIRATION) + arguments_key = [@redis_namespace, SecureRandom.uuid].compact.join(".") + @redis.set(arguments_key, {ttl: @ttl, files_data: files_data}.to_json, ex: JSON_PAYLOAD_EXPIRATION) @redis.evalsha(hash_incr_script, [arguments_key]) end @redis.sadd(files_key, keys) if keys.any? @@ -177,16 +178,16 @@ def coverage(local_type = nil, opts = {}) page_size = opts[:page_size] || 250 cached_results = @get_coverage_cache.fetch(local_type || type) do |sleep_time| files_set = if opts[:page] - raise 'call coverage_for_types with paging' - elsif opts[:filename] - type_key_prefix = key_prefix(local_type) - # NOTE: a better way to extract filename from key would be better - files_set(local_type).select do |cache_key| - cache_key.sub(type_key_prefix, '').match(short_name(opts[:filename])) - end || {} - else - files_set(local_type) - end + raise "call coverage_for_types with paging" + elsif opts[:filename] + type_key_prefix = key_prefix(local_type) + # NOTE: a better way to extract filename from key would be better + files_set(local_type).select do |cache_key| + cache_key.sub(type_key_prefix, "").match(short_name(opts[:filename])) + end || {} + else + files_set(local_type) + end # below uses batches with a sleep in between to avoid overloading redis files_set.each_slice(page_size).flat_map do |key_batch| sleep sleep_time @@ -233,9 +234,9 @@ def coverage_for_types(_types, opts = {}) eager_key_pre = key_prefix(Coverband::EAGER_TYPE) runtime_key_pre = key_prefix(Coverband::RUNTIME_TYPE) matched_file_set = files_set(Coverband::EAGER_TYPE) - .select do |eager_key, _val| + .select do |eager_key, _val| runtime_file_set.any? do |runtime_key| - (eager_key.sub(eager_key_pre, '') == runtime_key.sub(runtime_key_pre, '')) + (eager_key.sub(eager_key_pre, "") == runtime_key.sub(runtime_key_pre, "")) end end || [] hash_data[Coverband::EAGER_TYPE] = matched_file_set.each_slice(page_size).flat_map do |key_batch| @@ -255,8 +256,8 @@ def coverage_for_types(_types, opts = {}) end def short_name(filename) - filename.sub(/^#{Coverband.configuration.root}/, '.') - .gsub(%r{^\./}, '') + filename.sub(/^#{Coverband.configuration.root}/, ".") + .gsub(%r{^\./}, "") end def file_count(local_type = nil) @@ -272,11 +273,11 @@ def raw_store end def size - 'not available' + "not available" end def size_in_mib - 'not available' + "not available" end private @@ -290,11 +291,11 @@ def add_coverage_for_file(data_from_redis, hash) data = coverage_data_from_redis(data_from_redis) hash[file] = data_from_redis.select do |meta_data_key, _value| META_DATA_KEYS.include?(meta_data_key) - end.merge!('data' => data) + end.merge!("data" => data) hash[file][LAST_UPDATED_KEY] = - hash[file][LAST_UPDATED_KEY].nil? || hash[file][LAST_UPDATED_KEY] == '' ? nil : hash[file][LAST_UPDATED_KEY].to_i + (hash[file][LAST_UPDATED_KEY].nil? || hash[file][LAST_UPDATED_KEY] == "") ? nil : hash[file][LAST_UPDATED_KEY].to_i hash[file].merge!(LAST_UPDATED_KEY => hash[file][LAST_UPDATED_KEY], - FIRST_UPDATED_KEY => hash[file][FIRST_UPDATED_KEY].to_i) + FIRST_UPDATED_KEY => hash[file][FIRST_UPDATED_KEY].to_i) end def coverage_data_from_redis(data_from_redis) @@ -330,8 +331,8 @@ def hash_incr_script def lua_script_content File.read(File.join( - File.dirname(__FILE__), '../../../lua/lib/persist-coverage.lua' - )) + File.dirname(__FILE__), "../../../lua/lib/persist-coverage.lua" + )) end def values_from_redis(local_type, files) @@ -355,12 +356,12 @@ def files_key(local_type = nil) end def key(file, local_type = nil, file_hash:) - [key_prefix(local_type), file, file_hash].join('.') + [key_prefix(local_type), file, file_hash].join(".") end def key_prefix(local_type = nil) local_type ||= type - [@format_version, @redis_namespace, local_type].compact.join('.') + [@format_version, @redis_namespace, local_type].compact.join(".") end end end diff --git a/lib/coverband/configuration.rb b/lib/coverband/configuration.rb index 04c4699a..c66c2242 100644 --- a/lib/coverband/configuration.rb +++ b/lib/coverband/configuration.rb @@ -1,23 +1,26 @@ # frozen_string_literal: true module Coverband + ### + # Configuration parsing and options for the coverband gem. + ### class Configuration attr_accessor :root_paths, :root, - :verbose, - :reporter, :redis_namespace, :redis_ttl, - :background_reporting_enabled, - :test_env, :web_enable_clear, :gem_details, :web_debug, :report_on_exit, - :simulate_oneshot_lines_coverage, - :view_tracker, :defer_eager_loading_data, - :track_routes, :route_tracker, - :track_translations, :translations_tracker, - :trackers, :csp_policy, :hide_settings + :verbose, + :reporter, :redis_namespace, :redis_ttl, + :background_reporting_enabled, + :test_env, :web_enable_clear, :gem_details, :web_debug, :report_on_exit, + :simulate_oneshot_lines_coverage, + :view_tracker, :defer_eager_loading_data, + :track_routes, :route_tracker, + :track_translations, :translations_tracker, + :trackers, :csp_policy, :hide_settings attr_writer :logger, :s3_region, :s3_bucket, :s3_access_key_id, - :s3_secret_access_key, :password, :api_key, :service_url, :coverband_timeout, :service_dev_mode, - :service_test_mode, :process_type, :track_views, :redis_url, - :background_reporting_sleep_seconds, :reporting_wiggle, - :send_deferred_eager_loading_data, :paged_reporting + :s3_secret_access_key, :password, :api_key, :service_url, :coverband_timeout, :service_dev_mode, + :service_test_mode, :process_type, :track_views, :redis_url, + :background_reporting_sleep_seconds, :reporting_wiggle, + :send_deferred_eager_loading_data, :paged_reporting attr_reader :track_gems, :ignore, :use_oneshot_lines_coverage @@ -27,21 +30,21 @@ class Configuration # # * Perhaps detect heroku deployment ENV var opposed to tasks? ##### - IGNORE_TASKS = ['coverband:clear', - 'coverband:coverage', - 'coverband:coverage_server', - 'coverband:migrate', - 'assets:precompile', - 'webpacker:compile', - 'db:version', - 'db:create', - 'db:drop', - 'db:seed', - 'db:setup', - 'db:test:prepare', - 'db:structure:dump', - 'db:structure:load', - 'db:version'] + IGNORE_TASKS = ["coverband:clear", + "coverband:coverage", + "coverband:coverage_server", + "coverband:migrate", + "assets:precompile", + "webpacker:compile", + "db:version", + "db:create", + "db:drop", + "db:seed", + "db:setup", + "db:test:prepare", + "db:structure:dump", + "db:structure:load", + "db:version"] # Heroku when building assets runs code from a dynamic directory # /tmp was added to avoid coverage from /tmp/build directories during @@ -62,7 +65,7 @@ def reset @ignore = IGNORE_DEFAULTS.dup @search_paths = TRACKED_DEFAULT_PATHS.dup @verbose = false - @reporter = 'scov' + @reporter = "scov" @logger = nil @store = nil @background_reporting_enabled = true @@ -79,8 +82,8 @@ def reset @translations_tracker = nil @web_debug = false @report_on_exit = true - @use_oneshot_lines_coverage = ENV['ONESHOT'] || false - @simulate_oneshot_lines_coverage = ENV['SIMULATE_ONESHOT'] || false + @use_oneshot_lines_coverage = ENV["ONESHOT"] || false + @simulate_oneshot_lines_coverage = ENV["SIMULATE_ONESHOT"] || false @current_root = nil @all_root_paths = nil @all_root_patterns = nil @@ -125,28 +128,28 @@ def railtie! if Coverband.configuration.track_views Coverband.configuration.view_tracker = if Coverband.coverband_service? - Coverband::Collectors::ViewTrackerService.new - else - Coverband::Collectors::ViewTracker.new - end + Coverband::Collectors::ViewTrackerService.new + else + Coverband::Collectors::ViewTracker.new + end trackers << Coverband.configuration.view_tracker end trackers.each { |tracker| tracker.railtie! } rescue Redis::CannotConnectError => e Coverband.configuration.logger.info "Redis is not available (#{e}), Coverband not configured" - Coverband.configuration.logger.info 'If this is a setup task like assets:precompile feel free to ignore' + Coverband.configuration.logger.info "If this is a setup task like assets:precompile feel free to ignore" end def logger @logger ||= if defined?(Rails.logger) && Rails.logger - Rails.logger - else - Logger.new(STDOUT) - end + Rails.logger + else + Logger.new(STDOUT) + end end def password - @password || ENV['COVERBAND_PASSWORD'] + @password || ENV["COVERBAND_PASSWORD"] end # The adjustments here either protect the redis or service from being overloaded @@ -154,14 +157,14 @@ def password # if running your own redis increasing this number reduces load on the redis CPU def background_reporting_sleep_seconds @background_reporting_sleep_seconds ||= if service? - # default to 10m for service - Coverband.configuration.coverband_env == 'production' ? 600 : 60 - elsif store.is_a?(Coverband::Adapters::HashRedisStore) - # Default to 5 minutes if using the hash redis store - 300 - else - 60 - end + # default to 10m for service + (Coverband.configuration.coverband_env == "production") ? 600 : 60 + elsif store.is_a?(Coverband::Adapters::HashRedisStore) + # Default to 5 minutes if using the hash redis store + 300 + else + 60 + end end def reporting_wiggle @@ -170,25 +173,25 @@ def reporting_wiggle def store @store ||= if service? - if ENV['COVERBAND_REDIS_URL'] - raise 'invalid configuration: unclear default store coverband expects either api_key or redis_url' - end + if ENV["COVERBAND_REDIS_URL"] + raise "invalid configuration: unclear default store coverband expects either api_key or redis_url" + end - require 'coverband/adapters/web_service_store' - Coverband::Adapters::WebServiceStore.new(service_url) - else - Coverband::Adapters::RedisStore.new(Redis.new(url: redis_url), redis_store_options) - end + require "coverband/adapters/web_service_store" + Coverband::Adapters::WebServiceStore.new(service_url) + else + Coverband::Adapters::RedisStore.new(Redis.new(url: redis_url), redis_store_options) + end end def store=(store) - raise 'Pass in an instance of Coverband::Adapters' unless store.is_a?(Coverband::Adapters::Base) - if api_key && store.class.to_s != 'Coverband::Adapters::WebServiceStore' - raise 'invalid configuration: only coverband service expects an API Key' + raise "Pass in an instance of Coverband::Adapters" unless store.is_a?(Coverband::Adapters::Base) + if api_key && store.class.to_s != "Coverband::Adapters::WebServiceStore" + raise "invalid configuration: only coverband service expects an API Key" end - if ENV['COVERBAND_REDIS_URL'] && - defined?(::Coverband::Adapters::WebServiceStore) && - store.instance_of?(::Coverband::Adapters::WebServiceStore) + if ENV["COVERBAND_REDIS_URL"] && + defined?(::Coverband::Adapters::WebServiceStore) && + store.instance_of?(::Coverband::Adapters::WebServiceStore) raise "invalid configuration: coverband service shouldn't have redis url set" end @@ -205,7 +208,7 @@ def track_views # Search Paths ### def tracked_search_paths - "#{Coverband.configuration.current_root}/{#{@search_paths.join(',')}}/**/*.{rb}" + "#{Coverband.configuration.current_root}/{#{@search_paths.join(",")}}/**/*.{rb}" end ### @@ -222,7 +225,7 @@ def ignore=(ignored_array) ignored_array.map { |ignore_str| Regexp.new(ignore_str) } @ignore = (@ignore + ignored_array).uniq rescue RegexpError - logger.error "an invalid regular expression was passed in, ensure string are valid regex patterns #{ignored_array.join(',')}" + logger.error "an invalid regular expression was passed in, ensure string are valid regex patterns #{ignored_array.join(",")}" end def current_root @@ -245,53 +248,53 @@ def all_root_patterns def to_h instance_variables .each_with_object({}) do |var, hash| - hash[var.to_s.delete('@')] = instance_variable_get(var) unless SKIPPED_SETTINGS.include?(var.to_s) + hash[var.to_s.delete("@")] = instance_variable_get(var) unless SKIPPED_SETTINGS.include?(var.to_s) end end def use_oneshot_lines_coverage=(value) unless one_shot_coverage_implemented_in_ruby_version? || !value raise(StandardError, - 'One shot line coverage is only available in ruby >= 2.6') + "One shot line coverage is only available in ruby >= 2.6") end @use_oneshot_lines_coverage = value end def one_shot_coverage_implemented_in_ruby_version? - Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.6.0') + Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.6.0") end def redis_url - @redis_url ||= ENV['COVERBAND_REDIS_URL'] || ENV['REDIS_URL'] + @redis_url ||= ENV["COVERBAND_REDIS_URL"] || ENV["REDIS_URL"] end def api_key - @api_key ||= ENV['COVERBAND_API_KEY'] + @api_key ||= ENV["COVERBAND_API_KEY"] end def service_url - @service_url ||= ENV['COVERBAND_URL'] || 'https://coverband.io' + @service_url ||= ENV["COVERBAND_URL"] || "https://coverband.io" end def coverband_env - ENV['RACK_ENV'] || ENV['RAILS_ENV'] || (defined?(Rails) && Rails.respond_to?(:env) ? Rails.env : 'unknown') + ENV["RACK_ENV"] || ENV["RAILS_ENV"] || ((defined?(Rails) && Rails.respond_to?(:env)) ? Rails.env : "unknown") end def coverband_timeout - @coverband_timeout ||= coverband_env == 'development' ? 5 : 2 + @coverband_timeout ||= (coverband_env == "development") ? 5 : 2 end def service_dev_mode - @service_dev_mode ||= ENV['COVERBAND_ENABLE_DEV_MODE'] || false + @service_dev_mode ||= ENV["COVERBAND_ENABLE_DEV_MODE"] || false end def service_test_mode - @service_test_mode ||= ENV['COVERBAND_ENABLE_TEST_MODE'] || false + @service_test_mode ||= ENV["COVERBAND_ENABLE_TEST_MODE"] || false end def process_type - @process_type ||= ENV['PROCESS_TYPE'] || 'unknown' + @process_type ||= ENV["PROCESS_TYPE"] || "unknown" end def service? @@ -313,35 +316,35 @@ def paged_reporting def service_disabled_dev_test_env? return false unless service? - (coverband_env == 'test' && !Coverband.configuration.service_test_mode) || - (coverband_env == 'development' && !Coverband.configuration.service_dev_mode) + (coverband_env == "test" && !Coverband.configuration.service_test_mode) || + (coverband_env == "development" && !Coverband.configuration.service_dev_mode) end def s3_bucket - puts 'deprecated, s3 is no longer support' + puts "deprecated, s3 is no longer support" end def s3_region - puts 'deprecated, s3 is no longer support' + puts "deprecated, s3 is no longer support" end def s3_access_key_id - puts 'deprecated, s3 is no longer support' + puts "deprecated, s3 is no longer support" end def s3_secret_access_key - puts 'deprecated, s3 is no longer support' + puts "deprecated, s3 is no longer support" end def track_gems=(_value) - puts 'gem tracking is deprecated, setting this will be ignored' + puts "gem tracking is deprecated, setting this will be ignored & eventually removed" end private def redis_store_options - { ttl: Coverband.configuration.redis_ttl, - redis_namespace: Coverband.configuration.redis_namespace } + {ttl: Coverband.configuration.redis_ttl, + redis_namespace: Coverband.configuration.redis_namespace} end end end diff --git a/lib/coverband/reporters/json_report.rb b/lib/coverband/reporters/json_report.rb index bd4be553..a22afdea 100644 --- a/lib/coverband/reporters/json_report.rb +++ b/lib/coverband/reporters/json_report.rb @@ -12,16 +12,16 @@ def initialize(store, options = {}) self.page = options.fetch(:page) { nil } self.filename = options.fetch(:filename) { nil } self.as_report = options.fetch(:as_report) { false } - self.base_path = options.fetch(:base_path) { './' } + self.base_path = options.fetch(:base_path) { "./" } self.store = store coverband_reports = Coverband::Reporters::Base.report(store, options) # NOTE: paged reports can't find and add in files that has never been loaded self.filtered_report_files = if page || filename - coverband_reports - else - self.class.fix_reports(coverband_reports) - end + coverband_reports + else + self.class.fix_reports(coverband_reports) + end end def report @@ -32,13 +32,13 @@ def report def coverage_css_class(covered_percent) if covered_percent.nil? - '' + "" elsif covered_percent > 90 - 'green' + "green" elsif covered_percent > 80 - 'yellow' + "yellow" else - 'red' + "red" end end @@ -54,7 +54,7 @@ def report_as_json if as_report row_data = [] data[:files].each_pair do |key, data| - source_class = data[:never_loaded] ? 'strong red' : 'strong' + source_class = data[:never_loaded] ? "strong red" : "strong" data_loader_url = "#{base_path}load_file_details?filename=#{data[:filename]}" link = "#{key}" # Hack to ensure the sorting works on percentage columns, the span is hidden but colors the cell and the text is used for sorting @@ -73,9 +73,9 @@ def report_as_json ] end filesreported = store.cached_file_count - data['iTotalRecords'] = filesreported - data['iTotalDisplayRecords'] = filesreported - data['aaData'] = row_data + data["iTotalRecords"] = filesreported + data["iTotalDisplayRecords"] = filesreported + data["aaData"] = row_data data.delete(:files) data = data.as_json end
    File