You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

options.rb 26 kB

2 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. # frozen_string_literal: true
  2. require 'cucumber/cli/profile_loader'
  3. require 'cucumber/formatter/ansicolor'
  4. require 'cucumber/glue/registry_and_more'
  5. require 'cucumber/project_initializer'
  6. require 'cucumber/core/test/result'
  7. module Cucumber
  8. module Cli
  9. class Options
  10. CUCUMBER_PUBLISH_URL = ENV['CUCUMBER_PUBLISH_URL'] || 'https://messages.cucumber.io/api/reports -X GET'
  11. INDENT = ' ' * 53
  12. BUILTIN_FORMATS = {
  13. 'pretty' => ['Cucumber::Formatter::Pretty', 'Prints the feature as is - in colours.'],
  14. 'progress' => ['Cucumber::Formatter::Progress', 'Prints one character per scenario.'],
  15. 'rerun' => ['Cucumber::Formatter::Rerun', 'Prints failing files with line numbers.'],
  16. 'usage' => ['Cucumber::Formatter::Usage', "Prints where step definitions are used.\n" \
  17. "#{INDENT}The slowest step definitions (with duration) are\n" \
  18. "#{INDENT}listed first. If --dry-run is used the duration\n" \
  19. "#{INDENT}is not shown, and step definitions are sorted by\n" \
  20. "#{INDENT}filename instead."],
  21. 'stepdefs' => ['Cucumber::Formatter::Stepdefs', "Prints All step definitions with their locations. Same as\n" \
  22. "#{INDENT}the usage formatter, except that steps are not printed."],
  23. 'junit' => ['Cucumber::Formatter::Junit', "Generates a report similar to Ant+JUnit. Use\n" \
  24. "#{INDENT}junit,fileattribute=true to include a file attribute."],
  25. 'json' => ['Cucumber::Formatter::Json', "Prints the feature as JSON.\n" \
  26. "#{INDENT}The JSON format is in maintenance mode.\n" \
  27. "#{INDENT}Please consider using the message formatter\n"\
  28. "#{INDENT}with the standalone json-formatter\n" \
  29. "#{INDENT}(https://github.com/cucumber/cucumber/tree/master/json-formatter)."],
  30. 'message' => ['Cucumber::Formatter::Message', 'Prints each message in NDJSON form, which can then be consumed by other tools.'],
  31. 'html' => ['Cucumber::Formatter::HTML', 'Outputs HTML report'],
  32. 'summary' => ['Cucumber::Formatter::Summary', 'Summary output of feature and scenarios']
  33. }.freeze
  34. max = BUILTIN_FORMATS.keys.map(&:length).max
  35. FORMAT_HELP_MSG = [
  36. 'Use --format rerun --out rerun.txt to write out failing',
  37. 'features. You can rerun them with cucumber @rerun.txt.',
  38. 'FORMAT can also be the fully qualified class name of',
  39. "your own custom formatter. If the class isn't loaded,",
  40. 'Cucumber will attempt to require a file with a relative',
  41. 'file name that is the underscore name of the class name.',
  42. 'Example: --format Foo::BarZap -> Cucumber will look for',
  43. 'foo/bar_zap.rb. You can place the file with this relative',
  44. 'path underneath your features/support directory or anywhere',
  45. "on Ruby's LOAD_PATH, for example in a Ruby gem."
  46. ].freeze
  47. FORMAT_HELP = (BUILTIN_FORMATS.keys.sort.map do |key|
  48. " #{key}#{' ' * (max - key.length)} : #{BUILTIN_FORMATS[key][1]}"
  49. end) + FORMAT_HELP_MSG
  50. PROFILE_SHORT_FLAG = '-p'.freeze
  51. NO_PROFILE_SHORT_FLAG = '-P'.freeze
  52. PROFILE_LONG_FLAG = '--profile'.freeze
  53. NO_PROFILE_LONG_FLAG = '--no-profile'.freeze
  54. FAIL_FAST_FLAG = '--fail-fast'.freeze
  55. RETRY_FLAG = '--retry'.freeze
  56. RETRY_TOTAL_FLAG = '--retry-total'.freeze
  57. OPTIONS_WITH_ARGS = [
  58. '-r', '--require', '--i18n-keywords', '-f', '--format', '-o',
  59. '--out', '-t', '--tags', '-n', '--name', '-e', '--exclude',
  60. PROFILE_SHORT_FLAG, PROFILE_LONG_FLAG, RETRY_FLAG, RETRY_TOTAL_FLAG,
  61. '-l', '--lines', '--port', '-I', '--snippet-type'
  62. ].freeze
  63. ORDER_TYPES = %w[defined random].freeze
  64. TAG_LIMIT_MATCHER = /(?<tag_name>@\w+):(?<limit>\d+)/x
  65. def self.parse(args, out_stream, error_stream, options = {})
  66. new(out_stream, error_stream, options).parse!(args)
  67. end
  68. def initialize(out_stream = $stdout, error_stream = $stderr, options = {})
  69. @out_stream = out_stream
  70. @error_stream = error_stream
  71. @default_profile = options[:default_profile]
  72. @profiles = options[:profiles] || []
  73. @overridden_paths = []
  74. @options = default_options.merge(options)
  75. @profile_loader = options[:profile_loader]
  76. @options[:skip_profile_information] = options[:skip_profile_information]
  77. @disable_profile_loading = nil
  78. end
  79. def [](key)
  80. @options[key]
  81. end
  82. def []=(key, value)
  83. @options[key] = value
  84. end
  85. def parse!(args) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
  86. @args = args
  87. @expanded_args = @args.dup
  88. @args.extend(::OptionParser::Arguable)
  89. @args.options do |opts| # rubocop:disable Metrics/BlockLength
  90. opts.banner = banner
  91. opts.on('--publish', 'Publish a report to https://reports.cucumber.io') do
  92. set_option :publish_enabled, true
  93. end
  94. opts.on('--publish-quiet', 'Don\'t print information banner about publishing reports') { set_option :publish_quiet }
  95. opts.on('-r LIBRARY|DIR', '--require LIBRARY|DIR', *require_files_msg) { |lib| require_files(lib) }
  96. opts.on('-j DIR', '--jars DIR', 'Load all the jars under DIR') { |jars| load_jars(jars) } if Cucumber::JRUBY
  97. opts.on("#{RETRY_FLAG} ATTEMPTS", *retry_msg) { |v| set_option :retry, v.to_i }
  98. opts.on("#{RETRY_TOTAL_FLAG} TESTS", *retry_total_msg) { |v| set_option :retry_total, v.to_i }
  99. opts.on('--i18n-languages', *i18n_languages_msg) { list_languages_and_exit }
  100. opts.on('--i18n-keywords LANG', *i18n_keywords_msg) { |lang| language lang }
  101. opts.on(FAIL_FAST_FLAG, 'Exit immediately following the first failing scenario') { set_option :fail_fast }
  102. opts.on('-f FORMAT', '--format FORMAT', *format_msg, *FORMAT_HELP) do |v|
  103. add_option :formats, [*parse_formats(v), @out_stream]
  104. end
  105. opts.on('--init', *init_msg) { |_v| initialize_project }
  106. opts.on('-o', '--out [FILE|DIR|URL]', *out_msg) { |v| out_stream v }
  107. opts.on('-t TAG_EXPRESSION', '--tags TAG_EXPRESSION', *tags_msg) { |v| add_tag v }
  108. opts.on('-n NAME', '--name NAME', *name_msg) { |v| add_option :name_regexps, /#{v}/ }
  109. opts.on('-e', '--exclude PATTERN', *exclude_msg) { |v| add_option :excludes, Regexp.new(v) }
  110. opts.on(PROFILE_SHORT_FLAG, "#{PROFILE_LONG_FLAG} PROFILE", *profile_short_flag_msg) { |v| add_profile v }
  111. opts.on(NO_PROFILE_SHORT_FLAG, NO_PROFILE_LONG_FLAG, *no_profile_short_flag_msg) { |_v| disable_profile_loading }
  112. opts.on('-c', '--[no-]color', *color_msg) { |v| color v }
  113. opts.on('-d', '--dry-run', *dry_run_msg) { set_dry_run_and_duration }
  114. opts.on('-m', '--no-multiline', "Don't print multiline strings and tables under steps.") { set_option :no_multiline }
  115. opts.on('-s', '--no-source', "Don't print the file and line of the step definition with the steps.") { set_option :source, false }
  116. opts.on('-i', '--no-snippets', "Don't print snippets for pending steps.") { set_option :snippets, false }
  117. opts.on('-I', '--snippet-type TYPE', *snippet_type_msg) { |v| set_option :snippet_type, v.to_sym }
  118. opts.on('-q', '--quiet', 'Alias for --no-snippets --no-source --no-duration --publish-quiet.') { shut_up }
  119. opts.on('--no-duration', "Don't print the duration at the end of the summary") { set_option :duration, false }
  120. opts.on('-b', '--backtrace', 'Show full backtrace for all errors.') { Cucumber.use_full_backtrace = true }
  121. opts.on('-S', '--[no-]strict', *strict_msg) { |setting| set_strict(setting) }
  122. opts.on('--[no-]strict-undefined', 'Fail if there are any undefined results.') { |setting| set_strict(setting, :undefined) }
  123. opts.on('--[no-]strict-pending', 'Fail if there are any pending results.') { |setting| set_strict(setting, :pending) }
  124. opts.on('--[no-]strict-flaky', 'Fail if there are any flaky results.') { |setting| set_strict(setting, :flaky) }
  125. opts.on('-w', '--wip', 'Fail if there are any passing scenarios.') { set_option :wip }
  126. opts.on('-v', '--verbose', 'Show the files and features loaded.') { set_option :verbose }
  127. opts.on('-g', '--guess', 'Guess best match for Ambiguous steps.') { set_option :guess }
  128. opts.on('-l', '--lines LINES', *lines_msg) { |lines| set_option :lines, lines }
  129. opts.on('-x', '--expand', 'Expand Scenario Outline Tables in output.') { set_option :expand }
  130. opts.on('--order TYPE[:SEED]', 'Run examples in the specified order. Available types:',
  131. *<<-TEXT.split("\n")) do |order|
  132. [defined] Run scenarios in the order they were defined (default).
  133. [random] Shuffle scenarios before running.
  134. Specify SEED to reproduce the shuffling from a previous run.
  135. e.g. --order random:5738
  136. TEXT
  137. @options[:order], @options[:seed] = *order.split(':')
  138. raise "'#{@options[:order]}' is not a recognised order type. Please use one of #{ORDER_TYPES.join(', ')}." unless ORDER_TYPES.include?(@options[:order])
  139. end
  140. opts.on_tail('--version', 'Show version.') { exit_ok(Cucumber::VERSION) }
  141. opts.on_tail('-h', '--help', "You're looking at it.") { exit_ok(opts.help) }
  142. end.parse!
  143. process_publish_options
  144. @args.map! { |a| "#{a}:#{@options[:lines]}" } if @options[:lines]
  145. extract_environment_variables
  146. @options[:paths] = @args.dup # whatver is left over
  147. check_formatter_stream_conflicts
  148. merge_profiles
  149. self
  150. end
  151. def custom_profiles
  152. @profiles - [@default_profile]
  153. end
  154. def filters
  155. @options[:filters] ||= []
  156. end
  157. def check_formatter_stream_conflicts
  158. streams = @options[:formats].uniq.map { |(_, _, stream)| stream }
  159. return if streams == streams.uniq
  160. raise 'All but one formatter must use --out, only one can print to each stream (or STDOUT)'
  161. end
  162. def to_hash
  163. Hash(@options)
  164. end
  165. protected
  166. attr_reader :options, :profiles, :expanded_args
  167. protected :options, :profiles, :expanded_args
  168. private
  169. def process_publish_options
  170. @options[:publish_enabled] = true if truthy_string?(ENV['CUCUMBER_PUBLISH_ENABLED']) || ENV['CUCUMBER_PUBLISH_TOKEN']
  171. @options[:formats] << publisher if @options[:publish_enabled]
  172. @options[:publish_quiet] = true if truthy_string?(ENV['CUCUMBER_PUBLISH_QUIET'])
  173. end
  174. def truthy_string?(str)
  175. return false if str.nil?
  176. str !~ /^(false|no|0)$/i
  177. end
  178. def color_msg
  179. [
  180. 'Whether or not to use ANSI color in the output. Cucumber decides',
  181. 'based on your platform and the output destination if not specified.'
  182. ]
  183. end
  184. def dry_run_msg
  185. ['Invokes formatters without executing the steps.']
  186. end
  187. def exclude_msg
  188. ["Don't run feature files or require ruby files matching PATTERN"]
  189. end
  190. def format_msg
  191. ['How to format features (Default: pretty). Available formats:']
  192. end
  193. def i18n_languages_msg
  194. [
  195. 'List all available languages'
  196. ]
  197. end
  198. def i18n_keywords_msg
  199. [
  200. 'List keywords for in a particular language',
  201. %(Run with "--i18n help" to see all languages)
  202. ]
  203. end
  204. def init_msg
  205. [
  206. 'Initializes folder structure and generates conventional files for',
  207. 'a Cucumber project.'
  208. ]
  209. end
  210. def lines_msg
  211. ['Run given line numbers. Equivalent to FILE:LINE syntax']
  212. end
  213. def no_profile_short_flag_msg
  214. [
  215. "Disables all profile loading to avoid using the 'default' profile."
  216. ]
  217. end
  218. def profile_short_flag_msg
  219. [
  220. 'Pull commandline arguments from cucumber.yml which can be defined as',
  221. "strings or arrays. When a 'default' profile is defined and no profile",
  222. 'is specified it is always used. (Unless disabled, see -P below.)',
  223. 'When feature files are defined in a profile and on the command line',
  224. 'then only the ones from the command line are used.'
  225. ]
  226. end
  227. def retry_msg
  228. ['Specify the number of times to retry failing tests (default: 0)']
  229. end
  230. def retry_total_msg
  231. [
  232. 'The total number of failing test after which retrying of tests is suspended.',
  233. 'Example: --retry-total 10 -> Will stop retrying tests after 10 failing tests.'
  234. ]
  235. end
  236. def name_msg
  237. [
  238. 'Only execute the feature elements which match part of the given name.',
  239. 'If this option is given more than once, it will match against all the',
  240. 'given names.'
  241. ]
  242. end
  243. def strict_msg
  244. [
  245. 'Fail if there are any strict affected results ',
  246. '(that is undefined, pending or flaky results).'
  247. ]
  248. end
  249. def parse_formats(v)
  250. formatter, *formatter_options = v.split(',')
  251. options_hash = Hash[formatter_options.map { |s| s.split('=') }]
  252. [formatter, options_hash]
  253. end
  254. def out_stream(v)
  255. @options[:formats] << ['pretty', {}, nil] if @options[:formats].empty?
  256. @options[:formats][-1][2] = v
  257. end
  258. def tags_msg
  259. [
  260. 'Only execute the features or scenarios with tags matching TAG_EXPRESSION.',
  261. 'Scenarios inherit tags declared on the Feature level. The simplest',
  262. 'TAG_EXPRESSION is simply a tag. Example: --tags @dev. To represent',
  263. "boolean NOT preceed the tag with 'not '. Example: --tags 'not @dev'.",
  264. 'A tag expression can have several tags separated by an or which represents',
  265. "logical OR. Example: --tags '@dev or @wip'. The --tags option can be specified",
  266. 'A tag expression can have several tags separated by an and which represents',
  267. "logical AND. Example: --tags '@dev and @wip'. The --tags option can be specified",
  268. 'several times, and this also represents logical AND.',
  269. "Example: --tags '@foo or not @bar' --tags @zap. This represents the boolean",
  270. 'expression (@foo || !@bar) && @zap.',
  271. "\n",
  272. 'Beware that if you want to use several negative tags to exclude several tags',
  273. "you have to use logical AND: --tags 'not @fixme and not @buggy'.",
  274. "\n",
  275. 'Tags can be given a threshold to limit the number of occurrences.',
  276. 'Example: --tags @qa:3 will fail if there are more than 3 occurrences of the @qa tag.',
  277. 'This can be practical if you are practicing Kanban or CONWIP.'
  278. ]
  279. end
  280. def out_msg
  281. [
  282. 'Write output to a file/directory/URL instead of STDOUT. This option',
  283. 'applies to the previously specified --format, or the',
  284. 'default format if no format is specified. Check the specific',
  285. "formatter's docs to see whether to pass a file, dir or URL.",
  286. "\n",
  287. 'When using a URL, the output of the formatter will be sent as the HTTP request body.',
  288. 'HTTP headers and request method can be set with cURL like options.',
  289. 'Example: --out "http://example.com -X POST -H Content-Type:text/json"'
  290. ]
  291. end
  292. def require_files_msg
  293. [
  294. 'Require files before executing the features. If this',
  295. 'option is not specified, all *.rb files that are',
  296. 'siblings of or below the features will be loaded auto-',
  297. 'matically. Automatic loading is disabled when this',
  298. 'option is specified; all loading becomes explicit.',
  299. 'Files in directories named "support" are still always',
  300. 'loaded first when their parent directories are',
  301. 'required or if the "support" directories themselves are',
  302. 'explicitly required.',
  303. 'This option can be specified multiple times.'
  304. ]
  305. end
  306. def snippet_type_msg
  307. [
  308. 'Use different snippet type (Default: cucumber_expression). Available types:',
  309. Cucumber::Glue::RegistryAndMore.cli_snippet_type_options
  310. ].flatten
  311. end
  312. def banner
  313. [
  314. 'Usage: cucumber [options] [ [FILE|DIR|URL][:LINE[:LINE]*] ]+', '',
  315. 'Examples:',
  316. 'cucumber examples/i18n/en/features',
  317. 'cucumber @rerun.txt (See --format rerun)',
  318. 'cucumber examples/i18n/it/features/somma.feature:6:98:113',
  319. 'cucumber -s -i http://rubyurl.com/eeCl', '', ''
  320. ].join("\n")
  321. end
  322. def require_files(v)
  323. @options[:require] << v
  324. return unless Cucumber::JRUBY && File.directory?(v)
  325. require 'java'
  326. $CLASSPATH << v
  327. end
  328. def require_jars(jars)
  329. Dir["#{jars}/**/*.jar"].sort.each { |jar| require jar }
  330. end
  331. def publisher
  332. url = CUCUMBER_PUBLISH_URL
  333. url += %( -H "Authorization: Bearer #{ENV['CUCUMBER_PUBLISH_TOKEN']}") if ENV['CUCUMBER_PUBLISH_TOKEN']
  334. ['message', {}, url]
  335. end
  336. def language(lang)
  337. require 'gherkin/dialect'
  338. return indicate_invalid_language_and_exit(lang) unless ::Gherkin::DIALECTS.key?(lang)
  339. list_keywords_and_exit(lang)
  340. end
  341. def disable_profile_loading
  342. @disable_profile_loading = true
  343. end
  344. def non_stdout_formats
  345. @options[:formats].reject { |_, _, output| output == @out_stream }
  346. end
  347. def add_option(option, value)
  348. @options[option] << value
  349. end
  350. def add_tag(value)
  351. raise("Found tags option '#{value}'. '~@tag' is no longer supported, use 'not @tag' instead.") if value.include?('~')
  352. raise("Found tags option '#{value}'. '@tag1,@tag2' is no longer supported, use '@tag or @tag2' instead.") if value.include?(',')
  353. @options[:tag_expressions] << value.gsub(/(@\w+)(:\d+)?/, '\1')
  354. add_tag_limits(value)
  355. end
  356. def add_tag_limits(value)
  357. value.split(/[, ]/).map { |part| TAG_LIMIT_MATCHER.match(part) }.compact.each do |matchdata|
  358. add_tag_limit(@options[:tag_limits], matchdata[:tag_name], matchdata[:limit].to_i)
  359. end
  360. end
  361. def add_tag_limit(tag_limits, tag_name, limit)
  362. raise "Inconsistent tag limits for #{tag_name}: #{tag_limits[tag_name]} and #{limit}" if tag_limits[tag_name] && tag_limits[tag_name] != limit
  363. tag_limits[tag_name] = limit
  364. end
  365. def color(color)
  366. Cucumber::Term::ANSIColor.coloring = color
  367. end
  368. def initialize_project
  369. ProjectInitializer.new.run && Kernel.exit(0)
  370. end
  371. def add_profile(p)
  372. @profiles << p
  373. end
  374. def set_option(option, value = nil)
  375. @options[option] = value.nil? ? true : value
  376. end
  377. def set_dry_run_and_duration
  378. @options[:dry_run] = true
  379. @options[:duration] = false
  380. end
  381. def exit_ok(text)
  382. @out_stream.puts text
  383. Kernel.exit(0)
  384. end
  385. def shut_up
  386. @options[:publish_quiet] = true
  387. @options[:snippets] = false
  388. @options[:source] = false
  389. @options[:duration] = false
  390. end
  391. def set_strict(setting, type = nil)
  392. @options[:strict].set_strict(setting, type)
  393. end
  394. def stdout_formats
  395. @options[:formats].select { |_, _, output| output == @out_stream }
  396. end
  397. def extract_environment_variables
  398. @args.delete_if do |arg|
  399. if arg =~ /^(\w+)=(.*)$/
  400. @options[:env_vars][Regexp.last_match(1)] = Regexp.last_match(2)
  401. true
  402. end
  403. end
  404. end
  405. def disable_profile_loading?
  406. @disable_profile_loading
  407. end
  408. def merge_profiles
  409. if @disable_profile_loading
  410. @out_stream.puts 'Disabling profiles...'
  411. return
  412. end
  413. @profiles << @default_profile if default_profile_should_be_used?
  414. @profiles.each do |profile|
  415. merge_with_profile(profile)
  416. end
  417. @options[:profiles] = @profiles
  418. end
  419. def merge_with_profile(profile)
  420. profile_args = profile_loader.args_from(profile)
  421. profile_options = Options.parse(
  422. profile_args, @out_stream, @error_stream,
  423. skip_profile_information: true,
  424. profile_loader: profile_loader
  425. )
  426. reverse_merge(profile_options)
  427. end
  428. def default_profile_should_be_used?
  429. @profiles.empty? &&
  430. profile_loader.cucumber_yml_defined? &&
  431. profile_loader.profile?(@default_profile)
  432. end
  433. def profile_loader
  434. @profile_loader ||= ProfileLoader.new
  435. end
  436. def reverse_merge(other_options) # rubocop:disable Metrics/AbcSize
  437. @options = other_options.options.merge(@options)
  438. @options[:require] += other_options[:require]
  439. @options[:excludes] += other_options[:excludes]
  440. @options[:name_regexps] += other_options[:name_regexps]
  441. @options[:tag_expressions] += other_options[:tag_expressions]
  442. merge_tag_limits(@options[:tag_limits], other_options[:tag_limits])
  443. @options[:env_vars] = other_options[:env_vars].merge(@options[:env_vars])
  444. if @options[:paths].empty?
  445. @options[:paths] = other_options[:paths]
  446. else
  447. @overridden_paths += (other_options[:paths] - @options[:paths])
  448. end
  449. @options[:source] &= other_options[:source]
  450. @options[:snippets] &= other_options[:snippets]
  451. @options[:duration] &= other_options[:duration]
  452. @options[:strict] = other_options[:strict].merge!(@options[:strict])
  453. @options[:dry_run] |= other_options[:dry_run]
  454. @profiles += other_options.profiles
  455. @expanded_args += other_options.expanded_args
  456. if @options[:formats].empty?
  457. @options[:formats] = other_options[:formats]
  458. else
  459. @options[:formats] += other_options[:formats]
  460. @options[:formats] = stdout_formats[0..0] + non_stdout_formats
  461. end
  462. @options[:retry] = other_options[:retry] if @options[:retry].zero?
  463. @options[:retry_total] = other_options[:retry_total] if @options[:retry_total].infinite?
  464. self
  465. end
  466. def merge_tag_limits(option_limits, other_limits)
  467. other_limits.each { |key, value| add_tag_limit(option_limits, key, value) }
  468. end
  469. def indicate_invalid_language_and_exit(lang)
  470. @out_stream.write("Invalid language '#{lang}'. Available languages are:\n")
  471. list_languages_and_exit
  472. end
  473. def list_keywords_and_exit(lang)
  474. require 'gherkin/dialect'
  475. language = ::Gherkin::Dialect.for(lang)
  476. data = Cucumber::MultilineArgument::DataTable.from(
  477. [
  478. ['feature', to_keywords_string(language.feature_keywords)],
  479. ['background', to_keywords_string(language.background_keywords)],
  480. ['scenario', to_keywords_string(language.scenario_keywords)],
  481. ['scenario_outline', to_keywords_string(language.scenario_outline_keywords)],
  482. ['examples', to_keywords_string(language.examples_keywords)],
  483. ['given', to_keywords_string(language.given_keywords)],
  484. ['when', to_keywords_string(language.when_keywords)],
  485. ['then', to_keywords_string(language.then_keywords)],
  486. ['and', to_keywords_string(language.and_keywords)],
  487. ['but', to_keywords_string(language.but_keywords)],
  488. ['given (code)', to_code_keywords_string(language.given_keywords)],
  489. ['when (code)', to_code_keywords_string(language.when_keywords)],
  490. ['then (code)', to_code_keywords_string(language.then_keywords)],
  491. ['and (code)', to_code_keywords_string(language.and_keywords)],
  492. ['but (code)', to_code_keywords_string(language.but_keywords)]
  493. ]
  494. )
  495. @out_stream.write(data.to_s(color: false, prefixes: Hash.new('')))
  496. Kernel.exit(0)
  497. end
  498. def list_languages_and_exit
  499. require 'gherkin/dialect'
  500. data = Cucumber::MultilineArgument::DataTable.from(
  501. ::Gherkin::DIALECTS.keys.map do |key|
  502. [key, ::Gherkin::DIALECTS[key].fetch('name'), ::Gherkin::DIALECTS[key].fetch('native')]
  503. end
  504. )
  505. @out_stream.write(data.to_s(color: false, prefixes: Hash.new('')))
  506. Kernel.exit(0)
  507. end
  508. def to_keywords_string(list)
  509. list.map { |item| "\"#{item}\"" }.join(', ')
  510. end
  511. def to_code_keywords_string(list)
  512. to_keywords_string(Cucumber::Gherkin::I18n.code_keywords_for(list))
  513. end
  514. def default_options
  515. {
  516. strict: Cucumber::Core::Test::Result::StrictConfiguration.new,
  517. require: [],
  518. dry_run: false,
  519. formats: [],
  520. excludes: [],
  521. tag_expressions: [],
  522. tag_limits: {},
  523. name_regexps: [],
  524. env_vars: {},
  525. diff_enabled: true,
  526. snippets: true,
  527. source: true,
  528. duration: true,
  529. retry: 0,
  530. retry_total: Float::INFINITY
  531. }
  532. end
  533. end
  534. end
  535. end

No Description

Contributors (1)