Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Direct DRb #107

Merged
merged 8 commits into from

3 participants

@nevir

A fleshed out version of #104. Major changes since:

  • It just uses :cli (relying on Shellwords to do the parsing)
  • RSpec uses a consistent protocol over DRb for versions 1 & 2, so this approach opts to just talk directly via DRb - avoiding dealing with the different RSpec command helpers
  • Tests!
lib/guard/rspec/runner.rb
@@ -64,26 +64,63 @@ def rspec_class
private
- def rspec_command(paths, options)
+ def rspec_arguments(paths, opts)
@rymai Owner
rymai added a note

Could you use options instead of opts everywhere? Thanks!

@nevir
nevir added a note

Doh, yeah, I'll switch that over

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@rymai
Owner

Other than that, looks good to me! Great job thanks!

@thibaudgg
Owner

Sounds good to me, @rymai ok for you?

@nevir

Alright, updated w/ that change

@thibaudgg
Owner

Ok nice, just one last point before I merge it. Can you please update the README regarding this update so everybody can easily understand what happens. Thanks!

@nevir

Good call! Updated

@thibaudgg thibaudgg merged commit 71a111c into guard:master
@thibaudgg
Owner

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 25, 2012
  1. @nevir

    Direct DRB calls

    nevir authored
  2. @nevir

    In process DRb runner for RSpec

    nevir authored
  3. @nevir

    Reorg

    nevir authored
  4. @nevir

    Fix bad merge

    nevir authored
  5. @nevir
  6. @nevir

    Tests for the DRb runner

    nevir authored
  7. @nevir

    opts -> options

    nevir authored
  8. @nevir

    Readme update

    nevir authored
