coverband 4.0.1 → 4.2.0

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 (90) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +6 -1
  3. data/.travis.yml +8 -2
  4. data/Gemfile +2 -0
  5. data/Gemfile.rails4 +2 -0
  6. data/README.md +135 -157
  7. data/Rakefile +1 -1
  8. data/changes.md +56 -5
  9. data/coverband.gemspec +5 -3
  10. data/lib/coverband/adapters/base.rb +44 -11
  11. data/lib/coverband/adapters/file_store.rb +8 -0
  12. data/lib/coverband/adapters/redis_store.rb +65 -8
  13. data/lib/coverband/at_exit.rb +37 -0
  14. data/lib/coverband/collectors/coverage.rb +43 -72
  15. data/lib/coverband/collectors/delta.rb +67 -0
  16. data/lib/coverband/configuration.rb +71 -2
  17. data/lib/coverband/integrations/background.rb +9 -10
  18. data/lib/coverband/integrations/middleware.rb +3 -2
  19. data/lib/coverband/integrations/rack_server_check.rb +4 -1
  20. data/lib/coverband/integrations/resque.rb +27 -0
  21. data/lib/coverband/reporters/base.rb +24 -36
  22. data/lib/coverband/reporters/html_report.rb +40 -15
  23. data/lib/coverband/reporters/web.rb +52 -10
  24. data/lib/coverband/utils/file_groups.rb +53 -0
  25. data/lib/coverband/utils/file_path_helper.rb +57 -0
  26. data/lib/coverband/utils/gem_list.rb +31 -0
  27. data/lib/coverband/utils/html_formatter.rb +50 -8
  28. data/lib/coverband/utils/railtie.rb +9 -1
  29. data/lib/coverband/utils/result.rb +16 -19
  30. data/lib/coverband/utils/results.rb +63 -0
  31. data/lib/coverband/utils/source_file.rb +23 -1
  32. data/lib/coverband/utils/tasks.rb +17 -17
  33. data/lib/coverband/version.rb +1 -1
  34. data/lib/coverband.rb +32 -2
  35. data/public/application.css +5 -0
  36. data/public/application.js +117 -1640
  37. data/public/dependencies.js +1581 -0
  38. data/public/favicon.png +0 -0
  39. data/test/benchmarks/benchmark.rake +36 -1
  40. data/test/benchmarks/coverage_fork.sh +37 -0
  41. data/test/{unit/adapters_base_test.rb → coverband/adapters/base_test.rb} +1 -1
  42. data/test/{unit/adapters_file_store_test.rb → coverband/adapters/file_store_test.rb} +1 -1
  43. data/test/{unit/adapters_redis_store_test.rb → coverband/adapters/redis_store_test.rb} +28 -11
  44. data/test/coverband/at_exit_test.rb +13 -0
  45. data/test/coverband/collectors/coverage_test.rb +73 -0
  46. data/test/coverband/collectors/delta_test.rb +52 -0
  47. data/test/coverband/configuration_test.rb +67 -0
  48. data/test/{unit → coverband}/coverband_test.rb +7 -0
  49. data/test/coverband/integrations/background_test.rb +31 -0
  50. data/test/{unit → coverband/integrations}/middleware_test.rb +3 -2
  51. data/test/{unit/rack_server_checkout_test.rb → coverband/integrations/rack_server_check_test.rb} +1 -1
  52. data/test/coverband/integrations/resque_worker_test.rb +42 -0
  53. data/test/coverband/integrations/test_resque_job.rb +7 -0
  54. data/test/coverband/reporters/base_test.rb +168 -0
  55. data/test/{unit/reports_console_test.rb → coverband/reporters/console_test.rb} +8 -7
  56. data/test/coverband/reporters/html_test.rb +45 -0
  57. data/test/coverband/reporters/web_test.rb +52 -0
  58. data/test/coverband/utils/file_groups_test.rb +55 -0
  59. data/test/{unit → coverband}/utils/file_list_test.rb +1 -1
  60. data/test/coverband/utils/gem_list_test.rb +48 -0
  61. data/test/{unit → coverband}/utils/source_file_test.rb +1 -1
  62. data/test/{unit → integration}/full_stack_test.rb +25 -12
  63. data/test/{unit → integration}/rails_full_stack_test.rb +27 -7
  64. data/test/rails4_dummy/config/application.rb +1 -1
  65. data/test/rails4_dummy/config/coverband.rb +4 -1
  66. data/test/rails4_dummy/tmp/.keep +0 -0
  67. data/test/rails5_dummy/config/application.rb +1 -1
  68. data/test/rails5_dummy/config/coverband.rb +3 -1
  69. data/test/rails5_dummy/tmp/.keep +0 -0
  70. data/test/rails_test_helper.rb +13 -8
  71. data/test/test_helper.rb +12 -17
  72. data/test/unique_files.rb +23 -0
  73. data/views/file_list.erb +33 -20
  74. data/views/gem_list.erb +54 -0
  75. data/views/layout.erb +13 -4
  76. data/views/settings.erb +35 -0
  77. data/views/source_file.erb +16 -4
  78. data/views/source_file_loader.erb +7 -0
  79. metadata +108 -62
  80. data/test/unit/background_test.rb +0 -29
  81. data/test/unit/collectors_coverage_test.rb +0 -48
  82. data/test/unit/configuration_test.rb +0 -23
  83. data/test/unit/reports_base_test.rb +0 -109
  84. data/test/unit/reports_html_test.rb +0 -29
  85. data/test/unit/reports_web_test.rb +0 -39
  86. /data/test/{unit → coverband}/utils/lines_classifier_test.rb +0 -0
  87. /data/test/{unit → coverband}/utils/result_test.rb +0 -0
  88. /data/test/{unit → coverband}/utils/s3_report_test.rb +0 -0
  89. /data/test/{unit → coverband}/utils/source_file_line_test.rb +0 -0
  90. /data/test/{unit/dog.rb → dog.rb} +0 -0
