gurke 2.0.0.dev.1.b17 → 2.0.0.dev.1.b18

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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- NDc3MWMyNWIzYzQ0MGRmMWFkMmIyMmFmMjYwMTgzN2I3NmQ0YTdmYQ==
4
+ MTliYzQwOGExYjAzODMxMTE3YWZhMmU1ZDhmZDM5YjZjZWM5Y2E5ZA==
5
5
  data.tar.gz: !binary |-
6
- ODQ4YWI3ZTUyZDhmYjk2YzQ2NWQwOWY1ZmM1YzFkNDQ5YWRhZTJjYQ==
6
+ ZjdiNTE1MDQ5NTk5ZmY4YTIwZGZhZWM2MmRhZGYwMWQ1YWRhZjVjYQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- OTcwMGYwOTM2NTEzNGQ0MWU0YmFiN2I3OWMxNjAxZTc4N2Q0OGEwYTdkOGY4
10
- NDJkNmI0MjY0ZTYzYzcyMTVhNDZkYWY0MWE2MzVmYTc1ZjE1ZTMxYzZkMjQ2
11
- OGU4ZTRmODkyZTQ2MWMzY2VjOTM0MWE5MTdhNWQwODdiYjEwZmQ=
9
+ Y2EzNTBhOTE4OTFmOWQ2ZjU0NzI1MWY2YjhhMGE1N2EyNGJiNjNlYTA5NTNl
10
+ MmVmM2Y0NWM4NTVhYzIxNDY0OTY5ZDBiZTZkYTIwODAwMDljMzIxNTNkMTYy
11
+ Y2JmZmZiZjc3ZTYwNTdkMzlhNzgyOTMwZjk3NzEzZWIzNDRjNGU=
12
12
  data.tar.gz: !binary |-
13
- MmUyOWRiZDQ3ZWYyMDU5ZGY0M2Y3ODk2MmIyYThjNGI2MGYzNTEyMTJiNzJl
14
- ZTAzOGZlNzIwMGJmNGQzZDBlY2E5ZDBlZDk1ZDlhOWVlZDFhMzJiYjJjODJh
15
- ZGNjMmU1OWFjOTdjMTkwNmFjNTE5ZDQ4NDI1MGViZTliNTA4ZjA=
13
+ NGFiZmE4ODNkMDkxODY3ZWZjYTE1ODBkYzNkYjY4MWU1NTgwNzYxZDEzY2Y4
14
+ ZTBlOWY3M2U2ZWQ2MGFjODFiYThhNDEyOWYzZmU3MWQ0M2JlMzVjMTBhZjVh
15
+ YzYwNDNhZGNmOTFjZmQ2YmYxYzRkZjU1MmM5ZDdlMDkyMzg2NTg=
data/lib/gurke.rb CHANGED
@@ -8,6 +8,9 @@ module Gurke
8
8
  require 'gurke/step'
9
9
  require 'gurke/tag'
10
10
 
11
+ require 'gurke/run_list'
12
+ require 'gurke/feature_list'
13
+
11
14
  require 'gurke/dsl'
12
15
  require 'gurke/builder'
13
16
  require 'gurke/configuration'
@@ -16,6 +19,11 @@ module Gurke
16
19
  require 'gurke/step_definition'
17
20
  require 'gurke/reporter'
18
21
 
22
+ module Reporters
23
+ require 'gurke/reporters/null_reporter'
24
+ require 'gurke/reporters/default_reporter'
25
+ end
26
+
19
27
  class Error < StandardError; end
20
28
  class StepPending < Error; end
21
29
  class StepAmbiguous < Error; end
@@ -14,20 +14,31 @@ module Gurke
14
14
  #
15
15
  attr_reader :line
16
16
 
17
+ # List of steps this background specifies.
18
+ #
19
+ # @return [Array<Step>] Steps.
20
+ #
21
+ attr_reader :steps
22
+
17
23
  # @api private
18
24
  attr_reader :raw
19
25
 
20
26
  # @api private
21
27
  def initialize(file, line, raw)
22
- @file, @line, @raw = file, line, raw
28
+ @file = file
29
+ @line = line
30
+ @raw = raw
31
+ @steps = RunList.new
23
32
  end
24
33
 
25
- # Return list of steps this background specifies.
26
- #
27
- # @return [Array<Step>] Steps.
34
+ # @api private
28
35
  #
29
- def steps
30
- @steps ||= []
36
+ def run(runner, reporter, scenario, world)
37
+ reporter.invoke :start_background, self, scenario
38
+
39
+ steps.run runner, reporter, scenario, world
40
+ ensure
41
+ reporter.invoke :end_background, self, scenario
31
42
  end
32
43
  end
33
44
  end
data/lib/gurke/builder.rb CHANGED
@@ -8,7 +8,7 @@ module Gurke
8
8
 
9
9
  def initialize(options)
10
10
  @options = options
