cucumber 3.1.2 → 4.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 (112) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +173 -14
  3. data/CONTRIBUTING.md +2 -18
  4. data/README.md +4 -5
  5. data/bin/cucumber +1 -1
  6. data/lib/autotest/cucumber_mixin.rb +34 -39
  7. data/lib/cucumber.rb +1 -1
  8. data/lib/cucumber/cli/configuration.rb +5 -5
  9. data/lib/cucumber/cli/main.rb +12 -12
  10. data/lib/cucumber/cli/options.rb +69 -74
  11. data/lib/cucumber/cli/profile_loader.rb +49 -26
  12. data/lib/cucumber/configuration.rb +31 -23
  13. data/lib/cucumber/constantize.rb +2 -5
  14. data/lib/cucumber/deprecate.rb +31 -7
  15. data/lib/cucumber/errors.rb +5 -7
  16. data/lib/cucumber/events.rb +13 -6
  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 +13 -0
  20. data/lib/cucumber/events/step_activated.rb +2 -1
  21. data/lib/cucumber/events/test_case_created.rb +13 -0
  22. data/lib/cucumber/events/test_case_ready.rb +12 -0
  23. data/lib/cucumber/events/test_step_created.rb +13 -0
  24. data/lib/cucumber/events/undefined_parameter_type.rb +10 -0
  25. data/lib/cucumber/file_specs.rb +6 -6
  26. data/lib/cucumber/filters.rb +1 -0
  27. data/lib/cucumber/filters/activate_steps.rb +5 -3
  28. data/lib/cucumber/filters/broadcast_test_case_ready_event.rb +12 -0
  29. data/lib/cucumber/filters/prepare_world.rb +5 -9
  30. data/lib/cucumber/filters/quit.rb +1 -3
  31. data/lib/cucumber/filters/tag_limits/verifier.rb +2 -4
  32. data/lib/cucumber/formatter/ansicolor.rb +40 -45
  33. data/lib/cucumber/formatter/ast_lookup.rb +165 -0
  34. data/lib/cucumber/formatter/backtrace_filter.rb +9 -8
  35. data/lib/cucumber/formatter/console.rb +58 -66
  36. data/lib/cucumber/formatter/console_counts.rb +4 -9
  37. data/lib/cucumber/formatter/console_issues.rb +6 -3
  38. data/lib/cucumber/formatter/duration.rb +1 -1
  39. data/lib/cucumber/formatter/duration_extractor.rb +3 -1
  40. data/lib/cucumber/formatter/errors.rb +6 -0
  41. data/lib/cucumber/formatter/fanout.rb +2 -0
  42. data/lib/cucumber/formatter/html.rb +11 -598
  43. data/lib/cucumber/formatter/http_io.rb +146 -0
  44. data/lib/cucumber/formatter/ignore_missing_messages.rb +1 -1
  45. data/lib/cucumber/formatter/interceptor.rb +8 -28
  46. data/lib/cucumber/formatter/io.rb +17 -11
  47. data/lib/cucumber/formatter/json.rb +101 -109
  48. data/lib/cucumber/formatter/junit.rb +56 -56
  49. data/lib/cucumber/formatter/message.rb +22 -0
  50. data/lib/cucumber/formatter/message_builder.rb +255 -0
  51. data/lib/cucumber/formatter/pretty.rb +359 -153
  52. data/lib/cucumber/formatter/progress.rb +30 -32
  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 +2 -3
  61. data/lib/cucumber/formatter/summary.rb +16 -8
  62. data/lib/cucumber/formatter/unicode.rb +15 -17
  63. data/lib/cucumber/formatter/usage.rb +11 -10
  64. data/lib/cucumber/gherkin/data_table_parser.rb +17 -6
  65. data/lib/cucumber/gherkin/formatter/ansi_escapes.rb +13 -17
  66. data/lib/cucumber/gherkin/formatter/escaping.rb +2 -2
  67. data/lib/cucumber/gherkin/steps_parser.rb +17 -8
  68. data/lib/cucumber/glue/dsl.rb +1 -1
  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.rb +4 -6
  78. data/lib/cucumber/multiline_argument/data_table.rb +97 -64
  79. data/lib/cucumber/multiline_argument/data_table/diff_matrices.rb +1 -1
  80. data/lib/cucumber/multiline_argument/doc_string.rb +1 -1
  81. data/lib/cucumber/platform.rb +3 -3
  82. data/lib/cucumber/rake/task.rb +16 -16
  83. data/lib/cucumber/rspec/disable_option_parser.rb +9 -8
  84. data/lib/cucumber/running_test_case.rb +2 -53
  85. data/lib/cucumber/runtime.rb +54 -58
  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 +3 -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/step_definition_light.rb +4 -3
  93. data/lib/cucumber/step_definitions.rb +2 -2
  94. data/lib/cucumber/step_match.rb +12 -11
  95. data/lib/cucumber/step_match_search.rb +2 -1
  96. data/lib/cucumber/term/ansicolor.rb +9 -9
  97. data/lib/cucumber/version +1 -1
  98. metadata +224 -82
  99. data/lib/cucumber/events/gherkin_source_parsed.rb~ +0 -14
  100. data/lib/cucumber/formatter/ast_lookup.rb~ +0 -9
  101. data/lib/cucumber/formatter/cucumber.css +0 -286
  102. data/lib/cucumber/formatter/cucumber.sass +0 -247
  103. data/lib/cucumber/formatter/hook_query_visitor.rb +0 -42
  104. data/lib/cucumber/formatter/html_builder.rb +0 -121
  105. data/lib/cucumber/formatter/inline-js.js +0 -30
  106. data/lib/cucumber/formatter/jquery-min.js +0 -154
  107. data/lib/cucumber/formatter/json_pretty.rb +0 -11
  108. data/lib/cucumber/formatter/legacy_api/adapter.rb +0 -1028
  109. data/lib/cucumber/formatter/legacy_api/ast.rb +0 -394
  110. data/lib/cucumber/formatter/legacy_api/results.rb +0 -50
  111. data/lib/cucumber/formatter/legacy_api/runtime_facade.rb +0 -32
  112. data/lib/cucumber/step_argument.rb +0 -25
