concurrent-ruby 0.4.1 → 0.5.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +31 -33
  3. data/lib/concurrent.rb +11 -3
  4. data/lib/concurrent/actor.rb +29 -29
  5. data/lib/concurrent/agent.rb +98 -16
  6. data/lib/concurrent/atomic.rb +125 -0
  7. data/lib/concurrent/channel.rb +36 -1
  8. data/lib/concurrent/condition.rb +67 -0
  9. data/lib/concurrent/copy_on_notify_observer_set.rb +80 -0
  10. data/lib/concurrent/copy_on_write_observer_set.rb +94 -0
  11. data/lib/concurrent/count_down_latch.rb +60 -0
  12. data/lib/concurrent/dataflow.rb +85 -0
  13. data/lib/concurrent/dereferenceable.rb +69 -31
  14. data/lib/concurrent/event.rb +27 -21
  15. data/lib/concurrent/future.rb +103 -43
  16. data/lib/concurrent/ivar.rb +78 -0
  17. data/lib/concurrent/mvar.rb +154 -0
  18. data/lib/concurrent/obligation.rb +94 -9
  19. data/lib/concurrent/postable.rb +11 -9
  20. data/lib/concurrent/promise.rb +101 -127
  21. data/lib/concurrent/safe_task_executor.rb +28 -0
  22. data/lib/concurrent/scheduled_task.rb +60 -54
  23. data/lib/concurrent/stoppable.rb +2 -2
  24. data/lib/concurrent/supervisor.rb +36 -29
  25. data/lib/concurrent/thread_local_var.rb +117 -0
  26. data/lib/concurrent/timer_task.rb +28 -30
  27. data/lib/concurrent/utilities.rb +1 -1
  28. data/lib/concurrent/version.rb +1 -1
  29. data/spec/concurrent/agent_spec.rb +121 -230
  30. data/spec/concurrent/atomic_spec.rb +201 -0
  31. data/spec/concurrent/condition_spec.rb +171 -0
  32. data/spec/concurrent/copy_on_notify_observer_set_spec.rb +10 -0
  33. data/spec/concurrent/copy_on_write_observer_set_spec.rb +10 -0
  34. data/spec/concurrent/count_down_latch_spec.rb +125 -0
  35. data/spec/concurrent/dataflow_spec.rb +160 -0
  36. data/spec/concurrent/dereferenceable_shared.rb +145 -0
  37. data/spec/concurrent/event_spec.rb +44 -9
  38. data/spec/concurrent/fixed_thread_pool_spec.rb +0 -1
  39. data/spec/concurrent/future_spec.rb +184 -69
  40. data/spec/concurrent/ivar_spec.rb +192 -0
  41. data/spec/concurrent/mvar_spec.rb +380 -0
  42. data/spec/concurrent/obligation_spec.rb +193 -0
  43. data/spec/concurrent/observer_set_shared.rb +233 -0
  44. data/spec/concurrent/postable_shared.rb +3 -7
  45. data/spec/concurrent/promise_spec.rb +270 -192
  46. data/spec/concurrent/safe_task_executor_spec.rb +58 -0
  47. data/spec/concurrent/scheduled_task_spec.rb +142 -38
  48. data/spec/concurrent/thread_local_var_spec.rb +113 -0
  49. data/spec/concurrent/thread_pool_shared.rb +2 -3
  50. data/spec/concurrent/timer_task_spec.rb +31 -1
  51. data/spec/spec_helper.rb +2 -3
  52. data/spec/support/functions.rb +4 -0
  53. data/spec/support/less_than_or_equal_to_matcher.rb +5 -0
  54. metadata +50 -30
  55. data/lib/concurrent/contract.rb +0 -21
  56. data/lib/concurrent/event_machine_defer_proxy.rb +0 -22
  57. data/md/actor.md +0 -404
  58. data/md/agent.md +0 -142
  59. data/md/channel.md +0 -40
  60. data/md/dereferenceable.md +0 -49
  61. data/md/future.md +0 -125
  62. data/md/obligation.md +0 -32
  63. data/md/promise.md +0 -217
  64. data/md/scheduled_task.md +0 -156
  65. data/md/supervisor.md +0 -246
  66. data/md/thread_pool.md +0 -225
  67. data/md/timer_task.md +0 -191
  68. data/spec/concurrent/contract_spec.rb +0 -34
  69. data/spec/concurrent/event_machine_defer_proxy_spec.rb +0 -240
