cucumber 3.2.0 → 5.2.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 (113) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +280 -19
  3. data/CONTRIBUTING.md +11 -25
  4. data/README.md +4 -5
  5. data/bin/cucumber +1 -1
  6. data/lib/autotest/cucumber_mixin.rb +46 -53
  7. data/lib/cucumber/cli/configuration.rb +5 -5
  8. data/lib/cucumber/cli/main.rb +12 -12
  9. data/lib/cucumber/cli/options.rb +90 -73
  10. data/lib/cucumber/cli/profile_loader.rb +49 -26
  11. data/lib/cucumber/configuration.rb +44 -29
  12. data/lib/cucumber/constantize.rb +2 -5
  13. data/lib/cucumber/deprecate.rb +31 -7
  14. data/lib/cucumber/errors.rb +5 -7
  15. data/lib/cucumber/events/envelope.rb +9 -0
  16. data/lib/cucumber/events/gherkin_source_parsed.rb +11 -0
  17. data/lib/cucumber/events/hook_test_step_created.rb +13 -0
  18. data/lib/cucumber/events/step_activated.rb +2 -1
  19. data/lib/cucumber/events/test_case_created.rb +13 -0
  20. data/lib/cucumber/events/test_case_ready.rb +12 -0
  21. data/lib/cucumber/events/test_step_created.rb +13 -0
  22. data/lib/cucumber/events/undefined_parameter_type.rb +10 -0
  23. data/lib/cucumber/events.rb +13 -6
  24. data/lib/cucumber/file_specs.rb +6 -6
  25. data/lib/cucumber/filters/activate_steps.rb +5 -3
  26. data/lib/cucumber/filters/broadcast_test_case_ready_event.rb +12 -0
  27. data/lib/cucumber/filters/prepare_world.rb +5 -9
  28. data/lib/cucumber/filters/quit.rb +1 -3
  29. data/lib/cucumber/filters/tag_limits/verifier.rb +2 -4
  30. data/lib/cucumber/filters.rb +1 -0
  31. data/lib/cucumber/formatter/ansicolor.rb +40 -45
  32. data/lib/cucumber/formatter/ast_lookup.rb +163 -0
  33. data/lib/cucumber/formatter/backtrace_filter.rb +9 -8
  34. data/lib/cucumber/formatter/console.rb +58 -66
  35. data/lib/cucumber/formatter/console_counts.rb +4 -9
  36. data/lib/cucumber/formatter/console_issues.rb +6 -3
  37. data/lib/cucumber/formatter/duration.rb +1 -1
  38. data/lib/cucumber/formatter/duration_extractor.rb +3 -1
  39. data/lib/cucumber/formatter/errors.rb +6 -0
  40. data/lib/cucumber/formatter/fanout.rb +2 -0
  41. data/lib/cucumber/formatter/html.rb +11 -598
  42. data/lib/cucumber/formatter/http_io.rb +43 -42
  43. data/lib/cucumber/formatter/ignore_missing_messages.rb +1 -1
  44. data/lib/cucumber/formatter/interceptor.rb +11 -30
  45. data/lib/cucumber/formatter/io.rb +46 -10
  46. data/lib/cucumber/formatter/json.rb +102 -116
  47. data/lib/cucumber/formatter/junit.rb +55 -55
  48. data/lib/cucumber/formatter/message.rb +22 -0
  49. data/lib/cucumber/formatter/message_builder.rb +255 -0
  50. data/lib/cucumber/formatter/pretty.rb +359 -153
  51. data/lib/cucumber/formatter/progress.rb +30 -32
  52. data/lib/cucumber/formatter/publish_banner_printer.rb +77 -0
  53. data/lib/cucumber/formatter/query/hook_by_test_step.rb +31 -0
  54. data/lib/cucumber/formatter/query/pickle_by_test.rb +26 -0
  55. data/lib/cucumber/formatter/query/pickle_step_by_test_step.rb +26 -0
  56. data/lib/cucumber/formatter/query/step_definitions_by_test_step.rb +40 -0
  57. data/lib/cucumber/formatter/query/test_case_started_by_test_case.rb +40 -0
  58. data/lib/cucumber/formatter/rerun.rb +22 -4
  59. data/lib/cucumber/formatter/stepdefs.rb +1 -2
  60. data/lib/cucumber/formatter/steps.rb +3 -4
  61. data/lib/cucumber/formatter/summary.rb +16 -8
  62. data/lib/cucumber/formatter/unicode.rb +15 -17
  63. data/lib/cucumber/formatter/url_reporter.rb +17 -0
  64. data/lib/cucumber/formatter/usage.rb +11 -10
  65. data/lib/cucumber/gherkin/data_table_parser.rb +17 -6
  66. data/lib/cucumber/gherkin/formatter/ansi_escapes.rb +13 -17
  67. data/lib/cucumber/gherkin/formatter/escaping.rb +2 -2
  68. data/lib/cucumber/gherkin/steps_parser.rb +17 -8
  69. data/lib/cucumber/glue/hook.rb +34 -11
  70. data/lib/cucumber/glue/invoke_in_world.rb +13 -18
  71. data/lib/cucumber/glue/proto_world.rb +42 -33
  72. data/lib/cucumber/glue/registry_and_more.rb +42 -12
  73. data/lib/cucumber/glue/snippet.rb +23 -22
  74. data/lib/cucumber/glue/step_definition.rb +42 -19
  75. data/lib/cucumber/glue/world_factory.rb +1 -1
  76. data/lib/cucumber/hooks.rb +11 -11
  77. data/lib/cucumber/multiline_argument/data_table/diff_matrices.rb +2 -2
  78. data/lib/cucumber/multiline_argument/data_table.rb +97 -64
  79. data/lib/cucumber/multiline_argument/doc_string.rb +1 -1
  80. data/lib/cucumber/multiline_argument.rb +4 -6
  81. data/lib/cucumber/platform.rb +3 -3
  82. data/lib/cucumber/rake/task.rb +16 -18
  83. data/lib/cucumber/rspec/disable_option_parser.rb +9 -8
  84. data/lib/cucumber/rspec/doubles.rb +3 -5
  85. data/lib/cucumber/running_test_case.rb +2 -53
  86. data/lib/cucumber/runtime/after_hooks.rb +8 -4
  87. data/lib/cucumber/runtime/before_hooks.rb +8 -4
  88. data/lib/cucumber/runtime/for_programming_languages.rb +4 -2
  89. data/lib/cucumber/runtime/step_hooks.rb +6 -2
  90. data/lib/cucumber/runtime/support_code.rb +13 -15
  91. data/lib/cucumber/runtime/user_interface.rb +6 -16
  92. data/lib/cucumber/runtime.rb +41 -58
  93. data/lib/cucumber/step_definition_light.rb +4 -3
  94. data/lib/cucumber/step_definitions.rb +2 -2
  95. data/lib/cucumber/step_match.rb +12 -11
  96. data/lib/cucumber/step_match_search.rb +2 -1
  97. data/lib/cucumber/term/ansicolor.rb +9 -9
  98. data/lib/cucumber/term/banner.rb +56 -0
  99. data/lib/cucumber/version +1 -1
  100. data/lib/cucumber.rb +1 -1
  101. metadata +253 -80
  102. data/lib/cucumber/formatter/cucumber.css +0 -286
  103. data/lib/cucumber/formatter/cucumber.sass +0 -247
  104. data/lib/cucumber/formatter/hook_query_visitor.rb +0 -42
  105. data/lib/cucumber/formatter/html_builder.rb +0 -121
  106. data/lib/cucumber/formatter/inline-js.js +0 -30
  107. data/lib/cucumber/formatter/jquery-min.js +0 -154
  108. data/lib/cucumber/formatter/json_pretty.rb +0 -11
  109. data/lib/cucumber/formatter/legacy_api/adapter.rb +0 -1028
  110. data/lib/cucumber/formatter/legacy_api/ast.rb +0 -394
  111. data/lib/cucumber/formatter/legacy_api/results.rb +0 -50
  112. data/lib/cucumber/formatter/legacy_api/runtime_facade.rb +0 -32
  113. data/lib/cucumber/step_argument.rb +0 -25
