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 +4 -4
- data/Gemfile +1 -0
- data/README.markdown +8 -0
- data/features/reporting/customized_reporter.feature +6 -0
- data/features/running_specific_scenarios.feature +24 -0
- data/features/steps/reporting/use_customized_reporter.rb +39 -0
- data/features/steps/running_specific_scenarios.rb +46 -0
- data/features/support/env.rb +0 -1
- data/features/support/feature_generator.rb +32 -8
- data/features/support/filesystem.rb +4 -1
- data/features/support/spinach_runner.rb +1 -1
- data/lib/spinach/cli.rb +4 -3
- data/lib/spinach/config.rb +8 -8
- data/lib/spinach/exceptions.rb +26 -0
- data/lib/spinach/feature.rb +11 -5
- data/lib/spinach/hooks.rb +10 -0
- data/lib/spinach/parser/visitor.rb +15 -10
- data/lib/spinach/reporter.rb +1 -0
- data/lib/spinach/reporter/failure_file.rb +59 -0
- data/lib/spinach/rspec/mocks.rb +18 -0
- data/lib/spinach/runner.rb +23 -32
- data/lib/spinach/runner/feature_runner.rb +31 -30
- data/lib/spinach/runner/scenario_runner.rb +9 -2
- data/lib/spinach/scenario.rb +2 -1
- data/lib/spinach/version.rb +1 -1
- data/test/spinach/cli_test.rb +9 -1
- data/test/spinach/config_test.rb +4 -4
- data/test/spinach/feature_test.rb +27 -0
- data/test/spinach/hooks_test.rb +18 -0
- data/test/spinach/parser/visitor_test.rb +7 -3
- data/test/spinach/reporter/failure_file_test.rb +84 -0
- data/test/spinach/runner/feature_runner_test.rb +66 -47
- data/test/spinach/runner/scenario_runner_test.rb +40 -3
- data/test/spinach/runner_test.rb +14 -14
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1f75603856e6e68fb9f7eb16a287062659034e02
|
4
|
+
data.tar.gz: a6ea70aaa3ef522f10e6b5c729671c94c84bc67c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c5f580a7a0bc2c9f4ce8901b78f1f63da2a72d14f8233ade3ebd423f6bf0989441b318970266610422b6e53de854fd6cf1ce868f53e80064f6606396d9c39c3f
|
7
|
+
data.tar.gz: 349c9aa0672871576ea8ff8f86944c24816048ccbee2ca3a360b446feed8d6ede0ab2671a8db2505ea4cfd80a0f3e5e66f55578e88d69db215981d37970c06a3
|
data/Gemfile
CHANGED
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
|
data/features/support/env.rb
CHANGED
@@ -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
|
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 +
|
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 +
|
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
|
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
|
129
|
-
'Formatter class
|
130
|
-
|
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',
|
data/lib/spinach/config.rb
CHANGED
@@ -31,7 +31,7 @@ module Spinach
|
|
31
31
|
:tags,
|
32
32
|
:generate,
|
33
33
|
:save_and_open_page_on_failure,
|
34
|
-
:
|
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
|
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
|
54
|
+
# @return [Array<reporter object>]
|
55
|
+
# The reporters that respond to specific messages.
|
56
56
|
#
|
57
57
|
# @api public
|
58
|
-
def
|
59
|
-
@
|
58
|
+
def reporter_classes
|
59
|
+
@reporter_classes || ["Spinach::Reporter::Stdout"]
|
60
60
|
end
|
61
61
|
|
62
|
-
# The "reporter_options" holds the options
|
62
|
+
# The "reporter_options" holds the options passed to reporter_classes
|
63
63
|
#
|
64
64
|
# @api public
|
65
65
|
def reporter_options
|
data/lib/spinach/exceptions.rb
CHANGED
@@ -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
|
data/lib/spinach/feature.rb
CHANGED
@@ -1,21 +1,27 @@
|
|
1
1
|
module Spinach
|
2
2
|
class Feature
|
3
|
-
attr_accessor :filename
|
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
|
18
|
-
@
|
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
|
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
|
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
|
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
|
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
|
75
|
-
scenario.name
|
76
|
-
scenario.
|
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
|
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
|
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
|