Skip to content

Commit

Permalink
Merge pull request #25 from opal/async-subject
Browse files Browse the repository at this point in the history
Promise Based Async Support – reprise (thanks to @wied03!)
elia committed Jul 28, 2015
2 parents 1670c91 + 4bc9d8e commit d6464d4
Showing 32 changed files with 1,449 additions and 607 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.DS_Store
Gemfile.lock
*.gem
Gemfile.lock
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## 0.5.0 (edge)

* By default, any subject, it example block, before(:each), after(:each), and around that returns a promise will be executed asynchronously. Async is NOT yet supported for context level hooks.

* Update to RSpec 3.1 (core is 3.1.7, expectations/support 3.1.2, mocks 3.1.3)

* Remove copy of source (and just rely on git submodule fetch)
6 changes: 0 additions & 6 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -5,11 +5,5 @@ unless Dir['rspec{,-{core,expectations,mocks,support}}'].any?
warn 'Run: "git submodule update --init" to get RSpec sources'
end

gem 'rspec', path: 'rspec'
gem 'rspec-support', path: 'rspec-support'
gem 'rspec-core', path: 'rspec-core'
gem 'rspec-mocks', path: 'rspec-mocks'
gem 'rspec-expectations', path: 'rspec-expectations'

# Opal 0.9 still in development
# gem 'opal', git: 'https://github.com/opal/opal.git'
54 changes: 40 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -62,28 +62,54 @@ describe MyClass do
end

# async example
async 'does something else, too' do
# ...
it 'does something else, too' do
promise = Promise.new
delay 1 do
expect(:foo).to eq(:foo)
promise.resolve
end
promise
end

