cucumber 8.0.0.rc.1 → 9.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.
data/README.md CHANGED
@@ -4,6 +4,7 @@
4
4
 
5
5
  # Cucumber
6
6
 
7
+ [![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://vshymanskyy.github.io/StandWithUkraine)
7
8
  [![OpenCollective](https://opencollective.com/cucumber/backers/badge.svg)](https://opencollective.com/cucumber)
8
9
  [![OpenCollective](https://opencollective.com/cucumber/sponsors/badge.svg)](https://opencollective.com/cucumber)
9
10
  [![pull requests](https://oselvar.com/api/badge?label=pull%20requests&csvUrl=https%3A%2F%2Fraw.githubusercontent.com%2Fcucumber%2Foselvar-github-metrics%2Fmain%2Fdata%2Fcucumber%2Fcucumber-ruby%2FpullRequests.csv)](https://oselvar.com/github/cucumber/oselvar-github-metrics/main/cucumber/cucumber-ruby)
@@ -50,13 +51,13 @@ Later in this document, bundler is considered being used so all commands are usi
50
51
 
51
52
  ### Supported platforms
52
53
 
54
+ - Ruby 3.2
53
55
  - Ruby 3.1
54
56
  - Ruby 3.0
55
57
  - Ruby 2.7
56
- - Ruby 2.6
58
+ - TruffleRuby 22.0.0+
57
59
  - JRuby (with [some limitations](https://github.com/cucumber/cucumber-ruby/blob/main/docs/jruby-limitations.md))
58
- - 9.3 >= 9.3.1 (there is a known issue with JRuby 9.3.0. More info can
59
- be found in the [PR#1571](https://github.com/cucumber/cucumber-ruby/pull/1571).)
60
+ - 9.4
60
61
 
61
62
  ### Ruby on Rails
62
63
 
@@ -56,11 +56,12 @@ module Cucumber
56
56
  NO_PROFILE_LONG_FLAG = '--no-profile'.freeze
57
57
  FAIL_FAST_FLAG = '--fail-fast'.freeze
58
58
  RETRY_FLAG = '--retry'.freeze
59
+ RETRY_TOTAL_FLAG = '--retry-total'.freeze
59
60
  OPTIONS_WITH_ARGS = [
60
61
  '-r', '--require', '--i18n-keywords', '-f', '--format', '-o',
61
62
  '--out', '-t', '--tags', '-n', '--name', '-e', '--exclude',
62
- PROFILE_SHORT_FLAG, PROFILE_LONG_FLAG, RETRY_FLAG, '-l',
63
- '--lines', '--port', '-I', '--snippet-type'
63
+ PROFILE_SHORT_FLAG, PROFILE_LONG_FLAG, RETRY_FLAG, RETRY_TOTAL_FLAG,
64
+ '-l', '--lines', '--port', '-I', '--snippet-type'
64
65
  ].freeze
65
66
  ORDER_TYPES = %w[defined random].freeze
66
67
  TAG_LIMIT_MATCHER = /(?<tag_name>@\w+):(?<limit>\d+)/x
@@ -108,6 +109,7 @@ module Cucumber
108
109
  opts.on('-j DIR', '--jars DIR', 'Load all the jars under DIR') { |jars| load_jars(jars) } if Cucumber::JRUBY
109
110
 
110
111
  opts.on("#{RETRY_FLAG} ATTEMPTS", *retry_msg) { |v| set_option :retry, v.to_i }
112
+ opts.on("#{RETRY_TOTAL_FLAG} TESTS", *retry_total_msg) { |v| set_option :retry_total, v.to_i }
111
113
  opts.on('--i18n-languages', *i18n_languages_msg) { list_languages_and_exit }
112
114
  opts.on('--i18n-keywords LANG', *i18n_keywords_msg) { |lang| language lang }
113
115
  opts.on(FAIL_FAST_FLAG, 'Exit immediately following the first failing scenario') { set_option :fail_fast }
@@ -271,6 +273,13 @@ Specify SEED to reproduce the shuffling from a previous run.
271
273
  ['Specify the number of times to retry failing tests (default: 0)']
272
274
  end
273
275
 
276
+ def retry_total_msg
277
+ [
278
+ 'The total number of failing test after which retrying of tests is suspended.',
279
+ 'Example: --retry-total 10 -> Will stop retrying tests after 10 failing tests.'
280
+ ]
281
+ end
282
+
274
283
  def name_msg
275
284
  [
276
285
  'Only execute the feature elements which match part of the given name.',
@@ -543,6 +552,7 @@ Specify SEED to reproduce the shuffling from a previous run.
543
552
  end
544
553
 
545
554
  @options[:retry] = other_options[:retry] if @options[:retry].zero?
555
+ @options[:retry_total] = other_options[:retry_total] if @options[:retry_total].infinite?
546
556
 
547
557
  self
548
558
  end
@@ -616,7 +626,8 @@ Specify SEED to reproduce the shuffling from a previous run.
616
626
  snippets: true,
617
627
  source: true,
618
628
  duration: true,
619
- retry: 0
629
+ retry: 0,
630
+ retry_total: Float::INFINITY
620
631
  }
621
632
  end
622
633
  end
@@ -78,6 +78,10 @@ module Cucumber
78
78
  @options[:retry]
79
79
  end
80
80
 
81
+ def retry_total_tests
82
+ @options[:retry_total]
83
+ end
84
+
81
85
  def guess?
82
86
  @options[:guess]
83
87
  end
@@ -273,7 +277,8 @@ module Cucumber
273
277
  snippets: true,
274
278
  source: true,
275
279
  duration: true,
276
- event_bus: Cucumber::Events.make_event_bus
280
+ event_bus: Cucumber::Events.make_event_bus,
281
+ retry_total: Float::INFINITY
277
282
  }
278
283
  end
279
284
 
@@ -7,6 +7,11 @@ require 'cucumber/events'
7
7
  module Cucumber
8
8
  module Filters
9
9
  class Retry < Core::Filter.new(:configuration)
10
+ def initialize(*_args)
11
+ super
12
+ @total_permanently_failed = 0
13
+ end
14
+
10
15
  def test_case(test_case)
11
16
  configuration.on_event(:test_case_finished) do |event|
12
17
  next unless retry_required?(test_case, event)
@@ -21,7 +26,21 @@ module Cucumber
21
26
  private
22
27
 
23
28
  def retry_required?(test_case, event)
24
- event.test_case == test_case && event.result.failed? && test_case_counts[test_case] < configuration.retry_attempts
29
+ return false unless event.test_case == test_case
30
+
31
+ return false unless event.result.failed?
32
+
33
+ return false if @total_permanently_failed >= configuration.retry_total_tests
34
+
35
+ retry_required = test_case_counts[test_case] < configuration.retry_attempts
36
+ if retry_required
37
+ # retry test
38
+ true
39
+ else
40
+ # test failed after max. attempts
41
+ @total_permanently_failed += 1
42
+ false
43
+ end
25
44
  end
26
45
 
27
46
  def test_case_counts
@@ -86,7 +86,7 @@ module Cucumber
86
86
  # example:
87
87
  #
88
88
  # apply_custom_colors('passed=white')
89
- def apply_custom_colors(colors)
89
+ def self.apply_custom_colors(colors)
90
90
  colors.split(':').each do |pair|
91
91
  a = pair.split('=')
92
92
  ALIASES[a[0]] = a[1]
@@ -22,6 +22,7 @@ module Cucumber
22
22
  @backtrace_filters << RbConfig::CONFIG['rubylibdir'] if RbConfig::CONFIG['rubylibdir']
23
23
 
24
24
  @backtrace_filters << 'org/jruby/' if ::Cucumber::JRUBY
25
+ @backtrace_filters << '<internal:' if RUBY_ENGINE == 'truffleruby'
25
26
 
26
27
  BACKTRACE_FILTER_PATTERNS = Regexp.new(@backtrace_filters.join('|'))
27
28
 
@@ -61,7 +61,7 @@ module Cucumber
61
61
  end
62
62
 
63
63
  def ensure_file(path, name)
64
- raise "You *must* specify --out FILE for the #{name} formatter" unless String == path.class
64
+ raise "You *must* specify --out FILE for the #{name} formatter" unless path.instance_of? String
65
65
  raise "I can't write #{name} to a directory - it has to be a file" if File.directory?(path)
66
66
  raise "I can't write #{name} to a file in the non-existing directory #{File.dirname(path)}" unless File.directory?(File.dirname(path))
67
67
 
@@ -69,7 +69,7 @@ module Cucumber
69
69
  end
70
70
 
71
71
  def ensure_dir(path, name)
72
- raise "You *must* specify --out DIR for the #{name} formatter" unless String == path.class
72
+ raise "You *must* specify --out DIR for the #{name} formatter" unless path.instance_of? String
73
73
  raise "I can't write #{name} reports to a file - it has to be a directory" if File.file?(path)
74
74
 
75
75
  FileUtils.mkdir_p(path) unless File.directory?(path)
@@ -19,8 +19,8 @@ module Cucumber
19
19
  @rb_language.build_rb_world_factory(world_modules, namespaced_world_modules, proc)
20
20
  end
21
21
 
22
- def register_rb_hook(phase, tag_names, proc)
23
- @rb_language.register_rb_hook(phase, tag_names, proc)
22
+ def register_rb_hook(phase, tag_names, proc, name: nil)
23
+ @rb_language.register_rb_hook(phase, tag_names, proc, name: name)
24
24
  end
25
25
 
26
26
  def define_parameter_type(parameter_type)
@@ -62,14 +62,14 @@ module Cucumber
62
62
 
63
63
  # Registers a proc that will run before each Scenario. You can register as many
64
64
  # as you want (typically from ruby scripts under <tt>support/hooks.rb</tt>).
65
- def Before(*tag_expressions, &proc)
66
- Dsl.register_rb_hook('before', tag_expressions, proc)
65
+ def Before(*tag_expressions, name: nil, &proc)
66
+ Dsl.register_rb_hook('before', tag_expressions, proc, name: name)
67
67
  end
68
68
 
69
69
  # Registers a proc that will run after each Scenario. You can register as many
70
70
  # as you want (typically from ruby scripts under <tt>support/hooks.rb</tt>).
71
- def After(*tag_expressions, &proc)
72
- Dsl.register_rb_hook('after', tag_expressions, proc)
71
+ def After(*tag_expressions, name: nil, &proc)
72
+ Dsl.register_rb_hook('after', tag_expressions, proc, name: name)
73
73
  end
74
74
 
75
75
  # Registers a proc that will be wrapped around each scenario. The proc
@@ -77,14 +77,14 @@ module Cucumber
77
77
  # argument (but passed as a regular argument, since blocks cannot accept
78
78
  # blocks in 1.8), on which it should call the .call method. You can register
79
79
  # as many as you want (typically from ruby scripts under <tt>support/hooks.rb</tt>).
80
- def Around(*tag_expressions, &proc)
81
- Dsl.register_rb_hook('around', tag_expressions, proc)
80
+ def Around(*tag_expressions, name: nil, &proc)
81
+ Dsl.register_rb_hook('around', tag_expressions, proc, name: name)
82
82
  end
83
83
 
84
84
  # Registers a proc that will run after each Step. You can register as
85
85
  # as you want (typically from ruby scripts under <tt>support/hooks.rb</tt>).
86
- def AfterStep(*tag_expressions, &proc)
87
- Dsl.register_rb_hook('after_step', tag_expressions, proc)
86
+ def AfterStep(*tag_expressions, name: nil, &proc)
87
+ Dsl.register_rb_hook('after_step', tag_expressions, proc, name: name)
88
88
  end
89
89
 
90
90
  def ParameterType(options)
@@ -108,20 +108,20 @@ module Cucumber
108
108
  end
109
109
 
110
110
  # Registers a proc that will run after Cucumber is configured in order to install an external plugin.
111
- def InstallPlugin(&proc)
112
- Dsl.register_rb_hook('install_plugin', [], proc)
111
+ def InstallPlugin(name: nil, &proc)
112
+ Dsl.register_rb_hook('install_plugin', [], proc, name: name)
113
113
  end
114
114
 
115
115
  # Registers a proc that will run before the execution of the scenarios.
116
116
  # Use it for your final set-ups
117
- def BeforeAll(&proc)
118
- Dsl.register_rb_hook('before_all', [], proc)
117
+ def BeforeAll(name: nil, &proc)
118
+ Dsl.register_rb_hook('before_all', [], proc, name: name)
119
119
  end
120
120
 
121
121
  # Registers a proc that will run after the execution of the scenarios.
122
122
  # Use it for your final clean-ups
123
- def AfterAll(&proc)
124
- Dsl.register_rb_hook('after_all', [], proc)
123
+ def AfterAll(name: nil, &proc)
124
+ Dsl.register_rb_hook('after_all', [], proc, name: name)
125
125
  end
126
126
 
127
127
  # Registers a new Ruby StepDefinition. This method is aliased
@@ -6,11 +6,12 @@ module Cucumber
6
6
  module Glue
7
7
  # TODO: Kill pointless wrapper for Before, After and AfterStep hooks with fire
8
8
  class Hook
9
- attr_reader :id, :tag_expressions, :location
9
+ attr_reader :id, :tag_expressions, :location, :name
10
10
 
11
- def initialize(id, registry, tag_expressions, proc)
11
+ def initialize(id, registry, tag_expressions, proc, name: nil)
12
12
  @id = id
13
13
  @registry = registry
14
+ @name = name
14
15
  @tag_expressions = sanitize_tag_expressions(tag_expressions)
15
16
  @proc = proc
16
17
  @location = Cucumber::Core::Test::Location.from_source_location(*@proc.source_location)
@@ -32,6 +33,7 @@ module Cucumber
32
33
  Cucumber::Messages::Envelope.new(
33
34
  hook: Cucumber::Messages::Hook.new(
34
35
  id: id,
36
+ name: name,
35
37
  tag_expression: tag_expressions.empty? ? nil : tag_expressions.join(' '),
36
38
  source_reference: Cucumber::Messages::SourceReference.new(
37
39
  uri: location.file,
@@ -3,7 +3,7 @@
3
3
  require 'cucumber/gherkin/formatter/ansi_escapes'
4
4
  require 'cucumber/core/test/data_table'
5
5
  require 'cucumber/deprecate'
6
- require 'mime/types'
6
+ require 'mini_mime'
7
7
 
8
8
  module Cucumber
9
9
  module Glue
@@ -92,7 +92,7 @@ module Cucumber
92
92
  return super unless File.file?(file)
93
93
 
94
94
  content = File.read(file, mode: 'rb')
95
- media_type = MIME::Types.type_for(file).first if media_type.nil?
95
+ media_type = MiniMime.lookup_by_filename(file)&.content_type if media_type.nil?
96
96
 
97
97
  super(content, media_type.to_s)
98
98
  rescue StandardError
@@ -72,8 +72,8 @@ module Cucumber
72
72
  end
73
73
  end
74
74
 
75
- def register_rb_hook(phase, tag_expressions, proc)
76
- hook = add_hook(phase, Hook.new(@configuration.id_generator.new_id, self, tag_expressions, proc))
75
+ def register_rb_hook(phase, tag_expressions, proc, name: nil)
76
+ hook = add_hook(phase, Hook.new(@configuration.id_generator.new_id, self, tag_expressions, proc, name: name))
77
77
  @configuration.notify :envelope, hook.to_envelope
78
78
  hook
79
79
  end
@@ -7,7 +7,7 @@ require 'cucumber/core/platform'
7
7
 
8
8
  module Cucumber
9
9
  unless defined?(Cucumber::VERSION)
10
- VERSION = File.read(File.expand_path('version', __dir__)).strip
10
+ VERSION = File.read(File.expand_path('../../VERSION', __dir__)).strip
11
11
  BINARY = File.expand_path("#{File.dirname(__FILE__)}/../../bin/cucumber")
12
12
  LIBDIR = File.expand_path("#{File.dirname(__FILE__)}/../../lib")
13
13
  RAILS = defined?(Rails)
@@ -36,7 +36,7 @@ module Cucumber
36
36
  attr_reader :args
37
37
 
38
38
  def initialize(libs, cucumber_opts, feature_files)
39
- raise 'libs must be an Array when running in-process' unless Array == libs.class
39
+ raise 'libs must be an Array when running in-process' unless libs.instance_of? Array
40
40
 
41
41
  libs.reverse_each { |lib| $LOAD_PATH.unshift(lib) }
42
42
  @args = (
@@ -113,7 +113,15 @@ module Cucumber
113
113
  attr_reader :cucumber_opts
114
114
 
115
115
  def cucumber_opts=(opts) # :nodoc:
116
- @cucumber_opts = String == opts.class ? opts.split(' ') : opts
116
+ unless opts.instance_of? String
117
+ @cucumber_opts = opts
118
+ return
119
+ end
120
+
121
+ @cucumber_opts = opts.split(' ')
122
+ return if @cucumber_opts.length <= 1
123
+
124
+ $stderr.puts 'WARNING: consider using an array rather than a space-delimited string with cucumber_opts to avoid undesired behavior.'
117
125
  end
118
126
 
119
127
  # Whether or not to fork a new ruby interpreter. Defaults to true. You may gain
@@ -155,7 +163,7 @@ module Cucumber
155
163
  end
156
164
 
157
165
  def runner(_task_args = nil) # :nodoc:
158
- cucumber_opts = [(ENV['CUCUMBER_OPTS'] ? ENV['CUCUMBER_OPTS'].split(/\s+/) : nil) || cucumber_opts_with_profile]
166
+ cucumber_opts = [ENV['CUCUMBER_OPTS']&.split(/\s+/) || cucumber_opts_with_profile]
159
167
  return ForkedCucumberRunner.new(libs, binary, cucumber_opts, bundler, feature_files) if fork
160
168
 
161
169
  InProcessCucumberRunner.new(libs, cucumber_opts, feature_files)