11
- @features = []
11
+ @features = FeatureList.new
12
12
  @language = 'en'
13
13
  @parser = Gherkin::Parser::Parser.new(
14
14
  self, true, 'root', false, @language)
@@ -30,49 +30,54 @@ module Gurke
30
30
  end
31
31
 
32
32
  def feature(raw)
33
- tags = raw.tags.map{|t| Tag.new(@file, t.line, t) }
33
+ tags = raw.tags.map{|t| Tag.new @file, t.line, t }
34
34
 
35
- @current_feature = Feature.new(@file, raw.line, tags, raw)
36
- @features << @current_feature
35
+ @feature = Feature.new(@file, raw.line, tags, raw)
36
+ @scenario = nil
37
+ @type = nil
38
+
39
+ features << @feature
37
40
  end
38
41
 
39
42
  def background(raw)
40
- @current_context = Background.new(@file, raw.line, raw)
41
- @current_feature.backgrounds << @current_context
43
+ @context = Background.new @file, raw.line, raw
44
+ @type = nil
45
+
46
+ @feature.backgrounds << @context
42
47
  end
43
48
 
44
49
  def scenario(raw)
45
- tags = raw.tags.map{|t| Tag.new(@file, t.line, t) }
46
- tags += @current_feature.tags
50
+ tags = raw.tags.map{|t| Tag.new @file, t.line, t }
51
+ tags += features.last.tags
47
52
 
48
- @current_context = Scenario.new(@file, raw.line, tags, raw)
53
+ @scenario = Scenario.new @feature, @file, raw.line, tags, raw
54
+ @context = @scenario
55
+ @type = nil
49
56
 
50
- unless filtered?(@current_context)
51
- @current_feature.scenarios << @current_context
52
- end
57
+ @feature.scenarios << @scenario unless filtered?(@scenario)
53
58
  end
54
59
 
55
60
  def step(raw)
56
- type = get_type(raw.keyword.strip)
61
+ @type = lookup_type raw.keyword.strip
57
62
 
58
- @current_context.steps << Step.new(@file, raw.line, type, raw)
63
+ @context.steps << Step.new(@file, raw.line, @type, raw)
59
64
  end
60
65
 
61
66
  def eof(*)
62
67
  @features.reject!{|f| f.scenarios.empty? }
63
- @current_context = nil
64
- @current_feature = nil
65
- @file = nil
68
+ @feature = nil
69
+ @scenario = nil
70
+ @context = nil
71
+ @type = nil
72
+ @file = nil
66
73
  end
67
74
 
68
- def get_type(keyword)
75
+ private
76
+
77
+ def lookup_type(keyword)
69
78
  case (kw = @keywords.fetch(keyword))
70
79
  when :and, :but
71
- if (step = @current_context.steps.last)
72
- step.type
73
- else
74
- nil
75
- end
80
+ @type
76
81
  else
77
82
  kw
78
83
  end
data/lib/gurke/feature.rb CHANGED
@@ -14,6 +14,18 @@ module Gurke
14
14
  #
15
15
  attr_reader :line
16
16
 
17
+ # List of scenarios this feature specifies.
18
+ #
19
+ # @return [Array<Scenario>] Scenarios.
20
+ #
21
+ attr_reader :scenarios
22
+
23
+ # List of backgrounds this feature specifies.
24
+ #
25
+ # @return [Array<Background>] Backgrounds.
26
+ #
27
+ attr_reader :backgrounds
28
+
17
29
  attr_reader :tags
18
30
 
19
31
  # @api private
@@ -21,7 +33,13 @@ module Gurke
21
33
 
22
34
  # @api private
23
35
  def initialize(file, line, tags, raw)
24
- @file, @line, @tags, @raw = file, line, tags, raw
36
+ @scenarios = RunList.new
37
+ @backgrounds = RunList.new
38
+
39
+ @file = file
40
+ @line = line
41
+ @tags = tags
42
+ @raw = raw
25
43
  end
26
44
 
27
45
  def name
@@ -32,18 +50,6 @@ module Gurke
32
50
  raw.description
33
51
  end
34
52
 
35
- # Return list of scenarios this feature specifies.
36
- #
37
- # @return [Array<Scenario>] Scenarios.
38
- #
39
- def scenarios
40
- @scenarios ||= []
41
- end
42
-
43
- def backgrounds
44
- @backgrounds ||= []
45
- end
46
-
47
53
  # Return name of this feature.
48
54
  #
49
55
  # @return [String] Feature name.
@@ -51,5 +57,34 @@ module Gurke
51
57
  def name
52
58
  raw.name
53
59
  end
