cucumber 2.0.0 → 2.0.1

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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +7 -9
  3. data/History.md +295 -265
  4. data/README.md +9 -7
  5. data/cucumber.gemspec +2 -2
  6. data/features/docs/cli/dry_run.feature +0 -3
  7. data/features/docs/cli/finding_steps.feature +28 -0
  8. data/features/docs/cli/run_specific_scenarios.feature +3 -1
  9. data/features/docs/cli/specifying_multiple_formatters.feature +22 -1
  10. data/features/docs/defining_steps/nested_steps.feature +0 -1
  11. data/features/docs/defining_steps/printing_messages.feature +4 -4
  12. data/features/docs/defining_steps/skip_scenario.feature +0 -2
  13. data/features/docs/exception_in_around_hook.feature +1 -3
  14. data/features/docs/formatters/html_formatter.feature +1 -0
  15. data/features/docs/formatters/json_formatter.feature +73 -62
  16. data/features/docs/formatters/junit_formatter.feature +130 -38
  17. data/features/docs/formatters/rerun_formatter.feature +60 -8
  18. data/features/docs/formatters/usage_formatter.feature +3 -7
  19. data/features/docs/getting_started.feature +1 -1
  20. data/features/docs/gherkin/background.feature +0 -11
  21. data/features/docs/gherkin/language_help.feature +5 -0
  22. data/features/docs/gherkin/outlines.feature +1 -3
  23. data/features/docs/gherkin/using_descriptions.feature +0 -1
  24. data/features/docs/raketask.feature +1 -1
  25. data/features/docs/writing_support_code/after_hooks.feature +22 -0
  26. data/features/lib/step_definitions/aruba_steps.rb +4 -0
  27. data/features/lib/step_definitions/junit_steps.rb +1 -1
  28. data/features/lib/support/normalise_output.rb +21 -4
  29. data/lib/cucumber/cli/configuration.rb +16 -13
  30. data/lib/cucumber/cli/main.rb +35 -10
  31. data/lib/cucumber/cli/options.rb +33 -9
  32. data/lib/cucumber/cli/rerun_file.rb +29 -0
  33. data/lib/cucumber/filters/prepare_world.rb +2 -3
  34. data/lib/cucumber/formatter/backtrace_filter.rb +40 -0
  35. data/lib/cucumber/formatter/console.rb +2 -3
  36. data/lib/cucumber/formatter/cucumber.css +1 -0
  37. data/lib/cucumber/formatter/duration_extractor.rb +28 -0
  38. data/lib/cucumber/formatter/hook_query_visitor.rb +40 -0
  39. data/lib/cucumber/formatter/html.rb +16 -1
  40. data/lib/cucumber/formatter/json.rb +287 -8
  41. data/lib/cucumber/formatter/junit.rb +92 -143
  42. data/lib/cucumber/formatter/legacy_api/adapter.rb +18 -54
  43. data/lib/cucumber/formatter/legacy_api/ast.rb +13 -0
  44. data/lib/cucumber/formatter/legacy_api/runtime_facade.rb +4 -0
  45. data/lib/cucumber/formatter/pretty.rb +2 -1
  46. data/lib/cucumber/formatter/progress.rb +20 -53
  47. data/lib/cucumber/formatter/rerun.rb +2 -1
  48. data/lib/cucumber/formatter/usage.rb +16 -22
  49. data/lib/cucumber/hooks.rb +18 -9
  50. data/lib/cucumber/multiline_argument/data_table.rb +40 -28
  51. data/lib/cucumber/platform.rb +1 -1
  52. data/lib/cucumber/rb_support/rb_hook.rb +4 -0
  53. data/lib/cucumber/running_test_case.rb +13 -4
  54. data/lib/cucumber/runtime/after_hooks.rb +7 -6
  55. data/lib/cucumber/runtime/before_hooks.rb +8 -4
  56. data/lib/cucumber/runtime/step_hooks.rb +5 -4
  57. data/lib/cucumber/runtime/support_code.rb +6 -15
  58. data/lib/cucumber/step_match.rb +1 -1
  59. data/spec/cucumber/cli/configuration_spec.rb +32 -5
  60. data/spec/cucumber/cli/main_spec.rb +3 -3
  61. data/spec/cucumber/cli/options_spec.rb +60 -1
  62. data/spec/cucumber/cli/rerun_spec.rb +89 -0
  63. data/spec/cucumber/formatter/html_spec.rb +84 -5
  64. data/spec/cucumber/formatter/json_spec.rb +757 -0
  65. data/spec/cucumber/formatter/junit_spec.rb +5 -5
  66. data/spec/cucumber/formatter/legacy_api/adapter_spec.rb +69 -8
  67. data/spec/cucumber/formatter/pretty_spec.rb +96 -0
  68. data/spec/cucumber/formatter/progress_spec.rb +85 -1
  69. data/spec/cucumber/formatter/rerun_spec.rb +3 -3
  70. data/spec/cucumber/multiline_argument/data_table_spec.rb +89 -0
  71. data/spec/cucumber/running_test_case_spec.rb +57 -1
  72. metadata +70 -60
  73. data/lib/cucumber/formatter/gherkin_formatter_adapter.rb +0 -204
  74. data/lib/cucumber/formatter/gpretty.rb +0 -24
