cucumber 2.0.0.beta.3 → 2.0.0.beta.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +20 -3
  3. data/cucumber.gemspec +2 -1
  4. data/examples/tcl/features/step_definitions/fib_steps.rb +1 -1
  5. data/features/docs/extending_cucumber/custom_formatter.feature +65 -7
  6. data/features/docs/formatters/debug_formatter.feature +24 -17
  7. data/features/docs/formatters/pretty_formatter.feature +42 -0
  8. data/features/docs/formatters/rerun_formatter.feature +3 -2
  9. data/lib/cucumber/cli/configuration.rb +3 -7
  10. data/lib/cucumber/cli/main.rb +1 -1
  11. data/lib/cucumber/{runtime → filters}/gated_receiver.rb +5 -1
  12. data/lib/cucumber/filters/quit.rb +24 -0
  13. data/lib/cucumber/filters/randomizer.rb +36 -0
  14. data/lib/cucumber/filters/tag_limits.rb +40 -0
  15. data/lib/cucumber/{runtime → filters}/tag_limits/test_case_index.rb +4 -2
  16. data/lib/cucumber/{runtime → filters}/tag_limits/verifier.rb +4 -2
  17. data/lib/cucumber/formatter/console.rb +2 -2
  18. data/lib/cucumber/formatter/debug.rb +1 -8
  19. data/lib/cucumber/formatter/fanout.rb +27 -0
  20. data/lib/cucumber/formatter/gherkin_formatter_adapter.rb +1 -3
  21. data/lib/cucumber/formatter/html.rb +12 -4
  22. data/lib/cucumber/formatter/ignore_missing_messages.rb +20 -0
  23. data/lib/cucumber/formatter/junit.rb +2 -2
  24. data/lib/cucumber/formatter/legacy_api/adapter.rb +1008 -0
  25. data/lib/cucumber/formatter/legacy_api/ast.rb +374 -0
  26. data/lib/cucumber/formatter/legacy_api/results.rb +51 -0
  27. data/lib/cucumber/formatter/legacy_api/runtime_facade.rb +30 -0
  28. data/lib/cucumber/formatter/pretty.rb +4 -0
  29. data/lib/cucumber/formatter/rerun.rb +14 -88
  30. data/lib/cucumber/language_support/language_methods.rb +0 -54
  31. data/lib/cucumber/multiline_argument/data_table.rb +3 -4
  32. data/lib/cucumber/platform.rb +1 -1
  33. data/lib/cucumber/runtime.rb +41 -107
  34. data/spec/cucumber/{runtime → filters}/gated_receiver_spec.rb +3 -3
  35. data/spec/cucumber/{runtime → filters}/tag_limits/test_case_index_spec.rb +3 -3
  36. data/spec/cucumber/{runtime → filters}/tag_limits/verifier_spec.rb +4 -4
  37. data/spec/cucumber/{runtime/tag_limits/filter_spec.rb → filters/tag_limits_spec.rb} +6 -6
  38. data/spec/cucumber/formatter/debug_spec.rb +39 -530
  39. data/spec/cucumber/formatter/html_spec.rb +56 -0
  40. data/spec/cucumber/formatter/legacy_api/adapter_spec.rb +1902 -0
  41. data/spec/cucumber/formatter/pretty_spec.rb +128 -0
  42. data/spec/cucumber/formatter/rerun_spec.rb +106 -0
  43. data/spec/cucumber/formatter/spec_helper.rb +6 -2
  44. data/spec/cucumber/rb_support/rb_language_spec.rb +2 -2
  45. data/spec/cucumber/rb_support/rb_step_definition_spec.rb +1 -1
  46. data/spec/cucumber/runtime_spec.rb +1 -5
  47. data/spec/spec_helper.rb +2 -0
  48. metadata +44 -29
  49. data/features/docs/extending_cucumber/formatter_callbacks.feature +0 -370
  50. data/features/docs/output_from_hooks.feature +0 -128
  51. data/lib/cucumber/reports/legacy_formatter.rb +0 -1349
  52. data/lib/cucumber/runtime/results.rb +0 -64
  53. data/lib/cucumber/runtime/tag_limits.rb +0 -15
  54. data/lib/cucumber/runtime/tag_limits/filter.rb +0 -31
  55. data/spec/cucumber/reports/legacy_formatter_spec.rb +0 -1860
  56. data/spec/cucumber/runtime/results_spec.rb +0 -88
@@ -1,6 +1,7 @@
1
1
  module Cucumber
2
- class Runtime
3
- module TagLimits
2
+ module Filters
3
+ class TagLimits
4
+
4
5
  class TestCaseIndex
5
6
  def initialize
6
7
  @index = Hash.new { |hash, key| hash[key] = [] }
@@ -24,6 +25,7 @@ module Cucumber
24
25
 
25
26
  attr_accessor :index
26
27
  end
28
+
27
29
  end
28
30
  end
29
31
  end
@@ -1,6 +1,7 @@
1
1
  module Cucumber
2
- class Runtime
3
- module TagLimits
2
+ module Filters
3
+ class TagLimits
4
+
4
5
  class Verifier
5
6
  def initialize(tag_limits)
6
7
  @tag_limits = tag_limits
@@ -51,6 +52,7 @@ module Cucumber
51
52
  attr_reader :limit
52
53
  attr_reader :locations
53
54
  end
55
+
54
56
  end
55
57
  end
56
58
  end
@@ -97,8 +97,8 @@ module Cucumber
97
97
 
98
98
  def collect_failing_scenarios(runtime)
99
99
  # TODO: brittle - stop coupling to types
