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
data/lib/spinach/reporter.rb
CHANGED
@@ -0,0 +1,59 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require_relative 'reporting'
|
3
|
+
require 'pp'
|
4
|
+
|
5
|
+
module Spinach
|
6
|
+
class Reporter
|
7
|
+
# The FailureFile reporter outputs failing scenarios to a temporary file, one per line.
|
8
|
+
#
|
9
|
+
class FailureFile < Reporter
|
10
|
+
|
11
|
+
# Initializes the output filename and the temporary directory.
|
12
|
+
#
|
13
|
+
def initialize(*args)
|
14
|
+
super(*args)
|
15
|
+
|
16
|
+
# Generate a unique filename for this test run, or use the supplied option
|
17
|
+
@filename = options[:failure_filename] || ENV['SPINACH_FAILURE_FILE'] || "tmp/spinach-failures_#{Time.now.strftime('%F_%H-%M-%S-%L')}.txt"
|
18
|
+
|
19
|
+
# Create the temporary directory where we will output our file, if necessary
|
20
|
+
Dir.mkdir('tmp', 0755) unless Dir.exist?('tmp')
|
21
|
+
|
22
|
+
# Collect an array of failing scenarios
|
23
|
+
@failing_scenarios = []
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :failing_scenarios, :filename
|
27
|
+
|
28
|
+
# Writes all failing scenarios to a file, unless our run was successful.
|
29
|
+
#
|
30
|
+
# @param [Boolean] success
|
31
|
+
# Indicates whether the entire test run was successful
|
32
|
+
#
|
33
|
+
def after_run(success)
|
34
|
+
# Save our failed scenarios to a file
|
35
|
+
File.open(@filename, 'w') { |f| f.write @failing_scenarios.join("\n") } unless success
|
36
|
+
end
|
37
|
+
|
38
|
+
# Adds a failing step to the output buffer.
|
39
|
+
#
|
40
|
+
def on_failed_step(*args)
|
41
|
+
add_scenario(current_feature.filename, current_scenario.lines[0])
|
42
|
+
end
|
43
|
+
|
44
|
+
# Adds a step that has raised an error to the output buffer.
|
45
|
+
#
|
46
|
+
def on_error_step(*args)
|
47
|
+
add_scenario(current_feature.filename, current_scenario.lines[0])
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# Adds a filename and line number to the output buffer, suitable for rerunning.
|
53
|
+
#
|
54
|
+
def add_scenario(filename, line)
|
55
|
+
@failing_scenarios << "#{filename}:#{line}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Inspired by cucumber/rspec/mocks
|
2
|
+
require 'rspec/mocks'
|
3
|
+
|
4
|
+
class Spinach::FeatureSteps
|
5
|
+
include RSpec::Mocks::ExampleMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
Spinach.hooks.before_scenario do
|
9
|
+
RSpec::Mocks.setup
|
10
|
+
end
|
11
|
+
|
12
|
+
Spinach.hooks.after_scenario do
|
13
|
+
begin
|
14
|
+
RSpec::Mocks.verify
|
15
|
+
ensure
|
16
|
+
RSpec::Mocks.teardown
|
17
|
+
end
|
18
|
+
end
|
data/lib/spinach/runner.rb
CHANGED
@@ -37,21 +37,14 @@ module Spinach
|
|
37
37
|
Spinach.config.support_path
|
38
38
|
end
|
39
39
|
|
40
|
-
# The feature files to run
|
41
|
-
attr_reader :filenames
|
42
|
-
|
43
|
-
# The default path where the steps are located
|
44
|
-
attr_reader :step_definitions_path
|
45
|
-
|
46
|
-
# The default path where the support files are located
|
47
|
-
attr_reader :support_path
|
48
|
-
|
49
40
|
# Inits the reporter with a default one.
|
50
41
|
#
|
51
42
|
# @api public
|
52
|
-
def
|
53
|
-
|
54
|
-
|
43
|
+
def init_reporters
|
44
|
+
Spinach.config[:reporter_classes].each do |reporter_class|
|
45
|
+
reporter = Support.constantize(reporter_class).new(Spinach.config.reporter_options)
|
46
|
+
reporter.bind
|
47
|
+
end
|
55
48
|
end
|
56
49
|
|
57
50
|
# Runs this runner and outputs the results in a colorful manner.
|
@@ -63,36 +56,34 @@ module Spinach
|
|
63
56
|
def run
|
64
57
|
require_dependencies
|
65
58
|
require_frameworks
|
66
|
-
|
59
|
+
init_reporters
|
67
60
|
|
68
|
-
|
61
|
+
features = filenames.map do |filename|
|
62
|
+
file, *lines = filename.split(":") # little more complex than just a "filename"
|
69
63
|
|
70
|
-
|
64
|
+
# FIXME Feature should be instantiated directly, not through an unrelated class method
|
65
|
+
feature = Parser.open_file(file).parse
|
66
|
+
feature.filename = file
|
71
67
|
|
72
|
-
|
73
|
-
|
74
|
-
|
68
|
+
feature.lines_to_run = lines if lines.any?
|
69
|
+
|
70
|
+
feature
|
75
71
|
end
|
76
72
|
|
77
|
-
|
78
|
-
|
79
|
-
|
73
|
+
suite_passed = true
|
74
|
+
|
75
|
+
Spinach.hooks.run_before_run
|
80
76
|
|
81
|
-
|
82
|
-
|
83
|
-
|
77
|
+
features.each do |feature|
|
78
|
+
feature_passed = FeatureRunner.new(feature).run
|
79
|
+
suite_passed &&= feature_passed
|
84
80
|
|
85
|
-
|
86
|
-
success = FeatureRunner.new(feature, line).run
|
87
|
-
successful = false unless success
|
88
|
-
throw :fail if fail_fast? && !successful
|
89
|
-
end
|
90
|
-
end
|
81
|
+
break if fail_fast? && !feature_passed
|
91
82
|
end
|
92
83
|
|
93
|
-
Spinach.hooks.run_after_run(
|
84
|
+
Spinach.hooks.run_after_run(suite_passed)
|
94
85
|
|
95
|
-
|
86
|
+
suite_passed
|
96
87
|
end
|
97
88
|
|
98
89
|
# Loads support files and step definitions, ensuring that env.rb is loaded
|
@@ -10,14 +10,9 @@ module Spinach
|
|
10
10
|
# @param [GherkinRuby::AST::Feature] feature
|
11
11
|
# The feature to run.
|
12
12
|
#
|
13
|
-
# @param [#to_i] line
|
14
|
-
# If a scenario line is passed, then only the scenario defined on that
|
15
|
-
# line will be run.
|
16
|
-
#
|
17
13
|
# @api public
|
18
|
-
def initialize(feature
|
14
|
+
def initialize(feature)
|
19
15
|
@feature = feature
|
20
|
-
@line = line.to_i if line
|
21
16
|
end
|
22
17
|
|
23
18
|
# @return [String]
|
@@ -25,7 +20,7 @@ module Spinach
|
|
25
20
|
#
|
26
21
|
# @api public
|
27
22
|
def feature_name
|
28
|
-
|
23
|
+
feature.name
|
29
24
|
end
|
30
25
|
|
31
26
|
# @return [Array<GherkinRuby::AST::Scenario>]
|
@@ -33,7 +28,7 @@ module Spinach
|
|
33
28
|
#
|
34
29
|
# @api public
|
35
30
|
def scenarios
|
36
|
-
|
31
|
+
feature.scenarios
|
37
32
|
end
|
38
33
|
|
39
34
|
# Runs this feature.
|
@@ -43,52 +38,58 @@ module Spinach
|
|
43
38
|
#
|
44
39
|
# @api public
|
45
40
|
def run
|
46
|
-
Spinach.hooks.run_before_feature
|
41
|
+
Spinach.hooks.run_before_feature(feature)
|
42
|
+
|
47
43
|
if Spinach.find_step_definitions(feature_name)
|
48
44
|
run_scenarios!
|
49
45
|
else
|
50
46
|
undefined_steps!
|
51
47
|
end
|
52
|
-
|
48
|
+
|
49
|
+
Spinach.hooks.run_after_feature(feature)
|
50
|
+
|
51
|
+
# FIXME The feature & scenario runners should have the same structure.
|
52
|
+
# They should either both return inverted failure or both return
|
53
|
+
# raw success.
|
53
54
|
!@failed
|
54
55
|
end
|
55
56
|
|
56
57
|
private
|
57
58
|
|
58
59
|
def feature_tags
|
59
|
-
if
|
60
|
-
|
60
|
+
if feature.respond_to?(:tags)
|
61
|
+
feature.tags
|
61
62
|
else
|
62
63
|
[]
|
63
64
|
end
|
64
65
|
end
|
65
66
|
|
66
67
|
def run_scenarios!
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
end
|
68
|
+
scenarios_to_run.each do |scenario|
|
69
|
+
success = ScenarioRunner.new(scenario).run
|
70
|
+
@failed = true unless success
|
71
|
+
|
72
|
+
break if Spinach.config.fail_fast && @failed
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
76
|
-
def
|
77
|
-
|
78
|
-
TagsMatcher.match(feature_tags + scenario.tags)
|
79
|
-
end
|
76
|
+
def undefined_steps!
|
77
|
+
Spinach.hooks.run_on_undefined_feature(feature)
|
80
78
|
|
81
|
-
|
82
|
-
return true unless @line
|
83
|
-
return false if @line < scenarios[current_scenario_index].line
|
84
|
-
next_scenario = scenarios[current_scenario_index+1]
|
85
|
-
!next_scenario || @line < next_scenario.line
|
79
|
+
@failed = true
|
86
80
|
end
|
87
81
|
|
82
|
+
def scenarios_to_run
|
83
|
+
feature.scenarios.select do |scenario|
|
84
|
+
has_a_tag_that_will_be_run = TagsMatcher.match(feature_tags + scenario.tags)
|
85
|
+
on_a_line_that_will_be_run = if feature.run_every_scenario?
|
86
|
+
true
|
87
|
+
else
|
88
|
+
(scenario.lines & feature.lines_to_run).any?
|
89
|
+
end
|
88
90
|
|
89
|
-
|
90
|
-
|
91
|
-
@failed = true
|
91
|
+
has_a_tag_that_will_be_run && on_a_line_that_will_be_run
|
92
|
+
end
|
92
93
|
end
|
93
94
|
end
|
94
95
|
end
|
@@ -61,7 +61,7 @@ module Spinach
|
|
61
61
|
end
|
62
62
|
step_definitions.after_each
|
63
63
|
end
|
64
|
-
raise
|
64
|
+
raise Spinach::HookNotYieldException.new('around_scenario') if !scenario_run && !@exception
|
65
65
|
Spinach.hooks.run_after_scenario @scenario, step_definitions
|
66
66
|
!@exception
|
67
67
|
end
|
@@ -74,8 +74,15 @@ module Spinach
|
|
74
74
|
# @api semipublic
|
75
75
|
def run_step(step)
|
76
76
|
step_location = step_definitions.step_location_for(step.name)
|
77
|
-
|
77
|
+
step_run = false
|
78
|
+
Spinach.hooks.run_around_step step, step_definitions do
|
79
|
+
step_run = true
|
80
|
+
step_definitions.execute(step)
|
81
|
+
end
|
82
|
+
raise Spinach::HookNotYieldException.new('around_step') if !step_run
|
78
83
|
Spinach.hooks.run_on_successful_step step, step_location, step_definitions
|
84
|
+
rescue Spinach::HookNotYieldException => e
|
85
|
+
raise e
|
79
86
|
rescue *Spinach.config[:failure_exceptions] => e
|
80
87
|
@exception = e
|
81
88
|
Spinach.hooks.run_on_failed_step step, @exception, step_location, step_definitions
|
data/lib/spinach/scenario.rb
CHANGED
data/lib/spinach/version.rb
CHANGED
data/test/spinach/cli_test.rb
CHANGED
@@ -76,7 +76,15 @@ tags:
|
|
76
76
|
Spinach.stubs(:config).returns(config)
|
77
77
|
cli = Spinach::Cli.new([opt, 'progress'])
|
78
78
|
cli.options
|
79
|
-
config.
|
79
|
+
config.reporter_classes.must_equal ['Spinach::Reporter::Progress']
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'sets multiple reporter classes' do
|
83
|
+
config = Spinach::Config.new
|
84
|
+
Spinach.stubs(:config).returns(config)
|
85
|
+
cli = Spinach::Cli.new([opt, 'progress,stdout'])
|
86
|
+
cli.options
|
87
|
+
config.reporter_classes.must_equal ['Spinach::Reporter::Progress', 'Spinach::Reporter::Stdout']
|
80
88
|
end
|
81
89
|
end
|
82
90
|
end
|
data/test/spinach/config_test.rb
CHANGED
@@ -16,14 +16,14 @@ describe Spinach::Config do
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
describe '#
|
19
|
+
describe '#reporter_classes' do
|
20
20
|
it 'returns a default' do
|
21
|
-
subject[:
|
21
|
+
subject[:reporter_classes].must_equal ["Spinach::Reporter::Stdout"]
|
22
22
|
end
|
23
23
|
|
24
24
|
it 'can be overwritten' do
|
25
|
-
subject[:
|
26
|
-
subject[:
|
25
|
+
subject[:reporter_classes] = ["MyOwnReporter"]
|
26
|
+
subject[:reporter_classes].must_equal ["MyOwnReporter"]
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
@@ -2,5 +2,32 @@ require 'test_helper'
|
|
2
2
|
|
3
3
|
module Spinach
|
4
4
|
describe Feature do
|
5
|
+
describe "#lines_to_run=" do
|
6
|
+
subject { Feature.new }
|
7
|
+
|
8
|
+
before { subject.lines_to_run = [4, 12] }
|
9
|
+
|
10
|
+
it 'writes lines_to_run' do
|
11
|
+
subject.lines_to_run.must_equal [4, 12]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#run_every_scenario?' do
|
16
|
+
subject { Feature.new }
|
17
|
+
|
18
|
+
describe 'when no line constraints have been specified' do
|
19
|
+
it 'is true' do
|
20
|
+
subject.run_every_scenario?.must_equal true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'when line constraints have been specified' do
|
25
|
+
before { subject.lines_to_run = [4, 12] }
|
26
|
+
|
27
|
+
it 'is false' do
|
28
|
+
subject.run_every_scenario?.must_equal false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
5
32
|
end
|
6
33
|
end
|
data/test/spinach/hooks_test.rb
CHANGED
@@ -59,6 +59,24 @@ describe Spinach::Hooks do
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
|
+
describe "around_step" do
|
63
|
+
it "responds to around_step" do
|
64
|
+
subject.must_respond_to :around_step
|
65
|
+
end
|
66
|
+
|
67
|
+
it "executes the hook with params" do
|
68
|
+
array = []
|
69
|
+
block = Proc.new do |arg1, arg2|
|
70
|
+
array << arg1
|
71
|
+
array << arg2
|
72
|
+
end
|
73
|
+
subject.send(:around_step, &block)
|
74
|
+
subject.send("run_around_step", 1, 2) do
|
75
|
+
end
|
76
|
+
array.must_equal [1, 2]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
62
80
|
describe "#on_tag" do
|
63
81
|
let(:scenario) do
|
64
82
|
stub(tags: ['javascript', 'capture'])
|
@@ -63,7 +63,11 @@ module Spinach
|
|
63
63
|
|
64
64
|
describe '#visit_Scenario' do
|
65
65
|
before do
|
66
|
-
@steps = [
|
66
|
+
@steps = [
|
67
|
+
stub_everything(line: 4),
|
68
|
+
stub_everything(line: 5),
|
69
|
+
stub_everything(line: 6)
|
70
|
+
]
|
67
71
|
@tags = [stub_everything, stub_everything, stub_everything]
|
68
72
|
@node = stub(
|
69
73
|
tags: @tags,
|
@@ -83,9 +87,9 @@ module Spinach
|
|
83
87
|
visitor.feature.scenarios.first.name.must_equal 'Go shopping on Saturday morning'
|
84
88
|
end
|
85
89
|
|
86
|
-
it 'sets the
|
90
|
+
it 'sets the lines' do
|
87
91
|
visitor.visit_Scenario(@node)
|
88
|
-
visitor.feature.scenarios.first.
|
92
|
+
visitor.feature.scenarios.first.lines.must_equal (3..6).to_a
|
89
93
|
end
|
90
94
|
|
91
95
|
it 'sets the tags' do
|