@@ -60,6 +60,7 @@ module Cucumber
60
60
  comments.each do |comment|
61
61
  formatter.comment_line comment.to_s.strip
62
62
  end
63
+ formatter.after_comment comments
63
64
  end
64
65
  end
65
66
 
@@ -107,6 +108,7 @@ module Cucumber
107
108
 
108
109
  def accept(formatter)
109
110
  formatter.before_step(self)
111
+ Ast::Comments.new(step.comments).accept(formatter)
110
112
  messages.each { |message| formatter.puts(message) }
111
113
  embeddings.each { |embedding| embedding.send_to_formatter(formatter) }
112
114
  formatter.before_step_result *step_result_attributes
@@ -370,6 +372,17 @@ module Cucumber
370
372
 
371
373
  Features = Struct.new(:duration)
372
374
 
375
+ class Background < SimpleDelegator
376
+ def initialize(feature, node)
377
+ super node
378
+ @feature = feature
379
+ end
380
+
381
+ def feature
382
+ @feature
383
+ end
384
+ end
385
+
373
386
  end
374
387
  end
375
388
  end
@@ -23,6 +23,10 @@ module Cucumber
23
23
  def steps(status = nil)
24
24
  results.steps(status)
25
25
  end
26
+
27
+ def step_match(step_name, name_to_report=nil)
28
+ support_code.step_match(step_name, name_to_report)
29
+ end
26
30
  end
27
31
 
28
32
  end
@@ -240,7 +240,8 @@ module Cucumber
240
240
  end
241
241
 
242
242
  def print_summary(features)
243
- print_stats(features, @options)
243
+ duration = features ? features.duration : nil
244
+ print_stats(duration, @options)
244
245
  print_snippets(@options)
245
246
  print_passing_wip(@options)
246
247
  end
@@ -1,5 +1,7 @@
1
1
  require 'cucumber/formatter/console'
2
2
  require 'cucumber/formatter/io'
3
+ require 'cucumber/formatter/duration_extractor'
4
+ require 'cucumber/formatter/hook_query_visitor'
3
5
 
4
6
  module Cucumber
5
7
  module Formatter
@@ -13,73 +15,38 @@ module Cucumber
13
15
  @runtime, @io, @options = runtime, ensure_io(path_or_io, "progress"), options
14
16
  @previous_step_keyword = nil
15
17
  @snippets_input = []
18
+ @total_duration = 0
16
19
  end
17
20
 
18
- def before_features(features)
19
- print_profile_information
20
- end
21
-
22
- def after_features(features)
23
- @io.puts
24
- @io.puts
25
- print_summary(features)
26
- end
27
-
28
- def before_feature_element(*args)
29
- @exception_raised = false
30
- end
31
-
32
- def after_feature_element(*args)
33
- progress(:failed) if (defined? @exception_raised) and (@exception_raised)
34
- @exception_raised = false
35
- end
36
-
37
- def before_steps(*args)
38
- progress(:failed) if (defined? @exception_raised) and (@exception_raised)
39
- @exception_raised = false
40
- end
41
-
42
- def after_steps(*args)
43
- @exception_raised = false
44
- end
45
-
46
- def after_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background, file_colon_line)
47
- progress(status)
48
- @status = status
49
- end
50
-
51
- def before_outline_table(outline_table)
52
- @outline_table = outline_table
53
- end
54
-
55
- def after_outline_table(outline_table)
56
- @outline_table = nil
57
- end
58
-
59
- def table_cell_value(value, status)
60
- return unless @outline_table
61
- status ||= @status
62
- progress(status) unless table_header_cell?(status)
21
+ def before_test_case(_test_case)
22
+ unless @profile_information_printed
23
+ print_profile_information
24
+ @profile_information_printed = true
25
+ end
26
+ @previous_step_keyword = nil
63
27
  end