it 'does another thing' do
# Argument is number of seconds, delay_with_promise is a convenience method that will
# call setTimeout with the block and return a promise
delay_with_promise 0 do
expect(:foo).to eq(:foo)
end
end
end
```

This just marks the example as running async. To actually handle the async result,
you also need to use a `run_async` call inside some future handler:
describe MyClass2 do
# will wait for the before promise to complete before proceeding
before do
delay_with_promise 0 do
puts 'async before 'action
end
end

# async subject works too
subject do
delay_with_promise 0 do
42
end
end

it { is_expected.to eq 42 }

```ruby
async 'HTTP requests should work' do
HTTP.get('/users/1.json') do |res|
run_async {
expect(res).to be_ok
}
# If you use an around block and have async specs, you must use this approach
around do |example|
puts 'do stuff before'
example.run.then do
puts 'do stuff after example'
end
end
end
```

The block passed to `run_async` informs the runner that this spec is finished
so it can move on. Any failures/expectations run inside this block will be run
in the context of the example.
Limitations:
* Right now, async before(:context) and after(:context) hooks cannot be async
* let dependencies cannot be async, only subject

## Contributing

110 changes: 109 additions & 1 deletion Rakefile
Original file line number Diff line number Diff line change
@@ -4,10 +4,118 @@ Bundler.require
Bundler::GemHelper.install_tasks

require 'opal/rspec/rake_task'
Opal::RSpec::RakeTask.new(:default)
Opal::RSpec::RakeTask.new(:specs_untested)

desc 'Generates an RSpec requires file free of dynamic requires'
task :generate_requires do
# Do this free of any requires used to make this Rake task happen
sh 'ruby -Irspec/lib -Irspec-core/lib/rspec -Irspec-support/lib/rspec util/create_requires.rb'
end

task :default do
test_output = `rake specs_untested`
raise "Expected test runner to fail due to failed tests, but got return code of #{$?.exitstatus}" if $?.success?
count_match = /(\d+) examples, (\d+) failures, (\d+) pending/.match(test_output)
raise 'Expected a finished count of test failures/success/etc. but did not see it' unless count_match
total, failed, pending = count_match.captures

actual_failures = []
test_output.scan /\d+\) (.*)/ do |match|
actual_failures << match[0].strip
end
actual_failures.sort!

failure_messages = []

bad_strings = [/.*is still running, after block problem.*/,
/.*should not have.*/,
/.*Expected \d+ after hits but got \d+.*/,
/.*Expected \d+ around hits but got \d+.*/]

bad_strings.each do |regex|
test_output.scan(regex) do |match|
failure_messages << "Expected not to see #{regex} in output, but found match #{match}"
end
end

expected_pending_count = 22

expected_failures= ['promise should make example fail properly before async block reached',
'promise matcher fails properly',
'promise non-assertion failure in promise no args',
'promise non-assertion failure in promise string arg',
'promise non-assertion failure in promise exception arg',
'pending in example no promise would not fail otherwise, thus fails properly',
'async/sync mix fails properly if a sync test is among async tests',
'async/sync mix can finish running after a long delay and fail properly',
'be_truthy fails properly with truthy values',
'subject sync unnamed assertion fails properly should eq 43',
'subject sync unnamed fails properly during subject create',
'subject async assertion implicit fails properly should eq 43',
'subject async fails properly during creation explicit async',
'subject async fails properly during creation implicit usage',
'subject async assertion explicit async fails properly',
'hooks around sync fails after example should equal 42',
'hooks around sync fails before example',
'hooks around async fails after example after(:each) async fails sync match passes',
'hooks around async fails after example after(:each) async passes sync match passes',
'hooks around async fails after example after(:each) sync fails sync match passes',
'hooks around async fails after example after(:each) sync passes sync match passes',
'hooks around async fails after example before(:each) fails should not reach the example',
'hooks around async fails after example matches another async match',
'hooks around async fails after example matches async match',
'hooks around async fails after example matches async match fails properly',
'hooks around async fails after example matches sync fails properly',
'hooks around async fails after example matches sync match',
'hooks around async fails before example after(:each) async fails sync match passes',
'hooks around async fails before example after(:each) async passes sync match passes',
'hooks around async fails before example after(:each) sync fails sync match passes',
'hooks around async fails before example after(:each) sync passes sync match passes',
'hooks around async fails before example before(:each) fails should not reach the example',
'hooks around async fails before example matches another async match',
'hooks around async fails before example matches async match',
'hooks around async fails before example matches async match fails properly',
'hooks around async fails before example matches sync fails properly',
'hooks around async fails before example matches sync match',
'hooks around async succeeds after(:each) async fails sync match passes',
'hooks around async succeeds after(:each) sync fails sync match passes',
'hooks around async succeeds before(:each) fails should not reach the example',
'hooks around async succeeds matches async match fails properly',
'hooks around async succeeds matches sync fails properly',
'hooks before async with async subject async match fails properly',
'hooks before async with async subject before :each fails properly should not reach the example',
'hooks before async with async subject before :each succeeds, assertion fails properly should not eq 42',
'hooks before async with async subject before :each succeeds, subject fails properly should not reach the example',
'hooks before async with async subject both subject and before(:each) fail properly should not reach the example',
'hooks before async with sync subject async match fails properly',
'hooks before async with sync subject before :each fails properly should not reach the example',
'hooks before async with sync subject match fails properly should not eq 42',
'hooks before sync with sync subject context fails properly should not reach the example',
'hooks before sync with sync subject before :each fails properly should not reach the example',
'hooks before sync with sync subject match fails properly should not eq 42',
'hooks before sync with sync subject first before :each in chain triggers failure inner context should not reach the example',
'hooks after sync after fails should eq 42',
'hooks after sync before fails should not reach the example',
'hooks after sync match fails async match',
'hooks after sync match fails sync match should eq 43',
'hooks after async after(:each) fails properly',
'hooks after async before(:each) fails properly',
'hooks after async match fails properly async match',
'hooks after async match fails properly sync match should eq 43',
'exception handling should fail properly if an exception is raised',
'exception handling should ignore an exception after a failed assertion'].sort
if actual_failures != expected_failures
unexpected = actual_failures - expected_failures
missing = expected_failures - actual_failures
failure_messages << "Expected test failures do not match actual\n"
failure_messages << "\nUnexpected fails:\n#{unexpected.join("\n")}\n\nMissing fails:\n#{missing.join("\n")}\n\n"
end

failure_messages << "Expected #{expected_pending_count} pending examples but actual was #{pending}" unless pending == expected_pending_count.to_s

if failure_messages.empty?
puts 'Test successful!'
else
raise "Test failed, reasons:\n\n#{failure_messages.join("\n")}\n"
end
end
3 changes: 0 additions & 3 deletions opal/opal/rspec.rb
Original file line number Diff line number Diff line change
@@ -10,9 +10,6 @@
# For now, always use our custom formatter for results
config.default_formatter = Opal::RSpec::Runner.default_formatter

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

# Always support expect() and .should syntax (we should not do this really..)
config.expect_with :rspec do |c|
c.syntax = [:should, :expect]
Loading

0 comments on commit d6464d4

Please sign in to comment.