@@ -20,7 +20,7 @@ module Cucumber
20
20
  def initialize(out_stream = STDOUT, error_stream = STDERR)
21
21
  @out_stream = out_stream
22
22
  @error_stream = error_stream
23
- @options = Options.new(@out_stream, @error_stream, :default_profile => 'default')
23
+ @options = Options.new(@out_stream, @error_stream, default_profile: 'default')
24
24
  end
25
25
 
26
26
  def parse!(args)
@@ -64,7 +64,7 @@ module Cucumber
64
64
  end
65
65
 
66
66
  def fail_fast?
67
- !!@options[:fail_fast]
67
+ @options[:fail_fast]
68
68
  end
69
69
 
70
70
  def retry_attempts
@@ -79,7 +79,7 @@ module Cucumber
79
79
  logger = Logger.new(@out_stream)
80
80
  logger.formatter = LogFormatter.new
81
81
  logger.level = Logger::INFO
82
- logger.level = Logger::DEBUG if self.verbose?
82
+ logger.level = Logger::DEBUG if verbose?
83
83
  logger
84
84
  end
85
85
 
@@ -108,7 +108,7 @@ module Cucumber
108
108
  end
109
109
 
110
110
  def to_hash
111
- Hash(@options).merge(out_stream: @out_stream, error_stream: @error_stream)
111
+ Hash(@options).merge(out_stream: @out_stream, error_stream: @error_stream, seed: seed)
112
112
  end
113
113
 
114
114
  private
@@ -131,7 +131,7 @@ module Cucumber
131
131
  f[2] == @out_stream ? -1 : 1
132
132
  end
133
133
  @options[:formats].uniq!
134
- @options.check_formatter_stream_conflicts()
134
+ @options.check_formatter_stream_conflicts
135
135
  end
136
136
  end
137
137
  end
@@ -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
@@ -10,25 +10,23 @@ module Cucumber
10
10
  module Cli
11
11
  class Options
