cucumber 3.1.2 → 8.0.0

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 (125) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1880 -1146
  3. data/CONTRIBUTING.md +220 -61
  4. data/README.md +143 -22
  5. data/bin/cucumber +1 -1
  6. data/lib/autotest/cucumber_mixin.rb +49 -53
  7. data/lib/autotest/discover.rb +3 -2
  8. data/lib/cucumber/cli/configuration.rb +32 -7
  9. data/lib/cucumber/cli/main.rb +16 -15
  10. data/lib/cucumber/cli/options.rb +111 -79
  11. data/lib/cucumber/cli/profile_loader.rb +45 -26
  12. data/lib/cucumber/cli/rerun_file.rb +1 -1
  13. data/lib/cucumber/configuration.rb +47 -31
  14. data/lib/cucumber/constantize.rb +3 -6
  15. data/lib/cucumber/deprecate.rb +32 -7
  16. data/lib/cucumber/errors.rb +5 -7
  17. data/lib/cucumber/events/envelope.rb +9 -0
  18. data/lib/cucumber/events/gherkin_source_parsed.rb +11 -0
  19. data/lib/cucumber/events/hook_test_step_created.rb +12 -0
  20. data/lib/cucumber/events/step_activated.rb +0 -5
  21. data/lib/cucumber/events/step_definition_registered.rb +0 -5
  22. data/lib/cucumber/events/test_case_created.rb +12 -0
  23. data/lib/cucumber/events/test_case_ready.rb +12 -0
  24. data/lib/cucumber/events/test_run_finished.rb +2 -1
  25. data/lib/cucumber/events/test_step_created.rb +12 -0
  26. data/lib/cucumber/events/undefined_parameter_type.rb +9 -0
  27. data/lib/cucumber/events.rb +15 -8
  28. data/lib/cucumber/file_specs.rb +8 -7
  29. data/lib/cucumber/filters/activate_steps.rb +6 -3
  30. data/lib/cucumber/filters/broadcast_test_case_ready_event.rb +12 -0
  31. data/lib/cucumber/filters/prepare_world.rb +5 -9
  32. data/lib/cucumber/filters/quit.rb +1 -3
  33. data/lib/cucumber/filters/tag_limits/verifier.rb +3 -7
  34. data/lib/cucumber/filters/tag_limits.rb +1 -3
  35. data/lib/cucumber/filters.rb +1 -0
  36. data/lib/cucumber/formatter/ansicolor.rb +74 -86
  37. data/lib/cucumber/formatter/ast_lookup.rb +163 -0
  38. data/lib/cucumber/formatter/backtrace_filter.rb +10 -7
  39. data/lib/cucumber/formatter/console.rb +76 -68
  40. data/lib/cucumber/formatter/console_counts.rb +4 -9
  41. data/lib/cucumber/formatter/console_issues.rb +12 -4
  42. data/lib/cucumber/formatter/duration.rb +1 -1
  43. data/lib/cucumber/formatter/duration_extractor.rb +4 -1
  44. data/lib/cucumber/formatter/errors.rb +7 -0
  45. data/lib/cucumber/formatter/fanout.rb +3 -1
  46. data/lib/cucumber/formatter/html.rb +11 -598
  47. data/lib/cucumber/formatter/http_io.rb +152 -0
  48. data/lib/cucumber/formatter/ignore_missing_messages.rb +2 -2
  49. data/lib/cucumber/formatter/interceptor.rb +11 -30
  50. data/lib/cucumber/formatter/io.rb +57 -13
  51. data/lib/cucumber/formatter/json.rb +119 -124
  52. data/lib/cucumber/formatter/junit.rb +75 -55
  53. data/lib/cucumber/formatter/message.rb +23 -0
  54. data/lib/cucumber/formatter/message_builder.rb +256 -0
  55. data/lib/cucumber/formatter/pretty.rb +370 -153
  56. data/lib/cucumber/formatter/progress.rb +31 -32
  57. data/lib/cucumber/formatter/publish_banner_printer.rb +77 -0
  58. data/lib/cucumber/formatter/query/hook_by_test_step.rb +32 -0
  59. data/lib/cucumber/formatter/query/pickle_by_test.rb +26 -0
  60. data/lib/cucumber/formatter/query/pickle_step_by_test_step.rb +26 -0
  61. data/lib/cucumber/formatter/query/step_definitions_by_test_step.rb +40 -0
  62. data/lib/cucumber/formatter/query/test_case_started_by_test_case.rb +42 -0
  63. data/lib/cucumber/formatter/rerun.rb +24 -4
  64. data/lib/cucumber/formatter/stepdefs.rb +1 -2
  65. data/lib/cucumber/formatter/steps.rb +8 -6
  66. data/lib/cucumber/formatter/summary.rb +17 -8
  67. data/lib/cucumber/formatter/unicode.rb +18 -20
  68. data/lib/cucumber/formatter/url_reporter.rb +17 -0
  69. data/lib/cucumber/formatter/usage.rb +18 -15
  70. data/lib/cucumber/gherkin/data_table_parser.rb +18 -6
  71. data/lib/cucumber/gherkin/formatter/ansi_escapes.rb +14 -18
  72. data/lib/cucumber/gherkin/formatter/escaping.rb +2 -2
  73. data/lib/cucumber/gherkin/steps_parser.rb +17 -8
  74. data/lib/cucumber/glue/dsl.rb +29 -15
  75. data/lib/cucumber/glue/hook.rb +37 -11
  76. data/lib/cucumber/glue/invoke_in_world.rb +17 -22
  77. data/lib/cucumber/glue/proto_world.rb +47 -53
  78. data/lib/cucumber/glue/registry_and_more.rb +62 -17
  79. data/lib/cucumber/glue/registry_wrapper.rb +31 -0
  80. data/lib/cucumber/glue/snippet.rb +23 -22
  81. data/lib/cucumber/glue/step_definition.rb +48 -23
  82. data/lib/cucumber/glue/world_factory.rb +1 -1
  83. data/lib/cucumber/hooks.rb +12 -11
  84. data/lib/cucumber/multiline_argument/data_table/diff_matrices.rb +4 -3
  85. data/lib/cucumber/multiline_argument/data_table.rb +143 -123
  86. data/lib/cucumber/multiline_argument/doc_string.rb +1 -1
  87. data/lib/cucumber/multiline_argument.rb +4 -6
  88. data/lib/cucumber/platform.rb +5 -5
  89. data/lib/cucumber/rake/task.rb +34 -25
  90. data/lib/cucumber/rspec/disable_option_parser.rb +15 -11
  91. data/lib/cucumber/rspec/doubles.rb +3 -5
  92. data/lib/cucumber/running_test_case.rb +3 -53
  93. data/lib/cucumber/runtime/after_hooks.rb +8 -4
  94. data/lib/cucumber/runtime/before_hooks.rb +8 -4
  95. data/lib/cucumber/runtime/for_programming_languages.rb +4 -2
  96. data/lib/cucumber/runtime/meta_message_builder.rb +106 -0
  97. data/lib/cucumber/runtime/step_hooks.rb +6 -2
  98. data/lib/cucumber/runtime/support_code.rb +16 -15
  99. data/lib/cucumber/runtime/user_interface.rb +10 -19
  100. data/lib/cucumber/runtime.rb +78 -76
  101. data/lib/cucumber/step_definition_light.rb +4 -3
  102. data/lib/cucumber/step_definitions.rb +2 -2
  103. data/lib/cucumber/step_match.rb +17 -20
  104. data/lib/cucumber/step_match_search.rb +5 -3
  105. data/lib/cucumber/term/ansicolor.rb +72 -48
  106. data/lib/cucumber/term/banner.rb +57 -0
  107. data/lib/cucumber/version +1 -1
  108. data/lib/cucumber.rb +3 -2
  109. data/lib/simplecov_setup.rb +1 -1
  110. metadata +279 -81
  111. data/lib/cucumber/core_ext/string.rb +0 -11
  112. data/lib/cucumber/events/gherkin_source_parsed.rb~ +0 -14
  113. data/lib/cucumber/formatter/ast_lookup.rb~ +0 -9
  114. data/lib/cucumber/formatter/cucumber.css +0 -286
  115. data/lib/cucumber/formatter/cucumber.sass +0 -247
  116. data/lib/cucumber/formatter/hook_query_visitor.rb +0 -42
  117. data/lib/cucumber/formatter/html_builder.rb +0 -121
  118. data/lib/cucumber/formatter/inline-js.js +0 -30
  119. data/lib/cucumber/formatter/jquery-min.js +0 -154
  120. data/lib/cucumber/formatter/json_pretty.rb +0 -11
  121. data/lib/cucumber/formatter/legacy_api/adapter.rb +0 -1028
  122. data/lib/cucumber/formatter/legacy_api/ast.rb +0 -394
  123. data/lib/cucumber/formatter/legacy_api/results.rb +0 -50
  124. data/lib/cucumber/formatter/legacy_api/runtime_facade.rb +0 -32
  125. data/lib/cucumber/step_argument.rb +0 -25