@@ -0,0 +1,193 @@
1
+ require 'spec_helper'
2
+
3
+ module Concurrent
4
+
5
+ describe Obligation do
6
+
7
+ let (:obligation_class) do
8
+
9
+ Class.new do
10
+ include Obligation
11
+
12
+ def initialize
13
+ init_mutex
14
+ end
15
+
16
+ public :state=, :compare_and_set_state, :if_state, :mutex
17
+ attr_writer :value, :reason
18
+ end
19
+ end
20
+
21
+ let (:obligation) { obligation_class.new }
22
+ let (:event) { double 'event' }
23
+
24
+ share_examples_for :incomplete do
25
+ it 'should be not completed' do
26
+ obligation.should_not be_completed
27
+ end
28
+
29
+ it 'should be incomplete' do
30
+ obligation.should be_incomplete
31
+ end
32
+
33
+ describe '#value' do
34
+
35
+ it 'should return immediately if timeout is zero' do
36
+ obligation.value(0).should be_nil
37
+ end
38
+
39
+ it 'should block on the event if timeout is not set' do
40
+ obligation.stub(:event).and_return(event)
41
+ event.should_receive(:wait).with(nil)
42
+
43
+ obligation.value
44
+ end
45
+
46
+ it 'should block on the event if timeout is not zero' do
47
+ obligation.stub(:event).and_return(event)
48
+ event.should_receive(:wait).with(5)
49
+
50
+ obligation.value(5)
51
+ end
52
+ end
53
+ end
54
+
55
+ context 'unscheduled' do
56
+ before(:each) { obligation.state = :unscheduled }
57
+ it_should_behave_like :incomplete
58
+ end
59
+
60
+ context 'pending' do
61
+ before(:each) { obligation.state = :pending }
62
+ it_should_behave_like :incomplete
63
+ end
64
+
65
+ context 'fulfilled' do
66
+
67
+ before(:each) do
68
+ obligation.state = :fulfilled
69
+ obligation.send(:value=, 42)
70
+ obligation.stub(:event).and_return(event)
71
+ end
72
+
73
+ it 'should be completed' do
74
+ obligation.should be_completed
75
+ end
76
+
77
+ it 'should be not incomplete' do
78
+ obligation.should_not be_incomplete
79
+ end
80
+
81
+ describe '#value' do
82
+
83
+ it 'should return immediately if timeout is zero' do
84
+ obligation.value(0).should eq 42
85
+ end
86
+
87
+ it 'should return immediately if timeout is not set' do
88
+ event.should_not_receive(:wait)
89
+
90
+ obligation.value.should eq 42
91
+ end
92
+
93
+ it 'should return immediately if timeout is not zero' do
94
+ event.should_not_receive(:wait)
95
+
96
+ obligation.value(5).should eq 42
97
+ end
98
+
99
+ end
100
+
101
+
102
+ end
103
+
104
+ context 'rejected' do
105
+
106
+ before(:each) do
107
+ obligation.state = :rejected
108
+ obligation.stub(:event).and_return(event)
109
+ end
110
+
111
+ it 'should be completed' do
112
+ obligation.should be_completed
113
+ end
114
+
115
+ it 'should be not incomplete' do
116
+ obligation.should_not be_incomplete
117
+ end
118
+ end
119
+
120
+ describe '#value' do
121
+
122
+ it 'should return immediately if timeout is zero' do
123
+ event.should_not_receive(:wait)
124
+
125
+ obligation.value(0).should be_nil
126
+ end
127
+
128
+ it 'should return immediately if timeout is not set' do
129
+ event.should_not_receive(:wait)
130
+
131
+ obligation.value.should be_nil
132
+ end
133
+
134
+ it 'should return immediately if timeout is not zero' do
135
+ event.should_not_receive(:wait)
136
+
137
+ obligation.value(5).should be_nil
138
+ end
139
+
140
+ end
141
+
142
+ describe '#compare_and_set_state' do
143
+
144
+ before(:each) { obligation.state = :unscheduled }
145
+
146
+ context 'unexpected state' do
147
+ it 'should return false if state is not the expected one' do
148
+ obligation.compare_and_set_state(:pending, :rejected).should be_false
149
+ end
150
+
151
+ it 'should not change the state if current is not the expected one' do
152
+ obligation.compare_and_set_state(:pending, :rejected)
153
+ obligation.state.should eq :unscheduled
154
+ end
155
+ end
156
+
157
+ context 'expected state' do
158
+ it 'should return true if state is the expected one' do
159
+ obligation.compare_and_set_state(:pending, :unscheduled).should be_true
160
+ end
161
+
162
+ it 'should not change the state if current is not the expected one' do
163
+ obligation.compare_and_set_state(:pending, :unscheduled)
164
+ obligation.state.should eq :pending
165
+ end
166
+ end
167
+
168
+ end
169
+
170
+ describe '#if_state' do
171
+
172
+ before(:each) { obligation.state = :unscheduled }
173
+
174
+ it 'should raise without block' do
175
+ expect { obligation.if_state(:pending) }.to raise_error(ArgumentError)
176
+ end
177
+
178
+ it 'should return false if state is not expected' do
179
+ obligation.if_state(:pending, :rejected) { 42 }.should be_false
180
+ end
181
+
182
+ it 'should the block value if state is expected' do
183
+ obligation.if_state(:rejected, :unscheduled) { 42 }.should eq 42
184
+ end
185
+
186
+ it 'should execute the block within the mutex' do
187
+ obligation.if_state(:unscheduled) { obligation.mutex.should be_locked }
188
+ end
189
+
190
+ end
191
+
192
+ end
193
+ end
@@ -0,0 +1,233 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples "an observer set" do
4
+
5
+ let (:observer_set) { described_class.new }
6
+ let (:observer) { double('observer') }
7
+ let (:another_observer) { double('another observer') }
8
+
9
+ describe '#add_observer' do
10
+
11
+ context 'with argument' do
12
+ it 'should return the passed function' do
13
+ observer_set.add_observer(observer, :a_method).should eq(:a_method)
14
+ end
15
+ end
16
+
17
+ context 'without arguments' do
18
+ it 'should return the default function' do
19
+ observer_set.add_observer(observer).should eq(:update)
20
+ end
21
+ end
22
+ end
23
+
24
+ describe '#notify_observers' do
25
+ it 'should return the observer set' do
26
+ observer_set.notify_observers.should be(observer_set)
27
+ end
28
+
29
+ context 'with a single observer' do
30
+ it 'should update a registered observer without arguments' do
31
+ expect(observer).to receive(:update).with(no_args)
32
+
33
+ observer_set.add_observer(observer)
34
+
35
+ observer_set.notify_observers
36
+ end
37
+
38
+ it 'should update a registered observer with arguments' do
39
+ expect(observer).to receive(:update).with(1, 2, 3)
40
+
41
+ observer_set.add_observer(observer)
42
+
43
+ observer_set.notify_observers(1, 2, 3)
44
+ end
45
+
46
+ it 'should notify an observer using the chosen method' do
47
+ expect(observer).to receive(:another_method).with('a string arg')
48
+
49
+ observer_set.add_observer(observer, :another_method)
50
+
51
+ observer_set.notify_observers('a string arg')
52
+ end
53
+
54
+ it 'should notify an observer once using the last added method' do
55
+ expect(observer).to receive(:another_method).with(any_args).never
56
+ expect(observer).to receive(:yet_another_method).with('a string arg')
57
+
58
+ observer_set.add_observer(observer, :another_method)
59
+ observer_set.add_observer(observer, :yet_another_method)
60
+
61
+ observer_set.notify_observers('a string arg')
62
+ end
63
+
64
+ it 'can be called many times' do
65
+ expect(observer).to receive(:update).with(:an_arg).twice
66
+ expect(observer).to receive(:update).with(no_args).once
67
+
68
+ observer_set.add_observer(observer)
69
+
70
+ observer_set.notify_observers(:an_arg)
71
+ observer_set.notify_observers
72
+ observer_set.notify_observers(:an_arg)
73
+ end
74
+ end
75
+
76
+ context 'with many observers' do
77
+ it 'should notify all observer using the chosen method' do
78
+ expect(observer).to receive(:a_method).with(4, 'a')
79
+ expect(another_observer).to receive(:update).with(4, 'a')
80
+
81
+ observer_set.add_observer(observer, :a_method)
82
+ observer_set.add_observer(another_observer)
83
+
84
+ observer_set.notify_observers(4, 'a')
85
+ end
86
+ end
87
+
88
+ context 'with a block' do
89
+
90
+ before(:each) do
91
+ observer.stub(:update).with(any_args)
92
+ another_observer.stub(:update).with(any_args)
93
+ end
94
+
95
+ it 'calls the block once for every observer' do
96
+
97
+ counter = double('block call counter')
98
+ expect(counter).to receive(:called).with(no_args).exactly(2).times
99
+
100
+ observer_set.add_observer(observer)
101
+ observer_set.add_observer(another_observer)
102
+
103
+ observer_set.notify_observers{ counter.called }
104
+ end
105
+
106
+ it 'passes the block return value to the update method' do
107
+
108
+ expect(observer).to receive(:update).with(1, 2, 3, 4)
109
+ observer_set.add_observer(observer)
110
+ observer_set.notify_observers{ [1, 2, 3, 4] }
111
+ end
112
+
113
+ it 'accepts blocks returning a single value' do
114
+
115
+ expect(observer).to receive(:update).with(:foo)
116
+ observer_set.add_observer(observer)
117
+ observer_set.notify_observers{ :foo }
118
+ end
119
+
120
+ it 'accepts block return values that include arrays' do
121
+
122
+ expect(observer).to receive(:update).with(1, [2, 3], 4)
123
+ observer_set.add_observer(observer)
124
+ observer_set.notify_observers{ [1, [2, 3], 4] }
125
+ end
126
+
127
+ it 'raises an exception if given both arguments and a block' do
128
+
129
+ observer_set.add_observer(observer)
130
+
131
+ expect {
132
+ observer_set.notify_observers(1, 2, 3, 4){ nil }
133
+ }.to raise_error(ArgumentError)
134
+ end
135
+ end
136
+ end
137
+
138
+ context '#count_observers' do
139
+ it 'should be zero after initialization' do
140
+ observer_set.count_observers.should eq 0
141
+ end
142
+
143
+ it 'should be 1 after the first observer is added' do
144
+ observer_set.add_observer(observer)
145
+ observer_set.count_observers.should eq 1
146
+ end
147
+
148
+ it 'should be 1 if the same observer is added many times' do
149
+ observer_set.add_observer(observer)
150
+ observer_set.add_observer(observer, :another_method)
151
+ observer_set.add_observer(observer, :yet_another_method)
152
+
153
+ observer_set.count_observers.should eq 1
154
+ end
155
+
156
+ it 'should be equal to the number of unique observers' do
157
+ observer_set.add_observer(observer)
158
+ observer_set.add_observer(another_observer)
159
+ observer_set.add_observer(double('observer 3'))
160
+ observer_set.add_observer(double('observer 4'))
161
+
162
+ observer_set.count_observers.should eq 4
163
+ end
164
+ end
165
+
166
+ describe '#delete_observer' do
167
+ it 'should not notify a deleted observer' do
168
+ expect(observer).to receive(:update).never
169
+
170
+ observer_set.add_observer(observer)
171
+ observer_set.delete_observer(observer)
172
+
173
+ observer_set.notify_observers
174
+ end
175
+
176
+ it 'can delete a non added observer' do
177
+ observer_set.delete_observer(observer)
178
+ end
179
+
180
+ it 'should return the observer' do
181
+ observer_set.delete_observer(observer).should be(observer)
182
+ end
183
+ end
184
+
185
+ describe '#delete_observers' do
186
+ it 'should remove all observers' do
187
+ expect(observer).to receive(:update).never
188
+ expect(another_observer).to receive(:update).never
189
+
190
+ observer_set.add_observer(observer)
191
+ observer_set.add_observer(another_observer)
192
+
193
+ observer_set.delete_observers
194
+
195
+ observer_set.notify_observers
196
+ end
197
+
198
+ it 'should return the observer set' do
199
+ observer_set.delete_observers.should be(observer_set)
200
+ end
201
+ end
202
+
203
+ describe '#notify_and_delete_observers' do
204
+ before(:each) do
205
+ observer_set.add_observer(observer, :a_method)
206
+ observer_set.add_observer(another_observer)
207
+
208
+ expect(observer).to receive(:a_method).with('args').once
209
+ expect(another_observer).to receive(:update).with('args').once
210
+ end
211
+
212
+ it 'should notify all observers' do
213
+ observer_set.notify_and_delete_observers('args')
214
+ end
215
+
216
+ it 'should clear observers' do
217
+ observer_set.notify_and_delete_observers('args')
218
+
219
+ observer_set.count_observers.should eq(0)
220
+ end
221
+
222
+ it 'can be called many times without any other notification' do
223
+ observer_set.notify_and_delete_observers('args')
224
+ observer_set.notify_and_delete_observers('args')
225
+ observer_set.notify_and_delete_observers('args')
226
+ end
227
+
228
+ it 'should return the observer set' do
229
+ observer_set.notify_and_delete_observers('args').should be(observer_set)
230
+ end
231
+ end
232
+
233
+ end
@@ -31,15 +31,11 @@ share_examples_for :postable do
31
31
  postable.stop
