duvet 0.3.2 → 0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/duvet.rb +77 -0
- data/lib/duvet/core_ext.rb +17 -0
- data/lib/duvet/cov.rb +100 -0
- data/lib/duvet/covs.rb +49 -0
- data/lib/duvet/version.rb +3 -0
- data/templates/css/styles.sass +178 -0
- data/templates/html/file.erb +71 -0
- data/templates/html/index.erb +69 -0
- data/templates/js/jquery.js +154 -0
- data/templates/js/main.js +56 -0
- data/templates/js/plugins.js +98 -0
- data/test/helper.rb +19 -0
- data/test/test_core_ext.rb +13 -0
- data/test/test_cov.rb +76 -0
- data/test/test_covs.rb +67 -0
- data/test/test_duvet.rb +17 -0
- metadata +29 -8
data/lib/duvet.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# Add this to test/helper.rb at the before __anything__ else
|
2
|
+
#
|
3
|
+
# require 'duvet'
|
4
|
+
# Duvet.start
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'coverage'
|
8
|
+
require 'pathname'
|
9
|
+
require 'erubis'
|
10
|
+
require 'sass'
|
11
|
+
|
12
|
+
require 'duvet/core_ext'
|
13
|
+
require 'duvet/covs'
|
14
|
+
require 'duvet/cov'
|
15
|
+
require 'duvet/version'
|
16
|
+
|
17
|
+
module Duvet
|
18
|
+
|
19
|
+
attr_accessor :opts
|
20
|
+
|
21
|
+
DEFAULTS = {:dir => 'cov', :style => 'rcov'}
|
22
|
+
|
23
|
+
TEMPLATE_PATH = Pathname.new(__FILE__).dirname + '..' + 'templates'
|
24
|
+
|
25
|
+
TEMPLATE_HASH = {
|
26
|
+
'time' => Time.now,
|
27
|
+
'version' => VERSION,
|
28
|
+
'name' => 'duvet'
|
29
|
+
}
|
30
|
+
|
31
|
+
# Start tracking
|
32
|
+
def self.start(opts={})
|
33
|
+
@opts = DEFAULTS.merge(opts)
|
34
|
+
|
35
|
+
Coverage.start
|
36
|
+
@running = true
|
37
|
+
end
|
38
|
+
|
39
|
+
# Get result
|
40
|
+
def self.result
|
41
|
+
cov = Coverage.result if running?
|
42
|
+
if @opts[:filter]
|
43
|
+
filtered = {}
|
44
|
+
@opts[:filter] = /#{@opts[:filter]}/ unless @opts[:filter].is_a?(Regexp)
|
45
|
+
cov.each do |k, v|
|
46
|
+
if @opts[:filter] =~ k
|
47
|
+
filtered[k] = v
|
48
|
+
end
|
49
|
+
end
|
50
|
+
cov = filtered
|
51
|
+
end
|
52
|
+
@result ||= Duvet::Covs.new(cov)
|
53
|
+
ensure
|
54
|
+
@running = false
|
55
|
+
end
|
56
|
+
|
57
|
+
# Write results
|
58
|
+
def self.write
|
59
|
+
self.result.write(Pathname.new(@opts[:dir])) if running?
|
60
|
+
end
|
61
|
+
|
62
|
+
# Proc to call when exiting
|
63
|
+
# @todo Allow user to override block used
|
64
|
+
def self.at_exit
|
65
|
+
Proc.new { self.write }
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [Boolean] whether coverage is running
|
69
|
+
def self.running?
|
70
|
+
@running
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
at_exit do
|
76
|
+
Duvet.at_exit.call
|
77
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Pathname
|
2
|
+
|
3
|
+
# Gets just the extension of the pathname
|
4
|
+
#
|
5
|
+
# @return [String] the extension of the path, without the '.'
|
6
|
+
def extension
|
7
|
+
self.extname[1..-1]
|
8
|
+
end
|
9
|
+
|
10
|
+
# Gets the name of the file without the extension
|
11
|
+
#
|
12
|
+
# @return [String] name of the file
|
13
|
+
def file_name
|
14
|
+
self.basename.to_s[0...-(self.extension.size+1)]
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
data/lib/duvet/cov.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
module Duvet
|
2
|
+
class Cov
|
3
|
+
attr_accessor :path
|
4
|
+
|
5
|
+
def initialize(path, cov)
|
6
|
+
@path = Pathname.new(path)
|
7
|
+
if @path.to_s.include?(Dir.pwd)
|
8
|
+
@path = @path.relative_path_from(Pathname.getwd)
|
9
|
+
end
|
10
|
+
|
11
|
+
@cov = cov
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [Array] lines in file
|
15
|
+
def lines
|
16
|
+
@cov
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Array] all lines which can be executed
|
20
|
+
def code_lines
|
21
|
+
@cov.reject {|i| i.nil?}
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [Array] all lines which have been ran
|
25
|
+
def ran_lines
|
26
|
+
@cov.reject {|i| i.nil? || i.zero?}
|
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
|
37
|
+
end
|
38
|
+
|
39
|
+
# Similar to #code_coverage but counts all lines, executable
|
40
|
+
# or not.
|
41
|
+
#
|
42
|
+
# @return [Integer] lines executed as a fraction
|
43
|
+
def total_coverage
|
44
|
+
return 0.0 if lines.size.zero?
|
45
|
+
ran_lines.size.to_f / lines.size.to_f
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [String] #code_coverage as ??.??%
|
49
|
+
def code_coverage_percent
|
50
|
+
"%.2f%" % (code_coverage*100)
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [String] #total_coverage as ??.??%
|
54
|
+
def total_coverage_percent
|
55
|
+
"%.2f%" % (total_coverage*100)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [String]
|
59
|
+
def report
|
60
|
+
str = "#{@path}\n"
|
61
|
+
str << " total: #{total_coverage_percent}\n"
|
62
|
+
str << " code: #{code_coverage_percent}\n\n"
|
63
|
+
str
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return [Hash] a hash of data for templating
|
67
|
+
def data
|
68
|
+
{
|
69
|
+
"file" => {
|
70
|
+
"path" => @path.to_s,
|
71
|
+
"url" => @path.file_name + '.html',
|
72
|
+
"source" => @path.readlines,
|
73
|
+
"lines" => lines.size,
|
74
|
+
"lines_code" => code_lines.size,
|
75
|
+
"lines_ran" => ran_lines.size
|
76
|
+
},
|
77
|
+
"coverage" => {
|
78
|
+
"code" => code_coverage_percent,
|
79
|
+
"total" => total_coverage_percent,
|
80
|
+
"lines" => lines
|
81
|
+
}
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
# Formats the coverage for the file to be written to a html
|
86
|
+
# file, then viewed in a web browser.
|
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) }
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
data/lib/duvet/covs.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module Duvet
|
2
|
+
class Covs < Array
|
3
|
+
|
4
|
+
def initialize(cov)
|
5
|
+
self.replace []
|
6
|
+
cov.each do |path, c|
|
7
|
+
self << Cov.new(path, c)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def report
|
12
|
+
map {|i| i.report }.join('')
|
13
|
+
end
|
14
|
+
|
15
|
+
def data
|
16
|
+
{ 'files' => map {|i| i.data } }
|
17
|
+
end
|
18
|
+
|
19
|
+
def format
|
20
|
+
template = (TEMPLATE_PATH + 'html' + 'index.erb').read
|
21
|
+
Erubis::Eruby.new(template).result(TEMPLATE_HASH.merge(self.data))
|
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
|
34
|
+
end
|
35
|
+
|
36
|
+
def write_resources(dir)
|
37
|
+
Pathname.glob(TEMPLATE_PATH + 'css' + '*').each do |i|
|
38
|
+
f = File.new(dir + 'styles.css', 'w')
|
39
|
+
f.write Sass::Engine.new(i.read).render
|
40
|
+
end
|
41
|
+
|
42
|
+
Pathname.glob(TEMPLATE_PATH + 'js' + '*').each do |i|
|
43
|
+
f = File.new(dir + i.basename, 'w')
|
44
|
+
f.write(i.read)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
$text: rgb(0, 0, 0)
|
2
|
+
|
3
|
+
$dark-grey: #333
|
4
|
+
$light-grey: #ccc
|
5
|
+
$white: #fff
|
6
|
+
|
7
|
+
$excluded: #e0dedb
|
8
|
+
$unran: #ce8b8c
|
9
|
+
$ran: #bed2be
|
10
|
+
|
11
|
+
// Main
|
12
|
+
html
|
13
|
+
margin: 0
|
14
|
+
padding: 0
|
15
|
+
width: 100%
|
16
|
+
|
17
|
+
body
|
18
|
+
background: #f4f2ed
|
19
|
+
font: 12px/1.3em Helvetica
|
20
|
+
margin: 8px
|
21
|
+
|
22
|
+
a
|
23
|
+
text-decoration: none
|
24
|
+
|
25
|
+
|
26
|
+
// Header
|
27
|
+
header
|
28
|
+
margin: 2em .5em
|
29
|
+
text-shadow: 0 1px 0 rgba(255, 255, 255, .5)
|
30
|
+
|
31
|
+
h1, h1 a
|
32
|
+
color: $dark-grey
|
33
|
+
|
34
|
+
h2
|
35
|
+
color: lighten($dark-grey, 40%)
|
36
|
+
font-size: 16px
|
37
|
+
|
38
|
+
// Filter/Search Box
|
39
|
+
#filter
|
40
|
+
position: absolute
|
41
|
+
top: 20px
|
42
|
+
right: 8px
|
43
|
+
border: 1px solid rgb(120, 120, 120)
|
44
|
+
background: rgb(240, 240, 240)
|
45
|
+
-webkit-border-radius: 3px
|
46
|
+
-webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, .1), 0 1px 0 white
|
47
|
+
-webkit-transition: all .2s
|
48
|
+
padding: 4px
|
49
|
+
width: 180px
|
50
|
+
|
51
|
+
&:focus
|
52
|
+
outline: none
|
53
|
+
background: rgb(244, 244, 244)
|
54
|
+
border: 1px solid rgb(100, 100, 100)
|
55
|
+
|
56
|
+
|
57
|
+
// Table
|
58
|
+
.table-wrapper
|
59
|
+
min-width: 700px
|
60
|
+
|
61
|
+
table
|
62
|
+
border: 1px solid grey
|
63
|
+
background: $excluded
|
64
|
+
border-collapse: collapse
|
65
|
+
width: 100%
|
66
|
+
margin-bottom: 1em
|
67
|
+
-webkit-box-shadow: 0 1px 0 white
|
68
|
+
|
69
|
+
table.source, pre, code
|
70
|
+
font: 11px/1.2em Menlo
|
71
|
+
|
72
|
+
td pre
|
73
|
+
margin: 0
|
74
|
+
|
75
|
+
|
76
|
+
// Summary
|
77
|
+
.summary
|
78
|
+
margin-bottom: 1em
|
79
|
+
background: $excluded
|
80
|
+
|
81
|
+
tbody
|
82
|
+
.name, .name a
|
83
|
+
color: $text
|
84
|
+
font-weight: bold
|
85
|
+
|
86
|
+
tbody tr:hover
|
87
|
+
background: darken($excluded, 10%)
|
88
|
+
|
89
|
+
thead
|
90
|
+
background: $dark-grey
|
91
|
+
color: $light-grey
|
92
|
+
|
93
|
+
th
|
94
|
+
cursor: pointer
|
95
|
+
|
96
|
+
tr
|
97
|
+
text-align: left
|
98
|
+
|
99
|
+
th, td
|
100
|
+
padding: .5em
|
101
|
+
|
102
|
+
.header::after
|
103
|
+
font-size: 10px
|
104
|
+
margin-left: 10px
|
105
|
+
|
106
|
+
.descending::after
|
107
|
+
content: '▼'
|
108
|
+
|
109
|
+
.ascending::after
|
110
|
+
content: '▲'
|
111
|
+
|
112
|
+
.lines, .loc, .ran, .cov, .code
|
113
|
+
width: 100px
|
114
|
+
|
115
|
+
// Totals
|
116
|
+
.totals
|
117
|
+
border-top: 1px solid grey
|
118
|
+
background: darken($excluded, 10%)
|
119
|
+
font-weight: bold
|
120
|
+
|
121
|
+
|
122
|
+
// Source
|
123
|
+
.source tr
|
124
|
+
pre
|
125
|
+
margin-left: 3px
|
126
|
+
|
127
|
+
.count
|
128
|
+
background: $white
|
129
|
+
-webkit-border-radius: 4px
|
130
|
+
-webkit-border-top-left-radius: 0px
|
131
|
+
-webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .2)
|
132
|
+
border: 1px solid black
|
133
|
+
display: block
|
134
|
+
padding: 3px
|
135
|
+
position: absolute
|
136
|
+
margin-top: -1.3em
|
137
|
+
right: 15px
|
138
|
+
opacity: 0
|
139
|
+
|
140
|
+
.no
|
141
|
+
color: $light-grey
|
142
|
+
background: $dark-grey
|
143
|
+
padding: 0 0 0 3px
|
144
|
+
width: 25px
|
145
|
+
|
146
|
+
&:hover
|
147
|
+
.count
|
148
|
+
opacity: 1
|
149
|
+
|
150
|
+
.no
|
151
|
+
color: $white
|
152
|
+
|
153
|
+
// line colouring
|
154
|
+
&.excluded
|
155
|
+
background: $excluded
|
156
|
+
&:hover
|
157
|
+
background: darken($excluded, 10%)
|
158
|
+
|
159
|
+
&.unran
|
160
|
+
background: $unran
|
161
|
+
&:hover
|
162
|
+
background: darken($unran, 10%)
|
163
|
+
|
164
|
+
&.ran
|
165
|
+
background: $ran
|
166
|
+
&:hover
|
167
|
+
background: darken($ran, 10%)
|
168
|
+
|
169
|
+
|
170
|
+
// Footer
|
171
|
+
footer
|
172
|
+
color: rgba(0, 0, 0, .6)
|
173
|
+
text-shadow: 0 1px 0 rgba(255, 255, 255, .5)
|
174
|
+
|
175
|
+
a
|
176
|
+
color: rgb(0, 0, 0)
|
177
|
+
|
178
|
+
|
@@ -0,0 +1,71 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8" />
|
5
|
+
<title><%= file['path'] %></title>
|
6
|
+
<link rel="stylesheet" href="styles.css" type="text/css" />
|
7
|
+
<script type="text/javascript" src="jquery.js"></script>
|
8
|
+
<script type="text/javascript" src="main.js"></script>
|
9
|
+
<script type="text/javascript" src="plugins.js"></script>
|
10
|
+
</head>
|
11
|
+
|
12
|
+
<body>
|
13
|
+
<header>
|
14
|
+
<h1><a href="index.html">Coverage</a></h1>
|
15
|
+
<h2><%= file['path'] %></h2>
|
16
|
+
</header>
|
17
|
+
|
18
|
+
<div class="table-wrapper">
|
19
|
+
<table class="summary" border="none">
|
20
|
+
<thead>
|
21
|
+
<tr>
|
22
|
+
<th>Name</th>
|
23
|
+
<th>Total Lines</th>
|
24
|
+
<th>Lines of Code</th>
|
25
|
+
<th>Lines Ran</th>
|
26
|
+
<th>Total Coverage</th>
|
27
|
+
<th>Code Coverage</th>
|
28
|
+
</tr>
|
29
|
+
</thead>
|
30
|
+
<tbody>
|
31
|
+
<tr>
|
32
|
+
<td class="name"><%= file['path'] %></td>
|
33
|
+
<td><%= file['lines'] %></td>
|
34
|
+
<td><%= file['lines_code']%></td>
|
35
|
+
<td><%= file['lines_ran']%></td>
|
36
|
+
<td><%= coverage['total']%></td>
|
37
|
+
<td><%= coverage['code']%></td>
|
38
|
+
</tr>
|
39
|
+
</tbody>
|
40
|
+
</table>
|
41
|
+
</div>
|
42
|
+
|
43
|
+
<div class="table-wrapper">
|
44
|
+
<table class="source" border="none">
|
45
|
+
<% file['source'].zip(coverage['lines']).each_with_index do |(line, count), i| %>
|
46
|
+
<% if count.nil? %>
|
47
|
+
<tr class="excluded">
|
48
|
+
<td class="no"><%= i %></td>
|
49
|
+
<td><pre><code><%= line %></code></pre></td>
|
50
|
+
</tr>
|
51
|
+
|
52
|
+
<% else %>
|
53
|
+
<tr class="<%= count.zero? ? "unran" : "ran" %>">
|
54
|
+
<td class="no"><%= i %></td>
|
55
|
+
<td><pre><code><%= line %></code></pre><span class="count"><%= count %></span></td>
|
56
|
+
</tr>
|
57
|
+
<% end %>
|
58
|
+
<% end %>
|
59
|
+
</table>
|
60
|
+
</div>
|
61
|
+
|
62
|
+
|
63
|
+
<footer>
|
64
|
+
<span>
|
65
|
+
Generated at <%= time %> with
|
66
|
+
<a href="http://github.com/hawx/duvet"><%= name %> <%= version %></a>
|
67
|
+
</span>
|
68
|
+
</footer>
|
69
|
+
</body>
|
70
|
+
</html>
|
71
|
+
|