@@ -3,6 +3,9 @@
3
3
  module Coverband
4
4
  module Adapters
5
5
  class Base
6
+ include Coverband::Utils::FilePathHelper
7
+ attr_accessor :type
8
+
6
9
  def initialize
7
10
  @file_hash_cache = {}
8
11
  end
@@ -11,13 +14,29 @@ module Coverband
11
14
  raise 'abstract'
12
15
  end
13
16
 
17
+ def clear_file!(_file)
18
+ raise 'abstract'
19
+ end
20
+
21
+ def migrate!
22
+ raise 'abstract'
23
+ end
24
+
25
+ def size
26
+ raise 'abstract'
27
+ end
28
+
29
+ def size_in_mib
30
+ format('%.2f', (size.to_f / 2**20))
31
+ end
32
+
14
33
  # Note: This could lead to slight race on redis
15
34
  # where multiple processes pull the old coverage and add to it then push
16
35
  # the Coverband 2 had the same issue,
17
36
  # and the tradeoff has always been acceptable
18
37
  def save_report(report)
19
38
  data = report.dup
20
- merge_reports(data, get_report)
39
+ data = merge_reports(data, get_report)
21
40
  save_coverage(data)
22
41
  end
23
42
 
@@ -25,6 +44,11 @@ module Coverband
25
44
  get_report
26
45
  end
27
46
 
47
+ def get_coverage_report
48
+ data = Coverband.configuration.store.split_coverage(Coverband::TYPES)
49
+ data.merge(Coverband::MERGED_TYPE => Coverband.configuration.store.merged_coverage(Coverband::TYPES))
50
+ end
51
+
28
52
  def covered_files
29
53
  coverage.keys || []
30
54
  end
@@ -36,6 +60,18 @@ module Coverband
36
60
 
37
61
  protected
38
62
 
63
+ def split_coverage(types)
64
+ types.reduce({}) do |data, type|
65
+ data.update(type => get_report(type))
66
+ end
67
+ end
68
+
69
+ def merged_coverage(types)
70
+ types.reduce({}) do |data, type|
71
+ merge_reports(data, get_report(type), skip_expansion: true)
72
+ end
73
+ end
74
+
39
75
  def save_coverage
