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