cucumber 3.2.0 → 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 (110) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +166 -19
  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/cli/configuration.rb +5 -5
  8. data/lib/cucumber/cli/main.rb +12 -12
  9. data/lib/cucumber/cli/options.rb +62 -71
  10. data/lib/cucumber/cli/profile_loader.rb +49 -26
  11. data/lib/cucumber/configuration.rb +31 -23
  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 +165 -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 +1 -1
  43. data/lib/cucumber/formatter/ignore_missing_messages.rb +1 -1
  44. data/lib/cucumber/formatter/interceptor.rb +8 -28
  45. data/lib/cucumber/formatter/io.rb +1 -1
  46. data/lib/cucumber/formatter/json.rb +101 -115
  47. data/lib/cucumber/formatter/junit.rb +56 -56
  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/query/hook_by_test_step.rb +31 -0
  53. data/lib/cucumber/formatter/query/pickle_by_test.rb +26 -0
  54. data/lib/cucumber/formatter/query/pickle_step_by_test_step.rb +26 -0
  55. data/lib/cucumber/formatter/query/step_definitions_by_test_step.rb +40 -0
  56. data/lib/cucumber/formatter/query/test_case_started_by_test_case.rb +40 -0
  57. data/lib/cucumber/formatter/rerun.rb +22 -4
  58. data/lib/cucumber/formatter/stepdefs.rb +1 -2
  59. data/lib/cucumber/formatter/steps.rb +2 -3
  60. data/lib/cucumber/formatter/summary.rb +16 -8
  61. data/lib/cucumber/formatter/unicode.rb +15 -17
  62. data/lib/cucumber/formatter/usage.rb +11 -10
  63. data/lib/cucumber/gherkin/data_table_parser.rb +17 -6
  64. data/lib/cucumber/gherkin/formatter/ansi_escapes.rb +13 -17
  65. data/lib/cucumber/gherkin/formatter/escaping.rb +2 -2
  66. data/lib/cucumber/gherkin/steps_parser.rb +17 -8
  67. data/lib/cucumber/glue/dsl.rb +1 -1
  68. data/lib/cucumber/glue/hook.rb +34 -11
  69. data/lib/cucumber/glue/invoke_in_world.rb +13 -18
  70. data/lib/cucumber/glue/proto_world.rb +42 -33
  71. data/lib/cucumber/glue/registry_and_more.rb +42 -12
  72. data/lib/cucumber/glue/snippet.rb +23 -22
  73. data/lib/cucumber/glue/step_definition.rb +42 -19
  74. data/lib/cucumber/glue/world_factory.rb +1 -1
  75. data/lib/cucumber/hooks.rb +11 -11
  76. data/lib/cucumber/multiline_argument/data_table/diff_matrices.rb +1 -1
  77. data/lib/cucumber/multiline_argument/data_table.rb +97 -64
  78. data/lib/cucumber/multiline_argument/doc_string.rb +1 -1
  79. data/lib/cucumber/multiline_argument.rb +4 -6
  80. data/lib/cucumber/platform.rb +3 -3
  81. data/lib/cucumber/rake/task.rb +16 -16
  82. data/lib/cucumber/rspec/disable_option_parser.rb +9 -8
  83. data/lib/cucumber/running_test_case.rb +2 -53
  84. data/lib/cucumber/runtime/after_hooks.rb +8 -4
  85. data/lib/cucumber/runtime/before_hooks.rb +8 -4
  86. data/lib/cucumber/runtime/for_programming_languages.rb +4 -2
  87. data/lib/cucumber/runtime/step_hooks.rb +3 -2
  88. data/lib/cucumber/runtime/support_code.rb +13 -15
  89. data/lib/cucumber/runtime/user_interface.rb +6 -16
  90. data/lib/cucumber/runtime.rb +54 -58
  91. data/lib/cucumber/step_definition_light.rb +4 -3
  92. data/lib/cucumber/step_definitions.rb +2 -2
  93. data/lib/cucumber/step_match.rb +12 -11
  94. data/lib/cucumber/step_match_search.rb +2 -1
  95. data/lib/cucumber/term/ansicolor.rb +9 -9
  96. data/lib/cucumber/version +1 -1
  97. data/lib/cucumber.rb +1 -1
  98. metadata +221 -77
  99. data/lib/cucumber/formatter/cucumber.css +0 -286
  100. data/lib/cucumber/formatter/cucumber.sass +0 -247
  101. data/lib/cucumber/formatter/hook_query_visitor.rb +0 -42
  102. data/lib/cucumber/formatter/html_builder.rb +0 -121
  103. data/lib/cucumber/formatter/inline-js.js +0 -30
  104. data/lib/cucumber/formatter/jquery-min.js +0 -154
  105. data/lib/cucumber/formatter/json_pretty.rb +0 -11
  106. data/lib/cucumber/formatter/legacy_api/adapter.rb +0 -1028
  107. data/lib/cucumber/formatter/legacy_api/ast.rb +0 -394
  108. data/lib/cucumber/formatter/legacy_api/results.rb +0 -50
  109. data/lib/cucumber/formatter/legacy_api/runtime_facade.rb +0 -32
  110. data/lib/cucumber/step_argument.rb +0 -25
