spinach 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/.gitignore +1 -0
  2. data/.yardopts +6 -0
  3. data/Gemfile +1 -0
  4. data/Guardfile +7 -0
  5. data/Rakefile +1 -0
  6. data/Readme.md +147 -10
  7. data/features/{generate_features.feature → automatic_feature_generation.feature} +0 -0
  8. data/features/steps/automatic_feature_generation.rb +5 -2
  9. data/features/steps/exit_status.rb +8 -3
  10. data/features/steps/feature_name_guessing.rb +4 -1
  11. data/features/steps/reporting/display_run_summary.rb +7 -4
  12. data/features/steps/reporting/error_reporting.rb +7 -2
  13. data/features/steps/reporting/show_step_source_location.rb +9 -5
  14. data/features/steps/reporting/undefined_feature_reporting.rb +4 -1
  15. data/features/steps/rspec_compatibility.rb +6 -2
  16. data/features/support/spinach_runner.rb +2 -9
  17. data/lib/spinach/capybara.rb +3 -4
  18. data/lib/spinach/cli.rb +0 -1
  19. data/lib/spinach/config.rb +0 -8
  20. data/lib/spinach/dsl.rb +4 -13
  21. data/lib/spinach/exceptions.rb +1 -1
  22. data/lib/spinach/feature_steps.rb +1 -9
  23. data/lib/spinach/generators/feature_generator.rb +3 -2
  24. data/lib/spinach/generators.rb +1 -1
  25. data/lib/spinach/hookable.rb +81 -0
  26. data/lib/spinach/hooks.rb +132 -0
  27. data/lib/spinach/reporter/stdout/error_reporting.rb +163 -0
  28. data/lib/spinach/reporter/stdout.rb +3 -151
  29. data/lib/spinach/reporter.rb +39 -25
  30. data/lib/spinach/runner/{feature.rb → feature_runner.rb} +5 -15
  31. data/lib/spinach/runner/scenario_runner.rb +65 -0
  32. data/lib/spinach/runner.rb +5 -11
  33. data/lib/spinach/version.rb +1 -1
  34. data/lib/spinach.rb +20 -8
  35. data/spinach.gemspec +2 -3
  36. data/test/spinach/capybara_test.rb +4 -3
  37. data/test/spinach/cli_test.rb +0 -1
  38. data/test/spinach/feature_steps_test.rb +6 -23
  39. data/test/spinach/generators/feature_generator_test.rb +2 -2
  40. data/test/spinach/generators_test.rb +2 -2
  41. data/test/spinach/hookable_test.rb +59 -0
  42. data/test/spinach/hooks_test.rb +28 -0
  43. data/test/spinach/reporter/stdout/error_reporting_test.rb +265 -0
  44. data/test/spinach/reporter/stdout_test.rb +1 -238
  45. data/test/spinach/reporter_test.rb +58 -103
  46. data/test/spinach/runner/{feature_test.rb → feature_runner_test.rb} +21 -23
  47. data/test/spinach/runner/scenario_runner_test.rb +111 -0
  48. data/test/spinach/runner_test.rb +1 -1
  49. data/test/spinach_test.rb +19 -18
  50. data/test/test_helper.rb +1 -1
  51. metadata +60 -61
  52. data/lib/spinach/runner/scenario.rb +0 -77
  53. data/test/spinach/runner/scenario_test.rb +0 -120
@@ -1,11 +1,8 @@
1
- require 'hooks'
2
-
3
1
  module Spinach
4
2
  # Runner gets the parsed data from the feature and performs the actual calls
5
3
  # to the feature classes.
6
4
  #
7
5
  class Runner
8
- include Hooks
9
6
 
10
7
  # The feature files to run
11
8
  attr_reader :filenames
@@ -16,9 +13,6 @@ module Spinach
16
13
  # The default path where the support files are located
17
14
  attr_reader :support_path
18
15
 
19
- define_hook :before_run
20
- define_hook :after_run
21
-
22
16
  # Initializes the runner with a parsed feature
23
17
  #
