|
- # frozen_string_literal: true
-
- require 'spec_helper'
- require 'yaml'
- require 'cucumber/cli/options'
-
- module Cucumber
- module Cli
- describe Options do
- def given_cucumber_yml_defined_as(hash_or_string)
- allow(File).to receive(:exist?) { true }
-
- cucumber_yml = hash_or_string.is_a?(Hash) ? hash_or_string.to_yaml : hash_or_string
-
- allow(IO).to receive(:read).with('cucumber.yml') { cucumber_yml }
- end
-
- before(:each) do
- allow(File).to receive(:exist?) { false } # Meaning, no cucumber.yml exists
- allow(Kernel).to receive(:exit)
- end
-
- def output_stream
- @output_stream ||= StringIO.new
- end
-
- def error_stream
- @error_stream ||= StringIO.new
- end
-
- def options
- @options ||= Options.new(output_stream, error_stream)
- end
-
- def prepare_args(args)
- args.is_a?(Array) ? args : args.split(' ')
- end
-
- describe 'parsing' do
- def when_parsing(args)
- yield
- options.parse!(prepare_args(args))
- end
-
- def after_parsing(args)
- options.parse!(prepare_args(args))
- yield
- end
-
- def with_env(name, value)
- previous_value = ENV[name]
- ENV[name] = value.to_s
- yield
- if previous_value.nil?
- ENV.delete(name)
- else
- ENV[name] = previous_value
- end
- end
-
- context '-r or --require' do
- it 'collects all specified files into an array' do
- after_parsing('--require some_file.rb -r another_file.rb') do
- expect(options[:require]).to eq ['some_file.rb', 'another_file.rb']
- end
- end
- end
-
- context '--i18n-languages' do
- include RSpec::WorkInProgress
-
- it 'lists all known languages' do
- after_parsing '--i18n-languages' do
- ::Gherkin::DIALECTS.keys.map do |key|
- expect(@output_stream.string).to include(key.to_s)
- end
- end
- end
-
- it 'exits the program' do
- when_parsing('--i18n-languages') { expect(Kernel).to receive(:exit) }
- end
- end
-
- context '--i18n-keywords' do
- context 'with invalid LANG' do
- include RSpec::WorkInProgress
-
- it 'exits' do
- when_parsing '--i18n-keywords foo' do
- expect(Kernel).to receive(:exit)
- end
- end
-
- it 'says the language was invalid' do
- after_parsing '--i18n-keywords foo' do
- expect(@output_stream.string).to include("Invalid language 'foo'. Available languages are:")
- end
- end
-
- it 'displays the language table' do
- after_parsing '--i18n-keywords foo' do
- ::Gherkin::DIALECTS.keys.map do |key|
- expect(@output_stream.string).to include(key.to_s)
- end
- end
- end
- end
- end
-
- context '-f FORMAT or --format FORMAT' do
- it 'defaults the output for the formatter to the output stream (STDOUT)' do
- after_parsing('-f pretty') { expect(options[:formats]).to eq [['pretty', {}, output_stream]] }
- end
-
- it 'extracts per-formatter options' do
- after_parsing('-f pretty,foo=bar,foo2=bar2') do
- expect(options[:formats]).to eq [['pretty', { 'foo' => 'bar', 'foo2' => 'bar2' }, output_stream]]
- end
- end
-
- it 'extracts junit formatter file attribute option' do
- after_parsing('-f junit,file-attribute=true') do
- expect(options[:formats]).to eq [['junit', { 'file-attribute' => 'true' }, output_stream]]
- end
- end
-
- it 'extracts junit formatter file attribute option with pretty' do
- after_parsing('-f pretty -f junit,file-attribute=true -o file.txt') do
- expect(options[:formats]).to eq [['pretty', {}, output_stream], ['junit', { 'file-attribute' => 'true' }, 'file.txt']]
- end
- end
- end
-
- context '-o [FILE|DIR] or --out [FILE|DIR]' do
- it "defaults the formatter to 'pretty' when not specified earlier" do
- after_parsing('-o file.txt') { expect(options[:formats]).to eq [['pretty', {}, 'file.txt']] }
- end
- it 'sets the output for the formatter defined immediately before it' do
- after_parsing('-f profile --out file.txt -f pretty -o file2.txt') do
- expect(options[:formats]).to eq [['profile', {}, 'file.txt'], ['pretty', {}, 'file2.txt']]
- end
- end
- end
-
- context 'handling multiple formatters' do
- it 'catches multiple command line formatters using the same stream' do
- expect { options.parse!(prepare_args('-f pretty -f progress')) }.to raise_error('All but one formatter must use --out, only one can print to each stream (or STDOUT)')
- end
-
- it 'catches multiple profile formatters using the same stream' do
- given_cucumber_yml_defined_as('default' => '-f progress -f pretty')
- options = Options.new(output_stream, error_stream, default_profile: 'default')
-
- expect { options.parse!(%w[]) }.to raise_error('All but one formatter must use --out, only one can print to each stream (or STDOUT)')
- end
-
- it 'profiles does not affect the catching of multiple command line formatters using the same stream' do
- given_cucumber_yml_defined_as('default' => '-q')
- options = Options.new(output_stream, error_stream, default_profile: 'default')
-
- expect { options.parse!(%w[-f progress -f pretty]) }.to raise_error('All but one formatter must use --out, only one can print to each stream (or STDOUT)')
- end
-
- it 'merges profile formatters and command line formatters' do
- given_cucumber_yml_defined_as('default' => '-f junit -o result.xml')
- options = Options.new(output_stream, error_stream, default_profile: 'default')
-
- options.parse!(%w[-f pretty])
-
- expect(options[:formats]).to eq [['pretty', {}, output_stream], ['junit', {}, 'result.xml']]
- end
- end
-
- context '-t TAGS --tags TAGS' do
- it 'handles tag expressions as argument' do
- after_parsing(['--tags', 'not @foo or @bar']) { expect(options[:tag_expressions]).to eq ['not @foo or @bar'] }
- end
-
- it 'stores tags passed with different --tags separately' do
- after_parsing('--tags @foo --tags @bar') { expect(options[:tag_expressions]).to eq ['@foo', '@bar'] }
- end
-
- it 'strips tag limits from the tag expressions stored' do
- after_parsing(['--tags', 'not @foo:2 or @bar:3']) { expect(options[:tag_expressions]).to eq ['not @foo or @bar'] }
- end
-
- it 'stores tag limits separately' do
- after_parsing(['--tags', 'not @foo:2 or @bar:3']) { expect(options[:tag_limits]).to eq Hash('@foo' => 2, '@bar' => 3) }
- end
-
- it 'raise exception for inconsistent tag limits' do
- expect { after_parsing('--tags @foo:2 --tags @foo:3') }.to raise_error(RuntimeError, 'Inconsistent tag limits for @foo: 2 and 3')
- end
- end
-
- context '-n NAME or --name NAME' do
- it 'stores the provided names as regular expressions' do
- after_parsing('-n foo --name bar') { expect(options[:name_regexps]).to eq [/foo/, /bar/] }
- end
- end
-
- context '-e PATTERN or --exclude PATTERN' do
- it 'stores the provided exclusions as regular expressions' do
- after_parsing('-e foo --exclude bar') { expect(options[:excludes]).to eq [/foo/, /bar/] }
- end
- end
-
- context '-l LINES or --lines LINES' do
- it 'adds line numbers to args' do
- options.parse!(%w[-l24 FILE])
-
- expect(options.instance_variable_get(:@args)).to eq ['FILE:24']
- end
- end
-
- context '-p PROFILE or --profile PROFILE' do
- it 'uses the default profile passed in during initialization if none are specified by the user' do
- given_cucumber_yml_defined_as('default' => '--require some_file')
-
- options = Options.new(output_stream, error_stream, default_profile: 'default')
- options.parse!(%w[--format progress])
-
- expect(options[:require]).to include('some_file')
- end
-
- it 'merges all uniq values from both cmd line and the profile' do
- given_cucumber_yml_defined_as('foo' => %w[--verbose])
- options.parse!(%w[--wip --profile foo])
-
- expect(options[:wip]).to be true
- expect(options[:verbose]).to be true
- end
-
- it "gives precendene to the origianl options' paths" do
- given_cucumber_yml_defined_as('foo' => %w[features])
- options.parse!(%w[my.feature -p foo])
-
- expect(options[:paths]).to eq %w[my.feature]
- end
-
- it 'combines the require files of both' do
- given_cucumber_yml_defined_as('bar' => %w[--require features -r dog.rb])
- options.parse!(%w[--require foo.rb -p bar])
-
- expect(options[:require]).to eq %w[foo.rb features dog.rb]
- end
-
- it 'combines the tag names of both' do
- given_cucumber_yml_defined_as('baz' => %w[-t @bar])
- options.parse!(%w[--tags @foo -p baz])
-
- expect(options[:tag_expressions]).to eq ['@foo', '@bar']
- end
-
- it 'combines the tag limits of both' do
- given_cucumber_yml_defined_as('baz' => %w[-t @bar:2])
- options.parse!(%w[--tags @foo:3 -p baz])
-
- expect(options[:tag_limits]).to eq Hash('@foo' => 3, '@bar' => 2)
- end
-
- it 'raise exceptions for inconsistent tag limits' do
- given_cucumber_yml_defined_as('baz' => %w[-t @bar:2])
-
- expect { options.parse!(%w[--tags @bar:3 -p baz]) }.to raise_error(RuntimeError, 'Inconsistent tag limits for @bar: 3 and 2')
- end
-
- it 'only takes the paths from the original options, and disgregards the profiles' do
- given_cucumber_yml_defined_as('baz' => %w[features])
- options.parse!(%w[my.feature -p baz])
-
- expect(options[:paths]).to eq ['my.feature']
- end
-
- it 'uses the paths from the profile when none are specified originally' do
- given_cucumber_yml_defined_as('baz' => %w[some.feature])
- options.parse!(%w[-p baz])
-
- expect(options[:paths]).to eq ['some.feature']
- end
-
- it 'combines environment variables from the profile but gives precendene to cmd line args' do
- given_cucumber_yml_defined_as('baz' => %w[FOO=bar CHEESE=swiss])
- options.parse!(%w[-p baz CHEESE=cheddar BAR=foo])
-
- expect(options[:env_vars]).to eq('BAR' => 'foo', 'FOO' => 'bar', 'CHEESE' => 'cheddar')
- end
-
- it 'disregards STDOUT formatter defined in profile when another is passed in (via cmd line)' do
- given_cucumber_yml_defined_as('foo' => %w[--format pretty])
- options.parse!(%w[--format progress --profile foo])
-
- expect(options[:formats]).to eq [['progress', {}, output_stream]]
- end
-
- it 'includes any non-STDOUT formatters from the profile' do
- given_cucumber_yml_defined_as('json' => %w[--format json -o features.json])
- options.parse!(%w[--format progress --profile json])
-
- expect(options[:formats]).to eq [['progress', {}, output_stream], ['json', {}, 'features.json']]
- end
-
- it 'does not include STDOUT formatters from the profile if there is a STDOUT formatter in command line' do
- given_cucumber_yml_defined_as('json' => %w[--format json -o features.json --format pretty])
- options.parse!(%w[--format progress --profile json])
-
- expect(options[:formats]).to eq [['progress', {}, output_stream], ['json', {}, 'features.json']]
- end
-
- it 'includes any STDOUT formatters from the profile if no STDOUT formatter was specified in command line' do
- given_cucumber_yml_defined_as('json' => %w[--format json])
- options.parse!(%w[--format rerun -o rerun.txt --profile json])
-
- expect(options[:formats]).to eq [['json', {}, output_stream], ['rerun', {}, 'rerun.txt']]
- end
-
- it 'assumes all of the formatters defined in the profile when none are specified on cmd line' do
- given_cucumber_yml_defined_as('json' => %w[--format progress --format json -o features.json])
- options.parse!(%w[--profile json])
-
- expect(options[:formats]).to eq [['progress', {}, output_stream], ['json', {}, 'features.json']]
- end
-
- # rubocop:disable Style/GlobalVars
- it 'only reads cucumber.yml once' do
- original_parse_count = $cucumber_yml_read_count
- $cucumber_yml_read_count = 0
-
- begin
- given_cucumber_yml_defined_as(<<-YML
- <% $cucumber_yml_read_count += 1 %>
- default: --format pretty
- YML
- )
- options = Options.new(output_stream, error_stream, default_profile: 'default')
- options.parse!(%w[-f progress])
-
- expect($cucumber_yml_read_count).to eq 1
- ensure
- $cucumber_yml_read_count = original_parse_count
- end
- end
- # rubocop:enable Style/GlobalVars
-
- it 'respects --quiet when defined in the profile' do
- given_cucumber_yml_defined_as('foo' => '-q')
- options.parse!(%w[-p foo])
-
- expect(options[:publish_quiet]).to be true
- expect(options[:snippets]).to be false
- expect(options[:source]).to be false
- expect(options[:duration]).to be false
- end
-
- it 'uses --no-duration when defined in the profile' do
- given_cucumber_yml_defined_as('foo' => '--no-duration')
- options.parse!(%w[-p foo])
-
- expect(options[:duration]).to be false
- end
-
- context '--retry ATTEMPTS' do
- context '--retry option not defined on the command line' do
- it 'uses the --retry option from the profile' do
- given_cucumber_yml_defined_as('foo' => '--retry 2')
- options.parse!(%w[-p foo])
-
- expect(options[:retry]).to be 2
- end
- end
-
- context '--retry option defined on the command line' do
- it 'ignores the --retry option from the profile' do
- given_cucumber_yml_defined_as('foo' => '--retry 2')
- options.parse!(%w[--retry 1 -p foo])
-
- expect(options[:retry]).to be 1
- end
- end
- end
-
- context '--retry-total TESTS' do
- context '--retry-total option not defined on the command line' do
- it 'uses the --retry-total option from the profile' do
- given_cucumber_yml_defined_as('foo' => '--retry-total 2')
- options.parse!(%w[-p foo])
-
- expect(options[:retry_total]).to be 2
- end
- end
-
- context '--retry-total option defined on the command line' do
- it 'ignores the --retry-total option from the profile' do
- given_cucumber_yml_defined_as('foo' => '--retry-total 2')
- options.parse!(%w[--retry-total 1 -p foo])
-
- expect(options[:retry_total]).to be 1
- end
- end
- end
- end
-
- context '-P or --no-profile' do
- it 'disables profiles' do
- given_cucumber_yml_defined_as('default' => '-v --require file_specified_in_default_profile.rb')
-
- after_parsing('-P --require some_file.rb') do
- expect(options[:require]).to eq ['some_file.rb']
- end
- end
-
- it 'notifies the user that the profiles are being disabled' do
- given_cucumber_yml_defined_as('default' => '-v')
-
- after_parsing('--no-profile --require some_file.rb') do
- expect(output_stream.string).to match(/Disabling profiles.../)
- end
- end
- end
-
- context '-b or --backtrace' do
- it "turns on cucumber's full backtrace" do
- when_parsing('-b') do
- expect(Cucumber).to receive(:use_full_backtrace=).with(true)
- end
- end
- end
-
- context '--version' do
- it "displays Cucumber's version" do
- after_parsing('--version') do
- expect(output_stream.string).to match(/#{Cucumber::VERSION}/)
- end
- end
-
- it 'exits the program' do
- when_parsing('--version') { expect(Kernel).to receive(:exit) }
- end
- end
-
- context 'environment variables (i.e. MODE=webrat)' do
- it 'places all of the environment variables into a hash' do
- after_parsing('MODE=webrat FOO=bar') do
- expect(options[:env_vars]).to eq('MODE' => 'webrat', 'FOO' => 'bar')
- end
- end
- end
-
- context '--retry ATTEMPTS' do
- it 'is 0 by default' do
- after_parsing('') do
- expect(options[:retry]).to eql 0
- end
- end
-
- it 'sets the options[:retry] value' do
- after_parsing('--retry 4') do
- expect(options[:retry]).to eql 4
- end
- end
- end
-
- context '--retry-total TESTS' do
- it 'is INFINITY by default' do
- after_parsing('') do
- expect(options[:retry_total]).to eql Float::INFINITY
- end
- end
-
- it 'sets the options[:retry_total] value' do
- after_parsing('--retry 3 --retry-total 10') do
- expect(options[:retry_total]).to eql 10
- end
- end
- end
-
- it 'assigns any extra arguments as paths to features' do
- after_parsing('-f pretty my_feature.feature my_other_features') do
- expect(options[:paths]).to eq ['my_feature.feature', 'my_other_features']
- end
- end
-
- it 'does not mistake environment variables as feature paths' do
- after_parsing('my_feature.feature FOO=bar') do
- expect(options[:paths]).to eq ['my_feature.feature']
- end
- end
-
- context '--snippet-type' do
- it 'parses the snippet type argument' do
- after_parsing('--snippet-type classic') do
- expect(options[:snippet_type]).to eq :classic
- end
- end
- end
-
- context '--publish' do
- it 'adds message formatter with output to default reports publishing url' do
- after_parsing('--publish') do
- expect(@options[:formats]).to include(['message', {}, Cucumber::Cli::Options::CUCUMBER_PUBLISH_URL])
- end
- end
-
- it 'enables publishing when CUCUMBER_PUBLISH_ENABLED=true' do
- with_env('CUCUMBER_PUBLISH_ENABLED', 'true') do
- after_parsing('') do
- expect(@options[:formats]).to include(['message', {}, Cucumber::Cli::Options::CUCUMBER_PUBLISH_URL])
- expect(@options[:publish_enabled]).to be true
- end
- end
- end
-
- it 'does not enable publishing when CUCUMBER_PUBLISH_ENABLED=false' do
- with_env('CUCUMBER_PUBLISH_ENABLED', 'false') do
- after_parsing('') do
- expect(@options[:publish_enabled]).to be_falsy
- end
- end
- end
-
- it 'adds authentication header with CUCUMBER_PUBLISH_TOKEN environment variable value if set' do
- with_env('CUCUMBER_PUBLISH_TOKEN', 'abcd1234') do
- after_parsing('--publish') do
- expect(@options[:formats]).to include(['message', {}, %(#{Cucumber::Cli::Options::CUCUMBER_PUBLISH_URL} -H "Authorization: Bearer abcd1234")])
- expect(@options[:publish_enabled]).to be true
- end
- end
- end
-
- it 'adds authentication header with CUCUMBER_PUBLISH_TOKEN environment variable value if set and no --publish' do
- with_env('CUCUMBER_PUBLISH_TOKEN', 'abcd1234') do
- after_parsing('') do
- expect(@options[:formats]).to include(['message', {}, %(#{Cucumber::Cli::Options::CUCUMBER_PUBLISH_URL} -H "Authorization: Bearer abcd1234")])
- expect(@options[:publish_enabled]).to be true
- end
- end
- end
- end
-
- context '--publish-quiet' do
- it 'silences publish banner' do
- after_parsing('--publish-quiet') do
- expect(@options[:publish_quiet]).to eq true
- end
- end
-
- it 'enables publishing when CUCUMBER_PUBLISH_QUIET=true' do
- with_env('CUCUMBER_PUBLISH_QUIET', 'true') do
- after_parsing('') do
- expect(@options[:publish_quiet]).to be true
- end
- end
- end
- end
- end
-
- describe 'dry-run' do
- it 'has the default value for snippets' do
- given_cucumber_yml_defined_as('foo' => %w[--dry-run])
- options.parse!(%w[--dry-run])
-
- expect(options[:snippets]).to be true
- end
-
- it 'sets snippets to false when no-snippets provided after dry-run' do
- given_cucumber_yml_defined_as('foo' => %w[--dry-run --no-snippets])
- options.parse!(%w[--dry-run --no-snippets])
-
- expect(options[:snippets]).to be false
- end
-
- it 'sets snippets to false when no-snippets provided before dry-run' do
- given_cucumber_yml_defined_as('foo' => %w[--no-snippet --dry-run])
- options.parse!(%w[--no-snippets --dry-run])
-
- expect(options[:snippets]).to be false
- end
- end
- end
- end
- end
|