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.
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