coverband 1.1 → 1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cc5c300ba7c487716b6a46f455757351caaaafea
4
- data.tar.gz: 6a8509f0b16f807930bcb15a409f273bbdcdd241
3
+ metadata.gz: f8438c95988521edfcb5911828e1b75b4a83d625
4
+ data.tar.gz: 8a92bc7880429005fb1ba837b273e4968bda8a7d
5
5
  SHA512:
6
- metadata.gz: fb3eeb08c1b3e00de3e9c19ceed3e3d3275c9e43b8e9b38bce8725788b00a951923bec225349eb8df1178810de31ccd914fe5663821f095f7fdcbc2c2692edc7
7
- data.tar.gz: b7794879639fb64bab184ceca7763c716db6384cb36d814db091b6a2e2d77375fb51f47f2df6321b68f27c4f513318c509a5fd974a0d2ac54a2a5f06b8031cd2
6
+ metadata.gz: 78922ea8ccdad5cc0b71ff9096d5e2928777b3ba58156bb3a637c46fe5fd43faa011d9f575e193c380209eebdf1920cd29cb3c958167f1d262dd830b92f2d782
7
+ data.tar.gz: b75a40fac79fb36c18bed97bb4f6f8e345ad94c21204d003ddc5cc03f25846ea6265b86b026e07bbe655b4ee999799e89fc265c4e143070128215dfde2c9c8c4
data/README.md CHANGED
@@ -2,21 +2,22 @@
2
2
 
