spinach 0.5.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,43 @@
1
+ class BeforeAndAfterHooksInheritanceBase < Spinach::FeatureSteps
2
+ class << self
3
+ attr_accessor :var1
4
+ attr_accessor :var2
5
+ end
6
+
7
+ before do
8
+ if self.class.var2.nil?
9
+ self.class.var2 = :i_am_here
10
+ else
11
+ self.class.var2 = :dirty
12
+ end
13
+ if self.class.var1.nil?
14
+ self.class.var1 = :clean
15
+ else
16
+ self.class.var1 = :dirty
17
+ end
18
+ end
19
+
20
+ after do
21
+ self.class.var1 = nil
22
+ end
23
+
24
+ end
25
+
26
+ class BeforeAndAfterHooksInheritance < BeforeAndAfterHooksInheritanceBase
27
+ before do
28
+ self.class.var1 = :in_subclass
29
+ end
30
+
31
+ after do
32
+ self.class.var2 = nil
33
+ end
34
+
35
+ Then 'I can see the variable being overridden in the subclass' do
36
+ self.class.var1.must_equal :in_subclass
37
+ end
38
+
39
+ Then "I can see the variable setup in the super class before hook" do
40
+ self.class.var2.must_equal :i_am_here
41
+ end
42
+
43
+ end
@@ -0,0 +1,98 @@
1
+ class UseCustomizedReporter < Spinach::FeatureSteps
2
+
3
+ feature "Use customized reporter"
4
+
5
+ include Integration::SpinachRunner
6
+
7
+ before do
8
+ class_str = <<-EOF
9
+ class Spinach::TestReporter < Spinach::Reporter
10
+ attr_reader :out, :error
11
+ attr_accessor :scenario_error
12
+ attr_accessor :scenario
13
+
14
+ def initialize(*args)
15
+ super(*args)
16
+ @out = options[:output] || $stdout
17
+ @error = options[:error] || $stderr
18
+ @max_step_name_length = 0
19
+ end
20
+
21
+ def before_feature_run(feature)
22
+ out.puts "The customized class"
23
+ end
24
+
25
+ def before_scenario_run(scenario, step_definitions = nil)
26
+ end
27
+
28
+ def after_scenario_run(scenario, step_definitions = nil)
29
+ end
30
+
31
+ def on_successful_step(step, step_location, step_definitions = nil)
32
+ end
33
+
34
+ def on_failed_step(step, failure, step_location, step_definitions = nil)
35
+ end
36
+
37
+ def on_error_step(step, failure, step_location, step_definitions = nil)
38
+ end
39
+
40
+ def on_undefined_step(step, failure, step_definitions = nil)
41
+ end
42
+
43
+ def on_pending_step(step, failure)
44
+ end
45
+
46
+ def on_feature_not_found(feature)
47
+ end
48
+
49
+ def on_skipped_step(step, step_definitions = nil)
50
+ end
51
+
52
+ def output_step(symbol, step, color, step_location = nil)
53
+ end
54
+
55
+ def after_run(success)
56
+ end
57
+
58
+ def run_summary
59
+ end
60
+
61
+ def full_step(step)
62
+ end
63
+
64
+ end
65
+ EOF
66
+
67
+ write_file "test_reporter.rb", class_str
68
+ end
69
+
70
+ Given 'I have a feature that has no error or failure' do
71
+ write_file('features/success_feature.feature', """
72
+ Feature: A success feature
73
+
74
+ Scenario: This is scenario will succeed
75
+ Then I succeed
76
+ """)
77
+
78
+ write_file('features/steps/success_feature.rb',
79
+ <<-EOF
80
+ require_relative "../../test_reporter"
81
+ class ASuccessFeature < Spinach::FeatureSteps
82
+ feature "A success feature"
83
+ Then "I succeed" do
84
+ end
85
+ end
86
+ EOF
87
+ )
88
+ @feature = "features/success_feature.feature"
89
+ end
90
+
91
+ When 'I run it using the new reporter' do
92
+ run_feature @feature, append: "-r Spinach::TestReporter"
93
+ end
94
+
95
+ Then 'I see the desired output' do
96
+ @stdout.must_include("The customized class")
97
+ end
98
+ end
data/lib/spinach.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require_relative 'spinach/version'
2
+ require_relative 'spinach/helpers'
2
3
  require_relative 'spinach/config'
