coverband 0.0.9 → 0.0.10
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +63 -2
- data/Rakefile +8 -0
- data/coverband.gemspec +3 -0
- data/lib/coverband.rb +25 -0
- data/lib/coverband/middleware.rb +19 -12
- data/lib/coverband/reporter.rb +20 -12
- data/lib/coverband/version.rb +1 -1
- data/test/test_helper.rb +34 -0
- data/test/unit/middleware_test.rb +25 -0
- data/test/unit/reporter_test.rb +63 -0
- metadata +59 -5
data/README.md
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
# Coverband
|
2
2
|
|
3
|
-
Rack middleware to
|
3
|
+
Rack middleware to measure production code coverage. Coverband allows easy configuration to collect and report on production code coverage.
|
4
|
+
|
5
|
+
* Allow sampleing to avoid the perf overhead on every request.
|
6
|
+
* Ignore directories to avoid data collection on vendor, lib, etc
|
7
|
+
* Take a baseline to get inital app loading coverage.
|
8
|
+
|
9
|
+
At the momement, Coverband relies on Ruby's `set_trace_func` hook. I attempted to use the standard lib's `Coverage` support but it proved buggy when stampling or stoping and starting collection. When [Coverage is patched](https://www.ruby-forum.com/topic/1811306) in future Ruby versions it would likely be better. Using `set_trace_func` has some limitations where it doesn't collect covered lines, but I have been impressed with the coverage it shows for both Sinatra and Rails applications.
|
4
10
|
|
5
11
|
## Installation
|
6
12
|
|
@@ -18,7 +24,61 @@ Or install it yourself as:
|
|
18
24
|
|
19
25
|
## Usage
|
20
26
|
|
21
|
-
|
27
|
+
After installing the gem you likely want to get the rake tasks configured as well as the rack middle ware.
|
28
|
+
|
29
|
+
#### Configuring Rake
|
30
|
+
|
31
|
+
Either add the below to your `Rakefile` or to a file included in you Rakefile
|
32
|
+
|
33
|
+
require 'coverband'
|
34
|
+
Coverband.configure do |config|
|
35
|
+
config.redis = Redis.new
|
36
|
+
# merge in lines to consider covered manually to override any misses
|
37
|
+
# existing_coverage = {'./cover_band_server/app.rb' => Array.new(31,1)}
|
38
|
+
# JSON.parse(File.read('./tmp/coverband_baseline.json')).merge(existing_coverage)
|
39
|
+
config.coverage_baseline = JSON.parse(File.read('./tmp/coverband_baseline.json'))
|
40
|
+
config.root_paths = ['/app/']
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "report unused lines"
|
44
|
+
task :coverband => :environment do
|
45
|
+
baseline = JSON.parse(File.read('./tmp/coverband_baseline.json'))
|
46
|
+
|
47
|
+
root_paths = ['/app/']
|
48
|
+
coverband_options = {:existing_coverage => baseline, :roots => root_paths}
|
49
|
+
Coverband::Reporter.report(Redis.new, coverband_options)
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "get coverage baseline"
|
53
|
+
task :coverband_baseline do
|
54
|
+
Coverband::Reporter.baseline {
|
55
|
+
#rails
|
56
|
+
require File.expand_path("../config/environment", __FILE__)
|
57
|
+
#sinatra
|
58
|
+
#require File.expand_path("./app", __FILE__)
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
#### Configure rack middleware
|
63
|
+
|
64
|
+
For the best coverage you want this loaded as early as possible. I have been putting it directly in my `config.ru` but you could use an initializer you may just end up missing some boot up coverage.
|
65
|
+
|
66
|
+
require File.dirname(__FILE__) + '/config/environment'
|
67
|
+
|
68
|
+
require 'coverband'
|
69
|
+
|
70
|
+
Coverband.configure do |config|
|
71
|
+
config.root = Dir.pwd
|
72
|
+
config.redis = Redis.new
|
73
|
+
config.coverage_baseline = JSON.parse(File.read('./tmp/coverband_baseline.json'))
|
74
|
+
config.root_paths = ['/app/']
|
75
|
+
config.ignore = ['vendor']
|
76
|
+
config.percentage = 100.0
|
77
|
+
end
|
78
|
+
|
79
|
+
use Coverband::Middleware
|
80
|
+
run ActionController::Dispatcher.new
|
81
|
+
|
22
82
|
|
23
83
|
## TODO
|
24
84
|
|
@@ -43,6 +103,7 @@ TODO: Write usage instructions here
|
|
43
103
|
* [Ruby Coverage docs](http://www.ruby-doc.org/stdlib-1.9.3/libdoc/coverage/rdoc/Coverage.html)
|
44
104
|
* [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
|
45
105
|
* [Jruby coverage bug](http://jira.codehaus.org/browse/JRUBY-6106?page=com.atlassian.jira.plugin.system.issuetabpanels:changehistory-tabpanel)
|
106
|
+
* [learn from oboe ruby code](https://github.com/appneta/oboe-ruby#writing-custom-instrumentation)
|
46
107
|
|
47
108
|
## Contributing
|
48
109
|
|
data/Rakefile
CHANGED
data/coverband.gemspec
CHANGED
@@ -20,6 +20,9 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_development_dependency "bundler", "~> 1.3"
|
22
22
|
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "mocha"
|
24
|
+
spec.add_development_dependency "shoulda"
|
23
25
|
spec.add_runtime_dependency "simplecov"
|
24
26
|
spec.add_runtime_dependency "json"
|
27
|
+
spec.add_runtime_dependency "redis"
|
25
28
|
end
|
data/lib/coverband.rb
CHANGED
@@ -5,5 +5,30 @@ require "coverband/reporter"
|
|
5
5
|
require "coverband/middleware"
|
6
6
|
|
7
7
|
module Coverband
|
8
|
+
|
9
|
+
class << self
|
10
|
+
attr_accessor :configuration
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.configure
|
14
|
+
self.configuration ||= Configuration.new
|
15
|
+
yield(configuration)
|
16
|
+
end
|
17
|
+
|
18
|
+
class Configuration
|
19
|
+
attr_accessor :redis, :coverage_baseline, :root_paths, :root, :ignore, :percentage, :verbose, :reporter
|
20
|
+
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@root = Dir.pwd
|
24
|
+
@redis = nil
|
25
|
+
@coverage_baseline = {}
|
26
|
+
@root_paths = []
|
27
|
+
@ignore = []
|
28
|
+
@percentage = 100.0
|
29
|
+
@verbose = false
|
30
|
+
@reporter = 'rcov'
|
31
|
+
end
|
32
|
+
end
|
8
33
|
|
9
34
|
end
|
data/lib/coverband/middleware.rb
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
module Coverband
|
2
2
|
class Middleware
|
3
3
|
|
4
|
-
def initialize(app
|
4
|
+
def initialize(app)
|
5
5
|
@app = app
|
6
|
-
root =
|
6
|
+
root = Coverband.configuration.root
|
7
7
|
@project_directory = File.expand_path(root)
|
8
8
|
@enabled = true
|
9
9
|
@function_set = false
|
10
10
|
@files = {}
|
11
11
|
|
12
|
-
@ignore_patterns =
|
13
|
-
@sample_percentage =
|
14
|
-
@reporter =
|
12
|
+
@ignore_patterns = Coverband.configuration.ignore
|
13
|
+
@sample_percentage = Coverband.configuration.percentage
|
14
|
+
@reporter = Coverband.configuration.redis
|
15
|
+
@verbose = Coverband.configuration.verbose
|
15
16
|
end
|
16
17
|
|
17
18
|
def call(env)
|
@@ -68,20 +69,26 @@ module Coverband
|
|
68
69
|
old_files = @files.dup
|
69
70
|
@files = {}
|
70
71
|
old_files.each_pair do |key, values|
|
71
|
-
@reporter.sadd "coverband", key
|
72
|
-
@reporter.
|
72
|
+
@reporter.sadd "coverband", key
|
73
|
+
if @reporter.inspect.match(/v2/)
|
74
|
+
values.each do |value|
|
75
|
+
@reporter.sadd "coverband.#{key}", value
|
76
|
+
end
|
77
|
+
else
|
78
|
+
@reporter.sadd "coverband.#{key}", values
|
79
|
+
end
|
73
80
|
end
|
74
81
|
end
|
75
82
|
else
|
76
|
-
puts "coverage report: "
|
77
|
-
puts @files.inspect
|
83
|
+
puts "coverage report: " if @verbose
|
84
|
+
puts @files.inspect if @verbose
|
78
85
|
end
|
79
86
|
else
|
80
|
-
puts "coverage disabled" if @
|
87
|
+
puts "coverage disabled" if @verbose
|
81
88
|
end
|
82
89
|
rescue RuntimeError => err
|
83
|
-
if @
|
84
|
-
puts "coverage missing"
|
90
|
+
if @verbose
|
91
|
+
puts "coverage missing"
|
85
92
|
puts "error: #{err.inspect} #{err.message}"
|
86
93
|
end
|
87
94
|
end
|
data/lib/coverband/reporter.rb
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
require 'simplecov'
|
2
2
|
|
3
3
|
module Coverband
|
4
|
-
|
5
4
|
class Reporter
|
6
5
|
|
7
6
|
def self.baseline
|
8
7
|
require 'coverage'
|
9
8
|
Coverage.start
|
10
9
|
yield
|
11
|
-
@project_directory = File.expand_path(
|
10
|
+
@project_directory = File.expand_path(Coverband.configuration.root)
|
12
11
|
results = Coverage.result
|
13
12
|
results = results.reject{|key, val| !key.match(@project_directory)}
|
14
13
|
puts results.inspect
|
@@ -16,18 +15,28 @@ module Coverband
|
|
16
15
|
File.open('./tmp/coverband_baseline.json', 'w') {|f| f.write(results.to_json) }
|
17
16
|
end
|
18
17
|
|
19
|
-
def self.report
|
20
|
-
|
21
|
-
|
22
|
-
|
18
|
+
def self.report
|
19
|
+
redis = Coverband.configuration.redis
|
20
|
+
roots = Coverband.configuration.root_paths
|
21
|
+
existing_coverage = Coverband.configuration.coverage_baseline
|
22
|
+
roots << "#{current_root}/"
|
23
23
|
puts "fixing root: #{roots.join(', ')}"
|
24
|
-
if
|
24
|
+
if Coverband.configuration.reporter=='rcov'
|
25
25
|
report_rcov(redis, existing_coverage, roots)
|
26
26
|
else
|
27
|
-
redis.smembers('coverband').
|
27
|
+
lines = redis.smembers('coverband').map{|key| report_line(redis, key) }
|
28
|
+
puts lines.join("\n")
|
28
29
|
end
|
29
30
|
end
|
30
31
|
|
32
|
+
def self.clear_coverage(redis)
|
33
|
+
Coverband.configuration.redis.smembers('coverband').each{|key| redis.del("coverband.#{key}")}
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.current_root
|
37
|
+
File.expand_path(Coverband.configuration.root)
|
38
|
+
end
|
39
|
+
|
31
40
|
private
|
32
41
|
|
33
42
|
def self.fix_file_names(report_hash, roots)
|
@@ -73,11 +82,11 @@ module Coverband
|
|
73
82
|
# /Users/danmayer/projects/cover_band_server/app/rb: ["54", "55"]
|
74
83
|
# /Users/danmayer/projects/cover_band_server/views/layout/erb: ["0", "33", "36", "37", "38", "39", "40", "62", "63", "66", "65532", "65533"]
|
75
84
|
def self.report_line(redis, key)
|
76
|
-
|
85
|
+
"#{key}: #{redis.smembers("coverband.#{key}").inspect}"
|
77
86
|
end
|
78
87
|
|
79
88
|
def self.filename_from_key(key, roots)
|
80
|
-
filename = key
|
89
|
+
filename = key
|
81
90
|
roots.each do |root|
|
82
91
|
filename = filename.gsub(/^#{root}/, './')
|
83
92
|
end
|
@@ -90,7 +99,7 @@ module Coverband
|
|
90
99
|
def self.line_hash(redis, key, roots)
|
91
100
|
filename = filename_from_key(key, roots)
|
92
101
|
if File.exists?(filename)
|
93
|
-
lines_hit = redis.smembers("coverband
|
102
|
+
lines_hit = redis.smembers("coverband.#{key}")
|
94
103
|
count = File.foreach(filename).inject(0) {|c, line| c+1}
|
95
104
|
if filename.match(/\.erb/)
|
96
105
|
line_array = Array.new(count, nil)
|
@@ -105,5 +114,4 @@ module Coverband
|
|
105
114
|
end
|
106
115
|
|
107
116
|
end
|
108
|
-
|
109
117
|
end
|
data/lib/coverband/version.rb
CHANGED
data/test/test_helper.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'simplecov'
|
3
|
+
require 'test/unit'
|
4
|
+
require 'shoulda'
|
5
|
+
require 'mocha'
|
6
|
+
require 'ostruct'
|
7
|
+
require 'json'
|
8
|
+
|
9
|
+
SimpleCov.start do
|
10
|
+
add_filter 'specs/ruby/1.9.1/gems/'
|
11
|
+
add_filter '/test/'
|
12
|
+
add_filter '/config/'
|
13
|
+
end
|
14
|
+
|
15
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
16
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
17
|
+
Mocha::Configuration.prevent(:stubbing_non_existent_method)
|
18
|
+
|
19
|
+
require 'coverband'
|
20
|
+
|
21
|
+
unless File.exist?('./tmp/coverband_baseline.json')
|
22
|
+
`mkdir -p ./tmp`
|
23
|
+
`touch ./tmp/coverband_baseline.json`
|
24
|
+
end
|
25
|
+
|
26
|
+
Coverband.configure do |config|
|
27
|
+
config.root = Dir.pwd
|
28
|
+
config.redis = Redis.new
|
29
|
+
#config.coverage_baseline = JSON.parse(File.read('./tmp/coverband_baseline.json'))
|
30
|
+
config.root_paths = ['/app/']
|
31
|
+
config.ignore = ['vendor']
|
32
|
+
config.percentage = 100.0
|
33
|
+
config.reporter = 'std_out'
|
34
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require File.expand_path('../test_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
class MiddlewareTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
FAKE_RESULTS = 'results'
|
6
|
+
|
7
|
+
should "call app" do
|
8
|
+
middleware = Coverband::Middleware.new(fake_app)
|
9
|
+
results = middleware.call({})
|
10
|
+
assert_equal FAKE_RESULTS, results
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def fake_app
|
16
|
+
@app ||= begin
|
17
|
+
my_app = OpenStruct.new()
|
18
|
+
def my_app.call(env)
|
19
|
+
FAKE_RESULTS
|
20
|
+
end
|
21
|
+
my_app
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require File.expand_path('../test_helper', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
class ReporterTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
should "record baseline" do
|
6
|
+
Coverage.expects(:start).at_least_once
|
7
|
+
Coverage.expects(:result).returns({'fake' => [0,1]}).at_least_once
|
8
|
+
File.expects(:open).once
|
9
|
+
|
10
|
+
Coverband::Reporter.stubs(:puts)
|
11
|
+
|
12
|
+
Coverband::Reporter.baseline{
|
13
|
+
#nothing
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
should "report data" do
|
18
|
+
Coverband.configure do |config|
|
19
|
+
config.redis = fake_redis
|
20
|
+
config.reporter = 'std_out'
|
21
|
+
end
|
22
|
+
|
23
|
+
Coverband::Reporter.expects(:current_root).returns('/root_dir')
|
24
|
+
fake_redis.expects(:smembers).with('coverband').returns(fake_coverband_members)
|
25
|
+
Coverband::Reporter.expects('puts').with("fixing root: /app/, /root_dir/")
|
26
|
+
|
27
|
+
fake_coverband_members.each do |key|
|
28
|
+
fake_redis.expects(:smembers).with("coverband.#{key}").returns(["54", "55"])
|
29
|
+
end
|
30
|
+
|
31
|
+
matchers = [regexp_matches(/tester/),
|
32
|
+
regexp_matches(/application_controller/),
|
33
|
+
regexp_matches(/account/),
|
34
|
+
regexp_matches(/54/)]
|
35
|
+
|
36
|
+
Coverband::Reporter.expects('puts').with(all_of(*matchers))
|
37
|
+
|
38
|
+
Coverband::Reporter.report
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def fake_redis
|
44
|
+
@redis ||= begin
|
45
|
+
redis = OpenStruct.new()
|
46
|
+
def redis.smembers(key)
|
47
|
+
end
|
48
|
+
redis
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def fake_coverband_members
|
53
|
+
["/Users/danmayer/projects/hearno/script/tester.rb",
|
54
|
+
"/Users/danmayer/projects/hearno/app/controllers/application_controller.rb",
|
55
|
+
"/Users/danmayer/projects/hearno/app/models/account.rb"
|
56
|
+
]
|
57
|
+
end
|
58
|
+
|
59
|
+
def fake_coverage_report
|
60
|
+
{"/Users/danmayer/projects/hearno/script/tester.rb"=>[1, nil, 1, 1, nil, nil, nil]}
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: coverband
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.10
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-12-09 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -43,6 +43,38 @@ dependencies:
|
|
43
43
|
- - ! '>='
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: mocha
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: shoulda
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
46
78
|
- !ruby/object:Gem::Dependency
|
47
79
|
name: simplecov
|
48
80
|
requirement: !ruby/object:Gem::Requirement
|
@@ -75,6 +107,22 @@ dependencies:
|
|
75
107
|
- - ! '>='
|
76
108
|
- !ruby/object:Gem::Version
|
77
109
|
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: redis
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
78
126
|
description: Rack middleware to help measure production code coverage
|
79
127
|
email:
|
80
128
|
- dan@mayerdan.com
|
@@ -93,6 +141,9 @@ files:
|
|
93
141
|
- lib/coverband/middleware.rb
|
94
142
|
- lib/coverband/reporter.rb
|
95
143
|
- lib/coverband/version.rb
|
144
|
+
- test/test_helper.rb
|
145
|
+
- test/unit/middleware_test.rb
|
146
|
+
- test/unit/reporter_test.rb
|
96
147
|
homepage: ''
|
97
148
|
licenses:
|
98
149
|
- MIT
|
@@ -108,7 +159,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
108
159
|
version: '0'
|
109
160
|
segments:
|
110
161
|
- 0
|
111
|
-
hash:
|
162
|
+
hash: 1293128324123628513
|
112
163
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
164
|
none: false
|
114
165
|
requirements:
|
@@ -117,11 +168,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
117
168
|
version: '0'
|
118
169
|
segments:
|
119
170
|
- 0
|
120
|
-
hash:
|
171
|
+
hash: 1293128324123628513
|
121
172
|
requirements: []
|
122
173
|
rubyforge_project:
|
123
174
|
rubygems_version: 1.8.25
|
124
175
|
signing_key:
|
125
176
|
specification_version: 3
|
126
177
|
summary: Rack middleware to help measure production code coverage
|
127
|
-
test_files:
|
178
|
+
test_files:
|
179
|
+
- test/test_helper.rb
|
180
|
+
- test/unit/middleware_test.rb
|
181
|
+
- test/unit/reporter_test.rb
|