coverband 4.2.0 → 4.2.1.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +29 -9
- data/.travis.yml +9 -1
- data/Gemfile +2 -1
- data/Gemfile.rails4 +1 -0
- data/README.md +34 -13
- data/Rakefile +10 -2
- data/changes.md +25 -10
- data/coverband.gemspec +5 -1
- data/lib/coverband.rb +30 -18
- data/lib/coverband/adapters/base.rb +2 -7
- data/lib/coverband/adapters/file_store.rb +1 -1
- data/lib/coverband/at_exit.rb +2 -11
- data/lib/coverband/collectors/coverage.rb +29 -32
- data/lib/coverband/collectors/delta.rb +20 -24
- data/lib/coverband/configuration.rb +49 -19
- data/lib/coverband/integrations/background.rb +6 -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 +39 -13
- data/lib/coverband/reporters/html_report.rb +21 -27
- data/lib/coverband/reporters/web.rb +2 -21
- data/lib/coverband/utils/file_list.rb +16 -5
- data/lib/coverband/utils/file_path_helper.rb +2 -0
- data/lib/coverband/utils/html_formatter.rb +25 -8
- data/lib/coverband/utils/lines_classifier.rb +5 -0
- data/lib/coverband/utils/railtie.rb +11 -12
- data/lib/coverband/utils/result.rb +5 -44
- data/lib/coverband/utils/results.rb +51 -0
- data/lib/coverband/utils/source_file.rb +29 -5
- data/lib/coverband/utils/tasks.rb +11 -2
- data/lib/coverband/version.rb +1 -1
- data/public/application.js +27 -0
- data/test/benchmarks/benchmark.rake +10 -20
- data/test/coverband/adapters/file_store_test.rb +5 -5
- data/test/coverband/adapters/redis_store_test.rb +8 -7
- data/test/coverband/at_exit_test.rb +0 -2
- data/test/coverband/collectors/coverage_test.rb +57 -9
- data/test/coverband/collectors/delta_test.rb +27 -6
- data/test/coverband/configuration_test.rb +47 -7
- data/test/coverband/coverband_test.rb +0 -1
- 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 +4 -3
- data/test/coverband/integrations/test_resque_job.rb +3 -1
- data/test/coverband/reporters/base_test.rb +4 -4
- data/test/coverband/reporters/console_test.rb +1 -2
- data/test/coverband/reporters/html_test.rb +79 -16
- data/test/coverband/reporters/web_test.rb +0 -10
- data/test/coverband/utils/file_groups_test.rb +1 -1
- data/test/coverband/utils/file_list_test.rb +5 -5
- data/test/coverband/utils/html_formatter_test.rb +45 -0
- data/test/coverband/utils/result_test.rb +27 -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/forked/rails_full_stack_test.rb +101 -0
- data/test/forked/rails_rake_full_stack_test.rb +32 -0
- data/test/integration/full_stack_test.rb +17 -15
- data/test/rails4_dummy/Rakefile +6 -0
- data/test/rails4_dummy/config/application.rb +8 -9
- data/test/rails4_dummy/config/coverband.rb +3 -3
- data/test/rails5_dummy/Rakefile +6 -0
- data/test/rails5_dummy/config/application.rb +6 -10
- data/test/rails5_dummy/config/coverband.rb +2 -2
- data/test/rails_test_helper.rb +23 -4
- data/test/test_helper.rb +26 -1
- data/test/unique_files.rb +6 -5
- data/views/file_list.erb +2 -2
- data/views/gem_list.erb +10 -1
- data/views/layout.erb +10 -3
- data/views/source_file.erb +13 -4
- data/views/source_file_loader.erb +1 -1
- metadata +79 -9
- data/test/coverband/integrations/middleware_test.rb +0 -96
- data/test/integration/rails_full_stack_test.rb +0 -95
@@ -53,11 +53,6 @@ module Coverband
|
|
53
53
|
coverage.keys || []
|
54
54
|
end
|
55
55
|
|
56
|
-
# TODO: deprecate / remove?
|
57
|
-
def covered_lines_for_file(file)
|
58
|
-
Array(coverage.dig(file, 'data'))
|
59
|
-
end
|
60
|
-
|
61
56
|
protected
|
62
57
|
|
63
58
|
def split_coverage(types)
|
@@ -76,7 +71,7 @@ module Coverband
|
|
76
71
|
raise 'abstract'
|
77
72
|
end
|
78
73
|
|
79
|
-
def get_report
|
74
|
+
def get_report(_type = nil)
|
80
75
|
raise 'abstract'
|
81
76
|
end
|
82
77
|
|
@@ -87,7 +82,7 @@ module Coverband
|
|
87
82
|
def expand_report(report)
|
88
83
|
expanded = {}
|
89
84
|
report_time = Time.now.to_i
|
90
|
-
updated_time =
|
85
|
+
updated_time = type == Coverband::EAGER_TYPE ? nil : report_time
|
91
86
|
report.each_pair do |key, line_data|
|
92
87
|
extended_data = {
|
93
88
|
'first_updated_at' => report_time,
|
data/lib/coverband/at_exit.rb
CHANGED
@@ -16,19 +16,10 @@ module Coverband
|
|
16
16
|
at_exit do
|
17
17
|
::Coverband::Background.stop
|
18
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) }
|
19
|
+
if !Coverband.configuration.report_on_exit
|
28
20
|
# skip reporting
|
29
21
|
else
|
30
|
-
Coverband.report_coverage
|
31
|
-
#Coverband.configuration.logger&.debug('Coverband: Reported coverage before exit')
|
22
|
+
Coverband.report_coverage
|
32
23
|
end
|
33
24
|
end
|
34
25
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'singleton'
|
3
4
|
require_relative 'delta'
|
4
5
|
|
@@ -13,10 +14,13 @@ module Coverband
|
|
13
14
|
class Coverage
|
14
15
|
include Singleton
|
15
16
|
|
17
|
+
def self.ruby_version_greater_than_or_equal_to?(version)
|
18
|
+
Gem::Version.new(RUBY_VERSION) >= Gem::Version.new(version)
|
19
|
+
end
|
20
|
+
|
16
21
|
def reset_instance
|
17
22
|
@project_directory = File.expand_path(Coverband.configuration.root)
|
18
|
-
@ignore_patterns = Coverband.configuration.ignore
|
19
|
-
@reporting_frequency = Coverband.configuration.reporting_frequency
|
23
|
+
@ignore_patterns = Coverband.configuration.ignore
|
20
24
|
@store = Coverband.configuration.store
|
21
25
|
@verbose = Coverband.configuration.verbose
|
22
26
|
@logger = Coverband.configuration.logger
|
@@ -34,29 +38,29 @@ module Coverband
|
|
34
38
|
@store.type = Coverband::EAGER_TYPE
|
35
39
|
end
|
36
40
|
|
37
|
-
def
|
38
|
-
|
39
|
-
|
41
|
+
def eager_loading
|
42
|
+
old_coverage_type = @store.type
|
43
|
+
eager_loading!
|
44
|
+
yield
|
45
|
+
ensure
|
46
|
+
report_coverage
|
47
|
+
@store.type = old_coverage_type
|
48
|
+
end
|
40
49
|
|
41
|
-
|
42
|
-
|
50
|
+
def report_coverage
|
51
|
+
@semaphore.synchronize do
|
52
|
+
raise 'no Coverband store set' unless @store
|
43
53
|
|
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))
|
54
|
+
files_with_line_usage = filtered_files(Delta.results)
|
51
55
|
@store.save_report(files_with_line_usage)
|
52
56
|
end
|
53
|
-
rescue StandardError =>
|
57
|
+
rescue StandardError => e
|
54
58
|
if @verbose
|
55
|
-
@logger
|
56
|
-
@logger
|
57
|
-
@logger
|
59
|
+
@logger.error 'coverage failed to store'
|
60
|
+
@logger.error "error: #{e.inspect} #{e.message}"
|
61
|
+
@logger.error e.backtrace
|
58
62
|
end
|
59
|
-
raise
|
63
|
+
raise e if @test_env
|
60
64
|
end
|
61
65
|
|
62
66
|
protected
|
@@ -83,25 +87,18 @@ module Coverband
|
|
83
87
|
end.select { |_file_name, coverage| coverage.any? { |value| value&.nonzero? } }
|
84
88
|
end
|
85
89
|
|
86
|
-
def ready_to_report?
|
87
|
-
(rand * 100.0) >= (100.0 - @reporting_frequency)
|
88
|
-
end
|
89
|
-
|
90
90
|
def initialize
|
91
|
-
|
92
|
-
|
93
|
-
|
91
|
+
@semaphore = Mutex.new
|
92
|
+
raise NotImplementedError, 'Coverage needs Ruby > 2.3.0' if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3.0')
|
93
|
+
|
94
94
|
require 'coverage'
|
95
|
-
if
|
95
|
+
if Coverage.ruby_version_greater_than_or_equal_to?('2.6.0')
|
96
|
+
::Coverage.start(oneshot_lines: Coverband.configuration.use_oneshot_lines_coverage) unless ::Coverage.running?
|
97
|
+
elsif Coverage.ruby_version_greater_than_or_equal_to?('2.5.0')
|
96
98
|
::Coverage.start unless ::Coverage.running?
|
97
99
|
else
|
98
100
|
::Coverage.start
|
99
101
|
end
|
100
|
-
if Coverband.configuration.safe_reload_files
|
101
|
-
Coverband.configuration.safe_reload_files.each do |safe_file|
|
102
|
-
load safe_file
|
103
|
-
end
|
104
|
-
end
|
105
102
|
reset_instance
|
106
103
|
end
|
107
104
|
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Coverband
|
3
4
|
module Collectors
|
4
5
|
class Delta
|
5
|
-
|
6
|
-
@@previous_coverage = nil
|
6
|
+
@@previous_coverage = {}
|
7
7
|
attr_reader :current_coverage
|
8
8
|
|
9
9
|
def initialize(current_coverage)
|
@@ -17,18 +17,9 @@ module Coverband
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def self.results(process_coverage = RubyCoverage)
|
20
|
-
|
21
|
-
|
22
|
-
|
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 ||= {}
|
20
|
+
coverage_results = process_coverage.results
|
21
|
+
coverage_results = transform_oneshot_lines_results(coverage_results) if Coverband.configuration.use_oneshot_lines_coverage
|
22
|
+
new(coverage_results).results
|
32
23
|
end
|
33
24
|
|
34
25
|
def results
|
@@ -38,28 +29,33 @@ module Coverband
|
|
38
29
|
end
|
39
30
|
|
40
31
|
def self.reset
|
41
|
-
@@previous_coverage =
|
32
|
+
@@previous_coverage = {}
|
42
33
|
end
|
43
34
|
|
44
35
|
private
|
45
36
|
|
46
37
|
def generate
|
47
38
|
current_coverage.each_with_object({}) do |(file, line_counts), new_results|
|
48
|
-
if @@previous_coverage[file]
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
39
|
+
new_results[file] = if @@previous_coverage[file]
|
40
|
+
array_diff(line_counts, @@previous_coverage[file])
|
41
|
+
else
|
42
|
+
line_counts
|
43
|
+
end
|
53
44
|
end
|
54
45
|
end
|
55
46
|
|
56
47
|
def array_diff(latest, original)
|
57
48
|
latest.map.with_index do |v, i|
|
58
|
-
if
|
59
|
-
|
60
|
-
|
61
|
-
|
49
|
+
[0, v - original[i]].max if v && original[i]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private_class_method def self.transform_oneshot_lines_results(results)
|
54
|
+
results.each_with_object({}) do |(file, coverage), new_results|
|
55
|
+
transformed_line_counts = coverage[:oneshot_lines].each_with_object(::Coverage.line_stub(file)) do |line_number, line_counts|
|
56
|
+
line_counts[line_number - 1] = 1
|
62
57
|
end
|
58
|
+
new_results[file] = transformed_line_counts
|
63
59
|
end
|
64
60
|
end
|
65
61
|
end
|
@@ -3,15 +3,31 @@
|
|
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
|
12
11
|
|
13
12
|
attr_writer :logger, :s3_region, :s3_bucket, :s3_access_key_id, :s3_secret_access_key
|
14
|
-
attr_reader :track_gems
|
13
|
+
attr_reader :track_gems, :ignore, :use_oneshot_lines_coverage
|
14
|
+
|
15
|
+
#####
|
16
|
+
# TODO: This is is brittle and not a great solution to avoid deploy time
|
17
|
+
# actions polluting the 'runtime' metrics
|
18
|
+
#
|
19
|
+
# * should we skip /bin/rails webpacker:compile ?
|
20
|
+
# * Perhaps detect heroku deployment ENV var opposed to tasks?
|
21
|
+
#####
|
22
|
+
IGNORE_TASKS = ['coverband:clear',
|
23
|
+
'coverband:coverage',
|
24
|
+
'coverband:coverage_server',
|
25
|
+
'coverband:migrate']
|
26
|
+
|
27
|
+
# Heroku when building assets runs code from a dynamic directory
|
28
|
+
# /tmp was added to avoid coverage from /tmp/build directories during
|
29
|
+
# heroku asset compilation
|
30
|
+
IGNORE_DEFAULTS = %w[vendor .erb$ .slim$ /tmp internal:prelude schema.rb]
|
15
31
|
|
16
32
|
def initialize
|
17
33
|
reset
|
@@ -20,12 +36,8 @@ module Coverband
|
|
20
36
|
def reset
|
21
37
|
@root = Dir.pwd
|
22
38
|
@root_paths = []
|
23
|
-
|
24
|
-
# /tmp was added to avoid coverage from /tmp/build directories during
|
25
|
-
# heroku asset compilation
|
26
|
-
@ignore = %w[vendor .erb$ .slim$ /tmp]
|
39
|
+
@ignore = IGNORE_DEFAULTS.dup
|
27
40
|
@additional_files = []
|
28
|
-
@reporting_frequency = 0.0
|
29
41
|
@verbose = false
|
30
42
|
@reporter = 'scov'
|
31
43
|
@logger = nil
|
@@ -38,6 +50,8 @@ module Coverband
|
|
38
50
|
@gem_details = false
|
39
51
|
@groups = {}
|
40
52
|
@web_debug = false
|
53
|
+
@report_on_exit = true
|
54
|
+
@use_oneshot_lines_coverage = false
|
41
55
|
|
42
56
|
# TODO: should we push these to adapter configs
|
43
57
|
@s3_region = nil
|
@@ -49,7 +63,7 @@ module Coverband
|
|
49
63
|
end
|
50
64
|
|
51
65
|
def logger
|
52
|
-
@logger ||= if defined?(Rails.logger)
|
66
|
+
@logger ||= if defined?(Rails.logger) && Rails.logger
|
53
67
|
Rails.logger
|
54
68
|
else
|
55
69
|
Logger.new(STDOUT)
|
@@ -77,21 +91,27 @@ module Coverband
|
|
77
91
|
end
|
78
92
|
|
79
93
|
def store=(store)
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
94
|
+
raise 'Pass in an instance of Coverband::Adapters' unless store.is_a?(Coverband::Adapters::Base)
|
95
|
+
|
96
|
+
@store = store
|
97
|
+
end
|
98
|
+
|
99
|
+
###
|
100
|
+
# Don't allow the ignore to override things like gem tracking
|
101
|
+
###
|
102
|
+
def ignore=(ignored_array)
|
103
|
+
@ignore = (@ignore + ignored_array).uniq
|
85
104
|
end
|
86
105
|
|
87
106
|
def track_gems=(value)
|
88
107
|
@track_gems = value
|
89
108
|
return unless @track_gems
|
109
|
+
|
90
110
|
# by default we ignore vendor where many deployments put gems
|
91
111
|
# we will remove this default if track_gems is set
|
92
112
|
@ignore.delete('vendor')
|
93
113
|
# while we want to allow vendored gems we don't want to track vendored ruby STDLIB
|
94
|
-
@ignore << 'vendor/ruby-*'
|
114
|
+
@ignore << 'vendor/ruby-*' unless @ignore.include?('vendor/ruby-*')
|
95
115
|
add_group('App', root)
|
96
116
|
# TODO: rework support for multiple gem paths
|
97
117
|
# currently this supports GEM_HOME (which should be first path)
|
@@ -135,7 +155,7 @@ module Coverband
|
|
135
155
|
roots
|
136
156
|
end
|
137
157
|
|
138
|
-
SKIPPED_SETTINGS = %w
|
158
|
+
SKIPPED_SETTINGS = %w[@s3_secret_access_key @store]
|
139
159
|
def to_h
|
140
160
|
instance_variables
|
141
161
|
.each_with_object('gem_paths': gem_paths) do |var, hash|
|
@@ -143,6 +163,16 @@ module Coverband
|
|
143
163
|
end
|
144
164
|
end
|
145
165
|
|
166
|
+
def use_oneshot_lines_coverage=(value)
|
167
|
+
raise(Exception, 'One shot line coverage is only available in ruby >= 2.6') unless one_shot_coverage_implemented_in_ruby_version? || !value
|
168
|
+
|
169
|
+
@use_oneshot_lines_coverage = value
|
170
|
+
end
|
171
|
+
|
172
|
+
def one_shot_coverage_implemented_in_ruby_version?
|
173
|
+
Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.6.0')
|
174
|
+
end
|
175
|
+
|
146
176
|
private
|
147
177
|
|
148
178
|
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,13 @@ 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')
|
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
|
-
logger
|
34
|
+
Coverband.report_coverage
|
35
|
+
logger.debug("Coverband: Reported coverage via thread. Sleeping #{sleep_seconds}s") if Coverband.configuration.verbose
|
34
36
|
sleep(sleep_seconds)
|
35
37
|
end
|
36
38
|
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
|