coverband 1.5.4 → 2.0.0.alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.travis.yml +3 -0
- data/Gemfile +2 -1
- data/Rakefile +5 -3
- data/coverband.gemspec +27 -23
- data/lib/coverband.rb +10 -14
- data/lib/coverband/adapters/file_store.rb +6 -7
- data/lib/coverband/adapters/memory_cache_store.rb +8 -5
- data/lib/coverband/adapters/redis_store.rb +10 -50
- data/lib/coverband/baseline.rb +5 -5
- data/lib/coverband/collectors/base.rb +140 -0
- data/lib/coverband/collectors/coverage.rb +148 -0
- data/lib/coverband/collectors/trace.rb +100 -0
- data/lib/coverband/configuration.rb +8 -10
- data/lib/coverband/middleware.rb +5 -5
- data/lib/coverband/reporters/base.rb +26 -31
- data/lib/coverband/reporters/console_report.rb +2 -3
- data/lib/coverband/reporters/simple_cov_report.rb +5 -6
- data/lib/coverband/s3_report_writer.rb +7 -8
- data/lib/coverband/s3_web.rb +3 -5
- data/lib/coverband/tasks.rb +23 -26
- data/lib/coverband/version.rb +3 -1
- data/test/benchmarks/benchmark.rake +38 -32
- data/test/benchmarks/dog.rb +3 -3
- data/test/fake_app/basic_rack.rb +4 -2
- data/test/test_helper.rb +17 -11
- data/test/unit/adapters_file_store_test.rb +12 -11
- data/test/unit/adapters_memory_cache_store_test.rb +3 -4
- data/test/unit/adapters_redis_store_test.rb +42 -118
- data/test/unit/baseline_test.rb +17 -20
- data/test/unit/collectors_base_test.rb +96 -0
- data/test/unit/collectors_coverage_test.rb +137 -0
- data/test/unit/collectors_trace_test.rb +96 -0
- data/test/unit/configuration_test.rb +8 -8
- data/test/unit/dog.rb +3 -1
- data/test/unit/middleware_test.rb +70 -61
- data/test/unit/reports_base_test.rb +62 -62
- data/test/unit/reports_console_test.rb +18 -21
- data/test/unit/reports_simple_cov_test.rb +23 -26
- data/test/unit/s3_report_writer_test.rb +6 -8
- data/test/unit/s3_web_test.rb +2 -1
- metadata +45 -25
- data/lib/coverband/base.rb +0 -210
- data/test/unit/base_test.rb +0 -100
@@ -0,0 +1,148 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coverband
|
4
|
+
module Collectors
|
5
|
+
class Coverage < Base
|
6
|
+
def record_coverage
|
7
|
+
# noop
|
8
|
+
end
|
9
|
+
|
10
|
+
def report_coverage
|
11
|
+
unless @enabled
|
12
|
+
@logger.info 'coverage disabled' if @verbose
|
13
|
+
return
|
14
|
+
end
|
15
|
+
|
16
|
+
if failed_recently?
|
17
|
+
@logger.info 'coverage reporting standing-by because of recent failure' if @verbose
|
18
|
+
return
|
19
|
+
end
|
20
|
+
|
21
|
+
new_results = nil
|
22
|
+
@semaphore.synchronize { new_results = new_coverage(::Coverage.peek_result.dup) }
|
23
|
+
new_results.each_pair do |file, line_counts|
|
24
|
+
next if @ignored_files.include?(file)
|
25
|
+
next unless track_file?(file)
|
26
|
+
add_file(file, line_counts)
|
27
|
+
end
|
28
|
+
|
29
|
+
if @verbose
|
30
|
+
@logger.info "coverband file usage: #{file_usage.inspect}"
|
31
|
+
output_file_line_usage if @verbose == 'debug'
|
32
|
+
end
|
33
|
+
|
34
|
+
if @store
|
35
|
+
if @stats
|
36
|
+
@before_time = Time.now
|
37
|
+
@stats.count 'coverband.files.recorded_files', @file_line_usage.length
|
38
|
+
end
|
39
|
+
|
40
|
+
@store.save_report(@file_line_usage)
|
41
|
+
if @stats
|
42
|
+
@time_spent = Time.now - @before_time
|
43
|
+
@stats.timing 'coverband.files.recorded_time', @time_spent
|
44
|
+
end
|
45
|
+
@file_line_usage.clear
|
46
|
+
elsif @verbose
|
47
|
+
@logger.info 'coverage report: '
|
48
|
+
@logger.info @file_line_usage.inspect
|
49
|
+
end
|
50
|
+
rescue RuntimeError => err
|
51
|
+
failed!
|
52
|
+
if @verbose
|
53
|
+
@logger.info 'coverage missing'
|
54
|
+
@logger.info "error: #{err.inspect} #{err.message}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
protected
|
59
|
+
|
60
|
+
def set_tracer
|
61
|
+
# no op
|
62
|
+
end
|
63
|
+
|
64
|
+
def unset_tracer
|
65
|
+
# no op
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def array_diff(latest, original)
|
71
|
+
latest.map.with_index { |v, i| v ? v - original[i] : nil }
|
72
|
+
end
|
73
|
+
|
74
|
+
def previous_results
|
75
|
+
@@previous_results
|
76
|
+
end
|
77
|
+
|
78
|
+
def add_previous_results(val)
|
79
|
+
@@previous_results = val
|
80
|
+
end
|
81
|
+
|
82
|
+
def new_coverage(current_coverage)
|
83
|
+
if previous_results
|
84
|
+
new_results = {}
|
85
|
+
current_coverage.each_pair do |file, line_counts|
|
86
|
+
new_results[file] = array_diff(line_counts, previous_results[file])
|
87
|
+
end
|
88
|
+
else
|
89
|
+
new_results = current_coverage
|
90
|
+
end
|
91
|
+
|
92
|
+
# if current_coverage['/Users/danmayer/projects/coverage_rails_benchmark/app/controllers/posts_controller.rb']
|
93
|
+
# puts "total"
|
94
|
+
# puts current_coverage['/Users/danmayer/projects/coverage_rails_benchmark/app/controllers/posts_controller.rb'].inspect
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
#
|
98
|
+
# if previous_results && previous_results['/Users/danmayer/projects/coverage_rails_benchmark/app/controllers/posts_controller.rb']
|
99
|
+
# puts "prev"
|
100
|
+
# puts previous_results['/Users/danmayer/projects/coverage_rails_benchmark/app/controllers/posts_controller.rb'].inspect
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# if new_results['/Users/danmayer/projects/coverage_rails_benchmark/app/controllers/posts_controller.rb']
|
104
|
+
# puts "new"
|
105
|
+
# puts new_results['/Users/danmayer/projects/coverage_rails_benchmark/app/controllers/posts_controller.rb'].inspect
|
106
|
+
# end
|
107
|
+
|
108
|
+
add_previous_results(current_coverage)
|
109
|
+
new_results.dup
|
110
|
+
end
|
111
|
+
|
112
|
+
# TODO this seems like a dumb conversion for the already good coverage format
|
113
|
+
# coverage is 0 based other implementation matches line number
|
114
|
+
def add_file(file, line_counts)
|
115
|
+
@file_line_usage[file] = Hash.new(0) unless @file_line_usage.include?(file)
|
116
|
+
line_counts.each_with_index do |line_count, index|
|
117
|
+
@file_line_usage[file][(index + 1)] = line_count if line_count
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def file_usage
|
122
|
+
hash = {}
|
123
|
+
@file_line_usage.each do |file, lines|
|
124
|
+
hash[file] = lines.values.compact.inject(0, :+)
|
125
|
+
end
|
126
|
+
hash.sort_by { |_key, value| value }
|
127
|
+
end
|
128
|
+
|
129
|
+
def initialize
|
130
|
+
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3.0')
|
131
|
+
raise NotImplementedError, 'not supported until Ruby 2.3.0 and later'
|
132
|
+
end
|
133
|
+
unless defined?(::Coverage)
|
134
|
+
# puts 'loading coverage'
|
135
|
+
require 'coverage'
|
136
|
+
end
|
137
|
+
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.5.0')
|
138
|
+
::Coverage.start unless ::Coverage.running?
|
139
|
+
else
|
140
|
+
::Coverage.start
|
141
|
+
end
|
142
|
+
@semaphore = Mutex.new
|
143
|
+
@@previous_results = nil
|
144
|
+
reset_instance
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coverband
|
4
|
+
module Collectors
|
5
|
+
class Trace < Base
|
6
|
+
|
7
|
+
def reset_instance
|
8
|
+
super
|
9
|
+
@tracer_set = false
|
10
|
+
@trace = create_trace_point
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
def report_coverage
|
15
|
+
unless @enabled
|
16
|
+
@logger.info 'coverage disabled' if @verbose
|
17
|
+
return
|
18
|
+
end
|
19
|
+
|
20
|
+
unset_tracer
|
21
|
+
|
22
|
+
if failed_recently?
|
23
|
+
@logger.info 'coverage reporting standing-by because of recent failure' if @verbose
|
24
|
+
return
|
25
|
+
end
|
26
|
+
|
27
|
+
if @verbose
|
28
|
+
@logger.info "coverband file usage: #{file_usage.inspect}"
|
29
|
+
output_file_line_usage if @verbose == 'debug'
|
30
|
+
end
|
31
|
+
|
32
|
+
if @store
|
33
|
+
if @stats
|
34
|
+
@before_time = Time.now
|
35
|
+
@stats.count 'coverband.files.recorded_files', @file_line_usage.length
|
36
|
+
end
|
37
|
+
@store.save_report(@file_line_usage)
|
38
|
+
if @stats
|
39
|
+
@time_spent = Time.now - @before_time
|
40
|
+
@stats.timing 'coverband.files.recorded_time', @time_spent
|
41
|
+
end
|
42
|
+
@file_line_usage.clear
|
43
|
+
elsif @verbose
|
44
|
+
@logger.info 'coverage report: '
|
45
|
+
@logger.info @file_line_usage.inspect
|
46
|
+
end
|
47
|
+
rescue RuntimeError => err
|
48
|
+
failed!
|
49
|
+
if @verbose
|
50
|
+
@logger.info 'coverage missing'
|
51
|
+
@logger.info "error: #{err.inspect} #{err.message}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
protected
|
56
|
+
|
57
|
+
def set_tracer
|
58
|
+
unless @tracer_set
|
59
|
+
@trace.enable
|
60
|
+
@tracer_set = true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def unset_tracer
|
65
|
+
@trace.disable
|
66
|
+
@tracer_set = false
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def add_file(file, line)
|
72
|
+
@file_line_usage[file] = Hash.new(0) unless @file_line_usage.include?(file)
|
73
|
+
@file_line_usage[file][line] += 1
|
74
|
+
end
|
75
|
+
|
76
|
+
def file_usage
|
77
|
+
hash = {}
|
78
|
+
@file_line_usage.each do |file, lines|
|
79
|
+
hash[file] = lines.values.inject(0, :+)
|
80
|
+
end
|
81
|
+
hash.sort_by { |_key, value| value }
|
82
|
+
end
|
83
|
+
|
84
|
+
def create_trace_point
|
85
|
+
TracePoint.new(*Coverband.configuration.trace_point_events) do |tp|
|
86
|
+
if Thread.current == @current_thread
|
87
|
+
file = tp.path
|
88
|
+
unless @ignored_files.include?(file)
|
89
|
+
if track_file?(file)
|
90
|
+
add_file(file, tp.lineno)
|
91
|
+
else
|
92
|
+
@ignored_files << file
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -1,15 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Coverband
|
2
4
|
class Configuration
|
3
|
-
|
4
5
|
attr_accessor :redis, :root_paths, :root,
|
5
6
|
:ignore, :additional_files, :percentage, :verbose, :reporter,
|
6
|
-
:stats, :
|
7
|
-
:include_gems, :memory_caching, :s3_bucket, :coverage_file,
|
8
|
-
:disable_on_failure_for
|
9
|
-
|
10
|
-
# deprecated, but leaving to allow old configs to 'just work'
|
11
|
-
# remove for 2.0
|
12
|
-
attr_accessor :coverage_baseline
|
7
|
+
:stats, :startup_delay, :trace_point_events,
|
8
|
+
:include_gems, :memory_caching, :s3_bucket, :coverage_file,
|
9
|
+
:collector, :disable_on_failure_for
|
10
|
+
attr_writer :logger, :store
|
13
11
|
|
14
12
|
def initialize
|
15
13
|
@root = Dir.pwd
|
@@ -22,6 +20,7 @@ module Coverband
|
|
22
20
|
@percentage = 0.0
|
23
21
|
@verbose = false
|
24
22
|
@reporter = 'scov'
|
23
|
+
@collector = 'trace'
|
25
24
|
@logger = Logger.new(STDOUT)
|
26
25
|
@startup_delay = 0
|
27
26
|
@trace_point_events = [:line]
|
@@ -35,7 +34,7 @@ module Coverband
|
|
35
34
|
@logger ||= Logger.new(STDOUT)
|
36
35
|
end
|
37
36
|
|
38
|
-
#TODO considering removing @redis / @coveragefile and have user set store directly
|
37
|
+
# TODO: considering removing @redis / @coveragefile and have user set store directly
|
39
38
|
def store
|
40
39
|
return @store if @store
|
41
40
|
if redis
|
@@ -45,6 +44,5 @@ module Coverband
|
|
45
44
|
end
|
46
45
|
@store
|
47
46
|
end
|
48
|
-
|
49
47
|
end
|
50
48
|
end
|
data/lib/coverband/middleware.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Coverband
|
2
4
|
class Middleware
|
3
|
-
|
4
5
|
def initialize(app)
|
5
6
|
@app = app
|
6
7
|
end
|
7
8
|
|
8
9
|
def call(env)
|
9
|
-
Coverband::Base.instance.configure_sampling
|
10
|
-
Coverband::Base.instance.record_coverage
|
10
|
+
Coverband::Collectors::Base.instance.configure_sampling
|
11
|
+
Coverband::Collectors::Base.instance.record_coverage
|
11
12
|
@app.call(env)
|
12
13
|
ensure
|
13
|
-
Coverband::Base.instance.report_coverage
|
14
|
+
Coverband::Collectors::Base.instance.report_coverage
|
14
15
|
end
|
15
|
-
|
16
16
|
end
|
17
17
|
end
|
@@ -1,7 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Coverband
|
2
4
|
module Reporters
|
3
5
|
class Base
|
4
|
-
|
5
6
|
def self.report(store, options = {})
|
6
7
|
roots = get_roots
|
7
8
|
additional_coverage_data = options.fetch(:additional_scov_data) { [] }
|
@@ -32,7 +33,7 @@ module Coverband
|
|
32
33
|
protected
|
33
34
|
|
34
35
|
def self.fix_file_names(report_hash, roots)
|
35
|
-
fixed_report = {} #normalize names across servers
|
36
|
+
fixed_report = {} # normalize names across servers
|
36
37
|
report_hash.each_pair do |key, values|
|
37
38
|
filename = filename_from_key(key, roots)
|
38
39
|
fixed_report[filename] = values
|
@@ -46,12 +47,10 @@ module Coverband
|
|
46
47
|
merged = []
|
47
48
|
longest = first.length > second.length ? first : second
|
48
49
|
|
49
|
-
longest.each_with_index do |
|
50
|
-
if first[index] || second[index]
|
51
|
-
|
52
|
-
|
53
|
-
merged[index] = nil
|
54
|
-
end
|
50
|
+
longest.each_with_index do |_line, index|
|
51
|
+
merged[index] = if first[index] || second[index]
|
52
|
+
(first[index].to_i + second[index].to_i)
|
53
|
+
end
|
55
54
|
end
|
56
55
|
|
57
56
|
merged
|
@@ -61,12 +60,12 @@ module Coverband
|
|
61
60
|
# expects = {"file.rb" => [0,2,4,nil,0,1,2]}
|
62
61
|
def self.merge_existing_coverage(scov_style_report, existing_coverage)
|
63
62
|
existing_coverage.each_pair do |file_key, existing_lines|
|
64
|
-
next if Coverband.configuration.ignore.any?{ |i| file_key.match(i) }
|
65
|
-
if current_line_hits = scov_style_report[file_key]
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
63
|
+
next if Coverband.configuration.ignore.any? { |i| file_key.match(i) }
|
64
|
+
scov_style_report[file_key] = if current_line_hits = scov_style_report[file_key]
|
65
|
+
merge_arrays(current_line_hits, existing_lines)
|
66
|
+
else
|
67
|
+
existing_lines
|
68
|
+
end
|
70
69
|
end
|
71
70
|
scov_style_report
|
72
71
|
end
|
@@ -87,18 +86,17 @@ module Coverband
|
|
87
86
|
# {"/Users/danmayer/projects/hearno/script/tester.rb"=>[1, nil, 1, 2, nil, nil, nil]}
|
88
87
|
def self.line_hash(store, key, roots)
|
89
88
|
filename = filename_from_key(key, roots)
|
90
|
-
if File.
|
91
|
-
|
92
|
-
count = File.foreach(filename).inject(0) { |c, line| c + 1 }
|
89
|
+
if File.exist?(filename)
|
90
|
+
count = File.foreach(filename).inject(0) { |c, _line| c + 1 }
|
93
91
|
line_array = Array.new(count, nil)
|
94
92
|
|
95
93
|
lines_hit = store.covered_lines_for_file(key)
|
96
94
|
if lines_hit.is_a?(Array)
|
97
|
-
line_array.each_with_index{|_,index| line_array[index] = 1 if lines_hit.include?((index + 1)) }
|
95
|
+
line_array.each_with_index { |_, index| line_array[index] = 1 if lines_hit.include?((index + 1)) }
|
98
96
|
else
|
99
|
-
line_array.each_with_index{|_,index| line_array[index] = (line_array[index].to_i + lines_hit[(index + 1).to_s].to_i) if lines_hit.keys.include?((index + 1).to_s) }
|
97
|
+
line_array.each_with_index { |_, index| line_array[index] = (line_array[index].to_i + lines_hit[(index + 1).to_s].to_i) if lines_hit.keys.include?((index + 1).to_s) }
|
100
98
|
end
|
101
|
-
{filename => line_array}
|
99
|
+
{ filename => line_array }
|
102
100
|
else
|
103
101
|
Coverband.configuration.logger.info "file #{filename} not found in project"
|
104
102
|
nil
|
@@ -115,19 +113,18 @@ module Coverband
|
|
115
113
|
# this logic should be pushed to base report
|
116
114
|
###
|
117
115
|
store.covered_files.each do |key|
|
118
|
-
next if Coverband.configuration.ignore.any?{ |i| key.match(i) }
|
116
|
+
next if Coverband.configuration.ignore.any? { |i| key.match(i) }
|
119
117
|
line_data = line_hash(store, key, roots)
|
120
118
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
if previous_line_hash
|
126
|
-
line_data[line_key] = merge_arrays(line_data[line_key], previous_line_hash)
|
127
|
-
end
|
119
|
+
next unless line_data
|
120
|
+
line_key = line_data.keys.first
|
121
|
+
previous_line_hash = scov_style_report[line_key]
|
128
122
|
|
129
|
-
|
123
|
+
if previous_line_hash
|
124
|
+
line_data[line_key] = merge_arrays(line_data[line_key], previous_line_hash)
|
130
125
|
end
|
126
|
+
|
127
|
+
scov_style_report.merge!(line_data)
|
131
128
|
end
|
132
129
|
|
133
130
|
scov_style_report = fix_file_names(scov_style_report, roots)
|
@@ -143,8 +140,6 @@ module Coverband
|
|
143
140
|
|
144
141
|
scov_style_report
|
145
142
|
end
|
146
|
-
|
147
143
|
end
|
148
144
|
end
|
149
145
|
end
|
150
|
-
|
@@ -1,7 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Coverband
|
2
4
|
module Reporters
|
3
5
|
class ConsoleReport < Base
|
4
|
-
|
5
6
|
def self.report(store, options = {})
|
6
7
|
scov_style_report = super(store, options)
|
7
8
|
|
@@ -10,8 +11,6 @@ module Coverband
|
|
10
11
|
end
|
11
12
|
scov_style_report
|
12
13
|
end
|
13
|
-
|
14
14
|
end
|
15
15
|
end
|
16
16
|
end
|
17
|
-
|