@@ -24,22 +24,15 @@ module Cucumber
24
24
  def execute!(existing_runtime = nil)
25
25
  trap_interrupt
26
26
 
27
- runtime = if existing_runtime
28
- existing_runtime.configure(configuration)
29
- existing_runtime
30
- else
31
- Runtime.new(configuration)
32
- end
27
+ runtime = runtime(existing_runtime)
33
28
 
34
29
  runtime.run!
35
30
  if Cucumber.wants_to_quit
36
31
  exit_unable_to_finish
32
+ elsif runtime.failure?
33
+ exit_tests_failed
37
34
  else
38
- if runtime.failure?
39
- exit_tests_failed
40
- else
41
- exit_ok
42
- end
35
+ exit_ok
43
36
  end
44
37
  rescue SystemExit => e
45
38
  @kernel.exit(e.status)
@@ -56,7 +49,7 @@ module Cucumber
56
49
  rescue Errno::EACCES, Errno::ENOENT => e
57
50
  @err.puts("#{e.message} (#{e.class})")
58
51
  exit_unable_to_finish
59
- rescue Exception => e
52
+ rescue Exception => e # rubocop:disable Lint/RescueException
60
53
  @err.puts("#{e.message} (#{e.class})")
61
54
  @err.puts(e.backtrace.join("\n"))
62
55
  exit_unable_to_finish
@@ -93,8 +86,15 @@ module Cucumber
93
86
  exit_unable_to_finish! if Cucumber.wants_to_quit
94
87
  Cucumber.wants_to_quit = true
95
88
  STDERR.puts "\nExiting... Interrupt again to exit immediately."
89
+ exit_unable_to_finish
96
90
  end
97
91
  end
92
+
93
+ def runtime(existing_runtime)
94
+ return Runtime.new(configuration) unless existing_runtime
95
+ existing_runtime.configure(configuration)
96
+ existing_runtime
97
+ end
98
98
  end
99
99
  end
100
100
  end
@@ -9,26 +9,25 @@ require 'cucumber/core/test/result'
9
9
  module Cucumber
10
10
  module Cli
11
11
  class Options
12
+ CUCUMBER_PUBLISH_URL = ENV['CUCUMBER_PUBLISH_URL'] || 'https://messages.cucumber.io/api/reports -X GET'
12
13
  INDENT = ' ' * 53