64
28
 
65
- def exception(*args)
66
- @exception_raised = true
29
+ def after_test_step(test_step, result)
30
+ progress(result.to_sym) if !HookQueryVisitor.new(test_step).hook? || result.failed?
31
+ collect_snippet_data(test_step, result) unless HookQueryVisitor.new(test_step).hook?
67
32
  end
68
33
 
69
- def before_test_case(test_case)
70
- @previous_step_keyword = nil
34
+ def after_test_case(_test_case, result)
35
+ @total_duration += DurationExtractor.new(result).result_duration
71
36
  end
72
37
 
73
- def after_test_step(test_step, result)
74
- collect_snippet_data(test_step, result)
38
+ def done
39
+ @io.puts
40
+ @io.puts
41
+ print_summary
75
42
  end
76
43
 
77
44
  private
78
45
 
79
- def print_summary(features)
46
+ def print_summary
80
47
  print_steps(:pending)
81
48
  print_steps(:failed)
82
- print_stats(features, @options)
49
+ print_stats(@total_duration, @options)
83
50
  print_snippets(@options)
84
51
  print_passing_wip(@options)
85
52
  end
@@ -8,10 +8,11 @@ module Cucumber
8
8
  def initialize(runtime, path_or_io, options)
9
9
  @io = ensure_io(path_or_io, "rerun")
10
10
  @failures = {}
11
+ @options = options
11
12
  end
12
13
 
13
14
  def after_test_case(test_case, result)
14
- return if result.passed?
15
+ return if result.ok?(@options[:strict])
15
16
  @failures[test_case.location.file] ||= []
16
17
  @failures[test_case.location.file] << test_case.location.line
17
18
  end
@@ -14,39 +14,33 @@ module Cucumber
14
14
  @runtime = runtime
15
15
  @io = ensure_io(path_or_io, "usage")
16
16
  @options = options
17
- @stepdef_to_match = Hash.new{|h,stepdef_key| h[stepdef_key] = []}
17
+ @stepdef_to_match = Hash.new { |h, stepdef_key| h[stepdef_key] = [] }
18
+ @total_duration = 0
18
19
  end
19
20
 
20
- def before_features(features)
21
- print_profile_information
22
- end
23
-
24
- def before_step(step)
25
- @step = step
26
- @start_time = Time.now
27
- end
21
+ def after_test_step(test_step, result)
22
+ return if HookQueryVisitor.new(test_step).hook?
28
23
 
29
- def before_step_result(*args)
30
- @duration = Time.now - @start_time
31
- end
32
-
33
- def after_step_result(keyword, step_match, multiline_arg, status, exception, source_indent, background, file_colon_line)
24
+ step_match = @runtime.step_match(test_step.source.last.name)
34
25
  step_definition = step_match.step_definition
35
- unless step_definition.nil? # nil if it's from a scenario outline
36
- stepdef_key = StepDefKey.new(step_definition.regexp_source, step_definition.file_colon_line)
26
+ stepdef_key = StepDefKey.new(step_definition.regexp_source, step_definition.file_colon_line)
27
+ unless @stepdef_to_match[stepdef_key].map { |key| key[:file_colon_line] }.include? test_step.location
28
+ duration = DurationExtractor.new(result).result_duration
37
29
 
38
30
  @stepdef_to_match[stepdef_key] << {
39
- :keyword => keyword,
40
- :step_match => step_match,
41
- :status => status,
42
- :file_colon_line => @step.file_colon_line,
43
- :duration => @duration
31
+ keyword: test_step.source.last.keyword,
32
+ step_match: step_match,
33
+ status: result.to_sym,
34
+ file_colon_line: test_step.location,
35
+ duration: duration
44
36
  }
45
37
  end
46
38
  super
47
39
  end
48
40
 
49
- def print_summary(features)
41
+ private
42
+
43
+ def print_summary
50
44
  add_unused_stepdefs
51
45
  aggregate_info
52
46
 
@@ -1,33 +1,42 @@
1
+ require 'pathname'
2
+ require 'cucumber/core/ast/location'
1
3
  require 'cucumber/core/test/around_hook'
2
4
 
3
5
  module Cucumber
4
6
 
5
- # Hooks quack enough like `Cucumber::Core::Ast` source nodes that we can use them as
7
+ # Hooks quack enough like `Cucumber::Core::Ast` source nodes that we can use them as
6
8
  # source for test steps
7
9
  module Hooks
8
10
 
