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
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # rubocop:disable Metrics/ModuleLength
4
+
3
5
  require 'cucumber/formatter/ansicolor'
4
6
  require 'cucumber/formatter/duration'
5
7
  require 'cucumber/gherkin/i18n'
@@ -32,7 +34,7 @@ module Cucumber
32
34
 
33
35
  def format_step(keyword, step_match, status, source_indent)
34
36
  comment = if source_indent
35
- c = ('# ' + step_match.location.to_s).indent(source_indent)
37
+ c = indent("# #{step_match.location}", source_indent)
36
38
  format_string(c, :comment)
37
39
  else
38
40
  ''
@@ -46,7 +48,7 @@ module Cucumber
46
48
  def format_string(o, status)
47
49
  fmt = format_for(status)
48
50
  o.to_s.split("\n").map do |line|
49
- if Proc === fmt
51
+ if Proc == fmt.class
50
52
  fmt.call(line)
51
53
  else
52
54
  fmt % line
@@ -54,10 +56,6 @@ module Cucumber
54
56
  end.join("\n")
55
57
  end
56
58
 
57
- def print_steps(status)
58
- print_elements(runtime.steps(status), status, 'steps')
59
- end
60
-
61
59
  def print_elements(elements, status, kind)
62
60
  return if elements.empty?
63
61
 
@@ -101,36 +99,43 @@ module Cucumber
101
99
  @io.puts(format_string(string, status))
102
100
  end
103
101
 
104
- def exception_message_string(e, indent)
102
+ def exception_message_string(e, indent_amount)
105
103
  message = "#{e.message} (#{e.class})".dup.force_encoding('UTF-8')
106
104
  message = linebreaks(message, ENV['CUCUMBER_TRUNCATE_OUTPUT'].to_i)
107
105
 
108
- "#{message}\n#{e.backtrace.join("\n")}".indent(indent)
106
+ indent("#{message}\n#{e.backtrace.join("\n")}", indent_amount)
109
107
  end
110
108
 
111
109
  # http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/10655