@@ -2,10 +2,12 @@
2
2
 
3
3
  require 'cucumber/formatter/progress'
4
4
  require 'cucumber/step_definition_light'
5
+ require 'cucumber/formatter/console'
5
6
 
6
7
  module Cucumber
7
8
  module Formatter
8
9
  class Usage < Progress
10
+ include Console
9
11
  class StepDefKey < StepDefinitionLight
10
12
  attr_accessor :mean_duration, :status
11
13
  end
@@ -17,7 +19,7 @@ module Cucumber
17
19
  @matches = {}
18
20
  config.on_event :step_activated do |event|
19
21
  test_step, step_match = *event.attributes
20
- @matches[test_step.source] = step_match
22
+ @matches[test_step.to_s] = step_match
21
23
  end
22
24
  config.on_event :step_definition_registered, &method(:on_step_definition_registered)
23
25
  end
@@ -28,25 +30,26 @@ module Cucumber
28
30
  end
29
31
 
30
32
  def on_step_match(event)
31
- @matches[event.test_step.source] = event.step_match
33
+ @matches[event.test_step.to_s] = event.step_match
32
34
  super
33
35
  end
34
36
 
35
37
  def on_test_step_finished(event)
36
- return if HookQueryVisitor.new(event.test_step).hook?
38
+ return if event.test_step.hook?
37
39
 
