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

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