40
76
  raise 'abstract'
41
77
  end
@@ -49,20 +85,23 @@ module Coverband
49
85
  end
50
86
 
51
87
  def expand_report(report)
88
+ expanded = {}
52
89
  report_time = Time.now.to_i
90
+ updated_time = self.type == Coverband::EAGER_TYPE ? nil : report_time
53
91
  report.each_pair do |key, line_data|
54
92
  extended_data = {
55
93
  'first_updated_at' => report_time,
56
- 'last_updated_at' => report_time,
94
+ 'last_updated_at' => updated_time,
57
95
  'file_hash' => file_hash(key),
58
96
  'data' => line_data
59
97
  }
60
- report[key] = extended_data
98
+ expanded[full_path_to_relative(key)] = extended_data
61
99
  end
100
+ expanded
62
101
  end
63
102
 
64
- def merge_reports(new_report, old_report)
65
- new_report = expand_report(new_report)
103
+ def merge_reports(new_report, old_report, options = {})
104
+ new_report = expand_report(new_report) unless options[:skip_expansion]
66
105
  keys = (new_report.keys + old_report.keys).uniq
67
106
  keys.each do |file|
68
107
  new_report[file] = if new_report[file] &&
@@ -90,12 +129,6 @@ module Coverband
90
129
  def array_add(latest, original)
91
130
  latest.map.with_index { |v, i| (v && original[i]) ? v + original[i] : nil }
92
131
  end
93
-
94
- def simple_report(report)
95
- report.each_with_object({}) do |(key, extended_data), simple|
96
- simple[key] = extended_data['data']
97
- end
98
- end
99
132
  end
100
133
  end
101
134
  end
@@ -20,6 +20,14 @@ module Coverband
20
20
  File.delete(path) if File.exist?(path)
21
21
  end
22
22
 
23
+ def size
24
+ File.size?(path).to_i
25
+ end
26
+
27
+ def migrate!
28
+ raise NotImplementedError, "FileStore doesn't support migrations"
29
+ end
30
+
23
31
  private
24
32
 
25
33
  attr_accessor :path
@@ -6,34 +6,91 @@ module Coverband
6
6
  # RedisStore store a merged coverage file to redis
7
7
  ###
8
8
  class RedisStore < Base
9
- BASE_KEY = 'coverband3_1'
9
+ ###
10
+ # This key isn't related to the coverband version, but to the interal format
11
+ # used to store data to redis. It is changed only when breaking changes to our
12
+ # redis format are required.
13
+ ###
14
+ REDIS_STORAGE_FORMAT_VERSION = 'coverband_3_2'
10
15
 
11
16
  def initialize(redis, opts = {})
12
17
  super()
13
18
  @redis = redis
14
19
  @ttl = opts[:ttl]
15
20
  @redis_namespace = opts[:redis_namespace]
21
+ @format_version = REDIS_STORAGE_FORMAT_VERSION
16
22
  end
17
23
 
18
24
  def clear!
19
- @redis.del(base_key)
25
+ Coverband::TYPES.each do |type|
26
+ @redis.del(type_base_key(type))
27
+ end
28
+ end
29
+
30
+ def clear_file!(filename)
31
+ Coverband::TYPES.each do |type|
32
+ data = get_report(type)
33
+ data.delete(filename)
34
+ save_coverage(data, type)
35
+ end
36
+ end
37
+
38
+ def size
39
+ @redis.get(base_key).bytesize
40
+ end
41
+
42
+ ###
43
+ # Current implementation moves from coverband3_1 to coverband_3_2
44
+ # In the future this can be made more general and support a more specific
45
+ # version format.
46
+ ###
47
+ def migrate!
48
+ reset_base_key
49
+ @format_version = 'coverband3_1'
50
+ previous_data = get_report
51
+ if previous_data.empty?
52
+ puts 'no previous data to migrate found'
53
+ exit 0
54
+ end
55
+ relative_path_report = previous_data.each_with_object({}) do |(key, vals), fixed_report|
56
+ fixed_report[full_path_to_relative(key)] = vals
57
+ end
58
+ clear!
59
+ reset_base_key
60
+ @format_version = REDIS_STORAGE_FORMAT_VERSION
61
+ save_coverage(merge_reports(get_report, relative_path_report, skip_expansion: true))
62
+ end
63
+
64
+ def type=(type)
65
+ super
66
+ reset_base_key
20
67
  end