32
32
  end
33
33
 
34
- it 'returns the current size of the queue' do
34
+ it 'returns true on success' do
35
35
  postable = postable_class.new{|msg| sleep }
36
36
  @thread = Thread.new{ postable.run }
37
- @thread.join(0.1)
38
- postable.post(true).should == 1
39
- @thread.join(0.1)
40
- postable.post(true).should == 1
41
- @thread.join(0.1)
42
- postable.post(true).should == 2
37
+ 5.times{ @thread.join(0.1); break if postable.running? }
38
+ postable.post(true).should be_true
43
39
  postable.stop
44
40
  end
45
41
 
@@ -9,20 +9,20 @@ module Concurrent
9
9
  let!(:thread_pool_user){ Promise }
10
10
  it_should_behave_like Concurrent::UsesGlobalThreadPool
11
11
 
12
+ let(:empty_root) { Promise.new { nil } }
12
13
  let!(:fulfilled_value) { 10 }
13
14
  let!(:rejected_reason) { StandardError.new('mojo jojo') }
14
15
 
15
16
  let(:pending_subject) do
16
- Promise.new{ sleep(3); fulfilled_value }
17
+ Promise.new{ sleep(0.3); fulfilled_value }.execute
17
18
  end
18
19
 
19
20
  let(:fulfilled_subject) do