13
- # rubocop:disable Layout/MultilineOperationIndentation
14
14
  BUILTIN_FORMATS = {
15
- 'html' => ['Cucumber::Formatter::Html', 'Generates a nice looking HTML report.'],
16
15
  'pretty' => ['Cucumber::Formatter::Pretty', 'Prints the feature as is - in colours.'],
17
16
  'progress' => ['Cucumber::Formatter::Progress', 'Prints one character per scenario.'],
18
17
  'rerun' => ['Cucumber::Formatter::Rerun', 'Prints failing files with line numbers.'],
19
- 'usage' => ['Cucumber::Formatter::Usage', "Prints where step definitions are used.\n" +
20
- "#{INDENT}The slowest step definitions (with duration) are\n" +
21
- "#{INDENT}listed first. If --dry-run is used the duration\n" +
22
- "#{INDENT}is not shown, and step definitions are sorted by\n" +
18
+ 'usage' => ['Cucumber::Formatter::Usage', "Prints where step definitions are used.\n" \
19
+ "#{INDENT}The slowest step definitions (with duration) are\n" \
20
+ "#{INDENT}listed first. If --dry-run is used the duration\n" \
21
+ "#{INDENT}is not shown, and step definitions are sorted by\n" \
23
22
  "#{INDENT}filename instead."],
24
- 'stepdefs' => ['Cucumber::Formatter::Stepdefs', "Prints All step definitions with their locations. Same as\n" +
23
+ 'stepdefs' => ['Cucumber::Formatter::Stepdefs', "Prints All step definitions with their locations. Same as\n" \
25
24
  "#{INDENT}the usage formatter, except that steps are not printed."],
26
25
  'junit' => ['Cucumber::Formatter::Junit', 'Generates a report similar to Ant+JUnit.'],
27
- 'json' => ['Cucumber::Formatter::Json', 'Prints the feature as JSON'],
28
- 'json_pretty' => ['Cucumber::Formatter::JsonPretty', 'Prints the feature as prettified JSON'],
26
+ 'json' => ['Cucumber::Formatter::Json', '[DEPRECATED] Prints the feature as JSON'],
27
+ 'message' => ['Cucumber::Formatter::Message', 'Outputs protobuf messages'],
28
+ 'html' => ['Cucumber::Formatter::HTML', 'Outputs HTML report'],
29
29
  'summary' => ['Cucumber::Formatter::Summary', 'Summary output of feature and scenarios']
30
- }
31
- # rubocop:enable Layout/MultilineOperationIndentation
30
+ }.freeze
32
31
  max = BUILTIN_FORMATS.keys.map(&:length).max
33
32
  FORMAT_HELP_MSG = [
34
33
  'Use --format rerun --out rerun.txt to write out failing',
@@ -41,24 +40,24 @@ module Cucumber
41
40
  'foo/bar_zap.rb. You can place the file with this relative',
42
41
  'path underneath your features/support directory or anywhere',
43
42
  "on Ruby's LOAD_PATH, for example in a Ruby gem."
44
- ]
43
+ ].freeze
45
44
 
46
45
  FORMAT_HELP = (BUILTIN_FORMATS.keys.sort.map do |key|
47
46
  " #{key}#{' ' * (max - key.length)} : #{BUILTIN_FORMATS[key][1]}"
48
47
  end) + FORMAT_HELP_MSG
49
- PROFILE_SHORT_FLAG = '-p'
50
- NO_PROFILE_SHORT_FLAG = '-P'
51
- PROFILE_LONG_FLAG = '--profile'
52
- NO_PROFILE_LONG_FLAG = '--no-profile'
53
- FAIL_FAST_FLAG = '--fail-fast'
54
- RETRY_FLAG = '--retry'
48
+ PROFILE_SHORT_FLAG = '-p'.freeze
49
+ NO_PROFILE_SHORT_FLAG = '-P'.freeze
50
+ PROFILE_LONG_FLAG = '--profile'.freeze
51
+ NO_PROFILE_LONG_FLAG = '--no-profile'.freeze
52
+ FAIL_FAST_FLAG = '--fail-fast'.freeze
53
+ RETRY_FLAG = '--retry'.freeze
55
54
  OPTIONS_WITH_ARGS = [
56
55
  '-r', '--require', '--i18n-keywords', '-f', '--format', '-o',
57
56
  '--out', '-t', '--tags', '-n', '--name', '-e', '--exclude',
58
57
  PROFILE_SHORT_FLAG, PROFILE_LONG_FLAG, RETRY_FLAG, '-l',
59
58
  '--lines', '--port', '-I', '--snippet-type'
60
- ]
61
- ORDER_TYPES = %w{defined random}
59
+ ].freeze
60
+ ORDER_TYPES = %w[defined random].freeze
62
61
  TAG_LIMIT_MATCHER = /(?<tag_name>\@\w+):(?<limit>\d+)/x
63
62
 
64
63
  def self.parse(args, out_stream, error_stream, options = {})
@@ -87,19 +86,21 @@ module Cucumber
87
86
  @options[key] = value
88
87
  end
89
88
 
90
- def parse!(args) # rubocop:disable Metrics/AbcSize
89
+ def parse!(args) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
91
90
  @args = args
92
91
  @expanded_args = @args.dup
93
92
 
94
93
  @args.extend(::OptionParser::Arguable)
95
94
 
