spinach 0.10.0 → 0.10.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b3695a752a99edd4e91fd72df976576aef38a93a
4
- data.tar.gz: 8148e0be9f45bd1808f0091edd87630967db287e
3
+ metadata.gz: 1f75603856e6e68fb9f7eb16a287062659034e02
4
+ data.tar.gz: a6ea70aaa3ef522f10e6b5c729671c94c84bc67c
5
5
  SHA512:
6
- metadata.gz: f57752557ea737923da6ab5356f7e76c1374590bc0c16dc22142cad4355f17513dac875f13e833dbecb126cb69e7b0515afc3306c2dcd651e6eb0a660484e7b1
7
- data.tar.gz: 7452e29f5d8c40f6ba0883c27fbc6ddb93fa24aa53a05ab36a9313765abc651546cc33897c01a4b82177829a0a587bfaaa7ca2a4ba47f8bd3d886fd17c459690
6
+ metadata.gz: c5f580a7a0bc2c9f4ce8901b78f1f63da2a72d14f8233ade3ebd423f6bf0989441b318970266610422b6e53de854fd6cf1ce868f53e80064f6606396d9c39c3f
7
+ data.tar.gz: 349c9aa0672871576ea8ff8f86944c24816048ccbee2ca3a360b446feed8d6ede0ab2671a8db2505ea4cfd80a0f3e5e66f55578e88d69db215981d37970c06a3
data/Gemfile CHANGED
@@ -4,6 +4,7 @@ source 'http://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  gem 'coveralls', require: false
7
+ gem 'pry-byebug', platforms: [:ruby]
7
8
 
8
9
  group :docs do
9
10
  gem 'yard'
data/README.markdown CHANGED
@@ -310,6 +310,14 @@ class Spinach::Features::SessionTimeout < Spinach::FeatureSteps
310
310
  end