112
- def linebreaks(s, max)
113
- return s unless max && max > 0
114
- s.gsub(/.{1,#{max}}(?:\s|\Z)/) { ($& + 5.chr).gsub(/\n\005/, "\n").gsub(/\005/, "\n") }.rstrip
110
+ def linebreaks(msg, max)
111
+ return msg unless max && max > 0
112
+
113
+ msg.gsub(/.{1,#{max}}(?:\s|\Z)/) do
114
+ (Regexp.last_match(0) + 5.chr).gsub(/\n\005/, "\n").gsub(/\005/, "\n")
115
+ end.rstrip
115
116
  end
116
117
 
117
- def collect_snippet_data(test_step, result)
118
+ def collect_snippet_data(test_step, ast_lookup)
118
119
  # collect snippet data for undefined steps
119
- return if hook?(test_step)
120
- keyword = test_step.source.last.actual_keyword(@previous_step_keyword)
121
- @previous_step_keyword = keyword
122
- return unless result.undefined?
123
- @snippets_input << Console::SnippetData.new(keyword, test_step.source.last)
120
+ keyword = ast_lookup.snippet_step_keyword(test_step)
121
+ @snippets_input << Console::SnippetData.new(keyword, test_step)
122
+ end
123
+
124
+ def collect_undefined_parameter_type_names(undefined_parameter_type)
125
+ @undefined_parameter_types << undefined_parameter_type.type_name
124
126
  end
125
127
 
126
128
  def print_snippets(options)
127
129
  return unless options[:snippets]
128
- return if runtime.steps(:undefined).empty?
129
130
 
130
131
  snippet_text_proc = lambda do |step_keyword, step_name, multiline_arg|
131
- runtime.snippet_text(step_keyword, step_name, multiline_arg)
132
+ snippet_text(step_keyword, step_name, multiline_arg)
133
+ end
134
+ do_print_snippets(snippet_text_proc) unless @snippets_input.empty?
135
+
136
+ @undefined_parameter_types.map do |type_name|
137
+ do_print_undefined_parameter_type_snippet(type_name)
132
138
  end
133
- do_print_snippets(snippet_text_proc)
134
139
  end
135
140
 
136
141
  def do_print_snippets(snippet_text_proc)
@@ -146,10 +151,15 @@ module Cucumber
146
151
  @io.flush
147
152
  end
148
153
 
149
- def print_passing_wip(options)
150
- return unless options[:wip]
151
- passed_messages = element_messages(runtime.scenarios(:passed), :passed)
152
- do_print_passing_wip(passed_messages)
154
+ def print_passing_wip(config, passed_test_cases, ast_lookup)
155
+ return unless config.wip?
156
+
157
+ messages = passed_test_cases.map do |test_case|
158
+ scenario_source = ast_lookup.scenario_source(test_case)
159
+ keyword = scenario_source.type == :Scenario ? scenario_source.scenario.keyword : scenario_source.scenario_outline.keyword
160
+ linebreaks("#{test_case.location.on_line(test_case.location.lines.max)}:in `#{keyword}: #{test_case.name}'", ENV['CUCUMBER_TRUNCATE_OUTPUT'].to_i)
161
+ end
162
+ do_print_passing_wip(messages)
153
163
  end
154
164
 
155
165
  def do_print_passing_wip(passed_messages)
@@ -161,59 +171,57 @@ module Cucumber
161
171
  end
162
172
  end
163
173
 
164
- def embed(file, mime_type, label)
165
- # no-op
166
- end
167
-
168
- # define @delayed_messages = [] in your Formatter if you want to
169
- # activate this feature
170
- def puts(*messages)
171
- if @delayed_messages
172
- @delayed_messages += messages
173
- else
174
- if @io
175
- @io.puts
176
- messages.each do |message|
177
- @io.puts(format_string(message, :tag))
178
- end
179
- @io.flush
180
- end
181
- end
182
- end
183
-
184
- def print_messages
185
- @delayed_messages.each { |message| print_message(message) }
186
- empty_messages
187
- end
174
+ def attach(src, media_type)
175
+ return unless media_type == 'text/x.cucumber.log+plain'
176
+ return unless @io
188
177
 
189
- def print_table_row_messages
190
- return if @delayed_messages.empty?
191
- @io.print(format_string(@delayed_messages.join(', '), :tag).indent(2))
192
- @io.flush
193
- empty_messages
194
- end
195
-
196
- def print_message(message)
197
- @io.puts(format_string(message, :tag).indent(@indent))
178
+ @io.puts
179
+ @io.puts(format_string(src, :tag))
198
180
  @io.flush
199
181
  end
200
182
 
201
- def empty_messages
202
- @delayed_messages = []
203
- end
204
-
205
183
  def print_profile_information
206
184
  return if @options[:skip_profile_information] || @options[:profiles].nil? || @options[:profiles].empty?
185
+
207
186
  do_print_profile_information(@options[:profiles])
208
187
  end
209
188
 
210
189
  def do_print_profile_information(profiles)
211
- profiles_sentence = profiles.size == 1 ? profiles.first :
212
- "#{profiles[0...-1].join(', ')} and #{profiles.last}"
190
+ profiles_sentence = if profiles.size == 1
191
+ profiles.first
192
+ else
193
+ "#{profiles[0...-1].join(', ')} and #{profiles.last}"
194
+ end
213
195
 
214
196
  @io.puts "Using the #{profiles_sentence} profile#{'s' if profiles.size > 1}..."
215
197
  end
216
198
 
199
+ def do_print_undefined_parameter_type_snippet(type_name)
200
+ camelized = type_name.split(/_|-/).collect(&:capitalize).join
201
+
202
+ @io.puts [
203
+ "The parameter #{type_name} is not defined. You can define a new one with:",
204
+ '',
205
+ 'ParameterType(',
206
+ " name: '#{type_name}',",
207
+ ' regexp: /some regexp here/,',
208
+ " type: #{camelized},",
209
+ ' # The transformer takes as many arguments as there are capture groups in the regexp,',
210
+ ' # or just one if there are none.',
211
+ " transformer: ->(s) { #{camelized}.new(s) }",
212
+ ')',
213
+ ''
214
+ ].join("\n")
215
+ end
216
+
217
+ def indent(string, padding)
218
+ if padding >= 0
219
+ string.gsub(/^/, ' ' * padding)
220
+ else
221
+ string.gsub(/^ {0,#{-padding}}/, '')
222
+ end
223
+ end
224
+
217
225
  private
218
226
 
219
227
  FORMATS = Hash.new { |hash, format| hash[format] = method(format).to_proc }
@@ -222,11 +230,8 @@ module Cucumber
222
230
  key = keys.join('_').to_sym
223
231
  fmt = FORMATS[key]
224
232
  raise "No format for #{key.inspect}: #{FORMATS.inspect}" if fmt.nil?
225
- fmt
226
- end
227
233
 
228
- def hook?(test_step)
229
- not test_step.source.last.respond_to?(:actual_keyword)
234
+ fmt
230
235
  end
231
236
 
232
237
  def element_messages(elements, status)
@@ -248,10 +253,13 @@ module Cucumber
248
253
 
249
254
  class SnippetData
250
255
  attr_reader :actual_keyword, :step
256
+
251
257
  def initialize(actual_keyword, step)
252
- @actual_keyword, @step = actual_keyword, step
258
+ @actual_keyword = actual_keyword
259
+ @step = step
253
260
  end
254
261
  end
255
262
  end
256
263
  end
257
264
  end
265
+ # rubocop:enable Metrics/ModuleLength
@@ -29,15 +29,10 @@ module Cucumber
29
29
  end
30
30
 
31
31
  def status_counts(summary)
32
- counts = Core::Test::Result::TYPES.map do |status|
33
- count = summary.total(status)
34
- [status, count]
35
- end.select do |status, count|
36
- count > 0
37
- end.map do |status, count|
38
- format_string("#{count} #{status}", status)
39
- end
40
- "(#{counts.join(", ")})" if counts.any?
32
+ counts = Core::Test::Result::TYPES.map { |status| [status, summary.total(status)] }
33
+ counts = counts.select { |_status, count| count > 0 }
34
+ counts = counts.map { |status, count| format_string("#{count} #{status}", status) }
35
+ "(#{counts.join(', ')})" if counts.any?
41
36
  end
42
37
  end
43
38
  end
@@ -5,7 +5,7 @@ module Cucumber
5
5
  class ConsoleIssues
6
6
  include Console
7
7
 
8
- def initialize(config)
8
+ def initialize(config, ast_lookup = AstLookup.new(config))
9
9
  @previous_test_case = nil
10
10
  @issues = Hash.new { |h, k| h[k] = [] }
11
11
  @config = config
@@ -18,10 +18,12 @@ module Cucumber
18
18
  @issues[:failed].delete(event.test_case)
19
19
  end
20
20
  end
21
+ @ast_lookup = ast_lookup
21
22
  end
22
23
 
23
24
  def to_s
24
25
  return if @issues.empty?
26
+
25
27
  result = Core::Test::Result::TYPES.map { |type| scenario_listing(type, @issues[type]) }
26
28
  result.flatten.join("\n")
27
29
  end
@@ -34,9 +36,12 @@ module Cucumber
34
36
 
35
37
  def scenario_listing(type, test_cases)
36
38
  return [] if test_cases.empty?
39
+
37
40
  [format_string("#{type_heading(type)} Scenarios:", type)] + test_cases.map do |test_case|
38
- source = @config.source? ? format_string(" # #{test_case.keyword}: #{test_case.name}", :comment) : ''
39
- format_string("cucumber #{profiles_string}" + test_case.location, type) + source
41
+ scenario_source = @ast_lookup.scenario_source(test_case)
42
+ keyword = scenario_source.type == :Scenario ? scenario_source.scenario.keyword : scenario_source.scenario_outline.keyword
43
+ source = @config.source? ? format_string(" # #{keyword}: #{test_case.name}", :comment) : ''
44
+ format_string("cucumber #{profiles_string}#{test_case.location.file}:#{test_case.location.lines.max}", type) + source
40
45
  end
41
46
  end
42
47
 
@@ -51,7 +56,10 @@ module Cucumber
51
56
 
52
57
  def profiles_string
53
58
  return if @config.custom_profiles.empty?
54
- @config.custom_profiles.map { |profile| "-p #{profile}" }.join(' ') + ' '
59
+
60
+ profiles = @config.custom_profiles.map { |profile| "-p #{profile}" }.join(' ')
61
+
62
+ "#{profiles} "
55
63
  end
56
64
  end
57
65
  end
@@ -8,7 +8,7 @@ module Cucumber
8
8
  # <tt>time</tt> format.
9
9
  def format_duration(seconds)
10
10
  m, s = seconds.divmod(60)
11
- "#{m}m#{format('%.3f', s)}s"
11
+ "#{m}m#{format('%<seconds>.3f', seconds: s)}s"
12
12
  end
13
13
  end
14
14
  end
@@ -4,6 +4,7 @@ module Cucumber
4
4
  module Formatter
5
5
  class DurationExtractor
6
6
  attr_reader :result_duration
7
+
7
8
  def initialize(result)
8
9
  @result_duration = 0
9
10
  result.describe_to(self)
@@ -22,8 +23,10 @@ module Cucumber
22
23
  def exception(*) end
23
24
 
24
25
  def duration(duration, *)
25
- duration.tap { |duration| @result_duration = duration.nanoseconds / 10**9.0 }
26
+ duration.tap { |dur| @result_duration = dur.nanoseconds / 10**9.0 }
26
27
  end
28
+
29
+ def attach(*) end
27
30
  end
28
31
  end
29
32
  end
@@ -0,0 +1,7 @@
1
+ module Cucumber
2
+ module Formatter
3
+ class TestCaseUnknownError < StandardError; end
4
+
5
+ class TestStepUnknownError < StandardError; end
6
+ end
7
+ end
@@ -13,13 +13,15 @@ module Cucumber
13
13
  end
14
14
 
15
15
  def method_missing(message, *args)
16
+ super unless recipients
17
+
16
18
  recipients.each do |recipient|
17
19
  recipient.send(message, *args) if recipient.respond_to?(message)
18
20
  end
19
21
  end
20
22
 
21
23
  def respond_to_missing?(name, include_private = false)
22
- recipients.any? { |recipient| recipient.respond_to?(name, include_private) }
24
+ recipients.any? { |recipient| recipient.respond_to?(name, include_private) } || super(name, include_private)
23
25
  end
24
26
  end
25
27
  end