12
12
  INDENT = ' ' * 53
13
- # rubocop:disable Layout/MultilineOperationIndentation
14
13
  BUILTIN_FORMATS = {
15
- 'html' => ['Cucumber::Formatter::Html', 'Generates a nice looking HTML report.'],
16
14
  'pretty' => ['Cucumber::Formatter::Pretty', 'Prints the feature as is - in colours.'],
17
15
  'progress' => ['Cucumber::Formatter::Progress', 'Prints one character per scenario.'],
18
16
  '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" +
17
+ 'usage' => ['Cucumber::Formatter::Usage', "Prints where step definitions are used.\n" \
18
+ "#{INDENT}The slowest step definitions (with duration) are\n" \
19
+ "#{INDENT}listed first. If --dry-run is used the duration\n" \
20
+ "#{INDENT}is not shown, and step definitions are sorted by\n" \
23
21
  "#{INDENT}filename instead."],
24
- 'stepdefs' => ['Cucumber::Formatter::Stepdefs', "Prints All step definitions with their locations. Same as\n" +
22
+ 'stepdefs' => ['Cucumber::Formatter::Stepdefs', "Prints All step definitions with their locations. Same as\n" \
25
23
  "#{INDENT}the usage formatter, except that steps are not printed."],
26
24
  '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'],
25
+ 'json' => ['Cucumber::Formatter::Json', '[DEPRECATED] Prints the feature as JSON'],
26
+ 'message' => ['Cucumber::Formatter::Message', 'Outputs protobuf messages'],
27
+ 'html' => ['Cucumber::Formatter::HTML', 'Outputs HTML report'],
29
28
  'summary' => ['Cucumber::Formatter::Summary', 'Summary output of feature and scenarios']
30
- }
31
- # rubocop:enable Layout/MultilineOperationIndentation
29
+ }.freeze
32
30
  max = BUILTIN_FORMATS.keys.map(&:length).max
33
31
  FORMAT_HELP_MSG = [
34
32
  'Use --format rerun --out rerun.txt to write out failing',
@@ -41,24 +39,24 @@ module Cucumber
41
39
  'foo/bar_zap.rb. You can place the file with this relative',
42
40
  'path underneath your features/support directory or anywhere',
43
41
  "on Ruby's LOAD_PATH, for example in a Ruby gem."
44
- ]
42
+ ].freeze
45
43
 
46
44
  FORMAT_HELP = (BUILTIN_FORMATS.keys.sort.map do |key|
47
45
  " #{key}#{' ' * (max - key.length)} : #{BUILTIN_FORMATS[key][1]}"
48
46
  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'
47
+ PROFILE_SHORT_FLAG = '-p'.freeze
48
+ NO_PROFILE_SHORT_FLAG = '-P'.freeze
49
+ PROFILE_LONG_FLAG = '--profile'.freeze
50
+ NO_PROFILE_LONG_FLAG = '--no-profile'.freeze
51
+ FAIL_FAST_FLAG = '--fail-fast'.freeze
52
+ RETRY_FLAG = '--retry'.freeze
55
53
  OPTIONS_WITH_ARGS = [
56
54
  '-r', '--require', '--i18n-keywords', '-f', '--format', '-o',
57
55
  '--out', '-t', '--tags', '-n', '--name', '-e', '--exclude',
58
56
  PROFILE_SHORT_FLAG, PROFILE_LONG_FLAG, RETRY_FLAG, '-l',
59
57
  '--lines', '--port', '-I', '--snippet-type'
60
- ]
61
- ORDER_TYPES = %w{defined random}
58
+ ].freeze
59
+ ORDER_TYPES = %w[defined random].freeze
62
60
  TAG_LIMIT_MATCHER = /(?<tag_name>\@\w+):(?<limit>\d+)/x
63
61
 
64
62
  def self.parse(args, out_stream, error_stream, options = {})
@@ -87,19 +85,17 @@ module Cucumber
87
85
  @options[key] = value
88
86
  end
89
87
 
90
- def parse!(args) # rubocop:disable Metrics/AbcSize
88
+ def parse!(args) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
91
89
  @args = args
