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.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +29 -9
  3. data/.travis.yml +10 -8
  4. data/CODE_OF_CONDUCT.md +76 -0
  5. data/Gemfile +3 -1
  6. data/Gemfile.rails4 +1 -0
  7. data/README.md +72 -43
  8. data/Rakefile +14 -3
  9. data/changes.md +66 -11
  10. data/coverband.gemspec +2 -5
  11. data/lib/coverband/adapters/base.rb +64 -45
  12. data/lib/coverband/adapters/file_store.rb +15 -9
  13. data/lib/coverband/adapters/hash_redis_store.rb +182 -0
  14. data/lib/coverband/adapters/redis_store.rb +33 -11
  15. data/lib/coverband/at_exit.rb +2 -11
  16. data/lib/coverband/collectors/coverage.rb +34 -40
  17. data/lib/coverband/collectors/delta.rb +34 -28
  18. data/lib/coverband/collectors/view_tracker.rb +124 -0
  19. data/lib/coverband/configuration.rb +96 -32
  20. data/lib/coverband/integrations/background.rb +9 -4
  21. data/lib/coverband/integrations/{middleware.rb → background_middleware.rb} +2 -6
  22. data/lib/coverband/integrations/bundler.rb +8 -0
  23. data/lib/coverband/integrations/report_middleware.rb +15 -0
  24. data/lib/coverband/integrations/resque.rb +3 -5
  25. data/lib/coverband/reporters/base.rb +40 -16
  26. data/lib/coverband/reporters/console_report.rb +1 -0
  27. data/lib/coverband/reporters/html_report.rb +21 -27
  28. data/lib/coverband/reporters/web.rb +19 -21
  29. data/lib/coverband/utils/absolute_file_converter.rb +47 -0
  30. data/lib/coverband/utils/file_hasher.rb +16 -0
  31. data/lib/coverband/utils/file_list.rb +16 -5
  32. data/lib/coverband/utils/html_formatter.rb +37 -8
  33. data/lib/coverband/utils/lines_classifier.rb +5 -0
  34. data/lib/coverband/utils/railtie.rb +20 -9
  35. data/lib/coverband/utils/relative_file_converter.rb +41 -0
  36. data/lib/coverband/utils/result.rb +1 -37
  37. data/lib/coverband/utils/results.rb +51 -0
  38. data/lib/coverband/utils/s3_report.rb +2 -2
  39. data/lib/coverband/utils/source_file.rb +31 -8
  40. data/lib/coverband/utils/tasks.rb +10 -10
  41. data/lib/coverband/version.rb +5 -1
  42. data/lib/coverband.rb +39 -22
  43. data/public/application.css +6 -6
  44. data/public/application.js +27 -0
  45. data/test/benchmarks/benchmark.rake +167 -73
  46. data/test/benchmarks/init_rails.rake +10 -0
  47. data/test/coverband/adapters/base_test.rb +73 -42
  48. data/test/coverband/adapters/file_store_test.rb +48 -37
  49. data/test/coverband/adapters/hash_redis_store_test.rb +190 -0
  50. data/test/coverband/adapters/redis_store_test.rb +92 -59
  51. data/test/coverband/at_exit_test.rb +0 -2
  52. data/test/coverband/collectors/coverage_test.rb +62 -10
  53. data/test/coverband/collectors/delta_test.rb +36 -6
  54. data/test/coverband/collectors/view_tracker_test.rb +77 -0
  55. data/test/coverband/configuration_test.rb +47 -7
  56. data/test/coverband/coverband_test.rb +14 -2
  57. data/test/coverband/integrations/background_middleware_test.rb +44 -0
  58. data/test/coverband/integrations/background_test.rb +1 -3
  59. data/test/coverband/integrations/report_middleware_test.rb +44 -0
  60. data/test/coverband/integrations/resque_worker_test.rb +6 -6
  61. data/test/coverband/integrations/test_resque_job.rb +3 -1
  62. data/test/coverband/reporters/base_test.rb +2 -79
  63. data/test/coverband/reporters/console_test.rb +2 -6
  64. data/test/coverband/reporters/html_test.rb +58 -19
  65. data/test/coverband/reporters/web_test.rb +0 -10
  66. data/test/coverband/utils/absolute_file_converter_test.rb +56 -0
  67. data/test/coverband/utils/file_groups_test.rb +11 -5
  68. data/test/coverband/utils/file_hasher_test.rb +29 -0
  69. data/test/coverband/utils/file_list_test.rb +5 -5
  70. data/test/coverband/utils/html_formatter_test.rb +43 -0
  71. data/test/coverband/utils/relative_file_converter_test.rb +39 -0
  72. data/test/coverband/utils/result_test.rb +6 -47
  73. data/test/coverband/utils/results_test.rb +54 -0
  74. data/test/coverband/utils/s3_report_test.rb +2 -0
  75. data/test/coverband/utils/source_file_test.rb +50 -0
  76. data/test/dog.rb.erb +12 -0
  77. data/test/forked/rails_full_stack_test.rb +106 -0
  78. data/test/forked/rails_rake_full_stack_test.rb +48 -0
  79. data/test/integration/full_stack_test.rb +17 -16
  80. data/test/rails4_dummy/Rakefile +6 -0
  81. data/test/rails4_dummy/config/application.rb +8 -9
  82. data/test/rails4_dummy/config/coverband.rb +5 -3
  83. data/test/rails5_dummy/Rakefile +6 -0
  84. data/test/rails5_dummy/config/application.rb +6 -10
  85. data/test/rails5_dummy/config/coverband.rb +4 -2
  86. data/test/rails_test_helper.rb +22 -5
  87. data/test/test_helper.rb +45 -8
  88. data/test/unique_files.rb +17 -9
  89. data/views/file_list.erb +2 -2
  90. data/views/gem_list.erb +10 -1
  91. data/views/layout.erb +6 -18
  92. data/views/nav.erb +35 -0
  93. data/views/source_file.erb +13 -4
  94. data/views/source_file_loader.erb +1 -1
  95. data/views/view_tracker.erb +37 -0
  96. metadata +59 -25
  97. data/lib/coverband/utils/file_path_helper.rb +0 -57
  98. data/test/coverband/integrations/middleware_test.rb +0 -96
  99. 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
