duvet 0.3.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +1 -1
- data/README.md +26 -9
- data/Rakefile +13 -2
- data/lib/duvet.rb +101 -43
- data/lib/duvet/cov.rb +58 -65
- data/lib/duvet/covs.rb +25 -37
- data/lib/duvet/version.rb +2 -2
- data/spec/duvet/cov_spec.rb +91 -0
- data/spec/duvet/covs_spec.rb +57 -0
- data/spec/duvet_spec.rb +81 -0
- data/{test → spec}/helper.rb +2 -8
- data/templates/css/styles.css +129 -0
- data/templates/html/file.erb +21 -22
- data/templates/html/index.erb +14 -15
- metadata +19 -44
- data/lib/duvet/core_ext.rb +0 -17
- data/test/test_core_ext.rb +0 -13
- data/test/test_cov.rb +0 -76
- data/test/test_covs.rb +0 -67
- data/test/test_duvet.rb +0 -17
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Duvet
|
2
2
|
|
3
|
-
Install with
|
4
|
-
|
3
|
+
Install with:
|
4
|
+
|
5
5
|
``` bash
|
6
6
|
$ gem install duvet
|
7
7
|
```
|
@@ -13,23 +13,40 @@ require 'duvet'
|
|
13
13
|
Duvet.start
|
14
14
|
```
|
15
15
|
|
16
|
-
|
16
|
+
Because `duvet` won't work for Ruby 1.8 you may want to rescue the error and
|
17
|
+
move on,
|
18
|
+
|
19
|
+
``` ruby
|
20
|
+
begin
|
21
|
+
require 'duvet'
|
22
|
+
Duvet.start
|
23
|
+
rescue LoadError
|
24
|
+
# ignore error
|
25
|
+
end
|
26
|
+
```
|
27
|
+
|
28
|
+
You can change the defaults by passing options to `Duvet.start`, for example:
|
17
29
|
|
18
30
|
``` ruby
|
19
31
|
Duvet.start :dir => 'coverage', :filter => 'app/lib'
|
20
32
|
```
|
21
33
|
|
22
|
-
`:dir` is the directory to write the coverage to
|
23
|
-
|
24
|
-
|
34
|
+
Where `:dir` is the directory to write the coverage to and `:filter` is a string
|
35
|
+
that a files path must match against. A regular expression can be used for more
|
36
|
+
control, but most of the time a simple string will suffice.
|
25
37
|
|
26
|
-
You can see
|
38
|
+
You can see the output of running `duvet` on itself [here][covg].
|
27
39
|
|
28
40
|
|
29
41
|
## Credits
|
30
42
|
|
31
|
-
This gem was created because I read this blog post
|
43
|
+
This gem was created because I read this [blog post][post] on the AT&T
|
44
|
+
engineering site by Aaron Patterson.
|
32
45
|
|
33
46
|
## Copyright
|
34
47
|
|
35
|
-
Copyright (c) 2010 Joshua Hawxwell. See LICENSE for details.
|
48
|
+
Copyright (c) 2010-11 Joshua Hawxwell. See LICENSE for details.
|
49
|
+
|
50
|
+
|
51
|
+
[covg]: http://hawx.github.com/duvet
|
52
|
+
[post]: http://engineering.attinteractive.com/2010/08/code-coverage-in-ruby-1-9/
|
data/Rakefile
CHANGED
@@ -1,9 +1,20 @@
|
|
1
1
|
require 'rake/testtask'
|
2
2
|
|
3
3
|
Rake::TestTask.new(:test) do |t|
|
4
|
-
t.libs << 'lib' << '
|
5
|
-
t.pattern = '
|
4
|
+
t.libs << 'lib' << 'spec'
|
5
|
+
t.pattern = 'spec/**/*_spec.rb'
|
6
6
|
t.verbose = true
|
7
7
|
end
|
8
8
|
|
9
|
+
|
10
|
+
task :build do
|
11
|
+
require 'sass'
|
12
|
+
path = File.dirname(__FILE__) + '/templates/css/'
|
13
|
+
content = Sass::Engine.new(IO.read(path + 'styles.sass')).render
|
14
|
+
|
15
|
+
File.open path + 'styles.css', 'w' do |f|
|
16
|
+
f.write content
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
9
20
|
task :default => :test
|
data/lib/duvet.rb
CHANGED
@@ -6,72 +6,130 @@
|
|
6
6
|
|
7
7
|
require 'coverage'
|
8
8
|
require 'pathname'
|
9
|
-
require '
|
9
|
+
require 'erb'
|
10
10
|
require 'sass'
|
11
11
|
|
12
|
-
require 'duvet/core_ext'
|
13
12
|
require 'duvet/covs'
|
14
13
|
require 'duvet/cov'
|
15
14
|
require 'duvet/version'
|
16
15
|
|
17
16
|
module Duvet
|
17
|
+
extend self
|
18
18
|
|
19
19
|
attr_accessor :opts
|
20
|
-
|
21
|
-
DEFAULTS = {
|
22
|
-
|
23
|
-
|
24
|
-
|
20
|
+
|
21
|
+
DEFAULTS = {
|
22
|
+
dir: 'cov',
|
23
|
+
style: 'rcov',
|
24
|
+
filter: ''
|
25
|
+
}
|
26
|
+
|
25
27
|
TEMPLATE_HASH = {
|
26
|
-
'
|
27
|
-
|
28
|
-
|
28
|
+
name: 'duvet',
|
29
|
+
time: Time.now,
|
30
|
+
version: VERSION
|
29
31
|
}
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
+
TEMPLATE_PATH = Pathname.new(__FILE__).dirname + '..' + 'templates'
|
34
|
+
|
35
|
+
# Start coverage tracking, needs to be called before any files are loaded.
|
36
|
+
#
|
37
|
+
# @param opts [Hash]
|
38
|
+
# @option opts [String, Pathname] :dir Directory to write coverage to
|
39
|
+
# @option opts [String, Regexp] :filter Pattern files must match to be written
|
40
|
+
def start(opts={})
|
33
41
|
@opts = DEFAULTS.merge(opts)
|
34
|
-
|
42
|
+
@opts[:filter] = Regexp.new(@opts[:filter])
|
43
|
+
@opts[:dir] = Pathname.new(@opts[:dir])
|
44
|
+
|
35
45
|
Coverage.start
|
36
|
-
@running = true
|
37
46
|
end
|
38
|
-
|
39
|
-
#
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
47
|
+
|
48
|
+
# Gets the results from the Coverage, filtered against the patterm given to
|
49
|
+
# #start.
|
50
|
+
#
|
51
|
+
# @return [Covs]
|
52
|
+
def result
|
53
|
+
cov = Coverage.result.find_all do |path, c|
|
54
|
+
@opts[:filter] =~ path
|
55
|
+
end
|
56
|
+
|
57
|
+
Duvet::Covs.from_data cov
|
58
|
+
end
|
59
|
+
|
60
|
+
# Creates a class with methods for rendering an ERB template.
|
61
|
+
#
|
62
|
+
# @param obj [Object] Object to create context for
|
63
|
+
# @return [#get_binding] Object which gives a binding for ERB templates
|
64
|
+
def context_for(obj)
|
65
|
+
case obj
|
66
|
+
when Array
|
67
|
+
obj.map {|i| context_for(i) }
|
68
|
+
when Hash
|
69
|
+
klass = Class.new
|
70
|
+
klass.send(:define_method, :get_binding) { binding }
|
71
|
+
|
72
|
+
obj.each do |k,v|
|
73
|
+
klass.send(:define_method, k) { Duvet.context_for(v) }
|
49
74
|
end
|
50
|
-
|
75
|
+
|
76
|
+
klass.new
|
77
|
+
else
|
78
|
+
obj
|
51
79
|
end
|
52
|
-
@result ||= Duvet::Covs.new(cov)
|
53
|
-
ensure
|
54
|
-
@running = false
|
55
80
|
end
|
56
|
-
|
57
|
-
#
|
58
|
-
|
59
|
-
|
81
|
+
|
82
|
+
# Renders a template with the data given.
|
83
|
+
#
|
84
|
+
# @param data [Hash] Data to insert into template
|
85
|
+
# @param template [String] Path to template from TEMPLATE_PATH
|
86
|
+
def format(data, template)
|
87
|
+
t = (TEMPLATE_PATH + template).read
|
88
|
+
vars = context_for TEMPLATE_HASH.merge(data)
|
89
|
+
|
90
|
+
ERB.new(t).result(vars.get_binding)
|
60
91
|
end
|
61
|
-
|
62
|
-
#
|
63
|
-
#
|
64
|
-
|
65
|
-
|
92
|
+
|
93
|
+
# Renders then writes a file based on the url in the data given.
|
94
|
+
#
|
95
|
+
# @param data [Hash] Data to insert into template
|
96
|
+
# @param template [String] Path to template from TEMPLATE_PATH
|
97
|
+
def write(data, template)
|
98
|
+
write_file format(data, template), data[:file][:url]
|
66
99
|
end
|
67
|
-
|
68
|
-
#
|
69
|
-
|
70
|
-
|
100
|
+
|
101
|
+
# Writes a file, creating directories where necessary.
|
102
|
+
#
|
103
|
+
# @param text [String] Text to write to file
|
104
|
+
# @param path [String] Path to write file to
|
105
|
+
def write_file(text, path)
|
106
|
+
write_to = @opts[:dir] + path
|
107
|
+
|
108
|
+
FileUtils.mkdir_p write_to.dirname
|
109
|
+
File.open write_to, 'w' do |f|
|
110
|
+
f.write text
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Writes all the resources to the correct directory.
|
115
|
+
def write_resources
|
116
|
+
paths = %w(css/styles.css js/main.js js/jquery.js js/plugins.js)
|
117
|
+
|
118
|
+
paths.each do |path|
|
119
|
+
path = Pathname.new(TEMPLATE_PATH + path)
|
120
|
+
write_file path.read, path.basename
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
# Call this on exit so that coverage is written.
|
125
|
+
def finish
|
126
|
+
FileUtils.mkdir_p @opts[:dir]
|
127
|
+
result.write
|
128
|
+
write_resources
|
71
129
|
end
|
72
130
|
|
73
131
|
end
|
74
132
|
|
75
133
|
at_exit do
|
76
|
-
Duvet.
|
134
|
+
Duvet.finish
|
77
135
|
end
|
data/lib/duvet/cov.rb
CHANGED
@@ -1,100 +1,93 @@
|
|
1
1
|
module Duvet
|
2
|
+
|
3
|
+
# A single file along with coverage data.
|
2
4
|
class Cov
|
3
5
|
attr_accessor :path
|
4
|
-
|
6
|
+
|
5
7
|
def initialize(path, cov)
|
6
8
|
@path = Pathname.new(path)
|
7
9
|
if @path.to_s.include?(Dir.pwd)
|
8
10
|
@path = @path.relative_path_from(Pathname.getwd)
|
9
11
|
end
|
10
|
-
|
12
|
+
|
11
13
|
@cov = cov
|
12
14
|
end
|
13
|
-
|
14
|
-
# @return [Array] lines in file
|
15
|
+
|
16
|
+
# @return [Array] Coverage data of lines in the file
|
15
17
|
def lines
|
16
18
|
@cov
|
17
19
|
end
|
18
|
-
|
19
|
-
# @return [Array] all lines which can be executed
|
20
|
+
|
21
|
+
# @return [Array] Coverage data for all the lines which can be executed
|
20
22
|
def code_lines
|
21
|
-
|
23
|
+
lines.reject {|i| i.nil?}
|
22
24
|
end
|
23
|
-
|
24
|
-
# @return [Array] all lines which have been ran
|
25
|
+
|
26
|
+
# @return [Array] Coverage data for all the lines which have been ran
|
25
27
|
def ran_lines
|
26
|
-
|
27
|
-
end
|
28
|
-
|
29
|
-
# Gives a fraction from 0 to 1 of how many lines of code have
|
30
|
-
# been executed. It ignores all lines that couldn't be executed
|
31
|
-
# such as comments.
|
32
|
-
#
|
33
|
-
# @return [Float] lines of code executed as a fraction
|
34
|
-
def code_coverage
|
35
|
-
return 0.0 if code_lines.size.zero?
|
36
|
-
ran_lines.size.to_f / code_lines.size.to_f
|
28
|
+
code_lines.reject {|i| i.zero?}
|
37
29
|
end
|
38
30
|
|
39
|
-
#
|
40
|
-
#
|
31
|
+
# Gives a real number between 0 and 1 indicating how many lines have been
|
32
|
+
# executed.
|
41
33
|
#
|
42
|
-
# @return [
|
34
|
+
# @return [Float] lines executed as a fraction
|
43
35
|
def total_coverage
|
44
36
|
return 0.0 if lines.size.zero?
|
45
37
|
ran_lines.size.to_f / lines.size.to_f
|
46
38
|
end
|
47
|
-
|
48
|
-
#
|
49
|
-
|
50
|
-
|
39
|
+
|
40
|
+
# Gives a real number between 0 and 1 indicating how many lines of code have
|
41
|
+
# been executed. It ignores all lines that can not be executed such as
|
42
|
+
# comments.
|
43
|
+
#
|
44
|
+
# @return [Float] lines of code executed as a fraction
|
45
|
+
def code_coverage
|
46
|
+
return 0.0 if code_lines.size.zero?
|
47
|
+
ran_lines.size.to_f / code_lines.size.to_f
|
51
48
|
end
|
52
|
-
|
53
|
-
# @
|
54
|
-
|
55
|
-
|
49
|
+
|
50
|
+
# @param [Float] Number to format
|
51
|
+
# @return [String] The number formatted as a percentage, ??.??%
|
52
|
+
def percent(num)
|
53
|
+
"%.2f%" % (num * 100)
|
56
54
|
end
|
57
|
-
|
58
|
-
# @return [String]
|
55
|
+
|
56
|
+
# @return [String] A simple text report of coverage
|
59
57
|
def report
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
58
|
+
<<EOS
|
59
|
+
#{@path}
|
60
|
+
total: #{percent(total_coverage)}
|
61
|
+
code: #{percent(code_coverage)}
|
62
|
+
EOS
|
64
63
|
end
|
65
|
-
|
66
|
-
# @return [Hash]
|
64
|
+
|
65
|
+
# @return [Hash] Data for templating
|
67
66
|
def data
|
68
67
|
{
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
68
|
+
file: {
|
69
|
+
path: @path.to_s,
|
70
|
+
url: @path.to_s[0..-@path.extname.size] + 'html',
|
71
|
+
root: '../' * @path.to_s.count('/'),
|
72
|
+
source: @path.readlines,
|
73
|
+
},
|
74
|
+
lines: {
|
75
|
+
total: lines.size,
|
76
|
+
code: code_lines.size,
|
77
|
+
ran: ran_lines.size
|
76
78
|
},
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
79
|
+
coverage: {
|
80
|
+
code: percent(code_coverage),
|
81
|
+
total: percent(total_coverage),
|
82
|
+
lines: lines
|
81
83
|
}
|
82
84
|
}
|
83
85
|
end
|
84
|
-
|
85
|
-
#
|
86
|
-
|
87
|
-
|
88
|
-
# @return [String]
|
89
|
-
def format
|
90
|
-
template = (TEMPLATE_PATH + 'html' + 'file.erb').read
|
91
|
-
Erubis::Eruby.new(template).result(TEMPLATE_HASH.merge(self.data))
|
92
|
-
end
|
93
|
-
|
94
|
-
def write(dir)
|
95
|
-
path = (dir + @path.file_name).to_s + '.html'
|
96
|
-
File.open(path, 'w') {|f| f.write(self.format) }
|
86
|
+
|
87
|
+
# Writes the file
|
88
|
+
def write
|
89
|
+
Duvet.write data, 'html/file.erb'
|
97
90
|
end
|
98
|
-
|
91
|
+
|
99
92
|
end
|
100
|
-
end
|
93
|
+
end
|
data/lib/duvet/covs.rb
CHANGED
@@ -1,49 +1,37 @@
|
|
1
1
|
module Duvet
|
2
|
+
|
3
|
+
# A list of Cov objects.
|
2
4
|
class Covs < Array
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
|
6
|
+
# Creates a new Covs array from the data given by Coverage.
|
7
|
+
#
|
8
|
+
# @param data [Hash] Data given by Coverage
|
9
|
+
def self.from_data(data)
|
10
|
+
new data.map {|p,c| Cov.new(p, c) }
|
9
11
|
end
|
10
|
-
|
12
|
+
|
13
|
+
# @return [String] A simple text report of coverage
|
11
14
|
def report
|
12
|
-
map
|
15
|
+
map(&:report).join("\n") + "\n"
|
13
16
|
end
|
14
|
-
|
17
|
+
|
18
|
+
# @return [Hash] Data used for templating
|
15
19
|
def data
|
16
|
-
{
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
def write(dir)
|
25
|
-
if size > 0
|
26
|
-
FileUtils.mkdir_p(dir)
|
27
|
-
File.open(dir + 'index.html', 'w') {|f| f.write(format) }
|
28
|
-
|
29
|
-
each {|c| c.write(dir) }
|
30
|
-
write_resources(dir)
|
31
|
-
else
|
32
|
-
warn "No files to create coverage for."
|
33
|
-
end
|
20
|
+
{
|
21
|
+
files: map(&:data),
|
22
|
+
file: {
|
23
|
+
url: 'index.html'
|
24
|
+
}
|
25
|
+
}
|
34
26
|
end
|
35
27
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
f.write Sass::Engine.new(i.read).render
|
40
|
-
end
|
28
|
+
# Writes the index and individual files.
|
29
|
+
def write
|
30
|
+
warn "No files to create coverage for." if empty?
|
41
31
|
|
42
|
-
|
43
|
-
|
44
|
-
f.write(i.read)
|
45
|
-
end
|
32
|
+
each &:write
|
33
|
+
Duvet.write data, 'html/index.erb'
|
46
34
|
end
|
47
|
-
|
35
|
+
|
48
36
|
end
|
49
37
|
end
|