concurrent-ruby 0.3.2 → 0.4.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.
@@ -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