92
90
  @expanded_args = @args.dup
93
91
 
94
92
  @args.extend(::OptionParser::Arguable)
95
93
 
96
- @args.options do |opts|
94
+ @args.options do |opts| # rubocop:disable Metrics/BlockLength
97
95
  opts.banner = banner
98
96
  opts.on('-r LIBRARY|DIR', '--require LIBRARY|DIR', *require_files_msg) { |lib| require_files(lib) }
99
97
 
100
- if Cucumber::JRUBY
101
- opts.on('-j DIR', '--jars DIR', 'Load all the jars under DIR') { |jars| load_jars(jars) }
102
- end
98
+ opts.on('-j DIR', '--jars DIR', 'Load all the jars under DIR') { |jars| load_jars(jars) } if Cucumber::JRUBY
103
99
 
104
100
  opts.on("#{RETRY_FLAG} ATTEMPTS", *retry_msg) { |v| set_option :retry, v.to_i }
105
101
  opts.on('--i18n-languages', *i18n_languages_msg) { list_languages_and_exit }
@@ -108,13 +104,13 @@ module Cucumber
108
104
  opts.on('-f FORMAT', '--format FORMAT', *format_msg, *FORMAT_HELP) do |v|
109
105
  add_option :formats, [*parse_formats(v), @out_stream]
110
106
  end
111
- opts.on('--init', *init_msg) { |v| initialize_project }
112
- opts.on('-o', '--out [FILE|DIR]', *out_msg) { |v| out_stream v }
107
+ opts.on('--init', *init_msg) { |_v| initialize_project }
108
+ opts.on('-o', '--out [FILE|DIR|URL]', *out_msg) { |v| out_stream v }
113
109
  opts.on('-t TAG_EXPRESSION', '--tags TAG_EXPRESSION', *tags_msg) { |v| add_tag v }
114
110
  opts.on('-n NAME', '--name NAME', *name_msg) { |v| add_option :name_regexps, /#{v}/ }
115
111
  opts.on('-e', '--exclude PATTERN', *exclude_msg) { |v| add_option :excludes, Regexp.new(v) }
116
112
  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 }
113
+ opts.on(NO_PROFILE_SHORT_FLAG, NO_PROFILE_LONG_FLAG, *no_profile_short_flag_msg) { |_v| disable_profile_loading }
118
114
  opts.on('-c', '--[no-]color', *color_msg) { |v| color v }
119
115
  opts.on('-d', '--dry-run', *dry_run_msg) { set_dry_run_and_duration }
120
116
  opts.on('-m', '--no-multiline', "Don't print multiline strings and tables under steps.") { set_option :no_multiline }
@@ -140,11 +136,9 @@ module Cucumber
140
136
  [random] Shuffle scenarios before running.
141
137
  Specify SEED to reproduce the shuffling from a previous run.
142
138
  e.g. --order random:5738
143
- TEXT
139
+ TEXT
144
140
  @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
141
+ raise "'#{@options[:order]}' is not a recognised order type. Please use one of #{ORDER_TYPES.join(', ')}." unless ORDER_TYPES.include?(@options[:order])
148
142
  end
149
143
 
150
144
  opts.on_tail('--version', 'Show version.') { exit_ok(Cucumber::VERSION) }
@@ -156,7 +150,7 @@ TEXT
156
150
  extract_environment_variables
157
151
  @options[:paths] = @args.dup # whatver is left over
158
152
 
159
- check_formatter_stream_conflicts()
153
+ check_formatter_stream_conflicts
160
154
 
161
155
  merge_profiles
162
156
 
@@ -171,7 +165,7 @@ TEXT
171
165
  @options[:filters] ||= []
172
166
  end
173
167
 
174
- def check_formatter_stream_conflicts()
168
+ def check_formatter_stream_conflicts
175
169
  streams = @options[:formats].uniq.map { |(_, _, stream)| stream }
176
170
  return if streams == streams.uniq
177
171
  raise 'All but one formatter must use --out, only one can print to each stream (or STDOUT)'
@@ -196,10 +190,7 @@ TEXT
196
190
  end
