concurrent-ruby 0.4.1 → 0.5.0.pre.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.
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