3
4
  require_relative 'spinach/hookable'
4
5
  require_relative 'spinach/hooks'
data/lib/spinach/cli.rb CHANGED
@@ -20,17 +20,10 @@ module Spinach
20
20
  #
21
21
  # @api public
22
22
  def run
23
+ options
23
24
  Spinach::Runner.new(feature_files).run
24
25
  end
25
26
 
26
- # Inits the reporter with a default one.
27
- #
28
- # @api public
29
- def init_reporter
30
- reporter = Spinach::Reporter::Stdout.new(options[:reporter])
31
- reporter.bind
32
- end
33
-
34
27
  # @return [Hash]
35
28
  # A hash of options separated by its type.
36
29
  #
@@ -52,8 +45,6 @@ module Spinach
52
45
  #
53
46
  # @api private
54
47
  def parse_options
55
- reporter_options = {}
56
- reporter_options[:backtrace] = false
57
48
  config = {}
58
49
 
59
50
  begin
@@ -65,7 +56,7 @@ module Spinach
65
56
 
66
57
  opts.on('-b', '--backtrace',
67
58
  'Show backtrace of errors') do |show_backtrace|
68
- reporter_options[:backtrace] = show_backtrace
59
+ config[:reporter_options] = {backtrace: show_backtrace}
69
60
  end
70
61
 
71
62
  opts.on('-t', '--tags TAG',
@@ -73,13 +64,7 @@ module Spinach
73
64
  config[:tags] ||= []
74
65
  tags = tag.delete('@').split(',')
75
66
 
76
- references_wip = lambda { |tag_groups|
77
- tag_groups.any? { |tag_group|
78
- tag_group.any? { |tag| tag =~ /wip$/ }
79
- }
80
- }
81
-
82
- unless references_wip.(config[:tags]) || references_wip.([tags])
67
+ if (config[:tags] + tags).flatten.none? { |t| t =~ /wip$/ }
83
68
  tags.unshift '~wip'
84
69
  end
85
70
 
@@ -100,16 +85,23 @@ module Spinach
100
85
  'Path where your features will be searched for') do |path|
101
86
  config[:features_path] = path
102
87
  end
88
+
89
+ opts.on('-r', '--reporter CLASS_NAME',
90
+ 'Formatter class name') do |class_name|
91
+ config[:reporter_class] = class_name
92
+ end
103
93
  end.parse!(@args)
104
94
 
105
95
  Spinach.config.parse_from_file
106
96
  config.each{|k,v| Spinach.config[k] = v}
97
+ if Spinach.config.tags.empty? ||
98
+ Spinach.config.tags.flatten.none?{ |t| t =~ /wip$/ }
99
+ Spinach.config.tags.unshift ['~wip']
100
+ end
107
101
  rescue OptionParser::ParseError => exception
108
102
  puts exception.message.capitalize
109
103
  exit 1
110
104
  end
111
-
112
- {reporter: reporter_options}
113
105
  end
114
106
 
115
107
  # Uses given args to list the feature files to run. It will find a single
@@ -29,7 +29,10 @@ module Spinach
29
29
  :failure_exceptions,
30
30
  :config_path,
31
31
  :tags,
32
- :save_and_open_page_on_failure
32
+ :save_and_open_page_on_failure,
33
+ :reporter_class,
34
+ :reporter_options
35
+
33
36
 
34
37
  # The "features path" holds the place where your features will be
35
38
  # searched for. Defaults to 'features'
@@ -42,6 +45,24 @@ module Spinach
42
45
  @features_path || 'features'
43
46
  end
44
47
 
