spinach 0.1.5.4 → 0.2.0.1

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 (69) hide show
  1. data/Gemfile +2 -0
  2. data/README.markdown +18 -12
  3. data/features/background.feature +13 -0
  4. data/features/reporting/show_step_source_location.feature +11 -1
  5. data/features/steps/automatic_feature_generation.rb +7 -7
  6. data/features/steps/background.rb +30 -0
  7. data/features/steps/exit_status.rb +12 -13
  8. data/features/steps/feature_name_guessing.rb +7 -7
  9. data/features/steps/reporting/display_run_summary.rb +22 -22
  10. data/features/steps/reporting/error_reporting.rb +7 -7
  11. data/features/steps/reporting/show_step_source_location.rb +63 -14
  12. data/features/steps/reporting/undefined_feature_reporting.rb +7 -7
  13. data/features/steps/rspec_compatibility.rb +14 -14
  14. data/features/support/error_reporting.rb +5 -5
  15. data/features/support/filesystem.rb +71 -0
  16. data/features/support/spinach_runner.rb +5 -6
  17. data/lib/spinach.rb +11 -6
  18. data/lib/spinach/background.rb +11 -0
  19. data/lib/spinach/capybara.rb +7 -1
  20. data/lib/spinach/cli.rb +36 -13
  21. data/lib/spinach/config.rb +40 -6
  22. data/lib/spinach/dsl.rb +14 -11
  23. data/lib/spinach/exceptions.rb +1 -1
  24. data/lib/spinach/feature.rb +16 -0
  25. data/lib/spinach/frameworks.rb +2 -0
  26. data/lib/spinach/{suites → frameworks}/minitest.rb +0 -0
  27. data/lib/spinach/{suites → frameworks}/rspec.rb +0 -0
  28. data/lib/spinach/generators/feature_generator.rb +12 -23
  29. data/lib/spinach/generators/step_generator.rb +5 -5
  30. data/lib/spinach/hookable.rb +6 -4
  31. data/lib/spinach/hooks.rb +12 -4
  32. data/lib/spinach/parser.rb +6 -8
  33. data/lib/spinach/parser/visitor.rb +109 -0
  34. data/lib/spinach/reporter.rb +10 -6
  35. data/lib/spinach/reporter/stdout.rb +41 -16
  36. data/lib/spinach/reporter/stdout/error_reporting.rb +2 -2
  37. data/lib/spinach/runner.rb +9 -6
  38. data/lib/spinach/runner/feature_runner.rb +40 -34
  39. data/lib/spinach/runner/scenario_runner.rb +63 -36
  40. data/lib/spinach/scenario.rb +12 -0
  41. data/lib/spinach/step.rb +10 -0
  42. data/lib/spinach/version.rb +1 -1
  43. data/spinach.gemspec +3 -3
  44. data/test/spinach/background_test.rb +6 -0
  45. data/test/spinach/capybara_test.rb +30 -13
  46. data/test/spinach/cli_test.rb +46 -1
  47. data/test/spinach/config_test.rb +39 -0
  48. data/test/spinach/dsl_test.rb +12 -10
  49. data/test/spinach/feature_steps_test.rb +3 -3
  50. data/test/spinach/feature_test.rb +6 -0
  51. data/test/spinach/{suites → frameworks}/minitest_test.rb +2 -2
  52. data/test/spinach/generators/feature_generator_test.rb +18 -58
  53. data/test/spinach/generators/step_generator_test.rb +3 -3
  54. data/test/spinach/generators_test.rb +12 -10
  55. data/test/spinach/hookable_test.rb +8 -0
  56. data/test/spinach/hooks_test.rb +6 -7
  57. data/test/spinach/parser/visitor_test.rb +173 -0
  58. data/test/spinach/parser_test.rb +14 -27
  59. data/test/spinach/reporter/stdout/error_reporting_test.rb +9 -9
  60. data/test/spinach/reporter/stdout_test.rb +15 -19
  61. data/test/spinach/reporter_test.rb +15 -0
  62. data/test/spinach/runner/feature_runner_test.rb +79 -69
  63. data/test/spinach/runner/scenario_runner_test.rb +118 -92
  64. data/test/spinach/runner_test.rb +10 -6
  65. data/test/spinach/scenario_test.rb +6 -0
  66. data/test/spinach/step_test.rb +6 -0
  67. data/test/spinach_test.rb +7 -7
  68. metadata +60 -39
  69. data/lib/spinach/suites.rb +0 -2
