concurrent-ruby 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+ require_relative 'postable_shared'
3
+ require_relative 'runnable_shared'
4
+ require_relative 'stoppable_shared'
5
+
6
+ module Concurrent
7
+
8
+ describe Channel do
9
+
10
+ context :runnable do
11
+ subject{ Channel.new{ nil } }
12
+ it_should_behave_like :runnable
13
+ end
14
+
15
+ context :stoppable do
16
+ subject do
17
+ task = Channel.new{ nil }
18
+ task.run!
19
+ task
20
+ end
21
+ it_should_behave_like :stoppable
22
+ end
23
+
24
+ context :postable do
25
+
26
+ let!(:postable_class){ Channel }
27
+
28
+ let(:sender) do
29
+ Channel.new do |*message|
30
+ if message.first.is_a?(Exception)
31
+ raise message.first
32
+ else
33
+ message.first
34
+ end
35
+ end
36
+ end
37
+
38
+ let(:receiver){ Channel.new{|*message| message.first } }
39
+
40
+ it_should_behave_like :postable
41
+ end
42
+
43
+ subject{ Channel.new{ nil } }
44
+
45
+ context '#initialize' do
46
+
47
+ it 'raises an exception if no block is given' do
48
+ expect {
49
+ Channel.new
50
+ }.to raise_error(ArgumentError)
51
+ end
52
+ end
53
+
54
+ context '#behavior' do
55
+
56
+ it 'calls the block once for each message' do
57
+ @expected = false
58
+ channel = Channel.new{ @expected = true }
59
+ channel.run!
60
+ channel << 42
61
+ sleep(0.1)
62
+ channel.stop
63
+ @expected.should be_true
64
+ end
65
+
66
+ it 'passes all arguments to the block' do
67
+ @expected = []
68
+ channel = Channel.new{|*message| @expected = message }
69
+ channel.run!
70
+ channel.post(1,2,3,4,5)
71
+ sleep(0.1)
72
+ channel.stop
73
+ @expected.should eq [1,2,3,4,5]
74
+ end
75
+ end
76
+
77
+ context '#pool' do
78
+
79
+ it 'passes a duplicate of the block to each channel in the pool' do
80
+ block = proc{ nil }
81
+ block.should_receive(:dup).exactly(5).times.and_return(proc{ nil })
82
+ mbox, pool = Channel.pool(5, &block)
83
+ end
84
+ end
85
+ end
86
+ end
@@ -2,7 +2,6 @@ require 'spec_helper'
2
2
 
3
3
  require 'concurrent/agent'
4
4
  require 'concurrent/future'
5
- require 'concurrent/goroutine'
6
5
  require 'concurrent/promise'
7
6
 
8
7
  if mri?
@@ -52,23 +51,6 @@ if mri?
52
51
 
53
52
  context 'operation' do
54
53
 
55
- context 'goroutine' do
56
-
57
- it 'passes all arguments to the block' do
58
- $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
59
-
60
- EventMachine.run do
61
-
62
- @expected = nil
63
- go(1, 2, 3){|a, b, c| @expected = [c, b, a] }
64
- sleep(0.1)
65
- @expected.should eq [3, 2, 1]
66
-
67
- EventMachine.stop
68
- end
69
- end
70
- end
71
-
72
54
  context Agent do
73
55
 
74
56
  subject { Agent.new(0) }
@@ -21,7 +21,7 @@ module Concurrent
21
21
  end
22
22
 
23
23
  it 'returns false if the event is unset' do
24
- subject.reset
24
+ #subject.reset
25
25
  subject.should_not be_set
26
26
  end
27
27
  end
@@ -29,7 +29,7 @@ module Concurrent
29
29
  context '#set' do
30
30
 
31
31
  it 'triggers the event' do
32
- subject.reset
32
+ #subject.reset
33
33
  @expected = false
34
34
  Thread.new{ subject.wait; @expected = true }
35
35
  sleep(0.1)
@@ -46,12 +46,47 @@ module Concurrent
46
46
 
47
47
  context '#reset' do
48
48
 
49
- it 'sets the state to unset' do
49
+ it 'does not change the state of an unset event' do
50
+ subject.reset
51
+ subject.should_not be_set
52
+ end
53
+
54
+ it 'does not trigger an unset event' do
55
+ @expected = false
56
+ Thread.new{ subject.wait; @expected = true }
57
+ sleep(0.1)
58
+ subject.reset
59
+ sleep(0.1)
60
+ @expected.should be_false
61
+ end
62
+
63
+ it 'does not interrupt waiting threads when event is unset' do
64
+ @expected = false
65
+ Thread.new{ subject.wait; @expected = true }
66
+ sleep(0.1)
67
+ subject.reset
68
+ sleep(0.1)
69
+ subject.set
70
+ sleep(0.1)
71
+ @expected.should be_true
72
+ end
73
+
74
+ it 'returns true when called on an unset event' do
75
+ subject.reset.should be_true
76
+ end
77
+
78
+ it 'sets the state of a set event to unset' do
50
79
  subject.set
