spinach 0.10.0 → 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
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