- @semaphore = Mutex.new
6
- @@previous_coverage = nil
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
- ::Coverage.peek_result.dup
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
- @semaphore.synchronize do
21
- set_default_results
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
- new_results = generate
36
- @@previous_coverage = current_coverage
37
- new_results
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 = nil
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
- new_results[file] = array_diff(line_counts, @@previous_coverage[file])
50
- else
51
- new_results[file] = line_counts
52
- end
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 (v && original[i])
59
- [0, v - original[i]].max
60
- else
61
- nil
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
- :ignore, :additional_files, :verbose,
7
- :reporter, :reporting_frequency,
8
- :redis_namespace, :redis_ttl,
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
- # Heroku when building assets runs code from a dynamic directory
24
- # /tmp was added to avoid coverage from /tmp/build directories during
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 = nil
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
- if store.is_a?(Coverband::Adapters::Base)
81
- @store = store
82
- else
83
- raise 'please pass in an subclass of Coverband::Adapters for supported stores'
84
- end
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
- # currently this supports GEM_HOME (which should be first path)
98
- # but various gem managers setup multiple gem paths
99
- # gem_paths.each_with_index do |path, index|
100
- # add_group("gems_#{index}", path)
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
- roots = Coverband.configuration.root_paths.dup
133
- roots += Coverband.configuration.gem_paths.dup if Coverband.configuration.track_gems
134
- roots << "#{Coverband.configuration.current_root}/"
135
- roots
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(@s3_secret_access_key @store)
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 && @thread.alive?
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
- logger&.debug('Coverband: Starting background reporting')
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(true)
33
- logger&.debug("Coverband: Reported coverage via thread. Sleeping #{sleep_seconds}s") if Coverband.configuration.verbose
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 Middleware
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
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BundlerEagerLoad
4
+ def require(*groups)
5
+ Coverband.eager_loading_coverage { super }
6
+ end
7
+ end
8
+ Bundler.singleton_class.prepend BundlerEagerLoad
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coverband
4
+ class ReportMiddleware
5
+ def initialize(app)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ @app.call(env)
11
+ ensure
12
+ Collectors::Coverage.instance.report_coverage
13
+ end
14
+ end
15
+ end
@@ -1,17 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Resque.after_fork do |job|
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::Collectors::Coverage.instance.report_coverage(true)
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(true)
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
- include Coverband::Utils::FilePathHelper
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
- if Coverband.configuration.verbose
17
- msg = "report:\n #{scov_style_report.inspect}"
18
- # Coverband.configuration.logger.debug msg
19
- end
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 = relative_path_to_full(key, roots)
35
- fixed_report[name][filename] = if fixed_report[name].key?(filename) && fixed_report[name][filename]['data'] && vals['data']
36
- merged_data = merge_arrays(fixed_report[name][filename]['data'], vals['data'])
37
- vals['data'] = merged_data
38
- vals
39
- else
40
- vals
41
- end
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