cucumber 8.0.0.rc.1 → 9.0.0

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