concurrent-ruby 0.1.1 → 0.2.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 +4 -4
- data/README.md +48 -1
- data/lib/concurrent.rb +8 -1
- data/lib/concurrent/agent.rb +19 -40
- data/lib/concurrent/cached_thread_pool.rb +10 -11
- data/lib/concurrent/defer.rb +8 -12
- data/lib/concurrent/executor.rb +95 -0
- data/lib/concurrent/fixed_thread_pool.rb +12 -6
- data/lib/concurrent/functions.rb +120 -0
- data/lib/concurrent/future.rb +8 -20
- data/lib/concurrent/global_thread_pool.rb +13 -0
- data/lib/concurrent/goroutine.rb +5 -1
- data/lib/concurrent/null_thread_pool.rb +22 -0
- data/lib/concurrent/obligation.rb +10 -64
- data/lib/concurrent/promise.rb +38 -60
- data/lib/concurrent/reactor.rb +166 -0
- data/lib/concurrent/reactor/drb_async_demux.rb +83 -0
- data/lib/concurrent/reactor/tcp_sync_demux.rb +131 -0
- data/lib/concurrent/supervisor.rb +100 -0
- data/lib/concurrent/thread_pool.rb +16 -5
- data/lib/concurrent/utilities.rb +8 -0
- data/lib/concurrent/version.rb +1 -1
- data/md/defer.md +4 -4
- data/md/executor.md +187 -0
- data/md/promise.md +2 -0
- data/md/thread_pool.md +27 -0
- data/spec/concurrent/agent_spec.rb +8 -27
- data/spec/concurrent/cached_thread_pool_spec.rb +14 -1
- data/spec/concurrent/defer_spec.rb +17 -21
- data/spec/concurrent/event_machine_defer_proxy_spec.rb +159 -149
- data/spec/concurrent/executor_spec.rb +200 -0
- data/spec/concurrent/fixed_thread_pool_spec.rb +2 -3
- data/spec/concurrent/functions_spec.rb +217 -0
- data/spec/concurrent/future_spec.rb +4 -11
- data/spec/concurrent/global_thread_pool_spec.rb +38 -0
- data/spec/concurrent/goroutine_spec.rb +15 -0
- data/spec/concurrent/null_thread_pool_spec.rb +54 -0
- data/spec/concurrent/obligation_shared.rb +127 -116
- data/spec/concurrent/promise_spec.rb +16 -14
- data/spec/concurrent/reactor/drb_async_demux_spec.rb +196 -0
- data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +410 -0
- data/spec/concurrent/reactor_spec.rb +364 -0
- data/spec/concurrent/supervisor_spec.rb +258 -0
- data/spec/concurrent/thread_pool_shared.rb +156 -161
- data/spec/concurrent/utilities_spec.rb +30 -1
- data/spec/spec_helper.rb +13 -0
- metadata +38 -9
@@ -9,7 +9,7 @@ module Concurrent
|
|
9
9
|
let!(:rejected_reason) { StandardError.new('mojo jojo') }
|
10
10
|
|
11
11
|
let(:pending_subject) do
|
12
|
-
Promise.new{ sleep(
|
12
|
+
Promise.new{ sleep(3); fulfilled_value }
|
13
13
|
end
|
14
14
|
|
15
15
|
let(:fulfilled_subject) do
|
@@ -22,10 +22,10 @@ module Concurrent
|
|
22
22
|
end
|
23
23
|
|
24
24
|
before(:each) do
|
25
|
-
|
25
|
+
Promise.thread_pool = FixedThreadPool.new(1)
|
26
26
|
end
|
27
27
|
|
28
|
-
it_should_behave_like Obligation
|
28
|
+
it_should_behave_like Concurrent::Obligation
|
29
29
|
|
30
30
|
context 'behavior' do
|
31
31
|
|
@@ -163,10 +163,9 @@ module Concurrent
|
|
163
163
|
end
|
164
164
|
|
165
165
|
it 'recursively rejects all children' do
|
166
|
-
p = Promise.new{
|
166
|
+
p = Promise.new{ raise StandardError.new('Boom!') }
|
167
167
|
promises = 10.times.collect{ p.then{ true } }
|
168
168
|
sleep(0.1)
|
169
|
-
|
170
169
|
10.times.each{|i| promises[i].should be_rejected }
|
171
170
|
end
|
172
171
|
|
@@ -183,7 +182,7 @@ module Concurrent
|
|
183
182
|
rescue(StandardError){|ex| @expected = 1 }.
|
184
183
|
rescue(StandardError){|ex| @expected = 2 }.
|
185
184
|
rescue(StandardError){|ex| @expected = 3 }
|
186
|
-
|
185
|
+
sleep(0.1)
|
187
186
|
@expected.should eq 1
|
188
187
|
end
|
189
188
|
|
@@ -198,6 +197,8 @@ module Concurrent
|
|
198
197
|
end
|
199
198
|
|
200
199
|
it 'searches associated rescue handlers in order' do
|
200
|
+
Promise.thread_pool = CachedThreadPool.new
|
201
|
+
|
201
202
|
@expected = nil
|
202
203
|
Promise.new{ raise ArgumentError }.
|
203
204
|
rescue(ArgumentError){|ex| @expected = 1 }.
|
@@ -263,7 +264,7 @@ module Concurrent
|
|
263
264
|
|
264
265
|
it 'calls matching rescue handlers on all children' do
|
265
266
|
@expected = []
|
266
|
-
Promise.new{
|
267
|
+
Promise.new{ raise StandardError }.
|
267
268
|
then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
|
268
269
|
then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
|
269
270
|
then{ sleep(0.1) }.rescue{ @expected << 'Boom!' }.
|
@@ -273,6 +274,14 @@ module Concurrent
|
|
273
274
|
|
274
275
|
@expected.length.should eq 5
|
275
276
|
end
|
277
|
+
|
278
|
+
it 'matches a rescue handler added after rejection' do
|
279
|
+
@expected = false
|
280
|
+
p = Promise.new{ raise StandardError }
|
281
|
+
sleep(0.1)
|
282
|
+
p.rescue(StandardError){ @expected = true }
|
283
|
+
@expected.should be_true
|
284
|
+
end
|
276
285
|
end
|
277
286
|
|
278
287
|
context 'aliases' do
|
@@ -298,13 +307,6 @@ module Concurrent
|
|
298
307
|
sleep(0.1)
|
299
308
|
@expected.should be_true
|
300
309
|
end
|
301
|
-
|
302
|
-
it 'aliases Kernel#promise for Promise.new' do
|
303
|
-
promise().should be_a(Promise)
|
304
|
-
promise(){ nil }.should be_a(Promise)
|
305
|
-
promise(1, 2, 3).should be_a(Promise)
|
306
|
-
promise(1, 2, 3){ nil }.should be_a(Promise)
|
307
|
-
end
|
308
310
|
end
|
309
311
|
end
|
310
312
|
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'faker'
|
3
|
+
|
4
|
+
module Concurrent
|
5
|
+
class Reactor
|
6
|
+
|
7
|
+
describe DRbAsyncDemux, not_on_travis: true do
|
8
|
+
|
9
|
+
subject{ DRbAsyncDemux.new }
|
10
|
+
|
11
|
+
after(:each) do
|
12
|
+
DRb.stop_service
|
13
|
+
end
|
14
|
+
|
15
|
+
def post_event(demux, event, *args)
|
16
|
+
there = DRbObject.new_with_uri(DRbAsyncDemux::DEFAULT_URI)
|
17
|
+
there.send(event, *args)
|
18
|
+
end
|
19
|
+
|
20
|
+
context '#initialize' do
|
21
|
+
|
22
|
+
it 'sets the initial state to :stopped' do
|
23
|
+
subject.should_not be_running
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'uses the given URI' do
|
27
|
+
uri = 'druby://concurrent-ruby.com:4242'
|
28
|
+
demux = DRbAsyncDemux.new(uri: uri)
|
29
|
+
demux.uri.should eq uri
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'uses the default URI when none given' do
|
33
|
+
demux = DRbAsyncDemux.new
|
34
|
+
demux.uri.should eq DRbAsyncDemux::DEFAULT_URI
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'uses the given ACL' do
|
38
|
+
acl = %w[deny all]
|
39
|
+
ACL.should_receive(:new).with(acl).and_return(acl)
|
40
|
+
demux = DRbAsyncDemux.new(acl: acl)
|
41
|
+
demux.acl.should eq acl
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'uses the default ACL when given' do
|
45
|
+
acl = DRbAsyncDemux::DEFAULT_ACL
|
46
|
+
ACL.should_receive(:new).with(acl).and_return(acl)
|
47
|
+
demux = DRbAsyncDemux.new
|
48
|
+
demux.acl.should eq acl
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context '#run' do
|
53
|
+
|
54
|
+
it 'raises an exception if already runed' do
|
55
|
+
subject.run
|
56
|
+
|
57
|
+
lambda {
|
58
|
+
subject.run
|
59
|
+
}.should raise_error(StandardError)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'installs the ACL' do
|
63
|
+
acl = %w[deny all]
|
64
|
+
ACL.should_receive(:new).once.with(acl).and_return(acl)
|
65
|
+
DRb.should_receive(:install_acl).once.with(acl)
|
66
|
+
demux = DRbAsyncDemux.new(acl: acl)
|
67
|
+
reactor = Concurrent::Reactor.new(demux)
|
68
|
+
Thread.new{ reactor.run }
|
69
|
+
sleep(0.1)
|
70
|
+
reactor.stop
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'runs DRb' do
|
74
|
+
uri = DRbAsyncDemux::DEFAULT_URI
|
75
|
+
DRb.should_receive(:start_service).with(uri, anything())
|
76
|
+
demux = DRbAsyncDemux.new(uri: uri)
|
77
|
+
reactor = Concurrent::Reactor.new(demux)
|
78
|
+
Thread.new{ reactor.run }
|
79
|
+
sleep(0.1)
|
80
|
+
reactor.stop
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context '#stop' do
|
85
|
+
|
86
|
+
it 'stops DRb' do
|
87
|
+
DRb.should_receive(:stop_service).at_least(1).times
|
88
|
+
subject.run
|
89
|
+
sleep(0.1)
|
90
|
+
subject.stop
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context '#running?' do
|
95
|
+
|
96
|
+
it 'returns false when stopped' do
|
97
|
+
subject.run
|
98
|
+
sleep(0.1)
|
99
|
+
subject.stop
|
100
|
+
sleep(0.1)
|
101
|
+
subject.should_not be_running
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'returns true when running' do
|
105
|
+
subject.run
|
106
|
+
sleep(0.1)
|
107
|
+
subject.should be_running
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context '#set_reactor' do
|
112
|
+
|
113
|
+
it 'raises an exception when given an invalid reactor' do
|
114
|
+
lambda {
|
115
|
+
subject.set_reactor(Concurrent::Reactor.new)
|
116
|
+
}.should_not raise_error
|
117
|
+
|
118
|
+
lambda {
|
119
|
+
subject.set_reactor('bogus')
|
120
|
+
}.should raise_error(ArgumentError)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context 'event handling' do
|
125
|
+
|
126
|
+
it 'should post events to the reactor' do
|
127
|
+
demux = subject
|
128
|
+
reactor = Concurrent::Reactor.new(demux)
|
129
|
+
reactor.add_handler(:foo){ nil }
|
130
|
+
reactor.should_receive(:handle).with(:foo, 1,2,3).and_return([:ok, nil])
|
131
|
+
|
132
|
+
Thread.new { reactor.run }
|
133
|
+
sleep(0.1)
|
134
|
+
|
135
|
+
post_event(demux, :foo, 1, 2, 3)
|
136
|
+
|
137
|
+
reactor.stop
|
138
|
+
sleep(0.1)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
specify 'integration', not_on_travis: true do
|
143
|
+
|
144
|
+
# server
|
145
|
+
demux = Concurrent::Reactor::DRbAsyncDemux.new
|
146
|
+
reactor = Concurrent::Reactor.new(demux)
|
147
|
+
|
148
|
+
reactor.add_handler(:echo) {|message| message }
|
149
|
+
reactor.add_handler(:error) {|message| raise StandardError.new(message) }
|
150
|
+
reactor.add_handler(:unknown) {|message| message }
|
151
|
+
reactor.add_handler(:abend) {|message| message }
|
152
|
+
|
153
|
+
t = Thread.new { reactor.run }
|
154
|
+
sleep(0.1)
|
155
|
+
|
156
|
+
# client
|
157
|
+
there = DRbObject.new_with_uri(DRbAsyncDemux::DEFAULT_URI)
|
158
|
+
|
159
|
+
# test :ok
|
160
|
+
10.times do
|
161
|
+
message = Faker::Company.bs
|
162
|
+
echo = there.echo(message)
|
163
|
+
echo.should eq message
|
164
|
+
end
|
165
|
+
|
166
|
+
# test :ex
|
167
|
+
lambda {
|
168
|
+
echo = there.error('error')
|
169
|
+
}.should raise_error(StandardError, 'error')
|
170
|
+
|
171
|
+
# test :noop
|
172
|
+
lambda {
|
173
|
+
echo = there.bogus('bogus')
|
174
|
+
}.should raise_error(NoMethodError)
|
175
|
+
|
176
|
+
# test unknown respone code
|
177
|
+
reactor.should_receive(:handle).with(:unknown).and_return([:unknown, nil])
|
178
|
+
lambda {
|
179
|
+
echo = there.unknown
|
180
|
+
}.should raise_error(DRb::DRbError)
|
181
|
+
|
182
|
+
# test handler error
|
183
|
+
reactor.should_receive(:handle).with(:abend).and_raise(ArgumentError)
|
184
|
+
lambda {
|
185
|
+
echo = there.abend
|
186
|
+
}.should raise_error(DRb::DRbRemoteError)
|
187
|
+
|
188
|
+
# cleanup
|
189
|
+
reactor.stop
|
190
|
+
sleep(0.1)
|
191
|
+
Thread.kill(t)
|
192
|
+
sleep(0.1)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,410 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'faker'
|
3
|
+
|
4
|
+
module Concurrent
|
5
|
+
class Reactor
|
6
|
+
|
7
|
+
describe TcpSyncDemux, not_on_travis: true do
|
8
|
+
|
9
|
+
subject do
|
10
|
+
@subject = TcpSyncDemux.new(port: 2000 + rand(8000))
|
11
|
+
end
|
12
|
+
|
13
|
+
after(:each) do
|
14
|
+
@subject.stop unless @subject.nil?
|
15
|
+
end
|
16
|
+
|
17
|
+
context '#initialize' do
|
18
|
+
|
19
|
+
it 'sets the initial state to :stopped' do
|
20
|
+
subject.should_not be_running
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'raises an exception if already runed' do
|
24
|
+
subject.run
|
25
|
+
|
26
|
+
lambda {
|
27
|
+
subject.run
|
28
|
+
}.should raise_error(StandardError)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'uses the given host' do
|
32
|
+
demux = TcpSyncDemux.new(host: 'www.foobar.com')
|
33
|
+
demux.host.should eq 'www.foobar.com'
|
34
|
+
demux.stop
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'uses the default host when none is given' do
|
38
|
+
demux = TcpSyncDemux.new
|
39
|
+
demux.host.should eq TcpSyncDemux::DEFAULT_HOST
|
40
|
+
demux.stop
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'uses the given port' do
|
44
|
+
demux = TcpSyncDemux.new(port: 4242)
|
45
|
+
demux.port.should eq 4242
|
46
|
+
demux.stop
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'uses the default port when none is given' do
|
50
|
+
demux = TcpSyncDemux.new
|
51
|
+
demux.port.should eq TcpSyncDemux::DEFAULT_PORT
|
52
|
+
demux.stop
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'uses the given ACL' do
|
56
|
+
acl = %w[deny all]
|
57
|
+
ACL.should_receive(:new).with(acl).and_return(acl)
|
58
|
+
demux = TcpSyncDemux.new(acl: acl)
|
59
|
+
demux.acl.should eq acl
|
60
|
+
demux.stop
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'uses the default ACL when given' do
|
64
|
+
acl = TcpSyncDemux::DEFAULT_ACL
|
65
|
+
ACL.should_receive(:new).with(acl).and_return(acl)
|
66
|
+
demux = TcpSyncDemux.new
|
67
|
+
demux.acl.should eq acl
|
68
|
+
demux.stop
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context '#run' do
|
73
|
+
|
74
|
+
it 'creates a new TCP server' do
|
75
|
+
TCPServer.should_receive(:new).with(TcpSyncDemux::DEFAULT_HOST, anything())
|
76
|
+
subject.run
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'returns true on success' do
|
80
|
+
TCPServer.stub(:new).with(TcpSyncDemux::DEFAULT_HOST, anything())
|
81
|
+
subject.run.should be_true
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'returns false on failure' do
|
85
|
+
TCPServer.stub(:new).with(TcpSyncDemux::DEFAULT_HOST, anything()) \
|
86
|
+
.and_raise(StandardError)
|
87
|
+
subject.run.should be_false
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'raises an exception when already running' do
|
91
|
+
subject.run
|
92
|
+
lambda {
|
93
|
+
subject.run
|
94
|
+
}.should raise_error
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context '#stop' do
|
99
|
+
|
100
|
+
let(:server){ double('tcp server') }
|
101
|
+
let(:socket){ double('tcp socket') }
|
102
|
+
|
103
|
+
before(:each) do
|
104
|
+
socket.stub(:close).with(no_args())
|
105
|
+
server.stub(:close).with(no_args())
|
106
|
+
subject.run
|
107
|
+
subject.instance_variable_set(:@socket, socket)
|
108
|
+
subject.instance_variable_set(:@server, server)
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'immediately returns true when not running' do
|
112
|
+
socket.should_not_receive(:close)
|
113
|
+
server.should_not_receive(:close)
|
114
|
+
demux = TcpSyncDemux.new
|
115
|
+
demux.stop.should be_true
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'closes the socket' do
|
119
|
+
socket.should_receive(:close).with(no_args())
|
120
|
+
subject.stop
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'closes the TCP server' do
|
124
|
+
server.should_receive(:close).with(no_args())
|
125
|
+
subject.stop
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'is supresses socket close exceptions' do
|
129
|
+
socket.should_receive(:close).and_raise(SocketError)
|
130
|
+
lambda {
|
131
|
+
subject.stop
|
132
|
+
}.should_not raise_error
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'supresses server close exceptions' do
|
136
|
+
server.should_receive(:close).and_raise(SocketError)
|
137
|
+
lambda {
|
138
|
+
subject.stop
|
139
|
+
}.should_not raise_error
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context '#running?' do
|
144
|
+
|
145
|
+
it 'returns false when stopped' do
|
146
|
+
subject.run
|
147
|
+
sleep(0.1)
|
148
|
+
subject.stop
|
149
|
+
sleep(0.1)
|
150
|
+
subject.should_not be_running
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'returns true when running' do
|
154
|
+
subject.run
|
155
|
+
sleep(0.1)
|
156
|
+
subject.should be_running
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
context '#reset' do
|
161
|
+
|
162
|
+
it 'closes the demux' do
|
163
|
+
subject.should_receive(:run).exactly(2).times.and_return(true)
|
164
|
+
subject.run
|
165
|
+
sleep(0.1)
|
166
|
+
subject.reset
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'starts the demux' do
|
170
|
+
# add one call to #stop for the :after clause
|
171
|
+
subject.should_receive(:stop).exactly(2).times.and_return(true)
|
172
|
+
sleep(0.1)
|
173
|
+
subject.reset
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
context '#accept' do
|
178
|
+
|
179
|
+
let!(:event){ :echo }
|
180
|
+
let!(:message){ 'hello world' }
|
181
|
+
|
182
|
+
def setup_demux
|
183
|
+
@demux = TcpSyncDemux.new(host: 'localhost', port: 5555, acl: %w[allow all])
|
184
|
+
@demux.run
|
185
|
+
end
|
186
|
+
|
187
|
+
def send_event_message
|
188
|
+
there = TCPSocket.open('localhost', 5555)
|
189
|
+
@thread = Thread.new do
|
190
|
+
there.puts(@demux.format_message(event, message))
|
191
|
+
end
|
192
|
+
|
193
|
+
@expected = nil
|
194
|
+
Timeout::timeout(2) do
|
195
|
+
@expected = @demux.accept
|
196
|
+
end
|
197
|
+
@thread.join(1)
|
198
|
+
end
|
199
|
+
|
200
|
+
after(:each) do
|
201
|
+
@demux.stop unless @demux.nil?
|
202
|
+
Thread.kill(@thread) unless @thread.nil?
|
203
|
+
@expected = @demux = @thread = nil
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'returns a correct EventContext object' do
|
207
|
+
setup_demux
|
208
|
+
send_event_message
|
209
|
+
@expected.should be_a(EventContext)
|
210
|
+
@expected.event.should eq :echo
|
211
|
+
@expected.args.should eq ['hello world']
|
212
|
+
@expected.callback.should be_nil
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'returns nil on exception' do
|
216
|
+
setup_demux
|
217
|
+
@demux.should_receive(:get_message).with(any_args()).and_raise(StandardError)
|
218
|
+
send_event_message
|
219
|
+
@expected.should be_nil
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'returns nil if the ACL rejects the client' do
|
223
|
+
acl = double('acl')
|
224
|
+
acl.should_receive(:allow_socket?).with(anything()).and_return(false)
|
225
|
+
ACL.should_receive(:new).with(anything()).and_return(acl)
|
226
|
+
setup_demux
|
227
|
+
send_event_message
|
228
|
+
@expected.should be_nil
|
229
|
+
end
|
230
|
+
|
231
|
+
it 'resets the demux on exception' do
|
232
|
+
setup_demux
|
233
|
+
@demux.should_receive(:get_message).with(any_args()).and_raise(StandardError)
|
234
|
+
@demux.should_receive(:reset).with(no_args())
|
235
|
+
send_event_message
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
context '#respond' do
|
240
|
+
|
241
|
+
it 'returns nil if the socket is nil' do
|
242
|
+
subject.stop
|
243
|
+
subject.respond(:ok, 'foo').should be_nil
|
244
|
+
end
|
245
|
+
|
246
|
+
it 'puts a message on the socket' do
|
247
|
+
socket = double('tcp socket')
|
248
|
+
socket.should_receive(:puts).with("ok\r\necho\r\n\r\n")
|
249
|
+
subject.instance_variable_set(:@socket, socket)
|
250
|
+
subject.respond(:ok, 'echo')
|
251
|
+
end
|
252
|
+
|
253
|
+
it 'resets the demux on exception' do
|
254
|
+
socket = double('tcp socket')
|
255
|
+
socket.should_receive(:puts).and_raise(SocketError)
|
256
|
+
subject.instance_variable_set(:@socket, socket)
|
257
|
+
subject.should_receive(:reset)
|
258
|
+
subject.respond(:ok, 'echo')
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
context '#format_message' do
|
263
|
+
|
264
|
+
it 'raises an exception when the event is nil' do
|
265
|
+
lambda {
|
266
|
+
subject.format_message(nil)
|
267
|
+
}.should raise_error(ArgumentError)
|
268
|
+
|
269
|
+
lambda {
|
270
|
+
TcpSyncDemux.format_message(nil)
|
271
|
+
}.should raise_error(ArgumentError)
|
272
|
+
end
|
273
|
+
|
274
|
+
it 'raises an exception when the event is an empty string' do
|
275
|
+
lambda {
|
276
|
+
subject.format_message(' ')
|
277
|
+
}.should raise_error(ArgumentError)
|
278
|
+
|
279
|
+
lambda {
|
280
|
+
TcpSyncDemux.format_message(' ')
|
281
|
+
}.should raise_error(ArgumentError)
|
282
|
+
end
|
283
|
+
|
284
|
+
it 'creates a message with no arguments' do
|
285
|
+
message = subject.format_message(:echo)
|
286
|
+
message.should eq "echo\r\n\r\n"
|
287
|
+
|
288
|
+
message = TcpSyncDemux.format_message('echo')
|
289
|
+
message.should eq "echo\r\n\r\n"
|
290
|
+
end
|
291
|
+
|
292
|
+
it 'creates a message with arguments' do
|
293
|
+
message = subject.format_message(:echo, 'hello', 'world')
|
294
|
+
message.should eq "echo\r\nhello\r\nworld\r\n\r\n"
|
295
|
+
|
296
|
+
message = TcpSyncDemux.format_message('echo', 'hello', 'world')
|
297
|
+
message.should eq "echo\r\nhello\r\nworld\r\n\r\n"
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
context '#parse_message' do
|
302
|
+
|
303
|
+
it 'accepts the message as a string' do
|
304
|
+
message = subject.parse_message("echo\r\nhello world\r\n\r\n")
|
305
|
+
message.should_not eq [nil, nil]
|
306
|
+
end
|
307
|
+
|
308
|
+
it 'accepts the message as an array of lines' do
|
309
|
+
message = subject.parse_message(%w[echo hello world])
|
310
|
+
message.should_not eq [nil, nil]
|
311
|
+
end
|
312
|
+
|
313
|
+
it 'recognizes an event name beginning with a colon' do
|
314
|
+
message = subject.parse_message(":echo\r\nhello world\r\n\r\n")
|
315
|
+
message.first.should eq :echo
|
316
|
+
|
317
|
+
message = TcpSyncDemux.parse_message(%w[:echo hello world])
|
318
|
+
message.first.should eq :echo
|
319
|
+
end
|
320
|
+
|
321
|
+
it 'recognizes an event name without a beginning colon' do
|
322
|
+
message = subject.parse_message(%w[echo hello world])
|
323
|
+
message.first.should eq :echo
|
324
|
+
|
325
|
+
message = TcpSyncDemux.parse_message("echo\r\nhello world\r\n\r\n")
|
326
|
+
message.first.should eq :echo
|
327
|
+
end
|
328
|
+
|
329
|
+
it 'parses a message without arguments' do
|
330
|
+
message = subject.parse_message("echo\r\n\r\n")
|
331
|
+
message.first.should eq :echo
|
332
|
+
|
333
|
+
message = TcpSyncDemux.parse_message(%w[echo])
|
334
|
+
message.first.should eq :echo
|
335
|
+
end
|
336
|
+
|
337
|
+
it 'parses a message with arguments' do
|
338
|
+
message = subject.parse_message(%w[echo hello world])
|
339
|
+
message.last.should eq %w[hello world]
|
340
|
+
|
341
|
+
message = TcpSyncDemux.parse_message("echo\r\nhello world\r\n\r\n")
|
342
|
+
message.last.should eq ['hello world']
|
343
|
+
end
|
344
|
+
|
345
|
+
it 'returns nil for a malformed message' do
|
346
|
+
message = subject.parse_message(nil)
|
347
|
+
message.should eq [nil, []]
|
348
|
+
|
349
|
+
message = subject.parse_message(' ')
|
350
|
+
message.should eq [nil, []]
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
specify 'integration', not_on_travis: true do
|
355
|
+
|
356
|
+
# server
|
357
|
+
demux = Concurrent::Reactor::TcpSyncDemux.new
|
358
|
+
reactor = Concurrent::Reactor.new(demux)
|
359
|
+
|
360
|
+
reactor.add_handler(:echo) {|*args| args.first }
|
361
|
+
reactor.add_handler(:error) {|*args| raise StandardError.new(args.first) }
|
362
|
+
reactor.add_handler(:unknown) {|*args| args.first }
|
363
|
+
reactor.add_handler(:abend) {|*args| args.first }
|
364
|
+
|
365
|
+
t = Thread.new { reactor.run }
|
366
|
+
sleep(0.1)
|
367
|
+
|
368
|
+
# client
|
369
|
+
there = TCPSocket.open(TcpSyncDemux::DEFAULT_HOST, TcpSyncDemux::DEFAULT_PORT)
|
370
|
+
|
371
|
+
# test :ok
|
372
|
+
10.times do
|
373
|
+
message = Faker::Company.bs
|
374
|
+
there.puts(Concurrent::Reactor::TcpSyncDemux.format_message(:echo, message))
|
375
|
+
result, echo = Concurrent::Reactor::TcpSyncDemux.get_message(there)
|
376
|
+
result.should eq :ok
|
377
|
+
echo.first.should eq message
|
378
|
+
end
|
379
|
+
|
380
|
+
# test :ex
|
381
|
+
there.puts(Concurrent::Reactor::TcpSyncDemux.format_message(:error, 'error'))
|
382
|
+
result, echo = Concurrent::Reactor::TcpSyncDemux.get_message(there)
|
383
|
+
result.should eq :ex
|
384
|
+
echo.first.should eq 'error'
|
385
|
+
|
386
|
+
# test :noop
|
387
|
+
there.puts(Concurrent::Reactor::TcpSyncDemux.format_message(:bogus, 'bogus'))
|
388
|
+
result, echo = Concurrent::Reactor::TcpSyncDemux.get_message(there)
|
389
|
+
result.should eq :noop
|
390
|
+
echo.first.should =~ /bogus/
|
391
|
+
|
392
|
+
# test handler error
|
393
|
+
ex = ArgumentError.new('abend')
|
394
|
+
ec = Reactor::EventContext.new(:abend, [])
|
395
|
+
Reactor::EventContext.should_receive(:new).with(:abend, []).and_return(ec)
|
396
|
+
reactor.should_receive(:handle_event).with(ec).and_raise(ex)
|
397
|
+
there.puts(Concurrent::Reactor::TcpSyncDemux.format_message(:abend))
|
398
|
+
result, echo = Concurrent::Reactor::TcpSyncDemux.get_message(there)
|
399
|
+
result.should eq :abend
|
400
|
+
echo.first.should eq 'abend'
|
401
|
+
|
402
|
+
#cleanup
|
403
|
+
reactor.stop
|
404
|
+
sleep(0.1)
|
405
|
+
Thread.kill(t)
|
406
|
+
sleep(0.1)
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|