Skip to content

Commit

Permalink
Add bisect formatter and server.
Browse files Browse the repository at this point in the history
  • Loading branch information
myronmarston committed Apr 6, 2015
1 parent a3a929c commit ebc8615
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 0 deletions.
46 changes: 46 additions & 0 deletions lib/rspec/core/bisect/server.rb
@@ -0,0 +1,46 @@
require 'drb/drb'

module RSpec
module Core
# @private
module Bisect
# @private
# A DRb server that receives run results from a separate RSpec process
# started by the bisect process.
class Server
def self.run
server = new
server.start
yield server
ensure
server.stop
end

def capture_run_results(abort_after_example_id=nil)
self.abort_after_example_id = abort_after_example_id
yield
latest_run_results
end

def start
# We pass `nil` as the first arg to allow it to pick a DRb port.
@drb = DRb.start_service(nil, self)
end

def stop
@drb.stop_service
end

def drb_port
@drb_port ||= Integer(@drb.uri[/\d+$/])
end

# Fetched via DRb by the BisectFormatter to determine when to abort.
attr_accessor :abort_after_example_id

# Set via DRb by the BisectFormatter with the results of the run.
attr_accessor :latest_run_results
end
end
end
end
3 changes: 3 additions & 0 deletions lib/rspec/core/formatters.rb
Expand Up @@ -71,6 +71,7 @@ module RSpec::Core::Formatters
autoload :ProgressFormatter, 'rspec/core/formatters/progress_formatter'
autoload :ProfileFormatter, 'rspec/core/formatters/profile_formatter'
autoload :JsonFormatter, 'rspec/core/formatters/json_formatter'
autoload :BisectFormatter, 'rspec/core/formatters/bisect_formatter'

# Register the formatter class
# @param formatter_class [Class] formatter class to register
Expand Down Expand Up @@ -186,6 +187,8 @@ def built_in_formatter(key)
ProgressFormatter
when 'j', 'json'
JsonFormatter
when 'bisect'
BisectFormatter
end
end

Expand Down
66 changes: 66 additions & 0 deletions lib/rspec/core/formatters/bisect_formatter.rb
@@ -0,0 +1,66 @@
require 'drb/drb'

module RSpec
module Core
module Formatters
# Used by `--bisect`. When it shells out and runs a portion of the suite, it uses
# this formatter as a means to have the status reported back to it, via DRb.
#
# Note that since DRb calls carry considerable overhead compared to normal
# method calls, we try to minimize the number of DRb calls for perf reasons,
# opting to communicate only at the start and the end of the run, rather than
# after each example.
# @private
class BisectFormatter
Formatters.register self, :start, :start_dump, :example_started,
:example_failed, :example_passed, :example_pending

def initialize(_output)
port = RSpec.configuration.drb_port
drb_uri = "druby://127.0.0.1:#{port}"
@all_example_ids = []
@failed_example_ids = []
@bisect_server = DRbObject.new_with_uri(drb_uri)
@abort_after_id = nil
end

def start(_notification)
@abort_after_id = @bisect_server.abort_after_example_id
end

def example_started(notification)
@all_example_ids << notification.example.id
end

def example_failed(notification)
@failed_example_ids << notification.example.id
example_finished(notification)
end

def example_passed(notification)
example_finished(notification)
end

def example_pending(notification)
example_finished(notification)
end

def start_dump(_notification)
@bisect_server.latest_run_results = RunResults.new(
@all_example_ids, @failed_example_ids
)
end

RunResults = Struct.new(:all_example_ids_in_execution_order,
:failed_example_ids)

private

def example_finished(notification)
return unless notification.example.id == @abort_after_id
RSpec.world.wants_to_quit = true
end
end
end
end
end
90 changes: 90 additions & 0 deletions spec/rspec/core/bisect/server_spec.rb
@@ -0,0 +1,90 @@
require 'rspec/core/bisect/server'
require 'support/formatter_support'

module RSpec::Core
RSpec.describe Bisect::Server do
RSpec::Matchers.define :have_running_server do
match do |drb|
begin
drb.current_server.alive?
rescue DRb::DRbServerNotFound
false
end
end
end

it 'always stops the server, even if an error occurs while yielding' do
expect(DRb).not_to have_running_server

expect {
Bisect::Server.run do
expect(DRb).to have_running_server
raise "boom"
end
}.to raise_error("boom")

expect(DRb).not_to have_running_server
end

context "when used in combination with the BisectFormatter", :slow do
include FormatterSupport

attr_reader :server

around do |ex|
Bisect::Server.run do |the_server|
@server = the_server
ex.run
end
end

def run_formatter_specs
RSpec.configuration.drb_port = server.drb_port
run_example_specs_with_formatter("bisect")
end

it 'receives suite results' do
results = server.capture_run_results do
run_formatter_specs
end

expect(results).to have_attributes(
:all_example_ids_in_execution_order => %w[
./spec/rspec/core/resources/formatter_specs.rb[1:1]
./spec/rspec/core/resources/formatter_specs.rb[2:1:1]
./spec/rspec/core/resources/formatter_specs.rb[2:2:1]
./spec/rspec/core/resources/formatter_specs.rb[3:1]
./spec/rspec/core/resources/formatter_specs.rb[4:1]
./spec/rspec/core/resources/formatter_specs.rb[5:1]
./spec/rspec/core/resources/formatter_specs.rb[5:2]
],
:failed_example_ids => %w[
./spec/rspec/core/resources/formatter_specs.rb[2:2:1]
./spec/rspec/core/resources/formatter_specs.rb[4:1]
./spec/rspec/core/resources/formatter_specs.rb[5:1]
./spec/rspec/core/resources/formatter_specs.rb[5:2]
]
)
end

it 'can abort the run early (e.g. when it is not interested in later examples)' do
results = server.capture_run_results("./spec/rspec/core/resources/formatter_specs.rb[2:2:1]") do
run_formatter_specs
end

expect(results).to have_attributes(
:all_example_ids_in_execution_order => %w[
./spec/rspec/core/resources/formatter_specs.rb[1:1]
./spec/rspec/core/resources/formatter_specs.rb[2:1:1]
./spec/rspec/core/resources/formatter_specs.rb[2:2:1]
],
:failed_example_ids => %w[
./spec/rspec/core/resources/formatter_specs.rb[2:2:1]
]
)
end

# TODO: test aborting after pending vs failed vs passing example if we keep this feature.
end
end
end

0 comments on commit ebc8615

Please sign in to comment.