20
- Promise.new{ fulfilled_value }.tap(){ sleep(0.1) }
21
+ Promise.fulfill(fulfilled_value)
21
22
  end
22
23
 
23
24
  let(:rejected_subject) do
24
- Promise.new{ raise rejected_reason }.
25
- rescue{ nil }.tap(){ sleep(0.1) }
25
+ Promise.reject( rejected_reason )
26
26
  end
27
27
 
28
28
  before(:each) do
@@ -36,241 +36,323 @@ module Concurrent
36
36
  promise.should be_a(Dereferenceable)
37
37
  end
38
38
 
39
- context '#then' do
39
+ context 'initializers' do
40
+ describe '.fulfill' do
41
+
42
+ subject { Promise.fulfill(10) }
43
+
44
+ it 'should return a Promise' do
45
+ subject.should be_a Promise
46
+ end
47
+
48
+ it 'should return a fulfilled Promise' do
49
+ subject.should be_fulfilled
50
+ end
51
+
52
+ it 'should return a Promise with set value' do
53
+ subject.value.should eq 10
54
+ end
55
+ end
56
+
57
+ describe '.reject' do
58
+
59
+ let(:reason) { ArgumentError.new }
60
+ subject { Promise.reject(reason) }
61
+
62
+ it 'should return a Promise' do
63
+ subject.should be_a Promise
64
+ end
65
+
66
+ it 'should return a rejected Promise' do
67
+ subject.should be_rejected
68
+ end
69
+
70
+ it 'should return a Promise with set reason' do
71
+ subject.reason.should be reason
72
+ end
73
+ end
74
+
75
+ describe '.new' do
76
+ it 'should return an unscheduled Promise' do
77
+ p = Promise.new { nil }
78
+ p.should be_unscheduled
79
+ end
80
+ end
81
+
82
+ describe '.execute' do
83
+ it 'creates a new Promise' do
84
+ p = Promise.execute{ nil }
85
+ p.should be_a(Promise)
86
+ end
87
+
88
+ it 'passes the block to the new Promise' do
89
+ p = Promise.execute { 20 }
90
+ sleep(0.1)
91
+ p.value.should eq 20
92
+ end
93
+
94
+ it 'calls #execute on the new Promise' do
95
+ p = double('promise')
96
+ Promise.stub(:new).with(any_args).and_return(p)
97
+ p.should_receive(:execute).with(no_args)
98
+ Promise.execute{ nil }
99
+ end
100
+ end
101
+ end
102
+
103
+ context '#execute' do
104
+
105
+ context 'unscheduled' do
106
+
107
+ it 'sets the promise to :pending' do
108
+ p = Promise.new { sleep(0.1) }.execute
109
+ p.should be_pending
110
+ end
111
+
112
+ it 'posts the block given in construction' do
113
+ Promise.thread_pool.should_receive(:post).with(any_args)
114
+ Promise.new { nil }.execute
115
+ end
116
+
117
+ end
118
+
119
+ context 'pending' do
120
+
121
+ it 'sets the promise to :pending' do
122
+ p = pending_subject.execute
123
+ p.should be_pending
124
+ end
125
+
126
+ it 'does not posts again' do
127
+ Promise.thread_pool.should_receive(:post).with(any_args).once
128
+ pending_subject.execute
129
+ end
40
130
 
