spinach 0.10.0 → 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -96,3 +96,4 @@ end
96
96
 
97
97
  require_relative 'reporter/stdout'
98
98
  require_relative 'reporter/progress'
99
+ require_relative 'reporter/failure_file'
@@ -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
@@ -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 init_reporter
53
- reporter = Support.constantize(Spinach.config[:reporter_class]).new(Spinach.config.reporter_options)
54
- reporter.bind
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
- init_reporter
59
+ init_reporters
67
60
 
68
- Spinach.hooks.run_before_run
61
+ features = filenames.map do |filename|
62
+ file, *lines = filename.split(":") # little more complex than just a "filename"
69
63
 
70
- successful = true
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
- filenames.map! do |filename|
73
- file, *lines = filename.split(":")
74
- [file, lines]
68
+ feature.lines_to_run = lines if lines.any?
69
+
70
+ feature
75
71
  end
76
72
 
77
- catch :fail do
78
- filenames.each do |filename, lines|
79
- lines = [nil] if lines.empty?
73
+ suite_passed = true
74
+
75
+ Spinach.hooks.run_before_run
80
76
 
81
- feature = Parser.open_file(filename).parse
82
- feature.filename = filename
83
- feature.lines = lines
77
+ features.each do |feature|
78
+ feature_passed = FeatureRunner.new(feature).run
79
+ suite_passed &&= feature_passed
84
80
 
85
- lines.each do |line|
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(successful)
84
+ Spinach.hooks.run_after_run(suite_passed)
94
85
 
95
- successful
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, line=nil)
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
- @feature.name
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
- @feature.scenarios
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 @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
- Spinach.hooks.run_after_feature @feature
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 @feature.respond_to?(:tags)
60
- @feature.tags
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
- scenarios.each_with_index do |scenario, current_scenario_index|
68
- if run_scenario?(scenario, current_scenario_index)
69
- success = ScenarioRunner.new(scenario).run
70
- @failed = true unless success
71
- break if Spinach.config.fail_fast && @failed
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 run_scenario?(scenario, current_scenario_index)
77
- match_line(current_scenario_index) &&
78
- TagsMatcher.match(feature_tags + scenario.tags)
79
- end
76
+ def undefined_steps!
77
+ Spinach.hooks.run_on_undefined_feature(feature)
80
78
 
81
- def match_line(current_scenario_index)
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
- def undefined_steps!
90
- Spinach.hooks.run_on_undefined_feature @feature
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 "around_scenario hooks *must* yield" if !scenario_run && !@exception
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
- step_definitions.execute(step)
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
@@ -1,12 +1,13 @@
1
1
  module Spinach
2
2
  class Scenario
3
- attr_accessor :line
3
+ attr_accessor :lines
4
4
  attr_accessor :name, :steps, :tags, :feature
5
5
 
6
6
  def initialize(feature)
7
7
  @feature = feature
8
8
  @steps = []
9
9
  @tags = []
10
+ @lines = []
10
11
  end
11
12
  end
12
13
  end
@@ -1,4 +1,4 @@
1
1
  module Spinach
2
2
  # Spinach version.
3
- VERSION = "0.10.0"
3
+ VERSION = "0.10.1"
4
4
  end
@@ -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.reporter_class.must_equal 'Spinach::Reporter::Progress'
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
@@ -16,14 +16,14 @@ describe Spinach::Config do
16
16
  end
17
17
  end
18
18
 
19
- describe '#reporter_class' do
19
+ describe '#reporter_classes' do
20
20
  it 'returns a default' do
21
- subject[:reporter_class].must_equal "Spinach::Reporter::Stdout"
21
+ subject[:reporter_classes].must_equal ["Spinach::Reporter::Stdout"]
22
22
  end
23
23
 
24
24
  it 'can be overwritten' do
25
- subject[:reporter_class] = "MyOwnReporter"
26
- subject[:reporter_class].must_equal "MyOwnReporter"
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
@@ -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 = [stub_everything, stub_everything, stub_everything]
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 line' do
90
+ it 'sets the lines' do
87
91
  visitor.visit_Scenario(@node)
88
- visitor.feature.scenarios.first.line.must_equal 3
92
+ visitor.feature.scenarios.first.lines.must_equal (3..6).to_a
89
93
  end
90
94
 
91
95
  it 'sets the tags' do