spinach 0.1.4 → 0.1.5

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