9
11
  class << self
10
- def before_hook(source, &block)
11
- build_hook_step(source, block, BeforeHook, Core::Test::UnskippableAction)
12
+ def before_hook(source, location, &block)
13
+ build_hook_step(source, location, block, BeforeHook, Core::Test::UnskippableAction)
12
14
  end
13
15
 
14
- def after_hook(source, &block)
15
- build_hook_step(source, block, AfterHook, Core::Test::UnskippableAction)
16
+ def after_hook(source, location, &block)
17
+ build_hook_step(source, location, block, AfterHook, Core::Test::UnskippableAction)
16
18
  end
17
19
 
18
- def after_step_hook(source, &block)
20
+ def after_step_hook(source, location, &block)
19
21
  raise ArgumentError unless source.last.kind_of?(Core::Ast::Step)
20
- build_hook_step(source, block, AfterStepHook, Core::Test::Action)
22
+ build_hook_step(source, location, block, AfterStepHook, Core::Test::Action)
21
23
  end
22
24
 
23
25
  def around_hook(source, &block)
24
26
  Core::Test::AroundHook.new(&block)
25
27
  end
26
28
 
29
+ def location(hook)
30
+ filepath, line = *hook.source_location
31
+ Core::Ast::Location.new(
32
+ Pathname.new(filepath).relative_path_from(Pathname.new(Dir.pwd)).to_path,
33
+ line)
34
+ end
35
+
27
36
  private
28
37
 
29
- def build_hook_step(source, block, hook_type, action_type)
30
- action = action_type.new(&block)
38
+ def build_hook_step(source, location, block, hook_type, action_type)
39
+ action = action_type.new(location, &block)
31
40
  hook = hook_type.new(action.location)
32
41
  Core::Test::Step.new(source + [hook], action)
33
42
  end
@@ -148,6 +148,22 @@ module Cucumber
148
148
  @hashes ||= build_hashes
149
149
  end
150
150
 
151
+ # Converts this table into an Array of Hashes where the keys are symbols.
152
+ # For example, a Table built from the following plain text:
153
+ #
154
+ # | foo | Bar | Foo Bar |
155
+ # | 2 | 3 | 5 |
156
+ # | 7 | 9 | 16 |
157
+ #
158
+ # Gets converted into the following:
159
+ #
160
+ # [{:foo => '2', :bar => '3', :foo_bar => '5'}, {:foo => '7', :bar => '9', :foo_bar => '16'}]
161
+ #
162
+ def symbolic_hashes
163
+ @header_conversion_proc = lambda { |h| symbolize_key(h) }
164
+ @symbolic_hashes ||= build_hashes
165
+ end
166
+
151
167
  # Converts this table into a Hash where the first column is
152
168
  # used as keys and the second column is used as values
153
169
  #
@@ -336,7 +352,7 @@ module Cucumber
336
352
  convert_columns!
337
353
 
338
354
  original_width = cell_matrix[0].length
339
- other_table_cell_matrix = pad!(other_table.cell_matrix)
355
+ @cell_matrix, other_table_cell_matrix = pad_and_match(@cell_matrix, other_table.cell_matrix)
340
356
  padded_width = cell_matrix[0].length
341
357
 
342
358
  missing_col = cell_matrix[0].detect{|cell| cell.status == :undefined}
@@ -556,44 +572,36 @@ module Cucumber
556
572
  @hashes = @rows_hash = @col_names = @rows = @columns = nil
557
573
  end
558
574
 
559
- # Pads our own cell_matrix and returns a cell matrix of same
560
- # column width that can be used for diffing
561
- def pad!(other_cell_matrix) #:nodoc:
575
+ # Pads two cell matrices to same column width and matches columns according to header value.
576
+ # The first cell matrix is the reference matrix with the second matrix matched against it.
577
+ def pad_and_match(a_cell_matrix, other_cell_matrix) #:nodoc:
562
578
  clear_cache!
563
- cols = cell_matrix.transpose
564
- unmapped_cols = other_cell_matrix.transpose
579
+ cols = a_cell_matrix.transpose
580
+ unmatched_cols = other_cell_matrix.transpose
565
581
 
566
- mapped_cols = []
567
582
 
568
- cols.each_with_index do |col, col_index|
569
- header = col[0]
570
- candidate_cols, unmapped_cols = unmapped_cols.partition do |other_col|
571
- other_col[0] == header
572
- end
573
- raise "More than one column has the header #{header}" if candidate_cols.size > 2
583
+ header_values = cols.map(&:first)
584
+ matched_cols = []
574
585
 