@@ -36,7 +36,7 @@ module Spinach
36
36
  if undefined_features.any?
37
37
  error.puts " Undefined features (#{undefined_features.length})".light_yellow
38
38
  undefined_features.each do |feature|
39
- error.puts " #{feature['name']}".yellow
39
+ error.puts " #{feature.name}".yellow
40
40
  end
41
41
  end
42
42
  end
@@ -93,7 +93,7 @@ module Spinach
93
93
  #
94
94
  def summarized_error(error)
95
95
  feature, scenario, step, exception = error
96
- summary = " #{feature['name']} :: #{scenario['name']} :: #{full_step step}"
96
+ summary = " #{feature.name} :: #{scenario.name} :: #{full_step step}"
97
97
  if exception.kind_of?(Spinach::StepNotDefinedException)
98
98
  summary.yellow
99
99
  else
@@ -54,14 +54,17 @@ module Spinach
54
54
  # @api public
55
55
  def run
56
56
  require_dependencies
57
- require_suites
57
+ require_frameworks
58
58
 
59
59
  Spinach.hooks.run_before_run
60
60
 
61
61
  successful = true
62
62
 
63
- filenames.each do |filename|
64
- success = FeatureRunner.new(filename).run
63
+ filenames.map do |filename|
64
+ filename.split(':')
65
+ end.each do |filename, line|
66
+ feature = Parser.open_file(filename).parse
67
+ success = FeatureRunner.new(feature, line).run
65
68
  successful = false unless success
66
69
  end
67
70
 
@@ -80,10 +83,10 @@ module Spinach
80
83
  end
81
84
  end
82
85
 
83
- # Requires the test suite support
86
+ # Requires the test framework support
84
87
  #
85
- def require_suites
86
- require_relative 'suites'
88
+ def require_frameworks
89
+ require_relative 'frameworks'
87
90
  end
88
91
 
89
92
  # @return [Array<String>] files
@@ -3,30 +3,19 @@ module Spinach
3
3
  # A feature runner handles a particular feature run.
4
4
  #
5
5
  class FeatureRunner
6
+ attr_reader :feature
6
7
 
7
- # The file that describes the feature.
8
- attr_reader :filename
9
-
10
- # @param [String] filename
11
- # path to the feature file. Scenario line could be passed to run just
12
- # that scenario.
13
- # @example feature/a_cool_feature.feature:12
14
- #
15
- # @api public
16
- def initialize(filename)
17
- @filename, @scenario_line = filename.split(':')
18
- end
19
-
20
- # The file taht describes the feature.
8
+ # @param [Gherkin::AST::Feature] feature
9
+ # The feature to run.
21
10
  #
22
- attr_reader :filename
23
-
24
- # @return [Hash]
25
- # The parsed data for this feature.
11
+ # @param [#to_i] line
12
+ # If a scenario line is passed, then only the scenario defined on that
13
+ # line will be run.
26
14
  #
27
15
  # @api public
28
- def data
29
- @data ||= Spinach::Parser.open_file(filename).parse
16
+ def initialize(feature, line=nil)
17
+ @feature = feature
18
+ @line = line.to_i if line
30
19
  end
31
20
 
32
21
  # @return [String]
@@ -34,15 +23,15 @@ module Spinach
34
23
  #
35
24
  # @api public
36
25
  def feature_name
37
- @feature_name ||= data['name']
26
+ @feature.name
38
27
  end
39
28
 
40
- # @return [Hash]
29
+ # @return [Array<Gherkin::AST::Scenario>]
41
30
  # The parsed scenarios for this runner's feature.
42
31
  #
43
32
  # @api public
44
33
  def scenarios
45
- @scenarios ||= (data['elements'] || [])
34
+ @feature.scenarios
46
35
  end
47
36
 
48
37
  # Runs this feature.
@@ -52,21 +41,38 @@ module Spinach
52
41
  #
53
42
  # @api public
54
43
  def run