48
+ # The "reporter class" holds the reporter class name
49
+ # Default to Spinach::Reporter::Stdout
50
+ #
51
+ # @return [reporter object]
52
+ # The reporter that responds to specific messages.
53
+ #
54
+ # @api public
55
+ def reporter_class
56
+ @reporter_class || "Spinach::Reporter::Stdout"
57
+ end
58
+
59
+ # The "reporter_options" holds the options of reporter_class
60
+ #
61
+ # @api public
62
+ def reporter_options
63
+ @reporter_options || {}
64
+ end
65
+
45
66
  # The "step definitions path" holds the place where your feature step
46
67
  # classes will be searched for. Defaults to '#{features_path}/steps'
47
68
  #
data/lib/spinach/dsl.rb CHANGED
@@ -58,6 +58,69 @@ module Spinach
58
58
  alias_method :Then, :step
59
59
  alias_method :And, :step
60
60
  alias_method :But, :step
61
+
62
+ # Defines a before hook for each scenario. The scope is limited only to the current
63
+ # step class (thus the current feature).
64
+ #
65
+ # When a scenario is executed, the before each block will be run first before any steps
66
+ #
67
+ # User can define multiple before blocks throughout the class hierarchy and they are chained
68
+ # through the inheritance chain when executing
69
+ #
70
+ # @example
71
+ #
72
+ # class MySpinach::Base< Spinach::FeatureSteps
73
+ # before do
74
+ # @var1 = 30
75
+ # @var2 = 40
76
+ # end
77
+ # end
78
+ #
79
+ # class MyFeature < MySpinach::Base
80
+ # before do
81
+ # self.original_session_timeout = 1000
82
+ # change_session_timeout_to(1)
83
+ # @var2 = 50
84
+ # end
85
+ # end
86
+ #
87
+ # When running a scenario in MyFeature, @var1 is 30 and @var2 is 50
88
+ #
89
+ # @api public
90
+ def before(&block)
91
+ define_before_or_after_method_with_block(:before, &block)
92
+ end
93
+
94
+ # Defines a after hook for each scenario. The scope is limited only to the current
95
+ # step class (thus the current feature).
96
+ #
97
+ # When a scenario is executed, the after each block will be run after any steps
98
+ #
99
+ # User can define multiple after blocks throughout the class hierarchy and they are chained
100
+ # through the inheritance chain when executing.
101
+ #
102
+ # @example
103
+ #
104
+ # class MySpinach::Base < Spinach::FeatureSteps
105
+ # after do
106
+ # @var1 = 30
107
+ # @var2 = 40
108
+ # end
109
+ # end
110
+ #
111
+ # class MyFeature < MySpinach::Base
112
+ # after do
113
+ # change_session_timeout_to(original_session_timeout)
114
+ # @var2 = 50
115
+ # end
116
+ # end
117
+ #
118
+ # When running a scenario in MyFeature, @var1 is 30 and @var2 is 50
119
+ #
120
+ # @api public
121
+ def after(&block)
122
+ define_before_or_after_method_with_block(:after, &block)
123
+ end
61
124
 
62
125
  # Sets the feature name.
63
126
  #
@@ -74,6 +137,25 @@ module Spinach
74
137
  @feature_name = name
75
138
  end
76
139
 
140
+ private
141
+
142
+ def before_or_after_private_method_name(location)
143
+ hash_value = hash
144
+ class_name = self.name || ""
145
+ class_name = class_name.gsub("::", "__").downcase
146
+ private_method_name = "_#{location}_each_block_#{hash.abs}_#{class_name}" #uniqueness
147
+ end
148
+
149
+ def define_before_or_after_method_with_block(location, &block)
150
+ define_method(before_or_after_private_method_name(location), &block)
151
+ private before_or_after_private_method_name(location)
152
+ private_method_name = before_or_after_private_method_name location
153
+ define_method "#{location}_each" do
154
+ super()
155
+ send(private_method_name)
156
+ end
157
+ end
158
+
77
159
  end
78
160
 
79
161
  # Instance methods to include in the host class.
@@ -26,5 +26,8 @@ module Spinach
26
26
  include_private(*args)