96
- @args.options do |opts|
95
+ @args.options do |opts| # rubocop:disable Metrics/BlockLength
97
96
  opts.banner = banner
97
+ opts.on('--publish', 'Publish a report to https://reports.cucumber.io') do
98
+ set_option :publish_enabled, true
99
+ end
100
+ opts.on('--publish-quiet', 'Don\'t print information banner about publishing reports') { set_option :publish_quiet }
98
101
  opts.on('-r LIBRARY|DIR', '--require LIBRARY|DIR', *require_files_msg) { |lib| require_files(lib) }
99
102
 
100
- if Cucumber::JRUBY
101
- opts.on('-j DIR', '--jars DIR', 'Load all the jars under DIR') { |jars| load_jars(jars) }
102
- end
103
+ opts.on('-j DIR', '--jars DIR', 'Load all the jars under DIR') { |jars| load_jars(jars) } if Cucumber::JRUBY
103
104
 
104
105
  opts.on("#{RETRY_FLAG} ATTEMPTS", *retry_msg) { |v| set_option :retry, v.to_i }
105
106
  opts.on('--i18n-languages', *i18n_languages_msg) { list_languages_and_exit }
@@ -108,20 +109,20 @@ module Cucumber
108
109
  opts.on('-f FORMAT', '--format FORMAT', *format_msg, *FORMAT_HELP) do |v|
109
110
  add_option :formats, [*parse_formats(v), @out_stream]
110
111
  end
111
- opts.on('--init', *init_msg) { |v| initialize_project }
112
+ opts.on('--init', *init_msg) { |_v| initialize_project }
112
113
  opts.on('-o', '--out [FILE|DIR|URL]', *out_msg) { |v| out_stream v }
113
114
  opts.on('-t TAG_EXPRESSION', '--tags TAG_EXPRESSION', *tags_msg) { |v| add_tag v }