60
+
61
+ def failed?
62
+ scenarios.any?(&:failed?)
63
+ end
64
+
65
+ def pending?
66
+ scenarios.any?(&:pending?)
67
+ end
68
+
69
+ # @api private
70
+ def run(runner, reporter)
71
+ reporter.invoke :before_feature, self
72
+
73
+ runner.hook :feature, nil do
74
+ run_feature runner, reporter
75
+ end
76
+ ensure
77
+ reporter.invoke :after_feature
78
+ end
79
+
80
+ private
81
+
82
+ def run_feature(runner, reporter)
83
+ reporter.invoke :start_feature, self
84
+
85
+ scenarios.run runner, reporter
86
+ ensure
87
+ reporter.invoke :end_feature, self
88
+ end
54
89
  end
55
90
  end
@@ -0,0 +1,37 @@
1
+ module Gurke
2
+ #
3
+ # A {FeatureList} is a list of {Feature} objects.
4
+ #
5
+ class FeatureList < Array
6
+ #
7
+ # Run all features from this list.
8
+ #
9
+ # @return [Boolean] False if any scenario has failed or is pending.
10
+ #
11
+ # @api private
12
+ #
13
+ def run(runner, reporter)
14
+ reporter.invoke :before_features, self
15
+
16
+ runner.hook(:features, nil) do
17
+ run_features runner, reporter
18
+ end
19
+
20
+ reporter.invoke :after_features, self
21
+
22
+ !any?{|s| s.failed? || s.pending? }
23
+ end
24
+
25
+ private
26
+
27
+ def run_features(runner, reporter)
28
+ reporter.invoke :start_features, self
29
+
30
+ each do |feature|
31
+ feature.run runner, reporter
32
+ end
33
+ ensure
34
+ reporter.invoke :end_features, self
35
+ end
36
+ end
37
+ end
@@ -1,98 +1,259 @@
1
- require 'colorize'
2
-
3
- # Colorize colors:
4
- # :black, :red, :green, :yellow, :blue,
5
- # :magenta, :cyan, :white, :default, :light_black,
6
- # :light_red, :light_green, :light_yellow, :light_blue,
7
- # :light_magenta, :light_cyan, :light_white
8
-
9
1
  module Gurke
2
+ #
3
+ # A {Reporter} provides callbacks that will be executed whenever
4
+ # a specific execution step starts or ends.
5
+ #
6
+ # @api public
10
7
  #
11
8
  class Reporter
12
- def start_features(*)
9
+ # rubocop:disable UnusedMethodArgument
10
+
11
+ # List of all callback methods as symbols.
12
+ #
13
+ CALLBACKS = [
14
+ :before_features,
15
+ :before_feature,
16
+ :before_scenario,
17
+ :before_step,
18
+ :start_features,
19
+ :start_feature,
20
+ :start_scenario,
21
+ :start_background,
22
+ :start_step,
23
+ :end_features,
24
+ :end_feature,
25
+ :end_scenario,
26
+ :end_background,
27
+ :end_step,
28
+ :after_features,
29
+ :after_feature,
30
+ :after_scenario,
31
+ :after_step
32
+ ]
33
+
34
+ # Called before the execution of any feature and before any
35
+ # before-features hook is invoked.
36
+ #
37
+ # @param features [Array<Feature>] List of all features that
38
+ # are going to be executed.
39
+ #
40
+ # @api public
41
+ #
42
+ def before_features(features)
43
+ raise NotImplementedError.new \
44
+ "#{self.class.name}#before_features must be implemented in subclass."
13
45
  end
14
46
 
15
- def start_feature(feature)
16
- io.puts "#{yellow('Feature')}: #{feature.name}"
17
- io.puts ' ' + light_black(feature.description.split("\n").join("\n "))
18
- io.puts
47
+ # Called before the execute of any feature, but after all
48
+ # before-features hooks.
49
+ #
50
+ # @param features [Array<Feature>] List of all features that
51
+ # are going to be executed.
52
+ #
53
+ # @api public
54
+ #
55
+ def start_features(features)
56
+ raise NotImplementedError.new \
57
+ "#{self.class.name}#before_features must be implemented in subclass."
19
58
  end
20
59
 
21
- def start_scenario(scenario, feature)
22
- io.puts " #{yellow('Scenario')}: #{scenario.name}"
23
- io.puts light_black(' Background:') if feature.backgrounds.any?
60
+ # Called for each feature before it starts, but before any
61
+ # before-feature hook is run.
62
+ #
63
+ # @param feature [Feature] The feature that is going to
64
+ # be executed now.
65
+ #
66
+ # @api public
67
+ #
68
+ def before_feature(feature)
69
+ raise NotImplementedError.new \
70
+ "#{self.class.name}#start_feature must be implemented in subclass."
24
71
  end
25
72
 
26
- def start_background(*)
27
- @background = true
73
+ # Called for each feature before it starts, but after
74
+ # all before-feature hooks.
75
+ #
76
+ # @param feature [Feature] The feature that is going to
77
+ # be executed now.
78
+ #
79
+ # @api public
80
+ #
81
+ def start_feature(feature)
82
+ raise NotImplementedError.new \
83
+ "#{self.class.name}#start_feature must be implemented in subclass."
28
84
  end