27
27
  end
28
28
  end
29
+
30
+ def before_each; end
31
+ def after_each; end
29
32
  end
30
33
  end
@@ -0,0 +1,16 @@
1
+ module Spinach
2
+ module Helpers
3
+ class << self
4
+ def constantize(string)
5
+ names = string.split('::')
6
+ names.shift if names.empty? || names.first.empty?
7
+
8
+ constant = Object
9
+ names.each do |name|
10
+ constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
11
+ end
12
+ constant
13
+ end
14
+ end
15
+ end
16
+ end
@@ -46,6 +46,14 @@ module Spinach
46
46
  # The default path where the support files are located
47
47
  attr_reader :support_path
48
48
 
49
+ # Inits the reporter with a default one.
50
+ #
51
+ # @api public
52
+ def init_reporter
53
+ reporter = Helpers.constantize(Spinach.config[:reporter_class]).new(Spinach.config.reporter_options)
54
+ reporter.bind
55
+ end
56
+
49
57
  # Runs this runner and outputs the results in a colorful manner.
50
58
  #
51
59
  # @return [true, false]
@@ -55,6 +63,7 @@ module Spinach
55
63
  def run
56
64
  require_dependencies
57
65
  require_frameworks
66
+ init_reporter
58
67
 
59
68
  Spinach.hooks.run_before_run
60
69
 
@@ -74,7 +74,7 @@ module Spinach
74
74
 
75
75
  def run_scenario?(scenario, current_scenario_index)
76
76
  match_line(current_scenario_index) &&
77
- TagsMatcher.match((feature_tags << scenario.tags).flatten.compact)
77
+ TagsMatcher.match(feature_tags + scenario.tags)
78
78
  end
79
79
 
80
80
  def match_line(current_scenario_index)
@@ -47,6 +47,7 @@ module Spinach
47
47
  scenario_run = false
48
48
  Spinach.hooks.run_around_scenario @scenario, step_definitions do
49
49
  scenario_run = true
50
+ step_definitions.before_each
50
51
  steps.each do |step|
51
52
  Spinach.hooks.run_before_step step, step_definitions
52
53
 
@@ -58,6 +59,7 @@ module Spinach
58
59
 
59
60
  Spinach.hooks.run_after_step step, step_definitions
60
61
  end
62
+ step_definitions.after_each
61
63
  end
62
64
  raise "around_scenario hooks *must* yield" if !scenario_run && !@exception
63
65
  Spinach.hooks.run_after_scenario @scenario, step_definitions
@@ -17,7 +17,10 @@ module Spinach
17
17
  def match(tags)
18
18
  return true if tag_groups.empty?
19
19
 
20
- tag_groups.all? { |tag_group| match_tag_group(tag_group, tags) }
20
+ tag_groups.all? { |tag_group|
21
+ res = match_tag_group(Array(tag_group), tags)
22
+ res
23
+ }
21
24
  end
22
25
 
23
26
  private
@@ -28,16 +31,20 @@ module Spinach
28
31
 
29
32
  def match_tag_group(tag_group, tags)
30
33
  matched_tags = tag_group.select { |tag| !tag_negated?(tag) }
31
-
32
- matched = matched_tags.any? { |tag| tags.include?(tag) }
33
-
34
- return true if matched
34
+ matched = if matched_tags.empty?
35
+ true
36
+ else
37
+ !tags.empty? && matched_tags.any? { |tag| tags.include?(tag) }
38
+ end
35
39
 
36
40
  negated_tags = tag_group.select { |tag| tag_negated? tag }
37
- negated = negated_tags.any? {|tag| !tags.include?(tag.delete(NEGATION_SIGN))}
41
+ negated = if tags.empty?
42
+ false
43
+ else
44
+ negated_tags.any? {|tag| tags.include?(tag.delete(NEGATION_SIGN))}
45
+ end
38
46
 
39
- return true if negated && tag_group.count == 1
40
- false
47
+ !negated && matched
41
48
  end
42
49
 
43
50
  def tag_negated?(tag)