114
115
  opts.on('-n NAME', '--name NAME', *name_msg) { |v| add_option :name_regexps, /#{v}/ }
115
116
  opts.on('-e', '--exclude PATTERN', *exclude_msg) { |v| add_option :excludes, Regexp.new(v) }
116
117
  opts.on(PROFILE_SHORT_FLAG, "#{PROFILE_LONG_FLAG} PROFILE", *profile_short_flag_msg) { |v| add_profile v }
117
- opts.on(NO_PROFILE_SHORT_FLAG, NO_PROFILE_LONG_FLAG, *no_profile_short_flag_msg) { |v| disable_profile_loading }
118
+ opts.on(NO_PROFILE_SHORT_FLAG, NO_PROFILE_LONG_FLAG, *no_profile_short_flag_msg) { |_v| disable_profile_loading }
118
119
  opts.on('-c', '--[no-]color', *color_msg) { |v| color v }
119
120
  opts.on('-d', '--dry-run', *dry_run_msg) { set_dry_run_and_duration }
120
121
  opts.on('-m', '--no-multiline', "Don't print multiline strings and tables under steps.") { set_option :no_multiline }
121
122
  opts.on('-s', '--no-source', "Don't print the file and line of the step definition with the steps.") { set_option :source, false }
122
123
  opts.on('-i', '--no-snippets', "Don't print snippets for pending steps.") { set_option :snippets, false }
123
124
  opts.on('-I', '--snippet-type TYPE', *snippet_type_msg) { |v| set_option :snippet_type, v.to_sym }
124
- opts.on('-q', '--quiet', 'Alias for --no-snippets --no-source.') { shut_up }
125
+ opts.on('-q', '--quiet', 'Alias for --no-snippets --no-source --no-duration --publish-quiet.') { shut_up }
125
126
  opts.on('--no-duration', "Don't print the duration at the end of the summary") { set_option :duration, false }
126
127
  opts.on('-b', '--backtrace', 'Show full backtrace for all errors.') { Cucumber.use_full_backtrace = true }
127
128
  opts.on('-S', '--[no-]strict', *strict_msg) { |setting| set_strict(setting) }
@@ -140,23 +141,23 @@ module Cucumber
140
141
  [random] Shuffle scenarios before running.
141
142
  Specify SEED to reproduce the shuffling from a previous run.
142
143
  e.g. --order random:5738
143
- TEXT
144
+ TEXT
144
145
  @options[:order], @options[:seed] = *order.split(':')
145
- unless ORDER_TYPES.include?(@options[:order])
146
- fail "'#{@options[:order]}' is not a recognised order type. Please use one of #{ORDER_TYPES.join(", ")}."
147
- end
146
+ raise "'#{@options[:order]}' is not a recognised order type. Please use one of #{ORDER_TYPES.join(', ')}." unless ORDER_TYPES.include?(@options[:order])
148
147
  end
149
148
 
150
149
  opts.on_tail('--version', 'Show version.') { exit_ok(Cucumber::VERSION) }
151
150
  opts.on_tail('-h', '--help', "You're looking at it.") { exit_ok(opts.help) }
152
151
  end.parse!
153
152
 
153
+ process_publish_options
154
+
154
155
  @args.map! { |a| "#{a}:#{@options[:lines]}" } if @options[:lines]
155
156
 
156
157
  extract_environment_variables
157
158
  @options[:paths] = @args.dup # whatver is left over
158
159
 
159
- check_formatter_stream_conflicts()
160
+ check_formatter_stream_conflicts
160
161
 
161
162
  merge_profiles
162
163
 
@@ -171,7 +172,7 @@ TEXT
171
172
  @options[:filters] ||= []
172
173
  end
173
174
 
174
- def check_formatter_stream_conflicts()
175
+ def check_formatter_stream_conflicts
175
176
  streams = @options[:formats].uniq.map { |(_, _, stream)| stream }
176
177
  return if streams == streams.uniq
177
178
  raise 'All but one formatter must use --out, only one can print to each stream (or STDOUT)'
@@ -188,6 +189,18 @@ TEXT
188
189
 
189
190
  private
190
191
 
192
+ def process_publish_options
193
+ @options[:publish_enabled] = true if truthy_string?(ENV['CUCUMBER_PUBLISH_ENABLED']) || ENV['CUCUMBER_PUBLISH_TOKEN']
194
+ @options[:formats] << publisher if @options[:publish_enabled]
195
+
196
+ @options[:publish_quiet] = true if truthy_string?(ENV['CUCUMBER_PUBLISH_QUIET'])
197
+ end
198
+
199
+ def truthy_string?(str)
200
+ return false if str.nil?
201
+ str !~ /^(false|no|0)$/i
202
+ end
203
+
191
204
  def color_msg
192
205
  [
193
206
  'Whether or not to use ANSI color in the output. Cucumber decides',
@@ -196,10 +209,7 @@ TEXT
196
209
  end
197
210
 
198
211
  def dry_run_msg
199
- [
200
- 'Invokes formatters without executing the steps.',
201
- 'This also omits the loading of your support/env.rb file if it exists.'
202
- ]
212
+ ['Invokes formatters without executing the steps.']
203
213
  end
204
214
 
205
215
  def exclude_msg
@@ -219,7 +229,7 @@ TEXT
219
229
  def i18n_keywords_msg
220
230
  [
221
231
  'List keywords for in a particular language',
222
- %{Run with "--i18n help" to see all languages}
232
+ %(Run with "--i18n help" to see all languages)
223
233
  ]
224
234
  end
225
235
 
@@ -320,11 +330,13 @@ TEXT
320
330
  [
321
331
  'Require files before executing the features. If this',
322
332
  'option is not specified, all *.rb files that are',
323
- 'siblings or below the features will be loaded auto-',
333
+ 'siblings of or below the features will be loaded auto-',
324
334
  'matically. Automatic loading is disabled when this',
325
- 'option is specified, and all loading becomes explicit.',
326
- 'Files under directories named "support" are always',
327
- 'loaded first.',
335
+ 'option is specified; all loading becomes explicit.',
336
+ 'Files in directories named "support" are still always',
337
+ 'loaded first when their parent directories are',
338
+ 'required or if the "support" directories themselves are',
339
+ 'explicitly required.',
328
340
  'This option can be specified multiple times.'
329
341
  ]
330
342
  end
@@ -355,13 +367,19 @@ TEXT
355
367
  end
356
368
 
357
369
  def require_jars(jars)
358
- Dir["#{jars}/**/*.jar"].each { |jar| require jar }
370
+ Dir["#{jars}/**/*.jar"].sort.each { |jar| require jar }
371
+ end
372
+
373
+ def publisher
374
+ url = CUCUMBER_PUBLISH_URL
375
+ url += %( -H "Authorization: Bearer #{ENV['CUCUMBER_PUBLISH_TOKEN']}") if ENV['CUCUMBER_PUBLISH_TOKEN']
376
+ ['message', {}, url]
359
377
  end
360
378
 
361
379
  def language(lang)
362
380
  require 'gherkin/dialect'
363
381
 
364
- return indicate_invalid_language_and_exit(lang) unless ::Gherkin::DIALECTS.keys.include? lang
382
+ return indicate_invalid_language_and_exit(lang) unless ::Gherkin::DIALECTS.key?(lang)
365
383
  list_keywords_and_exit(lang)
366
384
  end
367
385
 
@@ -370,7 +388,7 @@ TEXT
370
388
  end
371
389
 
372
390
  def non_stdout_formats
373
- @options[:formats].select { |_, _, output| output != @out_stream }
391
+ @options[:formats].reject { |_, _, output| output == @out_stream }
374
392
  end
375
393
 
376
394
  def add_option(option, value)
@@ -378,8 +396,8 @@ TEXT
378
396
  end
379
397
 
380
398
  def add_tag(value)
381
- warn("Deprecated: Found tags option '#{value}'. Support for '~@tag' will be removed from the next release of Cucumber. Please use 'not @tag' instead.") if value.include?('~')
382
- warn("Deprecated: Found tags option '#{value}'. Support for '@tag1,@tag2' will be removed from the next release of Cucumber. Please use '@tag or @tag2' instead.") if value.include?(',')
399
+ raise("Found tags option '#{value}'. '~@tag' is no longer supported, use 'not @tag' instead.") if value.include?('~')
400
+ raise("Found tags option '#{value}'. '@tag1,@tag2' is no longer supported, use '@tag or @tag2' instead.") if value.include?(',')
383
401
  @options[:tag_expressions] << value.gsub(/(@\w+)(:\d+)?/, '\1')
384
402
  add_tag_limits(value)
385
403
  end
@@ -391,9 +409,7 @@ TEXT
391
409
  end
392
410
 
393
411
  def add_tag_limit(tag_limits, tag_name, limit)
394
- if tag_limits[tag_name] && tag_limits[tag_name] != limit
395
- raise "Inconsistent tag limits for #{tag_name}: #{tag_limits[tag_name]} and #{limit}"
396
- end
412
+ raise "Inconsistent tag limits for #{tag_name}: #{tag_limits[tag_name]} and #{limit}" if tag_limits[tag_name] && tag_limits[tag_name] != limit
397
413
  tag_limits[tag_name] = limit
398
414
  end
399
415
 
@@ -424,6 +440,7 @@ TEXT
424
440
  end
425
441
 
426
442
  def shut_up
443
+ @options[:publish_quiet] = true
427
444
  @options[:snippets] = false
428
445
  @options[:source] = false
429
446
  @options[:duration] = false
@@ -440,7 +457,7 @@ TEXT
440
457
  def extract_environment_variables
441
458
  @args.delete_if do |arg|
442
459
  if arg =~ /^(\w+)=(.*)$/
443
- @options[:env_vars][$1] = $2
460
+ @options[:env_vars][Regexp.last_match(1)] = Regexp.last_match(2)
444
461
  true
445
462
  end
446
463
  end
@@ -469,8 +486,8 @@ TEXT
469
486
  profile_args = profile_loader.args_from(profile)
470
487
  profile_options = Options.parse(
471
488
  profile_args, @out_stream, @error_stream,
472
- :skip_profile_information => true,
473
- :profile_loader => profile_loader
489
+ skip_profile_information: true,
490
+ profile_loader: profile_loader
474
491
  )
475
492
  reverse_merge(profile_options)
476
493
  end
@@ -478,14 +495,14 @@ TEXT
478
495
  def default_profile_should_be_used?
479
496
  @profiles.empty? &&
480
497
  profile_loader.cucumber_yml_defined? &&
481
- profile_loader.has_profile?(@default_profile)
498
+ profile_loader.profile?(@default_profile)
482
499
  end
483
500
 
484
501
  def profile_loader
485
502
  @profile_loader ||= ProfileLoader.new
486
503
  end
487
504
 
488
- def reverse_merge(other_options)
505
+ def reverse_merge(other_options) # rubocop:disable Metrics/AbcSize
489
506
  @options = other_options.options.merge(@options)
490
507
  @options[:require] += other_options[:require]
491
508
  @options[:excludes] += other_options[:excludes]
@@ -514,7 +531,7 @@ TEXT
514
531
  @options[:formats] = stdout_formats[0..0] + non_stdout_formats
515
532
  end
516
533
 
517
- @options[:retry] = other_options[:retry] if @options[:retry] == 0
534
+ @options[:retry] = other_options[:retry] if @options[:retry].zero?
518
535
 
519
536
  self
520
537
  end
@@ -550,7 +567,7 @@ TEXT
550
567
  ['but (code)', to_code_keywords_string(language.but_keywords)]
551
568
  ]
552
569
  )
