Skip to content

Commit

Permalink
Initial commit for supporting async specs under rspec
Browse files Browse the repository at this point in the history
  • Loading branch information
adambeynon committed Nov 1, 2013
1 parent 31045e5 commit a95cd0b
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 10 deletions.
5 changes: 5 additions & 0 deletions opal/opal/rspec.rb
Expand Up @@ -9,11 +9,16 @@
require 'opal/rspec/text_formatter'
require 'opal/rspec/browser_formatter'
require 'opal/rspec/runner'
require 'opal/rspec/async'

RSpec.configure do |config|
# For now, always use our custom formatter for results
config.formatter = Opal::RSpec::Runner.default_formatter

# Async helpers for specs
config.include Opal::RSpec::AsyncHelpers
config.extend Opal::RSpec::AsyncDefinitions

# Always support expect() and .should syntax (we should not do this really..)
config.expect_with :rspec do |c|
c.syntax = [:should, :expect]
Expand Down
129 changes: 129 additions & 0 deletions opal/opal/rspec/async.rb
@@ -0,0 +1,129 @@
module Opal
module RSpec
module AsyncDefinitions
def async(desc, *args, &block)
options = ::RSpec::Core::Metadata.build_hash_from(args)
Opal::RSpec::AsyncExample.register(self, desc, options, block)
end
end

module AsyncHelpers
def run_async(&block)
::RSpec.current_example.continue_async(block)
end

def set_timeout(duration, &block)
`setTimeout(block, duration)`
self
end
end

class AsyncRunner
def initialize(runner, reporter, finish_block)
@runner = runner
@reporter = reporter
@finish_block = finish_block
end

def run
@examples = AsyncExample.examples.clone
run_next_example
end

def run_next_example
if @examples.empty?
finish
else
run_example @examples.pop
end
end

def run_example(example)
example_group = example.example_group

@reporter.example_group_started example_group
instance = example_group.new

example.run(instance, @reporter) do
example_finished example
end
end

def example_finished(example)
@reporter.example_group_finished example.example_group
run_next_example
end

# Called once all examples have finished. Just calls back to main
# runner to inform it
def finish
@finish_block.call
end
end

class AsyncExample < ::RSpec::Core::Example
def self.register(*args)
examples << new(*args)
end

def self.examples
@examples ||= []
end

def run(example_group_instance, reporter, &after_run_block)
@example_group_instance = example_group_instance
@reporter = reporter
@after_run_block = after_run_block

should_wait = true

::RSpec.current_example = self

start(reporter)

begin
run_before_each
@example_group_instance.instance_exec(self, &@example_block)
rescue Exception => e
set_exception(e)
should_wait = false
end

async_example_finished unless should_wait
end

def continue_async(block)
begin
block.call
rescue Exception => e
set_exception(e)
end

async_example_finished
end

def async_example_finished
begin
run_after_each
rescue Exception => e
set_exception(e)
ensure
@example_group_instance.instance_variables.each do |ivar|
@example_group_instance.instance_variable_set(ivar, nil)
end
@example_group_instance = nil

begin
assign_generated_description
rescue Exception => e
set_exception(e, "while assigning the example description")
end
end

finish(@reporter)
::RSpec.current_example = nil
@after_run_block.call
end
end
end
end
37 changes: 27 additions & 10 deletions opal/opal/rspec/runner.rb
Expand Up @@ -28,25 +28,42 @@ def autorun
end
end

def initialize(options={}, configuration=::RSpec::configuration, world=::RSpec::world)
def initialize(options = {})
@options = options
@configuration = configuration
@world = world
@world = ::RSpec.world
@configuration = ::RSpec.configuration
end

def run(err=$stdout, out=$stdout)
@configuration.error_stream = err
@configuration.output_stream ||= out

@configuration.reporter.report(@world.example_count) do |reporter|
begin
@configuration.run_hook(:before, :suite)
@world.example_groups.map {|g| g.run(reporter) }.all? ? 0 : @configuration.failure_exit_code
ensure
@configuration.run_hook(:after, :suite)
end
self.start
run_examples

run_async_examples do
self.finish
end
end

def run_examples
@world.example_groups.map { |g| g.run(@reporter) }.all?
end

def run_async_examples(&block)
AsyncRunner.new(self, @reporter, block).run
end

def start
@reporter = @configuration.reporter
@reporter.start(@world.example_count)
@configuration.run_hook(:before, :suite)
end

def finish
@configuration.run_hook(:after, :suite)
@reporter.finish
end
end
end
end
29 changes: 29 additions & 0 deletions spec/async_spec.rb
@@ -0,0 +1,29 @@
describe "Asynchronous helpers" do

let(:foo) { 100 }

before do
@model = Object.new
end

async "can run examples async" do
run_async do
1.should == 1
end
end

async "can access let() helpers and before() helpers" do
run_async do
foo.should eq(100)
@model.should be_kind_of(Object)
end
end

async "can finish running after a long delay" do
obj = [1, 2, 3, 4]

set_timeout 100 do
run_async { obj.should == [1, 2, 3, 4] }
end
end
end

0 comments on commit a95cd0b

Please sign in to comment.