311
311
  ```
312
312
 
313
+ ## RSpec mocks
314
+
315
+ If you need access to the [rspec-mocks](https://github.com/rspec/rspec-mocks) methods in your steps, add this line to your `env.rb`:
316
+
317
+ ```ruby
318
+ require 'spinach/rspec/mocks'
319
+ ```
320
+
313
321
  ## Reporters
314
322
 
315
323
  Spinach supports two kinds of reporters by default: `stdout` and `progress`.
@@ -7,3 +7,9 @@ Feature: Use customized reporter
7
7
  Given I have a feature that has no error or failure
8
8
  When I run it using the new reporter
9
9
  Then I see the desired output
10
+
11
+ Scenario: Multiple reporters
12
+ Given I have a feature that has one failure
13
+ When I run it using two custom reporters
14
+ Then I see one reporter's output on the screen
15
+ And I see the other reporter's output in a file
@@ -0,0 +1,24 @@
1
+ Feature: Running Specific Scenarios
2
+ In order to test only specific scenarios
3
+ As a developer
4
+ I want spinach to run only the scenarios I specify
5
+
6
+ Scenario: Specifying line numbers
7
+ Given I have a feature with 2 scenarios
8
+ When I specify that only the second should be run
9
+ Then One will succeed and none will fail
10
+
11
+ Scenario: Including tags
12
+ Given I have a tagged feature with 2 scenarios
13
+ When I include the tag of the failing scenario
14
+ Then None will succeed and one will fail
15
+
16
+ Scenario: Excluding tags
17
+ Given I have a tagged feature with 2 scenarios
18
+ When I exclude the tag of the passing scenario
19
+ Then None will succeed and one will fail
20
+
21
+ Scenario: Combining tags
22
+ Given I have a tagged feature with 2 scenarios
23
+ When I include the tag of the feature and exclude the tag of the failing scenario
24
+ Then One will succeed and none will fail
@@ -1,3 +1,5 @@
1
+ require 'securerandom'
2
+
1
3
  class UseCustomizedReporter < Spinach::FeatureSteps
2
4
 
3
5
  feature "Use customized reporter"
@@ -88,6 +90,28 @@ class ASuccessFeature < Spinach::FeatureSteps
88
90
  @feature = "features/success_feature.feature"
89
91
  end
90
92
 
93
+ Given 'I have a feature that has one failure' do
94
+ write_file('features/failure_feature.feature', """
95
+ Feature: A failure feature
96
+
97
+ Scenario: This is scenario will fail
98
+ Then I fail
99
+ """)
100
+
101
+ write_file('features/steps/failure_feature.rb',
102
+ <<-EOF
103
+ require_relative "../../test_reporter"
104
+ class AFailureFeature < Spinach::FeatureSteps
105
+ feature "A failure feature"
106
+ Then "I fail" do
107
+ assert false
108
+ end
109
+ end
110
+ EOF
111
+ )
112
+ @feature = "features/failure_feature.feature"
113
+ end
114
+
91
115
  When 'I run it using the new reporter' do
92
116
  run_feature @feature, append: "-r test_reporter"
93
117
  end
@@ -95,4 +119,19 @@ class ASuccessFeature < Spinach::FeatureSteps
95
119
  Then 'I see the desired output' do
96
120
  @stdout.must_include("The customized class")
97
121
  end
122
+
123
+ When 'I run it using two custom reporters' do
124
+ @failure_filename = "tmp/custom-reporter-#{SecureRandom.hex}.txt"
125
+ @expected_path = "tmp/fs/#{@failure_filename}"
126
+ run_feature @feature, append: "-r failure_file,test_reporter", env: { 'SPINACH_FAILURE_FILE' => @failure_filename }
127
+ end
128
+
129
+ Then 'I see one reporter\'s output on the screen' do
130
+ @stdout.must_include("The customized class")
131
+ end
132
+
133
+ Then 'I see the other reporter\'s output in a file' do
134
+ assert File.exist?(@expected_path), "Reporter should have created an output file: #{@expected_path}"
135
+ File.open(@expected_path).read.must_equal "features/failure_feature.feature:4"
136
+ end
98
137
  end
@@ -0,0 +1,46 @@
1
+ class Spinach::Features::RunningSpecificScenarios < Spinach::FeatureSteps
2
+ include Integration::SpinachRunner
3
+
4
+ Given 'I have a feature with 2 scenarios' do
5
+ @feature = Integration::FeatureGenerator.failure_feature_with_two_scenarios
6
+ end
7
+
8
+ When 'I specify that only the second should be run' do
9
+ feature_file = Pathname.new(Filesystem.dirs.first).join(@feature)
10
+ step_lines = feature_file.each_line.with_index.select do |line, index|
11
+ line.match(/^\s*Then/)
12
+ end
13
+
14
+ line_number = step_lines[1].last
15
+
16
+ run_feature("#{@feature}:#{line_number}")
17
+ end
18
+
19
+ Then 'One will succeed and none will fail' do
20
+ @stdout.must_match("(1) Successful")
21
+ @stdout.must_match("(0) Failed")
22
+ @stdout.must_match("(0) Pending")
23
+ end
24
+
25
+ Given 'I have a tagged feature with 2 scenarios' do
26
+ @feature = Integration::FeatureGenerator.tagged_failure_feature_with_two_scenarios
27
+ end
28
+
29
+ When 'I include the tag of the failing scenario' do
30
+ run_feature(@feature, {append: "-t @failing"})
31
+ end
32
+
33
+ Then 'None will succeed and one will fail' do
34
+ @stdout.must_match("(0) Successful")
35
+ @stdout.must_match("(1) Failed")
36
+ @stdout.must_match("(0) Pending")
37
+ end
38
+
39
+ When 'I exclude the tag of the passing scenario' do
40
+ run_feature(@feature, {append: "-t ~@passing"})
41
+ end
42
+
43
+ When 'I include the tag of the feature and exclude the tag of the failing scenario' do
44
+ run_feature(@feature, {append: "-t @feature,~@failing"})
45
+ end
46
+ end
@@ -1,7 +1,6 @@
1
1
  require 'minitest/autorun'
2
2
  require 'minitest/spec'
3
3
  require_relative 'filesystem'
4
-
5
4
  require 'simplecov'
6
5
 
7
6
  if ENV['CI'] && !defined?(Rubinius)
@@ -10,8 +10,8 @@ module Integration
10
10
  #
11
11
  # @api private
12
12
  def success_feature
13
- feature= success_scenario_title + success_scenario
14
- steps = success_step_class_str + success_step + "\nend"
13
+ feature = success_scenario_title + success_scenario
14
+ steps = success_step_class_str + success_step + "\nend"
15
15
  write_feature 'features/success_feature.feature', feature,
16
16
  'features/steps/success_feature.rb', steps
17
17
  end
@@ -37,12 +37,37 @@ module Integration
37
37
  #
38
38
  # @api private
39
39
  def failure_feature_with_two_scenarios
40
- feature = failure_feature_title + failure_sceario + success_scenario
40
+ feature = failure_feature_title + failure_scenario + success_scenario
41
41
  steps = failure_step + success_step + "\nend"
42
42
  write_feature failure_filename, feature,
43
43
  failure_step_filename, steps
44
44
  end
45
45
 
46
+ # Generate a tagged feature that has 2 tagged scenarios.
47
+ # 1 should pass, and 1 should fail.
48
+ #
49
+ # @return feature_filename
50
+ # The feature file name
51
+ #
52
+ # @api private
53
+ def tagged_failure_feature_with_two_scenarios
54
+ feature = "@feature\n" +
55
+ failure_feature_title +
56
+ " @failing" +
57
+ failure_scenario + "\n" +
58
+ " @passing\n" +
59
+ " " + success_scenario
60
+
61
+ steps = failure_step +
62
+ success_step + "\n" +
63
+ "end"
64
+
65
+ write_feature(
66
+ failure_filename, feature,
67
+ failure_step_filename, steps
68
+ )
69
+ end
70
+
46
71
  # Generate a feature with 1 scenario that should fail
47
72
  #
48
73
  # @return feature_filename
@@ -50,7 +75,7 @@ module Integration
50
75
  #
51
76
  # @api private
52
77
  def failure_feature
53
- feature = failure_feature_title + failure_sceario
78
+ feature = failure_feature_title + failure_scenario
54
79
  write_feature failure_filename, feature,
55
80
  failure_step_filename, failure_step + "\nend"
56
81
  end
@@ -141,11 +166,10 @@ module Integration
141
166
  "Feature: A failure feature\n\n"
142
167
  end
143
168
 
144
- def failure_sceario
145
- """
169
+ def failure_scenario
170
+ "
146
171
  Scenario: This is scenario will fail
147
- Then I fail
148
- """
172
+ Then I fail\n"
149
173
  end
150
174
  end
151
175
  end
@@ -46,11 +46,14 @@ module Filesystem
46
46
  #
47
47
  # @param [String] command
48
48
  # The command to run.
49
+ # @param [Hash] env
50
+ # Hash of environment variables to use with command
49
51
  #
50
52
  # @api public
51
- def run(command)
53
+ def run(command, env = nil)
52
54
  in_current_dir do
53
55
  args = command.strip.split(" ")
56
+ args = args.unshift(env) if env
54
57
  @stdout, @stderr, @last_exit_status = Open3.capture3(*args)
55
58
  end
56
59
 
@@ -20,7 +20,7 @@ module Integration
20
20
  use_minitest if options[:framework] == :minitest
21
21
  use_rspec if options[:framework] == :rspec
22
22
  spinach = File.expand_path("bin/spinach")
23
- run "#{ruby} #{spinach} #{feature} #{options[:append]}"
23
+ run "#{ruby} #{spinach} #{feature} #{options[:append]}", options[:env]
24
24
  end
25
25
 
26
26
  def ruby
data/lib/spinach/cli.rb CHANGED
@@ -125,9 +125,10 @@ module Spinach
125
125
  config[:features_path] = path
126
126
  end
127
127
 
128
- opts.on('-r', '--reporter CLASS_NAME',
129
- 'Formatter class name') do |class_name|
130
- config[:reporter_class] = reporter_class(class_name)
128
+ opts.on('-r', '--reporter CLASS_NAMES',
129
+ 'Formatter class names, separated by commas') do |class_names|
130
+ names = class_names.split(',').map { |c| reporter_class(c) }
131
+ config[:reporter_classes] = names
131
132
  end
132
133
 
133
134
  opts.on_tail('--fail-fast',
@@ -31,7 +31,7 @@ module Spinach
31
31
  :tags,
32
32
  :generate,
33
33
  :save_and_open_page_on_failure,
34
- :reporter_class,
34
+ :reporter_classes,
35
35
  :reporter_options,
36
36
  :fail_fast,
37
37
  :audit
@@ -48,18 +48,18 @@ module Spinach
48
48
  @features_path || 'features'
49
49
  end
50
50
 
51
- # The "reporter class" holds the reporter class name
52
- # Default to Spinach::Reporter::Stdout
51
+ # The "reporter classes" holds an array of reporter class name
52
+ # Default to ["Spinach::Reporter::Stdout"]
53
53
  #
54
- # @return [reporter object]
55
- # The reporter that responds to specific messages.
54
+ # @return [Array<reporter object>]
55
+ # The reporters that respond to specific messages.
56
56
  #
57
57
  # @api public
58
- def reporter_class
59
- @reporter_class || "Spinach::Reporter::Stdout"
58
+ def reporter_classes
59
+ @reporter_classes || ["Spinach::Reporter::Stdout"]
60
60
  end
61
61
 
62
- # The "reporter_options" holds the options of reporter_class
62
+ # The "reporter_options" holds the options passed to reporter_classes
63
63
  #
64
64
  # @api public
65
65
  def reporter_options
@@ -22,6 +22,9 @@ module Spinach
22
22
  end
23
23
  end
24
24
 
25
+ # This class represents the exception raised when Spinach find a step
26
+ # which claims to be pending for a {FeatureSteps}.
27
+ #
25
28
  class StepPendingException < StandardError
26
29
  attr_reader :reason
27
30
  attr_accessor :step
@@ -42,4 +45,27 @@ module Spinach
42
45
  "Step '#{@step.name}' pending"
43
46
  end
44
47
  end
48
+
49
+ # This class represents the exception raised when Spinach detects
50
+ # that the around_scenario hook does not yield.
51
+ #
52
+ class HookNotYieldException < StandardError
53
+ attr_reader :hook
54
+
55
+ # @param [String] hook
56
+ # The hook which did not yield
57
+ #
58
+ # @api public
59
+ def initialize(hook)
60
+ @hook = hook
61
+ end
62
+
63
+ # @return [String]
64
+ # A custom message when a hook did not yield.
65
+ #
66
+ # @api public
67
+ def message
68
+ "#{@hook} hooks *must* yield"
69
+ end
70
+ end
45
71
  end
@@ -1,21 +1,27 @@
1
1
  module Spinach
2
2
  class Feature
3
- attr_accessor :filename, :lines
3
+ attr_accessor :filename
4
4
  attr_accessor :name, :scenarios, :tags
5
5
  attr_accessor :background
6
6
  attr_accessor :description
7
+ attr_reader :lines_to_run
7
8
 
8
9
  def initialize
9
- @scenarios = []
10
- @tags = []
10
+ @scenarios = []
11
+ @tags = []
12
+ @lines_to_run = []
11
13
  end
12
14
 
13
15
  def background_steps
14
16
  @background.nil? ? [] : @background.steps
15
17
  end
16
18
 
17
- def lines=(value)
18
- @lines = value.map(&:to_i) if value
19
+ def lines_to_run=(lines)
20
+ @lines_to_run = lines.map(&:to_i) if lines && lines.any?
21
+ end
22
+
23
+ def run_every_scenario?
24
+ lines_to_run.empty?
19
25
  end
20
26
 
21
27
  # Run the provided code for every step
data/lib/spinach/hooks.rb CHANGED
@@ -57,6 +57,7 @@ module Spinach
57
57
  hook :before_scenario
58
58
 
59
59
  # Runs around every scenario
60
+ #
60
61
  # @example
61
62
  # Spinach.hooks.around_scenario do |scenario_data, step_definitions, &block|
62
63
  # # feature_data is a hash of the parsed scenario data
@@ -80,6 +81,15 @@ module Spinach
80
81
  # end
81
82
  hook :before_step
82
83
 
84
+ # Runs around every step
85
+ #
86
+ # @example
87
+ # Spinach.hooks.around_step do |step_data, step_definitions, &block|
88
+ # # step_data contains a hash with this step's data
89
+ # block.call
90
+ # end
91
+ around_hook :around_step
92
+
83
93
  # Runs after every step execution
84
94
  #
85
95
  # @example
@@ -5,7 +5,7 @@ module Spinach
5
5
  #
6
6
  # @example
7
7
  #
8
- # ast = GherkinRuby.parse(File.read('some.feature')
8
+ # ast = GherkinRuby.parse(File.read('some.feature'))
9
9
  # visitor = Spinach::Parser::Visitor.new
10
10
  # feature = visitor.visit(ast)
11
11
  #
@@ -25,7 +25,8 @@ module Spinach
25
25
  #
26
26
  # @api public
27
27
  def visit(ast)
28
- ast.accept self
28
+ ast.accept(self)
29
+
29
30
  @feature
30
31
  end
31
32
 
@@ -36,12 +37,13 @@ module Spinach
36
37
  #
37
38
  # @api public
38
39
  def visit_Feature(node)
39
- @feature.name = node.name
40
+ @feature.name = node.name
40
41
  @feature.description = node.description
42
+
41
43
  node.background.accept(self) if node.background
42
44
 
43
45
  @current_tag_set = @feature
44
- node.tags.each { |tag| tag.accept(self) }
46
+ node.tags.each { |tag| tag.accept(self) }
45
47
  @current_tag_set = nil
46
48
 
47
49
  node.scenarios.each { |scenario| scenario.accept(self) }
@@ -54,7 +56,7 @@ module Spinach
54
56
  #
55
57
  # @api public
56
58
  def visit_Background(node)
57
- background = Background.new(@feature)
59
+ background = Background.new(@feature)
58
60
  background.line = node.line
59
61
 
60
62
  @current_step_set = background
@@ -71,12 +73,15 @@ module Spinach
71
73
  #
72
74
  # @api public
73
75
  def visit_Scenario(node)
74
- scenario = Scenario.new(@feature)
75
- scenario.name = node.name
76
- scenario.line = node.line
76
+ scenario = Scenario.new(@feature)
77
+ scenario.name = node.name
78
+ scenario.lines = [
79
+ node.line,
80
+ *node.steps.map(&:line)
81
+ ].uniq.sort
77
82
 
78
83
  @current_tag_set = scenario
79
- node.tags.each { |tag| tag.accept(self) }
84
+ node.tags.each { |tag| tag.accept(self) }
80
85
  @current_tag_set = nil
81
86
 
82
87
  @current_step_set = scenario
@@ -103,7 +108,7 @@ module Spinach
103
108
  #
104
109
  # @api public
105
110
  def visit_Step(node)
106
- step = Step.new(@current_scenario)
111
+ step = Step.new(@current_step_set)
107
112
  step.name = node.name
108
113
  step.line = node.line
109
114
  step.keyword = node.keyword