553
- @out_stream.write(data.to_s({ color: false, prefixes: Hash.new('') }))
570
+ @out_stream.write(data.to_s(color: false, prefixes: Hash.new('')))
554
571
  Kernel.exit(0)
555
572
  end
556
573
 
@@ -561,7 +578,7 @@ TEXT
561
578
  [key, ::Gherkin::DIALECTS[key].fetch('name'), ::Gherkin::DIALECTS[key].fetch('native')]
562
579
  end
563
580
  )
564
- @out_stream.write(data.to_s({ color: false, prefixes: Hash.new('') }))
581
+ @out_stream.write(data.to_s(color: false, prefixes: Hash.new('')))
565
582
  Kernel.exit(0)
566
583
  end
567
584
 
@@ -575,20 +592,20 @@ TEXT
575
592
 
576
593
  def default_options
577
594
  {
578
- :strict => Cucumber::Core::Test::Result::StrictConfiguration.new,
579
- :require => [],
580
- :dry_run => false,
581
- :formats => [],
582
- :excludes => [],
583
- :tag_expressions => [],
584
- :tag_limits => {},
585
- :name_regexps => [],
586
- :env_vars => {},
587
- :diff_enabled => true,
588
- :snippets => true,
589
- :source => true,
590
- :duration => true,
591
- :retry => 0
595
+ strict: Cucumber::Core::Test::Result::StrictConfiguration.new,
596
+ require: [],
597
+ dry_run: false,
598
+ formats: [],
599
+ excludes: [],
600
+ tag_expressions: [],
601
+ tag_limits: {},
602
+ name_regexps: [],
603
+ env_vars: {},
604
+ diff_enabled: true,
605
+ snippets: true,
606
+ source: true,
607
+ duration: true,
608
+ retry: 0
592
609
  }
593
610
  end
594
611
  end
@@ -16,26 +16,19 @@ Could not find profile: '#{profile}'
16
16
 
17
17
  Defined profiles in cucumber.yml:
18
18
  * #{cucumber_yml.keys.sort.join("\n * ")}
19
- END_OF_ERROR
19
+ END_OF_ERROR
20
20
  end
21
21
 
22
22
  args_from_yml = cucumber_yml[profile] || ''
23
23
 
24
- require 'shellwords'
25
-
26
24
  case args_from_yml
27
25
  when String
