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