concurrent-ruby 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/LICENSE +21 -21
  2. data/README.md +275 -275
  3. data/lib/concurrent.rb +28 -28
  4. data/lib/concurrent/agent.rb +114 -114
  5. data/lib/concurrent/cached_thread_pool.rb +131 -129
  6. data/lib/concurrent/defer.rb +65 -65
  7. data/lib/concurrent/event.rb +60 -60
  8. data/lib/concurrent/event_machine_defer_proxy.rb +23 -23
  9. data/lib/concurrent/executor.rb +96 -95
  10. data/lib/concurrent/fixed_thread_pool.rb +99 -95
  11. data/lib/concurrent/functions.rb +120 -120
  12. data/lib/concurrent/future.rb +42 -42
  13. data/lib/concurrent/global_thread_pool.rb +16 -16
  14. data/lib/concurrent/goroutine.rb +29 -29
  15. data/lib/concurrent/null_thread_pool.rb +22 -22
  16. data/lib/concurrent/obligation.rb +67 -67
  17. data/lib/concurrent/promise.rb +174 -174
  18. data/lib/concurrent/reactor.rb +166 -166
  19. data/lib/concurrent/reactor/drb_async_demux.rb +83 -83
  20. data/lib/concurrent/reactor/tcp_sync_demux.rb +131 -131
  21. data/lib/concurrent/supervisor.rb +105 -100
  22. data/lib/concurrent/thread_pool.rb +76 -76
  23. data/lib/concurrent/utilities.rb +32 -32
  24. data/lib/concurrent/version.rb +3 -3
  25. data/lib/concurrent_ruby.rb +1 -1
  26. data/md/agent.md +123 -123
  27. data/md/defer.md +174 -174
  28. data/md/event.md +32 -32
  29. data/md/executor.md +187 -187
  30. data/md/future.md +83 -83
  31. data/md/goroutine.md +52 -52
  32. data/md/obligation.md +32 -32
  33. data/md/promise.md +227 -227
  34. data/md/thread_pool.md +224 -224
  35. data/spec/concurrent/agent_spec.rb +386 -386
  36. data/spec/concurrent/cached_thread_pool_spec.rb +125 -125
  37. data/spec/concurrent/defer_spec.rb +195 -195
  38. data/spec/concurrent/event_machine_defer_proxy_spec.rb +256 -256
  39. data/spec/concurrent/event_spec.rb +134 -134
  40. data/spec/concurrent/executor_spec.rb +200 -200
  41. data/spec/concurrent/fixed_thread_pool_spec.rb +83 -83
  42. data/spec/concurrent/functions_spec.rb +217 -217
  43. data/spec/concurrent/future_spec.rb +108 -108
  44. data/spec/concurrent/global_thread_pool_spec.rb +38 -38
  45. data/spec/concurrent/goroutine_spec.rb +67 -67
  46. data/spec/concurrent/null_thread_pool_spec.rb +57 -54
  47. data/spec/concurrent/obligation_shared.rb +132 -132
  48. data/spec/concurrent/promise_spec.rb +312 -312
  49. data/spec/concurrent/reactor/drb_async_demux_spec.rb +196 -196
  50. data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +410 -410
  51. data/spec/concurrent/reactor_spec.rb +364 -364
  52. data/spec/concurrent/supervisor_spec.rb +269 -258
  53. data/spec/concurrent/thread_pool_shared.rb +204 -204
  54. data/spec/concurrent/utilities_spec.rb +74 -74
  55. data/spec/spec_helper.rb +32 -32
  56. metadata +20 -16
  57. checksums.yaml +0 -7
@@ -1,196 +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
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
@@ -1,410 +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
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