coverband 1.1 → 1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +28 -30
- data/coverband.gemspec +3 -0
- data/lib/coverband.rb +5 -2
- data/lib/coverband/base.rb +8 -3
- data/lib/coverband/configuration.rb +6 -1
- data/lib/coverband/memory_cache_store.rb +42 -0
- data/lib/coverband/redis_store.rb +6 -0
- data/lib/coverband/reporter.rb +59 -44
- data/lib/coverband/s3_report_writer.rb +30 -0
- data/lib/coverband/s3_web.rb +21 -0
- data/lib/coverband/version.rb +1 -1
- data/test/benchmarks/benchmark.rake +12 -6
- data/test/benchmarks/dog.rb +7 -0
- data/test/unit/base_test.rb +24 -1
- data/test/unit/memory_cache_store_test.rb +67 -0
- data/test/unit/redis_store_test.rb +16 -0
- data/test/unit/s3_report_writer_test.rb +21 -0
- data/test/unit/s3_web_test.rb +26 -0
- metadata +56 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f8438c95988521edfcb5911828e1b75b4a83d625
|
4
|
+
data.tar.gz: 8a92bc7880429005fb1ba837b273e4968bda8a7d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
10
|
-
|
11
|
-
|
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
|
-
|
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
|
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 =
|
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
|
150
|
+
Create an initializers file
|
159
151
|
|
160
152
|
```ruby
|
161
|
-
# config/
|
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
|
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
|
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
|
301
|
-
* If you have
|
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
|
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.
|
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
|
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
|
342
|
-
* [Jruby coverage bug](
|
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
|
data/lib/coverband/base.rb
CHANGED
@@ -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
|
-
|
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
|
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
|
-
|
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
|
data/lib/coverband/reporter.rb
CHANGED
@@ -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
|
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 =
|
83
|
-
|
84
|
-
|
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
|
-
|
88
|
-
|
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
|
-
|
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
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
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.
|
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
|
data/lib/coverband/version.rb
CHANGED
@@ -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
|
25
|
-
config.root
|
26
|
-
config.startup_delay
|
27
|
-
config.percentage
|
28
|
-
config.logger
|
29
|
-
config.verbose
|
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
|
|
data/test/unit/base_test.rb
CHANGED
@@ -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.
|
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-
|
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.
|
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
|