24
18
  # @param [Array<String>] filenames
@@ -62,16 +56,16 @@ module Spinach
62
56
  require_dependencies
63
57
  require_suites
64
58
 
65
- run_hook :before_run
59
+ Spinach.hooks.run_before_run
66
60
 
67
61
  successful = true
68
62
 
69
63
  filenames.each do |filename|
70
- success = Feature.new(filename).run
64
+ success = FeatureRunner.new(filename).run
71
65
  successful = false unless success
72
66
  end
73
67
 
74
- run_hook :after_run, successful
68
+ Spinach.hooks.run_after_run(successful)
75
69
 
76
70
  successful
77
71
  end
@@ -113,5 +107,5 @@ module Spinach
113
107
  end
114
108
  end
115
109
 
116
- require_relative 'runner/feature'
117
- require_relative 'runner/scenario'
110
+ require_relative 'runner/feature_runner'
111
+ require_relative 'runner/scenario_runner'
@@ -1,4 +1,4 @@
1
1
  module Spinach
2
2
  # Spinach version.
3
- VERSION = "0.1.4"
3
+ VERSION = "0.1.5"
4
4
  end
data/lib/spinach.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  require_relative 'spinach/version'
2
2
  require_relative 'spinach/config'
3
+ require_relative 'spinach/hookable'
4
+ require_relative 'spinach/hooks'
3
5
  require_relative 'spinach/support'
4
6
  require_relative 'spinach/exceptions'
5
7
  require_relative 'spinach/runner'
@@ -23,22 +25,29 @@ require_relative 'spinach/generators'
23
25
  # scenarios.
24
26
  #
25
27
  module Spinach
26
- @@features = []
28
+ @@feature_steps = []
27
29
 
28
- # @return [Array<Feature>]
30
+ # @return [Array<FeatureSteps>]
29
31
  # All the registered features.
30
32
  #
31
33
  # @api public
32
- def self.features
33
- @@features
34
+ def self.feature_steps
35
+ @@feature_steps
34
36
  end
35
37
 
36
38
  # Resets Spinach to a pristine state, as if no feature was ever registered.
37
39
  # Mostly useful in Spinach's own testing.
38
40
  #
39
41
  # @api semipublic
40
- def self.reset_features
41
- @@features = []
42
+ def self.reset_feature_steps
43
+ @@feature_steps = []
44
+ end
45
+
46
+ # Returns a new hook object that will receive all the messages from the run
47
+ # and fire up the appropiate callbacks when needed.
48
+ #
49
+ def self.hooks
50
+ @@hooks ||= Hooks.new
42
51
  end
43
52
 
44
53
  # Finds a feature given a feature name.
@@ -46,10 +55,13 @@ module Spinach
46
55
  # @param [String] name
47
56
  # The feature name.
48
57
  #
58
+ # @return [FeatureSteps]
59
+ # the {FeatureSteps} class for the given feature name
60
+ #
49
61
  # @api public
50
- def self.find_feature(name)
62
+ def self.find_feature_steps(name)
51
63
  klass = Spinach::Support.camelize(name)
52
- @@features.detect do |feature|
64
+ feature_steps.detect do |feature|
53
65
  feature.feature_name.to_s == name.to_s ||
54
66
  feature.name == klass
55
67
  end || raise(Spinach::FeatureStepsNotFoundException, [klass, name])
data/spinach.gemspec CHANGED
@@ -10,10 +10,7 @@ Gem::Specification.new do |gem|
10
10
  gem.homepage = "http://github.com/codegram/spinach"
11
11
 
12
12
  gem.add_runtime_dependency 'gherkin'
13
- gem.add_runtime_dependency 'minitest'
14
13
  gem.add_runtime_dependency 'colorize'
15
- gem.add_runtime_dependency 'hooks'
16
- gem.add_development_dependency 'purdytest'
17
14
  gem.add_development_dependency 'rake'
18
15
  gem.add_development_dependency 'mocha'
19
16
  gem.add_development_dependency 'sinatra'
