mattwynne-cucover 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/README.markdown +7 -3
  2. data/bin/cucover +0 -2
  3. data/lib/cucover.rb +167 -88
  4. 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
- * 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.
30
- * Consider making failing features run-run even when they're not dirty, just for the good old red-bar feel
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
@@ -1,5 +1,3 @@
1
1
  #!/usr/bin/env ruby
2
- require 'rubygems'
3
- require 'cucumber'
4
2
  require File.expand_path(File.dirname(__FILE__) + '../../lib/cucover')
5
3
  load Cucumber::BINARY
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 << self
10
- def record(file)
11
- additional_covered_files << file
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 save(analyzed_files)
24
- FileUtils.mkdir_p File.dirname(cache_filename)
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 exists?
31
- File.exist?(cache_filename)
26
+ def fail!
27
+ @failed = true
32
28
  end
33
29
 
34
- def any_dirty_files?
35
- not dirty_files.empty?
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 time
39
- File.mtime(cache_filename)
42
+ def may_execute?
43
+ dirty? || failed_on_last_run?
40
44
  end
41
-
45
+
42
46
  private
43
-
44
- def dirty_files
45
- source_files.select do |source_file|
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 source_files
51
- result = []
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 cache_filename
61
- @feature_file.gsub /([^\/]*\.feature)/, '.coverage/\1'
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 accept(visitor)
68
- return unless dirty?
69
- Cucover.additional_covered_files.clear
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(@file)
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 + Cucover.additional_covered_files.uniq).map{ |f| File.expand_path(f).gsub(/^#{Dir.pwd}\//, '') }
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
- module LazyFeatures
114
- def add_feature(feature)
115
- super feature.extend(LazyFeature)
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
- module Rails
120
- class << self
121
- def patch_if_necessary
122
- return if @patched
123
- return unless defined?(ActionView)
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
- module RecordsRenders
136
- def render
137
- Cucover.record(@filename)
138
- super
166
+ def record(status)
167
+ write_to_cache do |file|
168
+ file.puts status
139
169
  end
140
170
  end
141
- end
142
- end
171
+
172
+ private
143
173
 
144
- module Cucumber
145
- module Ast
146
- class Features
147
- class << self
148
- def new(*args)
149
- super(*args).extend(Cucover::LazyFeatures)
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mattwynne-cucover
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Wynne