41
- it 'returns a new Promise when :pending' do
42
- p1 = pending_subject
43
- p2 = p1.then{}
44
- p2.should be_a(Promise)
45
- p1.should_not eq p2
46
131
  end
47
132
 
48
- it 'returns a new Promise when :fulfilled' do
49
- p1 = fulfilled_subject
50
- p2 = p1.then{}
51
- p2.should be_a(Promise)
52
- p1.should_not eq p2
133
+
134
+ describe 'with children' do
135
+
136
+ let(:root) { Promise.new { sleep(0.1); nil } }
137
+ let(:c1) { root.then { nil } }
138
+ let(:c2) { root.then { nil } }
139
+ let(:c2_1) { c2.then { nil } }
140
+
141
+ context 'when called on the root' do
142
+ it 'should set all promises to :pending' do
143
+ root.execute
144
+
145
+ c1.should be_pending
146
+ c2.should be_pending
147
+ c2_1.should be_pending
148
+ [root, c1, c2, c2_1].each { |p| p.should be_pending }
149
+ end
150
+ end
151
+
152
+ context 'when called on a child' do
153
+ it 'should set all promises to :pending' do
154
+ c2_1.execute
155
+
156
+ [root, c1, c2, c2_1].each { |p| p.should be_pending }
157
+ end
158
+ end
159
+
160
+ end
161
+ end
162
+
163
+ describe '#then' do
164
+
165
+ it 'returns a new promise when a block is passed' do
166
+ child = empty_root.then { nil }
167
+ child.should be_a Promise
168
+ child.should_not be empty_root
53
169
  end