55
- Spinach.hooks.run_before_feature data
56
- if Spinach.find_feature_steps(feature_name)
57
- scenarios.each do |scenario|
58
- if !@scenario_line || scenario['line'].to_s == @scenario_line
59
- success = ScenarioRunner.new(feature_name, scenario).run
60
- @failed = true unless success
61
- end
62
- end
44
+ Spinach.hooks.run_before_feature @feature
45
+ if Spinach.find_step_definitions(feature_name)
46
+ run_scenarios!
63
47
  else
64
- Spinach.hooks.run_on_undefined_feature data
65
- @failed = true
48
+ undefined_steps!
66
49
  end
67
- Spinach.hooks.run_after_feature data
50
+ Spinach.hooks.run_after_feature @feature
68
51
  !@failed
69
52
  end
53
+
54
+ private
55
+
56
+ def run_scenarios!
57
+ scenarios.each_with_index do |scenario, current_scenario_index|
58
+ if match(current_scenario_index)
59
+ success = ScenarioRunner.new(scenario).run
60
+ @failed = true unless success
61
+ end
62
+ end
63
+ end
64
+
65
+ def match(current_scenario_index)
66
+ return true unless @line
67
+ return false if @line<scenarios[current_scenario_index].line
68
+ next_scenario = scenarios[current_scenario_index+1]
69
+ !next_scenario || @line<next_scenario.line
70
+ end
71
+
72
+ def undefined_steps!
73
+ Spinach.hooks.run_on_undefined_feature @feature
74
+ @failed = true
75
+ end
70
76
  end
71
77
  end
72
78
  end
@@ -3,61 +3,88 @@ module Spinach
3
3
  # A Scenario Runner handles a particular scenario run.
4
4
  #
5
5
  class ScenarioRunner
6
- attr_reader :feature_name, :data
7
-
8
- # @param [String] feature_name
9
- # The feature name
6
+ # @param [Gherkin::AST::Scenario] scenario
7
+ # The scenario.
10
8
  #
11
- # @param [Hash] data
12
- # The parsed feature data.
9
+ # @api public
10
+ def initialize(scenario)
11
+ @scenario = scenario
12
+ end
13
+
14
+ # @return [Gherkin::AST::Feature>]
15
+ # The feature containing the scenario.
13
16
  #
14
17
  # @api public
15
- def initialize(feature_name, data)
16
- @feature_name = feature_name
17
- @data = data
18
+ def feature
19
+ @scenario.feature
18
20
  end
19
21
 
22
+ # @return [Array<Gherkin::AST::Step>]
23
+ # An array of steps.
24
+ #
25
+ # @api public
20
26
  def steps
21
- @steps ||= data['steps']
27
+ feature.background_steps + @scenario.steps
22
28
  end
23
29
 
24
30
  # @return [FeatureSteps]
25
- # The feature object used to run this scenario.
31
+ # The step definitions for the current feature.
26
32
  #
27
33
  # @api public
28
- def feature_steps
29
- @feature_steps ||= Spinach.find_feature_steps(feature_name).new
34
+ def step_definitions
35
+ @step_definitions ||= Spinach.find_step_definitions(feature.name).new
30
36
  end
31
37
 
32
- # Runs this scenario
33
- # @return [True, False]
34
- # true if this scenario succeeded, false if not
38
+ # Runs the scenario, capturing any exception, and running the
39
+ # corresponding hooks.
40
+ #
41
+ # @return [true, false]
42
+ # Whether the scenario succeeded or not.
43
+ #
44
+ # @api public
35
45
  def run
36
- Spinach.hooks.run_before_scenario data
37
- steps.each do |step|
38
- Spinach.hooks.run_before_step step
39
- unless @exception
40
- begin
41
- step_location = feature_steps.execute_step(step['name'])
42
- Spinach.hooks.run_on_successful_step step, step_location
43
- rescue *Spinach.config[:failure_exceptions] => e
44
- @exception = e
45
- Spinach.hooks.run_on_failed_step step, @exception, step_location
46
- rescue Spinach::StepNotDefinedException => e
47
- @exception = e
48
- Spinach.hooks.run_on_undefined_step step, @exception
49
- rescue Exception => e
50
- @exception = e
51
- Spinach.hooks.run_on_error_step step, @exception, step_location
46
+ Spinach.hooks.run_before_scenario @scenario
47
+ scenario_run = false
48
+ Spinach.hooks.run_around_scenario @scenario do
49
+ scenario_run = true
50
+ steps.each do |step|
51
+ Spinach.hooks.run_before_step step
52
+
53
+ if @exception
54
+ Spinach.hooks.run_on_skipped_step step
55
+ else
56
+ run_step(step)
52
57
  end
