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.
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