rx-rspec 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1c09e839a091c586847715b943b738538766dbd9
4
- data.tar.gz: 24371ceefd48af50620a622f1677d99145453f2d
3
+ metadata.gz: 5ec713b68381116f5cd0dd29928c999f8e831176
4
+ data.tar.gz: 41a63b49be631831293838c800c634fa5526a5d0
5
5
  SHA512:
6
- metadata.gz: a1d837c502f93a25eb91a31a0464a894b3ef404a1b9ea006f668a15394e351278b3404631fcf1d58a6283bc6ab85531f5b3813e79dc24614ba23e4dae88a989f
7
- data.tar.gz: 7b22bc0e6606ac700b44ac10e22f325e550d98cbe9c1ae53012a7473abdc896bff221f035aaa20e049cb3de7b2325863710481c7090d3b46701e1d2fe5c91fd3
6
+ metadata.gz: 9ae42de39081fd07ec0cefeca36057110f8e61110e46e679337283e0167ff7e6a8a2e75dffc5df029506bf60939cb28bc1bcd476f6a682d4ed4af856d8e0874c
7
+ data.tar.gz: fab1f1821c010e82aa29f8a1b8f63729f75a6dae62c66e5f4ebcc4eda7e61f36246c68e2ee2131fb14b1fc4738f7a103d913ce861ef6a4bdbc5099e53cb6e1b7
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rx-rspec (0.3.1)
4
+ rx-rspec (0.4.1)
5
5
  rx
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -32,12 +32,19 @@ describe 'awesome' do
32
32
  it { should emit_exactly(1, 2, 3) }
33
33
  end
