cucumber 7.1.0 → 9.2.1

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 (104) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +17 -27
  3. data/VERSION +1 -0
  4. data/lib/cucumber/cli/configuration.rb +4 -1
  5. data/lib/cucumber/cli/main.rb +5 -4
  6. data/lib/cucumber/cli/options.rb +73 -67
  7. data/lib/cucumber/cli/profile_loader.rb +6 -10
  8. data/lib/cucumber/cli/rerun_file.rb +1 -1
  9. data/lib/cucumber/configuration.rb +12 -6
  10. data/lib/cucumber/constantize.rb +1 -1
  11. data/lib/cucumber/deprecate.rb +6 -46
  12. data/lib/cucumber/errors.rb +3 -2
  13. data/lib/cucumber/events/envelope.rb +2 -0
  14. data/lib/cucumber/events/gherkin_source_parsed.rb +2 -0
  15. data/lib/cucumber/events/gherkin_source_read.rb +2 -0
  16. data/lib/cucumber/events/hook_test_step_created.rb +1 -2
  17. data/lib/cucumber/events/step_activated.rb +0 -6
  18. data/lib/cucumber/events/step_definition_registered.rb +0 -5
  19. data/lib/cucumber/events/test_case_created.rb +1 -2
  20. data/lib/cucumber/events/test_case_finished.rb +2 -0
  21. data/lib/cucumber/events/test_case_started.rb +2 -0
  22. data/lib/cucumber/events/test_run_finished.rb +2 -1
  23. data/lib/cucumber/events/test_step_created.rb +1 -2
  24. data/lib/cucumber/events/test_step_finished.rb +2 -0
  25. data/lib/cucumber/events/test_step_started.rb +2 -0
  26. data/lib/cucumber/events/undefined_parameter_type.rb +3 -2
  27. data/lib/cucumber/events.rb +1 -1
  28. data/lib/cucumber/file_specs.rb +2 -1
  29. data/lib/cucumber/filters/activate_steps.rb +1 -0
  30. data/lib/cucumber/filters/retry.rb +20 -1
  31. data/lib/cucumber/filters/tag_limits/verifier.rb +1 -3
  32. data/lib/cucumber/filters/tag_limits.rb +1 -3
  33. data/lib/cucumber/formatter/ansicolor.rb +70 -78
  34. data/lib/cucumber/formatter/ast_lookup.rb +16 -8
  35. data/lib/cucumber/formatter/backtrace_filter.rb +2 -1
  36. data/lib/cucumber/formatter/console.rb +26 -16
  37. data/lib/cucumber/formatter/console_counts.rb +3 -1
  38. data/lib/cucumber/formatter/console_issues.rb +10 -3
  39. data/lib/cucumber/formatter/curl_option_parser.rb +49 -0
  40. data/lib/cucumber/formatter/duration_extractor.rb +1 -0
  41. data/lib/cucumber/formatter/errors.rb +3 -0
  42. data/lib/cucumber/formatter/fail_fast.rb +1 -1
  43. data/lib/cucumber/formatter/fanout.rb +1 -1
  44. data/lib/cucumber/formatter/html.rb +2 -0
  45. data/lib/cucumber/formatter/http_io.rb +10 -137
  46. data/lib/cucumber/formatter/ignore_missing_messages.rb +1 -1
  47. data/lib/cucumber/formatter/io.rb +5 -3
  48. data/lib/cucumber/formatter/io_http_buffer.rb +88 -0
  49. data/lib/cucumber/formatter/json.rb +10 -12
  50. data/lib/cucumber/formatter/junit.rb +10 -7
  51. data/lib/cucumber/formatter/message_builder.rb +24 -8
  52. data/lib/cucumber/formatter/pretty.rb +24 -10
  53. data/lib/cucumber/formatter/progress.rb +1 -0
  54. data/lib/cucumber/formatter/publish_banner_printer.rb +0 -2
  55. data/lib/cucumber/formatter/query/hook_by_test_step.rb +3 -0
  56. data/lib/cucumber/formatter/query/pickle_by_test.rb +2 -0
  57. data/lib/cucumber/formatter/query/pickle_step_by_test_step.rb +2 -0
  58. data/lib/cucumber/formatter/query/step_definitions_by_test_step.rb +2 -0
  59. data/lib/cucumber/formatter/query/test_case_started_by_test_case.rb +4 -0
  60. data/lib/cucumber/formatter/rerun.rb +5 -3
  61. data/lib/cucumber/formatter/summary.rb +1 -0
  62. data/lib/cucumber/formatter/unicode.rb +7 -7
  63. data/lib/cucumber/formatter/url_reporter.rb +3 -1
  64. data/lib/cucumber/formatter/usage.rb +3 -3
  65. data/lib/cucumber/gherkin/data_table_parser.rb +1 -0
  66. data/lib/cucumber/gherkin/formatter/ansi_escapes.rb +25 -27
  67. data/lib/cucumber/glue/dsl.rb +20 -25
  68. data/lib/cucumber/glue/hook.rb +6 -3
  69. data/lib/cucumber/glue/invoke_in_world.rb +5 -5
  70. data/lib/cucumber/glue/proto_world.rb +30 -34
  71. data/lib/cucumber/glue/registry_and_more.rb +15 -25
  72. data/lib/cucumber/glue/snippet.rb +4 -2
  73. data/lib/cucumber/glue/step_definition.rb +6 -3
  74. data/lib/cucumber/glue/world_factory.rb +2 -0
  75. data/lib/cucumber/hooks.rb +1 -0
  76. data/lib/cucumber/multiline_argument/data_table/diff_matrices.rb +4 -1
  77. data/lib/cucumber/multiline_argument/data_table.rb +68 -80
  78. data/lib/cucumber/platform.rb +11 -16
  79. data/lib/cucumber/rake/task.rb +22 -15
  80. data/lib/cucumber/rspec/disable_option_parser.rb +6 -3
  81. data/lib/cucumber/running_test_case.rb +2 -1
  82. data/lib/cucumber/runtime/for_programming_languages.rb +1 -2
  83. data/lib/cucumber/runtime/meta_message_builder.rb +108 -0
  84. data/lib/cucumber/runtime/support_code.rb +3 -0
  85. data/lib/cucumber/runtime/user_interface.rb +7 -6
  86. data/lib/cucumber/runtime.rb +22 -38
  87. data/lib/cucumber/step_match.rb +6 -4
  88. data/lib/cucumber/step_match_search.rb +3 -2
  89. data/lib/cucumber/term/ansicolor.rb +74 -50
  90. data/lib/cucumber/term/banner.rb +3 -0
  91. data/lib/cucumber.rb +2 -1
  92. data/lib/simplecov_setup.rb +1 -1
  93. metadata +95 -244
  94. data/CHANGELOG.md +0 -3131
  95. data/CONTRIBUTING.md +0 -250
  96. data/lib/autotest/cucumber.rb +0 -8
  97. data/lib/autotest/cucumber_mixin.rb +0 -130
  98. data/lib/autotest/cucumber_rails.rb +0 -8
  99. data/lib/autotest/cucumber_rails_rspec.rb +0 -8
  100. data/lib/autotest/cucumber_rails_rspec2.rb +0 -8
  101. data/lib/autotest/cucumber_rspec.rb +0 -8
  102. data/lib/autotest/cucumber_rspec2.rb +0 -8
  103. data/lib/autotest/discover.rb +0 -13
  104. data/lib/cucumber/version +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f93e3cad9c408e1d22f274d24602251d1a1160e7a9438f4a8efeff9d77065978