100
- scenario_class = Cucumber::Reports::Legacy::Ast::Scenario
101
- example_table_class = Cucumber::Core::Ast::ExamplesTable
100
+ scenario_class = LegacyApi::Ast::Scenario
101
+ example_table_class = Core::Ast::ExamplesTable
102
102
 
103
103
  runtime.scenarios(:failed).select do |s|
104
104
  [scenario_class, example_table_class].include?(s.class)
@@ -6,7 +6,6 @@ module Cucumber
6
6
  class Debug
7
7
  def initialize(runtime, io, options)
8
8
  @io = io
9
- @indent = 0
10
9
  end
11
10
 
12
11
  def log(message)
@@ -19,9 +18,7 @@ module Cucumber
19
18
  end
20
19
 
21
20
  def method_missing(name, *args)
22
- @indent -= 2 if name.to_s =~ /^after/
23
21
  print(name)
24
- @indent += 2 if name.to_s =~ /^before/
25
22
  end
26
23
 
27
24
  def puts(*args)
@@ -31,11 +28,7 @@ module Cucumber
31
28
  private
32
29
 
33
30
  def print(text)
34
- @io.puts "#{indent}#{text}"
35
- end
36
-
37
- def indent
38
- (' ' * @indent)
31
+ @io.puts text
39
32
  end
40
33
  end
41
34
  end
@@ -0,0 +1,27 @@
1
+ module Cucumber
2
+ module Formatter
3
+
4
+ # Forwards any messages sent to this object to all recipients
5
+ # that respond to that message.
6
+ class Fanout < BasicObject
7
+ attr_reader :recipients
8
+ private :recipients
9
+
10
+ def initialize(recipients)
11
+ @recipients = recipients
12
+ end
13
+
14
+ def method_missing(message, *args)
15
+ recipients.each do |recipient|
16
+ recipient.send(message, *args) if recipient.respond_to?(message)
17
+ end
18
+ end
19
+
20
+ def respond_to_missing?(name, include_private = false)
21
+ recipients.any? { |recipient| recipient.respond_to?(name, include_private) }
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+ end
@@ -84,7 +84,6 @@ module Cucumber
84
84
  end
85
85
  @gf.match(match)
86
86
  end
87
- @step_time = Time.now
88
87
  end
89
88
 
90
89
  def before_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background, file_colon_line)
@@ -139,9 +138,8 @@ module Cucumber
139
138
 
140
139
  #used for capturing duration
141
140
  def after_step(step)
142
- step_finish = (Time.now - @step_time)
143
141
  unless @outline and @options[:expand] and not @in_instantiated_scenario
144
- @gf.append_duration(step_finish)
142
+ step.duration.tap { |duration| @gf.append_duration(duration.nanoseconds / 10 ** 9.0) }
145
143
  end
146
144
  end
147
145
 
@@ -12,7 +12,7 @@ module Cucumber
12
12
  Cucumber::Core::Ast::Scenario => 'scenario',
13
13
  Cucumber::Core::Ast::ScenarioOutline => 'scenario outline'
14
14
  }
15
- AST_DATA_TABLE = Cucumber::Reports::Legacy::Ast::MultilineArg::DataTable
15
+ AST_DATA_TABLE = LegacyApi::Ast::MultilineArg::DataTable
16
16
 
17
17
  include ERB::Util # for the #h method
18
18
  include Duration
@@ -179,11 +179,16 @@ module Cucumber
179
179
  @scenario_red = false
180
180
  css_class = AST_CLASSES[feature_element.class]
181
181
  @builder << "<div class='#{css_class}'>"
182
+ @in_scenario_outline = feature_element.class == Cucumber::Core::Ast::ScenarioOutline
182
183
  end
183
184
 
184
185
  def after_feature_element(feature_element)
186
+ unless @in_scenario_outline
187
+ print_messages
188
+ @builder << '</ol>'
189
+ end
185
190
  @builder << '</div>'
186
- @open_step_list = true
191
+ @in_scenario_outline = nil
187
192
  end
188
193
 
189
194
  def scenario_name(keyword, name, file_colon_line, source_indent)
@@ -211,7 +216,7 @@ module Cucumber
211
216
  end
212
217
 
213
218
  def before_examples(examples)
214
- @builder << '<div class="examples">'
219
+ @builder << '<div class="examples">'
215
220
  end
216
221
 
217
222
  def after_examples(examples)
@@ -231,10 +236,12 @@ module Cucumber
231
236
  end
232
237
 
233
238
  def after_steps(steps)
234
- @builder << '</ol>'
239
+ print_messages
240
+ @builder << '</ol>' if @in_background or @in_scenario_outline
235
241
  end
236
242
 
237
243
  def before_step(step)
244
+ print_messages
238
245
  @step_id = step.dom_id
239
246
  @step_number += 1
240
247
  @step = step
@@ -290,6 +297,7 @@ module Cucumber
290
297
 
291
298
  def exception(exception, status)
292
299
  return if @hide_this_step
300
+ print_messages
293
301
  build_exception_detail(exception)
294
302
  end
295
303
 
@@ -0,0 +1,20 @@
1
+ module Cucumber
2
+ module Formatter
3
+
4
+ class IgnoreMissingMessages < BasicObject
5
+ def initialize(receiver)
6
+ @receiver = receiver
7
+ end
8
+
9
+ def method_missing(message, *args)
10
+ @receiver.send(message, *args) if @receiver.respond_to?(message)
11
+ end
12
+
13
+ def respond_to_missing?(name, include_private = false)
14
+ @receiver.respond_to?(name, include_private)
15
+ end
16
+ end
17
+
18
+ end
19
+ end
20
+
@@ -9,8 +9,8 @@ module Cucumber
9
9
  class Junit
