cucumber 3.1.2 → 4.0.0

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