4
- data.tar.gz: 8f963ac7a227bd73cc4177024825731788d47049f3fbe383d3051831eba4b0c8
3
+ metadata.gz: f6b3938bfcfa22aba558e5ea44825ed2ee19e9c7d26c0a5613f7998095d5c287
4
+ data.tar.gz: edc19d118e96175085722756b873b48c2ec319663edeedd81de027a48ac7d66c
5
5
  SHA512:
6
- metadata.gz: 2f8f88993561639861f542d90b09e46a7686d20ffadc690b6e310e8b6559f6f025647af8efed7683ccaf44932275cfcf1e68af9e2e26ecb9957d1fd32af317d1
7
- data.tar.gz: e2ea746319ad640a04789974c66bc6e2d14abfbbf7d7d6f5e3412e2c1b7318a54f938a58aa140cd6d5739fd4107844c235e312daaaa29506f3fa449d81115ce7
6
+ metadata.gz: f1338b46610ebd6fff711400c0160294f451e3c98558ca155712d07040a06f9a55fcb99c6e9cd00e5df248a415928b2a7c70da76a7badcc461b832ec464b66a8
7
+ data.tar.gz: b1c00b91c1055daf161482ba19b4cfa60089140af3212f9a1d71ef9a692d4dabd43fdb5fa3fa0bfcbcce018053f0ebc5460f1449bfaa32fa6afb0f532156c9d1
data/README.md CHANGED
@@ -1,14 +1,11 @@
1
- <p align="center">
2
- <img src="./.github/img/cucumber-open-logo.png" alt="Cucumber Open - Supported by Smartbear" width="428" />
3
- </p>
1
+ <img src="docs/img/cucumber-open-logo.png" alt="Cucumber Open - Supported by Smartbear" width="428" />
4
2
 
5
3
  # Cucumber
6
4
 