38
40
  test_step = event.test_step
39
41
  result = event.result.with_filtered_backtrace(Cucumber::Formatter::BacktraceFilter)
40
- step_match = @matches[test_step.source]
42
+ step_match = @matches[test_step.to_s]
41
43
 
42
44
  unless step_match.nil?
43
45
  step_definition = step_match.step_definition
44
46
  stepdef_key = StepDefKey.new(step_definition.expression.to_s, step_definition.location)
45
47
  unless @stepdef_to_match[stepdef_key].map { |key| key[:location] }.include? test_step.location
46
48
  duration = DurationExtractor.new(result).result_duration
49
+ keyword = @ast_lookup.step_source(test_step).step.keyword
47
50
 
48
51
  @stepdef_to_match[stepdef_key] << {
49
- keyword: test_step.source.last.keyword,
52
+ keyword: keyword,
50
53
  step_match: step_match,
51
54
  status: result.to_sym,
52
55
  location: test_step.location,
@@ -64,9 +67,9 @@ module Cucumber
64
67
  aggregate_info
65
68
 
66
69
  keys = if config.dry_run?
67
- @stepdef_to_match.keys.sort { |a, b| a.regexp_source <=> b.regexp_source }
70
+ @stepdef_to_match.keys.sort_by(&:regexp_source)
68
71
  else
69
- @stepdef_to_match.keys.sort { |a, b| a.mean_duration <=> b.mean_duration }.reverse
72
+ @stepdef_to_match.keys.sort_by(&:mean_duration).reverse
70
73
  end
71
74
 
72
75
  keys.each do |stepdef_key|
@@ -75,7 +78,7 @@ module Cucumber
75
78
  if @stepdef_to_match[stepdef_key].any?
76
79
  print_steps(stepdef_key)
77
80
  else
78
- @io.puts(' ' + format_string('NOT MATCHED BY ANY STEPS', :failed))
81
+ @io.puts(" #{format_string('NOT MATCHED BY ANY STEPS', :failed)}")
79
82
  end
80
83
  end
81
84
  @io.puts
@@ -83,11 +86,11 @@ module Cucumber
83
86
  end
84
87
 
85
88
  def print_step_definition(stepdef_key)
86
- @io.print format_string(format('%.7f', stepdef_key.mean_duration), :skipped) + ' ' unless config.dry_run?
89
+ @io.print "#{format_string(format('%<duration>.7f', duration: stepdef_key.mean_duration), :skipped)} " unless config.dry_run?
87
90
  @io.print format_string(stepdef_key.regexp_source, stepdef_key.status)
88
91
  if config.source?
89
- indent = max_length - stepdef_key.regexp_source.unpack('U*').length
90
- line_comment = " # #{stepdef_key.location}".indent(indent)
92
+ indent_amount = max_length - stepdef_key.regexp_source.unpack('U*').length
93
+ line_comment = indent(" # #{stepdef_key.location}", indent_amount)
91
94
  @io.print(format_string(line_comment, :comment))
92
95
  end
93
96
  @io.puts
@@ -96,11 +99,11 @@ module Cucumber
96
99
  def print_steps(stepdef_key)
97
100
  @stepdef_to_match[stepdef_key].each do |step|
98
101
  @io.print ' '
99
- @io.print format_string(format('%.7f', step[:duration]), :skipped) + ' ' unless config.dry_run?
102
+ @io.print "#{format_string(format('%<duration>.7f', duration: step[:duration]), :skipped)} " unless config.dry_run?
100
103
  @io.print format_step(step[:keyword], step[:step_match], step[:status], nil)
101
104
  if config.source?
102
- indent = max_length - (step[:keyword].unpack('U*').length + step[:step_match].format_args.unpack('U*').length)
103
- line_comment = " # #{step[:location]}".indent(indent)
105
+ indent_amount = max_length - (step[:keyword].unpack('U*').length + step[:step_match].format_args.unpack('U*').length)
106
+ line_comment = indent(" # #{step[:location]}", indent_amount)
104
107
  @io.print(format_string(line_comment, :comment))
105
108
  end
106
109
  @io.puts
@@ -135,7 +138,7 @@ module Cucumber
135
138
  end
136
139
 
137
140
  def worst_status(statuses)
138
- [:passed, :undefined, :pending, :skipped, :failed].find do |status|
141
+ %i[passed undefined pending skipped failed].find do |status|
139
142
  statuses.include?(status)
140
143
  end
141
144
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'gherkin/token_scanner'
4
- require 'gherkin/parser'
3
+ require 'gherkin'
5
4
  require 'gherkin/dialect'
6
5
 
7
6
  module Cucumber
@@ -12,15 +11,28 @@ module Cucumber
12
11
  end
13
12
 
14
13
  def parse(text)
15
- token_scanner = ::Gherkin::TokenScanner.new(feature_header + text)
16
- parser = ::Gherkin::Parser.new
17
- gherkin_document = parser.parse(token_scanner)
14
+ gherkin_document = nil
15
+ messages = ::Gherkin.from_source('dummy', feature_header + text, gherkin_options)
18
16
 
19
- gherkin_document[:feature][:children][0][:steps][0][:argument][:rows].each do |row|
17
+ messages.each do |message|
18
+ gherkin_document = message.gherkin_document.to_h unless message.gherkin_document.nil?
19
+ end
20
+
21
+ return if gherkin_document.nil?
22
+
23
+ gherkin_document[:feature][:children][0][:scenario][:steps][0][:data_table][:rows].each do |row|
20
24
  @builder.row(row[:cells].map { |cell| cell[:value] })
21
25
  end
22
26
  end
23
27
 
28
+ def gherkin_options
29
+ {
30
+ include_source: false,
31
+ include_gherkin_document: true,
32
+ include_pickles: false
33
+ }
34
+ end
35
+
24
36
  def feature_header
25
37
  dialect = ::Gherkin::Dialect.for('en')
26
38
  %(#{dialect.feature_keywords[0]}:
@@ -51,23 +51,21 @@ module Cucumber
51
51
  'white' => "\e[37m",
52
52
  'grey' => "\e[90m",
53
53
  'bold' => "\e[1m"
54
- }
54
+ }.freeze
55
55
 