29
85
 
30
- def finish_background(*)
31
- @background = false
86
+ # Called for each each scenario before it starts. Will be
87
+ # called before any hooks for the given scenario is executed.
88
+ #
89
+ # @param scenario [Scenario] Current scenario.
90
+ #
91
+ # @api public
92
+ #
93
+ def before_scenario(scenario)
94
+ raise NotImplementedError.new \
95
+ "#{self.class.name}#before_scenario must be implemented in subclass."
32
96
  end
33
97
 
34
- def start_step(step, *)
35
- io.print ' ' if @background
36
- io.print ' '
37
- io.print yellow(step.keyword)
38
- io.print step.name.gsub(/"(.*?)"/, cyan('\0'))
98
+ # Called for each each scenario before it starts, but after
99
+ # all before hooks for given scenario.
100
+ #
101
+ # @param scenario [Scenario] Current scenario.
102
+ #
103
+ # @api public
104
+ #
105
+ def start_scenario(scenario)
106
+ raise NotImplementedError.new \
107
+ "#{self.class.name}#start_scenario must be implemented in subclass."
39
108
  end
40
109
 
41
- def finish_step(step, *)
42
- case step.state
43
- when :pending
44
- print_braces yellow('pending')
45
- when :failed
46
- print_braces red('failure')
47
- io.puts
48
- io.puts red(" #{step.exception.class}:")
110
+ # Called before each background.
111
+ #
112
+ # @param background [Background] Current background.
113
+ # @param scenario [Scenario] Current scenario.
114
+ #
115
+ # @api public
116
+ #
117
+ def start_background(background, scenario)
118
+ raise NotImplementedError.new \
119
+ "#{self.class.name}#start_background must be implemented in subclass."
120
+ end
49
121
 
50
- msg = step.exception.message.split("\n").join("\n ")
51
- io.puts red(" #{msg}")
122
+ # Called after each background.
123
+ #
124
+ # @param background [Background] Current background.
125
+ # @param scenario [Scenario] Current scenario.
126
+ #
127
+ # @api public
128
+ #
129
+ def end_background(background, scenario)
130
+ raise NotImplementedError.new \
131
+ "#{self.class.name}#end_background must be implemented in subclass."
132
+ end
52
133
 
53
- io.puts red(" #{step.exception.backtrace.join("\n ")}")
54
- when :success
55
- print_braces green('success')
56
- else
57
- print_braces cyan('skipped')
58
- end
59
- io.puts
60
- io.flush
134
+ # Called before each step and before any before-step hook.
135
+ #
136
+ # @param step [Step] Current Step.
137
+ # @param scenario [Scenario] Current scenario.
138
+ #
139
+ # @api public
140
+ #
141
+ def before_step(step, scenario)
142
+ raise NotImplementedError.new \
143
+ "#{self.class.name}#before_step must be implemented in subclass."
61
144
  end
62
145
 
63
- def finish_scenario(*)
64
- io.puts
146
+ # Called before each step and after all before-step hooks.
147
+ #
148
+ # @param step [Step] Current Step.
149
+ # @param scenario [Scenario] Current scenario.
150
+ #
151
+ # @api public
152
+ #
153
+ def start_step(step, scenario)
154
+ raise NotImplementedError.new \
155
+ "#{self.class.name}#start_step must be implemented in subclass."
65
156
  end
66
157
 
67
- def finish_feature(*)
68
- io.puts
158
+ # Called after each step but before any after-step hook.
159
+ #
160
+ # @param step_result [StepResult] Result of current Step.
161
+ # @param scenario [Scenario] Current scenario.
162
+ # @param feature [Feature] Current feature.
163
+ #
164
+ # @api public
165
+ #
166
+ def end_step(step_result, scenario)
167
+ raise NotImplementedError.new \
168
+ "#{self.class.name}#end_step must be implemented in subclass."
69
169
  end
70
170
 
71
- def finish_features(features)
72
- scenarios = features.map(&:scenarios).flatten
171
+ # Called after each step, after all step hook.
172
+ #
173
+ # @param step_result [StepResult] Result of current Step.
174
+ # @param scenario [Scenario] Current scenario.
175
+ # @param feature [Feature] Current feature.
176
+ #
177
+ # @api public
178
+ #
179
+ def after_step(step_result, scenario)
180
+ raise NotImplementedError.new \
181
+ "#{self.class.name}#after_step must be implemented in subclass."
182
+ end
73
183
 
74
- io.puts " #{scenarios.size} scenarios: "\
75
- "#{scenarios.select(&:failed?).size} failing, "\
76
- "#{scenarios.select(&:pending?).size} pending"
77
- io.puts
184
+ # Called after each scenario but before any after hook.
185
+ #
186
+ # @param scenario [Scenario] Current scenario.
187
+ # @param feature [Feature] Current feature.
188
+ #
189
+ # @api public
190
+ #
191
+ def end_scenario(scenario)
192
+ raise NotImplementedError.new \
193
+ "#{self.class.name}#end_scenario must be implemented in subclass."
78
194
  end