197
191
 
198
192
  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
- ]
193
+ ['Invokes formatters without executing the steps.']
203
194
  end
204
195
 
205
196
  def exclude_msg
@@ -219,7 +210,7 @@ TEXT
219
210
  def i18n_keywords_msg
220
211
  [
221
212
  'List keywords for in a particular language',
222
- %{Run with "--i18n help" to see all languages}
213
+ %(Run with "--i18n help" to see all languages)
223
214
  ]
224
215
  end
225
216
 
@@ -305,10 +296,14 @@ TEXT
305
296
 
306
297
  def out_msg
307
298
  [
308
- 'Write output to a file/directory instead of STDOUT. This option',
299
+ 'Write output to a file/directory/URL instead of STDOUT. This option',
309
300
  'applies to the previously specified --format, or the',
310
301
  'default format if no format is specified. Check the specific',
311
- "formatter's docs to see whether to pass a file or a dir."
302
+ "formatter's docs to see whether to pass a file, dir or URL.",
303
+ "\n",
304
+ 'When using a URL, the output of the formatter will be sent as the HTTP request body.',
305
+ 'HTTP headers and request method can be set with cURL like options.',
306
+ 'Example: --out "http://example.com -X POST -H Content-Type:text/json"'
312
307
  ]
313
308
  end
314
309
 
@@ -316,11 +311,13 @@ TEXT
316
311
  [
317
312
  'Require files before executing the features. If this',
318
313
  'option is not specified, all *.rb files that are',
319
- 'siblings or below the features will be loaded auto-',
314
+ 'siblings of or below the features will be loaded auto-',
320
315
  'matically. Automatic loading is disabled when this',
321
- 'option is specified, and all loading becomes explicit.',
322
- 'Files under directories named "support" are always',
323
- 'loaded first.',
316
+ 'option is specified; all loading becomes explicit.',
317
+ 'Files in directories named "support" are still always',
318
+ 'loaded first when their parent directories are',
319
+ 'required or if the "support" directoires themselves are',
320
+ 'explicitly required.',
324
321
  'This option can be specified multiple times.'
325
322
  ]
326
323
  end
@@ -357,7 +354,7 @@ TEXT
357
354
  def language(lang)
358
355
  require 'gherkin/dialect'
359
356
 
360
- return indicate_invalid_language_and_exit(lang) unless ::Gherkin::DIALECTS.keys.include? lang
357
+ return indicate_invalid_language_and_exit(lang) unless ::Gherkin::DIALECTS.key?(lang)
361
358
  list_keywords_and_exit(lang)
362
359
  end
363
360
 
@@ -366,7 +363,7 @@ TEXT
366
363
  end
367
364
 
368
365
  def non_stdout_formats
369
- @options[:formats].select { |_, _, output| output != @out_stream }
366
+ @options[:formats].reject { |_, _, output| output == @out_stream }
370
367
  end
371
368
 
372
369
  def add_option(option, value)
@@ -374,8 +371,8 @@ TEXT
374
371
  end
375
372
 
376
373
  def add_tag(value)
377
- 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?('~')
378
- 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?(',')
374
+ raise("Found tags option '#{value}'. '~@tag' is no longer supported, use 'not @tag' instead.") if value.include?('~')
375
+ raise("Found tags option '#{value}'. '@tag1,@tag2' is no longer supported, use '@tag or @tag2' instead.") if value.include?(',')
379
376
  @options[:tag_expressions] << value.gsub(/(@\w+)(:\d+)?/, '\1')
380
377
  add_tag_limits(value)
381
378
  end
@@ -387,9 +384,7 @@ TEXT
387
384
  end
388
385
 
389
386
  def add_tag_limit(tag_limits, tag_name, limit)
390
- if tag_limits[tag_name] && tag_limits[tag_name] != limit
391
- raise "Inconsistent tag limits for #{tag_name}: #{tag_limits[tag_name]} and #{limit}"
392
- end
387
+ raise "Inconsistent tag limits for #{tag_name}: #{tag_limits[tag_name]} and #{limit}" if tag_limits[tag_name] && tag_limits[tag_name] != limit
393
388
  tag_limits[tag_name] = limit
