cucumber 4.1.0 → 9.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +134 -21
  3. data/VERSION +1 -0
  4. data/lib/cucumber/cli/configuration.rb +27 -2
  5. data/lib/cucumber/cli/main.rb +5 -4
  6. data/lib/cucumber/cli/options.rb +102 -65
  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 +26 -13
  10. data/lib/cucumber/constantize.rb +1 -1
  11. data/lib/cucumber/deprecate.rb +6 -46
  12. data/lib/cucumber/errors.rb +4 -3
  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 +2 -2
  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 -85
  34. data/lib/cucumber/formatter/ast_lookup.rb +16 -10
  35. data/lib/cucumber/formatter/backtrace_filter.rb +3 -1
  36. data/lib/cucumber/formatter/console.rb +34 -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 +3 -1
  45. data/lib/cucumber/formatter/http_io.rb +10 -136
  46. data/lib/cucumber/formatter/ignore_missing_messages.rb +1 -1
  47. data/lib/cucumber/formatter/interceptor.rb +4 -3
  48. data/lib/cucumber/formatter/io.rb +50 -12
  49. data/lib/cucumber/formatter/io_http_buffer.rb +88 -0
  50. data/lib/cucumber/formatter/json.rb +36 -37
  51. data/lib/cucumber/formatter/junit.rb +29 -9
  52. data/lib/cucumber/formatter/message.rb +3 -2
  53. data/lib/cucumber/formatter/message_builder.rb +32 -16
  54. data/lib/cucumber/formatter/pretty.rb +44 -29
  55. data/lib/cucumber/formatter/progress.rb +2 -1
  56. data/lib/cucumber/formatter/publish_banner_printer.rb +75 -0
  57. data/lib/cucumber/formatter/query/hook_by_test_step.rb +3 -0
  58. data/lib/cucumber/formatter/query/pickle_by_test.rb +2 -0
  59. data/lib/cucumber/formatter/query/pickle_step_by_test_step.rb +2 -0
  60. data/lib/cucumber/formatter/query/step_definitions_by_test_step.rb +2 -0
  61. data/lib/cucumber/formatter/query/test_case_started_by_test_case.rb +4 -0
  62. data/lib/cucumber/formatter/rerun.rb +7 -5
  63. data/lib/cucumber/formatter/steps.rb +6 -3
  64. data/lib/cucumber/formatter/summary.rb +2 -1
  65. data/lib/cucumber/formatter/unicode.rb +7 -7
  66. data/lib/cucumber/formatter/url_reporter.rb +19 -0
  67. data/lib/cucumber/formatter/usage.rb +9 -7
  68. data/lib/cucumber/gherkin/data_table_parser.rb +2 -1
  69. data/lib/cucumber/gherkin/formatter/ansi_escapes.rb +25 -27
  70. data/lib/cucumber/gherkin/steps_parser.rb +1 -1
  71. data/lib/cucumber/glue/dsl.rb +30 -16
  72. data/lib/cucumber/glue/hook.rb +6 -3
  73. data/lib/cucumber/glue/invoke_in_world.rb +5 -5
  74. data/lib/cucumber/glue/proto_world.rb +37 -57
  75. data/lib/cucumber/glue/registry_and_more.rb +31 -12
  76. data/lib/cucumber/glue/registry_wrapper.rb +31 -0
  77. data/lib/cucumber/glue/snippet.rb +4 -2
  78. data/lib/cucumber/glue/step_definition.rb +9 -7
  79. data/lib/cucumber/glue/world_factory.rb +2 -0
  80. data/lib/cucumber/hooks.rb +1 -0
  81. data/lib/cucumber/multiline_argument/data_table/diff_matrices.rb +5 -2
  82. data/lib/cucumber/multiline_argument/data_table.rb +65 -79
  83. data/lib/cucumber/platform.rb +11 -16
  84. data/lib/cucumber/rake/task.rb +22 -17
  85. data/lib/cucumber/rspec/disable_option_parser.rb +6 -3
  86. data/lib/cucumber/rspec/doubles.rb +3 -5
  87. data/lib/cucumber/running_test_case.rb +2 -1
  88. data/lib/cucumber/runtime/for_programming_languages.rb +1 -2
  89. data/lib/cucumber/runtime/meta_message_builder.rb +108 -0
  90. data/lib/cucumber/runtime/support_code.rb +3 -0
  91. data/lib/cucumber/runtime/user_interface.rb +7 -6
  92. data/lib/cucumber/runtime.rb +50 -24
  93. data/lib/cucumber/step_match.rb +7 -11
  94. data/lib/cucumber/step_match_search.rb +3 -2
  95. data/lib/cucumber/term/ansicolor.rb +74 -50
  96. data/lib/cucumber/term/banner.rb +59 -0
  97. data/lib/cucumber.rb +2 -1
  98. data/lib/simplecov_setup.rb +1 -1
  99. metadata +103 -229
  100. data/CHANGELOG.md +0 -2682
  101. data/CONTRIBUTING.md +0 -71
  102. data/lib/autotest/cucumber.rb +0 -8
  103. data/lib/autotest/cucumber_mixin.rb +0 -132
  104. data/lib/autotest/cucumber_rails.rb +0 -8
  105. data/lib/autotest/cucumber_rails_rspec.rb +0 -8
  106. data/lib/autotest/cucumber_rails_rspec2.rb +0 -8
  107. data/lib/autotest/cucumber_rspec.rb +0 -8
  108. data/lib/autotest/cucumber_rspec2.rb +0 -8
  109. data/lib/autotest/discover.rb +0 -13
  110. data/lib/cucumber/core_ext/string.rb +0 -11
  111. data/lib/cucumber/version +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a1f051aade812ee4bb28ab3670734b189ade8bc0cd0150a686e21874594ae62f