79
195
 
80
- private
196
+ # Called after each scenario and after all hooks.
197
+ #
198
+ # @param scenario [Scenario] Current scenario.
199
+ # @param feature [Feature] Current feature.
200
+ #
201
+ # @api public
202
+ #
203
+ def after_scenario(scenario)
204
+ raise NotImplementedError.new \
205
+ "#{self.class.name}#after_scenario must be implemented in subclass."
206
+ end
81
207
 
82
- def print_braces(str)
83
- io.print " (#{str})"
208
+ # Called after each feature but before any after hook.
209
+ #
210
+ # @param feature [Feature] Current feature.
211
+ #
212
+ # @api public
213
+ #
214
+ def end_feature(feature)
215
+ raise NotImplementedError.new \
216
+ "#{self.class.name}#end_feature must be implemented in subclass."
84
217
  end
85
218
 
86
- def io
87
- $stdout
219
+ # Called after each feature and after all hooks.
220
+ #
221
+ # @param feature [Feature] Current feature.
222
+ #
223
+ # @api public
224
+ #
225
+ def after_feature(feature)
226
+ raise NotImplementedError.new \
227
+ "#{self.class.name}#after_feature must be implemented in subclass."
88
228
  end
89
229
 
90
- [:black, :red, :green, :yellow, :blue,
91
- :magenta, :cyan, :white, :default, :light_black,
92
- :light_red, :light_green, :light_yellow, :light_blue,
93
- :light_magenta, :light_cyan, :light_white].each do |color|
230
+ # Called after all features but before any after-features hook.
231
+ #
232
+ # @param features [Array<Feature>] List of all features.
233
+ #
234
+ # @api public
235
+ #
236
+ def end_features(features)
237
+ raise NotImplementedError.new \
238
+ "#{self.class.name}#end_features must be implemented in subclass."
239
+ end
240
+
241
+ # Called after all features and after all hooks.
242
+ #
243
+ # @param features [Array<Feature>] List of all features.
244
+ #
245
+ # @api public
246
+ #
247
+ def after_features(features)
248
+ raise NotImplementedError.new \
249
+ "#{self.class.name}#after_features must be implemented in subclass."
250
+ end
94
251
 
95
- define_method(color){|str| io.tty? ? str.send(color) : str }
252
+ # @visibility private
253
+ def invoke(mth, *args)
254
+ send mth, *args
255
+ rescue => e
256
+ warn "Rescued in reporter: #{e}\n" + e.backtrace.join("\n")
96
257
  end
97
258
  end
98
259
  end
@@ -0,0 +1,95 @@
1
+ require 'colorize'
2
+
3
+ # Colorize colors:
4
+ # :black, :red, :green, :yellow, :blue,
5
+ # :magenta, :cyan, :white, :default, :light_black,
6
+ # :light_red, :light_green, :light_yellow, :light_blue,
7
+ # :light_magenta, :light_cyan, :light_white
8
+
9
+ module Gurke::Reporters
10
+ #
11
+ class DefaultReporter < NullReporter
12
+ def before_feature(feature)
13
+ io.puts "#{yellow('Feature')}: #{feature.name}"
14
+ io.puts ' ' + light_black(feature.description.split("\n").join("\n "))
15
+ io.puts
16
+ end
17
+
18
+ def before_scenario(scenario)
19
+ io.puts " #{yellow('Scenario')}: #{scenario.name}"
20
+ io.puts light_black(' Background:') if scenario.backgrounds.any?
21
+ end
22
+
23
+ def start_background(*)
24
+ @background = true
25
+ end
26
+
27
+ def end_background(*)
28
+ @background = false
29
+ end
30
+
31
+ def before_step(step, *)
32
+ io.print ' ' if @background
33
+ io.print ' '
34
+ io.print yellow(step.keyword)
35
+ io.print step.name.gsub(/"(.*?)"/, cyan('\0'))
36
+ end
37
+
38
+ def after_step(step, *)
39
+ case step.state
40
+ when :pending
41
+ print_braces yellow('pending')
42
+ when :failed
43
+ print_braces red('failure')
44
+ io.puts
45
+ io.puts red(" #{step.exception.class}:")
46
+
47
+ msg = step.exception.message.split("\n").join("\n ")
48
+ io.puts red(" #{msg}")
49
+
50
+ io.puts red(" #{step.exception.backtrace.join("\n ")}")
51
+ when :success
52
+ print_braces green('success')
53
+ else
54
+ print_braces cyan('skipped')
55
+ end
56
+ io.puts
57
+ io.flush
58
+ end
59
+
60
+ def after_scenario(*)
61
+ io.puts
62
+ end
63
+
64
+ def after_feature(*)
65
+ io.puts
66
+ end
67
+
68
+ def after_features(features)
69
+ scenarios = features.map(&:scenarios).flatten
70
+
71
+ io.puts " #{scenarios.size} scenarios: "\
72
+ "#{scenarios.select(&:failed?).size} failing, "\
73
+ "#{scenarios.select(&:pending?).size} pending"
74
+ io.puts
75
+ end
76
+
77
+ private
78
+
79
+ def print_braces(str)
80
+ io.print " (#{str})"
81
+ end
82
+
83
+ def io
84
+ $stdout
85
+ end
86
+
87
+ [:black, :red, :green, :yellow, :blue,
88
+ :magenta, :cyan, :white, :default, :light_black,
89
+ :light_red, :light_green, :light_yellow, :light_blue,
90
+ :light_magenta, :light_cyan, :light_white].each do |color|
91
+
92
+ define_method(color){|str| io.tty? ? str.send(color) : str }
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,12 @@
1
+ module Gurke::Reporters
2
+ #
3
+ # The {NullReporter} does not output anything.
4
+ #
5
+ class NullReporter < Gurke::Reporter
6
+ Gurke::Reporter::CALLBACKS.each do |cb|
7
+ class_eval <<-EOF, __FILE__, __LINE__
8
+ def #{cb}(*) end
9
+ EOF
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,18 @@
1
+ module Gurke
2
+ #
3
+ # A {RunList} is a list of {Background}, {Scenario}
4
+ # or {Step} objects that can be {#run}.
5
+ #
6
+ class RunList < Array
7
+ #
8
+ # Run all backgrounds from this list.
9
+ #
10
+ # @api private
11
+ #
12
+ def run(runner, reporter, *args)
13
+ each do |o|
14
+ o.run runner, reporter, *args
15
+ end
16
+ end
17
+ end
18
+ end
data/lib/gurke/runner.rb CHANGED
@@ -12,108 +12,32 @@ module Gurke
12
12
  end