53
- else
54
- Spinach.hooks.run_on_skipped_step step
58
+
59
+ Spinach.hooks.run_after_step step
55
60
  end
56
- Spinach.hooks.run_after_step step
57
61
  end
58
- Spinach.hooks.run_after_scenario data
62
+ raise "around_scenario hooks *must* yield" if !scenario_run && !@exception
63
+ Spinach.hooks.run_after_scenario @scenario
59
64
  !@exception
60
65
  end
66
+
67
+ # Runs a particular step.
68
+ #
69
+ # @param [Gherkin::AST::Step] step
70
+ # The step to be run.
71
+ #
72
+ # @api semipublic
73
+ def run_step(step)
74
+ step_location = step_definitions.step_location_for(step.name)
75
+ step_definitions.execute(step)
76
+ Spinach.hooks.run_on_successful_step step, step_location
77
+ rescue *Spinach.config[:failure_exceptions] => e
78
+ @exception = e
79
+ Spinach.hooks.run_on_failed_step step, @exception, step_location
80
+ rescue Spinach::StepNotDefinedException => e
81
+ @exception = e
82
+ Spinach.hooks.run_on_undefined_step step, @exception
83
+ rescue Exception => e
84
+ @exception = e
85
+ Spinach.hooks.run_on_error_step step, @exception, step_location
86
+ end
87
+
61
88
  end
62
89
  end
63
90
  end
@@ -0,0 +1,12 @@
1
+ module Spinach
2
+ class Scenario
3
+ attr_accessor :line
4
+ attr_accessor :name, :steps, :tags, :feature
5
+
6
+ def initialize(feature)
7
+ @feature = feature
8
+ @steps = []
9
+ @tags = []
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,10 @@
1
+ module Spinach
2
+ class Step
3
+ attr_accessor :line
4
+ attr_accessor :name, :keyword, :scenario
5
+
6
+ def initialize(scenario)
7
+ @scenario = scenario
8
+ end
9
+ end
10
+ end
@@ -1,4 +1,4 @@
1
1
  module Spinach
2
2
  # Spinach version.
3
- VERSION = "0.1.5.4"
3
+ VERSION = "0.2.0.1"
4
4
  end
data/spinach.gemspec CHANGED
@@ -9,18 +9,18 @@ Gem::Specification.new do |gem|
9
9
  gem.summary = %q{Spinach is a BDD framework on top of gherkin}
10
10
  gem.homepage = "http://github.com/codegram/spinach"
11
11
 
12
- gem.add_runtime_dependency 'gherkin', '2.5.4'
12
+ gem.add_runtime_dependency 'gherkin-ruby', '~> 0.0.2'
13
13
  gem.add_runtime_dependency 'colorize'
14
14
  gem.add_development_dependency 'rake'
15
15
  gem.add_development_dependency 'mocha'
16
16
  gem.add_development_dependency 'sinatra'
17
17
  gem.add_development_dependency 'capybara'
18
- gem.add_development_dependency 'aruba'
18
+ gem.add_development_dependency 'open4'
19
19
  gem.add_development_dependency 'pry'
20
20
  gem.add_development_dependency 'simplecov'
21
21
  gem.add_development_dependency 'rspec'
22
22
  gem.add_development_dependency 'fakefs'
23
- gem.add_development_dependency 'minitest'
23
+ gem.add_development_dependency 'minitest', '~> 2.8.0'
24
24
  gem.add_development_dependency 'turn'
25
25
 
26
26
  gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
@@ -0,0 +1,6 @@
1
+ require 'test_helper'
2
+
3
+ module Spinach
4
+ describe Background do
5
+ end
6
+ end
@@ -27,6 +27,19 @@ describe Spinach::FeatureSteps::Capybara do
27
27
  end.new
28
28
  end
29
29
 
30
+ let(:parsed_feature) { Spinach::Parser.new("""
31
+ Feature: A test feature
32
+ Scenario: A test scenario
33
+ Given Hello
34
+ Then Goodbye
35
+
36
+ @javascript
37
+ Scenario: Another test scenario
38
+ Given Hello
39
+ Then Goodbye
40
+ """).parse
41
+ }
42
+
30
43
  it 'includes capybara into all features' do