21
68
 
22
69
  private
23
70
 
24
71
  attr_reader :redis
25
72
 
73
+ def reset_base_key
74
+ @base_key = nil
75
+ end
76
+
26
77
  def base_key
27
- @base_key ||= [BASE_KEY, @redis_namespace].compact.join('.')
78
+ @base_key ||= [@format_version, @redis_namespace, type].compact.join('.')
79
+ end
80
+
81
+ def type_base_key(local_type)
82
+ [@format_version, @redis_namespace, local_type].compact.join('.')
28
83
  end
29
84
 
30
- def save_coverage(data)
31
- redis.set base_key, data.to_json
32
- redis.expire(base_key, @ttl) if @ttl
85
+ def save_coverage(data, local_type = nil)
86
+ local_type ||= type
87
+ redis.set type_base_key(local_type), data.to_json
88
+ redis.expire(type_base_key(local_type), @ttl) if @ttl
33
89
  end
34
90
 
35
- def get_report
36
- data = redis.get base_key
91
+ def get_report(local_type = nil)
92
+ local_type ||= type
93
+ data = redis.get type_base_key(local_type)
37
94
  data ? JSON.parse(data) : {}
38
95
  end
39
96
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coverband
4
+ class AtExit
5
+ @semaphore = Mutex.new
6
+
7
+ @at_exit_registered = nil
8
+ def self.register
9
+ return if ENV['COVERBAND_DISABLE_AT_EXIT']
10
+ return if @at_exit_registered
11
+
12
+ @semaphore.synchronize do
13
+ return if @at_exit_registered
14
+
15
+ @at_exit_registered = true
16
+ at_exit do
17
+ ::Coverband::Background.stop
18
+
19
+ #####
20
+ # TODO: This is is brittle and not a great solution to avoid deploy time
21
+ # actions polluting the 'runtime' metrics
22
+ #
23
+ # * should we skip /bin/rails webpacker:compile ?
24
+ # * Perhaps detect heroku deployment ENV var opposed to tasks?
25
+ #####
26
+ default_heroku_tasks = ['assets:clean', 'assets:precompile']
27
+ if defined?(Rake) && Rake.respond_to?(:application) && (Rake&.application&.top_level_tasks || []).any? { |task| default_heroku_tasks.include?(task) }
28
+ # skip reporting
29
+ else
30
+ Coverband.report_coverage(true)
31
+ #Coverband.configuration.logger&.debug('Coverband: Reported coverage before exit')
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,124 +1,97 @@
1
1
  # frozen_string_literal: true
2
+ require 'singleton'
3
+ require_relative 'delta'
2
4
 
3
5
  module Coverband
4
6
  module Collectors
5
7
  ###
6
- # TODO: look at alternatives to semaphore
7
- # StandardError seems line be better option
8
+ # StandardError seems like be better option
8
9
  # coverband previously had RuntimeError here
9
10
  # but runtime error can let a large number of error crash this method
10
11
  # and this method is currently in a ensure block in middleware and threads
11
12
  ###
12
13
  class Coverage
13
- def self.instance
14
- Thread.current[:coverband_instance] ||= Coverband::Collectors::Coverage.new
15
- end
14
+ include Singleton
16
15
 
17
16
  def reset_instance
18
17
  @project_directory = File.expand_path(Coverband.configuration.root)
19
- @file_line_usage = {}
20
18
  @ignore_patterns = Coverband.configuration.ignore + ['internal:prelude', 'schema.rb']
21
19
  @reporting_frequency = Coverband.configuration.reporting_frequency
22
20
  @store = Coverband.configuration.store
23
21
  @verbose = Coverband.configuration.verbose
24
22
  @logger = Coverband.configuration.logger
25
- @current_thread = Thread.current
26
23
  @test_env = Coverband.configuration.test_env
