concurrent-ruby 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +48 -1
  3. data/lib/concurrent.rb +8 -1
  4. data/lib/concurrent/agent.rb +19 -40
  5. data/lib/concurrent/cached_thread_pool.rb +10 -11
  6. data/lib/concurrent/defer.rb +8 -12
  7. data/lib/concurrent/executor.rb +95 -0
  8. data/lib/concurrent/fixed_thread_pool.rb +12 -6
  9. data/lib/concurrent/functions.rb +120 -0
  10. data/lib/concurrent/future.rb +8 -20
  11. data/lib/concurrent/global_thread_pool.rb +13 -0
  12. data/lib/concurrent/goroutine.rb +5 -1
  13. data/lib/concurrent/null_thread_pool.rb +22 -0
  14. data/lib/concurrent/obligation.rb +10 -64
  15. data/lib/concurrent/promise.rb +38 -60
  16. data/lib/concurrent/reactor.rb +166 -0
  17. data/lib/concurrent/reactor/drb_async_demux.rb +83 -0
  18. data/lib/concurrent/reactor/tcp_sync_demux.rb +131 -0
  19. data/lib/concurrent/supervisor.rb +100 -0
  20. data/lib/concurrent/thread_pool.rb +16 -5
  21. data/lib/concurrent/utilities.rb +8 -0
  22. data/lib/concurrent/version.rb +1 -1
  23. data/md/defer.md +4 -4
  24. data/md/executor.md +187 -0
  25. data/md/promise.md +2 -0
  26. data/md/thread_pool.md +27 -0
  27. data/spec/concurrent/agent_spec.rb +8 -27
  28. data/spec/concurrent/cached_thread_pool_spec.rb +14 -1
  29. data/spec/concurrent/defer_spec.rb +17 -21
  30. data/spec/concurrent/event_machine_defer_proxy_spec.rb +159 -149
  31. data/spec/concurrent/executor_spec.rb +200 -0
  32. data/spec/concurrent/fixed_thread_pool_spec.rb +2 -3
  33. data/spec/concurrent/functions_spec.rb +217 -0
  34. data/spec/concurrent/future_spec.rb +4 -11
  35. data/spec/concurrent/global_thread_pool_spec.rb +38 -0
  36. data/spec/concurrent/goroutine_spec.rb +15 -0
  37. data/spec/concurrent/null_thread_pool_spec.rb +54 -0
  38. data/spec/concurrent/obligation_shared.rb +127 -116
  39. data/spec/concurrent/promise_spec.rb +16 -14
  40. data/spec/concurrent/reactor/drb_async_demux_spec.rb +196 -0
  41. data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +410 -0
  42. data/spec/concurrent/reactor_spec.rb +364 -0
  43. data/spec/concurrent/supervisor_spec.rb +258 -0
  44. data/spec/concurrent/thread_pool_shared.rb +156 -161
  45. data/spec/concurrent/utilities_spec.rb +30 -1
  46. data/spec/spec_helper.rb +13 -0
  47. 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(1) }
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
- $GLOBAL_THREAD_POOL = CachedThreadPool.new
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{ Thread.pass; raise StandardError.new('Boom!') }
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
- sleep(0.1)
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{ Thread.pass; raise StandardError }.
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