rosarium 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f0c296247c0162ee53783379115ff4a8aaa6a33e
4
+ data.tar.gz: fcfdc65d25c8610894b0c00087b4a12762c53056
5
+ SHA512:
6
+ metadata.gz: 36bc6091d46684b63bb142db5b4b638785ea69552f8e00f2377c7dfdbe88be38abcc9d60fe827bca648308c30071f272b58ff7aca87e86679f888527897e4ae9
7
+ data.tar.gz: 6a6a19090556dfba2defc71ff3c2ac44e884682003df62439148c39b1c546e49f7b4a074276c83362552fc66b1e47984ccac758c7ab0e9116f447fd2fab88e2c
data/Gemfile ADDED
@@ -0,0 +1 @@
1
+ gem 'rspec', '~> 3.4'
data/Gemfile.lock ADDED
@@ -0,0 +1,25 @@
1
+ GEM
2
+ specs:
3
+ diff-lcs (1.2.5)
4
+ rspec (3.4.0)
5
+ rspec-core (~> 3.4.0)
6
+ rspec-expectations (~> 3.4.0)
7
+ rspec-mocks (~> 3.4.0)
8
+ rspec-core (3.4.1)
9
+ rspec-support (~> 3.4.0)
10
+ rspec-expectations (3.4.0)
11
+ diff-lcs (>= 1.2.0, < 2.0)
12
+ rspec-support (~> 3.4.0)
13
+ rspec-mocks (3.4.0)
14
+ diff-lcs (>= 1.2.0, < 2.0)
15
+ rspec-support (~> 3.4.0)
16
+ rspec-support (3.4.1)
17
+
18
+ PLATFORMS
19
+ ruby
20
+
21
+ DEPENDENCIES
22
+ rspec (~> 3.4)
23
+
24
+ BUNDLED WITH
25
+ 1.11.2
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2016 Rachel Evans
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # Rosarium
2
+
3
+ A library for implementing Promises - or something like them - in ruby.
4
+
5
+ # Why?
6
+
7
+ Because I keep hitting bugs and annoying inflexibilities in `concurrent-ruby`,
8
+ whereas I really enjoy the stability and flexibility of JavaScript's "Q"
9
+ library (<https://github.com/kriskowal/q/wiki/API-Reference>).
10
+
11
+ I'm not expecting anyone but me to use this code at this time. But you're
12
+ welcome to do so, if you like.
13
+
14
+ # Example
15
+
16
+ ```
17
+ require 'rosarium'
18
+ ```
19
+
20
+ ## Static methods for creating promises:
21
+
22
+ ```
23
+ # Immediately ready for async execution:
24
+ promise = Rosarium::Promise.execute { ... }
25
+
26
+ # Immediately fulfilled:
27
+ promise = Rosarium::Promise.resolve(anything_except_a_promise)
28
+
29
+ # Immediately rejected:
30
+ promise = Rosarium::Promise.reject(an_exception)
31
+
32
+ # The same promise (returns its argument)
33
+ a_promise = Rosarium::Promise.resolve(a_promise)
34
+
35
+ # Once all promises in the list are fulfilled, then fulfill with a list of
36
+ # their values. If any promise in the list is rejected, then reject with
37
+ # the same reason:
38
+ promise = Rosarium::Promise.all([ promise1, promise2, ... ])
39
+
40
+ # Wait for all the promises in the list to become settled (fulfilled or
41
+ # rejected); then fulfill with the list of promises.
42
+ promise = Rosarium::Promise.all_settled([ promise1, promise2, ... ])
43
+ ```
44
+
45
+ ## Deferreds
46
+
47
+ ```
48
+ # Create a "deferred":
49
+ deferred = Rosarium::Promise.defer
50
+ promise = deferred.promise
51
+ ```
52
+
53
+ then later, use the "deferred" to fulfill or reject the promise:
54
+
55
+ ```
56
+ # Fulfill:
57
+ deferred.resolve(anything_except_a_promise)
58
+
59
+ # Reject:
60
+ deferred.reject(an_exception)
61
+
62
+ # Fulfill or reject, once the other promise is fulfilled / rejected:
63
+ deferred.resolve(other_promise)
64
+ ```
65
+
66
+ ## Methods of promises:
67
+
68
+ ```
69
+ # One of: :pending, :resolving, :fulfilled, :rejected.
70
+ promise.state
71
+
72
+ # Wait for the promise to be settled, then return its value (if fulfilled -
73
+ # note the value may be nil), or nil (if rejected).
74
+ promise.value
75
+
76
+ # Wait for the promise to be settled, then return its reason (if rejected),
77
+ # or nil (if fulfilled).
78
+ promise.reason
79
+
80
+ # true iff state == :fulfilled
81
+ promise.fulfilled?
82
+
83
+ # true iff state == :rejected
84
+ promise.rejected?
85
+
86
+ # Wait for the promise to be settled, then return its value (if fulfilled),
87
+ # or raise with the rejection reason (if rejected).
88
+ promise.value!
89
+
90
+ # Wait for the promise to be settled
91
+ promise.wait
92
+ ```
93
+
94
+ Chaining promises together:
95
+
96
+ ```
97
+ # Handling promise1 fulfillment:
98
+ promise2 = promise1.then { |promise1_value| ... }
99
+
100
+ # Four different ways of handling promise1 rejection:
101
+ promise2 = promise1.then(Proc.new { |promise1_reason| ... })
102
+ promise2 = promise1.rescue { |promise1_reason| ... }
103
+ promise2 = promise1.catch { |promise1_reason| ... }
104
+ promise2 = promise1.on_error { |promise1_reason| ... }
105
+
106
+ # Handle both fulfillment and rejection:
107
+ promise2 = promise1.then(Proc.new { |promise1_reason| ... }) { |promise1_value| ... }
108
+ ```
109
+
110
+ # Miscellany
111
+
112
+ Promise code (every time a ruby block appears in the above examples) is run
113
+ via a fixed-size thread pool, currently set to 10 threads. Execution order is
114
+ not defined.
115
+
116
+ In comparison to the Promises/A+ spec <https://promisesaplus.com/>, these
117
+ promises have an extra possible state, `:resolving`. You are encouraged to
118
+ use `#fulfilled?` and `#rejected?` instead anyway.
119
+
@@ -0,0 +1,25 @@
1
+ module Rosarium
2
+
3
+ class Deferred
4
+
5
+ def initialize(promise, resolver, rejecter)
6
+ @promise = promise
7
+ @resolver = resolver
8
+ @rejecter = rejecter
9
+ end
10
+
11
+ def promise
12
+ @promise
13
+ end
14
+
15
+ def resolve(value)
16
+ @resolver.call(value)
17
+ end
18
+
19
+ def reject(reason)
20
+ @rejecter.call(reason)
21
+ end
22
+
23
+ end
24
+
25
+ end
@@ -0,0 +1,61 @@
1
+ module Rosarium
2
+
3
+ class FixedThreadExecutor
4
+
5
+ def initialize(max = 1)
6
+ @max = max
7
+ @mutex = Mutex.new
8
+ @waiting = []
9
+ @executing = 0
10
+ @threads = []
11
+ end
12
+
13
+ def submit(&block)
14
+ @mutex.synchronize do
15
+ @waiting << block
16
+ if @executing < @max
17
+ @executing = @executing + 1
18
+ t = Thread.new { execute_and_count_down }
19
+ @threads.push t
20
+ end
21
+ end
22
+ end
23
+
24
+ def discard
25
+ @mutex.synchronize { @waiting.clear }
26
+ end
27
+
28
+ def wait_until_idle
29
+ loop do
30
+ t = @mutex.synchronize { @threads.shift }
31
+ t or break
32
+ t.join
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def execute_and_count_down
39
+ begin
40
+ execute
41
+ ensure
42
+ @mutex.synchronize do
43
+ @executing = @executing - 1
44
+ end
45
+ end
46
+ end
47
+
48
+ def execute
49
+ while true
50
+ block = @mutex.synchronize { @waiting.shift }
51
+ block or break
52
+ begin
53
+ block.call
54
+ rescue Exception => e
55
+ end
56
+ end
57
+ end
58
+
59
+ end
60
+
61
+ end
@@ -0,0 +1,111 @@
1
+ module Rosarium
2
+
3
+ class Promise < SimplePromise
4
+
5
+ DEFAULT_ON_FULFILL = Proc.new {|value| value}
6
+ DEFAULT_ON_REJECT = Proc.new {|reason| raise reason}
7
+
8
+ def self.defer
9
+ new_deferred
10
+ end
11
+
12
+ def self.resolve(value)
13
+ if value.kind_of? Promise
14
+ return value
15
+ end
16
+
17
+ deferred = new_deferred
18
+ deferred.resolve(value)
19
+ deferred.promise
20
+ end
21
+
22
+ def self.reject(reason)
23
+ deferred = new_deferred
24
+ deferred.reject(reason)
25
+ deferred.promise
26
+ end
27
+
28
+ def self.execute(&block)
29
+ deferred = new_deferred
30
+ EXECUTOR.submit do
31
+ begin
32
+ deferred.resolve block.call
33
+ rescue Exception => e
34
+ deferred.reject e
35
+ end
36
+ end
37
+ deferred.promise
38
+ end
39
+
40
+ def self.all_settled(promises)
41
+ return resolve([]) if promises.empty?
42
+
43
+ deferred = new_deferred
44
+ promises = promises.dup
45
+
46
+ check = Proc.new do
47
+ if promises.all? {|promise| promise.fulfilled? or promise.rejected?}
48
+ deferred.resolve promises
49
+ end
50
+ end
51
+
52
+ promises.each do |promise|
53
+ promise.then(check) { check.call }
54
+ end
55
+
56
+ deferred.promise
57
+ end
58
+
59
+ def self.all(promises)
60
+ return resolve([]) if promises.empty?
61
+
62
+ deferred = new_deferred
63
+ promises = promises.dup
64
+
65
+ do_reject = Proc.new {|reason| deferred.reject reason}
66
+ do_fulfill = Proc.new do
67
+ if promises.all?(&:fulfilled?)
68
+ deferred.resolve(promises.map &:value)
69
+ end
70
+ end
71
+
72
+ promises.each do |promise|
73
+ promise.then(do_reject) { do_fulfill.call }
74
+ end
75
+
76
+ deferred.promise
77
+ end
78
+
79
+ def then(on_rejected = nil, &on_fulfilled)
80
+ deferred = self.class.new_deferred
81
+
82
+ on_fulfilled ||= DEFAULT_ON_FULFILL
83
+ on_rejected ||= DEFAULT_ON_REJECT
84
+
85
+ on_resolution do
86
+ callback, arg = if fulfilled?
87
+ [ on_fulfilled, value ]
88
+ else
89
+ [ on_rejected, reason ]
90
+ end
91
+
92
+ begin
93
+ deferred.resolve(callback.call arg)
94
+ rescue Exception => e
95
+ deferred.reject e
96
+ end
97
+ end
98
+
99
+ deferred.promise
100
+ end
101
+
102
+ def rescue(&block)
103
+ self.then(block)
104
+ end
105
+
106
+ alias_method :catch, :rescue
107
+ alias_method :on_error, :rescue
108
+
109
+ end
110
+
111
+ end
@@ -0,0 +1,155 @@
1
+ module Rosarium
2
+
3
+ class SimplePromise
4
+
5
+ def self.new_deferred
6
+ promise = new
7
+ resolver = promise.method :resolve
8
+ rejecter = promise.method :reject
9
+
10
+ class <<promise
11
+ undef :resolve
12
+ undef :reject
13
+ end
14
+
15
+ Deferred.new(promise, resolver, rejecter)
16
+ end
17
+
18
+ def initialize
19
+ @state = :pending
20
+ @mutex = Mutex.new
21
+ @condition = ConditionVariable.new
22
+ @on_resolution = []
23
+ end
24
+
25
+ def state
26
+ synchronized { @state }
27
+ end
28
+
29
+ def value
30
+ wait
31
+ synchronized { @value }
32
+ end
33
+
34
+ def reason
35
+ wait
36
+ synchronized { @reason }
37
+ end
38
+
39
+ def fulfilled?
40
+ state == :fulfilled
41
+ end
42
+
43
+ def rejected?
44
+ state == :rejected
45
+ end
46
+
47
+ def value!
48
+ wait
49
+ synchronized do
50
+ if @state == :rejected
51
+ raise @reason
52
+ else
53
+ @value
54
+ end
55
+ end
56
+ end
57
+
58
+ def wait
59
+ on_resolution do
60
+ @mutex.synchronize { @condition.broadcast }
61
+ end
62
+
63
+ @mutex.synchronize do
64
+ loop do
65
+ return if @state == :fulfilled or @state == :rejected
66
+ @condition.wait @mutex
67
+ end
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def synchronized
74
+ @mutex.synchronize { yield }
75
+ end
76
+
77
+ public
78
+
79
+ def resolve(value)
80
+ _resolve(value, nil)
81
+ end
82
+
83
+ def reject(reason)
84
+ raise "reason must be an Exception" unless reason.kind_of? Exception
85
+ _resolve(nil, reason)
86
+ end
87
+
88
+ private
89
+
90
+ def _resolve(value, reason)
91
+ callbacks = []
92
+ add_on_resolution = false
93
+
94
+ synchronized do
95
+ if @state == :pending
96
+ if value.kind_of? SimplePromise
97
+ @state = :resolving
98
+ add_on_resolution = true
99
+ elsif reason.nil?
100
+ @value = value
101
+ @state = :fulfilled
102
+ callbacks.concat @on_resolution
103
+ @on_resolution.clear
104
+ else
105
+ @reason = reason
106
+ @state = :rejected
107
+ callbacks.concat @on_resolution
108
+ @on_resolution.clear
109
+ end
110
+ end
111
+ end
112
+
113
+ if add_on_resolution
114
+ value.on_resolution { copy_resolution_from value }
115
+ end
116
+
117
+ callbacks.each {|c| EXECUTOR.submit { c.call } }
118
+ end
119
+
120
+ def copy_resolution_from(other)
121
+ callbacks = []
122
+
123
+ synchronized do
124
+ if @state == :resolving
125
+ @value = other.value
126
+ @reason = other.reason
127
+ @state = other.state
128
+ callbacks.concat @on_resolution
129
+ @on_resolution.clear
130
+ end
131
+ end
132
+
133
+ callbacks.each {|c| EXECUTOR.submit { c.call } }
134
+ end
135
+
136
+ protected
137
+
138
+ def on_resolution(&block)
139
+ immediate = synchronized do
140
+ if @state == :fulfilled or @state == :rejected
141
+ true
142
+ else
143
+ @on_resolution << block
144
+ false
145
+ end
146
+ end
147
+
148
+ block.call if immediate
149
+
150
+ nil
151
+ end
152
+
153
+ end
154
+
155
+ end
data/lib/rosarium.rb ADDED
@@ -0,0 +1,10 @@
1
+ require_relative 'rosarium/fixed_thread_executor'
2
+ require_relative 'rosarium/simple_promise'
3
+ require_relative 'rosarium/deferred'
4
+ require_relative 'rosarium/promise'
5
+
6
+ module Rosarium
7
+
8
+ EXECUTOR = FixedThreadExecutor.new(10)
9
+
10
+ end
@@ -0,0 +1,134 @@
1
+ require "rosarium"
2
+ require_relative "./promise_test_helper"
3
+
4
+ describe "deferred promises" do
5
+
6
+ include PromiseTestHelper
7
+
8
+ it "creates a pending promise" do
9
+ deferred = Rosarium::Promise.defer
10
+ check_pending deferred.promise
11
+ end
12
+
13
+ it "deferred can be fulfilled only once" do
14
+ deferred = Rosarium::Promise.defer
15
+ check_pending deferred.promise
16
+ deferred.resolve 7
17
+ check_fulfilled deferred.promise, 7
18
+ deferred.resolve 8
19
+ check_fulfilled deferred.promise, 7
20
+ deferred.reject an_error
21
+ check_fulfilled deferred.promise, 7
22
+ end
23
+
24
+ it "deferred can be rejected only once" do
25
+ e = an_error
26
+ deferred = Rosarium::Promise.defer
27
+ check_pending deferred.promise
28
+ deferred.reject e
29
+ check_rejected deferred.promise, e
30
+ deferred.reject an_error("again")
31
+ check_rejected deferred.promise, e
32
+ deferred.resolve 9
33
+ check_rejected deferred.promise, e
34
+ end
35
+
36
+ it "can only be rejected with an exception" do
37
+ deferred = Rosarium::Promise.defer
38
+ check_pending deferred.promise
39
+ expect { deferred.reject "123" }.to raise_error /reason must be an Exception/
40
+ check_pending deferred.promise
41
+ end
42
+
43
+ it "can be resolved with an already-fulfilled promise" do
44
+ d1 = Rosarium::Promise.defer
45
+ d2 = Rosarium::Promise.defer
46
+ d2.resolve 7
47
+ d1.resolve(d2.promise)
48
+ check_fulfilled d1.promise, 7
49
+ end
50
+
51
+ it "can be resolved with an already-rejected promise" do
52
+ d1 = Rosarium::Promise.defer
53
+ d2 = Rosarium::Promise.defer
54
+ e = an_error
55
+ d2.reject e
56
+ d1.resolve(d2.promise)
57
+ check_rejected d1.promise, e
58
+ end
59
+
60
+ it "can be resolved with a later-fulfilled promise" do
61
+ d1 = Rosarium::Promise.defer
62
+ d2 = Rosarium::Promise.defer
63
+ d1.resolve(d2.promise)
64
+ check_resolving d1.promise
65
+ d2.resolve 7
66
+ d1.promise.wait
67
+ check_fulfilled d1.promise, 7
68
+ end
69
+
70
+ it "can be resolved with a later-rejected promise" do
71
+ d1 = Rosarium::Promise.defer
72
+ d2 = Rosarium::Promise.defer
73
+ d1.resolve(d2.promise)
74
+ check_resolving d1.promise
75
+ e = an_error
76
+ d2.reject e
77
+ d1.promise.wait
78
+ check_rejected d1.promise, e
79
+ end
80
+
81
+ it "waits for a value (fulfilled)" do
82
+ d = Rosarium::Promise.defer
83
+ check_pending d.promise
84
+ Thread.new { sleep 0.1; d.resolve 7 }
85
+ v = d.promise.value
86
+ expect(v).to eq(7)
87
+ end
88
+
89
+ it "waits for a value (rejected)" do
90
+ d = Rosarium::Promise.defer
91
+ check_pending d.promise
92
+ Thread.new { sleep 0.1; d.reject an_error }
93
+ v = d.promise.value
94
+ expect(v).to eq(nil)
95
+ expect(d.promise).to be_rejected
96
+ end
97
+
98
+ it "waits for a reason (fulfilled)" do
99
+ d = Rosarium::Promise.defer
100
+ check_pending d.promise
101
+ Thread.new { sleep 0.1; d.resolve 7 }
102
+ r = d.promise.reason
103
+ expect(r).to eq(nil)
104
+ expect(d.promise).to be_fulfilled
105
+ end
106
+
107
+ it "waits for a reason (rejected)" do
108
+ d = Rosarium::Promise.defer
109
+ check_pending d.promise
110
+ e = an_error
111
+ Thread.new { sleep 0.1; d.reject e }
112
+ r = d.promise.reason
113
+ expect(r).to eq(e)
114
+ end
115
+
116
+ it "waits for a value! (fulfilled)" do
117
+ d = Rosarium::Promise.defer
118
+ check_pending d.promise
119
+ Thread.new { sleep 0.1; d.resolve 7 }
120
+ v = d.promise.value!
121
+ expect(v).to eq(7)
122
+ end
123
+
124
+ it "waits for a value! (rejected)" do
125
+ d = Rosarium::Promise.defer
126
+ check_pending d.promise
127
+ e = an_error
128
+ Thread.new { sleep 0.1; d.reject e }
129
+ expect {
130
+ d.promise.value!
131
+ }.to raise_error(e)
132
+ end
133
+
134
+ end
@@ -0,0 +1,38 @@
1
+ require "rosarium"
2
+
3
+ describe Rosarium::FixedThreadExecutor do
4
+
5
+ it "runs a job" do
6
+ ex = Rosarium::FixedThreadExecutor.new(1)
7
+ done = false
8
+ ex.submit { done = true }
9
+ ex.wait_until_idle
10
+ expect(done).to be_truthy
11
+ end
12
+
13
+ it "discards exceptions" do
14
+ ex = Rosarium::FixedThreadExecutor.new(1)
15
+ done = false
16
+ ex.submit { raise "bang" }
17
+ ex.submit { done = true }
18
+ ex.wait_until_idle
19
+ expect(done).to be_truthy
20
+ end
21
+
22
+ it "runs jobs concurrently" do
23
+ ex = Rosarium::FixedThreadExecutor.new(3)
24
+ m = Mutex.new
25
+ done = []
26
+ 3.times do
27
+ ex.submit do
28
+ m.synchronize { done << "s" }
29
+ sleep 0.1
30
+ m.synchronize { done << "e" }
31
+ end
32
+ end
33
+ ex.wait_until_idle
34
+ expect(done).to eq(%w[ s s s e e e ])
35
+ end
36
+
37
+ end
38
+
@@ -0,0 +1,71 @@
1
+ require "rosarium"
2
+ require_relative "./promise_test_helper"
3
+
4
+ describe Rosarium::Promise do
5
+
6
+ include PromiseTestHelper
7
+
8
+ # Chaining promises
9
+
10
+ it "supports simple 'then'" do
11
+ deferred = Rosarium::Promise.defer
12
+ chained = deferred.promise.then {|arg| arg * 2}
13
+ check_pending chained
14
+ deferred.resolve 7
15
+ sleep 0.1
16
+ check_fulfilled chained, 14
17
+ end
18
+
19
+ it "rejects if 'then' raises an error" do
20
+ e = an_error
21
+ deferred = Rosarium::Promise.defer
22
+ chained = deferred.promise.then { raise e }
23
+ check_pending chained
24
+ deferred.resolve 7
25
+ sleep 0.1
26
+ check_rejected chained, e
27
+ end
28
+
29
+ it "rejects if the parent rejects" do
30
+ e = an_error
31
+ deferred = Rosarium::Promise.defer
32
+ then_called = false
33
+ chained = deferred.promise.then { then_called = true }
34
+ check_pending chained
35
+ deferred.reject e
36
+ chained.wait
37
+ check_rejected chained, e
38
+ expect(then_called).to be_falsy
39
+ end
40
+
41
+ it "supports then(on_rejected)" do
42
+ e = an_error
43
+ e2 = an_error("another")
44
+ deferred = Rosarium::Promise.defer
45
+ got_args = nil
46
+ chained = deferred.promise.then(Proc.new {|*args| got_args = args; raise e2 }) { raise "should never be called" }
47
+ deferred.reject e
48
+ chained.wait
49
+ check_rejected chained, e2
50
+ expect(got_args).to eq([e])
51
+ end
52
+
53
+ it "on_rejected can cause fulfilled" do
54
+ deferred = Rosarium::Promise.defer
55
+ chained = deferred.promise.then(Proc.new {7}) { raise "should never be called" }
56
+ deferred.reject an_error
57
+ chained.wait
58
+ check_fulfilled chained, 7
59
+ end
60
+
61
+ it "supports rescue/catch/on_error" do
62
+ %i[ rescue catch on_error ].each do |method|
63
+ deferred = Rosarium::Promise.defer
64
+ chained = deferred.promise.send(method) { 7 }
65
+ deferred.reject an_error
66
+ chained.wait
67
+ check_fulfilled chained, 7
68
+ end
69
+ end
70
+
71
+ end
@@ -0,0 +1,103 @@
1
+ require "rosarium"
2
+ require_relative "./promise_test_helper"
3
+
4
+ describe "instantly-resolved promises" do
5
+
6
+ include PromiseTestHelper
7
+
8
+ it "returns the same promise" do
9
+ d = Rosarium::Promise.defer
10
+ t = Rosarium::Promise.resolve d.promise
11
+ expect(t).to eq(d.promise)
12
+ end
13
+
14
+ it "creates a fulfilled promise" do
15
+ t = Rosarium::Promise.resolve 7
16
+ check_fulfilled t, 7
17
+ expect(t).not_to respond_to(:fulfill)
18
+ expect(t).not_to respond_to(:reject)
19
+ end
20
+
21
+ it "creates a rejected promise" do
22
+ e = an_error
23
+ t = Rosarium::Promise.reject an_error
24
+ check_rejected t, an_error
25
+ expect(t).not_to respond_to(:fulfill)
26
+ expect(t).not_to respond_to(:reject)
27
+ end
28
+
29
+ it "creates an immediately-executable promise" do
30
+ promise = Rosarium::Promise.execute do
31
+ sleep 0.1 ; 7
32
+ end
33
+ check_pending promise
34
+ sleep 0.2
35
+ check_fulfilled promise, 7
36
+ end
37
+
38
+ it "catches errors from the executed block and rejects" do
39
+ e = RuntimeError.new("bang")
40
+ promise = Rosarium::Promise.execute do
41
+ sleep 0.1 ; raise e
42
+ end
43
+ check_pending promise
44
+ sleep 0.2
45
+ check_rejected promise, e
46
+ end
47
+
48
+ it "supports all_settled (empty)" do
49
+ promise = Rosarium::Promise.all_settled []
50
+ check_fulfilled promise, []
51
+ end
52
+
53
+ it "supports all_settled (non-empty)" do
54
+ d1 = Rosarium::Promise.defer
55
+ d2 = Rosarium::Promise.defer
56
+ promise = Rosarium::Promise.all_settled [d1.promise, d2.promise]
57
+ check_pending promise
58
+
59
+ d1.resolve 7
60
+ check_pending promise
61
+
62
+ e = an_error
63
+ d2.reject e
64
+ promise.wait
65
+ check_fulfilled promise, [ d1.promise, d2.promise ]
66
+ end
67
+
68
+ it "supports all (empty)" do
69
+ promise = Rosarium::Promise.all []
70
+ check_fulfilled promise, []
71
+ end
72
+
73
+ it "supports all (reject)" do
74
+ d1 = Rosarium::Promise.defer
75
+ d2 = Rosarium::Promise.defer
76
+ d3 = Rosarium::Promise.defer
77
+ promise = Rosarium::Promise.all [d1.promise, d2.promise, d3.promise]
78
+ check_pending promise
79
+
80
+ d1.resolve 7
81
+ check_pending promise
82
+
83
+ e = an_error
84
+ d3.reject e
85
+ promise.wait
86
+ check_rejected promise, e
87
+ end
88
+
89
+ it "supports all (fulfill)" do
90
+ d1 = Rosarium::Promise.defer
91
+ d2 = Rosarium::Promise.defer
92
+ promise = Rosarium::Promise.all [d1.promise, d2.promise]
93
+ check_pending promise
94
+
95
+ d1.resolve 7
96
+ check_pending promise
97
+
98
+ d2.resolve 8
99
+ promise.wait
100
+ check_fulfilled promise, [7,8]
101
+ end
102
+
103
+ end
@@ -0,0 +1,39 @@
1
+ module PromiseTestHelper
2
+
3
+ def check_pending(promise)
4
+ expect(promise.state).to eq(:pending)
5
+ expect(promise).not_to be_fulfilled
6
+ expect(promise).not_to be_rejected
7
+ # expect(promise.value).to be_nil # should block
8
+ # expect(promise.reason).to be_nil # should block
9
+ end
10
+
11
+ def check_resolving(promise)
12
+ expect(promise.state).to eq(:resolving)
13
+ expect(promise).not_to be_fulfilled
14
+ expect(promise).not_to be_rejected
15
+ # expect(promise.value).to be_nil # should block
16
+ # expect(promise.reason).to be_nil # should block
17
+ end
18
+
19
+ def check_fulfilled(promise, value)
20
+ expect(promise.state).to eq(:fulfilled)
21
+ expect(promise).to be_fulfilled
22
+ expect(promise).not_to be_rejected
23
+ expect(promise.value).to eq(value)
24
+ expect(promise.reason).to be_nil
25
+ end
26
+
27
+ def check_rejected(promise, e)
28
+ expect(promise.state).to eq(:rejected)
29
+ expect(promise).not_to be_fulfilled
30
+ expect(promise).to be_rejected
31
+ expect(promise.value).to eq(nil)
32
+ expect(promise.reason).to eq(e)
33
+ end
34
+
35
+ def an_error(message = "bang")
36
+ RuntimeError.new(message)
37
+ end
38
+
39
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rosarium
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Rachel Evans
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-07-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.4'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.4'
27
+ description: |2
28
+ Rosarium implements something that's a bit like Promises,
29
+ inspired by the stability and ease of use of Q
30
+ (<https://github.com/kriskowal/q/wiki/API-Reference>).
31
+ email: git@rve.org.uk
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - Gemfile
37
+ - Gemfile.lock
38
+ - LICENSE
39
+ - README.md
40
+ - lib/rosarium.rb
41
+ - lib/rosarium/deferred.rb
42
+ - lib/rosarium/fixed_thread_executor.rb
43
+ - lib/rosarium/promise.rb
44
+ - lib/rosarium/simple_promise.rb
45
+ - spec/deferred_spec.rb
46
+ - spec/fixed_thread_executor_spec.rb
47
+ - spec/promise_methods_spec.rb
48
+ - spec/promise_static_spec.rb
49
+ - spec/promise_test_helper.rb
50
+ homepage: http://rve.org.uk/gems/rosarium
51
+ licenses:
52
+ - Apache-2.0
53
+ metadata: {}
54
+ post_install_message:
55
+ rdoc_options: []
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ requirements: []
69
+ rubyforge_project:
70
+ rubygems_version: 2.5.1
71
+ signing_key:
72
+ specification_version: 4
73
+ summary: Promises, or something like them
74
+ test_files: []