27
- @@previous_results = nil
28
- Thread.current[:coverband_instance] = nil
24
+ @track_gems = Coverband.configuration.track_gems
25
+ Delta.reset
29
26
  self
30
27
  end
31
28
 
29
+ def runtime!
30
+ @store.type = nil
31
+ end
32
+
33
+ def eager_loading!
34
+ @store.type = Coverband::EAGER_TYPE
35
+ end
36
+
32
37
  def report_coverage(force_report = false)
33
38
  return if !ready_to_report? && !force_report
34
39
  raise 'no Coverband store set' unless @store
35
40
 
36
- new_results = get_new_coverage_results
37
- add_filtered_files(new_results)
38
- @store.save_report(files_with_line_usage)
39
- @file_line_usage.clear
41
+ original_previous_set = Delta.previous_results
42
+ files_with_line_usage = filtered_files(Delta.results)
43
+
44
+ ###
45
+ # Hack to prevent processes and threads from reporting first Coverage hit
46
+ # when we are in runtime collection mode, which do not have a cache of previous
47
+ # coverage to remove the initial stdlib Coverage loading data
48
+ ###
49
+ if ((original_previous_set.nil? && @store.type == Coverband::EAGER_TYPE) ||
50
+ (original_previous_set && @store.type != Coverband::EAGER_TYPE))
51
+ @store.save_report(files_with_line_usage)
52
+ end
40
53
  rescue StandardError => err
41
54
  if @verbose
42
- @logger.error 'coverage failed to store'
43
- @logger.error "error: #{err.inspect} #{err.message}"
44
- @logger.error err.backtrace
55
+ @logger&.error 'coverage failed to store'
56
+ @logger&.error "error: #{err.inspect} #{err.message}"
57
+ @logger&.error err.backtrace
45
58
  end
46
59
  raise err if @test_env
47
60
  end
48
61
 
49
62
  protected
50
63
 
64
+ ###
65
+ # Normally I would break this out into additional methods
66
+ # and improve the readability but this is in a tight loop
67
+ # on the critical performance path, and any refactoring I come up with
68
+ # would slow down the performance.
69
+ ###
51
70
  def track_file?(file)
52
71
  @ignore_patterns.none? do |pattern|
53
72
  file.include?(pattern)
54
- end && file.start_with?(@project_directory)
73
+ end && (file.start_with?(@project_directory) ||
74
+ (@track_gems &&
75
+ Coverband.configuration.gem_paths.any? { |path| file.start_with?(path) }))
55
76
  end
56
77
 
57
78
  private
58
79
 
59
- def add_filtered_files(new_results)
60
- new_results.each_pair do |file, line_counts|
61
- next unless track_file?(file)
62
- add_file(file, line_counts)
63
- end
80
+ def filtered_files(new_results)
81
+ new_results.each_with_object({}) do |(file, line_counts), file_line_usage|
82
+ file_line_usage[file] = line_counts if track_file?(file)
83
+ end.select { |_file_name, coverage| coverage.any? { |value| value&.nonzero? } }
64
84
  end
65
85
 
66
86
  def ready_to_report?
67
87
  (rand * 100.0) >= (100.0 - @reporting_frequency)
68
88
  end
69
89
 
70
- def get_new_coverage_results
71
- @semaphore.synchronize { new_coverage(::Coverage.peek_result.dup) }
72
- end
73
-
74
- def files_with_line_usage
75
- @file_line_usage.select do |_file_name, coverage|
76
- coverage.any? { |value| value&.nonzero? }
77
- end
78
- end
79
-
80
- def array_diff(latest, original)
81
- latest.map.with_index { |v, i| (v && original[i]) ? v - original[i] : nil }
82
- end
83
-
84
- def previous_results
85
- @@previous_results
86
- end
87
-
88
- def add_previous_results(val)
89
- @@previous_results = val
90
- end
91
-
92
- def new_coverage(current_coverage)
93
- if previous_results
94
- new_results = {}
95
- current_coverage.each_pair do |file, line_counts|
96
- if previous_results[file]
97
- new_results[file] = array_diff(line_counts, previous_results[file])
98
- else
99
- new_results[file] = line_counts
100
- end
101
- end
102
- else
103
- new_results = current_coverage
104
- end
105
-
106
- add_previous_results(current_coverage)
107
- new_results.dup
108
- end
109
-
110
- def add_file(file, line_counts)
111
- @file_line_usage[file] = line_counts
112
- end
113
-
114
90
  def initialize