56
56
  ALIASES = Hash.new do |h, k|
57
- if k.to_s =~ /(.*)_arg/
58
- h[$1] + ',bold'
59
- end
60
- end.merge({
61
- 'undefined' => 'yellow',
62
- 'pending' => 'yellow',
63
- 'executing' => 'grey',
64
- 'failed' => 'red',
65
- 'passed' => 'green',
66
- 'outline' => 'cyan',
67
- 'skipped' => 'cyan',
68
- 'comments' => 'grey',
69
- 'tag' => 'cyan'
70
- })
57
+ "#{h[Regexp.last_match(1)]},bold" if k.to_s =~ /(.*)_arg/
58
+ end.merge(
59
+ 'undefined' => 'yellow',
60
+ 'pending' => 'yellow',
61
+ 'executing' => 'grey',
62
+ 'failed' => 'red',
63
+ 'passed' => 'green',
64
+ 'outline' => 'cyan',
65
+ 'skipped' => 'cyan',
66
+ 'comments' => 'grey',
67
+ 'tag' => 'cyan'
68
+ )
71
69
 
72
70
  if ENV['GHERKIN_COLORS'] # Example: export GHERKIN_COLORS="passed=red:failed=yellow"
73
71
  ENV['GHERKIN_COLORS'].split(':').each do |pair|
@@ -76,7 +74,7 @@ module Cucumber
76
74
  end
77
75
  end
78
76
 
79
- ALIASES.keys.each do |key|
77
+ ALIASES.each_key do |key|
80
78
  define_method(key) do
81
79
  ALIASES[key].split(',').map { |color| COLORS[color] }.join('')
82
80
  end
@@ -93,8 +91,6 @@ module Cucumber
93
91
  def up(n)
94
92
  "\e[#{n}A"
95
93
  end
96
-
97
- extend self
98
94
  end
99
95
  end
100
96
  end
@@ -10,8 +10,8 @@ module Cucumber
10
10
  # * \ becomes \\
11
11
  #
12
12
  # This is used in the pretty formatter.
13
- def escape_cell(s)
14
- s.gsub(/\\(?!\|)/, '\\\\\\\\').gsub(/\n/, '\\n').gsub(/\|/, '\\|')
13
+ def escape_cell(sym)
14
+ sym.gsub(/\\(?!\|)/, '\\\\\\\\').gsub(/\n/, '\\n').gsub(/\|/, '\\|')
15
15
  end
16
16
  end
17
17
  end
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'gherkin/token_scanner'
4
- require 'gherkin/token_matcher'
5
- require 'gherkin/parser'
3
+ require 'gherkin'
6
4
  require 'gherkin/dialect'
7
5
 
8
6
  module Cucumber
@@ -15,12 +13,23 @@ module Cucumber
15
13
 
16
14
  def parse(text)
17
15
  dialect = ::Gherkin::Dialect.for(@language)
18
- token_matcher = ::Gherkin::TokenMatcher.new(@language)
19
- token_scanner = ::Gherkin::TokenScanner.new(feature_header(dialect) + text)
20
- parser = ::Gherkin::Parser.new
21
- gherkin_document = parser.parse(token_scanner, token_matcher)
16
+ gherkin_document = nil
17
+ messages = ::Gherkin.from_source('dummy', feature_header(dialect) + text, gherkin_options)
22
18
 