4
- data.tar.gz: 80c272791953dc442492c0571acffc628cb7b1f37a28a00adeef413bc7f76e1b
3
+ metadata.gz: 1c84d3e0ab90cabd1d67121371c8a95c0e5f91bfa5f3ec42a1024c1a85130bde
4
+ data.tar.gz: 1386d49afc93d04c9b616f38ab4466c8aa3092aa60a293c82557465ad8b4538a
5
5
  SHA512:
6
- metadata.gz: 5026e105060af702f3b0adf50f3925ce04fdee7de79c2482093831cab838e2a605d69c056733d7820b6592c7229ee93e1ce64b80ae373ac7acd29f11d113d3a8
7
- data.tar.gz: c66fe1effa60270eb430bc2c381252e5c3da5dec524e49c58a7e508febaeb0b1398791f75eba5542395672b2431dafcfb820da2870410a52474ebc157bfbe333
6
+ metadata.gz: afd28a66515dbb07168f7c906d1de7feb5c780437a22b0492f218db943555bc996e7dfc77750257033c9ae8c843e350ca12c389963b78e6ddf1c58dc3f1df371
7
+ data.tar.gz: bd21e8e340afc215e887be2e353fd9060a4a4ccf1975298403ed6487e263b4103ef01a73e069f16758cc286ba6d507ae0e2f4b9d72916fb976cbbe440af9d154
data/README.md CHANGED
@@ -1,38 +1,151 @@
1
- [![OpenCollective](https://opencollective.com/cucumber/backers/badge.svg)](https://opencollective.com/cucumber)
2
- [![OpenCollective](https://opencollective.com/cucumber/sponsors/badge.svg)](https://opencollective.com/cucumber)
1
+ <img src="docs/img/cucumber-open-logo.png" alt="Cucumber Open - Supported by Smartbear" width="428" />
3
2
 
4
- [![CircleCI](https://circleci.com/gh/cucumber/cucumber-ruby.svg?style=svg)](https://circleci.com/gh/cucumber/cucumber-ruby)
3
+ # Cucumber
5
4
 
5
+ [![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://vshymanskyy.github.io/StandWithUkraine)
6
+ [![OpenCollective](https://opencollective.com/cucumber/backers/badge.svg)](https://opencollective.com/cucumber)
7
+ [![OpenCollective](https://opencollective.com/cucumber/sponsors/badge.svg)](https://opencollective.com/cucumber)
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)
6
9
  [![Code Climate](https://codeclimate.com/github/cucumber/cucumber-ruby.svg)](https://codeclimate.com/github/cucumber/cucumber-ruby)
7
- [![Coverage Status](https://coveralls.io/repos/cucumber/cucumber-ruby/badge.svg?branch=master)](https://coveralls.io/r/cucumber/cucumber-ruby?branch=master)
8
-
9
- # Cucumber
10
+ [![Coverage Status](https://coveralls.io/repos/cucumber/cucumber-ruby/badge.svg?branch=main)](https://coveralls.io/r/cucumber/cucumber-ruby?branch=main)
10
11
 
11
12
  Cucumber is a tool for running automated tests written in plain language. Because they're
12
13
  written in plain language, they can be read by anyone on your team. Because they can be
13
14
  read by anyone, you can use them to help improve communication, collaboration and trust on
14
15
  your team.
15
16
 
16
- Where to get more info:
17
+ <img src="docs/img/gherkin-example.png" alt="Cucumber Gherkin Example" width="728" />
18
+
19
+ This is the Ruby implementation of Cucumber. Cucumber is also available for [JavaScript](https://github.com/cucumber/cucumber-js),
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/.
21
+
22
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for info on contributing to Cucumber (issues, PRs, etc.).
23
+
24
+ Everyone interacting in this codebase and issue tracker is expected to follow the
25
+ Cucumber [code of conduct](https://cucumber.io/conduct).
26
+
27
+ ## Installation
28
+
29
+ Cucumber for Ruby is a Ruby gem. Install it as you would install any gem: add
30
+ `cucumber` to your Gemfile:
31
+
32
+ gem 'cucumber'
33
+
34
+ then install it:
35
+
36
+ $ bundle
37
+
38
+ or install the gem directly:
39
+
40
+ $ gem install cucumber
41
+
42
+ Later in this document, bundler is considered being used so all commands are using
43
+ `bundle exec`. If this is not the case for you, execute `cucumber` directly, without
44
+ `bundle exec`.
45
+
46
+ ### Supported platforms
47
+
48
+ - Ruby 3.2
49
+ - Ruby 3.1
50
+ - Ruby 3.0
51
+ - Ruby 2.7
52
+ - TruffleRuby 22.0.0+
53
+ - JRuby 9.4+ (with [some limitations](https://github.com/cucumber/cucumber-ruby/blob/main/docs/jruby-limitations.md))
54
+
55
+ ### Ruby on Rails
56
+
57
+ Using Ruby on Rails? You can use [cucumber-rails](https://github.com/cucumber/cucumber-rails) to bring Cucumber into your Rails project.
58
+
59
+ ## Usage
60
+
61
+ ### Initialization
62
+
63
+ If you need to, initialize your `features` directory with
64
+
65
+ $ bundle exec cucumber --init
66
+
67
+ This will create the following directories and files if they do not exist already:
68
+
69
+ features
70
+ ├── step_definitions
71
+ └── support
72
+ └── env.rb
73
+
74
+ ### Create your specification
75
+
76
+ Create a file named `rule.feature` in the `features` directory with:
77
+
78
+ ```gherkin
79
+ # features/rule.feature
80
+
81
+ Feature: Rule Sample
82
+
83
+ Rule: This is a rule
84
+
85
+ Example: A passing example
86
+ Given this will pass
87
+ When I do an action
88
+ Then some results should be there
89
+
90
+ Example: A failing example
91
+ Given this will fail
92
+ When I do an action
93
+ Then some results should be there
94
+
95
+ ```
96
+
97
+ ### Automate your specification
98
+
99
+ And a file named `steps.rb` in `features/step_definitions` with:
100
+
101
+ ```ruby
102
+ # features/step_definitions/steps.rb
103
+
104
+ Given('this will pass') do
105
+ @this_will_pass = true
106
+ end
107
+
108
+ Given('this will fail') do
109
+ @this_will_pass = false
110
+ end
111
+
112
+ When('I do an action') do
113
+ :no_op
114
+ end
115
+
116
+ Then("some results should be there") do
117
+ expect(@this_will_pass).to be true
118
+ end
119
+ ```
120
+
121
+ ### Run Cucumber
122
+
123
+ $ bundle exec cucumber
124
+
125
+ To execute a single feature file:
126
+
127
+ $ bundle exec cucumber features/rule.feature
128
+
129
+ To execute a single example, indicates the line of the name of the example:
130
+
131
+ $ bundle exec cucumber features/rule.feature:5
132
+
133
+ To summarize the results on the standard output, and generate a HTML report on disk:
134
+
135
+ $ bundle exec cucumber --format summary --format html --out report.html
17
136
 
18
- * The main website: https://cucumber.io/
19
- * Documentation: https://cucumber.io/docs
20
- * Ruby API Documentation: http://www.rubydoc.info/github/cucumber/cucumber-ruby/
21
- * Support forum: https://groups.google.com/group/cukes
22
- * Chat: ([Slack](https://cucumber.io/support#slack) and [Gitter](https://cucumber.io/support#gitter))
137
+ For more command line options
23
138
 
24
- See [CONTRIBUTING.md](CONTRIBUTING.md) for info on contributing to Cucumber.
139
+ $ bundle exec cucumber --help
25
140
 
26
- ## Supported platforms
27
- * Ruby 2.6
28
- * Ruby 2.5
29
- * Ruby 2.4
30
- * Ruby 2.3
31
- * JRuby 9.2 (with [some limitations](https://github.com/cucumber/cucumber-ruby/blob/master/docs/jruby-limitations.md))
141
+ You can also find documentation on the command line possibilities in [features/docs/cli](features/docs/cli).
32
142
 
33
- ## Code of Conduct
143
+ ## Documentation and support
34
144
 
35
- Everyone interacting in this codebase and issue tracker is expected to follow the Cucumber [code of conduct](https://github.com/cucumber/cucumber/blob/master/CODE_OF_CONDUCT.md).
145
+ - Getting started, writing features, step definitions, and more: https://cucumber.io/docs
146
+ - Ruby API Documentation: http://www.rubydoc.info/github/cucumber/cucumber-ruby/
147
+ - Community support forum: https://community.smartbear.com/t5/Cucumber-Open/bd-p/CucumberOS
148
+ - Slack: [register for an account](https://cucumberbdd-slack-invite.herokuapp.com/) then head over to [#intro](https://cucumberbdd.slack.com/messages/C5WD8SA21/)
36
149
 
37
150
  ## Copyright
38
151
 
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 9.2.0
@@ -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
 
@@ -126,13 +129,35 @@ module Cucumber
126
129
  end
127
130
 
128
131
  def arrange_formats
129
- @options[:formats] << ['pretty', {}, @out_stream] if @options[:formats].empty?
132
+ add_default_formatter if needs_default_formatter?
133
+
130
134
  @options[:formats] = @options[:formats].sort_by do |f|
131
135
  f[2] == @out_stream ? -1 : 1
132
136
  end
133
137
  @options[:formats].uniq!
134
138
  @options.check_formatter_stream_conflicts
135
139
  end
140
+
141
+ def add_default_formatter
142
+ @options[:formats] << ['pretty', {}, @out_stream]
143
+ end
144
+
145
+ def needs_default_formatter?
146
+ formatter_missing? || publish_only?
147
+ end
148
+
149
+ def formatter_missing?
150
+ @options[:formats].empty?
151
+ end
152
+
153
+ def publish_only?
154
+ @options[:formats]
155
+ .uniq
156
+ .map { |formatter, _, stream| [formatter, stream] }
157
+ .uniq
158
+ .reject { |formatter, stream| formatter == 'message' && stream != @out_stream }
159
+ .empty?
160
+ end
136
161
  end
137
162
  end
138
163
  end
@@ -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
@@ -9,23 +9,29 @@ require 'cucumber/core/test/result'
9
9
  module Cucumber
10
10
  module Cli
11
11
  class Options
12
+ CUCUMBER_PUBLISH_URL = ENV['CUCUMBER_PUBLISH_URL'] || 'https://messages.cucumber.io/api/reports -X GET'
12
13
  INDENT = ' ' * 53
13
14
  BUILTIN_FORMATS = {
14
- 'pretty' => ['Cucumber::Formatter::Pretty', 'Prints the feature as is - in colours.'],
15
- 'progress' => ['Cucumber::Formatter::Progress', 'Prints one character per scenario.'],
16
- 'rerun' => ['Cucumber::Formatter::Rerun', 'Prints failing files with line numbers.'],
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" \
21
- "#{INDENT}filename instead."],
22
- 'stepdefs' => ['Cucumber::Formatter::Stepdefs', "Prints All step definitions with their locations. Same as\n" \
23
- "#{INDENT}the usage formatter, except that steps are not printed."],
24
- 'junit' => ['Cucumber::Formatter::Junit', 'Generates a report similar to Ant+JUnit.'],
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'],
28
- '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']
29
35
  }.freeze
30
36
  max = BUILTIN_FORMATS.keys.map(&:length).max
31
37
  FORMAT_HELP_MSG = [
@@ -44,26 +50,27 @@ module Cucumber
44
50
  FORMAT_HELP = (BUILTIN_FORMATS.keys.sort.map do |key|
45
51
  " #{key}#{' ' * (max - key.length)} : #{BUILTIN_FORMATS[key][1]}"
46
52
  end) + FORMAT_HELP_MSG
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
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'
53
60
  OPTIONS_WITH_ARGS = [
54
61
  '-r', '--require', '--i18n-keywords', '-f', '--format', '-o',
55
62
  '--out', '-t', '--tags', '-n', '--name', '-e', '--exclude',
56
- PROFILE_SHORT_FLAG, PROFILE_LONG_FLAG, RETRY_FLAG, '-l',
57
- '--lines', '--port', '-I', '--snippet-type'
63
+ PROFILE_SHORT_FLAG, PROFILE_LONG_FLAG, RETRY_FLAG, RETRY_TOTAL_FLAG,
64
+ '-l', '--lines', '--port', '-I', '--snippet-type'
58
65
  ].freeze
59
66
  ORDER_TYPES = %w[defined random].freeze
60
- TAG_LIMIT_MATCHER = /(?<tag_name>\@\w+):(?<limit>\d+)/x
67
+ TAG_LIMIT_MATCHER = /(?<tag_name>@\w+):(?<limit>\d+)/x.freeze
61
68
 
62
69
  def self.parse(args, out_stream, error_stream, options = {})
63
70
  new(out_stream, error_stream, options).parse!(args)
64
71
  end
65
72
 
66
- def initialize(out_stream = STDOUT, error_stream = STDERR, options = {})
73
+ def initialize(out_stream = $stdout, error_stream = $stderr, options = {})
67
74
  @out_stream = out_stream
68
75
  @error_stream = error_stream
69
76
 
@@ -85,29 +92,34 @@ module Cucumber
85
92
  @options[key] = value
86
93
  end
87
94
 
88
- def parse!(args) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
95
+ def parse!(args)
89
96
  @args = args
90
97
  @expanded_args = @args.dup
91
98
 
92
99
  @args.extend(::OptionParser::Arguable)
93
100
 
94
- @args.options do |opts| # rubocop:disable Metrics/BlockLength
101
+ @args.options do |opts|
95
102
  opts.banner = banner
103
+ opts.on('--publish', 'Publish a report to https://reports.cucumber.io') do
104
+ set_option :publish_enabled, true
105
+ end
106
+ opts.on('--publish-quiet', 'Don\'t print information banner about publishing reports') { set_option :publish_quiet }
96
107
  opts.on('-r LIBRARY|DIR', '--require LIBRARY|DIR', *require_files_msg) { |lib| require_files(lib) }
97
108
 
98
109
  opts.on('-j DIR', '--jars DIR', 'Load all the jars under DIR') { |jars| load_jars(jars) } if Cucumber::JRUBY
99
110
 
100
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 }
101
113
  opts.on('--i18n-languages', *i18n_languages_msg) { list_languages_and_exit }
102
114
  opts.on('--i18n-keywords LANG', *i18n_keywords_msg) { |lang| language lang }
103
115
  opts.on(FAIL_FAST_FLAG, 'Exit immediately following the first failing scenario') { set_option :fail_fast }
104
116
  opts.on('-f FORMAT', '--format FORMAT', *format_msg, *FORMAT_HELP) do |v|
105
117
  add_option :formats, [*parse_formats(v), @out_stream]
106
118
  end
107
- opts.on('--init', *init_msg) { |_v| initialize_project }
119
+ opts.on('--init', *init_msg) { initialize_project }
108
120
  opts.on('-o', '--out [FILE|DIR|URL]', *out_msg) { |v| out_stream v }
109
- opts.on('-t TAG_EXPRESSION', '--tags TAG_EXPRESSION', *tags_msg) { |v| add_tag v }
110
- 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}/) }
111
123
  opts.on('-e', '--exclude PATTERN', *exclude_msg) { |v| add_option :excludes, Regexp.new(v) }
112
124
  opts.on(PROFILE_SHORT_FLAG, "#{PROFILE_LONG_FLAG} PROFILE", *profile_short_flag_msg) { |v| add_profile v }
113
125
  opts.on(NO_PROFILE_SHORT_FLAG, NO_PROFILE_LONG_FLAG, *no_profile_short_flag_msg) { |_v| disable_profile_loading }
@@ -117,7 +129,7 @@ module Cucumber
117
129
  opts.on('-s', '--no-source', "Don't print the file and line of the step definition with the steps.") { set_option :source, false }
118
130
  opts.on('-i', '--no-snippets', "Don't print snippets for pending steps.") { set_option :snippets, false }
119
131
  opts.on('-I', '--snippet-type TYPE', *snippet_type_msg) { |v| set_option :snippet_type, v.to_sym }
120
- opts.on('-q', '--quiet', 'Alias for --no-snippets --no-source.') { shut_up }
132
+ opts.on('-q', '--quiet', 'Alias for --no-snippets --no-source --no-duration --publish-quiet.') { shut_up }
121
133
  opts.on('--no-duration', "Don't print the duration at the end of the summary") { set_option :duration, false }
122
134
  opts.on('-b', '--backtrace', 'Show full backtrace for all errors.') { Cucumber.use_full_backtrace = true }
123
135
  opts.on('-S', '--[no-]strict', *strict_msg) { |setting| set_strict(setting) }
@@ -131,11 +143,11 @@ module Cucumber
131
143
  opts.on('-x', '--expand', 'Expand Scenario Outline Tables in output.') { set_option :expand }
132
144
 
133
145
  opts.on('--order TYPE[:SEED]', 'Run examples in the specified order. Available types:',
134
- *<<-TEXT.split("\n")) do |order|
135
- [defined] Run scenarios in the order they were defined (default).
136
- [random] Shuffle scenarios before running.
137
- Specify SEED to reproduce the shuffling from a previous run.
138
- 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
139
151
  TEXT
140
152
  @options[:order], @options[:seed] = *order.split(':')
141
153
  raise "'#{@options[:order]}' is not a recognised order type. Please use one of #{ORDER_TYPES.join(', ')}." unless ORDER_TYPES.include?(@options[:order])
@@ -145,6 +157,8 @@ Specify SEED to reproduce the shuffling from a previous run.
145
157
  opts.on_tail('-h', '--help', "You're looking at it.") { exit_ok(opts.help) }
146
158
  end.parse!
147
159
 
160
+ process_publish_options
161
+
148
162
  @args.map! { |a| "#{a}:#{@options[:lines]}" } if @options[:lines]
149
163
 
150
164
  extract_environment_variables
@@ -168,6 +182,7 @@ Specify SEED to reproduce the shuffling from a previous run.
168
182
  def check_formatter_stream_conflicts
169
183
  streams = @options[:formats].uniq.map { |(_, _, stream)| stream }
170
184
  return if streams == streams.uniq
185
+
171
186
  raise 'All but one formatter must use --out, only one can print to each stream (or STDOUT)'
172
187
  end
173
188
 
@@ -182,6 +197,19 @@ Specify SEED to reproduce the shuffling from a previous run.
182
197
 
183
198
  private
184
199
 
200
+ def process_publish_options
201
+ @options[:publish_enabled] = true if truthy_string?(ENV['CUCUMBER_PUBLISH_ENABLED']) || ENV['CUCUMBER_PUBLISH_TOKEN']
202
+ @options[:formats] << publisher if @options[:publish_enabled]
203
+
204
+ @options[:publish_quiet] = true if truthy_string?(ENV['CUCUMBER_PUBLISH_QUIET'])
205
+ end
206
+
207
+ def truthy_string?(str)
208
+ return false if str.nil?
209
+
210
+ str !~ /^(false|no|0)$/i
211
+ end
212
+
185
213
  def color_msg
186
214
  [
187
215
  'Whether or not to use ANSI color in the output. Cucumber decides',
@@ -245,6 +273,13 @@ Specify SEED to reproduce the shuffling from a previous run.
245
273
  ['Specify the number of times to retry failing tests (default: 0)']
246
274
  end
247
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
+
248
283
  def name_msg
249
284
  [
250
285
  'Only execute the feature elements which match part of the given name.',
@@ -260,15 +295,15 @@ Specify SEED to reproduce the shuffling from a previous run.
260
295
  ]
261
296
  end
262
297
 
263
- def parse_formats(v)
264
- formatter, *formatter_options = v.split(',')
265
- 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('=') }]
266
301
  [formatter, options_hash]
267
302
  end
268
303
 
269
- def out_stream(v)
304
+ def out_stream(value)
270
305
  @options[:formats] << ['pretty', {}, nil] if @options[:formats].empty?
271
- @options[:formats][-1][2] = v
306
+ @options[:formats][-1][2] = value
272
307
  end
273
308
 
274
309
  def tags_msg
@@ -316,7 +351,7 @@ Specify SEED to reproduce the shuffling from a previous run.
316
351
  'option is specified; all loading becomes explicit.',
317
352
  'Files in directories named "support" are still always',
318
353
  'loaded first when their parent directories are',
319
- 'required or if the "support" directoires themselves are',
354
+ 'required or if the "support" directories themselves are',
320
355
  'explicitly required.',
321
356
  'This option can be specified multiple times.'
322
357
  ]
@@ -340,21 +375,29 @@ Specify SEED to reproduce the shuffling from a previous run.
340
375
  ].join("\n")
341
376
  end
342
377
 
343
- def require_files(v)
344
- @options[:require] << v
345
- 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
+
346
382
  require 'java'
347
- $CLASSPATH << v
383
+ $CLASSPATH << filenames
348
384
  end
349
385
 
350
386
  def require_jars(jars)
351
- Dir["#{jars}/**/*.jar"].each { |jar| require jar }
387
+ Dir["#{jars}/**/*.jar"].sort.each { |jar| require jar }
388
+ end
389
+
390
+ def publisher
391
+ url = CUCUMBER_PUBLISH_URL
392
+ url += %( -H "Authorization: Bearer #{ENV['CUCUMBER_PUBLISH_TOKEN']}") if ENV['CUCUMBER_PUBLISH_TOKEN']
393
+ ['message', {}, url]
352
394
  end
353
395
 
354
396
  def language(lang)
355
397
  require 'gherkin/dialect'
356
398
 
357
399
  return indicate_invalid_language_and_exit(lang) unless ::Gherkin::DIALECTS.key?(lang)
400
+
358
401
  list_keywords_and_exit(lang)
359
402
  end
360
403
 
@@ -373,6 +416,7 @@ Specify SEED to reproduce the shuffling from a previous run.
373
416
  def add_tag(value)
374
417
  raise("Found tags option '#{value}'. '~@tag' is no longer supported, use 'not @tag' instead.") if value.include?('~')
375
418
  raise("Found tags option '#{value}'. '@tag1,@tag2' is no longer supported, use '@tag or @tag2' instead.") if value.include?(',')
419
+
376
420
  @options[:tag_expressions] << value.gsub(/(@\w+)(:\d+)?/, '\1')
377
421
  add_tag_limits(value)
378
422
  end
@@ -385,6 +429,7 @@ Specify SEED to reproduce the shuffling from a previous run.
385
429
 
386
430
  def add_tag_limit(tag_limits, tag_name, limit)
387
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
+
388
433
  tag_limits[tag_name] = limit
389
434
  end
390
435
 
@@ -396,8 +441,8 @@ Specify SEED to reproduce the shuffling from a previous run.
396
441
  ProjectInitializer.new.run && Kernel.exit(0)
397
442
  end
398
443
 
399
- def add_profile(p)
400
- @profiles << p
444
+ def add_profile(profile)
445
+ @profiles << profile
401
446
  end
402
447
 
403
448
  def set_option(option, value = nil)
@@ -410,11 +455,12 @@ Specify SEED to reproduce the shuffling from a previous run.
410
455
  end
411
456
 
412
457
  def exit_ok(text)
413
- @out_stream.puts text
458
+ @out_stream.puts(text)
414
459
  Kernel.exit(0)
415
460
  end
416
461
 
417
462
  def shut_up
463
+ @options[:publish_quiet] = true
418
464
  @options[:snippets] = false
419
465
  @options[:source] = false
420
466
  @options[:duration] = false
@@ -437,22 +483,11 @@ Specify SEED to reproduce the shuffling from a previous run.
437
483
  end
438
484
  end
439
485
 
440
- def disable_profile_loading?
441
- @disable_profile_loading
442
- end
443
-
444
486
  def merge_profiles
445
- if @disable_profile_loading
446
- @out_stream.puts 'Disabling profiles...'
447
- return
448
- end
487
+ return @out_stream.puts 'Disabling profiles...' if @disable_profile_loading
449
488
 
450
489
  @profiles << @default_profile if default_profile_should_be_used?
451
-
452
- @profiles.each do |profile|
453
- merge_with_profile(profile)
454
- end
455
-
490
+ @profiles.each { |profile| merge_with_profile(profile) }
456
491
  @options[:profiles] = @profiles
457
492
  end
458
493
 
@@ -476,7 +511,7 @@ Specify SEED to reproduce the shuffling from a previous run.
476
511
  @profile_loader ||= ProfileLoader.new
477
512
  end
478
513
 
479
- def reverse_merge(other_options) # rubocop:disable Metrics/AbcSize
514
+ def reverse_merge(other_options)
480
515
  @options = other_options.options.merge(@options)
481
516
  @options[:require] += other_options[:require]
482
517
  @options[:excludes] += other_options[:excludes]
@@ -506,6 +541,7 @@ Specify SEED to reproduce the shuffling from a previous run.
506
541
  end
507
542
 
508
543
  @options[:retry] = other_options[:retry] if @options[:retry].zero?
544
+ @options[:retry_total] = other_options[:retry_total] if @options[:retry_total].infinite?
509
545
 
510
546
  self
511
547
  end
@@ -579,7 +615,8 @@ Specify SEED to reproduce the shuffling from a previous run.
579
615
  snippets: true,
580
616
  source: true,
581
617
  duration: true,
582
- retry: 0
618
+ retry: 0,
619
+ retry_total: Float::INFINITY
583
620
  }
584
621
  end
585
622
  end