coverband 1.5.4 → 2.0.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.travis.yml +3 -0
  4. data/Gemfile +2 -1
  5. data/Rakefile +5 -3
  6. data/coverband.gemspec +27 -23
  7. data/lib/coverband.rb +10 -14
  8. data/lib/coverband/adapters/file_store.rb +6 -7
  9. data/lib/coverband/adapters/memory_cache_store.rb +8 -5
  10. data/lib/coverband/adapters/redis_store.rb +10 -50
  11. data/lib/coverband/baseline.rb +5 -5
  12. data/lib/coverband/collectors/base.rb +140 -0
  13. data/lib/coverband/collectors/coverage.rb +148 -0
  14. data/lib/coverband/collectors/trace.rb +100 -0
  15. data/lib/coverband/configuration.rb +8 -10
  16. data/lib/coverband/middleware.rb +5 -5
  17. data/lib/coverband/reporters/base.rb +26 -31
  18. data/lib/coverband/reporters/console_report.rb +2 -3
  19. data/lib/coverband/reporters/simple_cov_report.rb +5 -6
  20. data/lib/coverband/s3_report_writer.rb +7 -8
  21. data/lib/coverband/s3_web.rb +3 -5
  22. data/lib/coverband/tasks.rb +23 -26
  23. data/lib/coverband/version.rb +3 -1
  24. data/test/benchmarks/benchmark.rake +38 -32
  25. data/test/benchmarks/dog.rb +3 -3
  26. data/test/fake_app/basic_rack.rb +4 -2
  27. data/test/test_helper.rb +17 -11
  28. data/test/unit/adapters_file_store_test.rb +12 -11
  29. data/test/unit/adapters_memory_cache_store_test.rb +3 -4
  30. data/test/unit/adapters_redis_store_test.rb +42 -118
  31. data/test/unit/baseline_test.rb +17 -20
  32. data/test/unit/collectors_base_test.rb +96 -0
  33. data/test/unit/collectors_coverage_test.rb +137 -0
  34. data/test/unit/collectors_trace_test.rb +96 -0
  35. data/test/unit/configuration_test.rb +8 -8
  36. data/test/unit/dog.rb +3 -1
  37. data/test/unit/middleware_test.rb +70 -61
  38. data/test/unit/reports_base_test.rb +62 -62
  39. data/test/unit/reports_console_test.rb +18 -21
  40. data/test/unit/reports_simple_cov_test.rb +23 -26
  41. data/test/unit/s3_report_writer_test.rb +6 -8
  42. data/test/unit/s3_web_test.rb +2 -1
  43. metadata +45 -25
  44. data/lib/coverband/base.rb +0 -210
  45. 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, :logger, :startup_delay, :trace_point_events,
7
- :include_gems, :memory_caching, :s3_bucket, :coverage_file, :store,
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
@@ -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 |line, index|
50
- if first[index] || second[index]
51
- merged[index] = (first[index].to_i + second[index].to_i)
52
- else
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
- scov_style_report[file_key] = merge_arrays(current_line_hits, existing_lines)
67
- else
68
- scov_style_report[file_key] = existing_lines
69
- end
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.exists?(filename)
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
- if line_data
122
- line_key = line_data.keys.first
123
- previous_line_hash = scov_style_report[line_key]
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
- scov_style_report.merge!(line_data)
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
-