13
13
 
14
14
  def reporter
15
- @reporter ||= Reporter.new
15
+ @reporter ||= Reporters::DefaultReporter.new
16
16
  end
17
17
 
18
18
  def run
19
19
  files.each{|f| builder.parse(f) }
20
+ features = builder.features
21
+ features.freeze
20
22
 
21
- with_hooks(:features, nil, nil) do
22
- run_features builder.features
23
- end
24
-
25
- !builder.features
26
- .map(&:scenarios)
27
- .flatten
28
- .any?{|s| s.failed? || s.pending? }
29
- end
30
-
31
- def run_features(features)
32
- reporter.start_features(features)
33
-
34
- features.each do |feature|
35
- run_feature(feature)
36
- end
37
-
38
- reporter.finish_features(features)
39
- end
40
-
41
- def run_feature(feature)
42
- reporter.start_feature(feature)
43
-
44
- feature.scenarios.each do |scenario|
45
- run_scenario(scenario, feature)
46
- end
47
-
48
- reporter.finish_feature(feature)
49
- end
50
-
51
- def run_scenario(scenario, feature)
52
- reporter.start_scenario(scenario, feature)
53
-
54
- world = world_for(scenario, feature)
55
-
56
- with_hooks(:scenario, scenario, world) do
57
- feature.backgrounds.each do |b|
58
- run_background(b, scenario, feature, world)
59
- end
60
- scenario.steps.each{|s| run_step(s, scenario, feature, world) }
61
- end
62
-
63
- reporter.finish_scenario(scenario, feature)
64
- end
65
-
66
- def run_background(background, scenario, feature, world)
67
- reporter.start_background(background)
68
-
69
- background.steps.each{|s| run_step(s, scenario, feature, world) }
70
-
71
- reporter.finish_background(background)
72
- end
73
-
74
- def run_step(step, scenario, feature, world)
75
- reporter.start_step(step, scenario, feature)
76
-
77
- result = nil
78
- with_filtered_backtrace do
79
- match = Steps.find_step(step, world, step.type)
80
-
81
- if scenario.pending? || scenario.failed?
82
- result = StepResult.new(step, :skipped)
83
- return
84
- end
85
-
86
- m = world.method(match.method_name)
87
- world.send match.method_name, *(match.params + [step])[0...m.arity]
88
- end
89
-
90
- result = StepResult.new(step, :success)
91
- rescue StepPending => e
92
- scenario.pending! e
93
- result = StepResult.new(step, :pending, e)
94
- rescue => e
95
- scenario.failed! e
96
- result = StepResult.new(step, :failed, e)
97
- ensure
98
- reporter.finish_step(result, scenario, feature)
23
+ features.run self, reporter
99
24
  end
100
25
 
101
- def with_hooks(scope, _context, world, &block)
102
- Configuration::BEFORE_HOOKS.for(scope).each do |hook|
103
- hook.run(world)
104
- end
26
+ def hook(scope, world, &block)
27
+ Configuration::BEFORE_HOOKS.for(scope).each{|hook| hook.run world }
105
28
  Configuration::AROUND_HOOKS.for(scope).reduce(block) do |blk, hook|