31
44
  @feature.kind_of? Capybara
32
45
  end
@@ -37,22 +50,26 @@ describe Spinach::FeatureSteps::Capybara do
37
50
  end
38
51
 
39
52
  it 'resets the capybara session after each scenario' do
40
- @feature_runner = Spinach::Runner::FeatureRunner.new(
41
- 'a_feature.feature')
53
+ @feature_runner = Spinach::Runner::FeatureRunner.new(parsed_feature)
42
54
 
43
- @feature_runner.stubs(data: Spinach::Parser.new('
44
- Feature: A test feature
45
- Scenario: A test scenario
46
- Given Hello
47
- Then Goodbye
48
- Scenario: Another test scenario
49
- Given Hello
50
- Then Goodbye
51
- ').parse).at_least_once
55
+ Capybara.current_session.expects(:reset!).at_least_once
52
56
 
53
- Spinach::Runner::ScenarioRunner.any_instance.stubs(feature_steps: @feature)
57
+ @feature_runner.run
58
+ end
54
59
 
55
- Capybara.current_session.expects(:reset!).at_least_once
60
+ it 'resets the javascript driver after each scenario' do
61
+ @feature_runner = Spinach::Runner::FeatureRunner.new(parsed_feature)
62
+
63
+ Capybara.expects(:use_default_driver).at_least(2)
64
+
65
+ @feature_runner.run
66
+ end
67
+
68
+ it 'changes the javascript driver when an scenario has the @javascript tag' do
69
+ @feature_runner = Spinach::Runner::FeatureRunner.new(parsed_feature)
70
+
71
+ Capybara.expects(:javascript_driver).at_least(1)
72
+ Capybara.expects(:current_driver=).at_least(1)
56
73
 
57
74
  @feature_runner.run
58
75
  end
@@ -18,7 +18,19 @@ describe Spinach::Cli do
18
18
  end
19
19
  end
20
20
 
21
- describe 'backtrace' do
21
+ describe 'features_path' do
22
+ %w{-f --features_path}.each do |opt|
23
+ it 'sets the given features_path' do
24
+ config = Spinach::Config.new
25
+ Spinach.stubs(:config).returns(config)
26
+ cli = Spinach::Cli.new([opt,"custom_path"])
27
+ cli.options
28
+ config.features_path.must_equal 'custom_path'
29
+ end
30
+ end
31
+ end
32
+
33
+ describe 'generate' do
22
34
  %w{-g --generate}.each do |opt|
23
35
  it 'inits the generator if #{opt}' do
24
36
  Spinach::Generators.expects(:bind)
@@ -40,6 +52,39 @@ describe Spinach::Cli do
40
52
  end
41
53
  end
42
54
  end
55
+
56
+ describe 'config_path' do
57
+ %w{-c --config_path}.each do |opt|
58
+ it "sets the config path" do
59
+ config = Spinach::Config.new
60
+ Spinach.stubs(:config).returns(config)
61
+ cli = Spinach::Cli.new([opt, 'config_file'])
62
+ config.expects(:parse_from_file)
63
+ cli.options
64
+ config.config_path.must_equal 'config_file'
65
+ end
66
+
67
+ it "gets overriden by the other cli options" do
68
+ config = Spinach::Config.new
69
+ Spinach.stubs(:config).returns(config)
70
+ YAML.stubs(:load_file).returns({features_path: 'my_path'})
71
+ cli = Spinach::Cli.new(['-f', 'another_path', opt, 'config_file'])
72
+ cli.options
73
+ config.features_path.must_equal 'another_path'
74
+ end
75
+ end
76
+ end
77
+
78
+ describe 'undefined option' do
79
+ %w{-lorem --ipsum}.each do |opt|
80
+ it 'exits and outputs error message with #{opt}' do
81
+ cli = Spinach::Cli.new([opt])
82
+ cli.expects(:exit)
83
+ cli.expects(:puts).with("Invalid option: #{opt}")
84
+ options = cli.options
85
+ end
86
+ end
87
+ end
43
88
  end
44
89
 
45
90
  describe '#init_reporter' do