51
80
  subject.should be_set
52
81
  subject.reset
53
82
  subject.should_not be_set
54
83
  end
84
+
85
+ it 'returns true when called on a set event' do
86
+ subject.set
87
+ subject.should be_set
88
+ subject.reset.should be_true
89
+ end
55
90
  end
56
91
 
57
92
  context '#wait' do
@@ -1,5 +1,4 @@
1
1
  require 'spec_helper'
2
- require 'concurrent/goroutine'
3
2
  require_relative 'uses_global_thread_pool_shared'
4
3
 
5
4
  module Concurrent
@@ -62,23 +61,5 @@ module Concurrent
62
61
  subject << proc{ nil }
63
62
  end
64
63
  end
65
-
66
- context 'operation' do
67
-
68
- context 'goroutine' do
69
-
70
- it 'gets a new thread' do
71
- $GLOBAL_THREAD_POOL = subject
72
-
73
- t = Thread.new{ nil }
74
-
75
- Thread.should_receive(:new).with(no_args()).and_return(t)
76
- go{ nil }
77
-
78
- Thread.should_receive(:new).with(1,2,3).and_return(t)
79
- go(1,2,3){ nil }
80
- end
81
- end
82
- end
83
64
  end
84
65
  end
@@ -0,0 +1,222 @@
1
+ require 'spec_helper'
2
+
3
+ share_examples_for :postable do
4
+
5
+ after(:each) do
6
+ subject.stop
7
+ @thread.kill unless @thread.nil?
8
+ sleep(0.1)
9
+ end
10
+
11
+ context '#post' do
12
+
13
+ it 'raises an exception when the message is empty' do
14
+ expect {
15
+ subject.post
16
+ }.to raise_error(ArgumentError)
17
+ end
18
+
19
+ it 'returns false when not running' do
20
+ subject.post(42).should be_false
21
+ end
22
+
23
+ it 'pushes a message onto the queue' do
24
+ @expected = false
25
+ postable = postable_class.new{|msg| @expected = msg }
26
+ @thread = Thread.new{ postable.run }
27
+ @thread.join(0.1)
28
+ postable.post(true)
29
+ @thread.join(0.1)
30
+ @expected.should be_true
31
+ postable.stop
32
+ end
33
+
34
+ it 'returns the current size of the queue' do
35
+ postable = postable_class.new{|msg| sleep }
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
43
+ postable.stop
44
+ end
45
+
46
+ it 'is aliased a <<' do
47
+ @expected = false
48
+ postable = postable_class.new{|msg| @expected = msg }
49
+ @thread = Thread.new{ postable.run }
50
+ @thread.join(0.1)
51
+ postable << true
52
+ @thread.join(0.1)
53
+ @expected.should be_true
54
+ postable.stop
55
+ end
56
+ end
57
+
58
+ context '#post?' do
59
+
60
+ it 'returns nil when not running' do
61
+ subject.post?(42).should be_false
62
+ end
63
+
64
+ it 'returns an Obligation' do
65
+ postable = postable_class.new{ nil }
66
+ @thread = Thread.new{ postable.run }
67
+ @thread.join(0.1)
68
+ obligation = postable.post?(nil)
69
+ obligation.should be_a(Concurrent::Obligation)
70
+ postable.stop
71
+ end
72
+
73
+ it 'fulfills the obligation on success' do
74
+ postable = postable_class.new{|msg| @expected = msg }
75
+ @thread = Thread.new{ postable.run }
76
+ @thread.join(0.1)
77
+ obligation = postable.post?(42)
78
+ @thread.join(0.1)
79
+ obligation.should be_fulfilled
80
+ obligation.value.should == 42
81
+ postable.stop
82
+ end
83
+
84
+ it 'rejects the obligation on failure' do
85
+ postable = postable_class.new{|msg| raise StandardError.new('Boom!') }
86
+ @thread = Thread.new{ postable.run }
87
+ @thread.join(0.1)
88
+ obligation = postable.post?(42)
89
+ @thread.join(0.1)
90
+ obligation.should be_rejected
91
+ obligation.reason.should be_a(StandardError)
92
+ postable.stop
93
+ end
94
+ end
95
+
96
+ context '#post!' do
97
+
98
+ it 'raises Concurrent::Runnable::LifecycleError when not running' do
99
+ expect {
100
+ subject.post!(1, 'Hello World!')
101
+ }.to raise_error(Concurrent::Runnable::LifecycleError)
102
+ end
103
+
104
+ it 'blocks for up to the given number of seconds' do
105
+ postable = postable_class.new{|msg| sleep }
106
+ @thread = Thread.new{ postable.run }
107
+ @thread.join(0.1)
108
+ start = Time.now.to_i
109
+ expect {
110
+ postable.post!(2, nil)
111
+ }.to raise_error
112
+ elapsed = Time.now.to_i - start
113
+ elapsed.should >= 2
114
+ postable.stop
115
+ end
116
+
117
+ it 'raises Concurrent::TimeoutError when seconds is zero' do
118
+ postable = postable_class.new{|msg| 42 }
119
+ @thread = Thread.new{ postable.run }
120
+ @thread.join(0.1)
121
+ expect {
122
+ postable.post!(0, nil)
123
+ }.to raise_error(Concurrent::TimeoutError)
124
+ postable.stop
125
+ end
126
+
127
+ it 'raises Concurrent::TimeoutError on timeout' do
128
+ postable = postable_class.new{|msg| sleep }
129
+ @thread = Thread.new{ postable.run }
130
+ @thread.join(0.1)
131
+ expect {
132
+ postable.post!(1, nil)
133
+ }.to raise_error(Concurrent::TimeoutError)
134
+ postable.stop
135
+ end
136
+
137
+ it 'bubbles the exception on error' do
138
+ postable = postable_class.new{|msg| raise StandardError.new('Boom!') }
139
+ @thread = Thread.new{ postable.run }
140
+ @thread.join(0.1)
141
+ expect {
142
+ postable.post!(1, nil)
143
+ }.to raise_error(StandardError)
144
+ postable.stop
145
+ end
146
+
147
+ it 'returns the result on success' do
148
+ postable = postable_class.new{|msg| 42 }
149
+ @thread = Thread.new{ postable.run }
150
+ @thread.join(0.1)
151
+ expected = postable.post!(1, nil)
152
+ expected.should == 42
153
+ postable.stop
154
+ end
155
+
156
+ it 'attempts to cancel the operation on timeout' do
157
+ @expected = 0
158
+ postable = postable_class.new{|msg| sleep(0.5); @expected += 1 }
159
+ @thread = Thread.new{ postable.run }
160
+ @thread.join(0.1)
161
+ postable.post(nil) # block the postable
162
+ expect {
163
+ postable.post!(0.1, nil)
164
+ }.to raise_error(Concurrent::TimeoutError)
165
+ sleep(1.5)
166
+ @expected.should == 1
167
+ postable.stop
168
+ end
169
+ end
170
+
171
+ context '#forward' do
172
+
173
+ let(:observer) { double('observer') }
174
+
175
+ before(:each) do
176
+ @sender = Thread.new{ sender.run }
177
+ @receiver = Thread.new{ receiver.run }
178
+ @sender.join(0.1)
179
+ @receiver.join(0.1)
180
+ end
181
+
182
+ after(:each) do
183
+ sender.stop
184
+ receiver.stop
185
+ sleep(0.1)
186
+ @sender.kill unless @sender.nil?
187
+ @receiver.kill unless @receiver.nil?
188
+ end
189
+
190
+ it 'returns false when sender not running' do
191
+ sender.stop
192
+ sleep(0.1)
193
+ sender.forward(receiver, 'Hello World!').should be_false
194
+ end
195
+
196
+ it 'forwards the result to the receiver on success' do
197
+ receiver.should_receive(:post).with(42)
198
+ sender.forward(receiver, 42)
199
+ sleep(0.1)
200
+ end
201
+
202
+ it 'does not forward on exception' do
203
+ receiver.should_not_receive(:post).with(42)
204
+ sender.forward(receiver, StandardError.new)
205
+ sleep(0.1)
206
+ end
207
+
208
+ it 'notifies observers on success' do
209
+ observer.should_receive(:update).with(any_args())
210
+ sender.add_observer(observer)
211
+ sender.forward(receiver, 42)
212
+ sleep(0.1)
213
+ end
214
+
215
+ it 'notifies observers on exception' do
216
+ observer.should_not_receive(:update).with(any_args())
217
+ sender.add_observer(observer)
218
+ sender.forward(receiver, StandardError.new)
219
+ sleep(0.1)
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ share_examples_for :stoppable do
4
+
5
+ after(:each) do
6
+ subject.stop
7
+ end
8
+
9
+ context 'stopping' do
10
+
11
+ it 'raises an exception when #before_stop does not receive a block' do
12
+ expect {
13
+ subject.before_stop
14
+ }.to raise_error(ArgumentError)
15
+ end
16
+
17
+ it 'raises an exception if #before_stop is called more than once' do
18
+ subject.before_stop{ nil }
19
+ expect {
20
+ subject.before_stop{ nil }
21
+ }.to raise_error(Concurrent::Runnable::LifecycleError)
22
+ end
23
+
24
+ it 'returns self from #before_stop' do
25
+ task = subject
26
+ task.before_stop{ nil }.should eq task
27
+ end
28
+
29
+ it 'calls the #before_stop block when stopping' do
30
+ @expected = false
31
+ subject.before_stop{ @expected = true }
32
+ subject.stop
33
+ sleep(0.1)
34
+ @expected.should be_true
35
+ end
36
+ end
37
+ end