115
91
  if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3.0')
116
92
  raise NotImplementedError, 'not supported until Ruby 2.3.0 and later'
117
93
  end
118
- unless defined?(::Coverage)
119
- # puts 'loading coverage'
120
- require 'coverage'
121
- end
94
+ require 'coverage'
122
95
  if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.5.0')
123
96
  ::Coverage.start unless ::Coverage.running?
124
97
  else
@@ -129,8 +102,6 @@ module Coverband
129
102
  load safe_file
130
103
  end
131
104
  end
132
- @semaphore = Mutex.new
133
- @@previous_results = nil
134
105
  reset_instance
135
106
  end
136
107
  end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+ module Coverband
3
+ module Collectors
4
+ class Delta
5
+ @semaphore = Mutex.new
6
+ @@previous_coverage = nil
7
+ attr_reader :current_coverage
8
+
9
+ def initialize(current_coverage)
10
+ @current_coverage = current_coverage
11
+ end
12
+
13
+ class RubyCoverage
14
+ def self.results
15
+ ::Coverage.peek_result.dup
16
+ end
17
+ end
18
+
19
+ 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 ||= {}
32
+ end
33
+
34
+ def results
35
+ new_results = generate
36
+ @@previous_coverage = current_coverage
37
+ new_results
38
+ end
39
+
40
+ def self.reset
41
+ @@previous_coverage = nil
42
+ end
43
+
44
+ private
45
+
46
+ def generate
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
53
+ end
54
+ end
55
+
56
+ def array_diff(latest, original)
57
+ latest.map.with_index do |v, i|
58
+ if (v && original[i])
59
+ [0, v - original[i]].max
60
+ else
61
+ nil
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -7,9 +7,11 @@ module Coverband
7
7
  :reporter, :reporting_frequency,
8
8
  :redis_namespace, :redis_ttl,
9
9
  :safe_reload_files, :background_reporting_enabled,
10
- :background_reporting_sleep_seconds, :test_env
10
+ :background_reporting_sleep_seconds, :test_env,
11
+ :web_enable_clear, :gem_details, :web_debug
11
12
 
12
13
  attr_writer :logger, :s3_region, :s3_bucket, :s3_access_key_id, :s3_secret_access_key
14
+ attr_reader :track_gems
13
15
 
14
16
  def initialize
15
17
  reset
@@ -18,7 +20,10 @@ module Coverband
18
20
  def reset
19
21
  @root = Dir.pwd
20
22
  @root_paths = []
21
- @ignore = %w(vendor .erb$ .slim$)
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]
22
27
  @additional_files = []
23
28
  @reporting_frequency = 0.0
24
29
  @verbose = false
@@ -28,6 +33,11 @@ module Coverband
28
33
  @background_reporting_enabled = true
29
34
  @background_reporting_sleep_seconds = 30
30
35
  @test_env = nil
36
+ @web_enable_clear = false
37
+ @track_gems = false
38
+ @gem_details = false
39
+ @groups = {}
40
+ @web_debug = false
31
41
 
32
42
  # TODO: should we push these to adapter configs
33
43
  @s3_region = nil
@@ -74,6 +84,65 @@ module Coverband
74
84
  end
75
85
  end
76
86
 