54
170
 
55
- it 'returns a new Promise when :rejected' do
56
- p1 = rejected_subject
57
- p2 = p1.then{}
58
- p2.should be_a(Promise)
59
- p1.should_not eq p2
171
+ it 'returns a new promise when a rescuer is passed' do
172
+ child = empty_root.then(Proc.new{})
173
+ child.should be_a Promise
174
+ child.should_not be empty_root
60
175
  end
61
176
 
62
- it 'immediately rejects new promises when self has been rejected' do
63
- p = rejected_subject
64
- p.then.should be_rejected
177
+ it 'returns a new promise when a block and rescuer are passed' do
178
+ child = empty_root.then(Proc.new{}) { nil }
179
+ child.should be_a Promise
180
+ child.should_not be empty_root
65
181
  end
66
182
 
67
- it 'accepts a nil block' do
68
- lambda {
69
- pending_subject.then
70
- }.should_not raise_error
183
+ it 'should have block or rescuers' do
184
+ expect { empty_root.then }.to raise_error(ArgumentError)
185
+ end
186
+
187
+ context 'unscheduled' do
188
+
189
+ let(:p1) { Promise.new {nil} }
190
+ let(:child) { p1.then{} }
191
+
192
+ it 'returns a new promise' do
193
+ child.should be_a Promise
194
+ p1.should_not be child
195
+ end
196
+
197
+ it 'returns an unscheduled promise' do
198
+ child.should be_unscheduled
199
+ end
200
+ end
201
+
202
+ context 'pending' do
203
+
204
+ let(:child) { pending_subject.then{} }
205
+
206
+ it 'returns a new promise' do
207
+ child.should be_a Promise
208
+ pending_subject.should_not be child
209
+ end
210
+
211
+ it 'returns a pending promise' do
212
+ child.should be_pending
213
+ end
214
+ end
215
+
216
+ context 'fulfilled' do
217
+ it 'returns a new Promise' do
218
+ p1 = fulfilled_subject
219
+ p2 = p1.then{}
220
+ p2.should be_a(Promise)
221
+ p1.should_not eq p2
222
+ end
223
+
224
+ it 'notifies fulfillment to new child' do
225
+ child = fulfilled_subject.then(Proc.new{ 7 }) { |v| v + 5 }
226
+ child.value.should eq fulfilled_value + 5
227
+ end
228
+
229
+ end
230
+
231
+ context 'rejected' do
232
+ it 'returns a new Promise when :rejected' do
233
+ p1 = rejected_subject
234
+ p2 = p1.then{}
235
+ p2.should be_a(Promise)
236
+ p1.should_not eq p2
237
+ end
238
+
239
+ it 'notifies rejection to new child' do
240
+ child = rejected_subject.then(Proc.new{ 7 }) { |v| v + 5 }
241
+ child.value.should eq 7
242
+ end
243
+
71
244
  end
72
245
 
73
246
  it 'can be called more than once' do
74
247
  p = pending_subject
75
248
  p1 = p.then{}
76
249
  p2 = p.then{}
77
- p1.object_id.should_not eq p2.object_id
250
+ p1.should_not be p2
78
251
  end
79
252
  end
80
253
 
81
- context '#rescue' do
82
-
83
- it 'returns self when a block is given' do
84
- p1 = pending_subject
85
- p2 = p1.rescue{}
86
- p1.object_id.should eq p2.object_id
254
+ describe 'on_success' do
255
+ it 'should have a block' do
256
+ expect { empty_root.on_success }.to raise_error(ArgumentError)
87
257
  end
88
258
 
89
- it 'returns self when no block is given' do
90
- p1 = pending_subject
91
- p2 = p1.rescue
92
- p1.object_id.should eq p2.object_id
259
+ it 'returns a new promise' do
260
+ child = empty_root.on_success { nil }
261
+ child.should be_a Promise
262
+ child.should_not be empty_root
93
263
  end
264
+ end
265
+
266
+ context '#rescue' do
94
267
 
95
- it 'accepts an exception class as the first parameter' do
96
- lambda {
97
- pending_subject.rescue(StandardError){}
98
- }.should_not raise_error
268
+ it 'returns a new promise' do
269
+ child = empty_root.rescue { nil }
270
+ child.should be_a Promise
271
+ child.should_not be empty_root
99
272
  end
100
273
  end
101
274
 
102
275
  context 'fulfillment' do
103
276
 
104
- it 'passes all arguments to the first promise in the chain' do
105
- @a = @b = @c = nil
106
- p = Promise.new(1, 2, 3) do |a, b, c|
107
- @a, @b, @c = a, b, c
108
- end
109
- sleep(0.1)
110
- [@a, @b, @c].should eq [1, 2, 3]
111
- end
112
-
113
277
  it 'passes the result of each block to all its children' do