28
- raise YmlLoadError, "The '#{profile}' profile in cucumber.yml was blank. Please define the command line arguments for the '#{profile}' profile in cucumber.yml.\n" if args_from_yml =~ /^\s*$/
29
- if Cucumber::WINDOWS
30
- # Shellwords treats backslash as an escape character so we have to mask it out temporarily
31
-
32
- placeholder = 'pseudo_unique_backslash_placeholder'
33
- sanitized_line = args_from_yml.gsub('\\', placeholder)
34
-
35
- args_from_yml = Shellwords.shellwords(sanitized_line).collect { |argument| argument.gsub(placeholder, '\\') }
36
- else
37
- args_from_yml = Shellwords.shellwords(args_from_yml)
26
+ if args_from_yml =~ /^\s*$/
27
+ raise YmlLoadError, "The '#{profile}' profile in cucumber.yml was blank." \
28
+ " Please define the command line arguments for the '#{profile}' profile in cucumber.yml.\n"
38
29
  end
30
+
31
+ args_from_yml = processed_shellwords(args_from_yml)
39
32
  when Array
40
33
  raise YmlLoadError, "The '#{profile}' profile in cucumber.yml was empty. Please define the command line arguments for the '#{profile}' profile in cucumber.yml.\n" if args_from_yml.empty?
41
34
  else
@@ -45,7 +38,7 @@ Defined profiles in cucumber.yml:
45
38
  args_from_yml
46
39
  end
47
40
 
48
- def has_profile?(profile)
41
+ def profile?(profile)
49
42
  cucumber_yml.key?(profile)
50
43
  end
51
44
 
@@ -58,29 +51,47 @@ Defined profiles in cucumber.yml:
58
51
  # Loads the profile, processing it through ERB and YAML, and returns it as a hash.
59
52
  def cucumber_yml
60
53
  return @cucumber_yml if @cucumber_yml
61
- unless cucumber_yml_defined?
62
- raise(ProfilesNotDefinedError, "cucumber.yml was not found. Current directory is #{Dir.pwd}. Please refer to cucumber's documentation on defining profiles in cucumber.yml. You must define a 'default' profile to use the cucumber command without any arguments.\nType 'cucumber --help' for usage.\n")
54
+
55
+ ensure_configuration_file_exists
56
+ process_configuration_file_with_erb
57
+ load_configuration
58
+
59
+ if @cucumber_yml.nil? || !@cucumber_yml.is_a?(Hash)
60
+ raise(YmlLoadError, 'cucumber.yml was found, but was blank or malformed. ' \
61
+ "Please refer to cucumber's documentation on correct profile usage.\n")
63
62
  end
64
63
 
64
+ @cucumber_yml
65
+ end
66
+
67
+ def ensure_configuration_file_exists
68
+ return if cucumber_yml_defined?
69
+
70
+ raise(ProfilesNotDefinedError, "cucumber.yml was not found. Current directory is #{Dir.pwd}." \
71
+ "Please refer to cucumber's documentation on defining profiles in cucumber.yml. You must define" \
72
+ "a 'default' profile to use the cucumber command without any arguments.\nType 'cucumber --help' for usage.\n")
73
+ end
74
+
75
+ def process_configuration_file_with_erb
65
76
  require 'erb'
66
- require 'yaml'
67
77
  begin
68
- @cucumber_erb = ERB.new(IO.read(cucumber_file), nil, '%').result(binding)
78
+ @cucumber_erb = if RUBY_VERSION >= '2.6'
79
+ ERB.new(IO.read(cucumber_file), trim_mode: '%').result(binding)
80
+ else
81
+ ERB.new(IO.read(cucumber_file), nil, '%').result(binding)
82
+ end
69
83
  rescue StandardError
70
- raise(YmlLoadError, "cucumber.yml was found, but could not be parsed with ERB. Please refer to cucumber's documentation on correct profile usage.\n#{$!.inspect}")
84
+ raise(YmlLoadError, "cucumber.yml was found, but could not be parsed with ERB. Please refer to cucumber's documentation on correct profile usage.\n#{$ERROR_INFO.inspect}")
71
85
  end
86
+ end
72
87
 
88
+ def load_configuration
89
+ require 'yaml'
73
90
  begin
74
- @cucumber_yml = YAML.load(@cucumber_erb)
91
+ @cucumber_yml = YAML.load(@cucumber_erb) # rubocop:disable Security/YAMLLoad
75
92
  rescue StandardError
76
93
  raise(YmlLoadError, "cucumber.yml was found, but could not be parsed. Please refer to cucumber's documentation on correct profile usage.\n")
77
94
  end
78
-
79
- if @cucumber_yml.nil? || !@cucumber_yml.is_a?(Hash)
80
- raise(YmlLoadError, "cucumber.yml was found, but was blank or malformed. Please refer to cucumber's documentation on correct profile usage.\n")
81
- end
82
-
83
- return @cucumber_yml
84
95
  end
85
96
 
86
97
  # Locates cucumber.yml file. The file can end in .yml or .yaml,
@@ -89,6 +100,18 @@ Defined profiles in cucumber.yml:
89
100
  def cucumber_file
90
101
  @cucumber_file ||= Dir.glob('{,.config/,config/}cucumber{.yml,.yaml}').first
91
102
  end