5
+ [![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://vshymanskyy.github.io/StandWithUkraine)
7
6
  [![OpenCollective](https://opencollective.com/cucumber/backers/badge.svg)](https://opencollective.com/cucumber)
8
7
  [![OpenCollective](https://opencollective.com/cucumber/sponsors/badge.svg)](https://opencollective.com/cucumber)
9
- [![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)
10
- [![issues](https://oselvar.com/api/badge?label=issues&csvUrl=https%3A%2F%2Fraw.githubusercontent.com%2Fcucumber%2Foselvar-github-metrics%2Fmain%2Fdata%2Fcucumber%2Fcucumber-ruby%2Fissues.csv)](https://oselvar.com/github/cucumber/oselvar-github-metrics/main/cucumber/cucumber-ruby)
11
- [![Test cucumber](https://github.com/cucumber/cucumber-ruby/actions/workflows/cucumber-ruby.yml/badge.svg)](https://github.com/cucumber/cucumber-ruby/actions/workflows/cucumber-ruby.yml)
8
+ [![Test cucumber](https://github.com/cucumber/cucumber-ruby/actions/workflows/test.yaml/badge.svg)](https://github.com/cucumber/cucumber-ruby/actions/workflows/test.yaml)
12
9
  [![Code Climate](https://codeclimate.com/github/cucumber/cucumber-ruby.svg)](https://codeclimate.com/github/cucumber/cucumber-ruby)
13
10
  [![Coverage Status](https://coveralls.io/repos/cucumber/cucumber-ruby/badge.svg?branch=main)](https://coveralls.io/r/cucumber/cucumber-ruby?branch=main)
14
11
 
@@ -17,9 +14,7 @@ written in plain language, they can be read by anyone on your team. Because they
17
14
  read by anyone, you can use them to help improve communication, collaboration and trust on
18
15
  your team.
19
16
 
20
- <p align="center">
21
- <img src="./.github/img/gherkin-example.png" alt="Cucumber Gherkin Example" width="728" />
22
- </p>
17
+ <img src="docs/img/gherkin-example.png" alt="Cucumber Gherkin Example" width="728" />
23
18
 
24
19
  This is the Ruby implementation of Cucumber. Cucumber is also available for [JavaScript](https://github.com/cucumber/cucumber-js),
25
20
  [Java](https://github.com/cucumber/cucumber-jvm), and a lot of other languages. You can find a list of implementations here: https://cucumber.io/docs/installation/.
@@ -50,21 +45,16 @@ Later in this document, bundler is considered being used so all commands are usi
50
45
 
51
46
  ### Supported platforms
52
47
 
48
+ - Ruby 3.2
49
+ - Ruby 3.1
53
50
  - Ruby 3.0
54
51
  - Ruby 2.7
55
- - Ruby 2.6
56
- - Ruby 2.5
57
- - Ruby 2.4
58
- - Ruby 2.3
59
- - JRuby 9.2 (with [some limitations](https://github.com/cucumber/cucumber-ruby/blob/main/docs/jruby-limitations.md))
60
-
61
- JRuby 9.3 is not supported yet due to a known issue. More info can
62
- be found in the [PR#1571](https://github.com/cucumber/cucumber-ruby/pull/1571).
52
+ - TruffleRuby 22.0.0+
53
+ - JRuby 9.4+ (with [some limitations](https://github.com/cucumber/cucumber-ruby/blob/main/docs/jruby-limitations.md))
63
54
 
64
55
  ### Ruby on Rails
65
56
 
66
- Using Ruby on Rails? You can use [cucumber-rails](https://github.com/cucumber/cucumber-rails)
67
- to bring Cucumber into your Rails project.
57
+ Using Ruby on Rails? You can use [cucumber-rails](https://github.com/cucumber/cucumber-rails) to bring Cucumber into your Rails project.
68
58
 
69
59
  ## Usage
70
60
 
@@ -111,19 +101,20 @@ And a file named `steps.rb` in `features/step_definitions` with:
111
101
  ```ruby
112
102
  # features/step_definitions/steps.rb
113
103
 
114
- Given("this will pass") do
104
+ Given('this will pass') do
115
105
  @this_will_pass = true
116
106
  end
117
107
 
118
- Given("this will fail") do
108
+ Given('this will fail') do
119
109
  @this_will_pass = false
120
110
  end
121
111
 
122
- When("I do an action") do
112
+ When('I do an action') do
113
+ :no_op
123
114
  end
124
115
 
125
116
  Then("some results should be there") do
126
- expect(@this_will_pass)
117
+ expect(@this_will_pass).to be true
127
118
  end
128
119
  ```
129
120
 
@@ -137,9 +128,9 @@ To execute a single feature file:
137
128
 
138
129
  To execute a single example, indicates the line of the name of the example:
139
130
 
140
- $ bundle exec cucumber features/rule.feature:7
131
+ $ bundle exec cucumber features/rule.feature:5
141
132
 
142
- To summarize the results on the standard output, and writte a HTML report on disk:
133
+ To summarize the results on the standard output, and generate a HTML report on disk:
143
134
 
144
135
  $ bundle exec cucumber --format summary --format html --out report.html
145
136
 
@@ -147,8 +138,7 @@ For more command line options
147
138
 
148
139
  $ bundle exec cucumber --help
149
140
 
150
- You can also find documentation on the command line possibilities in
151
- [features/docs/cli](features/docs/cli).
141
+ You can also find documentation on the command line possibilities in [features/docs/cli](features/docs/cli).
152
142
 
153
143
  ## Documentation and support
154
144
 
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 9.2.1
@@ -9,7 +9,9 @@ require 'cucumber'
9
9
  module Cucumber
10
10
  module Cli
11
11
  class YmlLoadError < StandardError; end
12
+
12
13
  class ProfilesNotDefinedError < YmlLoadError; end
14
+
13
15
  class ProfileNotFound < StandardError; end
14
16
 
15
17
  class Configuration
@@ -17,7 +19,7 @@ module Cucumber
17
19
 
18
20
  attr_reader :out_stream
19
21
 
20
- def initialize(out_stream = STDOUT, error_stream = STDERR)
22
+ def initialize(out_stream = $stdout, error_stream = $stderr)
21
23
  @out_stream = out_stream
22
24
  @error_stream = error_stream
23
25
  @options = Options.new(@out_stream, @error_stream, default_profile: 'default')
@@ -28,6 +30,7 @@ module Cucumber
28
30
  @options.parse!(args)
29
31
  arrange_formats
30
32
  raise("You can't use both --strict and --wip") if strict.strict? && wip?
33
+
31
34
  set_environment_variables
32
35
  end
33
36
 
@@ -14,7 +14,7 @@ module Cucumber
14
14
  end
15
15
  end
16
16
 
17
- def initialize(args, _ = nil, out = STDOUT, err = STDERR, kernel = Kernel)
17
+ def initialize(args, out = $stdout, err = $stderr, kernel = Kernel)
18
18
  @args = args
19
19
  @out = out
20
20
  @err = err
@@ -41,7 +41,7 @@ module Cucumber
41
41
  @err.puts("Couldn't open #{e.path}")
42
42
  exit_unable_to_finish
43
43
  rescue FeatureFolderNotFoundException => e
44
- @err.puts(e.message + '. You can use `cucumber --init` to get started.')
44
+ @err.puts("#{e.message}. You can use `cucumber --init` to get started.")
45
45
  exit_unable_to_finish
46
46
  rescue ProfilesNotDefinedError, YmlLoadError, ProfileNotFound => e
47
47
  @err.puts(e.message)
@@ -49,7 +49,7 @@ module Cucumber
49
49
  rescue Errno::EACCES, Errno::ENOENT => e
50
50
  @err.puts("#{e.message} (#{e.class})")
51
51
  exit_unable_to_finish
52
- rescue Exception => e # rubocop:disable Lint/RescueException
52
+ rescue Exception => e
53
53
  @err.puts("#{e.message} (#{e.class})")
54
54
  @err.puts(e.backtrace.join("\n"))
55
55
  exit_unable_to_finish
@@ -85,13 +85,14 @@ module Cucumber
85
85
  trap('INT') do
86
86
  exit_unable_to_finish! if Cucumber.wants_to_quit
87
87
  Cucumber.wants_to_quit = true
88
- STDERR.puts "\nExiting... Interrupt again to exit immediately."
88
+ $stderr.puts "\nExiting... Interrupt again to exit immediately."
89
89
  exit_unable_to_finish
90
90
  end
91
91
  end
92
92
 
93
93
  def runtime(existing_runtime)
94
94
  return Runtime.new(configuration) unless existing_runtime
95
+
95
96
  existing_runtime.configure(configuration)
96
97
  existing_runtime
97
98
  end
@@ -12,26 +12,26 @@ module Cucumber
12
12
  CUCUMBER_PUBLISH_URL = ENV['CUCUMBER_PUBLISH_URL'] || 'https://messages.cucumber.io/api/reports -X GET'
13
13
  INDENT = ' ' * 53
14
14
  BUILTIN_FORMATS = {
15
- 'pretty' => ['Cucumber::Formatter::Pretty', 'Prints the feature as is - in colours.'],
16
- 'progress' => ['Cucumber::Formatter::Progress', 'Prints one character per scenario.'],
17
- 'rerun' => ['Cucumber::Formatter::Rerun', 'Prints failing files with line numbers.'],
18
- 'usage' => ['Cucumber::Formatter::Usage', "Prints where step definitions are used.\n" \
19
- "#{INDENT}The slowest step definitions (with duration) are\n" \
20
- "#{INDENT}listed first. If --dry-run is used the duration\n" \
21
- "#{INDENT}is not shown, and step definitions are sorted by\n" \
22
- "#{INDENT}filename instead."],
23
- 'stepdefs' => ['Cucumber::Formatter::Stepdefs', "Prints All step definitions with their locations. Same as\n" \
24
- "#{INDENT}the usage formatter, except that steps are not printed."],
25
- 'junit' => ['Cucumber::Formatter::Junit', "Generates a report similar to Ant+JUnit. Use\n" \
26
- "#{INDENT}junit,fileattribute=true to include a file attribute."],
27
- 'json' => ['Cucumber::Formatter::Json', "Prints the feature as JSON.\n" \
28
- "#{INDENT}The JSON format is in maintenance mode.\n" \
29
- "#{INDENT}Please consider using the message formatter\n"\
30
- "#{INDENT}with the standalone json-formatter\n" \
31
- "#{INDENT}(https://github.com/cucumber/cucumber/tree/master/json-formatter)."],
32
- 'message' => ['Cucumber::Formatter::Message', 'Prints each message in NDJSON form, which can then be consumed by other tools.'],
33
- 'html' => ['Cucumber::Formatter::HTML', 'Outputs HTML report'],
34
- 'summary' => ['Cucumber::Formatter::Summary', 'Summary output of feature and scenarios']
15
+ 'pretty' => ['Cucumber::Formatter::Pretty', 'Prints the feature as is - in colours.'],
16
+ 'progress' => ['Cucumber::Formatter::Progress', 'Prints one character per scenario.'],
17
+ 'rerun' => ['Cucumber::Formatter::Rerun', 'Prints failing files with line numbers.'],
18
+ 'usage' => ['Cucumber::Formatter::Usage', "Prints where step definitions are used.\n" \
19
+ "#{INDENT}The slowest step definitions (with duration) are\n" \
20
+ "#{INDENT}listed first. If --dry-run is used the duration\n" \
21
+ "#{INDENT}is not shown, and step definitions are sorted by\n" \
22
+ "#{INDENT}filename instead."],
23
+ 'stepdefs' => ['Cucumber::Formatter::Stepdefs', "Prints All step definitions with their locations. Same as\n" \
24
+ "#{INDENT}the usage formatter, except that steps are not printed."],
25
+ 'junit' => ['Cucumber::Formatter::Junit', "Generates a report similar to Ant+JUnit. Use\n" \
26
+ "#{INDENT}junit,fileattribute=true to include a file attribute."],
27
+ 'json' => ['Cucumber::Formatter::Json', "Prints the feature as JSON.\n" \
28
+ "#{INDENT}The JSON format is in maintenance mode.\n" \
29
+ "#{INDENT}Please consider using the message formatter\n"\
30
+ "#{INDENT}with the standalone json-formatter\n" \
31
+ "#{INDENT}(https://github.com/cucumber/cucumber/tree/master/json-formatter)."],
32
+ 'message' => ['Cucumber::Formatter::Message', 'Prints each message in NDJSON form, which can then be consumed by other tools.'],
33
+ 'html' => ['Cucumber::Formatter::HTML', 'Outputs HTML report'],
34
+ 'summary' => ['Cucumber::Formatter::Summary', 'Summary output of feature and scenarios']
35
35
  }.freeze
36
36
  max = BUILTIN_FORMATS.keys.map(&:length).max
37
37
  FORMAT_HELP_MSG = [
@@ -50,26 +50,27 @@ module Cucumber
50
50
  FORMAT_HELP = (BUILTIN_FORMATS.keys.sort.map do |key|
51
51
  " #{key}#{' ' * (max - key.length)} : #{BUILTIN_FORMATS[key][1]}"
52
52
  end) + FORMAT_HELP_MSG
53
- PROFILE_SHORT_FLAG = '-p'.freeze
54
- NO_PROFILE_SHORT_FLAG = '-P'.freeze
55
- PROFILE_LONG_FLAG = '--profile'.freeze
56
- NO_PROFILE_LONG_FLAG = '--no-profile'.freeze
57
- FAIL_FAST_FLAG = '--fail-fast'.freeze
58
- RETRY_FLAG = '--retry'.freeze
53
+ PROFILE_SHORT_FLAG = '-p'
54
+ NO_PROFILE_SHORT_FLAG = '-P'
55
+ PROFILE_LONG_FLAG = '--profile'
56
+ NO_PROFILE_LONG_FLAG = '--no-profile'
57
+ FAIL_FAST_FLAG = '--fail-fast'
58
+ RETRY_FLAG = '--retry'
59
+ RETRY_TOTAL_FLAG = '--retry-total'
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
- TAG_LIMIT_MATCHER = /(?<tag_name>\@\w+):(?<limit>\d+)/x
67
+ TAG_LIMIT_MATCHER = /(?<tag_name>@\w+):(?<limit>\d+)/x.freeze
67
68
 
68
69
  def self.parse(args, out_stream, error_stream, options = {})
69
70
  new(out_stream, error_stream, options).parse!(args)
70
71
  end
71
72
 
72
- def initialize(out_stream = STDOUT, error_stream = STDERR, options = {})
73
+ def initialize(out_stream = $stdout, error_stream = $stderr, options = {})
73
74
  @out_stream = out_stream
74
75
  @error_stream = error_stream
75
76
 
@@ -91,13 +92,13 @@ module Cucumber
91
92
  @options[key] = value
92
93
  end
93
94
 
94
- def parse!(args) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
95
+ def parse!(args)
95
96
  @args = args
96
97
  @expanded_args = @args.dup
97
98
 
98
99
  @args.extend(::OptionParser::Arguable)
99
100
 
100
- @args.options do |opts| # rubocop:disable Metrics/BlockLength
101
+ @args.options do |opts|
101
102
  opts.banner = banner
102
103
  opts.on('--publish', 'Publish a report to https://reports.cucumber.io') do
103
104
  set_option :publish_enabled, true
@@ -108,16 +109,17 @@ 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 }
114
116
  opts.on('-f FORMAT', '--format FORMAT', *format_msg, *FORMAT_HELP) do |v|
115
117
  add_option :formats, [*parse_formats(v), @out_stream]
116
118
  end
117
- opts.on('--init', *init_msg) { |_v| initialize_project }
119
+ opts.on('--init', *init_msg) { initialize_project }
118
120
  opts.on('-o', '--out [FILE|DIR|URL]', *out_msg) { |v| out_stream v }
119
- opts.on('-t TAG_EXPRESSION', '--tags TAG_EXPRESSION', *tags_msg) { |v| add_tag v }
120
- opts.on('-n NAME', '--name NAME', *name_msg) { |v| add_option :name_regexps, /#{v}/ }
121
+ opts.on('-t TAG_EXPRESSION', '--tags TAG_EXPRESSION', *tags_msg) { |v| add_tag(v) }
122
+ opts.on('-n NAME', '--name NAME', *name_msg) { |v| add_option(:name_regexps, /#{v}/) }
121
123
  opts.on('-e', '--exclude PATTERN', *exclude_msg) { |v| add_option :excludes, Regexp.new(v) }
122
124
  opts.on(PROFILE_SHORT_FLAG, "#{PROFILE_LONG_FLAG} PROFILE", *profile_short_flag_msg) { |v| add_profile v }
123
125
  opts.on(NO_PROFILE_SHORT_FLAG, NO_PROFILE_LONG_FLAG, *no_profile_short_flag_msg) { |_v| disable_profile_loading }
@@ -141,11 +143,11 @@ module Cucumber
141
143
  opts.on('-x', '--expand', 'Expand Scenario Outline Tables in output.') { set_option :expand }
142
144
 
143
145
  opts.on('--order TYPE[:SEED]', 'Run examples in the specified order. Available types:',
144
- *<<-TEXT.split("\n")) do |order|
145
- [defined] Run scenarios in the order they were defined (default).
146
- [random] Shuffle scenarios before running.
147
- Specify SEED to reproduce the shuffling from a previous run.
148
- e.g. --order random:5738
146
+ *<<~TEXT.split("\n")) do |order|
147
+ [defined] Run scenarios in the order they were defined (default).
148
+ [random] Shuffle scenarios before running.
149
+ Specify SEED to reproduce the shuffling from a previous run.
150
+ e.g. --order random:5738
149
151
  TEXT
150
152
  @options[:order], @options[:seed] = *order.split(':')
151
153
  raise "'#{@options[:order]}' is not a recognised order type. Please use one of #{ORDER_TYPES.join(', ')}." unless ORDER_TYPES.include?(@options[:order])
@@ -180,6 +182,7 @@ Specify SEED to reproduce the shuffling from a previous run.
180
182
  def check_formatter_stream_conflicts
181
183
  streams = @options[:formats].uniq.map { |(_, _, stream)| stream }
182
184
  return if streams == streams.uniq
185
+
183
186
  raise 'All but one formatter must use --out, only one can print to each stream (or STDOUT)'
184
187
  end
185
188
 
@@ -203,6 +206,7 @@ Specify SEED to reproduce the shuffling from a previous run.
203
206
 
204
207
  def truthy_string?(str)
205
208
  return false if str.nil?
209
+
206
210
  str !~ /^(false|no|0)$/i
207
211
  end
208
212
 
@@ -269,6 +273,13 @@ Specify SEED to reproduce the shuffling from a previous run.
269
273
  ['Specify the number of times to retry failing tests (default: 0)']
270
274
  end
271
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
+
272
283
  def name_msg
273
284
  [
274
285
  'Only execute the feature elements which match part of the given name.',
@@ -284,15 +295,15 @@ Specify SEED to reproduce the shuffling from a previous run.
284
295
  ]
285
296
  end
286
297
 
287
- def parse_formats(v)
288
- formatter, *formatter_options = v.split(',')
289
- options_hash = Hash[formatter_options.map { |s| s.split('=') }]
298
+ def parse_formats(value)
299
+ formatter, *formatter_options = value.split(',')
300
+ options_hash = Hash[formatter_options.map { |string| string.split('=') }]
290
301
  [formatter, options_hash]
291
302
  end
292
303
 
293
- def out_stream(v)
304
+ def out_stream(value)
294
305
  @options[:formats] << ['pretty', {}, nil] if @options[:formats].empty?
295
- @options[:formats][-1][2] = v
306
+ @options[:formats][-1][2] = value
296
307
  end
297
308
 
298
309
  def tags_msg
@@ -364,11 +375,12 @@ Specify SEED to reproduce the shuffling from a previous run.
364
375
  ].join("\n")
365
376
  end
366
377
 
367
- def require_files(v)
368
- @options[:require] << v
369
- return unless Cucumber::JRUBY && File.directory?(v)
378
+ def require_files(filenames)
379
+ @options[:require] << filenames
380
+ return unless Cucumber::JRUBY && File.directory?(filenames)
381
+
370
382
  require 'java'
371
- $CLASSPATH << v
383
+ $CLASSPATH << filenames
372
384
  end
373
385
 
374
386
  def require_jars(jars)
@@ -385,6 +397,7 @@ Specify SEED to reproduce the shuffling from a previous run.
385
397
  require 'gherkin/dialect'
386
398
 
387
399
  return indicate_invalid_language_and_exit(lang) unless ::Gherkin::DIALECTS.key?(lang)
400
+
388
401
  list_keywords_and_exit(lang)
389
402
  end
390
403
 
@@ -403,6 +416,7 @@ Specify SEED to reproduce the shuffling from a previous run.
403
416
  def add_tag(value)
404
417
  raise("Found tags option '#{value}'. '~@tag' is no longer supported, use 'not @tag' instead.") if value.include?('~')
405
418
  raise("Found tags option '#{value}'. '@tag1,@tag2' is no longer supported, use '@tag or @tag2' instead.") if value.include?(',')
419
+
406
420
  @options[:tag_expressions] << value.gsub(/(@\w+)(:\d+)?/, '\1')
407
421
  add_tag_limits(value)
408
422
  end
@@ -415,6 +429,7 @@ Specify SEED to reproduce the shuffling from a previous run.
415
429
 
416
430
  def add_tag_limit(tag_limits, tag_name, limit)
417
431
  raise "Inconsistent tag limits for #{tag_name}: #{tag_limits[tag_name]} and #{limit}" if tag_limits[tag_name] && tag_limits[tag_name] != limit
432
+
418
433
  tag_limits[tag_name] = limit
419
434
  end
420
435
 
@@ -426,8 +441,8 @@ Specify SEED to reproduce the shuffling from a previous run.
426
441
  ProjectInitializer.new.run && Kernel.exit(0)
427
442
  end
428
443
 
429
- def add_profile(p)
430
- @profiles << p
444
+ def add_profile(profile)
445
+ @profiles << profile
431
446
  end
432
447
 
433
448
  def set_option(option, value = nil)
@@ -440,7 +455,7 @@ Specify SEED to reproduce the shuffling from a previous run.
440
455
  end
441
456
 
442
457
  def exit_ok(text)
443
- @out_stream.puts text
458
+ @out_stream.puts(text)
444
459
  Kernel.exit(0)
445
460
  end
446
461
 
@@ -468,22 +483,11 @@ Specify SEED to reproduce the shuffling from a previous run.
468
483
  end
469
484
  end
470
485
 
471
- def disable_profile_loading?
472
- @disable_profile_loading
473
- end
474
-
475
486
  def merge_profiles
476
- if @disable_profile_loading
477
- @out_stream.puts 'Disabling profiles...'
478
- return
479
- end
487
+ return @out_stream.puts 'Disabling profiles...' if @disable_profile_loading
480
488
 
481
489
  @profiles << @default_profile if default_profile_should_be_used?
482
-
483
- @profiles.each do |profile|
484
- merge_with_profile(profile)
485
- end
486
-
490
+ @profiles.each { |profile| merge_with_profile(profile) }
487
491
  @options[:profiles] = @profiles
488
492
  end
489
493
 
@@ -507,7 +511,7 @@ Specify SEED to reproduce the shuffling from a previous run.
507
511
  @profile_loader ||= ProfileLoader.new
508
512
  end
509
513
 
510
- def reverse_merge(other_options) # rubocop:disable Metrics/AbcSize
514
+ def reverse_merge(other_options)
511
515
  @options = other_options.options.merge(@options)
512
516
  @options[:require] += other_options[:require]
513
517
  @options[:excludes] += other_options[:excludes]
@@ -537,6 +541,7 @@ Specify SEED to reproduce the shuffling from a previous run.
537
541
  end
538
542
 
539
543
  @options[:retry] = other_options[:retry] if @options[:retry].zero?
544
+ @options[:retry_total] = other_options[:retry_total] if @options[:retry_total].infinite?
540
545
 
541
546
  self
542
547
  end
@@ -610,7 +615,8 @@ Specify SEED to reproduce the shuffling from a previous run.
610
615
  snippets: true,
611
616
  source: true,
612
617
  duration: true,
613
- retry: 0
618
+ retry: 0,
619
+ retry_total: Float::INFINITY
614
620
  }
615
621
  end
616
622
  end
@@ -11,11 +11,11 @@ module Cucumber
11
11
 
12
12
  def args_from(profile)
13
13
  unless cucumber_yml.key?(profile)
14
- raise(ProfileNotFound, <<-END_OF_ERROR)
15
- Could not find profile: '#{profile}'
14
+ raise(ProfileNotFound, <<~END_OF_ERROR)
15
+ Could not find profile: '#{profile}'
16
16
 
17
- Defined profiles in cucumber.yml:
18
- * #{cucumber_yml.keys.sort.join("\n * ")}
17
+ Defined profiles in cucumber.yml:
18
+ * #{cucumber_yml.keys.sort.join("\n * ")}
19
19
  END_OF_ERROR
20
20
  end
21
21
 
@@ -75,11 +75,7 @@ Defined profiles in cucumber.yml:
75
75
  def process_configuration_file_with_erb
76
76
  require 'erb'
77
77
  begin
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
78
+ @cucumber_erb = ERB.new(IO.read(cucumber_file), trim_mode: '%').result(binding)
83
79
  rescue StandardError
84
80
  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}")
85
81
  end
@@ -88,7 +84,7 @@ Defined profiles in cucumber.yml:
88
84
  def load_configuration
89
85
  require 'yaml'
90
86
  begin
91
- @cucumber_yml = YAML.load(@cucumber_erb) # rubocop:disable Security/YAMLLoad
87
+ @cucumber_yml = YAML.load(@cucumber_erb)
92
88
  rescue StandardError
93
89
  raise(YmlLoadError, "cucumber.yml was found, but could not be parsed. Please refer to cucumber's documentation on correct profile usage.\n")
94
90
  end
@@ -10,7 +10,7 @@ module Cucumber
10
10
  end
11
11
 
12
12
  def self.real_path(path)
13
- path[1..-1] # remove leading @
13
+ path[1..]
14
14
  end
15
15
 
16
16
  def initialize(path)
@@ -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
@@ -176,12 +180,12 @@ module Cucumber
176
180
  end
177
181
 
178
182
  def support_to_load
179
- support_files = all_files_to_load.select { |f| f =~ %r{/support/} }
183
+ support_files = all_files_to_load.select { |f| f =~ /\/support\// }
180
184
 
181
185
  # env_files are separated from other_files so we can ensure env files
182
186
  # load first.
183
187
  #
184
- env_files = support_files.select { |f| f =~ %r{/support/env\..*} }
188
+ env_files = support_files.select { |f| f =~ /\/support\/env\..*/ }
185
189
  other_files = support_files - env_files
186
190
  env_files.reverse + other_files.reverse
187
191
  end
@@ -189,7 +193,7 @@ module Cucumber
189
193
  def all_files_to_load
190
194
  files = require_dirs.map do |path|
191
195
  path = path.tr('\\', '/') # In case we're on windows. Globs don't work with backslashes.
192
- path = path.gsub(/\/$/, '') # Strip trailing slash. # rubocop:disable Style/RegexpLiteral
196
+ path = path.gsub(/\/$/, '') # Strip trailing slash.
193
197
  File.directory?(path) ? Dir["#{path}/**/*"] : path
194
198
  end.flatten.uniq
195
199
  remove_excluded_files_from(files)
@@ -200,7 +204,7 @@ module Cucumber
200
204
  end
201
205
 
202
206
  def step_defs_to_load
203
- all_files_to_load.reject { |f| f =~ %r{/support/} }
207
+ all_files_to_load.reject { |f| f =~ /\/support\// }
204
208
  end
205
209
 
206
210
  def formatter_factories
@@ -209,7 +213,7 @@ module Cucumber
209
213
  yield factory,
210
214
  formatter_options,
211
215
  path_or_io
212
- rescue Exception => e # rubocop:disable Lint/RescueException
216
+ rescue Exception => e
213
217
  raise e, "#{e.message}\nError creating formatter: #{format}", e.backtrace
214
218
  end
215
219
  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
 
@@ -283,6 +288,7 @@ module Cucumber
283
288
 
284
289
  def with_default_features_path(paths)
285
290
  return default_features_paths if paths.empty?
291
+
286
292
  paths
287
293
  end
288
294
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'cucumber/platform'
4
4
  module Cucumber
5
- module Constantize #:nodoc:
5
+ module Constantize # :nodoc:
6
6
  def constantize(camel_cased_word)
7
7
  try = 0
8
8
  begin