114
- @expected = nil
115
- Promise.new(10){|a| a * 2 }.then{|result| @expected = result}
278
+ expected = nil
279
+ Promise.new{ 20 }.then{ |result| expected = result }.execute
116
280
  sleep(0.1)
117
- @expected.should eq 20
281
+ expected.should eq 20
118
282
  end
119
283
 
120
284
  it 'sets the promise value to the result if its block' do
121
- p = Promise.new(10){|a| a * 2 }.then{|result| result * 2}
285
+ root = Promise.new{ 20 }
286
+ p = root.then{ |result| result * 2}.execute
122
287
  sleep(0.1)
288
+ root.value.should eq 20
123
289
  p.value.should eq 40
124
290
  end
125
291
 
126
292
  it 'sets the promise state to :fulfilled if the block completes' do
127
- p = Promise.new(10){|a| a * 2 }.then{|result| result * 2}
293
+ p = Promise.new{ 10 * 2 }.then{|result| result * 2}.execute
128
294
  sleep(0.1)
129
295
  p.should be_fulfilled
130
296
  end
131
297
 
132
298
  it 'passes the last result through when a promise has no block' do
133
- @expected = nil
134
- Promise.new(10){|a| a * 2 }.then.then{|result| @expected = result}
299
+ expected = nil
300
+ Promise.new{ 20 }.then(Proc.new{}).then{|result| expected = result}.execute
301
+ sleep(0.1)
302
+ expected.should eq 20
303
+ end
304
+
305
+ it 'uses result as fulfillment value when a promise has no block' do
306
+ p = Promise.new{ 20 }.then(Proc.new{}).execute
307
+ sleep(0.1)
308
+ p.value.should eq 20
309
+ end
310
+
311
+ it 'can manage long chain' do
312
+ root = Promise.new { 20 }
313
+ p1 = root.then { |b| b * 3 }
314
+ p2 = root.then { |c| c + 2 }
315
+ p3 = p1.then { |d| d + 7 }
316
+
317
+ root.execute
135
318
  sleep(0.1)
136
- @expected.should eq 20
319
+
320
+ root.value.should eq 20
321
+ p1.value.should eq 60
322
+ p2.value.should eq 22
323
+ p3.value.should eq 67
137
324
  end
138
325
  end
139
326
 
140
327
  context 'rejection' do
141
328
 
142
- it 'sets the promise reason the error object on exception' do
143
- p = Promise.new{ sleep(0.1); raise StandardError.new('Boom!') }
144
- sleep(0.2)
145
- p.reason.should be_a(StandardError)
146
- p.reason.should.to_s =~ /Boom!/
329
+ it 'passes the reason to all its children' do
330
+ expected = nil
331
+ Promise.new{ raise ArgumentError }.then(Proc.new{ |reason| expected = reason }).execute
332
+ sleep(0.1)
333
+ expected.should be_a ArgumentError
147
334
  end
148
335
 
149
- it 'sets the promise state to :rejected on exception' do
150
- p = Promise.new{ raise StandardError.new('Boom!') }
336
+ it 'sets the promise value to the result if its block' do
337
+ root = Promise.new{ raise ArgumentError }
338
+ p = root.then(Proc.new{ |reason| 42 }).execute
339
+ sleep(0.1)
340
+ p.value.should eq 42
341
+ end
342
+
343
+ it 'sets the promise state to :rejected if the block completes' do
344
+ p = Promise.new{ raise ArgumentError }.execute
151
345
  sleep(0.1)
152
346
  p.should be_rejected
153
347
  end
154
348
 