3
3
  [![Build Status](https://travis-ci.org/danmayer/coverband.svg?branch=master)](https://travis-ci.org/danmayer/coverband)
4
4
 
5
- A gem to measure production code coverage. Coverband allows easy configuration to collect and report on production code coverage. It can be used as Rack middleware, wrapping a block with sampling, or manually configured to meet any need (like coverage on background jobs).
5
+ A gem to measure production code usage, showing each line of code that is executed. Coverband allows easy configuration to collect and report on production code usage. It can be used as Rack middleware, wrapping a block with sampling, or manually configured to meet any need (like usage during background jobs). I like to think of this as production code coverage, but that implies test coverage to some folks, so being more explicit to say that it shows when a line of code is executed in a given environment is the most accurate way to describe it.
6
6
 
7
7
  * Allow sampling to avoid the performance overhead on every request.
8
8
  * Ignore directories to avoid overhead data collection on vendor, lib, etc.
9
- * Take a baseline to get initial app loading coverage.
10
-
11
- coverband 1.1+ supports ruby 2.0+. For ruby 1.9 use coverband 1.0.X.
9
+ * Take a baseline to get initial app execution during app initialization. (the baseline is important because some code is executed during app load, but might not be invoked during any requests, think prefetching, initial cache builds, setting constants, etc...)
10
+ * Development mode for additional code usage details (number of LOC execution during single request, etc).
11
+ * Coverband is not intended for test code coverage, for that just check out [SimpleCov](https://github.com/colszowka/simplecov).
12
12
 
13
+ __Notes:__ Latest versions of Coverband drop support for anything less than Ruby 2.0, and Ruby 2.1+ is recommended.
13
14
 
14
15
  ###### Success:
15
16
  After running in production for 30 minutes, we were able very easily delete 2000 LOC after looking through the data. We expect to be able to clean up much more after it has collected more data.
16
17
 
17
18
  ###### Performance Impact
18
19
 
19
- Because coverband 2.0+ uses TracePoint which is much speedier than the ruby 1.9 set_trace_func, the performance impact is reasonable and there is no need for the c-ext [coverband_ext](https://github.com/danmayer/coverband_ext). If you are stuck on ruby 1.9 and coverband 1.0.X, the [coverband_ext](https://github.com/danmayer/coverband_ext) is a must.
20
+ The performance impact on Ruby 2.1+ is fairly small and no longer requires a C-extension. Look at the benchmark rake task for specific details.
20
21
 
21
22
  ## Installation
22
23
 
@@ -50,13 +51,7 @@ Details on a example Sinatra app
50
51
 
51
52
  ## Notes
52
53
 
53
- * Coverband has been running in production on Ruby 1.9.3, 2.x, 2.1.x on Sinatra, Rails 2.3.x, Rails 3.0.x, Rails 3.1.x, and Rails 3.2.x
54
- * No 1.8.7 support, Coverband requires Ruby 1.9.3+
55
- * There is a performance impact which is why the gem supports sampling. On low traffic sites I am running a sample rate of 20% and on very high traffic sites I am sampling at 1%, which still gives useful data
56
- * The impact with the pure Ruby coverband can't be rather significant on sampled requests
57
- * Most of the overhead is in the Ruby coverage collector, you can now use [coverband_ext](https://github.com/danmayer/coverband_ext) to run a C extension collector which is MUCH faster.
58
- * Using Redis 2.x gem, while supported, is slow and not recommended. It will have a larger impact on overhead performance. Although the Ruby collection dwarfs the redis time, so it likely doesn't matter much.
59
- * Make sure to ignore any folders like `vendor` and possibly `lib` as it can help reduce performance overhead. Or ignore specific frequently hit in app files for better perf.
54
+ * Coverband has been used on large scale production websites without large impacts on performance. Adjusting the samplerate to achieve an acceptable trade-off on detailed information vs performance impact.
60
55
 
61
56
  ## Configuration
62
57
 
@@ -66,16 +61,12 @@ You need to configure cover band you can either do that passing in all configura
66
61
 
67
62
  ```ruby
68
63
  #config/coverband.rb
69
- require 'json'
70
-
71
- baseline = Coverband.parse_baseline
72
-
73
64
  Coverband.configure do |config|
74
65
  config.root = Dir.pwd
75
66
  if defined? Redis
76
67
  config.redis = Redis.new(:host => 'redis.host.com', :port => 49182, :db => 1)
77
68
  end
78
- config.coverage_baseline = baseline
69
+ config.coverage_baseline = Coverband.parse_baseline
79
70
  config.root_paths = ['/app/'] # /app/ is needed for heroku deployments
80
71
  # regex paths can help if you are seeing files duplicated for each capistrano deployment release
81
72
  #config.root_paths = ['/server/apps/my_app/releases/\d+/']
@@ -83,6 +74,7 @@ Coverband.configure do |config|
83
74
  # Since rails and other frameworks lazy load code. I have found it is bad to allow
84
75
  # initial requests to record with coverband. This ignores first 15 requests
85
76
  config.startup_delay = Rails.env.production? ? 15 : 2
77
+ # Percentage of requests recorded
86
78
  config.percentage = Rails.env.production? ? 30.0 : 100.0
87
79
 
88
80
  config.logger = Rails.logger
@@ -101,7 +93,7 @@ end
101
93
 
102
94
  ### 2. Configuring Rake
103
95
 
104
- Either add the below to your `Rakefile` or to a file included in your Rakefile such as `lib/tasks/coverband` if you want to break it up that way.
96
+ Either add the below to your `Rakefile` or to a file included in your Rakefile such as `lib/tasks/coverband.rake` if you want to break it up that way.
105
97
 
106
98
  ```ruby
107
99
  require 'coverband'
@@ -117,7 +109,7 @@ rake coverband:clear # reset coverband coverage data
117
109
  rake coverband:coverage # report runtime coverband code coverage
118
110
  ```
119
111
 
120
- The default Coverband baseline task will try to detect your app as either Rack or Rails environment. It will load the app to take a baseline reading. If the baseline task doesn't load your app well you can override the default baseline to create a better baseline yourself. Below for example is how I take a baseline on a pretty simple Sinatra app.
112
+ The default Coverband baseline task will try to detect your app as either Rack or Rails environment. It will load the app to take a baseline reading. The baseline coverage is important because some code is executed during app load, but might not be invoked during any requests, think prefetching, initial cache builds, setting constants, etc. If the baseline task doesn't load your app well you can override the default baseline to create a better baseline yourself. Below for example is how I take a baseline on a pretty simple Sinatra app.
121
113
 
122
114
  ```ruby
123
115
  namespace :coverband do
@@ -155,10 +147,10 @@ run ActionController::Dispatcher.new
155
147
 
156
148
  #### For Rails apps
157
149
 
158
- Create an initializer file
150
+ Create an initializers file
159
151
 
160
152
  ```ruby
161
- # config/initializes/coverband_middleware.rb
153
+ # config/initializers/coverband_middleware.rb
162
154
 
163
155
  # Configure the Coverband Middleware
164
156
  require 'coverband'
@@ -182,7 +174,7 @@ module MyApplication
182
174
  end
183
175
  ```
184
176
 
185
- Note: To debug in development mode, I recommend turning verbose logging on `config.verbose = true` and passing in the Rails.logger `config.logger = Rails.logger` to the Coverband config. This makes it easy to follow in development mode. Be careful to not leave these on in production as they will effect performance.
177
+ Note: To debug in development mode, I recommend turning verbose logging on `config.verbose = true` and passing in the Rails.logger `config.logger = Rails.logger` to the Coverband config. This makes it easy to follow in development mode. Be careful to not leave these on in production as they will affect performance.
186
178
 
187
179
  ## Usage
188
180
 
@@ -190,11 +182,12 @@ Note: To debug in development mode, I recommend turning verbose logging on `conf
190
182
  2. Hit your development server exercising the endpoints you want to verify Coverband is recording (you should see debug outputs in your server console)
191
183
  3. Run `rake coverband:coverage` again, previously it should have only shown the baseline data of your app initializing. After using it in development it should show increased coverage from the actions you have exercised.
192
184
 
193
- Note: if you use `rails s` and data aren't reccorded, make sure it is using your `config.ru`.
185
+ Note: if you use `rails s` and data aren't recorded, make sure it is using your `config.ru`.
194
186
 
195
187
  ## Example apps
196
188
 
197
189
  - [Rails app](https://github.com/arnlen/rails-coverband-example-app)
190
+ - [Rails app with Coverband 1.1](https://github.com/danmayer/covered_rails)
198
191
  - [Sinatra app](https://github.com/danmayer/churn-site)
199
192
  - [Non rack ruby app](https://github.com/danmayer/coverband_examples)
200
193
 
@@ -295,10 +288,12 @@ data = JSON.parse(File.read("blah.json"))
295
288
  Coverband::Reporter.report :additional_scov_data => [data]
296
289
  ```
297
290
 
291
+ You can also pass a `:additional_scov_data => [data]` option to `Coverband::Reporter.get_current_scov_data` to write out merged data.
292
+
298
293
  ### Known issues
299
294
 
300
- * If you don't have a baseline recorded your coverage can look odd like you are missing a bunch of data. It would be good if coverband gave a more actionable warning in this situation.
301
- * If you have simplecov filters, you need to clear them prior to generating your coverage report. As the filters will be applied to coverband as well and can often filter out everything we are recording.
295
+ * If you don't have a baseline recorded your coverage can look odd like you are missing a bunch of data. It would be good if Coverband gave a more actionable warning in this situation.
296
+ * If you have SimpleCov filters, you need to clear them prior to generating your coverage report. As the filters will be applied to Coverband as well and can often filter out everything we are recording.
302
297
  * the line numbers reported for `ERB` files are often off and aren't considered useful. I recommend filtering out .erb using the `config.ignore` option.
303
298
 
304
299
  ## TODO
@@ -311,7 +306,9 @@ Coverband::Reporter.report :additional_scov_data => [data]
311
306
  * blank rails app
312
307
  * blank Sinatra app
313
308
  * report on Coverband files that haven't recorded any coverage (find things like events and crons that aren't recording, or dead files)
314
- * ability to change the coverband config at runtime by changing the config pushed to the Redis hash. In memory cache around the changes to only make that call periodically.
309
+ * ability to change the Coverband config at runtime by changing the config pushed to the Redis hash. In memory cache around the changes to only make that call periodically.
310
+ * Opposed to just showing code usage on a route allow 'tagging' events which would record line coverage for that tag (this would allow tagging all code that modified an ActiveRecord model for example
311
+ * mountable rack app to view coverage similar to flipper-ui
315
312
 
316
313
  ## Contributing
317
314
 
@@ -319,11 +316,12 @@ Coverband::Reporter.report :additional_scov_data => [data]
319
316
  2. Create your feature branch (`git checkout -b my-new-feature`)
320
317
  3. Commit your changes (`git commit -am 'Add some feature'`)
321
318
  4. Push to the branch (`git push origin my-new-feature`)
322
- 5. Create new Pull Request
319
+ 5. Make sure all tests are passing (run `bundle install`, make sure Redis is running, and then execute `bundle exec rake test`)
320
+ 6. Create new Pull Request
323
321
 
324
322
  ## Resources
325
323
 
326
- These notes of kind of for myself, but if anyone is seriously interested in contributing to the project, these resorces might be helpfu. I learned a lot looking at various existing projects and open source code.
324
+ These notes of kind of for myself, but if anyone is seriously interested in contributing to the project, these resources might be helpful. I learned a lot looking at various existing projects and open source code.
327
325
 
328
326
  ##### Ruby Std-lib Coverage
329
327
 
@@ -338,8 +336,8 @@ These notes of kind of for myself, but if anyone is seriously interested in cont
338
336
  * [erb syntax](http://stackoverflow.com/questions/7996695/rails-erb-syntax) parse out and mark lines as important
339
337
  * [ruby 2 tracer](https://github.com/brightbox/deb-ruby2.0/blob/master/lib/tracer.rb)
340
338
  * [coveralls hosted code coverage tracking](https://coveralls.io/docs/ruby) currently for test coverage but might be a good partner for production coverage
341
- * [simplecov walk through](http://www.tamingthemindmonkey.com/2011/09/27/ruby-code-coverage-using-simplecov) copy some of the syntax sugar setup for cover band
342
- * [Jruby coverage bug](http://jira.codehaus.org/browse/JRUBY-6106?page=com.atlassian.jira.plugin.system.issuetabpanels:changehistory-tabpanel)
339
+ * [simplecov usage example](http://www.cakesolutions.net/teamblogs/brief-introduction-to-rspec-and-simplecov-for-ruby) copy some of the syntax sugar setup for cover band
340
+ * [Jruby coverage bug](https://github.com/jruby/jruby/issues/1196)
343
341
  * [learn from oboe ruby code](https://github.com/appneta/oboe-ruby#writing-custom-instrumentation)
344
342
  * [learn from stackprof](https://github.com/tmm1/stackprof#readme)
345
343
  * I believe there are possible ways to get even better data using the new [Ruby2 TracePoint API](http://www.ruby-doc.org/core/TracePoint.html)
data/coverband.gemspec CHANGED
@@ -22,8 +22,11 @@ Gem::Specification.new do |spec|
22
22
  spec.add_development_dependency "rake"
23
23
  spec.add_development_dependency "mocha", "~> 0.14.0"
24
24
  spec.add_development_dependency "rack"
25
+ spec.add_development_dependency "rack-test"
25
26
  spec.add_development_dependency "test-unit"
26
27
  spec.add_runtime_dependency "simplecov"
27
28
  spec.add_runtime_dependency "json"
28
29
  spec.add_runtime_dependency "redis"
30
+ spec.add_runtime_dependency 'aws-sdk', '~> 2'
31
+ spec.add_runtime_dependency 'sinatra'
29
32
  end
data/lib/coverband.rb CHANGED
@@ -1,17 +1,20 @@
1
1
  require 'redis'
2
2
  require 'logger'
3
+ require 'aws-sdk'
3
4
 
4
5
  require 'coverband/version'
5
6
  require 'coverband/configuration'
6
7
  require 'coverband/redis_store'
8
+ require 'coverband/memory_cache_store'
7
9
  require 'coverband/base'
8
10
  require 'coverband/reporter'
9
11
  require 'coverband/middleware'
12
+ require 'coverband/s3_report_writer'
10
13
 
11
14
  module Coverband
12
15
 
13
16
  CONFIG_FILE = './config/coverband.rb'
14
-
17
+
15
18
  class << self
16
19
  attr_accessor :configuration_data
17
20
  end
@@ -41,5 +44,5 @@ module Coverband
41
44
  def self.configuration
42
45
  self.configuration_data ||= Configuration.new
43
46
  end
44
-
47
+
45
48
  end
@@ -45,7 +45,10 @@ module Coverband
45
45
  @ignore_patterns = Coverband.configuration.ignore + ["internal:prelude"]
46
46
  @ignore_patterns += ['gems'] unless Coverband.configuration.include_gems
47
47
  @sample_percentage = Coverband.configuration.percentage
48
- @reporter = Coverband::RedisStore.new(Coverband.configuration.redis) if Coverband.configuration.redis
48
+ if Coverband.configuration.redis
49
+ @reporter = Coverband::RedisStore.new(Coverband.configuration.redis)
50
+ @reporter = Coverband::MemoryCacheStore.new(@reporter) if Coverband.configuration.memory_caching
51
+ end
49
52
  @stats = Coverband.configuration.stats
50
53
  @verbose = Coverband.configuration.verbose
51
54
  @logger = Coverband.configuration.logger
@@ -87,8 +90,10 @@ module Coverband
87
90
 
88
91
  @files.reject!{|file, lines| !track_file?(file) }
89
92
 
93
+ #make lines uniq
94
+ @files.each{|file, lines| lines.uniq!}
95
+
90
96
  if @verbose
91
- @file_usage.reject!{|file, line_count| !track_file?(file) }
92
97
  @logger.info "coverband file usage: #{@file_usage.sort_by {|_key, value| value}.inspect}"
93
98
  if @verbose=="debug"
94
99
  output_file_line_usage
@@ -162,7 +167,7 @@ module Coverband
162
167
  @file_line_usage[file][line] += 1
163
168
  end
164
169
  file_lines = (@files[file] ||= [])
165
- file_lines << line
170
+ file_lines.push(line) unless file_lines.include?(line)
166
171
  end
167
172
  end
168
173
  end
@@ -1,6 +1,10 @@
1
1
  module Coverband
2
2
  class Configuration
3
- attr_accessor :redis, :coverage_baseline, :root_paths, :root, :ignore, :percentage, :verbose, :reporter, :stats, :logger, :startup_delay, :baseline_file, :trace_point_events, :include_gems
3
+
4
+ attr_accessor :redis, :coverage_baseline, :root_paths, :root,
5
+ :ignore, :percentage, :verbose, :reporter, :stats,
6
+ :logger, :startup_delay, :baseline_file, :trace_point_events,
7
+ :include_gems, :memory_caching, :s3_bucket
4
8
 
5
9
  def initialize
6
10
  @root = Dir.pwd
@@ -17,6 +21,7 @@ module Coverband
17
21
  @logger = Logger.new(STDOUT)
18
22
  @startup_delay = 0
19
23
  @trace_point_events = [:line]
24
+ @memory_caching = false
20
25
  end
21
26
 
22
27
  def logger
@@ -0,0 +1,42 @@
1
+ module Coverband
2
+ class MemoryCacheStore
3
+
4
+ attr_accessor :store
5
+
6
+
7
+ def self.files_cache
8
+ @files_cache ||= Hash.new
9
+ end
10
+
11
+ def self.reset!
12
+ files_cache.clear
13
+ end
14
+
15
+ def initialize(store)
16
+ @store = store
17
+ end
18
+
19
+ def store_report files
20
+ filtered_files = filter(files)
21
+ store.store_report(filtered_files) if filtered_files.any?
22
+ end
23
+
24
+ private
25
+
26
+ def files_cache
27
+ self.class.files_cache
28
+ end
29
+
30
+ def filter(files)
31
+ files.each_with_object(Hash.new) do |(file, lines), filtered_file_hash|
32
+ #first time we see a file, we pre-init the in memory cache to whatever is in store(redis)
33
+ line_cache = files_cache[file] ||= Set.new(store.covered_lines_for_file(file))
34
+ lines.reject! do |line|
35
+ line_cache.include?(line) ? true : (line_cache << line and false)
36
+ end
37
+ filtered_file_hash[file] = lines if lines.any?
38
+ end
39
+ end
40
+
41
+ end
42
+ end
@@ -15,6 +15,12 @@ module Coverband
15
15
  end
16
16
  end
17
17
 
18
+
19
+ def covered_lines_for_file(file)
20
+ @redis.smembers("coverband.#{file}").map(&:to_i)
21
+ end
22
+
23
+
18
24
  def sadd_supports_array?
19
25
  @_sadd_supports_array
20
26
  end
@@ -7,7 +7,7 @@ module Coverband
7
7
  yield
8
8
  @project_directory = File.expand_path(Coverband.configuration.root)
9
9
  results = Coverage.result
10
- results = results.reject{|key, val| !key.match(@project_directory) || Coverband.configuration.ignore.any?{|pattern| key.match(/#{pattern}/)} }
10
+ results = results.reject { |key, val| !key.match(@project_directory) || Coverband.configuration.ignore.any? { |pattern| key.match(/#{pattern}/) } }
11
11
 
12
12
  if Coverband.configuration.verbose
13
13
  Coverband.configuration.logger.info results.inspect
@@ -15,7 +15,7 @@ module Coverband
15
15
 
16
16
  config_dir = File.dirname(Coverband.configuration.baseline_file)
17
17
  Dir.mkdir config_dir unless File.exists?(config_dir)
18
- File.open(Coverband.configuration.baseline_file, 'w') {|f| f.write(results.to_json) }
18
+ File.open(Coverband.configuration.baseline_file, 'w') { |f| f.write(results.to_json) }
19
19
  end
20
20
 
21
21
  def self.report(options = {})
@@ -28,27 +28,27 @@ module Coverband
28
28
  redis = Coverband.configuration.redis
29
29
  roots = get_roots
30
30
  existing_coverage = Coverband.configuration.coverage_baseline
31
- open_report = options.fetch(:open_report){ true }
31
+ open_report = options.fetch(:open_report) { true }
32
32
 
33
33
  if Coverband.configuration.verbose
34
34
  Coverband.configuration.logger.info "fixing root: #{roots.join(', ')}"
35
35
  end
36
36
 
37
- if Coverband.configuration.reporter=='scov'
38
- additional_scov_data = options.fetch(:additional_scov_data){ [] }
37
+ if Coverband.configuration.reporter == 'scov'
38
+ additional_scov_data = options.fetch(:additional_scov_data) { [] }
39
39
  if Coverband.configuration.verbose
40
40
  print additional_scov_data
41
41
  end
42
42
  report_scov(redis, existing_coverage, additional_scov_data, roots, open_report)
43
43
  else
44
- lines = redis.smembers('coverband').map{|key| report_line(redis, key) }
44
+ lines = redis.smembers('coverband').map{ |key| report_line(redis, key) }
45
45
  Coverband.configuration.logger.info lines.join("\n")
46
46
  end
47
47
  end
48
48
 
49
49
  def self.clear_coverage(redis = nil)
50
50
  redis ||= Coverband.configuration.redis
51
- redis.smembers('coverband').each{|key| redis.del("coverband.#{key}")}
51
+ redis.smembers('coverband').each { |key| redis.del("coverband.#{key}") }
52
52
  redis.del("coverband")
53
53
  end
54
54
 
@@ -66,7 +66,7 @@ module Coverband
66
66
 
67
67
  def self.fix_file_names(report_hash, roots)
68
68
  fixed_report = {} #normalize names across servers
69
- report_hash.each_pair do |key, values|
69
+ report_hash.each_pair do |key, values|
70
70
  filename = filename_from_key(key, roots)
71
71
  fixed_report[filename] = values
72
72
  end
@@ -79,47 +79,54 @@ module Coverband
79
79
  # [0,0,1,0,1]
80
80
  def self.merge_arrays(first, second)
81
81
  merged = []
82
- longest = if first.length > second.length
83
- first
84
- else
85
- second
82
+ longest = first.length > second.length ? first : second
83
+
84
+ longest.each_with_index do |line, index|
85
+ if first[index] || second[index]
86
+ merged[index] = (first[index].to_i + second[index].to_i >= 1 ? 1 : 0)
87
+ else
88
+ merged[index] = nil
89
+ end
86
90
  end
87
- longest.each_with_index do |line, index|
88
- if first[index] || second[index]
89
- merged[index] = (first[index].to_i + second[index].to_i >= 1 ? 1 : 0)
90
- else
91
- merged[index] = nil
92
- end
93
- end
94
- merged
91
+
92
+ merged
95
93
  end
96
94
 
97
-
98
- def self.get_current_scov_data
99
- get_current_scov_data_imp(Coverband.configuration.redis, get_roots)
95
+
96
+ def self.get_current_scov_data(options = {})
97
+ additional_scov_data = options.fetch(:additional_scov_data) { [] }
98
+
99
+ if (additional_scov_data)
100
+ report_scov_with_additional_data(Coverband.configuration.redis, Coverband.configuration.coverage_baseline, additional_scov_data, get_roots)
101
+ else
102
+ get_current_scov_data_imp(Coverband.configuration.redis, get_roots)
103
+ end
100
104
  end
101
105
 
102
106
  def self.get_current_scov_data_imp(redis, roots)
103
107
  scov_style_report = {}
108
+
104
109
  redis.smembers('coverband').each do |key|
105
- next if Coverband.configuration.ignore.any?{ |i| key.match(i)}
106
- line_data = line_hash(redis, key, roots)
107
-
108
- if line_data
109
- line_key = line_data.keys.first
110
- previous_line_hash = scov_style_report[line_key]
111
- if previous_line_hash
112
-
113
- line_data[line_key] = merge_arrays(line_data[line_key], previous_line_hash)
114
- end
115
- scov_style_report.merge!(line_data)
116
- end
117
- end
110
+ next if Coverband.configuration.ignore.any?{ |i| key.match(i) }
111
+ line_data = line_hash(redis, key, roots)
112
+
113
+ if line_data
114
+ line_key = line_data.keys.first
115
+ previous_line_hash = scov_style_report[line_key]
116
+
117
+ if previous_line_hash
118
+ line_data[line_key] = merge_arrays(line_data[line_key], previous_line_hash)
119
+ end
120
+
121
+ scov_style_report.merge!(line_data)
122
+ end
123
+ end
124
+
118
125
  scov_style_report = fix_file_names(scov_style_report, roots)
119
126
  scov_style_report
120
127
  end
121
-
122
- def self.report_scov(redis, existing_coverage, additional_scov_data, roots, open_report)
128
+
129
+ def self.report_scov_with_additional_data(redis, existing_coverage, additional_scov_data, roots)
123
130
  scov_style_report = get_current_scov_data_imp redis, roots
124
131
  existing_coverage = fix_file_names(existing_coverage, roots)
125
132
  scov_style_report = merge_existing_coverage(scov_style_report, existing_coverage)
@@ -127,25 +134,33 @@ module Coverband
127
134
  additional_scov_data.each do |data|
128
135
  scov_style_report = merge_existing_coverage(scov_style_report, data)
129
136
  end
130
-
137
+
138
+ scov_style_report
139
+ end
140
+
141
+ def self.report_scov(redis, existing_coverage, additional_scov_data, roots, open_report)
142
+ scov_style_report = report_scov_with_additional_data(redis, existing_coverage, additional_scov_data, roots)
143
+
131
144
  if Coverband.configuration.verbose
132
145
  Coverband.configuration.logger.info "report: "
133
146
  Coverband.configuration.logger.info scov_style_report.inspect
134
147
  end
135
-
148
+
136
149
  SimpleCov::Result.new(scov_style_report).format!
137
150
  if open_report
138
151
  `open #{SimpleCov.coverage_dir}/index.html`
139
152
  else
140
153
  Coverband.configuration.logger.info "report is ready and viewable: open #{SimpleCov.coverage_dir}/index.html"
141
154
  end
155
+ S3ReportWriter.new(Coverband.configuration.s3_bucket).persist! if Coverband.configuration.s3_bucket
142
156
  end
143
157
 
158
+
144
159
  def self.merge_existing_coverage(scov_style_report, existing_coverage)
145
160
  existing_coverage.each_pair do |key, lines|
146
161
  if current_lines = scov_style_report[key]
147
162
  lines.each_with_index do |line, index|
148
- if line.nil? && current_lines[index].to_i==0
163
+ if line.nil? && current_lines[index].to_i == 0
149
164
  current_lines[index] = nil
150
165
  else
151
166
  current_lines[index] = current_lines[index] ? (current_lines[index].to_i + line.to_i) : nil
@@ -169,7 +184,7 @@ module Coverband
169
184
  def self.line_members(redis, key)
170
185
  redis.smembers("coverband.#{key}").inspect
171
186
  end
172
-
187
+
173
188
  def self.filename_from_key(key, roots)
174
189
  filename = key
175
190
  roots.each do |root|
@@ -188,13 +203,13 @@ module Coverband
188
203
  filename = filename_from_key(key, roots)
189
204
  if File.exists?(filename)
190
205
  lines_hit = redis.smembers("coverband.#{key}")
191
- count = File.foreach(filename).inject(0) {|c, line| c+1}
206
+ count = File.foreach(filename).inject(0) { |c, line| c + 1 }
192
207
  if filename.match(/\.erb/)
193
208
  line_array = Array.new(count, nil)
194
209
  else
195
210
  line_array = Array.new(count, 0)
196
211
  end
197
- line_array.each_with_index{|line,index| line_array[index]=1 if lines_hit.include?((index+1).to_s) }
212
+ line_array.each_with_index{|line,index| line_array[index] = 1 if lines_hit.include?((index + 1).to_s) }
198
213
  {filename => line_array}
199
214
  else
200
215
  Coverband.configuration.logger.info "file #{filename} not found in project"
@@ -0,0 +1,30 @@
1
+ class S3ReportWriter
2
+
3
+ def initialize(bucket_name)
4
+ @bucket_name = bucket_name
5
+ end
6
+
7
+ def persist!
8
+ object.put(body: coverage_content)
9
+ end
10
+
11
+ private
12
+
13
+ def coverage_content
14
+ File.read("#{SimpleCov.coverage_dir}/index.html").gsub("./assets/#{Gem::Specification.find_by_name('simplecov-html').version.version}/", '')
15
+ end
16
+
17
+ def object
18
+ bucket.object('coverband/index.html')
19
+ end
20
+
21
+ def s3
22
+ Aws::S3::Resource.new
23
+ end
24
+
25
+ def bucket
26
+ s3.bucket(@bucket_name)
27
+ end
28
+
29
+
30
+ end
@@ -0,0 +1,21 @@
1
+ require 'sinatra/base'
2
+
3
+ module Coverband
4
+
5
+ class S3Web < Sinatra::Base
6
+
7
+ set :public_folder, proc { File.expand_path('public', Gem::Specification.find_by_name('simplecov-html').full_gem_path) }
8
+
9
+ get '/' do
10
+ s3.get_object(bucket: Coverband.configuration.s3_bucket, key:'coverband/index.html').body.read
11
+ end
12
+
13
+ private
14
+
15
+ def s3
16
+ @s3 ||= Aws::S3::Client.new
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -1,3 +1,3 @@
1
1
  module Coverband
2
- VERSION = "1.1"
2
+ VERSION = "1.2"
3
3
  end
@@ -1,5 +1,6 @@
1
1
  require 'coverband'
2
2
  require 'redis'
3
+ require File.join(File.dirname(__FILE__), 'dog')
3
4
 
4
5
  namespace :benchmarks do
5
6
 
@@ -21,12 +22,14 @@ namespace :benchmarks do
21
22
  require 'classifier-reborn'
22
23
 
23
24
  Coverband.configure do |config|
24
- config.redis = Redis.new
25
- config.root = Dir.pwd
26
- config.startup_delay = 0
27
- config.percentage = 100.0
28
- config.logger = $stdout
29
- config.verbose = false
25
+ config.redis = Redis.new
26
+ config.root = Dir.pwd
27
+ config.startup_delay = 0
28
+ config.percentage = 100.0
29
+ config.logger = $stdout
30
+ config.verbose = false
31
+ #config.memory_caching = true
32
+ #config.trace_point_events = [:call]
30
33
  end
31
34
 
32
35
  end
@@ -57,6 +60,9 @@ namespace :benchmarks do
57
60
  bayes_classification
58
61
  lsi_classification
59
62
  end
63
+
64
+ #simulate many calls to the same line
65
+ 10_000.times { Dog.new.bark }
60
66
  end
61
67
 
62
68
 
@@ -0,0 +1,7 @@
1
+ class Dog
2
+
3
+ def bark
4
+ "bark"
5
+ end
6
+
7
+ end
@@ -3,6 +3,19 @@ require File.expand_path('./dog', File.dirname(__FILE__))
3
3
 
4
4
  class BaseTest < Test::Unit::TestCase
5
5
 
6
+ test 'defaults to a redis store' do
7
+ coverband = Coverband::Base.instance.reset_instance
8
+ assert_equal Coverband::RedisStore, coverband.instance_variable_get('@reporter').class
9
+ end
10
+
11
+
12
+ test 'configure memory caching' do
13
+ Coverband.configuration.memory_caching = true
14
+ coverband = Coverband::Base.instance.reset_instance
15
+ assert_equal Coverband::MemoryCacheStore, coverband.instance_variable_get('@reporter').class
16
+ Coverband.configuration.memory_caching = false
17
+ end
18
+
6
19
  test "start should enable coverage" do
7
20
  coverband = Coverband::Base.instance.reset_instance
8
21
  assert_equal false, coverband.instance_variable_get("@enabled")
@@ -65,7 +78,17 @@ class BaseTest < Test::Unit::TestCase
65
78
  store.expects(:store_report).once.with(has_entries(dog_file => [3]) )
66
79
  assert_equal false, coverband.instance_variable_get("@enabled")
67
80
  coverband.start
68
- Dog.new.bark
81
+ 5.times { Dog.new.bark }
82
+ coverband.stop
83
+ coverband.save
84
+ end
85
+
86
+ test "tracer should collect uniq line numbers" do
87
+ dog_file = File.expand_path('./dog.rb', File.dirname(__FILE__))
88
+ coverband = Coverband::Base.instance.reset_instance
89
+ coverband.start
90
+ 100.times { Dog.new.bark }
91
+ assert_equal 1, coverband.instance_variable_get("@files")[dog_file].select{ |i| 3 == i }.count
69
92
  coverband.stop
70
93
  coverband.save
71
94
  end
@@ -0,0 +1,67 @@
1
+ require File.expand_path('../test_helper', File.dirname(__FILE__))
2
+
3
+ module Coverband
4
+ class MemoryCacheStoreTest < Test::Unit::TestCase
5
+
6
+ def setup
7
+ MemoryCacheStore.reset!
8
+ @store = mock('store')
9
+ @memory_store = MemoryCacheStore.new(@store)
10
+ end
11
+
12
+
13
+ test 'it passes data into store' do
14
+ data = {
15
+ 'file1' => [ 3, 5 ],
16
+ 'file2' => [1, 2]
17
+ }
18
+ @store.expects(:store_report).with data
19
+ @store.expects(:covered_lines_for_file).with('file1').returns []
20
+ @store.expects(:covered_lines_for_file).with('file2').returns []
21
+ @memory_store.store_report data
22
+ end
23
+
24
+ test 'it passes data into store only once' do
25
+ data = {
26
+ 'file1' => [ 3, 5 ],
27
+ 'file2' => [1, 2]
28
+ }
29
+ @store.expects(:store_report).once.with data
30
+ @store.expects(:covered_lines_for_file).with('file1').returns []
31
+ @store.expects(:covered_lines_for_file).with('file2').returns []
32
+ 2.times { @memory_store.store_report data }
33
+ end
34
+
35
+ test 'it only passes files and lines we have not hit yet' do
36
+ first_data = {
37
+ 'file1' => [ 3, 5 ],
38
+ 'file2' => [1, 2]
39
+ }
40
+ second_data = {
41
+ 'file1' => [ 3, 5, 10 ],
42
+ 'file2' => [1, 2]
43
+ }
44
+ @store.expects(:covered_lines_for_file).with('file1').returns []
45
+ @store.expects(:covered_lines_for_file).with('file2').returns []
46
+ @store.expects(:store_report).once.with first_data
47
+ @store.expects(:store_report).once.with(
48
+ 'file1' => [10]
49
+ )
50
+ @memory_store.store_report first_data
51
+ @memory_store.store_report second_data
52
+ end
53
+
54
+ test 'it initializes cache with what is in store' do
55
+ data = {
56
+ 'file1' => [ 3, 5 ],
57
+ 'file2' => [1, 2]
58
+ }
59
+ @store.expects(:covered_lines_for_file).with('file1').returns [3,5]
60
+ @store.expects(:covered_lines_for_file).with('file2').returns [2]
61
+ @store.expects(:store_report).with( 'file2' => [1] )
62
+ @memory_store.store_report data
63
+ end
64
+
65
+ end
66
+
67
+ end
@@ -2,6 +2,22 @@ require File.expand_path('../test_helper', File.dirname(__FILE__))
2
2
 
3
3
  class RedisTest < Test::Unit::TestCase
4
4
 
5
+ def setup
6
+ @redis = Redis.new
7
+ @redis.flushdb
8
+ @store = Coverband::RedisStore.new(@redis)
9
+ end
10
+
11
+ def test_covered_lines_for_file
12
+ @redis.sadd('coverband.dog.rb', 1)
13
+ @redis.sadd('coverband.dog.rb', 2)
14
+ assert_equal @store.covered_lines_for_file('dog.rb').sort, [1, 2]
15
+ end
16
+
17
+ def test_covered_lines_when_null
18
+ assert_equal @store.covered_lines_for_file('dog.rb'), []
19
+ end
20
+
5
21
  private
6
22
 
7
23
  def test_data
@@ -0,0 +1,21 @@
1
+ require File.expand_path('../test_helper', File.dirname(__FILE__))
2
+
3
+ module Coverband
4
+
5
+ class S3ReportWriterTest < Test::Unit::TestCase
6
+
7
+ test 'it writes the coverage report to s3' do
8
+ s3 = mock('s3_resource')
9
+ bucket = mock('bucket')
10
+ object = mock('object')
11
+ s3.expects(:bucket).with('coverage-bucket').returns(bucket)
12
+ bucket.expects(:object).with('coverband/index.html').returns(object)
13
+ File.expects(:read).with("#{SimpleCov.coverage_dir}/index.html").returns("content ./assets/#{Gem::Specification.find_by_name('simplecov-html').version.version}/")
14
+ object.expects(:put).with(body: 'content ')
15
+ Aws::S3::Resource.expects(:new).returns(s3)
16
+ S3ReportWriter.new('coverage-bucket').persist!
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,26 @@
1
+ require File.expand_path('../test_helper', File.dirname(__FILE__))
2
+ require File.expand_path('../../lib/coverband/s3_web', File.dirname(__FILE__))
3
+ require 'rack/test'
4
+
5
+ ENV['RACK_ENV'] = 'test'
6
+
7
+ module Coverband
8
+ class S3WebTest < Test::Unit::TestCase
9
+
10
+ include Rack::Test::Methods
11
+
12
+ def app
13
+ Coverband::S3Web
14
+ end
15
+
16
+ test 'renders content from the coverband/index.html object' do
17
+ Coverband.configuration.s3_bucket = 'coverage-bucket'
18
+ s3 = mock('s3')
19
+ Aws::S3::Client.expects(:new).returns(s3)
20
+ s3.expects(:get_object).with(bucket: 'coverage-bucket', key: 'coverband/index.html').returns mock('response', body: mock('body', read: 'content'))
21
+ get '/'
22
+ assert last_response.ok?
23
+ assert_equal 'content', last_response.body
24
+ end
25
+ end
26
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: coverband
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.1'
4
+ version: '1.2'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Mayer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-24 00:00:00.000000000 Z
11
+ date: 2016-11-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rack-test
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: test-unit
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -122,6 +136,34 @@ dependencies:
122
136
  - - ">="
123
137
  - !ruby/object:Gem::Version
124
138
  version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: aws-sdk
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '2'
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '2'
153
+ - !ruby/object:Gem::Dependency
154
+ name: sinatra
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :runtime
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
125
167
  description: Rack middleware to help measure production code coverage
126
168
  email:
127
169
  - dan@mayerdan.com
@@ -142,21 +184,28 @@ files:
142
184
  - lib/coverband.rb
143
185
  - lib/coverband/base.rb
144
186
  - lib/coverband/configuration.rb
187
+ - lib/coverband/memory_cache_store.rb
145
188
  - lib/coverband/middleware.rb
146
189
  - lib/coverband/redis_store.rb
147
190
  - lib/coverband/reporter.rb
191
+ - lib/coverband/s3_report_writer.rb
192
+ - lib/coverband/s3_web.rb
148
193
  - lib/coverband/tasks.rb
149
194
  - lib/coverband/version.rb
150
195
  - test/benchmarks/.gitignore
151
196
  - test/benchmarks/benchmark.rake
197
+ - test/benchmarks/dog.rb
152
198
  - test/fake_app/basic_rack.rb
153
199
  - test/test_helper.rb
154
200
  - test/unit/base_test.rb
155
201
  - test/unit/configuration_test.rb
156
202
  - test/unit/dog.rb
203
+ - test/unit/memory_cache_store_test.rb
157
204
  - test/unit/middleware_test.rb
158
205
  - test/unit/redis_store_test.rb
159
206
  - test/unit/reporter_test.rb
207
+ - test/unit/s3_report_writer_test.rb
208
+ - test/unit/s3_web_test.rb
160
209
  homepage: ''
161
210
  licenses:
162
211
  - MIT
@@ -177,18 +226,22 @@ required_rubygems_version: !ruby/object:Gem::Requirement
177
226
  version: '0'
178
227
  requirements: []
179
228
  rubyforge_project:
180
- rubygems_version: 2.2.5
229
+ rubygems_version: 2.5.1
181
230
  signing_key:
182
231
  specification_version: 4
183
232
  summary: Rack middleware to help measure production code coverage
184
233
  test_files:
185
234
  - test/benchmarks/.gitignore
186
235
  - test/benchmarks/benchmark.rake
236
+ - test/benchmarks/dog.rb
187
237
  - test/fake_app/basic_rack.rb
188
238
  - test/test_helper.rb
189
239
  - test/unit/base_test.rb
190
240
  - test/unit/configuration_test.rb
191
241
  - test/unit/dog.rb
242
+ - test/unit/memory_cache_store_test.rb
192
243
  - test/unit/middleware_test.rb
193
244
  - test/unit/redis_store_test.rb
194
245
  - test/unit/reporter_test.rb
246
+ - test/unit/s3_report_writer_test.rb
247
+ - test/unit/s3_web_test.rb