@@ -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 }
107
+ opts.on('--init', *init_msg) { |_v| initialize_project }
112
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
 
@@ -320,11 +311,13 @@ TEXT
320
311
  [
321
312
  'Require files before executing the features. If this',
322
313
  'option is not specified, all *.rb files that are',
323
- 'siblings or below the features will be loaded auto-',
314
+ 'siblings of or below the features will be loaded auto-',
324
315
  '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.',
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.',
328
321
  'This option can be specified multiple times.'
329
322
  ]
330
323
  end
@@ -361,7 +354,7 @@ TEXT
361
354
  def language(lang)
362
355
  require 'gherkin/dialect'
363
356
 
364
- 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)
365
358
  list_keywords_and_exit(lang)
366
359
  end
367
360
 
@@ -370,7 +363,7 @@ TEXT
370
363
  end
371
364
 
372
365
  def non_stdout_formats
373
- @options[:formats].select { |_, _, output| output != @out_stream }
366
+ @options[:formats].reject { |_, _, output| output == @out_stream }
374
367
  end
375
368
 
376
369
  def add_option(option, value)
@@ -378,8 +371,8 @@ TEXT
378
371
  end
379
372
 
380
373
  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?(',')
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?(',')
383
376
  @options[:tag_expressions] << value.gsub(/(@\w+)(:\d+)?/, '\1')
384
377
  add_tag_limits(value)
385
378
  end
@@ -391,9 +384,7 @@ TEXT
391
384
  end
392
385
 
393
386
  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
387
+ raise "Inconsistent tag limits for #{tag_name}: #{tag_limits[tag_name]} and #{limit}" if tag_limits[tag_name] && tag_limits[tag_name] != limit
397
388
  tag_limits[tag_name] = limit
398
389
  end
399
390
 
@@ -440,7 +431,7 @@ TEXT
440
431
  def extract_environment_variables
441
432
  @args.delete_if do |arg|
442
433
  if arg =~ /^(\w+)=(.*)$/
443
- @options[:env_vars][$1] = $2
434
+ @options[:env_vars][Regexp.last_match(1)] = Regexp.last_match(2)
444
435
  true
445
436
  end
446
437
  end
@@ -469,8 +460,8 @@ TEXT
469
460
  profile_args = profile_loader.args_from(profile)
470
461
  profile_options = Options.parse(
471
462
  profile_args, @out_stream, @error_stream,
472
- :skip_profile_information => true,
473
- :profile_loader => profile_loader
463
+ skip_profile_information: true,
464
+ profile_loader: profile_loader
474
465
  )
475
466
  reverse_merge(profile_options)
476
467
  end
@@ -478,14 +469,14 @@ TEXT
478
469
  def default_profile_should_be_used?
479
470
  @profiles.empty? &&
480
471
  profile_loader.cucumber_yml_defined? &&
481
- profile_loader.has_profile?(@default_profile)
472
+ profile_loader.profile?(@default_profile)
482
473
  end
483
474
 
484
475
  def profile_loader
485
476
  @profile_loader ||= ProfileLoader.new
486
477
  end
487
478
 
488
- def reverse_merge(other_options)
479
+ def reverse_merge(other_options) # rubocop:disable Metrics/AbcSize
489
480
  @options = other_options.options.merge(@options)
490
481
  @options[:require] += other_options[:require]
491
482
  @options[:excludes] += other_options[:excludes]
@@ -514,7 +505,7 @@ TEXT
514
505
  @options[:formats] = stdout_formats[0..0] + non_stdout_formats
515
506
  end
516
507
 