103
+
104
+ def processed_shellwords(args_from_yml)
105
+ require 'shellwords'
106
+
107
+ return Shellwords.shellwords(args_from_yml) unless Cucumber::WINDOWS
108
+
109
+ # Shellwords treats backslash as an escape character so we have to mask it out temporarily
110
+ placeholder = 'pseudo_unique_backslash_placeholder'
111
+ sanitized_line = args_from_yml.gsub('\\', placeholder)
112
+
113
+ Shellwords.shellwords(sanitized_line).collect { |argument| argument.gsub(placeholder, '\\') }
114
+ end
92
115
  end
93
116
  end
94
117
  end
@@ -3,6 +3,7 @@
3
3
  require 'cucumber/constantize'
4
4
  require 'cucumber/cli/rerun_file'
5
5
  require 'cucumber/events'
6
+ require 'cucumber/messages'
6
7
  require 'cucumber/core/event_bus'
7
8
  require 'cucumber/core/test/result'
8
9
  require 'forwardable'
@@ -54,13 +55,21 @@ module Cucumber
54
55
  end
55
56
 
56
57
  def seed
57
- Integer(@options[:seed] || rand(0xFFFF))
58
+ @options[:seed]
58
59
  end
59
60
 
60
61
  def dry_run?
61
62
  @options[:dry_run]
62
63
  end
63
64
 
65
+ def publish_enabled?
66
+ @options[:publish_enabled]
67
+ end
68
+
69
+ def publish_quiet?
70
+ @options[:publish_quiet]
71
+ end
72
+
64
73
  def fail_fast?
65
74
  @options[:fail_fast]
66
75
  end
@@ -168,19 +177,23 @@ module Cucumber
168
177
 
169
178
  def support_to_load
170
179
  support_files = all_files_to_load.select { |f| f =~ %r{/support/} }
180
+
181
+ # env_files are separated from other_files so we can ensure env files
182
+ # load first.
183
+ #
171
184
  env_files = support_files.select { |f| f =~ %r{/support/env\..*} }
172
185
  other_files = support_files - env_files
173
- @options[:dry_run] ? other_files : env_files + other_files
186
+ env_files.reverse + other_files.reverse
174
187
  end
175
188
 
176
189
  def all_files_to_load
177
190
  files = require_dirs.map do |path|
178
191
  path = path.tr('\\', '/') # In case we're on windows. Globs don't work with backslashes.
179
- path = path.gsub(/\/$/, '') # Strip trailing slash.
192
+ path = path.gsub(/\/$/, '') # Strip trailing slash. # rubocop:disable Style/RegexpLiteral
180
193
  File.directory?(path) ? Dir["#{path}/**/*"] : path
181
194
  end.flatten.uniq
182
195
  remove_excluded_files_from(files)
183
- files.reject! { |f| !File.file?(f) }
196
+ files.select! { |f| File.file?(f) }
184
197
  files.reject! { |f| File.extname(f) == '.feature' }
185
198
  files.reject! { |f| f =~ /^http/ }
186
199
  files.sort
@@ -192,15 +205,12 @@ module Cucumber
192
205
 
193
206
  def formatter_factories
194
207
  formats.map do |format, formatter_options, path_or_io|
195
- begin
196
- factory = formatter_class(format)
197
- yield factory,
198
- formatter_options,
199
- path_or_io,
200
- Cli::Options.new(STDOUT, STDERR, @options)
201
- rescue Exception => e
202
- raise e, "#{e.message}\nError creating formatter: #{format}", e.backtrace
203
- end
208
+ factory = formatter_class(format)
209
+ yield factory,
210
+ formatter_options,
211
+ path_or_io
212
+ rescue Exception => e # rubocop:disable Lint/RescueException
213
+ raise e, "#{e.message}\nError creating formatter: #{format}", e.backtrace
204
214
  end
205
215
  end
206
216
 
@@ -239,26 +249,31 @@ module Cucumber
239
249
  @options[:event_bus]
240
250
  end
241
251
 
252
+ def id_generator
253
+ @id_generator ||= Cucumber::Messages::IdGenerator::UUID.new
254
+ end
255
+
242
256
  private
243
257
 
244
258
  def default_options
245
259
  {
246
- :autoload_code_paths => ['features/support', 'features/step_definitions'],
247
- :filters => [],
248
- :strict => Cucumber::Core::Test::Result::StrictConfiguration.new,
249
- :require => [],
250
- :dry_run => false,
251
- :fail_fast => false,
252
- :formats => [],
253
- :excludes => [],
254
- :tag_expressions => [],
255
- :name_regexps => [],
256
- :env_vars => {},
257
- :diff_enabled => true,
258
- :snippets => true,
259
- :source => true,
260
- :duration => true,
261
- :event_bus => Cucumber::Events.make_event_bus
260
+ autoload_code_paths: ['features/support', 'features/step_definitions'],
261
+ filters: [],
262
+ strict: Cucumber::Core::Test::Result::StrictConfiguration.new,
263
+ require: [],
264
+ dry_run: false,
265
+ publish_quiet: false,
266
+ fail_fast: false,
267
+ formats: [],
268
+ excludes: [],
269
+ tag_expressions: [],
270
+ name_regexps: [],
271
+ env_vars: {},
272
+ diff_enabled: true,
273
+ snippets: true,
274
+ source: true,
275
+ duration: true,
276
+ event_bus: Cucumber::Events.make_event_bus
262
277
  }
263
278
  end
264
279