34
34
  ```
35
+ You can also set a timeout in seconds so your specs don't block forever in case an observable never emits/completes/errors.
36
+ ```
37
+ it 'should be fast' do
38
+ expect(observable).to emit_exactly(42).within(0.1)
39
+ end
40
+ ```
35
41
 
36
42
  ## Matchers
37
43
 
38
44
  rx-spec include the following matchers:
39
45
 
40
- - **emit_nothing()** matches an observable that completes without emitting events or erroring.
41
- - **emit_exactly()** matches against all items produced by the observable and requires the observable to be completed.
42
- - **emit_first()** matches against the first elements of the observable, but does not require it to complete
43
- - **emit_include()** consumes elements until the expected elements have occurred
46
+ - **emit_error(class, message)** matches an observable that errors without emitting any values.
47
+ - **emit_exactly(\*events)** matches an observable that emits exactly the given values and requires the observable to be completed.
48
+ - **emit_first(\*events)** matches against the first values emitted by the observable, but does not require it to complete.
49
+ - **emit_include(\*events)** consumes values until the expected values have been seen.
50
+ - **emit_nothing()** matches an observable that completes without emitting values or erroring.
@@ -1,3 +1,5 @@
1
+ require 'rx-rspec/error'
1
2
  require 'rx-rspec/exactly'
2
3
  require 'rx-rspec/first'
3
4
  require 'rx-rspec/include'
5
+ require 'rx-rspec/nothing'
@@ -0,0 +1,47 @@
1
+ require 'rspec'
2
+
3
+ module RxRspec
4
+ class TimeoutError < RuntimeError ; end
5
+
6
+ class AsyncRunner
7
+ def initialize(timeout)
8
+ @timeout = timeout
9
+ end
10
+
11
+ def await_done(&block)
12
+ condition = ConditionVariable.new
13
+ deadline = Time.now + @timeout
14
+ done_args = nil
15
+ error = nil
16
+ Thread.new do
17
+ done = Proc.new do |*args|
18
+ done_args = args unless done_args
19
+ condition.signal
20
+ end
21
+ begin
22
+ block.call(done)
23
+ rescue => e
24
+ error = e
25
+ done.call
26
+ end
27
+ end.join(@timeout)
28
+
29
+ gate = Mutex.new
30
+ while Time.now < deadline && done_args.nil?
31
+ gate.synchronize { condition.wait(gate, deadline - Time.now) }
32
+ end
33
+
34
+ if error
35
+ raise error
36
+ elsif done_args.nil?
37
+ RSpec::Expectations.fail_with("Timeout after #{@timeout} seconds")
38
+ end
39
+
40
+ if done_args.nil? || done_args.size == 0
41
+ nil
42
+ else
43
+ done_args
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,38 @@
1
+ require 'rspec'
2
+ require 'rx-rspec/shared'
3
+
4
+ RSpec::Matchers.define :emit_error do
5
+ include RxRspec::Shared
6
+
7
+ match do |actual|
8
+ error_class, message = expected
9
+ begin
10
+ @actual = await_done do |done|
11
+ actual.subscribe(
12
+ lambda { |event| done.call(:events, event) },
13
+ lambda { |err| done.call(:error, err) },
14
+ lambda { done.call(:complete, nil) }
15
+ )
16
+ end
17
+ rescue Exception => err
18
+ @actual = [:error, err]
19
+ raise err
20
+ end
21
+ type, emitted = @actual
22
+ return type == :error &&
23
+ values_match?(error_class, emitted) &&
24
+ values_match?(message, emitted.message)
25
+ end
26
+
27
+ failure_message do
28
+ type, emitted = @actual
29
+ if type == :events
30
+ "unexpected #{emitted} emitted"
31
+ elsif type == :complete
32
+ "completed without error"
33
+ else
34
+ error_class, message = expected
35
+ present_error("#{error_class} with #{message}", emitted)
36
+ end
37
+ end
38
+ end
@@ -1,47 +1,39 @@
1
1
  require 'rspec'
2
2
  require 'rx-rspec/shared'
3
3
 
4
- # TODO:
5
- # - should have specific timeout message 'x seconds waiting for y'
6
-
7
4
  RSpec::Matchers.define :emit_exactly do |*expected|
8
5
  include RxRspec::Shared
9
6
 
10
- events = []
11
- errors = []
12
7
  match do |actual|
13
- Thread.new do
14
- spinlock = expected.size
15
- actual.subscribe(
16
- lambda do |event|
17
- events << event
18
- spinlock -= 1
19
- end,
20
- lambda do |err|
21
- errors << err
22
- spinlock = 0
23
- end,
24
- lambda { spinlock = 0 }
25
- )
26
- for n in 1..10
27
- break if spinlock == 0
28
- sleep(0.05)
8
+ events = []
9
+ begin
10
+ error = await_done do |done|
11
+ actual.subscribe(
12
+ lambda do |event|
13
+ events << event
14
+ done.call if events.size > expected.size
15
+ end,
16
+ lambda { |err| done.call(:error, err) },
17
+ lambda { done.call }
18
+ )
29
19
  end
30
- raise 'timeout' if spinlock > 0
31
- end.join
20
+ rescue Exception => err
21
+ @actual = [:error, err]
22
+ raise err
23
+ end
32
24
 
33
- return false unless errors.empty?
34
- @actual = events
35
- values_match? expected, events
25
+ @actual = error || [:events, events]
26
+ error.nil? && values_match?(expected, events)
36
27
  end
37
28
 
38
29
  diffable
39
30
 
40
31
  failure_message do
41
- if errors.empty?
42
- "expected #{events} to match #{expected}"
32
+ type, emitted = @actual
33
+ if type == :events
34
+ "expected #{emitted} to match #{expected}"
43
35
  else
44
- present_error(expected, errors[0])
36
+ present_error(expected, emitted)
45
37
  end
46
38
  end
47
39
  end
@@ -1,46 +1,39 @@
1
1
  require 'rspec'
2
2
  require 'rx-rspec/shared'
3
3
 
4
- # TODO:
5
- # - should have specific timeout message 'x seconds waiting for y'
6
-
7
4
  RSpec::Matchers.define :emit_first do |*expected|
8
5
  include RxRspec::Shared
9
6
 
10
- events = []
11
- errors = []
12
- completed = []
13
-
14
7
  match do |actual|
15
8
  raise 'Please supply at least one expectation' if expected.size == 0
16
9
  expect_clone = expected.dup
17
- Thread.new do
18
- deadline = Time.now + 0.5
19
- actual.take_while do |event|
20
- events << event
21
- values_match?(expect_clone.shift, event) && expect_clone.size > 0
22
- end.subscribe(
23
- lambda { |event| },
24
- lambda { |err| errors << err },
25
- lambda { completed << :complete }
26
- )
27
-
28
- until Time.now > deadline
29
- break if expect_clone.empty?
30
- break unless completed.empty? && errors.empty?
31
- sleep(0.05)
10
+ events = []
11
+ begin
12
+ error = await_done do |done|
13
+ actual.take_while do |event|
14
+ events << event
15
+ values_match?(expect_clone.shift, event) && expect_clone.size > 0
16
+ end.subscribe(
17
+ lambda { |event| },
18
+ lambda { |err| done.call(:error, err) },
19
+ lambda { done.call }
20
+ )
32
21
  end
33
- raise 'timeout' if Time.now > deadline
34
- end.join
22
+ rescue Exception => err
23
+ @actual = [:error, err]
24
+ raise err
25
+ end
35
26
 
36
- values_match? expected, events
27
+ @actual = error || [:events, events]
28
+ error.nil? && values_match?(expected, events)
37
29
  end
38
30
 
39
31
  failure_message do
40
- if errors.empty?
41
- "expected #{events} to emit at least #{expected}"
32
+ type, emitted = @actual
33
+ if type == :events
34
+ "expected #{emitted} to emit at least #{expected}"
42
35
  else
43
- present_error(expected, errors[0])
36
+ present_error(expected, emitted)
44
37
  end
45
38
  end
46
39
  end
@@ -1,47 +1,40 @@
1
1
  require 'rspec'
2
2
  require 'rx-rspec/shared'
3
3
 
4
- # TODO:
5
- # - should have specific timeout message 'x seconds waiting for y'
6
-
7
4
  RSpec::Matchers.define :emit_include do |*expected|
8
5
  include RxRspec::Shared
9
6
 
10
- events = []
11
- errors = []
12
- completed = []
13
-
14
7
  match do |actual|
15
8
  expected = expected.dup
16
- Thread.new do
17
- deadline = Time.now + 0.5
18
- actual.take_while do |event|
19
- events << event
20
- idx = expected.index { |exp| values_match?(exp, event) }
21
- expected.delete_at(idx) unless idx.nil?
22
- expected.size > 0
23
- end.subscribe(
24
- lambda { |event| },
25
- lambda { |err| errors << err },
26
- lambda { completed << :complete }
27
- )
28
-
29
- until Time.now > deadline
30
- break if expected.empty?
31
- break unless completed.empty? && errors.empty?
32
- sleep(0.05)
9
+ events = []
10
+ begin
11
+ error = await_done do |done|
12
+ actual.take_while do |event|
13
+ events << event
14
+ idx = expected.index { |exp| values_match?(exp, event) }
15
+ expected.delete_at(idx) unless idx.nil?
16
+ expected.size > 0
17
+ end.subscribe(
18
+ lambda { |event| },
19
+ lambda { |err| done.call(:error, err) },
20
+ lambda { done.call }
21
+ )
33
22
  end
34
- raise 'timeout' if Time.now > deadline
35
- end.join
23
+ rescue Exception => err
24
+ @actual = [:error, err]
25
+ raise err
26
+ end
36
27
 
37
- expected.empty?
28
+ @actual = error || [:events, events]
29
+ expected.empty? && error.nil?
38
30
  end
39
31
 
40
32
  failure_message do
41
- if errors.empty?
42
- "expected #{events} to include #{expected}"
33
+ type, emitted = @actual
34
+ if type == :events
35
+ "expected #{emitted} to include #{expected}"
43
36
  else
44
- present_error(expected, errors[0])
37
+ present_error(expected, emitted)
45
38
  end
46
39
  end
47
40
  end
@@ -1,39 +1,31 @@
1
1
  require 'rspec'
2
2
  require 'rx-rspec/shared'
3
3
 
4
- # TODO:
5
- # - should have specific timeout message 'x seconds waiting for y'
6
-
7
- RSpec::Matchers.define :emit_nothing do |*expected|
4
+ RSpec::Matchers.define :emit_nothing do
8
5
  include RxRspec::Shared
9
6
 
10
- events = []
11
- errors = []
12
- completed = []
13
7
  match do |actual|
14
- Thread.new do
15
- actual.subscribe(
16
- lambda { |event| events << event },
17
- lambda { |err| errors << err },
18
- lambda { completed << :complete }
19
- )
20
- for n in 1..10
21
- break unless completed.empty?
22
- break unless errors.empty? && events.empty?
23
- sleep(0.05)
8
+ begin
9
+ @actual = await_done do |done|
10
+ actual.subscribe(
11
+ lambda { |event| done.call(:events, event) },
12
+ lambda { |err| done.call(:error, err) },
13
+ lambda { done.call }
14
+ )
24
15
  end
25
- raise 'timeout' if errors.empty? && events.empty? && completed.empty?
26
- end.join
27
-
28
- return false unless errors.empty?
29
- return events.empty?
16
+ rescue Exception => err
17
+ @actual = [:error, err]
18
+ raise err
19
+ end
20
+ return @actual.nil?
30
21
  end
31
22
 
32
23
  failure_message do
33
- if errors.empty?
34
- "unexpected #{events[0]} emitted"
24
+ type, emitted = @actual
25
+ if type == :events
26
+ "unexpected #{emitted} emitted"
35
27
  else
36
- present_error(expected, errors[0])
28
+ present_error(expected, emitted)
37
29
  end
38
30
  end
39
31
  end
@@ -1,7 +1,35 @@
1
+ require 'rspec'
2
+ require 'rx-rspec/async_runner'
3
+
1
4
  module RxRspec
2
5
  module Shared
6
+ DEFAULT_TIMEOUT = 0.5
7
+
8
+ def self.included(mod)
9
+ mod.chain :within do |seconds|
10
+ @timeout = seconds
11
+ end
12
+
13
+ mod.description do
14
+ if @timeout
15
+ super() + " within #{@timeout} seconds"
16
+ else
17
+ super()
18
+ end
19
+ end
20
+ end
21
+
22
+ def timeout
23
+ @timeout || DEFAULT_TIMEOUT
24
+ end
25
+
26
+ def await_done(&block)
27
+ AsyncRunner.new(timeout).await_done(&block)
28
+ end
29
+
3
30
  def present_error(expected, error)
4
31
  backtrace = error.backtrace || []
32
+ return error.message if /^Timeout/.match(error.message)
5
33
  present_error = "#{error.inspect}:#{$/}#{backtrace.join($/)}"
6
34
  "expected #{expected} but received error #{present_error}"
7
35
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = 'rx-rspec'
3
- spec.version = '0.3.1'
3
+ spec.version = '0.4.1'
4
4
  spec.summary = 'rspec testing support for RxRuby'
5
5
  spec.description = 'Writing specs for reactive streams is tricky both because of their asynchronous nature and because their next/error/completed semantics. The goal of rx-rspec is to provide powerful matchers that lets you express your expectations in a traditional rspec-like synchronous manner.'
6
6
  spec.authors = ['Anders Qvist']
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rx-rspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anders Qvist
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-14 00:00:00.000000000 Z
11
+ date: 2018-03-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rx
@@ -110,6 +110,8 @@ files:
110
110
  - LICENSE.md
111
111
  - README.md
112
112
  - lib/rx-rspec.rb
113
+ - lib/rx-rspec/async_runner.rb
114
+ - lib/rx-rspec/error.rb
113
115
  - lib/rx-rspec/exactly.rb
114
116
  - lib/rx-rspec/first.rb
115
117
  - lib/rx-rspec/include.rb