517
- @options[:retry] = other_options[:retry] if @options[:retry] == 0
508
+ @options[:retry] = other_options[:retry] if @options[:retry].zero?
518
509
 
519
510
  self
520
511
  end
@@ -550,7 +541,7 @@ TEXT
550
541
  ['but (code)', to_code_keywords_string(language.but_keywords)]
551
542
  ]
552
543
  )
553
- @out_stream.write(data.to_s({ color: false, prefixes: Hash.new('') }))
544
+ @out_stream.write(data.to_s(color: false, prefixes: Hash.new('')))
554
545
  Kernel.exit(0)
555
546
  end
556
547
 
@@ -561,7 +552,7 @@ TEXT
561
552
  [key, ::Gherkin::DIALECTS[key].fetch('name'), ::Gherkin::DIALECTS[key].fetch('native')]
562
553
  end
563
554
  )
564
- @out_stream.write(data.to_s({ color: false, prefixes: Hash.new('') }))
555
+ @out_stream.write(data.to_s(color: false, prefixes: Hash.new('')))
565
556
  Kernel.exit(0)
566
557
  end
567
558
 
@@ -575,20 +566,20 @@ TEXT
575
566
 
576
567
  def default_options
577
568
  {
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
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
592
583
  }
593
584
  end
594
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
@@ -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,7 +55,7 @@ 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?
@@ -168,19 +169,23 @@ module Cucumber
168
169
 
169
170
  def support_to_load
170
171
  support_files = all_files_to_load.select { |f| f =~ %r{/support/} }
172
+
173
+ # env_files are separated from other_files so we can ensure env files
174
+ # load first.
175
+ #
171
176
  env_files = support_files.select { |f| f =~ %r{/support/env\..*} }
172
177
  other_files = support_files - env_files
173
- @options[:dry_run] ? other_files : env_files + other_files
178
+ env_files.reverse + other_files.reverse
174
179
  end
175
180
 
176
181
  def all_files_to_load
177
182
  files = require_dirs.map do |path|
178
183
  path = path.tr('\\', '/') # In case we're on windows. Globs don't work with backslashes.
179
- path = path.gsub(/\/$/, '') # Strip trailing slash.
184
+ path = path.gsub(/\/$/, '') # Strip trailing slash. # rubocop:disable Style/RegexpLiteral
180
185
  File.directory?(path) ? Dir["#{path}/**/*"] : path
181
186
  end.flatten.uniq
182
187
  remove_excluded_files_from(files)
183
- files.reject! { |f| !File.file?(f) }
188
+ files.select! { |f| File.file?(f) }
184
189
  files.reject! { |f| File.extname(f) == '.feature' }
185
190
  files.reject! { |f| f =~ /^http/ }
186
191
  files.sort
@@ -196,9 +201,8 @@ module Cucumber
196
201
  factory = formatter_class(format)
197
202
  yield factory,
198
203
  formatter_options,
199
- path_or_io,
200
- Cli::Options.new(STDOUT, STDERR, @options)
201
- rescue Exception => e
204
+ path_or_io
205
+ rescue Exception => e # rubocop:disable Lint/RescueException
202
206
  raise e, "#{e.message}\nError creating formatter: #{format}", e.backtrace
203
207
  end
204
208
  end
@@ -239,26 +243,30 @@ module Cucumber
239
243
  @options[:event_bus]
240
244
  end
241
245
 
246
+ def id_generator
247
+ @id_generator ||= Cucumber::Messages::IdGenerator::UUID.new
248
+ end
249
+
242
250
  private
243
251
 
244
252
  def default_options
245
253
  {
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
254
+ autoload_code_paths: ['features/support', 'features/step_definitions'],
255
+ filters: [],
256
+ strict: Cucumber::Core::Test::Result::StrictConfiguration.new,
257
+ require: [],
258
+ dry_run: false,
259
+ fail_fast: false,
260
+ formats: [],
261
+ excludes: [],
262
+ tag_expressions: [],
263
+ name_regexps: [],
264
+ env_vars: {},
265
+ diff_enabled: true,
266
+ snippets: true,
267
+ source: true,
268
+ duration: true,
269
+ event_bus: Cucumber::Events.make_event_bus
262
270
  }
263
271
  end
264
272
 
@@ -17,11 +17,8 @@ module Cucumber
17
17
  constant
18
18
  rescue NameError => e
19
19
  require underscore(camel_cased_word)