23
- @builder.steps(gherkin_document[:feature][:children][0][:steps])
19
+ messages.each do |message|
20
+ gherkin_document = message.gherkin_document.to_h unless message.gherkin_document.nil?
21
+ end
22
+
23
+ @builder.steps(gherkin_document[:feature][:children][0][:scenario][:steps])
24
+ end
25
+
26
+ def gherkin_options
27
+ {
28
+ default_dialect: @language,
29
+ include_source: false,
30
+ include_gherkin_document: true,
31
+ include_pickles: false
32
+ }
24
33
  end
25
34
 
26
35
  def feature_header(dialect)
@@ -19,8 +19,8 @@ module Cucumber
19
19
  @rb_language.build_rb_world_factory(world_modules, namespaced_world_modules, proc)
20
20
  end
21
21
 
22
- def register_rb_hook(phase, tag_names, proc)
23
- @rb_language.register_rb_hook(phase, tag_names, proc)
22
+ def register_rb_hook(phase, tag_names, proc, name: nil)
23
+ @rb_language.register_rb_hook(phase, tag_names, proc, name: name)
24
24
  end
25
25
 
26
26
  def define_parameter_type(parameter_type)
@@ -62,14 +62,14 @@ module Cucumber
62
62
 
63
63
  # Registers a proc that will run before each Scenario. You can register as many
64
64
  # as you want (typically from ruby scripts under <tt>support/hooks.rb</tt>).
65
- def Before(*tag_expressions, &proc)
66
- Dsl.register_rb_hook('before', tag_expressions, proc)
65
+ def Before(*tag_expressions, name: nil, &proc)
66
+ Dsl.register_rb_hook('before', tag_expressions, proc, name: name)
67
67
  end
68
68
 
69
69
  # Registers a proc that will run after each Scenario. You can register as many
70
70
  # as you want (typically from ruby scripts under <tt>support/hooks.rb</tt>).
71
- def After(*tag_expressions, &proc)
72
- Dsl.register_rb_hook('after', tag_expressions, proc)
71
+ def After(*tag_expressions, name: nil, &proc)
72
+ Dsl.register_rb_hook('after', tag_expressions, proc, name: name)
73
73
  end
74
74
 
75
75
  # Registers a proc that will be wrapped around each scenario. The proc
@@ -77,14 +77,14 @@ module Cucumber
77
77
  # argument (but passed as a regular argument, since blocks cannot accept
78
78
  # blocks in 1.8), on which it should call the .call method. You can register
79
79
  # as many as you want (typically from ruby scripts under <tt>support/hooks.rb</tt>).
80
- def Around(*tag_expressions, &proc)
81
- Dsl.register_rb_hook('around', tag_expressions, proc)
80
+ def Around(*tag_expressions, name: nil, &proc)
81
+ Dsl.register_rb_hook('around', tag_expressions, proc, name: name)
82
82
  end
83
83
 
84
84
  # Registers a proc that will run after each Step. You can register as
85
85
  # as you want (typically from ruby scripts under <tt>support/hooks.rb</tt>).
86
- def AfterStep(*tag_expressions, &proc)
87
- Dsl.register_rb_hook('after_step', tag_expressions, proc)
86
+ def AfterStep(*tag_expressions, name: nil, &proc)
87
+ Dsl.register_rb_hook('after_step', tag_expressions, proc, name: name)
88
88
  end
89
89
 
90
90
  def ParameterType(options)
@@ -107,10 +107,21 @@ module Cucumber
107
107
  value.nil? ? default : value
108
108
  end
109
109
 
110
- # Registers a proc that will run after Cucumber is configured. You can register as
111
- # as you want (typically from ruby scripts under <tt>support/hooks.rb</tt>).
112
- def AfterConfiguration(&proc)
113
- Dsl.register_rb_hook('after_configuration', [], proc)
110
+ # Registers a proc that will run after Cucumber is configured in order to install an external plugin.
111
+ def InstallPlugin(name: nil, &proc)
112
+ Dsl.register_rb_hook('install_plugin', [], proc, name: name)
113
+ end
114
+
115
+ # Registers a proc that will run before the execution of the scenarios.
116
+ # Use it for your final set-ups
117
+ def BeforeAll(name: nil, &proc)
118
+ Dsl.register_rb_hook('before_all', [], proc, name: name)
119
+ end
120
+
121
+ # Registers a proc that will run after the execution of the scenarios.
122
+ # Use it for your final clean-ups
123
+ def AfterAll(name: nil, &proc)
124
+ Dsl.register_rb_hook('after_all', [], proc, name: name)
114
125
  end
115
126
 
116
127
  # Registers a new Ruby StepDefinition. This method is aliased
@@ -136,5 +147,8 @@ module Cucumber
136
147
  end
137
148
  end
138
149
 