394
389
  end
395
390
 
@@ -436,7 +431,7 @@ TEXT
436
431
  def extract_environment_variables
437
432
  @args.delete_if do |arg|
438
433
  if arg =~ /^(\w+)=(.*)$/
439
- @options[:env_vars][$1] = $2
434
+ @options[:env_vars][Regexp.last_match(1)] = Regexp.last_match(2)
440
435
  true
441
436
  end
442
437
  end
@@ -465,8 +460,8 @@ TEXT
465
460
  profile_args = profile_loader.args_from(profile)
466
461
  profile_options = Options.parse(
467
462
  profile_args, @out_stream, @error_stream,
468
- :skip_profile_information => true,
469
- :profile_loader => profile_loader
463
+ skip_profile_information: true,
464
+ profile_loader: profile_loader
470
465
  )
471
466
  reverse_merge(profile_options)
472
467
  end
@@ -474,14 +469,14 @@ TEXT
474
469
  def default_profile_should_be_used?
475
470
  @profiles.empty? &&
476
471
  profile_loader.cucumber_yml_defined? &&
477
- profile_loader.has_profile?(@default_profile)
472
+ profile_loader.profile?(@default_profile)
478
473
  end
479
474
 
480
475
  def profile_loader
481
476
  @profile_loader ||= ProfileLoader.new
482
477
  end
483
478
 
484
- def reverse_merge(other_options)
479
+ def reverse_merge(other_options) # rubocop:disable Metrics/AbcSize
485
480
  @options = other_options.options.merge(@options)
486
481
  @options[:require] += other_options[:require]
487
482
  @options[:excludes] += other_options[:excludes]
@@ -510,7 +505,7 @@ TEXT
510
505
  @options[:formats] = stdout_formats[0..0] + non_stdout_formats
511
506
  end
512
507
 
513
- @options[:retry] = other_options[:retry] if @options[:retry] == 0
508
+ @options[:retry] = other_options[:retry] if @options[:retry].zero?
514
509
 
515
510
  self
516
511
  end
@@ -546,7 +541,7 @@ TEXT
546
541
  ['but (code)', to_code_keywords_string(language.but_keywords)]
547
542
  ]
548
543
  )
549
- @out_stream.write(data.to_s({ color: false, prefixes: Hash.new('') }))
544
+ @out_stream.write(data.to_s(color: false, prefixes: Hash.new('')))
550
545
  Kernel.exit(0)
551
546
  end
552
547
 
@@ -557,7 +552,7 @@ TEXT
557
552
  [key, ::Gherkin::DIALECTS[key].fetch('name'), ::Gherkin::DIALECTS[key].fetch('native')]
558
553
  end
559
554
  )
560
- @out_stream.write(data.to_s({ color: false, prefixes: Hash.new('') }))
555
+ @out_stream.write(data.to_s(color: false, prefixes: Hash.new('')))
561
556
  Kernel.exit(0)
562
557
  end
563
558
 
@@ -571,20 +566,20 @@ TEXT
571
566
 
572
567
  def default_options
573
568
  {
574
- :strict => Cucumber::Core::Test::Result::StrictConfiguration.new,
575
- :require => [],
576
- :dry_run => false,
577
- :formats => [],
578
- :excludes => [],
579
- :tag_expressions => [],
580
- :tag_limits => {},
581
- :name_regexps => [],
582
- :env_vars => {},
583
- :diff_enabled => true,
584
- :snippets => true,
585
- :source => true,
586
- :duration => true,
587
- :retry => 0
569
+ strict: Cucumber::Core::Test::Result::StrictConfiguration.new,
570
+ require: [],
571
+ dry_run: false,
572
+ formats: [],
573
+ excludes: [],
574
+ tag_expressions: [],
575
+ tag_limits: {},
576
+ name_regexps: [],
577
+ env_vars: {},
578
+ diff_enabled: true,
579
+ snippets: true,
580
+ source: true,
581
+ duration: true,
582
+ retry: 0
588
583
  }
589
584
  end
590
585
  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