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.
- checksums.yaml +5 -5
- data/.gitignore +6 -1
- data/.travis.yml +8 -2
- data/Gemfile +2 -0
- data/Gemfile.rails4 +2 -0
- data/README.md +135 -157
- data/Rakefile +1 -1
- data/changes.md +56 -5
- data/coverband.gemspec +5 -3
- data/lib/coverband/adapters/base.rb +44 -11
- data/lib/coverband/adapters/file_store.rb +8 -0
- data/lib/coverband/adapters/redis_store.rb +65 -8
- data/lib/coverband/at_exit.rb +37 -0
- data/lib/coverband/collectors/coverage.rb +43 -72
- data/lib/coverband/collectors/delta.rb +67 -0
- data/lib/coverband/configuration.rb +71 -2
- data/lib/coverband/integrations/background.rb +9 -10
- data/lib/coverband/integrations/middleware.rb +3 -2
- data/lib/coverband/integrations/rack_server_check.rb +4 -1
- data/lib/coverband/integrations/resque.rb +27 -0
- data/lib/coverband/reporters/base.rb +24 -36
- data/lib/coverband/reporters/html_report.rb +40 -15
- data/lib/coverband/reporters/web.rb +52 -10
- data/lib/coverband/utils/file_groups.rb +53 -0
- data/lib/coverband/utils/file_path_helper.rb +57 -0
- data/lib/coverband/utils/gem_list.rb +31 -0
- data/lib/coverband/utils/html_formatter.rb +50 -8
- data/lib/coverband/utils/railtie.rb +9 -1
- data/lib/coverband/utils/result.rb +16 -19
- data/lib/coverband/utils/results.rb +63 -0
- data/lib/coverband/utils/source_file.rb +23 -1
- data/lib/coverband/utils/tasks.rb +17 -17
- data/lib/coverband/version.rb +1 -1
- data/lib/coverband.rb +32 -2
- data/public/application.css +5 -0
- data/public/application.js +117 -1640
- data/public/dependencies.js +1581 -0
- data/public/favicon.png +0 -0
- data/test/benchmarks/benchmark.rake +36 -1
- data/test/benchmarks/coverage_fork.sh +37 -0
- data/test/{unit/adapters_base_test.rb → coverband/adapters/base_test.rb} +1 -1
- data/test/{unit/adapters_file_store_test.rb → coverband/adapters/file_store_test.rb} +1 -1
- data/test/{unit/adapters_redis_store_test.rb → coverband/adapters/redis_store_test.rb} +28 -11
- data/test/coverband/at_exit_test.rb +13 -0
- data/test/coverband/collectors/coverage_test.rb +73 -0
- data/test/coverband/collectors/delta_test.rb +52 -0
- data/test/coverband/configuration_test.rb +67 -0
- data/test/{unit → coverband}/coverband_test.rb +7 -0
- data/test/coverband/integrations/background_test.rb +31 -0
- data/test/{unit → coverband/integrations}/middleware_test.rb +3 -2
- data/test/{unit/rack_server_checkout_test.rb → coverband/integrations/rack_server_check_test.rb} +1 -1
- data/test/coverband/integrations/resque_worker_test.rb +42 -0
- data/test/coverband/integrations/test_resque_job.rb +7 -0
- data/test/coverband/reporters/base_test.rb +168 -0
- data/test/{unit/reports_console_test.rb → coverband/reporters/console_test.rb} +8 -7
- data/test/coverband/reporters/html_test.rb +45 -0
- data/test/coverband/reporters/web_test.rb +52 -0
- data/test/coverband/utils/file_groups_test.rb +55 -0
- data/test/{unit → coverband}/utils/file_list_test.rb +1 -1
- data/test/coverband/utils/gem_list_test.rb +48 -0
- data/test/{unit → coverband}/utils/source_file_test.rb +1 -1
- data/test/{unit → integration}/full_stack_test.rb +25 -12
- data/test/{unit → integration}/rails_full_stack_test.rb +27 -7
- data/test/rails4_dummy/config/application.rb +1 -1
- data/test/rails4_dummy/config/coverband.rb +4 -1
- data/test/rails4_dummy/tmp/.keep +0 -0
- data/test/rails5_dummy/config/application.rb +1 -1
- data/test/rails5_dummy/config/coverband.rb +3 -1
- data/test/rails5_dummy/tmp/.keep +0 -0
- data/test/rails_test_helper.rb +13 -8
- data/test/test_helper.rb +12 -17
- data/test/unique_files.rb +23 -0
- data/views/file_list.erb +33 -20
- data/views/gem_list.erb +54 -0
- data/views/layout.erb +13 -4
- data/views/settings.erb +35 -0
- data/views/source_file.erb +16 -4
- data/views/source_file_loader.erb +7 -0
- metadata +108 -62
- data/test/unit/background_test.rb +0 -29
- data/test/unit/collectors_coverage_test.rb +0 -48
- data/test/unit/configuration_test.rb +0 -23
- data/test/unit/reports_base_test.rb +0 -109
- data/test/unit/reports_html_test.rb +0 -29
- data/test/unit/reports_web_test.rb +0 -39
- /data/test/{unit → coverband}/utils/lines_classifier_test.rb +0 -0
- /data/test/{unit → coverband}/utils/result_test.rb +0 -0
- /data/test/{unit → coverband}/utils/s3_report_test.rb +0 -0
- /data/test/{unit → coverband}/utils/source_file_line_test.rb +0 -0
- /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' =>
|
|
94
|
+
'last_updated_at' => updated_time,
|
|
57
95
|
'file_hash' => file_hash(key),
|
|
58
96
|
'data' => line_data
|
|
59
97
|
}
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ||= [
|
|
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
|
-
|
|
32
|
-
redis.
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
43
|
-
@logger
|
|
44
|
-
@logger
|
|
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
|
|
60
|
-
new_results.
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
23
|
+
return if running?
|
|
18
24
|
|
|
19
25
|
logger = Coverband.configuration.logger
|
|
20
26
|
@semaphore.synchronize do
|
|
21
|
-
|
|
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
|
|
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
|
-
|
|
14
|
+
Background.start
|
|
14
15
|
else
|
|
15
|
-
|
|
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
|
-
|
|
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
|