575
- other_padded_col = if candidate_cols.size == 1
576
- # Found a matching column
577
- candidate_cols[0]
586
+ header_values.each_with_index do |v, i|
587
+ mapped_index = unmatched_cols.index{|unmapped_col| unmapped_col.first == v}
588
+ if (mapped_index)
589
+ matched_cols << unmatched_cols.delete_at(mapped_index)
578
590
  else
579
- mark_as_missing(cols[col_index])
580
- (0...other_cell_matrix.length).map do |row|
581
- val = row == 0 ? header.value : nil
582
- SurplusCell.new(val, self, -1)
583
- end
591
+ mark_as_missing(cols[i])
592
+ empty_col = other_cell_matrix.collect {SurplusCell.new(nil, self, -1)}
593
+ empty_col.first.value = v
594
+ matched_cols << empty_col
584
595
  end
585
- mapped_cols.insert(col_index, other_padded_col)
586
596
  end
587
597
 
588
- unmapped_cols.each_with_index do |col, col_index|
589
- empty_col = (0...cell_matrix.length).map do |row|
590
- SurplusCell.new(nil, self, -1)
591
- end
598
+
599
+ empty_col = cell_matrix.collect {SurplusCell.new(nil, self, -1)}
600
+ unmatched_cols.each do
592
601
  cols << empty_col
593
602
  end
594
603
 
595
- @cell_matrix = cols.transpose
596
- (mapped_cols + unmapped_cols).transpose
604
+ return cols.transpose, (matched_cols + unmatched_cols).transpose
597
605
  end
598
606
 
599
607
  def ensure_table(table_or_array) #:nodoc:
@@ -624,6 +632,10 @@ module Cucumber
624
632
  end
625
633
  end
626
634
 
635
+ def symbolize_key(key)
636
+ key.downcase.tr(' ', '_').to_sym
637
+ end
638
+
627
639
  # Represents a row of cells or columns of cells
628
640
  class Cells #:nodoc:
629
641
  include Enumerable
@@ -4,7 +4,7 @@ require 'rbconfig'
4
4
 
5
5
  module Cucumber
6
6
  unless defined?(Cucumber::VERSION)
7
- VERSION = '2.0.0'
7
+ VERSION = '2.0.1'
8
8
  BINARY = File.expand_path(File.dirname(__FILE__) + '/../../bin/cucumber')
9
9
  LIBDIR = File.expand_path(File.dirname(__FILE__) + '/../../lib')
10
10
  JRUBY = defined?(JRUBY_VERSION)
@@ -10,6 +10,10 @@ module Cucumber
10
10
  @proc = proc
11
11
  end
12
12
 
13
+ def source_location
14
+ @proc.source_location
15
+ end
16
+
13
17
  def invoke(pseudo_method, arguments, &block)
14
18
  @rb_language.current_world.cucumber_instance_exec(false, pseudo_method, *[arguments, block].compact, &@proc)
15
19
  end
@@ -2,7 +2,7 @@ require 'delegate'
2
2
 
3
3
  module Cucumber
4
4
  # Represents the current status of a running test case.
5
- #
5
+ #
6
6
  # This wraps a `Cucumber::Core::Test::Case` and delegates
7
7
  # many methods to that object.
8
8
  #
@@ -15,11 +15,11 @@ module Cucumber
15
15
  #
16
16
  # The test case might come from a regular Scenario or
17
17
  # a Scenario outline. You can call the `#outline?`
18
- # predicate to find out. If it's from an outline,
18
+ # predicate to find out. If it's from an outline,
19
19
  # you get a couple of extra methods.
20
20
  module RunningTestCase
21
21
  def self.new(test_case)
22
- Builder.new(test_case).result
22
+ Builder.new(test_case).running_test_case
23
23
  end
24
24
 
25
25
  class Builder
@@ -45,7 +45,7 @@ module Cucumber
45
45
  def examples_table_row(row)
46
46
  end
47
47
 
48
- def result
48
+ def running_test_case
49
49
  @factory.new(@test_case)
50
50
  end
51
51
  end
@@ -62,6 +62,15 @@ module Cucumber
62
62
  hook.tag_expressions.all? { |expression| @test_case.match_tags?(expression) }
63
63
  end
64
64
 
65
+ def exception
66
+ return unless @result.failed?
67
+ @result.exception
68
+ end
69
+
70
+ def status
71
+ @result.to_sym
72
+ end
73
+
65
74
  def failed?
66
75
  @result.failed?
67
76
  end