87
+ def track_gems=(value)
88
+ @track_gems = value
89
+ return unless @track_gems
90
+ # by default we ignore vendor where many deployments put gems
91
+ # we will remove this default if track_gems is set
92
+ @ignore.delete('vendor')
93
+ # while we want to allow vendored gems we don't want to track vendored ruby STDLIB
94
+ @ignore << 'vendor/ruby-*'
95
+ add_group('App', root)
96
+ # 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)
103
+ end
104
+
105
+ #
106
+ # Returns the configured groups. Add groups using SimpleCov.add_group
107
+ #
108
+ def groups
109
+ @groups ||= {}
110
+ end
111
+
112
+ #
113
+ # Define a group for files. Works similar to add_filter, only that the first
114
+ # argument is the desired group name and files PASSING the filter end up in the group
115
+ # (while filters exclude when the filter is applicable).
116
+ #
117
+ def add_group(group_name, filter_argument = nil)
118
+ groups[group_name] = filter_argument
119
+ end
120
+
121
+ def gem_paths
122
+ # notes ignore any paths that aren't on this system, resolves
123
+ # bug related to multiple ruby version managers / bad dot files
124
+ Gem::PathSupport.new(ENV).path.select { |path| File.exist?(path) }
125
+ end
126
+
127
+ def current_root
128
+ File.expand_path(Coverband.configuration.root)
129
+ end
130
+
131
+ 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
136
+ end
137
+
138
+ SKIPPED_SETTINGS = %w(@s3_secret_access_key @store)
139
+ def to_h
140
+ instance_variables
141
+ .each_with_object('gem_paths': gem_paths) do |var, hash|
142
+ hash[var.to_s.delete('@')] = instance_variable_get(var) unless SKIPPED_SETTINGS.include?(var.to_s)
143
+ end
144
+ end
145
+
77
146
  private
78
147
 
79
148
  def redis_url
@@ -3,8 +3,10 @@
3
3
  module Coverband
4
4
  class Background
5
5
  @semaphore = Mutex.new
6
+ @thread = nil
6
7
 
7
8
  def self.stop
9
+ return unless @thread
8
10
  @semaphore.synchronize do
9
11
  if @thread
10
12
  @thread.exit
@@ -13,28 +15,25 @@ module Coverband
13
15
  end
14
16
  end
15
17
 
18
+ def self.running?
19
+ @thread && @thread.alive?
20
+ end
21
+
16
22
  def self.start
17
- return if @thread
23
+ return if running?
18
24
 
19
25
  logger = Coverband.configuration.logger
20
26
  @semaphore.synchronize do
21
- binding.pry if defined?($debug) && $debug
22
- return if @thread
27
+ return if running?
23
28
  logger&.debug('Coverband: Starting background reporting')
24
29
  sleep_seconds = Coverband.configuration.background_reporting_sleep_seconds
25
30
  @thread = Thread.new do
26
31
  loop do
27
- Coverband::Collectors::Coverage.instance.report_coverage(true)
32
+ Coverband.report_coverage(true)
28
33
  logger&.debug("Coverband: Reported coverage via thread. Sleeping #{sleep_seconds}s") if Coverband.configuration.verbose
29
34
  sleep(sleep_seconds)
30
35
  end
31
36
  end
32
- binding.pry if defined?($debug) && $debug
33
- end
34
- at_exit do
35
- stop
36
- Coverband::Collectors::Coverage.instance.report_coverage(true)
37
- logger&.debug('Coverband: Reported coverage before exit')
38
37
  end
39
38
  end
40
39
  end
@@ -9,10 +9,11 @@ module Coverband
9
9
  def call(env)
10
10
  @app.call(env)
11
11
  ensure
12
+ AtExit.register
12
13
  if Coverband.configuration.background_reporting_enabled
13
- Coverband::Background.start
14
+ Background.start
14
15
  else
15
- Coverband::Collectors::Coverage.instance.report_coverage
16
+ Collectors::Coverage.instance.report_coverage
16
17
  end
17
18
  end
18
19
  end
@@ -20,7 +20,10 @@ module Coverband
20
20
 
21
21
  def rails_server?
22
22
  @stack.any? do |location|
23
- location.path.include?('rails/commands/commands_tasks.rb') && location.label == 'server'
23
+ (
24
+ location.path.include?('rails/commands/commands_tasks.rb') && location.label == 'server' ||
25
+ location.path.include?('rails/commands/server/server_command.rb') && location.label == 'perform'
26
+ )
24
27
  end
25
28
  end
26
29
  end