This page is out of date. Refresh to see the latest.
View
10 README.md
@@ -98,7 +98,7 @@ end
```
-Former `:color`, `:drb`, `:fail_fast` and `:formatter` options are thus deprecated and have no effect anymore.
+Former `:color`, `:drb`, `:fail_fast` and `:formatter` options are deprecated and have no effect anymore.
### List of available options:
@@ -116,6 +116,14 @@ Former `:color`, `:drb`, `:fail_fast` and `:formatter` options are thus deprecat
:spec_paths => ["spec"] # specify an array of paths that contain spec files
```
+### DRb mode
+
+When you specify `--drb` within `:cli`, guard-rspec will circumvent the `rspec` command line tool by
+directly communicating with the RSpec DRb server. This avoids the extra overhead incurred by your
+shell, bundler and loading RSpec's environment just to send a DRb message. It shaves off a
+second or two before the specs start to run; they should run almost immediately.
+
+
Notification
------------
View
94 lib/guard/rspec/runner.rb
@@ -23,21 +23,21 @@ def run(paths, options = {})
message = options[:message] || "Running: #{paths.join(' ')}"
UI.info(message, :reset => true)
- success = system(rspec_command(paths, @options.merge(options)))
+ options = @options.merge(options)
- if @options[:notification] && !drb_used? && !success && rspec_command_exited_with_an_exception?
- Notifier.notify("Failed", :title => "RSpec results", :image => :failed, :priority => 2)
+ if drb_used?
+ run_via_drb(paths, options)
+ else
+ run_via_shell(paths, options)
end
-
- success
end
def rspec_version
@rspec_version ||= @options[:version] || determine_rspec_version
end
- def rspec_exec
- @rspec_exec ||= begin
+ def rspec_executable
+ @rspec_executable ||= begin
exec = rspec_class.downcase
binstubs? ? "bin/#{exec}" : exec
end
@@ -47,7 +47,7 @@ def failure_exit_code_supported?
@failure_exit_code_supported ||= begin
cmd_parts = []
cmd_parts << "bundle exec" if bundler?
- cmd_parts << rspec_exec
+ cmd_parts << rspec_executable
cmd_parts << "--help"
`#{cmd_parts.join(' ')}`.include? "--failure-exit-code"
end
@@ -64,26 +64,63 @@ def rspec_class
private
+ def rspec_arguments(paths, options)
+ arg_parts = []
+ arg_parts << options[:cli]
+ arg_parts << "-f progress" if !options[:cli] || options[:cli].split(/[\s=]/).none? { |w| %w[-f --format].include?(w) }
+ if @options[:notification]
+ arg_parts << "-r #{File.dirname(__FILE__)}/formatters/notification_#{rspec_class.downcase}.rb"
+ arg_parts << "-f Guard::RSpec::Formatter::Notification#{rspec_class}#{rspec_version == 1 ? ":" : " --out "}/dev/null"
+ end
+ arg_parts << "--failure-exit-code #{FAILURE_EXIT_CODE}" if failure_exit_code_supported?
+ arg_parts << paths.join(' ')
+
+ arg_parts.compact.join(' ')
+ end
+
def rspec_command(paths, options)
cmd_parts = []
cmd_parts << "rvm #{@options[:rvm].join(',')} exec" if @options[:rvm].respond_to?(:join)
cmd_parts << "bundle exec" if bundler?
- cmd_parts << rspec_exec << options[:cli]
- cmd_parts << "-f progress" if !options[:cli] || options[:cli].split(/[\s=]/).none? { |w| %w[-f --format].include?(w) }
- if @options[:notification]
- cmd_parts << "-r #{File.dirname(__FILE__)}/formatters/notification_#{rspec_class.downcase}.rb"
- cmd_parts << "-f Guard::RSpec::Formatter::Notification#{rspec_class}#{rspec_version == 1 ? ":" : " --out "}/dev/null"
- end
- cmd_parts << "--failure-exit-code #{FAILURE_EXIT_CODE}" if failure_exit_code_supported?
- cmd_parts << paths.join(' ')
+ cmd_parts << rspec_executable
+ cmd_parts << rspec_arguments(paths, options)
cmd_parts.compact.join(' ')
end
+ def run_via_shell(paths, options)
+ success = system(rspec_command(paths, options))
+
+ if @options[:notification] && !drb_used? && !success && rspec_command_exited_with_an_exception?
+ Notifier.notify("Failed", :title => "RSpec results", :image => :failed, :priority => 2)
+ end
+
+ success
+ end
+
def rspec_command_exited_with_an_exception?
failure_exit_code_supported? && $?.exitstatus != FAILURE_EXIT_CODE
end
+ # We can optimize this path by hitting up the drb server directly, circumventing the overhead
+ # of the user's shell, bundler and ruby environment.
+ def run_via_drb(paths, options)
+ require "shellwords"
+ argv = rspec_arguments(paths, options).shellsplit
+
+ # The user can specify --drb-port for rspec, we need to honor it.
+ if idx = argv.index("--drb-port")
+ port = argv[idx + 1].to_i
+ end
+ port = ENV["RSPEC_DRB"] || 8989 unless port && port > 0
+
+ ret = drb_service(port.to_i).run(argv, $stderr, $stdout)
+ ret == 0
+ rescue DRb::DRbConnError
+ # Fall back to the shell runner; we don't want to mangle the environment!
+ run_via_shell(paths, options)
+ end
+
def drb_used?
if @drb_used.nil?
@drb_used = @options[:cli] && @options[:cli].include?('--drb')
@@ -92,6 +129,31 @@ def drb_used?
end
end
+ # RSpec 1 & 2 use the same DRb call signature, and we can avoid loading a large chunk of rspec
+ # just to let DRb know what to do.
+ #
+ # For reference:
+ #
+ # * RSpec 1: https://github.com/myronmarston/rspec-1/blob/master/lib/spec/runner/drb_command_line.rb
+ # * RSpec 2: https://github.com/rspec/rspec-core/blob/master/lib/rspec/core/drb_command_line.rb
+ def drb_service(port)
+ require "drb/drb"
+
+ # Make sure we have a listener running
+ unless @drb_listener_running
+ begin
+ DRb.start_service("druby://localhost:0")
+ rescue SocketError, Errno::EADDRNOTAVAIL
+ DRb.start_service("druby://:0")
+ end
+
+ @drb_listener_running = true
+ end
+
+ @drb_services ||= {}
+ @drb_services[port.to_i] ||= DRbObject.new_with_uri("druby://127.0.0.1:#{port}")
+ end
+
def bundler_allowed?
if @bundler_allowed.nil?
@bundler_allowed = File.exist?("#{Dir.pwd}/Gemfile")
View
38 spec/guard/rspec/runner_spec.rb
@@ -75,12 +75,48 @@
context 'using DRb' do
subject { described_class.new(:cli => '--drb') }
+ let(:service) {
+ service_double = double
+ subject.should_receive(:drb_service) { |port|
+ service_double.stub(:port) { port }
+ service_double
+ }
+
+ service_double
+ }
+
it 'does not notify notifies when RSpec fails to execute and using drb' do
- subject.should_receive(:rspec_command) { "`exit 1`" }
+ service.should_receive(:run) { 1 }
Guard::Notifier.should_not_receive(:notify)
subject.run(['spec'])
end
+
+ it 'should fall back to the command runner with an inactive server' do
+ service.should_receive(:run).and_raise(DRb::DRbConnError)
+ subject.should_receive(:run_via_shell)
+
+ subject.run(['spec'])
+ end
+
+ it 'should default to DRb port 8989' do
+ service.should_receive(:run) { 0 }
+ subject.run(['spec'])
+ service.port.should == 8989
+ end
+
+ it 'should honor RSPEC_DRB' do
+ ENV['RSPEC_DRB'] = '12345'
+ service.should_receive(:run) { 0 }
+ subject.run(['spec'])
+ service.port.should == 12345
+ end
+
+ it 'should honor --drb-port' do
+ service.should_receive(:run) { 0 }
+ subject.run(['spec'], cli: '--drb --drb-port 2222')
+ service.port.should == 2222
+ end
end
it 'does not notify that RSpec failed when the specs failed' do
Something went wrong with that request. Please try again.