106
29
  proc { hook.run(world, blk) }
107
30
  end.call
31
+ ensure
108
32
  Configuration::AFTER_HOOKS.for(scope).each do |hook|
109
- hook.run(world)
33
+ begin
34
+ hook.run world
35
+ rescue => e
36
+ warn "Rescued error in after hook: #{e}\n#{e.backtrace.join("\n")}"
37
+ end
110
38
  end
111
39
  end
112
40
 
113
- def world_for(scenario, _feature)
114
- scenario.send(:world)
115
- end
116
-
117
41
  def with_filtered_backtrace
118
42
  yield
119
43
  rescue => e
@@ -123,34 +47,5 @@ module Gurke
123
47
  end
124
48
  raise
125
49
  end
126
-
127
- #
128
- class StepResult
129
- attr_reader :step, :exception, :state
130
-
131
- def initialize(step, state, err = nil)
132
- @step, @state, @exception = step, state, err
133
- end
134
-
135
- Gurke::Step.public_instance_methods(false).each do |m|
136
- define_method(m){|*args| step.send m, *args }
137
- end
138
-
139
- def failed?
140
- @state == :failed
141
- end
142
-
143
- def pending?
144
- @state == :pending
145
- end
146
-
147
- def skipped?
148
- @state == :skipped
149
- end
150
-
151
- def success?
152
- @state == :success
153
- end
154
- end
155
50
  end
156
51
  end
@@ -14,24 +14,35 @@ module Gurke
14
14
  #
15
15
  attr_reader :line
16
16
 
17
+ # The feature that contains this scenario.
18
+ #
19
+ # @return [Feature] Parent feature.
20
+ #
21
+ attr_reader :feature
22
+
23
+ # List of this scenario's steps.
24
+ #
25
+ # This does not include background steps.
26
+ #
27
+ # @return [Array<Step>] Steps.
28
+ #
29
+ attr_reader :steps
30
+
31
+ #
17
32
  attr_reader :tags
18
33
 
19
34
  # @api private
20
35
  attr_reader :raw
21
36
 
22
37
  # @api private
23
- def initialize(file, line, tags, raw)
24
- @file, @line, @tags, @raw = file, line, tags, raw
25
- end
26
-
27
- # Return list of this scenario's steps.
28
- #
29
- # This does not include background steps.
30
- #
31
- # @return [Array<Step>] Steps.
32
38
  #
33
- def steps
34
- @steps ||= []
39
+ def initialize(feature, file, line, tags, raw)
40
+ @feature = feature
41
+ @steps = RunList.new
42
+ @file = file
43
+ @line = line
44
+ @tags = tags
45
+ @raw = raw
35
46
  end
36
47
 
37
48
  # Return name of the scenario.
@@ -42,23 +53,52 @@ module Gurke
42
53
  raw.name
43
54
  end
44
55
 
56
+ # Return all backgrounds for this scenario.
57
+ #
58
+ # They are taken from the feature containing this scenario.
59
+ #
60
+ # @return [Array<Background>] Backgrounds.
61
+ #
62
+ def backgrounds
63
+ feature.backgrounds
64
+ end
65
+
66
+ # Check if scenario is pending.
67
+ #
68
+ # @return [Boolean] True if pending, false otherwise.
69
+ #
45
70
  def pending?
46
71
  @state == :pending
47
72
  end
48
73
 
74
+ # Check if scenario has failed.
75
+ #
76
+ # @return [Boolean] True if failed, false otherwise.
77
+ #
49
78
  def failed?
50
79
  @state == :failed
51
80
  end
52
81
 
82
+ # Exception that led to either pending or failed state.
83
+ #
84
+ # @return [Exception] Exception or nil of none given.
85
+ #
53
86
  attr_reader :exception
54
87
 
55
- # @api private
56
- def failed!(error)
88
+ # Call to mark scenario as failed.
89
+ #
90
+ # @param error [Exception] Given an exception as reason.
91
+ #
92
+ def failed!(error = nil)
57
93
  @exception = error
58
94
  @state = :failed
59
95
  end
60
96
 
61
- # @api private
97
+ # Call to mark scenario as pending. Will do nothing
98
+ # if scenario is already failed.
99
+ #
100
+ # @param error [Exception] Given an exception as reason.
101
+ #
62
102
  def pending!(error)
63
103
  return if failed?
64
104
 
@@ -66,8 +106,29 @@ module Gurke
66
106
  @state = :pending
67
107
  end
68
108
 
109
+ # @api private
110
+ #
111
+ def run(runner, reporter)
112
+ reporter.invoke :before_scenario, self
113
+
114
+ runner.hook :scenario, world do
115
+ run_scenario runner, reporter
116
+ end
117
+ ensure
118
+ reporter.invoke :after_scenario, self
119
+ end
120
+
69
121
  private
