cucumber 2.0.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
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