@@ -23,6 +20,8 @@ Gem::Specification.new do |gem|
23
20
  gem.add_development_dependency 'simplecov'
24
21
  gem.add_development_dependency 'rspec'
25
22
  gem.add_development_dependency 'fakefs'
23
+ gem.add_development_dependency 'minitest'
24
+ gem.add_development_dependency 'turn'
26
25
 
27
26
  gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
28
27
  gem.files = `git ls-files`.split("\n")
@@ -1,4 +1,5 @@
1
1
  require 'test_helper'
2
+ require 'spinach'
2
3
  require 'spinach/capybara'
3
4
  require 'sinatra'
4
5
 
@@ -35,7 +36,7 @@ describe Spinach::FeatureSteps::Capybara do
35
36
  end
36
37
 
37
38
  it 'resets the capybara session after each scenario' do
38
- @feature_runner = Spinach::Runner::Feature.new(
39
+ @feature_runner = Spinach::Runner::FeatureRunner.new(
39
40
  'a_feature.feature')
40
41
 
41
42
  @feature_runner.stubs(data: Spinach::Parser.new('
@@ -48,9 +49,9 @@ describe Spinach::FeatureSteps::Capybara do
48
49
  Then Goodbye
49
50
  ').parse).at_least_once
50
51
 
51
- Spinach::Runner::Scenario.any_instance.stubs(feature: @feature)
52
+ Spinach::Runner::ScenarioRunner.any_instance.stubs(feature_steps: @feature)
52
53
 
53
- Capybara.current_session.expects(:reset!).twice
54
+ Capybara.current_session.expects(:reset!).at_least_once
54
55
 
55
56
  @feature_runner.run
56
57
  end
@@ -49,7 +49,6 @@ describe Spinach::Cli do
49
49
  reporter.expects(:bind)
50
50
  Spinach::Reporter::Stdout.stubs(new: reporter)
51
51
  cli.init_reporter
52
- Spinach.config.default_reporter.wont_equal nil
53
52
  end
54
53
  end
55
54
 
@@ -10,13 +10,13 @@ describe Spinach::FeatureSteps do
10
10
  describe 'class methods' do
11
11
  describe '#inherited' do
12
12
  it 'registers any feature subclass' do
13
- @feature1 = Class.new(Spinach::FeatureSteps)
14
- @feature2 = Class.new(Spinach::FeatureSteps)
15
- @feature3 = Class.new
13
+ @feature_steps1 = Class.new(Spinach::FeatureSteps)
14
+ @feature_steps2 = Class.new(Spinach::FeatureSteps)
15
+ @feature_steps3 = Class.new
16
16
 
17
- Spinach.features.must_include @feature1
18
- Spinach.features.must_include @feature2
19
- Spinach.features.wont_include @feature3
17
+ Spinach.feature_steps.must_include @feature_steps1
18
+ Spinach.feature_steps.must_include @feature_steps2
19
+ Spinach.feature_steps.wont_include @feature_steps3
20
20
  end
21
21
  end
22
22
  end
@@ -46,21 +46,4 @@ describe Spinach::FeatureSteps do
46
46
  end
47
47
  end
48
48
 
49
- describe 'Object#Feature' do
50
- it 'creates a feature class' do
51
- feature = Feature('Hola') do
52
- attr_accessor :test
53
- When 'Test' do
54
- self.test = true
55
- end
56
- end
57
-
58
- Spinach.features.must_include feature
59
-
60
- instance = feature.new
61
- instance.execute_step('Test')
62
-
63
- instance.test.must_equal true
64
- end
65
- end
66
49
  end
@@ -73,7 +73,7 @@ module Spinach::Generators
73
73
  it "generates an entire feature_steps class definition" do
74
74
  result = subject.generate
75
75
  klass = eval(result)
76
- feature_runner = Spinach::Runner::Feature.new(stub_everything)
76
+ feature_runner = Spinach::Runner::FeatureRunner.new(stub_everything)
77
77
  feature_runner.stubs(data: data)
78
78
  feature_runner.run.must_equal true
79
79
  end
@@ -97,7 +97,7 @@ module Spinach::Generators
97
97
  must_include 'features/steps/cheezburger_can_i_has.rb'
98
98
  end
99
99
  end
100
-
100
+
101
101
  describe "#store" do
102
102
  it "stores the generated feature into a file" do
103
103
  FakeFS.activate!
@@ -22,8 +22,8 @@ describe Spinach::Generators do
22
22
  it "binds the generator to the missing feature hook" do
23
23
  subject.expects(:generate_feature).with(data)
24
24
  subject.bind
25
- Spinach::Runner::Feature.new(stub_everything).run_hook :when_not_found, data
26
- Spinach::Runner::Feature._when_not_found_callbacks = []
25
+ Spinach.hooks.run_on_undefined_feature data
26
+ Spinach.hooks.reset
27
27
  end
28
28
  end
29
29
 
@@ -0,0 +1,59 @@
1
+ require_relative '../test_helper'
2
+
3
+ describe Spinach::Hookable do
4
+ subject do
5
+ Class.new do
6
+ include Spinach::Hookable
7
+ end.new
8
+ end
9
+
10
+ describe ".define_hook" do
11
+ it "defines a new hook" do
12
+ subject.class.hook :before_save
13
+ subject.must_respond_to :before_save
14
+ end
15
+ end
16
+
17
+ describe "hooking mechanism" do
18
+ describe "without params" do
19
+ it "adds a new hook to the queue" do
20
+ subject.add_hook(:before_save) do
21
+ end
22
+ (subject.hooks_for(:before_save).empty?).must_equal false
23
+ end
24
+
25
+ it "allows to run a hook" do
26
+ arbitrary_variable = false
27
+ subject.add_hook(:before_save) do
28
+ arbitrary_variable = true
29
+ end
30
+ subject.run_hook(:before_save)
31
+ arbitrary_variable.must_equal true
32
+ end
33
+ end
34
+
35
+ describe "with params" do
36
+ it "adds a new hook to the queue" do
37
+ subject.add_hook(:before_save) do |var1, var2|
38
+ end
39
+ (subject.hooks_for(:before_save).empty?).must_equal false
40
+ end
41
+
42
+ it "allows to run a hook" do
43
+ array = []
44
+ subject.add_hook(:before_save) do |var1, var2|
45
+ array << var1
46
+ array << var2
47
+ end
48
+ subject.run_hook(:before_save, 1, 2)
49
+ array.must_equal [1, 2]
50
+ end
51
+ end
52
+
53
+ describe "#reset_hooks" do
54
+ it "resets the hooks to a pristine state" do
55
+ subject.add_hook(:before_save)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,28 @@
1
+ require_relative '../test_helper'
2
+
3
+ describe Spinach::Hooks do
4
+ subject do
5
+ Spinach::Hooks.new
6
+ end
7
+
8
+ describe "hooks" do
9
+ %w{before_run after_run before_feature after_feature on_undefined_feature
10
+ before_scenario after_scenario before_step after_step on_successful_step
11
+ on_failed_step on_error_step on_undefined_step on_skipped_step}.each do |callback|
12
+ it "responds to #{callback}" do
13
+ subject.must_respond_to callback
14
+ end
15
+
16
+ it "executes the hook with params" do
17
+ array = []
18
+ block = Proc.new do |arg1, arg2|
19
+ array << arg1
20
+ array << arg2
21
+ end
22
+ subject.send(callback, &block)
23
+ subject.send("run_#{callback}", 1, 2)
24
+ array.must_equal [1, 2]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,265 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative '../../../test_helper'
4
+
5
+ describe Spinach::Reporter::Stdout do
6
+ let(:exception) { StandardError.new('Something went wrong') }
7
+
8
+ let(:error) do
9
+ [{'name' => 'My feature'},
10
+ {'name' => 'A scenario'},
11
+ {'keyword' => 'Keyword', 'name' => 'step name'},
12
+ exception]
13
+ end
14
+
15
+ let(:steps) do
16
+ [error]
17
+ end
18
+
19
+
20
+ before do
21
+ @out = StringIO.new
22
+ @error = StringIO.new
23
+ @reporter = Spinach::Reporter::Stdout.new(
24
+ output: @out,
25
+ error: @error
26
+ )
27
+ end
28
+
29
+ describe '#error_summary' do
30
+ it 'prints a summary with all the errors' do
31
+ @reporter.expects(:report_error_steps).once
32
+ @reporter.expects(:report_failed_steps).once
33
+ @reporter.expects(:report_undefined_steps).once
34
+ @reporter.expects(:report_undefined_features).once
35
+
36
+ @reporter.error_summary
37
+
38
+ @error.string.must_include 'Error summary'
39
+ end
40
+ end
41
+
42
+ describe '#run_summary' do
43
+ it 'prints a run summary' do
44
+ @reporter.run_summary
45
+
46
+ @out.string.must_include 'Steps Summary:'
47
+ end
48
+ end
49
+
50
+ describe '#report_error_steps' do
51
+ describe 'when some steps have raised an error' do
52
+ it 'outputs the errors' do
53
+ steps = [anything]
54
+ @reporter.stubs(:error_steps).returns(steps)
55
+ @reporter.expects(:report_errors).with('Errors', steps, :light_red)
56
+
57
+ @reporter.report_error_steps
58
+ end
59
+ end
60
+
61
+ describe 'when there are no error steps' do
62
+ it 'does nothing' do
63
+ @reporter.expects(:report_errors).never
64
+
65
+ @reporter.report_error_steps
66
+ end
67
+ end
68
+ end
69
+
70
+ describe '#report_failed_steps' do
71
+ describe 'when some steps have failed' do
72
+ it 'outputs the failing steps' do
73
+ steps = [anything]
74
+ @reporter.stubs(:failed_steps).returns(steps)
75
+ @reporter.expects(:report_errors).with('Failures', steps, :light_red)
76
+
77
+ @reporter.report_failed_steps
78
+ end
79
+ end
80
+
81
+ describe 'when there are no failed steps' do
82
+ it 'does nothing' do
83
+ @reporter.expects(:report_errors).never
84
+
85
+ @reporter.report_failed_steps
86
+ end
87
+ end
88
+ end
89
+
90
+ describe '#report_undefined_steps' do
91
+ describe 'when some steps have undefined' do
92
+ it 'outputs the failing steps' do
93
+ steps = [anything]
94
+ @reporter.stubs(:undefined_steps).returns(steps)
95
+ @reporter.expects(:report_errors).with('Undefined steps', steps, :yellow)
96
+
97
+ @reporter.report_undefined_steps
98
+ end
99
+ end
100
+
101
+ describe 'when there are no undefined steps' do
102
+ it 'does nothing' do
103
+ @reporter.expects(:report_errors).never
104
+
105
+ @reporter.report_undefined_steps
106
+ end
107
+ end
108
+ end
109
+
110
+ describe '#report_undefined_features' do
111
+ describe 'when some features are undefined' do
112
+ it 'outputs the undefined features' do
113
+ @reporter.undefined_features << {'name' => 'Undefined feature name'}
114
+ @reporter.report_undefined_features
115
+
116
+ @error.string.must_include "Undefined features (1)"
117
+ @error.string.must_include "Undefined feature name"
118
+ end
119
+ end
120
+
121
+ describe 'when there are no undefined features' do
122
+ it 'does nothing' do
123
+ error = @error.string.dup
124
+ @reporter.report_undefined_steps
125
+
126
+ error.must_equal @error.string
127
+ end
128
+ end
129
+ end
130
+
131
+ describe '#report_errors' do
132
+ describe 'when some steps have raised an error' do
133
+ it 'outputs a the banner with the number of steps given' do
134
+ @reporter.report_errors('Banner', steps, :blue)
135
+
136
+ @error.string.must_include 'Banner (1)'
137
+ end
138
+
139
+ it 'prints a summarized error' do
140
+ @reporter.report_errors('Banner', steps, :blue)
141
+
142
+ @error.string.must_include 'My feature :: A scenario :: Keyword step name'
143
+ end
144
+
145
+ it 'colorizes the output' do
146
+ String.any_instance.expects(:colorize).with(:blue).at_least_once
147
+
148
+ @reporter.report_errors('Banner', [], :blue)
149
+ end
150
+ end
151
+ end
152
+
153
+ describe '#report_error' do
154
+ it 'raises when given an unsupported format' do
155
+ proc {
156
+ @reporter.report_error(error, :nil)
157
+ }.must_raise RuntimeError, 'Format not defined'
158
+ end
159
+
160
+ it 'prints a summarized error by default' do
161
+ @reporter.expects(:summarized_error).with(error).returns('summarized error')
162
+
163
+ @reporter.report_error(error)
164
+
165
+ @error.string.must_include 'summarized error'
166
+ end
167
+
168
+ it 'prints a summarized error' do
169
+ @reporter.expects(:summarized_error).with(error).returns('summarized error')
170
+
171
+ @reporter.report_error(error, :summarized)
172
+
173
+ @error.string.must_include 'summarized error'
174
+ end
175
+
176
+ it 'prints a full error' do
177
+ @reporter.expects(:full_error).with(error).returns('full error')
178
+
179
+ @reporter.report_error(error, :full)
180
+
181
+ @error.string.must_include 'full error'
182
+ end
183
+ end
184
+
185
+ describe '#summarized_error' do
186
+ it 'prints the error' do
187
+ summary = @reporter.summarized_error(error)
188
+
189
+ summary.must_include 'My feature :: A scenario :: Keyword step name'
190
+ end
191
+
192
+ it 'colorizes the print' do
193
+ String.any_instance.expects(:red)
194
+
195
+ @reporter.summarized_error(error)
196
+ end
197
+
198
+ describe 'when given an undefined step exception' do
199
+ it 'prints the error in yellow' do
200
+ undefined_error = error
201
+ undefined_error.insert(3, Spinach::StepNotDefinedException.new(anything))
202
+
203
+ String.any_instance.expects(:yellow)
204
+
205
+ @reporter.summarized_error(error)
206
+ end
207
+ end
208
+ end
209
+
210
+ describe '#full_error' do
211
+ before do
212
+ @reporter.expects(:report_exception).with(exception).returns('Exception backtrace')
213
+ end
214
+
215
+ it 'returns the exception data' do
216
+ exception.expects(:backtrace).returns(['first backtrace line'])
217
+ output = @reporter.full_error(error)
218
+
219
+ output.must_include 'Exception backtrace'
220
+ end
221
+
222
+ it 'returns the first backtrace line' do
223
+ exception.expects(:backtrace).returns(['first backtrace line'])
224
+ output = @reporter.full_error(error)
225
+
226
+ output.must_include 'first backtrace line'
227
+ end
228
+
229
+ describe 'when the user wants to print the full backtrace' do
230
+ it 'prints the full backtrace' do
231
+ @reporter.stubs(:options).returns({backtrace: true})
232
+ exception.expects(:backtrace).returns(['first backtrace line', 'second backtrace line'])
233
+
234
+ output = @reporter.full_error(error)
235
+
236
+ output.must_include 'first backtrace line'
237
+ output.must_include 'second backtrace line'
238
+ end
239
+ end
240
+ end
241
+
242
+ describe '#report_exception' do
243
+ it 'returns the exception data' do
244
+ output = @reporter.report_exception(exception)
245
+
246
+ output.must_include 'Something went wrong'
247
+ end
248
+
249
+ it 'colorizes the print' do
250
+ String.any_instance.expects(:red)
251
+
252
+ @reporter.report_exception(exception)
253
+ end
254
+
255
+ describe 'when given an undefined step exception' do
256
+ it 'prints the error in yellow' do
257
+ undefined_exception = Spinach::StepNotDefinedException.new(anything)
258
+
259
+ String.any_instance.expects(:yellow)
260
+
261
+ @reporter.report_exception(undefined_exception)
262
+ end
263
+ end
264
+ end
265
+ end