70
122
 
123
+ def run_scenario(runner, reporter)
124
+ reporter.invoke :start_scenario, self
125
+
126
+ feature.backgrounds.run runner, reporter, self, world
127
+ steps.run runner, reporter, self, world
128
+ ensure
129
+ reporter.invoke :end_scenario, self
130
+ end
131
+
71
132
  def world
72
133
  @world ||= begin
73
134
  cls = Class.new
data/lib/gurke/step.rb CHANGED
@@ -37,5 +37,77 @@ module Gurke
37
37
  def doc_string
38
38
  raw.doc_string.value if raw.doc_string
39
39
  end
40
+
41
+ # @api private
42
+ #
43
+ def run(runner, reporter, scenario, world)
44
+ reporter.invoke :before_step, self, scenario
45
+
46
+ result = runner.hook(:step, world) do
47
+ run_step runner, reporter, scenario, world
48
+ end
49
+ ensure
50
+ reporter.invoke :after_step, result, scenario
51
+ end
52
+
53
+ private
54
+
55
+ def run_step(runner, reporter, scenario, world)
56
+ reporter.invoke :start_step, self, scenario
57
+
58
+ result = nil
59
+ runner.with_filtered_backtrace do
60
+ match = Steps.find_step self, world, type
61
+
62
+ if scenario.pending? || scenario.failed?
63
+ result = StepResult.new self, :skipped
64
+ return result
65
+ end
66
+
67
+ m = world.method match.method_name
68
+ world.send match.method_name, *(match.params + [self])[0...m.arity]
69
+ end
70
+
71
+ result = StepResult.new self, :success
72
+ rescue StepPending => e
73
+ scenario.pending! e
74
+ result = StepResult.new self, :pending, e
75
+ rescue => e
76
+ scenario.failed! e
77
+ result = StepResult.new self, :failed, e
78
+ ensure
79
+ reporter.invoke :end_step, result, scenario
80
+ end
81
+
82
+ #
83
+ class StepResult
84
+ attr_reader :step, :exception, :state
85
+
86
+ def initialize(step, state, err = nil)
87
+ @step, @state, @exception = step, state, err
88
+ end
89
+
90
+ Step.public_instance_methods(false).each do |mth|
91
+ class_eval <<-EOS, __FILE__, __LINE__
92
+ def #{mth}(*args) @step.send #{mth}, *args; end
93
+ EOS
94
+ end
95
+
96
+ def failed?
97
+ @state == :failed
98
+ end
99
+
100
+ def pending?
101
+ @state == :pending
102
+ end
103
+
104
+ def skipped?
105
+ @state == :skipped
106
+ end
107
+
108
+ def success?
109
+ @state == :success
110
+ end
111
+ end
40
112
  end
41
113
  end
@@ -0,0 +1,17 @@
1
+ if ENV['CI'] || (defined?(:RUBY_ENGINE) && RUBY_ENGINE != 'rbx')
2
+ require 'coveralls'
3
+ Coveralls.wear! do
4
+ add_filter 'spec'
5
+ end
6
+ end
7
+
8
+ require 'bundler'
9
+ Bundler.require
10
+
11
+ require 'gurke'
12
+
13
+ Dir[File.expand_path('spec/support/**/*.rb')].each{|f| require f }
14
+
15
+ RSpec.configure do |config|
16
+ config.order = 'random'
17
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gurke
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.dev.1.b17
4
+ version: 2.0.0.dev.1.b18
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jan Graichen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-30 00:00:00.000000000 Z
11
+ date: 2014-05-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: trollop
@@ -93,8 +93,12 @@ files:
93
93
  - lib/gurke/configuration.rb
94
94
  - lib/gurke/dsl.rb
95
95
  - lib/gurke/feature.rb
96
+ - lib/gurke/feature_list.rb
96
97
  - lib/gurke/reporter.rb
98
+ - lib/gurke/reporters/default_reporter.rb
99
+ - lib/gurke/reporters/null_reporter.rb
97
100
  - lib/gurke/rspec.rb
101
+ - lib/gurke/run_list.rb
98
102
  - lib/gurke/runner.rb
99
103
  - lib/gurke/scenario.rb
100
104
  - lib/gurke/step.rb
@@ -102,6 +106,7 @@ files:
102
106
  - lib/gurke/steps.rb
103
107
  - lib/gurke/tag.rb
104
108
  - lib/gurke/version.rb
109
+ - spec/spec_helper.rb
105
110
  homepage: https://github.com/jgraichen/gurke
106
111
  licenses:
107
112
  - MIT
@@ -134,3 +139,4 @@ test_files:
134
139
  - features/gurke/step_specific_definitions.feature
135
140
  - features/support/steps/cli_steps.rb
136
141
  - features/support/steps/file_steps.rb
142
+ - spec/spec_helper.rb