139
- # TODO: can we avoid adding methods to the global namespace (Kernel)
150
+ # rubocop:disable Style/MixinUsage
151
+ # This "should" always be present, because it allows users to write `Before` and `After`
152
+ # See. https://github.com/cucumber/cucumber-ruby/pull/1566#discussion_r683235396
140
153
  extend(Cucumber::Glue::Dsl)
154
+ # rubocop:enable Style/MixinUsage
@@ -6,14 +6,16 @@ module Cucumber
6
6
  module Glue
7
7
  # TODO: Kill pointless wrapper for Before, After and AfterStep hooks with fire
8
8
  class Hook
9
- attr_reader :tag_expressions, :location
9
+ attr_reader :id, :tag_expressions, :location, :name
10
10
 
11
- def initialize(registry, tag_expressions, proc)
11
+ def initialize(id, registry, tag_expressions, proc, name: nil)
12
+ @id = id
12
13
  @registry = registry
13
- @tag_expressions = tag_expressions
14
+ @name = name
15
+ @tag_expressions = sanitize_tag_expressions(tag_expressions)
14
16
  @proc = proc
15
- @location = Cucumber::Core::Ast::Location.from_source_location(*@proc.source_location)
16
- warn_for_old_style_tag_expressions(tag_expressions)
17
+ @location = Cucumber::Core::Test::Location.from_source_location(*@proc.source_location)
18
+ fail_for_old_style_tag_expressions(@tag_expressions)
17
19
  end
18
20
 
19
21
  def invoke(pseudo_method, arguments, &block)
@@ -27,16 +29,40 @@ module Cucumber
27
29
  )
28
30
  end
29
31
 
32
+ def to_envelope
33
+ Cucumber::Messages::Envelope.new(
34
+ hook: Cucumber::Messages::Hook.new(
35
+ id: id,
36
+ name: name,
37
+ tag_expression: tag_expressions.empty? ? nil : tag_expressions.join(' '),
38
+ source_reference: Cucumber::Messages::SourceReference.new(
39
+ uri: location.file,
40
+ location: Cucumber::Messages::Location.new(
41
+ line: location.lines.first
42
+ )
43
+ )
44
+ )
45
+ )
46
+ end
47
+
30
48
  private
31
49
 
32
- def warn_for_old_style_tag_expressions(tag_expressions)
50
+ def sanitize_tag_expressions(tag_expressions)
51
+ # TODO: remove when '~@no-clobber' has been changed to 'not @no-clobber' in aruba
52
+ tag_expressions.map { |tag_expression| tag_expression == '~@no-clobber' ? 'not @no-clobber' : tag_expression }
53
+ end
54
+
55
+ def fail_for_old_style_tag_expressions(tag_expressions)
33
56
  tag_expressions.each do |tag_expression|
34
- if tag_expression.include?('~') && tag_expression != '~@no-clobber' # ~@no-clobber is used in aruba
35
- warn("Deprecated: Found tagged hook with '#{tag_expression}'. Support for '~@tag' will be removed from the next release of Cucumber. Please use 'not @tag' instead.")
36
- end
37
- if tag_expression.include?(',')
38
- warn("Deprecated: Found tagged hook with '#{tag_expression}'. Support for '@tag1,@tag2' will be removed from the next release of Cucumber. Please use '@tag or @tag2' instead.")
57
+ if tag_expression.include?('~')
58
+ raise("Found tagged hook with '#{tag_expression}'." \
59
+ "'~@tag' is no longer supported, use 'not @tag' instead.")
39
60
  end
61
+
62
+ next unless tag_expression.include?(',')
63
+
64
+ warn("Found tagged hook with '#{tag_expression}'." \
65
+ "'@tag1,@tag2' is no longer supported, use '@tag or @tag2' instead.")
40
66
  end
41
67
  end
42
68
  end
@@ -11,16 +11,16 @@ module Cucumber
11
11
  return if Cucumber.use_full_backtrace
12
12
 
13
13
  instance_exec_pos = backtrace.index(instance_exec_invocation_line)