155
- it 'recursively rejects all children' do
156
- p = Promise.new{ sleep(0.1); raise StandardError.new('Boom!') }
157
- promises = 10.times.collect{ p.then{ true } }
158
- sleep(0.5)
159
- 10.times.each{|i| promises[i].should be_rejected }
160
- end
161
-
162
- it 'skips processing rejected promises' do
163
- p = Promise.new{ sleep(0.1); raise StandardError.new('Boom!') }
164
- promises = 3.times.collect{ p.then{ true } }
165
- sleep(0.5)
166
- promises.each{|p| p.value.should_not be_true }
167
- end
168
-
169
- it 'calls the first exception block with a matching class' do
170
- @expected = nil
171
- Promise.new{ sleep(0.1); raise StandardError }.
172
- rescue(StandardError){|ex| @expected = 1 }.
173
- rescue(StandardError){|ex| @expected = 2 }.
174
- rescue(StandardError){|ex| @expected = 3 }
175
- sleep(0.2)
176
- @expected.should eq 1
177
- end
178
-
179
- it 'matches all with a rescue with no class given' do
180
- @expected = nil
181
- Promise.new{ sleep(0.1); raise NoMethodError }.
182
- rescue(LoadError){|ex| @expected = 1 }.
183
- rescue{|ex| @expected = 2 }.
184
- rescue(StandardError){|ex| @expected = 3 }
185
- sleep(0.2)
186
- @expected.should eq 2
187
- end
188
-
189
- it 'searches associated rescue handlers in order' do
190
- Promise.thread_pool = CachedThreadPool.new
191
-
192
- @expected = nil
193
- Promise.new{ sleep(0.1); raise ArgumentError }.
194
- rescue(ArgumentError){|ex| @expected = 1 }.
195
- rescue(LoadError){|ex| @expected = 2 }.
196
- rescue(StandardError){|ex| @expected = 3 }
197
- sleep(0.2)
198
- @expected.should eq 1
199
-
200
- @expected = nil
201
- Promise.new{ sleep(0.1); raise LoadError }.
202
- rescue(ArgumentError){|ex| @expected = 1 }.
203
- rescue(LoadError){|ex| @expected = 2 }.
204
- rescue(StandardError){|ex| @expected = 3 }
205
- sleep(0.2)
206
- @expected.should eq 2
207
-
208
- @expected = nil
209
- Promise.new{ sleep(0.1); raise StandardError }.
210
- rescue(ArgumentError){|ex| @expected = 1 }.
211
- rescue(LoadError){|ex| @expected = 2 }.
212
- rescue(StandardError){|ex| @expected = 3 }
213
- sleep(0.2)
214
- @expected.should eq 3
215
- end
216
-
217
- it 'passes the exception object to the matched block' do
218
- @expected = nil
219
- Promise.new{ sleep(0.1); raise StandardError }.
220
- rescue(ArgumentError){|ex| @expected = ex }.
221
- rescue(LoadError){|ex| @expected = ex }.
222
- rescue(StandardError){|ex| @expected = ex }
223
- sleep(0.2)
224
- @expected.should be_a(StandardError)
225
- end
226
-
227
- it 'ignores rescuers without a block' do
228
- @expected = nil
229
- Promise.new{ sleep(0.1); raise StandardError }.
230
- rescue(StandardError).
231
- rescue(StandardError){|ex| @expected = ex }
232
- sleep(0.2)
233
- @expected.should be_a(StandardError)
234
- end
235
-
236
- it 'supresses the exception if no rescue matches' do
237
- lambda {
238
- Promise.new{ sleep(0.1); raise StandardError }.
239
- rescue(ArgumentError){|ex| @expected = ex }.
240
- rescue(NotImplementedError){|ex| @expected = ex }.
241
- rescue(NoMethodError){|ex| @expected = ex }
242
- sleep(0.2)
243
- }.should_not raise_error
244
- end
245
-
246
- it 'supresses exceptions thrown from rescue handlers' do
247
- lambda {
248
- Promise.new{ sleep(0.1); raise ArgumentError }.
249
- rescue(StandardError){ raise StandardError }
250
- sleep(0.2)
251
- }.should_not raise_error
252
- end
253
-
254
- it 'calls matching rescue handlers on all children' do
255
- @expected = []
256
- Promise.new{ sleep(0.1); raise StandardError }.
257
- then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
258
- then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
259
- then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
260
- then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
261
- then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }
262
- sleep(1)
263
-
264
- @expected.length.should eq 5
265
- end
266
-
267
- it 'matches a rescue handler added after rejection' do
268
- @expected = false
269
- p = Promise.new{ sleep(0.1); raise StandardError }
270
- sleep(0.2)
271
- p.rescue(StandardError){ @expected = true }
272
- @expected.should be_true
349
+ it 'uses reason as rejection reason when a promise has no rescue callable' do
350
+ p = Promise.new{ raise ArgumentError }.then { |val| val }.execute
351
+ sleep(0.1)
352
+ p.should be_rejected
353
+ p.reason.should be_a ArgumentError
273
354
  end
355
+
274
356
  end
275
357
 
276
358
  context 'aliases' do
@@ -284,17 +366,13 @@ module Concurrent
284
366
  end
285
367
 
286
368
  it 'aliases #catch for #rescue' do
287
- @expected = nil
288
- Promise.new{ raise StandardError }.catch{ @expected = true }
289
- sleep(0.1)
290
- @expected.should be_true
369
+ child = rejected_subject.catch { 7 }
370
+ child.value.should eq 7
291
371
  end
292
372
 
293
373
  it 'aliases #on_error for #rescue' do
294
- @expected = nil
295
- Promise.new{ raise StandardError }.on_error{ @expected = true }
296
- sleep(0.1)
297
- @expected.should be_true
374
+ child = rejected_subject.on_error { 7 }
375
+ child.value.should eq 7
298
376
  end
299
377
  end
300
378
  end