20
- if try < 2
21
- retry
22
- else
23
- raise e
24
- end
20
+ retry if try < 2
21
+ raise e
25
22
  end
26
23
  end
27
24
 
@@ -5,23 +5,47 @@ require 'cucumber/gherkin/formatter/ansi_escapes'
5
5
 
6
6
  module Cucumber
7
7
  module Deprecate
8
- module ForUsers
9
- AnsiEscapes = Cucumber::Gherkin::Formatter::AnsiEscapes
8
+ class AnsiString
9
+ include Cucumber::Gherkin::Formatter::AnsiEscapes
10
+
11
+ def self.failure_message(message)
12
+ AnsiString.new.failure_message(message)
13
+ end
14
+
15
+ def failure_message(message)
16
+ failed + message + reset
17
+ end
18
+ end
10
19
 
20
+ class CliOption
21
+ def self.deprecate(stream, option, message, remove_after_version)
22
+ return if stream.nil?
23
+ stream.puts(
24
+ AnsiString.failure_message(
25
+ "\nWARNING: #{option} is deprecated" \
26
+ " and will be removed after version #{remove_after_version}.\n#{message}.\n"
27
+ )
28
+ )
29
+ end
30
+ end
31
+
32
+ module ForUsers
11
33
  def self.call(message, method, remove_after_version)
12
- STDERR.puts AnsiEscapes.failed + "\nWARNING: ##{method} is deprecated and will be removed after version #{remove_after_version}. #{message}.\n(Called from #{caller[2]})" + AnsiEscapes.reset
34
+ STDERR.puts AnsiString.failure_message(
35
+ "\nWARNING: ##{method} is deprecated" \
36
+ " and will be removed after version #{remove_after_version}. #{message}.\n" \
37
+ "(Called from #{caller(3..3).first})"
38
+ )
13
39
  end
14
40
  end
15
41
 
16
42
  module ForDevelopers
17
43
  def self.call(_message, _method, remove_after_version)
18
- if Cucumber::VERSION > remove_after_version
19
- raise "This method is due for removal after version #{remove_after_version}"
20
- end
44
+ raise "This method is due for removal after version #{remove_after_version}" if Cucumber::VERSION > remove_after_version
21
45
  end
22
46
  end
23
47
 
24
- STRATEGY = $0.match(/rspec$/) ? ForDevelopers : ForUsers
48
+ STRATEGY = $PROGRAM_NAME =~ /rspec$/ ? ForDevelopers : ForUsers
25
49
  end
26
50
 
27
51
  def self.deprecate(*args)
@@ -6,14 +6,12 @@ module Cucumber
6
6
  # Raised when there is no matching StepDefinition for a step.
7
7
  class Undefined < Core::Test::Result::Undefined
8
8
  def self.from(result, step_name)
9
- if result.is_a?(self)
10
- return result.with_message(with_prefix(result.message))
11
- end
9
+ return result.with_message(with_prefix(result.message)) if result.is_a?(self)
12
10
 
13
11
  begin
14
- raise self.new(with_prefix(step_name))
15
- rescue => exception
16
- return exception
12
+ raise new(with_prefix(step_name)) # rubocop:disable Style/RaiseArgs
13
+ rescue StandardError => e
14
+ return e
17
15
  end
18
16
  end
19
17
 
@@ -37,7 +35,7 @@ module Cucumber
37
35
  # Raised when a step matches 2 or more StepDefinitions
38
36
  class Ambiguous < StandardError
39
37
  def initialize(step_name, step_definitions, used_guess)
40
- message = String.new
38
+ message = String.new # rubocop:disable Style/EmptyLiteral
41
39
  message << "Ambiguous match of \"#{step_name}\":\n\n"
42
40
  message << step_definitions.map(&:backtrace_line).join("\n")
43
41
  message << "\n\n"
@@ -0,0 +1,9 @@
1
+ require 'cucumber/core/events'
2
+
3
+ module Cucumber
4
+ module Events
5
+ class Envelope < Core::Event.new(:envelope)
6
+ attr_reader :envelope
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ require 'cucumber/core/events'
2
+
3
+ module Cucumber
4
+ module Events
5
+ # Fired after we've parsed the contents of a feature file
6
+ class GherkinSourceParsed < Core::Event.new(:gherkin_document)
7
+ # The Gherkin Ast
8
+ attr_reader :gherkin_document
9
+ end
10
+ end
11
+ end