14
- if instance_exec_pos
15
- replacement_line = instance_exec_pos + INSTANCE_EXEC_OFFSET
16
- backtrace[replacement_line].gsub!(/`.*'/, "`#{pseudo_method}'") if pseudo_method
14
+ return unless instance_exec_pos
17
15
 
18
- depth = backtrace.count { |line| line == instance_exec_invocation_line }
19
- end_pos = depth > 1 ? instance_exec_pos : -1
16
+ replacement_line = instance_exec_pos + INSTANCE_EXEC_OFFSET
17
+ backtrace[replacement_line].gsub!(/`.*'/, "`#{pseudo_method}'") if pseudo_method
20
18
 
21
- backtrace[replacement_line + 1..end_pos] = nil
22
- backtrace.compact!
23
- end
19
+ depth = backtrace.count { |line| line == instance_exec_invocation_line }
20
+ end_pos = depth > 1 ? instance_exec_pos : -1
21
+
22
+ backtrace[replacement_line + 1..end_pos] = nil
23
+ backtrace.compact!
24
24
  end
25
25
 
26
26
  def self.cucumber_instance_exec_in(world, check_arity, pseudo_method, *args, &block)
@@ -28,12 +28,10 @@ module Cucumber
28
28
  if check_arity && !cucumber_compatible_arity?(args, block)
29
29
  world.instance_exec do
30
30
  ari = block.arity
31
- ari = ari < 0 ? (ari.abs - 1).to_s + '+' : ari
31
+ ari = ari < 0 ? "#{ari.abs - 1}+" : ari
32
32
  s1 = ari == 1 ? '' : 's'
33
33
  s2 = args.length == 1 ? '' : 's'
34
- raise ArityMismatchError.new(
35
- "Your block takes #{ari} argument#{s1}, but the Regexp matched #{args.length} argument#{s2}."
36
- )
34
+ raise ArityMismatchError, "Your block takes #{ari} argument#{s1}, but the Regexp matched #{args.length} argument#{s2}."
37
35
  end
38
36
  else
39
37
  world.instance_exec(*args, &block)
@@ -43,20 +41,17 @@ module Cucumber
43
41
 
44
42
  def self.cucumber_compatible_arity?(args, block)
45
43
  return true if block.arity == args.length
46
- if block.arity < 0
47
- return true if args.length >= (block.arity.abs - 1)
48
- end
44
+ return true if block.arity.negative? && args.length >= (block.arity.abs - 1)
45
+
49
46
  false
50
47
  end
51
48
 
52
49
  def self.cucumber_run_with_backtrace_filtering(pseudo_method)
53
- begin
54
- yield
55
- rescue Exception => e
56
- instance_exec_invocation_line = "#{__FILE__}:#{__LINE__ - 2}:in `cucumber_run_with_backtrace_filtering'"
57
- replace_instance_exec_invocation_line!((e.backtrace || []), instance_exec_invocation_line, pseudo_method)
58
- raise e
59
- end
50
+ yield
51
+ rescue Exception => e # rubocop:disable Lint/RescueException
52
+ instance_exec_invocation_line = "#{__FILE__}:#{__LINE__ - 2}:in `cucumber_run_with_backtrace_filtering'"
53
+ replace_instance_exec_invocation_line!((e.backtrace || []), instance_exec_invocation_line, pseudo_method)
54
+ raise e
60
55
  end
61
56
 
62
57
  INSTANCE_EXEC_OFFSET = -3
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'cucumber/gherkin/formatter/ansi_escapes'
4
- require 'cucumber/core/ast/data_table'
4
+ require 'cucumber/core/test/data_table'
5
+ require 'cucumber/deprecate'
6
+ require 'mime/types'
5
7
 
6
8
  module Cucumber
7
9
  module Glue
@@ -24,7 +26,7 @@ module Cucumber
24
26
  # @example Passing a multiline string
25
27
  # step "the email should contain:", "Dear sir,\nYou've won a prize!\n"
26
28
  # @param [String] name The name of the step
27
- # @param [String,Cucumber::Ast::DocString,Cucumber::Ast::Table] multiline_argument
29
+ # @param [String,Cucumber::Test::DocString,Cucumber::Ast::Table] multiline_argument
28
30
  def step(name, raw_multiline_arg = nil)
29
31
  super
30
32
  end
@@ -67,20 +69,8 @@ module Cucumber
67
69
  # %w{ CUC-101 Peeler 22 }
68
70
  # ])
69
71
  #
70
- def table(text_or_table, file = nil, line = 0)
71
- location = !file ? Core::Ast::Location.of_caller : Core::Ast::Location.new(file, line)
72
- MultilineArgument::DataTable.from(text_or_table, location)
73
- end
74
-
75
- # Print a message to the output.
76
- #
77
- # @note Cucumber might surprise you with the behaviour of this method. Instead
78
- # of sending the output directly to STDOUT, Cucumber will intercept and cache
79
- # the message until the current step has finished, and then display it.
80
- #
81
- # If you'd prefer to see the message immediately, call {Kernel.puts} instead.
82
- def puts(*messages)
83
- super
72
+ def table(text_or_table)
73
+ MultilineArgument::DataTable.from(text_or_table)
84
74
  end
85
75
 
86
76
  # Pause the tests and ask the operator for input
@@ -88,23 +78,37 @@ module Cucumber
88
78
  super
89
79
  end
90
80
 
91
- # Embed an image in the output
92
- def embed(file, mime_type, label = 'Screenshot')
81
+ def log(*messages)
82
+ messages.each { |message| attach(message.to_s.dup, 'text/x.cucumber.log+plain') }
83
+ end
84
+
85
+ # Attach a file to the output
86
+ # @param file [string|io] the file to attach.
87
+ # It can be a string containing the file content itself,
88
+ # the file path, or an IO ready to be read.
89
+ # @param media_type [string] the media type. If file is a valid path,
90
+ # media_type can be ommitted, it will then be inferred from the file name.
91
+ def attach(file, media_type = nil)
92
+ return super unless File.file?(file)
93
+
94
+ content = File.read(file, mode: 'rb')
95
+ media_type = MIME::Types.type_for(file).first if media_type.nil?
96
+
97
+ super(content, media_type.to_s)
98
+ rescue StandardError
93
99
  super
94
100
  end
95
101
 
96
102
  # Mark the matched step as pending.
97
103
  def pending(message = 'TODO')
98
- if block_given?
99
- begin
100
- yield
101
- rescue Exception
102
- raise Pending, message
103
- end
104
- raise Pending, "Expected pending '#{message}' to fail. No Error was raised. No longer pending?"
105
- else
104
+ raise Pending, message unless block_given?
105
+
106
+ begin
107
+ yield
108
+ rescue Exception # rubocop:disable Lint/RescueException
106
109
  raise Pending, message
107
110
  end
111
+ raise Pending, "Expected pending '#{message}' to fail. No Error was raised. No longer pending?"
108
112
  end
109
113
 
110
114
  # Skips this step and the remaining steps in the scenario
@@ -123,8 +127,8 @@ module Cucumber
123
127
  end
124
128
 
125
129
  # Dynamially generate the API module, closuring the dependencies
126
- def self.for(runtime, language)
127
- Module.new do
130
+ def self.for(runtime, language) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
131
+ Module.new do # rubocop:disable Metrics/BlockLength
128
132
  def self.extended(object)
129
133
  # wrap the dynamically generated module so that we can document the methods
130
134
  # for yardoc, which doesn't like define_method.
@@ -134,37 +138,26 @@ module Cucumber
134
138
  # TODO: pass these in when building the module, instead of mutating them later
135
139
  # Extend the World with user-defined modules
136
140
  def add_modules!(world_modules, namespaced_world_modules)
137
- add_world_modules!(world_modules)
138
- add_namespaced_modules!(namespaced_world_modules)
141
+ add_world_modules!(world_modules) if world_modules.any?
142
+ add_namespaced_modules!(namespaced_world_modules) if namespaced_world_modules.any?
139
143
  end
140
144
 
141
145
  define_method(:step) do |name, raw_multiline_arg = nil|
142
- location = Core::Ast::Location.of_caller
146
+ location = Core::Test::Location.of_caller
143
147
  runtime.invoke_dynamic_step(name, MultilineArgument.from(raw_multiline_arg, location))
144
148
  end
145
149
 
146
150
  define_method(:steps) do |steps_text|
147
- location = Core::Ast::Location.of_caller
151
+ location = Core::Test::Location.of_caller
148
152
  runtime.invoke_dynamic_steps(steps_text, language, location)
149
153
  end
150
154
 
151
- # rubocop:disable UnneededInterpolation
152
- define_method(:puts) do |*messages|
153
- # Even though they won't be output until later, converting the messages to
154
- # strings right away will protect them from modifications to their original
155
- # objects in the mean time
156
- messages.collect! { |message| "#{message}" }
157
-
158
- runtime.puts(*messages)
159
- end
160
- # rubocop:enable UnneededInterpolation
161
-
162
155
  define_method(:ask) do |question, timeout_seconds = 60|
163
156
  runtime.ask(question, timeout_seconds)
164
157
  end
165
158
 
166
- define_method(:embed) do |file, mime_type, label = 'Screenshot'|
167
- runtime.embed(file, mime_type, label)
159
+ define_method(:attach) do |file, media_type|
160
+ runtime.attach(file, media_type)
168
161
  end
169
162
 
170
163
  # Prints the list of modules that are included in the World
@@ -174,7 +167,7 @@ module Cucumber
174
167
  modules += included_modules
175
168
  end
176
169
  modules << stringify_namespaced_modules
177
- format('#<%s:0x%x>', modules.join('+'), self.object_id)
170
+ format('#<%<modules>s:0x%<object_id>x>', modules: modules.join('+'), object_id: object_id)
178
171
  end
179
172
 
180
173
  private
@@ -192,14 +185,13 @@ module Cucumber
192
185
  modules.each do |namespace, world_modules|
193
186
  world_modules.each do |world_module|
194
187
  variable_name = "@__#{namespace}_world"
188
+ inner_world = instance_variable_get(variable_name) || Object.new
189
+
190
+ instance_variable_set(
191
+ variable_name,
192
+ inner_world.extend(world_module)
193
+ )
195
194
 
196
- inner_world = if self.class.respond_to?(namespace)
197
- instance_variable_get(variable_name)
198
- else
199
- Object.new
200
- end
201
- instance_variable_set(variable_name,
202
- inner_world.extend(world_module))
203
195
  self.class.send(:define_method, namespace) do
204
196
  instance_variable_get(variable_name)
205
197
  end
@@ -209,6 +201,8 @@ module Cucumber
209
201
 
210
202
  # @private
211
203
  def stringify_namespaced_modules
204
+ return '' if @__namespaced_modules.nil?
205
+
212
206
  @__namespaced_modules.map { |k, v| "#{v.join(',')} (as #{k})" }.join('+')
213
207
  end
214
208
  end