mattwynne-cucover 0.0.2 → 0.0.3
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/README.markdown +7 -3
- data/bin/cucover +0 -2
- data/lib/cucover.rb +167 -88
- metadata +1 -1
data/README.markdown
CHANGED
@@ -10,11 +10,14 @@ How does it decide whether it needs to run a feature? Every time you run a featu
|
|
10
10
|
|
11
11
|
* Uses RCov to map features to covered source files
|
12
12
|
* Patches Rails to also map features to covered .erb templates
|
13
|
+
* Shows skipped Scenarios, for confidence
|
14
|
+
* Re-runs failing features, even when nothing has changed, for that good old red-bar feel.
|
13
15
|
|
14
16
|
## Installation and Usage
|
15
17
|
|
16
18
|
Something like this, as I haven't figured out the dependencies yet for the gem:
|
17
19
|
|
20
|
+
sudo gem install cucumber
|
18
21
|
sudo gem install spicycode-rcov
|
19
22
|
sudo gem install mattwynne-cucover
|
20
23
|
|
@@ -26,8 +29,10 @@ To run your features lazily, just use the cucover binary instead of cucumber. Us
|
|
26
29
|
* This is very new and experimental. There may be bugs. Feedback is welcome via github messages.
|
27
30
|
|
28
31
|
## Todo
|
29
|
-
*
|
30
|
-
*
|
32
|
+
* One or two of the features for Cucuover itself seem to flicker (intermittently fail). This is probably due to timing issues when figuring out if a file is dirty.
|
33
|
+
* Make laziness work down to the granularity of scenarios, rather than features
|
34
|
+
* I suspect it does wierd things if you pass more than one visitor. Need to test for this.
|
35
|
+
* Speed up the Rails test - maybe strip some guff out of the environment load?
|
31
36
|
|
32
37
|
## Similar 'Selective Testing' Tools
|
33
38
|
|
@@ -45,4 +50,3 @@ To run your features lazily, just use the cucover binary instead of cucumber. Us
|
|
45
50
|
* JUnitMax
|
46
51
|
* a selective testing tool by [Kent Beck](http://www.threeriversinstitute.org/blog)
|
47
52
|
* http://junitmax.com/
|
48
|
-
|
data/bin/cucover
CHANGED
data/lib/cucover.rb
CHANGED
@@ -1,92 +1,81 @@
|
|
1
|
-
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
-
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
1
|
|
4
2
|
require 'rubygems'
|
3
|
+
|
4
|
+
gem 'cucumber', '>=0.3'
|
5
|
+
require 'cucumber'
|
6
|
+
|
7
|
+
gem 'spicycode-rcov', '>=0.8.1.5.0'
|
5
8
|
require 'rcov'
|
9
|
+
require 'spec'
|
10
|
+
|
11
|
+
$:.unshift(File.dirname(__FILE__))
|
12
|
+
require 'cucover/monkey'
|
13
|
+
require 'cucover/rails'
|
6
14
|
|
7
15
|
module Cucover
|
8
16
|
|
9
|
-
class
|
10
|
-
def
|
11
|
-
|
12
|
-
end
|
13
|
-
def additional_covered_files
|
14
|
-
@additional_covered_files ||= []
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
class SourceFileCache
|
19
|
-
def initialize(feature_file)
|
20
|
-
@feature_file = feature_file
|
17
|
+
class TestRun
|
18
|
+
def initialize(feature_file, visitor)
|
19
|
+
@feature_file, @visitor = feature_file, visitor
|
21
20
|
end
|
22
21
|
|
23
|
-
def
|
24
|
-
|
25
|
-
File.open(cache_filename, "w") do |file|
|
26
|
-
file.puts analyzed_files
|
27
|
-
end
|
22
|
+
def record(source_file)
|
23
|
+
additional_covered_files << source_file
|
28
24
|
end
|
29
25
|
|
30
|
-
def
|
31
|
-
|
26
|
+
def fail!
|
27
|
+
@failed = true
|
32
28
|
end
|
33
29
|
|
34
|
-
def
|
35
|
-
|
30
|
+
def watch
|
31
|
+
announce_skip unless may_execute?
|
32
|
+
|
33
|
+
analyzer.run_hooked do
|
34
|
+
yield
|
35
|
+
end
|
36
|
+
|
37
|
+
record(@feature_file)
|
38
|
+
source_files_cache.save analyzed_files
|
39
|
+
status_cache.record(status)
|
36
40
|
end
|
37
41
|
|
38
|
-
def
|
39
|
-
|
42
|
+
def may_execute?
|
43
|
+
dirty? || failed_on_last_run?
|
40
44
|
end
|
41
|
-
|
45
|
+
|
42
46
|
private
|
43
|
-
|
44
|
-
def
|
45
|
-
|
46
|
-
File.mtime(Dir.pwd + '/' + source_file.strip) >= time
|
47
|
-
end
|
47
|
+
|
48
|
+
def status
|
49
|
+
@failed ? :failed : :passed
|
48
50
|
end
|
49
51
|
|
50
|
-
def
|
51
|
-
|
52
|
-
File.open(cache_filename, "r") do |file|
|
53
|
-
file.each_line do |line|
|
54
|
-
result.push line
|
55
|
-
end
|
56
|
-
end
|
57
|
-
result
|
52
|
+
def additional_covered_files
|
53
|
+
@additional_covered_files ||= []
|
58
54
|
end
|
59
55
|
|
60
|
-
def
|
61
|
-
|
56
|
+
def announce_skip
|
57
|
+
messages = []
|
58
|
+
messages << "Cucover - Skipping clean feature"
|
59
|
+
messages << "Last run status: #{status_cache.last_run_status}"
|
60
|
+
@visitor.announce messages.flatten.map{ |m| "[ #{m.rstrip} ]"}.join("\n")
|
62
61
|
end
|
63
|
-
end
|
64
|
-
|
65
|
-
module LazyFeature
|
66
62
|
|
67
|
-
def
|
68
|
-
return unless
|
69
|
-
|
70
|
-
analyzer.run_hooked do
|
71
|
-
super
|
72
|
-
end
|
73
|
-
source_files_cache.save analyzed_files
|
63
|
+
def failed_on_last_run?
|
64
|
+
return false unless status_cache.exists?
|
65
|
+
status_cache.last_run_status == "failed"
|
74
66
|
end
|
75
|
-
|
76
|
-
private
|
77
67
|
|
78
68
|
def dirty?
|
79
69
|
return true unless source_files_cache.exists?
|
80
|
-
return true if changed_since_last_run?
|
81
70
|
source_files_cache.any_dirty_files?
|
82
71
|
end
|
83
72
|
|
84
|
-
def changed_since_last_run?
|
85
|
-
File.mtime(@file) >= source_files_cache.time
|
86
|
-
end
|
87
|
-
|
88
73
|
def source_files_cache
|
89
|
-
@source_files_cache ||= SourceFileCache.new(@
|
74
|
+
@source_files_cache ||= SourceFileCache.new(@feature_file)
|
75
|
+
end
|
76
|
+
|
77
|
+
def status_cache
|
78
|
+
@status_cache ||= StatusCache.new(@feature_file)
|
90
79
|
end
|
91
80
|
|
92
81
|
def source_files
|
@@ -98,61 +87,151 @@ module Cucover
|
|
98
87
|
end
|
99
88
|
|
100
89
|
def normalized_files
|
101
|
-
(analyzer.analyzed_files +
|
90
|
+
(analyzer.analyzed_files + additional_covered_files.uniq).map{ |f| File.expand_path(f).gsub(/^#{Dir.pwd}\//, '') }
|
102
91
|
end
|
103
92
|
|
104
93
|
def boring?(file)
|
94
|
+
return false
|
105
95
|
(file.match /gem/) || (file.match /vendor/) || (file.match /lib\/ruby/)
|
106
96
|
end
|
107
97
|
|
108
98
|
def analyzer
|
109
99
|
@analyzer ||= Rcov::CodeCoverageAnalyzer.new
|
110
100
|
end
|
101
|
+
|
111
102
|
end
|
112
103
|
|
113
|
-
|
114
|
-
def
|
115
|
-
|
104
|
+
class << self
|
105
|
+
def start_test(test, visitor)
|
106
|
+
@current_test = TestRun.new(test.file, visitor)
|
107
|
+
|
108
|
+
@current_test.watch do
|
109
|
+
yield
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def fail_current_test!
|
114
|
+
current_test.fail!
|
115
|
+
end
|
116
|
+
|
117
|
+
def record(source_file)
|
118
|
+
current_test.record(source_file)
|
119
|
+
end
|
120
|
+
|
121
|
+
def should_skip?
|
122
|
+
not current_test.may_execute?
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def current_test
|
128
|
+
@current_test or raise("You need to start the a test first!")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class Cache
|
133
|
+
def initialize(feature_file)
|
134
|
+
@feature_file = feature_file
|
135
|
+
end
|
136
|
+
|
137
|
+
def exists?
|
138
|
+
File.exist?(cache_filename)
|
139
|
+
end
|
140
|
+
|
141
|
+
def cache_filename
|
142
|
+
@feature_file.gsub /([^\/]*\.feature)/, '.coverage/\1'
|
143
|
+
end
|
144
|
+
|
145
|
+
def time
|
146
|
+
File.mtime(cache_filename)
|
147
|
+
end
|
148
|
+
|
149
|
+
def write_to_cache
|
150
|
+
FileUtils.mkdir_p File.dirname(cache_filename)
|
151
|
+
File.open(cache_filename, "w") do |file|
|
152
|
+
yield file
|
153
|
+
end
|
116
154
|
end
|
117
155
|
end
|
118
156
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
ActionView::Template.instance_eval do
|
126
|
-
def new(*args)
|
127
|
-
super(*args).extend(Cucover::Rails::RecordsRenders)
|
128
|
-
end
|
157
|
+
class StatusCache < Cache
|
158
|
+
def last_run_status
|
159
|
+
File.open(cache_filename, "r") do |file|
|
160
|
+
file.each_line do |line|
|
161
|
+
return line.strip
|
129
162
|
end
|
130
|
-
|
131
|
-
@patched = true
|
132
163
|
end
|
133
164
|
end
|
134
165
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
super
|
166
|
+
def record(status)
|
167
|
+
write_to_cache do |file|
|
168
|
+
file.puts status
|
139
169
|
end
|
140
170
|
end
|
141
|
-
|
142
|
-
|
171
|
+
|
172
|
+
private
|
143
173
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
174
|
+
def cache_filename
|
175
|
+
super + '.status'
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
class SourceFileCache < Cache
|
180
|
+
def save(analyzed_files)
|
181
|
+
write_to_cache do |file|
|
182
|
+
file.puts analyzed_files
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def any_dirty_files?
|
187
|
+
not dirty_files.empty?
|
188
|
+
end
|
189
|
+
|
190
|
+
def source_files
|
191
|
+
result = []
|
192
|
+
File.open(cache_filename, "r") do |file|
|
193
|
+
file.each_line do |line|
|
194
|
+
result.push line
|
150
195
|
end
|
151
196
|
end
|
197
|
+
result
|
198
|
+
end
|
199
|
+
|
200
|
+
private
|
201
|
+
|
202
|
+
def dirty_files
|
203
|
+
source_files.select do |source_file|
|
204
|
+
File.mtime(source_file.strip) >= time
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
|
210
|
+
module LazyStepInvocation
|
211
|
+
def accept(visitor)
|
212
|
+
skip_invoke! if Cucover.should_skip?
|
213
|
+
super
|
214
|
+
end
|
215
|
+
|
216
|
+
def failed(exception, clear_backtrace)
|
217
|
+
Cucover.fail_current_test!
|
218
|
+
super
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
module LazyFeature
|
223
|
+
def accept(visitor)
|
224
|
+
Cucover.start_test(self, visitor) do
|
225
|
+
super
|
226
|
+
end
|
152
227
|
end
|
228
|
+
|
153
229
|
end
|
154
230
|
end
|
155
231
|
|
232
|
+
Cucover::Monkey.extend_every Cucumber::Ast::Feature => Cucover::LazyFeature
|
233
|
+
Cucover::Monkey.extend_every Cucumber::Ast::StepInvocation => Cucover::LazyStepInvocation
|
234
|
+
|
156
235
|
Before do
|
157
236
|
Cucover::Rails.patch_if_necessary
|
158
|
-
end
|
237
|
+
end
|