diff --git a/.rubocop_rspec_base.yml b/.rubocop_rspec_base.yml index 59233df0e8..712141c35f 100644 --- a/.rubocop_rspec_base.yml +++ b/.rubocop_rspec_base.yml @@ -1,4 +1,4 @@ -# This file was generated on 2016-05-11T21:32:01+10:00 from the rspec-dev repo. +# This file was generated on 2016-06-14T22:22:14-07:00 from the rspec-dev repo. # DO NOT modify it by hand as your changes will get lost the next time it is generated. # This file contains defaults for RSpec projects. Individual projects diff --git a/.travis.yml b/.travis.yml index 8829779a0c..7b013d790d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,9 @@ -# This file was generated on 2016-05-11T21:32:01+10:00 from the rspec-dev repo. +# This file was generated on 2016-06-14T22:22:14-07:00 from the rspec-dev repo. # DO NOT modify it by hand as your changes will get lost the next time it is generated. language: ruby sudo: false +email: false cache: directories: - ../bundle @@ -19,7 +20,7 @@ rvm: - 1.9.3 - 2.0.0 - 2.1 - - 2.2 + - 2.2.5 - 2.3.1 - ruby-head - ree diff --git a/Changelog.md b/Changelog.md index feba641d9a..307936a41f 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,15 @@ -### 3.5 Development -[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.5.0.beta4...master) +### 3.5.0 / 2016-07-01 +[Full Changelog](http://github.com/rspec/rspec-core/compare/v3.5.0.beta4...v3.5.0) + +Enhancements: + +* Include any `SPEC_OPTS` in reproduction command printed at the end of + a bisect run. (Simon Coffey, #2274) + +Bug Fixes: + +* Handle `--bisect` in `SPEC_OPTS` environment variable correctly so as + to avoid infinite recursion. (Simon Coffey, #2271) ### 3.5.0.beta4 / 2016-06-05 [Full Changelog](http://github.com/rspec/rspec-core/compare/v3.5.0.beta3...v3.5.0.beta4) diff --git a/appveyor.yml b/appveyor.yml index 16cd1624dd..44f17544fd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -# This file was generated on 2016-05-11T21:32:01+10:00 from the rspec-dev repo. +# This file was generated on 2016-06-14T22:22:14-07:00 from the rspec-dev repo. # DO NOT modify it by hand as your changes will get lost the next time it is generated. version: "{build}" diff --git a/features/command_line/bisect.feature b/features/command_line/bisect.feature index 4c915e8d83..76886adf00 100644 --- a/features/command_line/bisect.feature +++ b/features/command_line/bisect.feature @@ -1,3 +1,4 @@ +@with-clean-spec-opts Feature: Bisect RSpec's `--order random` and `--seed` options help surface flickering examples that only fail when one or more other examples are executed first. It can be very difficult to isolate the exact combination of examples that triggers the failure. The `--bisect` flag helps solve that problem. diff --git a/features/support/require_expect_syntax_in_aruba_specs.rb b/features/support/require_expect_syntax_in_aruba_specs.rb index 191998ec35..e37958d94c 100644 --- a/features/support/require_expect_syntax_in_aruba_specs.rb +++ b/features/support/require_expect_syntax_in_aruba_specs.rb @@ -1,6 +1,6 @@ if defined?(Cucumber) require 'shellwords' - Before('~@allow-should-syntax') do + Before('~@allow-should-syntax', '~@with-clean-spec-opts') do set_env('SPEC_OPTS', "-r#{Shellwords.escape(__FILE__)}") end diff --git a/lib/rspec/core.rb b/lib/rspec/core.rb index 3276b69594..d5b6b5b287 100644 --- a/lib/rspec/core.rb +++ b/lib/rspec/core.rb @@ -31,6 +31,7 @@ option_parser configuration_options runner + invocations example shared_example_group example_group diff --git a/lib/rspec/core/bisect/runner.rb b/lib/rspec/core/bisect/runner.rb index 4625b5dabb..a243185016 100644 --- a/lib/rspec/core/bisect/runner.rb +++ b/lib/rspec/core/bisect/runner.rb @@ -1,5 +1,6 @@ RSpec::Support.require_rspec_core "shell_escape" require 'open3' +require 'shellwords' module RSpec module Core @@ -37,6 +38,7 @@ def command_for(locations) def repro_command_from(locations) parts = [] + parts.concat environment_repro_parts parts << "rspec" parts.concat Formatters::Helpers.organize_ids(locations) parts.concat original_cli_args_without_locations @@ -71,13 +73,16 @@ def run_locations(*capture_args) # https://github.com/jruby/jruby/issues/2766 if Open3.respond_to?(:capture2e) && !RSpec::Support::Ruby.jruby? def run_command(cmd) - Open3.capture2e(cmd).first + Open3.capture2e(bisect_environment_hash, cmd).first end else # for 1.8.7 # :nocov: def run_command(cmd) out = err = nil + original_spec_opts = ENV['SPEC_OPTS'] + ENV['SPEC_OPTS'] = spec_opts_without_bisect + Open3.popen3(cmd) do |_, stdout, stderr| # Reading the streams blocks until the process is complete out = stdout.read @@ -85,10 +90,34 @@ def run_command(cmd) end "Stdout:\n#{out}\n\nStderr:\n#{err}" + ensure + ENV['SPEC_OPTS'] = original_spec_opts end # :nocov: end + def bisect_environment_hash + if ENV.key?('SPEC_OPTS') + { 'SPEC_OPTS' => spec_opts_without_bisect } + else + {} + end + end + + def environment_repro_parts + bisect_environment_hash.map do |k, v| + %Q(#{k}="#{v}") + end + end + + def spec_opts_without_bisect + Shellwords.join( + Shellwords.split(ENV.fetch('SPEC_OPTS', '')).reject do |arg| + arg =~ /^--bisect/ + end + ) + end + def reusable_cli_options @reusable_cli_options ||= begin opts = original_cli_args_without_locations diff --git a/lib/rspec/core/configuration_options.rb b/lib/rspec/core/configuration_options.rb index 9a6cc78d52..f46df2f521 100644 --- a/lib/rspec/core/configuration_options.rb +++ b/lib/rspec/core/configuration_options.rb @@ -35,6 +35,9 @@ def configure_filter_manager(filter_manager) # @return [Hash] the final merged options, drawn from all external sources attr_reader :options + # @return [Array] the original command-line arguments + attr_reader :args + private def organize_options diff --git a/lib/rspec/core/invocations.rb b/lib/rspec/core/invocations.rb new file mode 100644 index 0000000000..0e7b6bd342 --- /dev/null +++ b/lib/rspec/core/invocations.rb @@ -0,0 +1,67 @@ +module RSpec + module Core + # @private + module Invocations + # @private + class InitializeProject + def call(*_args) + RSpec::Support.require_rspec_core "project_initializer" + ProjectInitializer.new.run + 0 + end + end + + # @private + class DRbWithFallback + def call(options, err, out) + require 'rspec/core/drb' + begin + return DRbRunner.new(options).run(err, out) + rescue DRb::DRbConnError + err.puts "No DRb server is running. Running in local process instead ..." + end + RSpec::Core::Runner.new(options).run(err, out) + end + end + + # @private + class Bisect + def call(options, _err, _out) + RSpec::Support.require_rspec_core "bisect/coordinator" + + success = RSpec::Core::Bisect::Coordinator.bisect_with( + options.args, + RSpec.configuration, + bisect_formatter_for(options.options[:bisect]) + ) + + success ? 0 : 1 + end + + private + + def bisect_formatter_for(argument) + return Formatters::BisectDebugFormatter if argument == "verbose" + Formatters::BisectProgressFormatter + end + end + + # @private + class PrintVersion + def call(_options, _err, out) + out.puts RSpec::Core::Version::STRING + 0 + end + end + + # @private + PrintHelp = Struct.new(:parser, :invalid_options) do + def call(_options, _err, out) + # Removing the blank invalid options from the output. + out.puts parser.to_s.gsub(/^\s+(#{invalid_options.join('|')})\s*$\n/, '') + 0 + end + end + end + end +end diff --git a/lib/rspec/core/option_parser.rb b/lib/rspec/core/option_parser.rb index a8e846ab76..d61e051df3 100644 --- a/lib/rspec/core/option_parser.rb +++ b/lib/rspec/core/option_parser.rb @@ -36,6 +36,7 @@ def parse(source=nil) # rubocop:disable MethodLength # rubocop:disable Metrics/AbcSize # rubocop:disable CyclomaticComplexity + # rubocop:disable PerceivedComplexity def parser(options) OptionParser.new do |parser| parser.banner = "Usage: rspec [options] [files or directories]\n\n" @@ -68,7 +69,8 @@ def parser(options) parser.on('--bisect[=verbose]', 'Repeatedly runs the suite in order to isolate the failures to the ', ' smallest reproducible case.') do |argument| - bisect_and_exit(argument) + options[:bisect] = argument || true + options[:runner] = RSpec::Core::Invocations::Bisect.new end parser.on('--[no-]fail-fast[=COUNT]', 'Abort the run after a certain number of failures (1 by default).') do |argument| @@ -96,8 +98,9 @@ def parser(options) options[:dry_run] = true end - parser.on('-X', '--[no-]drb', 'Run examples via DRb.') do |o| - options[:drb] = o + parser.on('-X', '--[no-]drb', 'Run examples via DRb.') do |use_drb| + options[:drb] = use_drb + options[:runner] = RSpec::Core::Invocations::DRbWithFallback.new if use_drb end parser.on('--drb-port PORT', 'Port to connect to the DRb server.') do |o| @@ -105,7 +108,7 @@ def parser(options) end parser.on('--init', 'Initialize your project with RSpec.') do |_cmd| - initialize_project_and_exit + options[:runner] = RSpec::Core::Invocations::InitializeProject.new end parser.separator("\n **** Output ****\n\n") @@ -242,7 +245,7 @@ def parser(options) parser.separator("\n **** Utility ****\n\n") parser.on('-v', '--version', 'Display the version.') do - print_version_and_exit + options[:runner] = RSpec::Core::Invocations::PrintVersion.new end # These options would otherwise be confusing to users, so we forcibly @@ -254,7 +257,7 @@ def parser(options) invalid_options = %w[-d --I] parser.on_tail('-h', '--help', "You're looking at it.") do - print_help_and_exit(parser, invalid_options) + options[:runner] = RSpec::Core::Invocations::PrintHelp.new(parser, invalid_options) end # This prevents usage of the invalid_options. @@ -268,6 +271,7 @@ def parser(options) # rubocop:enable Metrics/AbcSize # rubocop:enable MethodLength # rubocop:enable CyclomaticComplexity + # rubocop:enable PerceivedComplexity def add_tag_filter(options, filter_type, tag_name, value=true) (options[filter_type] ||= {})[tag_name] = value @@ -281,39 +285,5 @@ def configure_only_failures(options) options[:only_failures] = true add_tag_filter(options, :inclusion_filter, :last_run_status, 'failed') end - - def initialize_project_and_exit - RSpec::Support.require_rspec_core "project_initializer" - ProjectInitializer.new.run - exit - end - - def bisect_and_exit(argument) - RSpec::Support.require_rspec_core "bisect/coordinator" - - success = Bisect::Coordinator.bisect_with( - original_args, - RSpec.configuration, - bisect_formatter_for(argument) - ) - - exit(success ? 0 : 1) - end - - def bisect_formatter_for(argument) - return Formatters::BisectDebugFormatter if argument == "verbose" - Formatters::BisectProgressFormatter - end - - def print_version_and_exit - puts RSpec::Core::Version::STRING - exit - end - - def print_help_and_exit(parser, invalid_options) - # Removing the blank invalid options from the output. - puts parser.to_s.gsub(/^\s+(#{invalid_options.join('|')})\s*$\n/, '') - exit - end end end diff --git a/lib/rspec/core/runner.rb b/lib/rspec/core/runner.rb index 2c1736f5ae..43a407f2f3 100644 --- a/lib/rspec/core/runner.rb +++ b/lib/rspec/core/runner.rb @@ -65,17 +65,11 @@ def self.run(args, err=$stderr, out=$stdout) trap_interrupt options = ConfigurationOptions.new(args) - if options.options[:drb] - require 'rspec/core/drb' - begin - DRbRunner.new(options).run(err, out) - return - rescue DRb::DRbConnError - err.puts "No DRb server is running. Running in local process instead ..." - end + if options.options[:runner] + options.options[:runner].call(options, err, out) + else + new(options).run(err, out) end - - new(options).run(err, out) end def initialize(options, configuration=RSpec.configuration, world=RSpec.world) diff --git a/lib/rspec/core/version.rb b/lib/rspec/core/version.rb index bd6224734e..c4667b1e56 100644 --- a/lib/rspec/core/version.rb +++ b/lib/rspec/core/version.rb @@ -3,7 +3,7 @@ module Core # Version information for RSpec Core. module Version # Current version of RSpec Core, in semantic versioning format. - STRING = '3.5.0.beta4' + STRING = '3.5.0' end end end diff --git a/script/clone_all_rspec_repos b/script/clone_all_rspec_repos index 7cd818fab7..052d37f0c6 100755 --- a/script/clone_all_rspec_repos +++ b/script/clone_all_rspec_repos @@ -1,5 +1,5 @@ #!/bin/bash -# This file was generated on 2016-05-11T21:32:01+10:00 from the rspec-dev repo. +# This file was generated on 2016-06-14T22:22:14-07:00 from the rspec-dev repo. # DO NOT modify it by hand as your changes will get lost the next time it is generated. set -e diff --git a/script/functions.sh b/script/functions.sh index ba69cb7616..809d71e56e 100644 --- a/script/functions.sh +++ b/script/functions.sh @@ -1,4 +1,4 @@ -# This file was generated on 2016-05-11T21:32:01+10:00 from the rspec-dev repo. +# This file was generated on 2016-06-14T22:22:14-07:00 from the rspec-dev repo. # DO NOT modify it by hand as your changes will get lost the next time it is generated. SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" diff --git a/script/predicate_functions.sh b/script/predicate_functions.sh index ae1d724a9b..08fb024587 100644 --- a/script/predicate_functions.sh +++ b/script/predicate_functions.sh @@ -1,4 +1,4 @@ -# This file was generated on 2016-05-11T21:32:01+10:00 from the rspec-dev repo. +# This file was generated on 2016-06-14T22:22:14-07:00 from the rspec-dev repo. # DO NOT modify it by hand as your changes will get lost the next time it is generated. function is_mri { diff --git a/script/run_build b/script/run_build index a92ef8e022..6c5c48bc7c 100755 --- a/script/run_build +++ b/script/run_build @@ -1,5 +1,5 @@ #!/bin/bash -# This file was generated on 2016-05-11T21:32:01+10:00 from the rspec-dev repo. +# This file was generated on 2016-06-14T22:22:14-07:00 from the rspec-dev repo. # DO NOT modify it by hand as your changes will get lost the next time it is generated. set -e diff --git a/script/travis_functions.sh b/script/travis_functions.sh index 7b1178be69..f2d2bc8023 100644 --- a/script/travis_functions.sh +++ b/script/travis_functions.sh @@ -1,4 +1,4 @@ -# This file was generated on 2016-05-11T21:32:01+10:00 from the rspec-dev repo. +# This file was generated on 2016-06-14T22:22:14-07:00 from the rspec-dev repo. # DO NOT modify it by hand as your changes will get lost the next time it is generated. # Taken from: diff --git a/spec/integration/bisect_spec.rb b/spec/integration/bisect_spec.rb index 6d673f3418..8d848a0cd6 100644 --- a/spec/integration/bisect_spec.rb +++ b/spec/integration/bisect_spec.rb @@ -10,11 +10,10 @@ module RSpec::Core def bisect(cli_args, expected_status=nil) RSpec.configuration.output_stream = formatter_output - parser = Parser.new(cli_args + ["--bisect"]) - expect(parser).to receive(:exit).with(expected_status) if expected_status expect { - parser.parse + status = RSpec::Core::Runner.run(cli_args + ["--bisect"]) + expect(status).to eq(expected_status) if expected_status }.to avoid_outputting.to_stdout_from_any_process.and avoid_outputting.to_stderr_from_any_process normalize_durations(formatter_output.string) diff --git a/spec/rspec/core/bisect/runner_spec.rb b/spec/rspec/core/bisect/runner_spec.rb index 5f06e3dc6d..d8b32f8d15 100644 --- a/spec/rspec/core/bisect/runner_spec.rb +++ b/spec/rspec/core/bisect/runner_spec.rb @@ -167,6 +167,13 @@ def repro_command_from(ids) expect(cmd).to include("--seed 1234").and exclude("spec/unit ") end + it 'includes the original SPEC_OPTS but excludes the --bisect flag' do + with_env_vars('SPEC_OPTS' => '--bisect --seed 1234') do + cmd = repro_command_from(%w[ ./spec/unit/1_spec.rb[1:1] ]) + expect(cmd).to include('SPEC_OPTS="--seed 1234"').and exclude("--bisect") + end + end + it 'includes original options that `command_for` excludes' do original_cli_args << "--format" << "progress" expect(runner.command_for(%w[ ./foo.rb[1:1] ])).to exclude("--format progress") @@ -213,10 +220,39 @@ def repro_command_from(ids) open3_method = Open3.respond_to?(:capture2e) ? :capture2e : :popen3 open3_method = :popen3 if RSpec::Support::Ruby.jruby? + def called_environment + @called_environment + end + + if open3_method == :capture2e + RSpec::Matchers.define :invoke_command_with_env do |command, environment| + match do |block| + block.call + + expect(Open3).to have_received(open3_method).with(environment, command) + end + + supports_block_expectations + end + elsif open3_method == :popen3 + RSpec::Matchers.define :invoke_command_with_env do |command, environment| + match do |block| + block.call + + expect(Open3).to have_received(open3_method).with(command) + expect(called_environment).to include(environment) + end + + supports_block_expectations + end + end + before do - allow(Open3).to receive(open3_method).and_return( + allow(Open3).to receive(open3_method) do + @called_environment = ENV.to_hash.dup [double("Exit Status"), double("Stdout/err")] - ) + end + allow(server).to receive(:capture_run_results) do |&block| block.call "the results" @@ -224,8 +260,35 @@ def repro_command_from(ids) end it "runs the suite with the original CLI options" do - runner.original_results - expect(Open3).to have_received(open3_method).with(a_string_including("--seed 1234")) + expect { + runner.original_results + }.to invoke_command_with_env(a_string_including("--seed 1234"), {}) + end + + context 'when --bisect is present in SPEC_OPTS' do + it "runs the suite with --bisect removed from the environment" do + expect { + with_env_vars 'SPEC_OPTS' => '--bisect --fail-fast' do + runner.original_results + end + }.to invoke_command_with_env( + a_string_including("--seed 1234"), + { 'SPEC_OPTS' => '--fail-fast' } + ) + end + end + + context 'when --bisect=verbose is present in SPEC_OPTS' do + it "runs the suite with --bisect removed from the environment" do + expect { + with_env_vars 'SPEC_OPTS' => '--bisect=verbose --fail-fast' do + runner.original_results + end + }.to invoke_command_with_env( + a_string_including("--seed 1234"), + { 'SPEC_OPTS' => '--fail-fast' } + ) + end end it 'returns the run results' do diff --git a/spec/rspec/core/invocations_spec.rb b/spec/rspec/core/invocations_spec.rb new file mode 100644 index 0000000000..a6efd64de5 --- /dev/null +++ b/spec/rspec/core/invocations_spec.rb @@ -0,0 +1,172 @@ +require 'rspec/core/drb' +require 'rspec/core/bisect/coordinator' +require 'rspec/core/project_initializer' + +module RSpec::Core + RSpec.describe Invocations do + let(:configuration_options) { instance_double(ConfigurationOptions) } + let(:err) { StringIO.new } + let(:out) { StringIO.new } + + def run_invocation + subject.call(configuration_options, err, out) + end + + describe Invocations::InitializeProject do + it "initializes a project and returns a 0 exit code" do + project_init = instance_double(ProjectInitializer, :run => nil) + allow(ProjectInitializer).to receive_messages(:new => project_init) + + exit_code = run_invocation + + expect(project_init).to have_received(:run) + expect(exit_code).to eq(0) + end + end + + describe Invocations::DRbWithFallback do + context 'when a DRb server is running' do + it "builds a DRbRunner and runs the specs" do + drb_proxy = instance_double(RSpec::Core::DRbRunner, :run => 0) + allow(RSpec::Core::DRbRunner).to receive(:new).and_return(drb_proxy) + + exit_code = run_invocation + + expect(drb_proxy).to have_received(:run).with(err, out) + expect(exit_code).to eq(0) + end + end + + context 'when a DRb server is not running' do + let(:runner) { instance_double(RSpec::Core::Runner, :run => 0) } + + before(:each) do + allow(RSpec::Core::Runner).to receive(:new).and_return(runner) + allow(RSpec::Core::DRbRunner).to receive(:new).and_raise(DRb::DRbConnError) + end + + it "outputs a message" do + run_invocation + + expect(err.string).to include( + "No DRb server is running. Running in local process instead ..." + ) + end + + it "builds a runner instance and runs the specs" do + run_invocation + + expect(RSpec::Core::Runner).to have_received(:new).with(configuration_options) + expect(runner).to have_received(:run).with(err, out) + end + + if RSpec::Support::RubyFeatures.supports_exception_cause? + it "prevents the DRb error from being listed as the cause of expectation failures" do + allow(RSpec::Core::Runner).to receive(:new) do |configuration_options| + raise RSpec::Expectations::ExpectationNotMetError + end + + expect { + run_invocation + }.to raise_error(RSpec::Expectations::ExpectationNotMetError) do |e| + expect(e.cause).to be_nil + end + end + end + end + end + + describe Invocations::Bisect do + let(:bisect) { nil } + let(:options) { { :bisect => bisect } } + let(:args) { double(:args) } + let(:success) { true } + + before do + allow(configuration_options).to receive_messages(:args => args, :options => options) + allow(RSpec::Core::Bisect::Coordinator).to receive(:bisect_with).and_return(success) + end + + it "starts the bisection coordinator" do + run_invocation + + expect(RSpec::Core::Bisect::Coordinator).to have_received(:bisect_with).with( + args, + RSpec.configuration, + Formatters::BisectProgressFormatter + ) + end + + context "when the bisection is successful" do + it "returns 0" do + exit_code = run_invocation + + expect(exit_code).to eq(0) + end + end + + context "when the bisection is unsuccessful" do + let(:success) { false } + + it "returns 1" do + exit_code = run_invocation + + expect(exit_code).to eq(1) + end + end + + context "and the verbose option is specified" do + let(:bisect) { "verbose" } + + it "starts the bisection coordinator with the debug formatter" do + run_invocation + + expect(RSpec::Core::Bisect::Coordinator).to have_received(:bisect_with).with( + args, + RSpec.configuration, + Formatters::BisectDebugFormatter + ) + end + end + end + + describe Invocations::PrintVersion do + it "prints the version and returns a zero exit code" do + + exit_code = run_invocation + + expect(exit_code).to eq(0) + expect(out.string).to include("#{RSpec::Core::Version::STRING}\n") + end + end + + describe Invocations::PrintHelp do + let(:parser) { instance_double(OptionParser) } + let(:invalid_options) { %w[ -d ] } + + subject { described_class.new(parser, invalid_options) } + + before do + allow(parser).to receive(:to_s).and_return(<<-EOS) + -d + --bisect[=verbose] Repeatedly runs the suite in order... + EOS + end + + it "prints the CLI options and returns a zero exit code" do + exit_code = run_invocation + + expect(exit_code).to eq(0) + expect(out.string).to include("--bisect") + end + + it "won't display invalid options in the help output" do + useless_lines = /^\s*-d\s*$\n/ + + run_invocation + + expect(out.string).to_not match(useless_lines) + end + end + end +end diff --git a/spec/rspec/core/option_parser_spec.rb b/spec/rspec/core/option_parser_spec.rb index ea548e1e72..b151a05c9c 100644 --- a/spec/rspec/core/option_parser_spec.rb +++ b/spec/rspec/core/option_parser_spec.rb @@ -1,4 +1,5 @@ -require 'rspec/core/project_initializer' +require 'rspec/core/drb' +require 'rspec/core/bisect/coordinator' module RSpec::Core RSpec.describe OptionParser do @@ -67,42 +68,55 @@ module RSpec::Core end end - it "won't display invalid options in the help output" do - def generate_help_text - parser = Parser.new(["--help"]) - allow(parser).to receive(:exit) - parser.parse - end + %w[ -h --help ].each do |option| + it 'sets the `:runner` option with the `PrintHelp` invocation' do + parser = Parser.new([option]) - useless_lines = /^\s*--?\w+\s*$\n/ + options = parser.parse - expect { generate_help_text }.to_not output(useless_lines).to_stdout + expect(options[:runner]).to be_instance_of(RSpec::Core::Invocations::PrintHelp) + end end %w[ -v --version ].each do |option| describe option do - it "prints the version and exits" do + it 'sets the `:runner` option with the `PrintVersion` invocation' do parser = Parser.new([option]) - expect(parser).to receive(:exit) - expect { - parser.parse - }.to output("#{RSpec::Core::Version::STRING}\n").to_stdout + options = parser.parse + + expect(options[:runner]).to be_instance_of(RSpec::Core::Invocations::PrintVersion) end end end - describe "--init" do - it "initializes a project and exits" do - project_init = instance_double(ProjectInitializer) - allow(ProjectInitializer).to receive_messages(:new => project_init) + %w[ -X --drb ].each do |option| + describe option do + let(:parser) { Parser.new([option]) } + + it 'sets the `:drb` option to true' do + options = parser.parse + expect(options[:drb]).to be_truthy + end + + it 'sets the `:runner` option with the `DrbWithFallback` invocation' do + options = parser.parse + + expect(options[:runner]).to be_instance_of(RSpec::Core::Invocations::DRbWithFallback) + end + end + end + + describe '--init' do + let(:initialize_project) { double(:initialize_project) } + + it 'sets the `:runner` option with the `InitializeProject` invocation' do parser = Parser.new(["--init"]) - expect(project_init).to receive(:run).ordered - expect(parser).to receive(:exit).ordered + options = parser.parse - parser.parse + expect(options[:runner]).to be_instance_of(RSpec::Core::Invocations::InitializeProject) end end @@ -317,6 +331,22 @@ def generate_help_text end end + describe "--bisect" do + it "sets the `:bisect` option" do + options = Parser.parse(%w[ --bisect ]) + + expect(options[:bisect]).to be_truthy + end + + it "sets the `:runner` option with the `Bisect` invocation" do + parser = Parser.new(['--bisect']) + + options = parser.parse + + expect(options[:runner]).to be_instance_of(RSpec::Core::Invocations::Bisect) + end + end + describe '--profile' do it 'sets profile_examples to true by default' do options = Parser.parse(%w[--profile]) diff --git a/spec/rspec/core/runner_spec.rb b/spec/rspec/core/runner_spec.rb index 66cf0668fc..afd4b4ed14 100644 --- a/spec/rspec/core/runner_spec.rb +++ b/spec/rspec/core/runner_spec.rb @@ -255,71 +255,39 @@ def interrupt end describe ".run" do + let(:args) { double(:args) } let(:err) { StringIO.new } let(:out) { StringIO.new } + let(:options) { { } } + let(:configuration_options) { double(:configuration_options, :options => options) } - context "with --drb or -X" do - before(:each) do - @options = RSpec::Core::ConfigurationOptions.new(%w[--drb --drb-port 8181 --color]) - allow(RSpec::Core::ConfigurationOptions).to receive(:new) { @options } - end - - def run_specs - RSpec::Core::Runner.run(%w[ --drb ], err, out) - end + before(:each) do + allow(RSpec::Core::ConfigurationOptions).to receive(:new).and_return(configuration_options) + end - context 'and a DRb server is running' do - it "builds a DRbRunner and runs the specs" do - drb_proxy = double(RSpec::Core::DRbRunner, :run => true) - expect(drb_proxy).to receive(:run).with(err, out) + context 'when the options contain a runner callable' do + let(:runner) { double(:runner, :call => nil) } + let(:options) { { :runner => runner } } - expect(RSpec::Core::DRbRunner).to receive(:new).and_return(drb_proxy) + it 'invokes the runner callable' do + RSpec::Core::Runner.run([], err, out) - run_specs - end + expect(runner).to have_received(:call).with(configuration_options, err, out) end + end - context 'and a DRb server is not running' do - before(:each) do - expect(RSpec::Core::DRbRunner).to receive(:new).and_raise(DRb::DRbConnError) - end - - it "outputs a message" do - allow(RSpec.configuration).to receive(:files_to_run) { [] } - expect(err).to receive(:puts).with( - "No DRb server is running. Running in local process instead ..." - ) - run_specs - end - - it "builds a runner instance and runs the specs" do - process_proxy = double(RSpec::Core::Runner, :run => 0) - expect(process_proxy).to receive(:run).with(err, out) - - expect(RSpec::Core::Runner).to receive(:new).and_return(process_proxy) + context 'when no runner callable is set' do + it 'instantiates a Runner instance and runs it' do + process_proxy = double(RSpec::Core::Runner, :run => 0) + allow(RSpec::Core::Runner).to receive(:new).and_return(process_proxy) - run_specs - end + RSpec::Core::Runner.run([], err, out) - if RSpec::Support::RubyFeatures.supports_exception_cause? - it "prevents the DRb error from being listed as the cause of expectation failures" do - subclass = Class.new(RSpec::Core::Runner) do - include RSpec::Matchers - - def run(err, out) - expect(1).to eq(2) - end - end - - expect { - subclass.run([], StringIO.new, StringIO.new) - }.to raise_error(RSpec::Expectations::ExpectationNotMetError) do |e| - expect(e.cause).to be_nil - end - end - end + expect(RSpec::Core::Runner).to have_received(:new) + expect(process_proxy).to have_received(:run).with(err, out) end end + end context "when run" do