coverband 4.2.0 → 4.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +29 -9
- data/.travis.yml +10 -8
- data/CODE_OF_CONDUCT.md +76 -0
- data/Gemfile +3 -1
- data/Gemfile.rails4 +1 -0
- data/README.md +72 -43
- data/Rakefile +14 -3
- data/changes.md +66 -11
- data/coverband.gemspec +2 -5
- data/lib/coverband/adapters/base.rb +64 -45
- data/lib/coverband/adapters/file_store.rb +15 -9
- data/lib/coverband/adapters/hash_redis_store.rb +182 -0
- data/lib/coverband/adapters/redis_store.rb +33 -11
- data/lib/coverband/at_exit.rb +2 -11
- data/lib/coverband/collectors/coverage.rb +34 -40
- data/lib/coverband/collectors/delta.rb +34 -28
- data/lib/coverband/collectors/view_tracker.rb +124 -0
- data/lib/coverband/configuration.rb +96 -32
- data/lib/coverband/integrations/background.rb +9 -4
- data/lib/coverband/integrations/{middleware.rb → background_middleware.rb} +2 -6
- data/lib/coverband/integrations/bundler.rb +8 -0
- data/lib/coverband/integrations/report_middleware.rb +15 -0
- data/lib/coverband/integrations/resque.rb +3 -5
- data/lib/coverband/reporters/base.rb +40 -16
- data/lib/coverband/reporters/console_report.rb +1 -0
- data/lib/coverband/reporters/html_report.rb +21 -27
- data/lib/coverband/reporters/web.rb +19 -21
- data/lib/coverband/utils/absolute_file_converter.rb +47 -0
- data/lib/coverband/utils/file_hasher.rb +16 -0
- data/lib/coverband/utils/file_list.rb +16 -5
- data/lib/coverband/utils/html_formatter.rb +37 -8
- data/lib/coverband/utils/lines_classifier.rb +5 -0
- data/lib/coverband/utils/railtie.rb +20 -9
- data/lib/coverband/utils/relative_file_converter.rb +41 -0
- data/lib/coverband/utils/result.rb +1 -37
- data/lib/coverband/utils/results.rb +51 -0
- data/lib/coverband/utils/s3_report.rb +2 -2
- data/lib/coverband/utils/source_file.rb +31 -8
- data/lib/coverband/utils/tasks.rb +10 -10
- data/lib/coverband/version.rb +5 -1
- data/lib/coverband.rb +39 -22
- data/public/application.css +6 -6
- data/public/application.js +27 -0
- data/test/benchmarks/benchmark.rake +167 -73
- data/test/benchmarks/init_rails.rake +10 -0
- data/test/coverband/adapters/base_test.rb +73 -42
- data/test/coverband/adapters/file_store_test.rb +48 -37
- data/test/coverband/adapters/hash_redis_store_test.rb +190 -0
- data/test/coverband/adapters/redis_store_test.rb +92 -59
- data/test/coverband/at_exit_test.rb +0 -2
- data/test/coverband/collectors/coverage_test.rb +62 -10
- data/test/coverband/collectors/delta_test.rb +36 -6
- data/test/coverband/collectors/view_tracker_test.rb +77 -0
- data/test/coverband/configuration_test.rb +47 -7
- data/test/coverband/coverband_test.rb +14 -2
- data/test/coverband/integrations/background_middleware_test.rb +44 -0
- data/test/coverband/integrations/background_test.rb +1 -3
- data/test/coverband/integrations/report_middleware_test.rb +44 -0
- data/test/coverband/integrations/resque_worker_test.rb +6 -6
- data/test/coverband/integrations/test_resque_job.rb +3 -1
- data/test/coverband/reporters/base_test.rb +2 -79
- data/test/coverband/reporters/console_test.rb +2 -6
- data/test/coverband/reporters/html_test.rb +58 -19
- data/test/coverband/reporters/web_test.rb +0 -10
- data/test/coverband/utils/absolute_file_converter_test.rb +56 -0
- data/test/coverband/utils/file_groups_test.rb +11 -5
- data/test/coverband/utils/file_hasher_test.rb +29 -0
- data/test/coverband/utils/file_list_test.rb +5 -5
- data/test/coverband/utils/html_formatter_test.rb +43 -0
- data/test/coverband/utils/relative_file_converter_test.rb +39 -0
- data/test/coverband/utils/result_test.rb +6 -47
- data/test/coverband/utils/results_test.rb +54 -0
- data/test/coverband/utils/s3_report_test.rb +2 -0
- data/test/coverband/utils/source_file_test.rb +50 -0
- data/test/dog.rb.erb +12 -0
- data/test/forked/rails_full_stack_test.rb +106 -0
- data/test/forked/rails_rake_full_stack_test.rb +48 -0
- data/test/integration/full_stack_test.rb +17 -16
- data/test/rails4_dummy/Rakefile +6 -0
- data/test/rails4_dummy/config/application.rb +8 -9
- data/test/rails4_dummy/config/coverband.rb +5 -3
- data/test/rails5_dummy/Rakefile +6 -0
- data/test/rails5_dummy/config/application.rb +6 -10
- data/test/rails5_dummy/config/coverband.rb +4 -2
- data/test/rails_test_helper.rb +22 -5
- data/test/test_helper.rb +45 -8
- data/test/unique_files.rb +17 -9
- data/views/file_list.erb +2 -2
- data/views/gem_list.erb +10 -1
- data/views/layout.erb +6 -18
- data/views/nav.erb +35 -0
- data/views/source_file.erb +13 -4
- data/views/source_file_loader.erb +1 -1
- data/views/view_tracker.erb +37 -0
- metadata +59 -25
- data/lib/coverband/utils/file_path_helper.rb +0 -57
- data/test/coverband/integrations/middleware_test.rb +0 -96
- data/test/integration/rails_full_stack_test.rb +0 -95
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
module Coverband
|
|
3
4
|
module Collectors
|
|
4
5
|
class Delta
|
|
5
|
-
|
|
6
|
-
@@
|
|
6
|
+
@@previous_coverage = {}
|
|
7
|
+
@@stubs = {}
|
|
8
|
+
|
|
7
9
|
attr_reader :current_coverage
|
|
8
10
|
|
|
9
11
|
def initialize(current_coverage)
|
|
@@ -12,54 +14,58 @@ module Coverband
|
|
|
12
14
|
|
|
13
15
|
class RubyCoverage
|
|
14
16
|
def self.results
|
|
15
|
-
|
|
17
|
+
if Coverband.configuration.use_oneshot_lines_coverage
|
|
18
|
+
::Coverage.result(clear: true, stop: false)
|
|
19
|
+
else
|
|
20
|
+
::Coverage.peek_result
|
|
21
|
+
end
|
|
16
22
|
end
|
|
17
23
|
end
|
|
18
24
|
|
|
19
25
|
def self.results(process_coverage = RubyCoverage)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
new(process_coverage.results).results
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def self.previous_results
|
|
27
|
-
@@previous_coverage
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def self.set_default_results
|
|
31
|
-
@@previous_coverage ||= {}
|
|
26
|
+
coverage_results = process_coverage.results
|
|
27
|
+
new(coverage_results).results
|
|
32
28
|
end
|
|
33
29
|
|
|
34
30
|
def results
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
31
|
+
if Coverband.configuration.use_oneshot_lines_coverage
|
|
32
|
+
transform_oneshot_lines_results(current_coverage)
|
|
33
|
+
else
|
|
34
|
+
new_results = generate
|
|
35
|
+
@@previous_coverage = current_coverage unless Coverband.configuration.simulate_oneshot_lines_coverage
|
|
36
|
+
new_results
|
|
37
|
+
end
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
def self.reset
|
|
41
|
-
@@previous_coverage =
|
|
41
|
+
@@previous_coverage = {}
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
private
|
|
45
45
|
|
|
46
46
|
def generate
|
|
47
47
|
current_coverage.each_with_object({}) do |(file, line_counts), new_results|
|
|
48
|
-
if @@previous_coverage[file]
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
new_results[file] = if @@previous_coverage && @@previous_coverage[file]
|
|
49
|
+
array_diff(line_counts, @@previous_coverage[file])
|
|
50
|
+
else
|
|
51
|
+
line_counts
|
|
52
|
+
end
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
def array_diff(latest, original)
|
|
57
57
|
latest.map.with_index do |v, i|
|
|
58
|
-
if
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
[0, v - original[i]].max if v && original[i]
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def transform_oneshot_lines_results(results)
|
|
63
|
+
results.each_with_object({}) do |(file, coverage), new_results|
|
|
64
|
+
@@stubs[file] ||= ::Coverage.line_stub(file)
|
|
65
|
+
transformed_line_counts = coverage[:oneshot_lines].each_with_object(@@stubs[file].dup) do |line_number, line_counts|
|
|
66
|
+
line_counts[line_number - 1] = 1
|
|
62
67
|
end
|
|
68
|
+
new_results[file] = transformed_line_counts
|
|
63
69
|
end
|
|
64
70
|
end
|
|
65
71
|
end
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'singleton'
|
|
4
|
+
|
|
5
|
+
module Coverband
|
|
6
|
+
module Collectors
|
|
7
|
+
###
|
|
8
|
+
# This class tracks view file usage via ActiveSupport::Notifications
|
|
9
|
+
#
|
|
10
|
+
# This is a port of Flatfoot, a project I open sourced years ago, but am now rolling into Coverband
|
|
11
|
+
# https://github.com/livingsocial/flatfoot
|
|
12
|
+
#
|
|
13
|
+
# TODO: test and ensure slim, haml, and other support
|
|
14
|
+
###
|
|
15
|
+
class ViewTracker
|
|
16
|
+
DEFAULT_TARGET = Dir.glob('app/views/**/*.html.erb').reject { |file| file.match(/(_mailer)/) }
|
|
17
|
+
attr_accessor :target, :logged_views, :views_to_record
|
|
18
|
+
attr_reader :logger, :roots, :store, :ignore_patterns
|
|
19
|
+
|
|
20
|
+
def initialize(options = {})
|
|
21
|
+
raise NotImplementedError, 'View Tracker requires Rails 4 or greater' unless self.class.supported_version?
|
|
22
|
+
|
|
23
|
+
@project_directory = File.expand_path(Coverband.configuration.root)
|
|
24
|
+
@ignore_patterns = Coverband.configuration.ignore
|
|
25
|
+
@store = options.fetch(:store) { Coverband.configuration.store }
|
|
26
|
+
@logger = options.fetch(:logger) { Coverband.configuration.logger }
|
|
27
|
+
@target = options.fetch(:target) { DEFAULT_TARGET }
|
|
28
|
+
|
|
29
|
+
@roots = options.fetch(:roots) { Coverband.configuration.all_root_patterns }
|
|
30
|
+
@roots = @roots.split(',') if @roots.is_a?(String)
|
|
31
|
+
|
|
32
|
+
@logged_views = []
|
|
33
|
+
@views_to_record = []
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
###
|
|
37
|
+
# This method is called on every render call, so we try to reduce method calls
|
|
38
|
+
# and ensure high performance
|
|
39
|
+
###
|
|
40
|
+
def track_views(_name, _start, _finish, _id, payload)
|
|
41
|
+
if (file = payload[:identifier])
|
|
42
|
+
if newly_seen_file?(file)
|
|
43
|
+
logged_views << file
|
|
44
|
+
views_to_record << file if track_file?(file)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
###
|
|
49
|
+
# Annoyingly while you get full path for templates
|
|
50
|
+
# notifications only pass part of the path for layouts dropping any format info
|
|
51
|
+
# such as .html.erb or .js.erb
|
|
52
|
+
# http://edgeguides.rubyonrails.org/active_support_instrumentation.html#render_partial-action_view
|
|
53
|
+
###
|
|
54
|
+
return unless (layout_file = payload[:layout])
|
|
55
|
+
return unless newly_seen_file?(layout_file)
|
|
56
|
+
|
|
57
|
+
logged_views << layout_file
|
|
58
|
+
views_to_record << layout_file if track_file?(layout_file, layout: true)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def used_views
|
|
62
|
+
views = redis_store.smembers(tracker_key)
|
|
63
|
+
normalized_views = []
|
|
64
|
+
views.each do |view|
|
|
65
|
+
roots.each do |root|
|
|
66
|
+
view = view.gsub(/#{root}/, '')
|
|
67
|
+
end
|
|
68
|
+
normalized_views << view
|
|
69
|
+
end
|
|
70
|
+
normalized_views
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def unused_views
|
|
74
|
+
recently_used_views = used_views
|
|
75
|
+
all_views = target.reject { |view| recently_used_views.include?(view) }
|
|
76
|
+
# since layouts don't include format we count them used if they match with ANY formats
|
|
77
|
+
all_views.reject { |view| view.match(/\/layouts\//) && recently_used_views.any? { |used_view| view.include?(used_view) } }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def reset_recordings
|
|
81
|
+
redis_store.del(tracker_key)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def self.supported_version?
|
|
85
|
+
defined?(Rails) && defined?(Rails::VERSION) && Rails::VERSION::STRING.split('.').first.to_i >= 4
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def report_views_tracked
|
|
89
|
+
views_to_record.each do |file|
|
|
90
|
+
redis_store.sadd(tracker_key, file)
|
|
91
|
+
end
|
|
92
|
+
self.views_to_record = []
|
|
93
|
+
rescue StandardError => e
|
|
94
|
+
# we don't want to raise errors if Coverband can't reach redis.
|
|
95
|
+
# This is a nice to have not a bring the system down
|
|
96
|
+
logger&.error "Coverband: view_tracker failed to store, error #{e.class.name}"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
protected
|
|
100
|
+
|
|
101
|
+
def newly_seen_file?(file)
|
|
102
|
+
return false if logged_views.include?(file)
|
|
103
|
+
|
|
104
|
+
true
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def track_file?(file, options = {})
|
|
108
|
+
@ignore_patterns.none? do |pattern|
|
|
109
|
+
file.include?(pattern)
|
|
110
|
+
end && (file.start_with?(@project_directory) || options[:layout])
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
def redis_store
|
|
116
|
+
store.raw_store
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def tracker_key
|
|
120
|
+
'render_tracker'
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
@@ -3,15 +3,36 @@
|
|
|
3
3
|
module Coverband
|
|
4
4
|
class Configuration
|
|
5
5
|
attr_accessor :root_paths, :root,
|
|
6
|
-
:
|
|
7
|
-
:reporter, :
|
|
8
|
-
:
|
|
9
|
-
:safe_reload_files, :background_reporting_enabled,
|
|
6
|
+
:additional_files, :verbose,
|
|
7
|
+
:reporter, :redis_namespace, :redis_ttl,
|
|
8
|
+
:background_reporting_enabled,
|
|
10
9
|
:background_reporting_sleep_seconds, :test_env,
|
|
11
|
-
:web_enable_clear, :gem_details, :web_debug
|
|
10
|
+
:web_enable_clear, :gem_details, :web_debug, :report_on_exit,
|
|
11
|
+
:simulate_oneshot_lines_coverage, :track_views, :view_tracker
|
|
12
12
|
|
|
13
|
-
attr_writer :logger, :s3_region, :s3_bucket, :s3_access_key_id, :s3_secret_access_key
|
|
14
|
-
attr_reader :track_gems
|
|
13
|
+
attr_writer :logger, :s3_region, :s3_bucket, :s3_access_key_id, :s3_secret_access_key, :password
|
|
14
|
+
attr_reader :track_gems, :ignore, :use_oneshot_lines_coverage
|
|
15
|
+
|
|
16
|
+
#####
|
|
17
|
+
# TODO: This is is brittle and not a great solution to avoid deploy time
|
|
18
|
+
# actions polluting the 'runtime' metrics
|
|
19
|
+
#
|
|
20
|
+
# * should we skip /bin/rails webpacker:compile ?
|
|
21
|
+
# * Perhaps detect heroku deployment ENV var opposed to tasks?
|
|
22
|
+
#####
|
|
23
|
+
IGNORE_TASKS = ['coverband:clear',
|
|
24
|
+
'coverband:coverage',
|
|
25
|
+
'coverband:coverage_server',
|
|
26
|
+
'coverband:migrate']
|
|
27
|
+
|
|
28
|
+
# Heroku when building assets runs code from a dynamic directory
|
|
29
|
+
# /tmp was added to avoid coverage from /tmp/build directories during
|
|
30
|
+
# heroku asset compilation
|
|
31
|
+
IGNORE_DEFAULTS = %w[vendor .erb$ .slim$ /tmp internal:prelude schema.rb]
|
|
32
|
+
|
|
33
|
+
# Add in missing files which were never loaded
|
|
34
|
+
# we need to know what all paths to check for unloaded files
|
|
35
|
+
TRACKED_DEFAULT_PATHS = %w[app lib config]
|
|
15
36
|
|
|
16
37
|
def initialize
|
|
17
38
|
reset
|
|
@@ -20,12 +41,9 @@ module Coverband
|
|
|
20
41
|
def reset
|
|
21
42
|
@root = Dir.pwd
|
|
22
43
|
@root_paths = []
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
# heroku asset compilation
|
|
26
|
-
@ignore = %w[vendor .erb$ .slim$ /tmp]
|
|
44
|
+
@ignore = IGNORE_DEFAULTS.dup
|
|
45
|
+
@seach_paths = TRACKED_DEFAULT_PATHS.dup
|
|
27
46
|
@additional_files = []
|
|
28
|
-
@reporting_frequency = 0.0
|
|
29
47
|
@verbose = false
|
|
30
48
|
@reporter = 'scov'
|
|
31
49
|
@logger = nil
|
|
@@ -36,8 +54,16 @@ module Coverband
|
|
|
36
54
|
@web_enable_clear = false
|
|
37
55
|
@track_gems = false
|
|
38
56
|
@gem_details = false
|
|
57
|
+
@track_views = false
|
|
39
58
|
@groups = {}
|
|
40
59
|
@web_debug = false
|
|
60
|
+
@report_on_exit = true
|
|
61
|
+
@use_oneshot_lines_coverage = ENV['ONESHOT'] || false
|
|
62
|
+
@simulate_oneshot_lines_coverage = ENV['SIMULATE_ONESHOT'] || false
|
|
63
|
+
@current_root = nil
|
|
64
|
+
@all_root_paths = nil
|
|
65
|
+
@all_root_patterns = nil
|
|
66
|
+
@password = nil
|
|
41
67
|
|
|
42
68
|
# TODO: should we push these to adapter configs
|
|
43
69
|
@s3_region = nil
|
|
@@ -45,17 +71,21 @@ module Coverband
|
|
|
45
71
|
@s3_access_key_id = nil
|
|
46
72
|
@s3_secret_access_key = nil
|
|
47
73
|
@redis_namespace = nil
|
|
48
|
-
@redis_ttl =
|
|
74
|
+
@redis_ttl = 2_592_000 # in seconds. Default is 30 days.
|
|
49
75
|
end
|
|
50
76
|
|
|
51
77
|
def logger
|
|
52
|
-
@logger ||= if defined?(Rails.logger)
|
|
78
|
+
@logger ||= if defined?(Rails.logger) && Rails.logger
|
|
53
79
|
Rails.logger
|
|
54
80
|
else
|
|
55
81
|
Logger.new(STDOUT)
|
|
56
82
|
end
|
|
57
83
|
end
|
|
58
84
|
|
|
85
|
+
def password
|
|
86
|
+
@password || ENV['COVERBAND_PASSWORD']
|
|
87
|
+
end
|
|
88
|
+
|
|
59
89
|
def s3_bucket
|
|
60
90
|
@s3_bucket || ENV['AWS_BUCKET']
|
|
61
91
|
end
|
|
@@ -77,29 +107,47 @@ module Coverband
|
|
|
77
107
|
end
|
|
78
108
|
|
|
79
109
|
def store=(store)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
110
|
+
raise 'Pass in an instance of Coverband::Adapters' unless store.is_a?(Coverband::Adapters::Base)
|
|
111
|
+
|
|
112
|
+
@store = store
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
###
|
|
116
|
+
# Search Paths
|
|
117
|
+
###
|
|
118
|
+
def tracked_search_paths
|
|
119
|
+
"#{Coverband.configuration.current_root}/{#{@seach_paths.join(',')}}/**/*.{rb}"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
###
|
|
123
|
+
# Don't allow the to override defaults
|
|
124
|
+
###
|
|
125
|
+
def search_paths=(path_array)
|
|
126
|
+
@seach_paths = (@seach_paths + path_array).uniq
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
###
|
|
130
|
+
# Don't allow the ignore to override things like gem tracking
|
|
131
|
+
###
|
|
132
|
+
def ignore=(ignored_array)
|
|
133
|
+
@ignore = (@ignore + ignored_array).uniq
|
|
85
134
|
end
|
|
86
135
|
|
|
87
136
|
def track_gems=(value)
|
|
88
137
|
@track_gems = value
|
|
89
138
|
return unless @track_gems
|
|
139
|
+
|
|
90
140
|
# by default we ignore vendor where many deployments put gems
|
|
91
141
|
# we will remove this default if track_gems is set
|
|
92
142
|
@ignore.delete('vendor')
|
|
93
143
|
# while we want to allow vendored gems we don't want to track vendored ruby STDLIB
|
|
94
|
-
@ignore << 'vendor/ruby-*'
|
|
144
|
+
@ignore << 'vendor/ruby-*' unless @ignore.include?('vendor/ruby-*')
|
|
95
145
|
add_group('App', root)
|
|
96
146
|
# TODO: rework support for multiple gem paths
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
|
|
101
|
-
# end
|
|
102
|
-
add_group('Gems', gem_paths.first)
|
|
147
|
+
# this works but seems hacky and error prone
|
|
148
|
+
# basically since it is converted to a regex we join all the paths
|
|
149
|
+
# with a regex 'OR' using '|'
|
|
150
|
+
add_group('Gems', gem_paths.join('|'))
|
|
103
151
|
end
|
|
104
152
|
|
|
105
153
|
#
|
|
@@ -125,17 +173,23 @@ module Coverband
|
|
|
125
173
|
end
|
|
126
174
|
|
|
127
175
|
def current_root
|
|
128
|
-
File.expand_path(Coverband.configuration.root)
|
|
176
|
+
@current_root ||= File.expand_path(Coverband.configuration.root).freeze
|
|
129
177
|
end
|
|
130
178
|
|
|
131
179
|
def all_root_paths
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
180
|
+
return @all_root_paths if @all_root_paths
|
|
181
|
+
|
|
182
|
+
@all_root_paths = Coverband.configuration.root_paths.dup
|
|
183
|
+
@all_root_paths += Coverband.configuration.gem_paths.dup if Coverband.configuration.track_gems
|
|
184
|
+
@all_root_paths << "#{Coverband.configuration.current_root}/"
|
|
185
|
+
@all_root_paths
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def all_root_patterns
|
|
189
|
+
@all_root_patterns ||= all_root_paths.map { |path| /^#{path}/ }.freeze
|
|
136
190
|
end
|
|
137
191
|
|
|
138
|
-
SKIPPED_SETTINGS = %w
|
|
192
|
+
SKIPPED_SETTINGS = %w[@s3_secret_access_key @store]
|
|
139
193
|
def to_h
|
|
140
194
|
instance_variables
|
|
141
195
|
.each_with_object('gem_paths': gem_paths) do |var, hash|
|
|
@@ -143,6 +197,16 @@ module Coverband
|
|
|
143
197
|
end
|
|
144
198
|
end
|
|
145
199
|
|
|
200
|
+
def use_oneshot_lines_coverage=(value)
|
|
201
|
+
raise(Exception, 'One shot line coverage is only available in ruby >= 2.6') unless one_shot_coverage_implemented_in_ruby_version? || !value
|
|
202
|
+
|
|
203
|
+
@use_oneshot_lines_coverage = value
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def one_shot_coverage_implemented_in_ruby_version?
|
|
207
|
+
Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.6.0')
|
|
208
|
+
end
|
|
209
|
+
|
|
146
210
|
private
|
|
147
211
|
|
|
148
212
|
def redis_url
|
|
@@ -7,6 +7,7 @@ module Coverband
|
|
|
7
7
|
|
|
8
8
|
def self.stop
|
|
9
9
|
return unless @thread
|
|
10
|
+
|
|
10
11
|
@semaphore.synchronize do
|
|
11
12
|
if @thread
|
|
12
13
|
@thread.exit
|
|
@@ -16,7 +17,7 @@ module Coverband
|
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
def self.running?
|
|
19
|
-
@thread
|
|
20
|
+
@thread&.alive?
|
|
20
21
|
end
|
|
21
22
|
|
|
22
23
|
def self.start
|
|
@@ -25,12 +26,16 @@ module Coverband
|
|
|
25
26
|
logger = Coverband.configuration.logger
|
|
26
27
|
@semaphore.synchronize do
|
|
27
28
|
return if running?
|
|
28
|
-
|
|
29
|
+
|
|
30
|
+
logger.debug('Coverband: Starting background reporting') if Coverband.configuration.verbose
|
|
29
31
|
sleep_seconds = Coverband.configuration.background_reporting_sleep_seconds
|
|
30
32
|
@thread = Thread.new do
|
|
31
33
|
loop do
|
|
32
|
-
Coverband.report_coverage
|
|
33
|
-
|
|
34
|
+
Coverband.report_coverage
|
|
35
|
+
Coverband.configuration.view_tracker&.report_views_tracked
|
|
36
|
+
if Coverband.configuration.verbose
|
|
37
|
+
logger.debug("Coverband: background reporting coverage (#{Coverband.configuration.store.type}). Sleeping #{sleep_seconds}s")
|
|
38
|
+
end
|
|
34
39
|
sleep(sleep_seconds)
|
|
35
40
|
end
|
|
36
41
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Coverband
|
|
4
|
-
class
|
|
4
|
+
class BackgroundMiddleware
|
|
5
5
|
def initialize(app)
|
|
6
6
|
@app = app
|
|
7
7
|
end
|
|
@@ -10,11 +10,7 @@ module Coverband
|
|
|
10
10
|
@app.call(env)
|
|
11
11
|
ensure
|
|
12
12
|
AtExit.register
|
|
13
|
-
if Coverband.configuration.background_reporting_enabled
|
|
14
|
-
Background.start
|
|
15
|
-
else
|
|
16
|
-
Collectors::Coverage.instance.report_coverage
|
|
17
|
-
end
|
|
13
|
+
Background.start if Coverband.configuration.background_reporting_enabled
|
|
18
14
|
end
|
|
19
15
|
end
|
|
20
16
|
end
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
Resque.after_fork do |
|
|
3
|
+
Resque.after_fork do |_job|
|
|
4
4
|
Coverband.start
|
|
5
5
|
Coverband.runtime_coverage!
|
|
6
|
-
# no reason to miss coverage on a first resque job
|
|
7
|
-
Coverband::Collectors::Delta.set_default_results
|
|
8
6
|
end
|
|
9
7
|
|
|
10
8
|
Resque.before_first_fork do
|
|
11
9
|
Coverband.eager_loading_coverage!
|
|
12
10
|
Coverband.configuration.background_reporting_enabled = false
|
|
13
11
|
Coverband::Background.stop
|
|
14
|
-
Coverband
|
|
12
|
+
Coverband.report_coverage
|
|
15
13
|
end
|
|
16
14
|
|
|
17
15
|
module Coverband
|
|
@@ -19,7 +17,7 @@ module Coverband
|
|
|
19
17
|
def perform
|
|
20
18
|
super
|
|
21
19
|
ensure
|
|
22
|
-
Coverband.report_coverage
|
|
20
|
+
Coverband.report_coverage
|
|
23
21
|
end
|
|
24
22
|
end
|
|
25
23
|
end
|
|
@@ -8,37 +8,61 @@ module Coverband
|
|
|
8
8
|
###
|
|
9
9
|
class Base
|
|
10
10
|
class << self
|
|
11
|
-
|
|
11
|
+
DATA_KEY = 'data'
|
|
12
|
+
|
|
12
13
|
def report(store, _options = {})
|
|
13
14
|
all_roots = Coverband.configuration.all_root_paths
|
|
14
15
|
scov_style_report = get_current_scov_data_imp(store, all_roots)
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
# These are extremelhy verbose but useful during coverband development, not generally for users
|
|
18
|
+
# Only avaiable by uncommenting this mode is never released
|
|
19
|
+
# if Coverband.configuration.verbose
|
|
20
|
+
# # msg = "report:\n #{scov_style_report.inspect}"
|
|
21
|
+
# # Coverband.configuration.logger.debug msg
|
|
22
|
+
# end
|
|
20
23
|
scov_style_report
|
|
21
24
|
end
|
|
22
25
|
|
|
26
|
+
###
|
|
27
|
+
# Add back files that exist in the project but have no Coverage
|
|
28
|
+
# This makes it easy to find and delete files with no references
|
|
29
|
+
###
|
|
30
|
+
def fix_reports(reports)
|
|
31
|
+
# list all files, even if not tracked by Coverband (0% coverage)
|
|
32
|
+
tracked_glob = Coverband.configuration.tracked_search_paths
|
|
33
|
+
filtered_report_files = {}
|
|
34
|
+
|
|
35
|
+
reports.each_pair do |report_name, report_data|
|
|
36
|
+
filtered_report_files[report_name] = {}
|
|
37
|
+
report_files = Coverband::Utils::Result.add_not_loaded_files(report_data, tracked_glob)
|
|
38
|
+
|
|
39
|
+
# apply coverband filters
|
|
40
|
+
report_files.each_pair do |file, data|
|
|
41
|
+
next if Coverband.configuration.ignore.any? { |i| file.match(i) }
|
|
42
|
+
|
|
43
|
+
filtered_report_files[report_name][file] = data
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
filtered_report_files
|
|
47
|
+
end
|
|
48
|
+
|
|
23
49
|
protected
|
|
24
50
|
|
|
25
51
|
def fix_file_names(report_hash, roots)
|
|
26
|
-
if Coverband.configuration.verbose
|
|
27
|
-
Coverband.configuration.logger.info "fixing root: #{roots.join(', ')}"
|
|
28
|
-
end
|
|
52
|
+
Coverband.configuration.logger.debug "fixing root: #{roots.join(', ')}" if Coverband.configuration.verbose
|
|
29
53
|
|
|
30
54
|
# normalize names across servers
|
|
31
55
|
report_hash.each_with_object({}) do |(name, report), fixed_report|
|
|
32
56
|
fixed_report[name] = {}
|
|
33
57
|
report.each_pair do |key, vals|
|
|
34
|
-
filename =
|
|
35
|
-
fixed_report[name][filename] = if fixed_report[name].key?(filename) && fixed_report[name][filename][
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
58
|
+
filename = Coverband::Utils::AbsoluteFileConverter.convert(key)
|
|
59
|
+
fixed_report[name][filename] = if fixed_report[name].key?(filename) && fixed_report[name][filename][DATA_KEY] && vals[DATA_KEY]
|
|
60
|
+
merged_data = merge_arrays(fixed_report[name][filename][DATA_KEY], vals[DATA_KEY])
|
|
61
|
+
vals[DATA_KEY] = merged_data
|
|
62
|
+
vals
|
|
63
|
+
else
|
|
64
|
+
vals
|
|
65
|
+
end
|
|
42
66
|
end
|
|
43
67
|
end
|
|
44
68
|
end
|
|
@@ -10,6 +10,7 @@ module Coverband
|
|
|
10
10
|
scov_style_report = super(store, options)
|
|
11
11
|
|
|
12
12
|
scov_style_report.each_pair do |file, usage|
|
|
13
|
+
# TODO: In Coverband 5 deprecate none hash format
|
|
13
14
|
if usage.is_a?(Hash)
|
|
14
15
|
Coverband.configuration.logger.info "#{file}: #{usage['data']}"
|
|
15
16
|
else
|