spinach 0.5.2 → 0.6.0

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.
@@ -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)