10
10
 
11
11
  # TODO: remove coupling to types
12
- AST_SCENARIO_OUTLINE = Cucumber::Core::Ast::ScenarioOutline
13
- AST_EXAMPLE_ROW = Reports::LegacyExampleTableRow
12
+ AST_SCENARIO_OUTLINE = Core::Ast::ScenarioOutline
13
+ AST_EXAMPLE_ROW = LegacyApi::Ast::ExampleTableRow
14
14
 
15
15
  include Io
16
16
 
@@ -0,0 +1,1008 @@
1
+ require 'forwardable'
2
+ require 'delegate'
3
+ require 'cucumber/errors'
4
+ require 'cucumber/multiline_argument'
5
+ require 'cucumber/formatter/legacy_api/ast'
6
+
7
+ module Cucumber
8
+ module Formatter
9
+ module LegacyApi
10
+
11
+ Adapter = Struct.new(:formatter, :results, :support_code, :config) do
12
+ extend Forwardable
13
+
14
+ def_delegators :formatter,
15
+ :ask
16
+
17
+ def_delegators :printer,
18
+ :embed
19
+
20
+ def before_test_case(test_case)
21
+ formatter.before_test_case(test_case)
22
+ printer.before_test_case(test_case)
23
+ end
24
+
25
+ def before_test_step(test_step)
26
+ formatter.before_test_step(test_step)
27
+ printer.before_test_step(test_step)
28
+ end
29
+
30
+ def after_test_step(test_step, result)
31
+ printer.after_test_step(test_step, result)
32
+ formatter.after_test_step(test_step, result)
33
+ end
34
+
35
+ def after_test_case(test_case, result)
36
+ record_test_case_result(test_case, result)
37
+ printer.after_test_case(test_case, result)
38
+ formatter.after_test_case(test_case, result)
39
+ end
40
+
41
+ def puts(*messages)
42
+ printer.puts(messages)
43
+ end
44
+
45
+ def done
46
+ printer.after
47
+ formatter.done
48
+ end
49
+
50
+ private
51
+
52
+ def printer
53
+ @printer ||= FeaturesPrinter.new(formatter, results, support_code, config).before
54
+ end
55
+
56
+ def record_test_case_result(test_case, result)
57
+ scenario = LegacyResultBuilder.new(result).scenario(test_case.name, test_case.location)
58
+ results.scenario_visited(scenario)
59
+ end
60
+
61
+ require 'cucumber/core/test/timer'
62
+ FeaturesPrinter = Struct.new(:formatter, :results, :support_code, :config) do
63
+ extend Forwardable
64
+
65
+ def before
66
+ timer.start
67
+ formatter.before_features(nil)
68
+ self
69
+ end
70
+
71
+ def before_test_case(test_case)
72
+ test_case.describe_source_to(self)
73
+ @child.before_test_case(test_case)
74
+ end
75
+
76
+ def before_test_step(*args)
77
+ @child.before_test_step(*args)
78
+ end
79
+
80
+ def after_test_step(test_step, result)
81
+ @child.after_test_step(test_step, result)
82
+ end
83
+
84
+ def after_test_case(*args)
85
+ @child.after_test_case(*args)
86
+ end
87
+
88
+ def feature(node, *)
89
+ if node != @current_feature
90
+ @child.after if @child
91
+ @child = FeaturePrinter.new(formatter, results, support_code, config, node).before
92
+ @current_feature = node
93
+ end
94
+ end
95
+
96
+ def scenario(node, *)
97
+ end
98
+
99
+ def scenario_outline(node, *)
100
+ end
101
+
102
+ def examples_table(node, *)
103
+ end
104
+
105
+ def examples_table_row(node, *)
106
+ end
107
+
108
+ def after
109
+ @child.after if @child
110
+ formatter.after_features Ast::Features.new(timer.sec)
111
+ self
112
+ end
113
+
114
+ def puts(messages)
115
+ @child.puts(messages)
116
+ end
117
+
118
+ def embed(src, mime_type, label)
119
+ @child.embed(src, mime_type, label)
120
+ end
121
+
122
+ private
123
+
124
+ def timer
125
+ @timer ||= Cucumber::Core::Test::Timer.new
126
+ end
127
+ end
128
+
129
+ module TestStepSource
130
+ def self.for(test_step, result)
131
+ collector = Collector.new
132
+ test_step.describe_source_to collector, result
133
+ collector.result.freeze
134
+ end
135
+
136
+ class Collector
137
+ attr_reader :result
138
+
139
+ def initialize
140
+ @result = StepSource.new
141
+ end
142
+
143
+ def method_missing(name, node, step_result, *args)
144
+ result.send "#{name}=", node
145
+ result.send "#{name}_result=", LegacyResultBuilder.new(step_result)
146
+ end
147
+ end
148
+
149
+ require 'ostruct'
150
+ class StepSource < OpenStruct
151
+ def build_step_invocation(indent, support_code, config, messages, embeddings)
152
+ step_result.step_invocation(
153
+ step_match(support_code),
154
+ step,
155
+ indent,
156
+ background,
157
+ config,
158
+ messages,
159
+ embeddings
160
+ )
161
+ end
162
+
163
+ private
164
+
165
+ def step_match(support_code)
166
+ support_code.step_match(step.name)
167
+ rescue Cucumber::Undefined
168
+ NoStepMatch.new(step, step.name)
169
+ end
170
+ end
171
+
172
+ end
173
+
174
+ Embedding = Struct.new(:src, :mime_type, :label) do
175
+
176
+ def send_to_formatter(formatter)
177
+ formatter.embed(src, mime_type, label)
178
+ end
179
+ end
180
+
181
+ FeaturePrinter = Struct.new(:formatter, :results, :support_code, :config, :node) do
182
+
183
+ def before
184
+ formatter.before_feature(node)
185
+ Ast::Comments.new(node.comments).accept(formatter)
186
+ Ast::Tags.new(node.tags).accept(formatter)
187
+ formatter.feature_name node.keyword, indented(node.legacy_conflated_name_and_description)
188
+ @delayed_messages = []
189
+ @delayed_embeddings = []
190
+ self
191
+ end
192
+
193
+ attr_reader :current_test_step_source
194
+
195
+ def before_test_case(test_case)
196
+ @before_hook_results = Ast::HookResultCollection.new
197
+ end
198
+
199
+ def before_test_step(test_step)
200
+ end
201
+
202
+ def after_test_step(test_step, result)
203
+ @current_test_step_source = TestStepSource.for(test_step, result)
204
+ # TODO: stop calling self, and describe source to another object
205
+ test_step.describe_source_to(self, result)
206
+ print_step
207
+ end
208
+
209
+ def after_test_case(*args)
210
+ if current_test_step_source && current_test_step_source.step_result.nil?
211
+ switch_step_container
212
+ end
213
+
214
+ # messages and embedding should already have been handled, but just in case...
215
+ @delayed_messages.each { |message| formatter.puts(message) }
216
+ @delayed_embeddings.each { |embedding| embedding.send_to_formatter(formatter) }
217
+ @delayed_messages = []
218
+ @delayed_embeddings = []
219
+
220
+ @child.after_test_case if @child
221
+ @previous_test_case_background = @current_test_case_background
222
+ @previous_test_case_scenario_outline = current_test_step_source && current_test_step_source.scenario_outline
223
+ end
224
+
225
+ def before_hook(location, result)
226
+ @before_hook_results << Ast::HookResult.new(LegacyResultBuilder.new(result), @delayed_messages, @delayed_embeddings)
227
+ @delayed_messages = []
228
+ @delayed_embeddings = []
229
+ end
230
+
231
+ def after_hook(location, result)
232
+ # if the scenario has no steps, we can hit this before we've created the scenario printer
233
+ # ideally we should call switch_step_container in before_step_step
234
+ switch_step_container if !@child
235
+ @child.after_hook Ast::HookResult.new(LegacyResultBuilder.new(result), @delayed_messages, @delayed_embeddings)
236
+ @delayed_messages = []
237
+ @delayed_embeddings = []
238
+ end
239
+
240
+ def after_step_hook(hook, result)
241
+ line = StepBacktraceLine.new(current_test_step_source.step)
242
+ @child.after_step_hook Ast::HookResult.new(LegacyResultBuilder.new(result).
243
+ append_to_exception_backtrace(line), @delayed_messages, @delayed_embeddings)
244
+ @delayed_messages = []
245
+ @delayed_embeddings = []
246
+ end
247
+
248
+ def background(node, *)
249
+ @current_test_case_background = node
250
+ end
251
+
252
+ def puts(messages)
253
+ @delayed_messages.push *messages
254
+ end
255
+
256
+ def embed(src, mime_type, label)
257
+ @delayed_embeddings.push Embedding.new(src, mime_type, label)
258
+ end
259
+
260
+ def step(*);end
261
+ def scenario(*);end
262
+ def scenario_outline(*);end
263
+ def examples_table(*);end
264
+ def examples_table_row(*);end
265
+ def feature(*);end
266
+
267
+ def after
268
+ @child.after if @child
269
+ formatter.after_feature(node)
270
+ self
271
+ end
272
+
273
+ private
274
+
275
+ attr_reader :before_hook_results
276
+ private :before_hook_results
277
+
278
+ def switch_step_container
279
+ switch_to_child select_step_container(current_test_step_source), current_test_step_source
280
+ end
281
+
282
+ def select_step_container(source)
283
+ if source.background
284
+ if same_background_as_previous_test_case?(source)
285
+ HiddenBackgroundPrinter.new(formatter, source.background)
286
+ else
287
+ BackgroundPrinter.new(formatter, source.background, before_hook_results)
288
+ end
289
+ elsif source.scenario
290
+ ScenarioPrinter.new(formatter, source.scenario, before_hook_results)
291
+ elsif source.scenario_outline
292
+ if same_scenario_outline_as_previous_test_case?(source) and @previous_outline_child
293
+ @previous_outline_child
294
+ else
295
+ ScenarioOutlinePrinter.new(formatter, config, source.scenario_outline)
296
+ end
297
+ else
298
+ raise 'unknown step container'
299
+ end
300
+ end
301
+
302
+ def same_background_as_previous_test_case?(source)
303
+ source.background == @previous_test_case_background
304
+ end
305
+
306
+ def same_scenario_outline_as_previous_test_case?(source)
307
+ source.scenario_outline == @previous_test_case_scenario_outline
308
+ end
309
+
310
+ def print_step
311
+ return unless current_test_step_source.step_result
312
+ switch_step_container
313
+
314
+ if current_test_step_source.scenario_outline
315
+ @child.examples_table(current_test_step_source.examples_table)
316
+ @child.examples_table_row(current_test_step_source.examples_table_row, before_hook_results)
317
+ end
318
+
319
+ if @failed_hidden_background_step
320
+ indent = Indent.new(@child.node)
321
+ step_invocation = @failed_hidden_background_step.build_step_invocation(indent, support_code, config, messages = [], embeddings = [])
322
+ @child.step_invocation(step_invocation, @failed_hidden_background_step)
323
+ @failed_hidden_background_step = nil
324
+ end
325
+
326
+ unless @last_step == current_test_step_source.step
327
+ indent ||= Indent.new(@child.node)
328
+ step_invocation = current_test_step_source.build_step_invocation(indent, support_code, config, @delayed_messages, @delayed_embeddings)
329
+ results.step_visited step_invocation
330
+ @child.step_invocation(step_invocation, current_test_step_source)
331
+ @last_step = current_test_step_source.step
332
+ end
333
+ @delayed_messages = []
334
+ @delayed_embeddings = []
335
+ end
336
+
337
+ def switch_to_child(child, source)
338
+ return if @child == child
339
+ if @child
340
+ if from_first_background(@child)
341
+ @first_background_failed = @child.failed?
342
+ elsif from_hidden_background(@child)
343
+ if not @first_background_failed
344
+ @failed_hidden_background_step = @child.get_failed_step_source
345
+ end
346
+ if @previous_outline_child
347
+ @previous_outline_child.after unless same_scenario_outline_as_previous_test_case?(source)
348
+ end
349
+ end
350
+ unless from_scenario_outline_to_hidden_backgroud(@child, child)
351
+ @child.after
352
+ @previous_outline_child = nil
353
+ else
354
+ @previous_outline_child = @child
355
+ end
356
+ end
357
+ child.before unless to_scenario_outline(child) and same_scenario_outline_as_previous_test_case?(source)
358
+ @child = child
359
+ end
360
+
361
+ def from_scenario_outline_to_hidden_backgroud(from, to)
362
+ from.class.name == ScenarioOutlinePrinter.name and
363
+ to.class.name == HiddenBackgroundPrinter.name
364
+ end
365
+
366
+ def from_first_background(from)
367
+ from.class.name == BackgroundPrinter.name
368
+ end
369
+
370
+ def from_hidden_background(from)
371
+ from.class.name == HiddenBackgroundPrinter.name
372
+ end
373
+
374
+ def to_scenario_outline(to)
375
+ to.class.name == ScenarioOutlinePrinter.name
376
+ end
377
+
378
+ def indented(nasty_old_conflation_of_name_and_description)
379
+ indent = ""
380
+ nasty_old_conflation_of_name_and_description.split("\n").map do |l|
381
+ s = "#{indent}#{l}"
382
+ indent = " "
383
+ s
384
+ end.join("\n")
385
+ end
386
+
387
+ end
388
+
389
+ module PrintsAfterHooks
390
+ def after_hook_results
391
+ @after_hook_results ||= Ast::HookResultCollection.new
392
+ end
393
+
394
+ def after_hook(result)
395
+ after_hook_results << result
396
+ end
397
+ end
398
+
399
+ # Basic printer used by default
400
+ class AfterHookPrinter
401
+ include Cucumber.initializer(:formatter)
402
+
403
+ include PrintsAfterHooks
404
+
405
+ def after
406
+ after_hook_results.accept(formatter)
407
+ end
408
+ end
409
+
410
+ BackgroundPrinter = Struct.new(:formatter, :node, :before_hook_results) do
411
+
412
+ def before
413
+ formatter.before_background node
414
+ formatter.background_name node.keyword, node.legacy_conflated_name_and_description, node.location.to_s, indent.of(node)
415
+ before_hook_results.accept(formatter)
416
+ self
417
+ end
418
+
419
+ def after_step_hook(result)
420
+ result.accept formatter
421
+ end
422
+
423
+ def step_invocation(step_invocation, source)
424
+ @child ||= StepsPrinter.new(formatter).before
425
+ @child.step_invocation step_invocation
426
+ if source.step_result.status == :failed
427
+ @failed = true
428
+ end
429
+ end
430
+
431
+ def after
432
+ @child.after if @child
433
+ formatter.after_background(node)
434
+ self
435
+ end
436
+
437
+ def failed?
438
+ @failed
439
+ end
440
+
441
+ private
442
+
443
+ def indent
444
+ @indent ||= Indent.new(node)
445
+ end
446
+ end
447
+
448
+ # Printer to handle background steps for anything but the first scenario in a
449
+ # feature. These steps should not be printed.
450
+ class HiddenBackgroundPrinter < Struct.new(:formatter, :node)
451
+ def get_failed_step_source
452
+ return @source_of_failed_step
453
+ end
454
+
455
+ def step_invocation(step_invocation, source)
456
+ if source.step_result.status == :failed
457
+ @source_of_failed_step = source
458
+ end
459
+ end
460
+
461
+ def before;self;end
462
+ def after;self;end
463
+ def before_hook(*);end
464
+ def after_hook(*);end
465
+ def after_step_hook(*);end
466
+ def examples_table(*);end
467
+ def after_test_case(*);end
468
+ end
469
+
470
+ ScenarioPrinter = Struct.new(:formatter, :node, :before_hook_results) do
471
+ include PrintsAfterHooks
472
+
473
+ def before
474
+ formatter.before_feature_element(node)
475
+ Ast::Tags.new(node.tags).accept(formatter)
476
+ formatter.scenario_name node.keyword, node.legacy_conflated_name_and_description, node.location.to_s, indent.of(node)
477
+ before_hook_results.accept(formatter)
478
+ self
479
+ end
480
+
481
+ def step_invocation(step_invocation, source)
482
+ @child ||= StepsPrinter.new(formatter).before
483
+ @child.step_invocation step_invocation
484
+ @last_step_result = source.step_result
485
+ end
486
+
487
+ def after_step_hook(result)
488
+ result.accept formatter
489
+ end
490
+
491
+ def after_test_case(*args)
492
+ after
493
+ end
494
+
495
+ def after
496
+ return if @done
497
+ @child.after if @child
498
+ # TODO - the last step result might not accurately reflect the
499
+ # overall scenario result.
500
+ scenario = last_step_result.scenario(node.name, node.location)
501
+ after_hook_results.accept(formatter)
502
+ formatter.after_feature_element(scenario)
503
+ @done = true
504
+ self
505
+ end
506
+
507
+ private
508
+
509
+ def last_step_result
510
+ @last_step_result || LegacyResultBuilder.new(Core::Test::Result::Unknown.new)
511
+ end
512
+
513
+ def indent
514
+ @indent ||= Indent.new(node)
515
+ end
516
+ end
517
+
518
+ StepsPrinter = Struct.new(:formatter) do
519
+ def before
520
+ formatter.before_steps(nil)
521
+ self
522
+ end
523
+
524
+ def step_invocation(step_invocation)
525
+ steps << step_invocation
526
+ step_invocation.accept(formatter)
527
+ self
528
+ end
529
+
530
+ def after
531
+ formatter.after_steps(steps)
532
+ self
533
+ end
534
+
535
+ private
536
+
537
+ def steps
538
+ @steps ||= Ast::StepInvocations.new
539
+ end
540
+
541
+ end
542
+
543
+ ScenarioOutlinePrinter = Struct.new(:formatter, :configuration, :node) do
544
+ extend Forwardable
545
+ def_delegators :@child, :after_hook, :after_step_hook
546
+
547
+ def before
548
+ formatter.before_feature_element(node)
549
+ Ast::Tags.new(node.tags).accept(formatter)
550
+ formatter.scenario_name node.keyword, node.legacy_conflated_name_and_description, node.location.to_s, indent.of(node)
551
+ OutlineStepsPrinter.new(formatter, configuration, indent).print(node)
552
+ self
553
+ end
554
+
555
+ def step_invocation(step_invocation, source)
556
+ node, result = source.step, source.step_result
557
+ @last_step_result = result
558
+ @child.step_invocation(step_invocation, source)
559
+ end
560
+
561
+ def examples_table(examples_table)
562
+ @child ||= ExamplesArrayPrinter.new(formatter, configuration).before
563
+ @child.examples_table(examples_table)
564
+ end
565
+
566
+ def examples_table_row(node, before_hook_results)
567
+ @child.examples_table_row(node, before_hook_results)
568
+ end
569
+
570
+ def after_test_case
571
+ @child.after_test_case
572
+ end
573
+
574
+ def after
575
+ @child.after if @child
576
+ # TODO - the last step result might not accurately reflect the
577
+ # overall scenario result.
578
+ scenario_outline = last_step_result.scenario_outline(node.name, node.location)
579
+ formatter.after_feature_element(scenario_outline)
580
+ self
581
+ end
582
+
583
+ private
584
+
585
+ def last_step_result
586
+ @last_step_result || Core::Test::Result::Unknown.new
587
+ end
588
+
589
+ def indent
590
+ @indent ||= Indent.new(node)
591
+ end
592
+ end
593
+
594
+ OutlineStepsPrinter = Struct.new(:formatter, :configuration, :indent, :outline) do
595
+ def print(node)
596
+ node.describe_to self
597
+ steps_printer.after
598
+ end
599
+
600
+ def scenario_outline(node, &descend)
601
+ descend.call(self)
602
+ end
603
+
604
+ def outline_step(step)
605
+ step_match = NoStepMatch.new(step, step.name)
606
+ step_invocation = LegacyResultBuilder.new(Core::Test::Result::Skipped.new).
607
+ step_invocation(step_match, step, indent, background = nil, configuration, messages = [], embeddings = [])
608
+ steps_printer.step_invocation step_invocation
609
+ end
610
+
611
+ def examples_table(*);end
612
+
613
+ private
614
+
615
+ def steps_printer
616
+ @steps_printer ||= StepsPrinter.new(formatter).before
617
+ end
618
+ end
619
+
620
+ ExamplesArrayPrinter = Struct.new(:formatter, :configuration) do
621
+ extend Forwardable
622
+ def_delegators :@child, :step_invocation, :after_hook, :after_step_hook, :after_test_case, :examples_table_row
623
+
624
+ def before
625
+ formatter.before_examples_array(:examples_array)
626
+ self
627
+ end
628
+
629
+ def examples_table(examples_table)
630
+ return if examples_table == @current
631
+ @child.after if @child
632
+ @child = ExamplesTablePrinter.new(formatter, configuration, examples_table).before
633
+ @current = examples_table
634
+ end
635
+
636
+ def after
637
+ @child.after if @child
638
+ formatter.after_examples_array
639
+ self
640
+ end
641
+ end
642
+
643
+ ExamplesTablePrinter = Struct.new(:formatter, :configuration, :node) do
644
+ extend Forwardable
645
+ def_delegators :@child, :step_invocation, :after_hook, :after_step_hook, :after_test_case
646
+
647
+ def before
648
+ formatter.before_examples(node)
649
+ formatter.examples_name(node.keyword, node.legacy_conflated_name_and_description)
650
+ formatter.before_outline_table(legacy_table)
651
+ if !configuration.expand?
652
+ HeaderTableRowPrinter.new(formatter, ExampleTableRow.new(node.header), Ast::Node.new).before.after
653
+ end
654
+ self
655
+ end
656
+
657
+ def examples_table_row(examples_table_row, before_hook_results)
658
+ return if examples_table_row == @current
659
+ @child.after if @child
660
+ row = ExampleTableRow.new(examples_table_row)
661
+ if !configuration.expand?
662
+ @child = TableRowPrinter.new(formatter, row, before_hook_results).before
663
+ else
664
+ @child = ExpandTableRowPrinter.new(formatter, row, before_hook_results).before
665
+ end
666
+ @current = examples_table_row
667
+ end
668
+
669
+ def after_test_case(*args)
670
+ @child.after_test_case
671
+ end
672
+
673
+ def after
674
+ @child.after if @child
675
+ formatter.after_outline_table(node)
676
+ formatter.after_examples(node)
677
+ self
678
+ end
679
+
680
+ private
681
+
682
+ def legacy_table
683
+ LegacyTable.new(node)
684
+ end
685
+
686
+ class ExampleTableRow < SimpleDelegator
687
+ def dom_id
688
+ file_colon_line.gsub(/[\/\.:]/, '_')
689
+ end
690
+ end
691
+
692
+ LegacyTable = Struct.new(:node) do
693
+ def col_width(index)
694
+ max_width = FindMaxWidth.new(index)
695
+ node.describe_to max_width
696
+ max_width.result
697
+ end
698
+
699
+ require 'gherkin/formatter/escaping'
700
+ FindMaxWidth = Struct.new(:index) do
701
+ include ::Gherkin::Formatter::Escaping
702
+
703
+ def examples_table(table, &descend)
704
+ @result = char_length_of(table.header.values[index])
705
+ descend.call(self)
706
+ end
707
+
708
+ def examples_table_row(row, &descend)
709
+ width = char_length_of(row.values[index])
710
+ @result = width if width > result
711
+ end
712
+
713
+ def result
714
+ @result ||= 0
715
+ end
716
+
717
+ private
718
+ def char_length_of(cell)
719
+ escape_cell(cell).unpack('U*').length
720
+ end
721
+ end
722
+ end
723
+ end
724
+
725
+ class TableRowPrinterBase < Struct.new(:formatter, :node, :before_hook_results)
726
+ include PrintsAfterHooks
727
+
728
+ def after_step_hook(result)
729
+ @after_step_hook_result ||= Ast::HookResultCollection.new
730
+ @after_step_hook_result << result
731
+ end
732
+
733
+ def after_test_case(*args)
734
+ after
735
+ end
736
+
737
+ private
738
+
739
+ def indent
740
+ :not_needed
741
+ end
742
+
743
+ def legacy_table_row
744
+ Ast::ExampleTableRow.new(exception, @status, node.values, node.location, node.language)
745
+ end
746
+
747
+ def exception
748
+ return nil unless @failed_step
749
+ @failed_step.exception
750
+ end
751
+ end
752
+
753
+ class HeaderTableRowPrinter < TableRowPrinterBase
754
+ def legacy_table_row
755
+ Ast::ExampleTableRow.new(exception, @status, node.values, node.location, Ast::NullLanguage.new)
756
+ end
757
+
758
+ def before
759
+ formatter.before_table_row(node)
760
+ self
761
+ end
762
+
763
+ def after
764
+ node.values.each do |value|
765
+ formatter.before_table_cell(value)
766
+ formatter.table_cell_value(value, :skipped_param)
767
+ formatter.after_table_cell(value)
768
+ end
769
+ formatter.after_table_row(legacy_table_row)
770
+ self
771
+ end
772
+ end
773
+
774
+
775
+ class TableRowPrinter < TableRowPrinterBase
776
+ def before
777
+ before_hook_results.accept(formatter)
778
+ formatter.before_table_row(node)
779
+ self
780
+ end
781
+
782
+ def step_invocation(step_invocation, source)
783
+ result = source.step_result
784
+ step_invocation.messages.each { |message| formatter.puts(message) }
785
+ step_invocation.embeddings.each { |embedding| embedding.send_to_formatter(formatter) }
786
+ @failed_step = step_invocation if result.status == :failed
787
+ @status = step_invocation.status unless already_failed?
788
+ end
789
+
790
+ def after
791
+ return if @done
792
+ @child.after if @child
793
+ node.values.each do |value|
794
+ formatter.before_table_cell(value)
795
+ formatter.table_cell_value(value, @status || :skipped)
796
+ formatter.after_table_cell(value)
797
+ end
798
+ @after_step_hook_result.send_output_to(formatter) if @after_step_hook_result
799
+ after_hook_results.send_output_to(formatter)
800
+ formatter.after_table_row(legacy_table_row)
801
+ @after_step_hook_result.describe_exception_to(formatter) if @after_step_hook_result
802
+ after_hook_results.describe_exception_to(formatter)
803
+ @done = true
804
+ self
805
+ end
806
+
807
+ private
808
+
809
+ def already_failed?
810
+ @status == :failed || @status == :undefined || @status == :pending
811
+ end
812
+ end
813
+
814
+ class ExpandTableRowPrinter < TableRowPrinterBase
815
+ def before
816
+ before_hook_results.accept(formatter)
817
+ self
818
+ end
819
+
820
+ def step_invocation(step_invocation, source)
821
+ result = source.step_result
822
+ @table_row ||= legacy_table_row
823
+ step_invocation.indent.record_width_of(@table_row)
824
+ if !@scenario_name_printed
825
+ print_scenario_name(step_invocation, @table_row)
826
+ @scenario_name_printed = true
827
+ end
828
+ step_invocation.accept(formatter)
829
+ @failed_step = step_invocation if result.status == :failed
830
+ @status = step_invocation.status unless @status == :failed
831
+ end
832
+
833
+ def after
834
+ return if @done
835
+ @child.after if @child
836
+ @after_step_hook_result.accept(formatter) if @after_step_hook_result
837
+ after_hook_results.accept(formatter)
838
+ @done = true
839
+ self
840
+ end
841
+
842
+ private
843
+
844
+ def print_scenario_name(step_invocation, table_row)
845
+ formatter.scenario_name table_row.keyword, table_row.name, node.location.to_s, step_invocation.indent.of(table_row)
846
+ end
847
+ end
848
+
849
+ class Indent
850
+ def initialize(node)
851
+ @widths = []
852
+ node.describe_to(self)
853
+ end
854
+
855
+ [:background, :scenario, :scenario_outline].each do |node_name|
856
+ define_method(node_name) do |node, &descend|
857
+ record_width_of node
858
+ descend.call(self)
859
+ end
860
+ end
861
+
862
+ [:step, :outline_step].each do |node_name|
863
+ define_method(node_name) do |node|
864
+ record_width_of node
865
+ end
866
+ end
867
+
868
+ def examples_table(*); end
869
+ def examples_table_row(*); end
870
+
871
+ def of(node)
872
+ # The length of the instantiated steps in --expand mode are currently
873
+ # not included in the calculation of max => make sure to return >= 1
874
+ [1, max - node.name.length - node.keyword.length].max
875
+ end
876
+
877
+ def record_width_of(node)
878
+ @widths << node.keyword.length + node.name.length + 1
879
+ end
880
+
881
+ private
882
+
883
+ def max
884
+ @widths.max
885
+ end
886
+ end
887
+
888
+ class LegacyResultBuilder
889
+ attr_reader :status
890
+ def initialize(result)
891
+ @result = result
892
+ @result.describe_to(self)
893
+ end
894
+
895
+ def passed
896
+ @status = :passed
897
+ end
898
+
899
+ def failed
900
+ @status = :failed
901
+ end
902
+
903
+ def undefined
904
+ @status = :undefined
905
+ end
906
+
907
+ def skipped
908
+ @status = :skipped
909
+ end
910
+
911
+ def pending(exception, *)
912
+ @exception = exception
913
+ @status = :pending
914
+ end
915
+
916
+ def exception(exception, *)
917
+ @exception = exception
918
+ end
919
+
920
+ def append_to_exception_backtrace(line)
921
+ @exception.set_backtrace(@exception.backtrace + [line.to_s]) if @exception
922
+ return self
923
+ end
924
+
925
+ def duration(duration, *)
926
+ @duration = duration
927
+ end
928
+
929
+ def step_invocation(step_match, step, indent, background, configuration, messages, embeddings)
930
+ Ast::StepInvocation.new(step_match, @status, @duration, step_exception(step, configuration), indent, background, step, messages, embeddings)
931
+ end
932
+
933
+ def scenario(name, location)
934
+ Ast::Scenario.new(@status, name, location)
935
+ end
936
+
937
+ def scenario_outline(name, location)
938
+ Ast::ScenarioOutline.new(@status, name, location)
939
+ end
940
+
941
+ def describe_exception_to(formatter)
942
+ formatter.exception(filtered_exception, @status) if @exception
943
+ end
944
+
945
+ private
946
+
947
+ def step_exception(step, configuration)
948
+ return filtered_step_exception(step) if @exception
949
+ return nil unless @status == :undefined && configuration.strict?
950
+ @exception = Cucumber::Undefined.from(@result, step.name)
951
+ filtered_step_exception(step)
952
+ end
953
+
954
+ def filtered_exception
955
+ BacktraceFilter.new(@exception.dup).exception
956
+ end
957
+
958
+ def filtered_step_exception(step)
959
+ exception = filtered_exception
960
+ exception.backtrace << StepBacktraceLine.new(step).to_s
961
+ return exception
962
+ end
963
+ end
964
+
965
+ end
966
+
967
+ class StepBacktraceLine < Struct.new(:step)
968
+ def to_s
969
+ step.backtrace_line
970
+ end
971
+ end
972
+
973
+ require 'cucumber/platform'
974
+ class BacktraceFilter
975
+ BACKTRACE_FILTER_PATTERNS = \
976
+ [/vendor\/rails|lib\/cucumber|bin\/cucumber:|lib\/rspec|gems\/|minitest|test\/unit|.gem\/ruby|lib\/ruby/]
977
+ if(::Cucumber::JRUBY)
978
+ BACKTRACE_FILTER_PATTERNS << /org\/jruby/
979
+ end
980
+ PWD_PATTERN = /#{::Regexp.escape(::Dir.pwd)}\//m
981
+
982
+ def initialize(exception)
983
+ @exception = exception
984
+ end
985
+
986
+ def exception
987
+ return @exception if ::Cucumber.use_full_backtrace
988
+ @exception.backtrace.each{|line| line.gsub!(PWD_PATTERN, "./")}
989
+
990
+ filtered = (@exception.backtrace || []).reject do |line|
991
+ BACKTRACE_FILTER_PATTERNS.detect { |p| line =~ p }
992
+ end
993
+
994
+ if ::ENV['CUCUMBER_TRUNCATE_OUTPUT']
995
+ # Strip off file locations
996
+ filtered = filtered.map do |line|
997
+ line =~ /(.*):in `/ ? $1 : line
998
+ end
999
+ end
1000
+
1001
+ @exception.set_backtrace(filtered)
1002
+ @exception
1003
+ end
1004
+ end
1005
+
1006
+ end
1007
+ end
1008
+ end