rjr 0.19.1 → 0.19.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +3 -0
- data/Rakefile +0 -2
- data/examples/timeout.rb +23 -0
- data/lib/rjr/dispatcher.rb +1 -1
- data/lib/rjr/messages/intermediate.rb +50 -0
- data/lib/rjr/messages/notification.rb +9 -16
- data/lib/rjr/messages/request.rb +14 -20
- data/lib/rjr/messages/response.rb +19 -26
- data/lib/rjr/messages.rb +1 -0
- data/lib/rjr/node.rb +50 -34
- data/lib/rjr/nodes/amqp.rb +19 -3
- data/lib/rjr/nodes/local.rb +12 -5
- data/lib/rjr/nodes/tcp.rb +25 -12
- data/lib/rjr/nodes/web.rb +3 -2
- data/lib/rjr/request.rb +11 -1
- data/lib/rjr/util/json_parser.rb +47 -14
- data/lib/rjr/util/logger.rb +4 -2
- data/lib/rjr/version.rb +1 -1
- data/specs/args_spec.rb +2 -2
- data/specs/dispatcher_spec.rb +9 -9
- data/specs/em_adapter_spec.rb +3 -3
- data/specs/handles_methods_spec.rb +2 -2
- data/specs/json_parser_spec.rb +30 -4
- data/specs/messages/intermediate_spec.rb +58 -0
- data/specs/messages/notification_spec.rb +12 -13
- data/specs/messages/request_spec.rb +16 -16
- data/specs/messages/response_spec.rb +15 -14
- data/specs/node_spec.rb +79 -31
- data/specs/nodes/amqp_spec.rb +1 -1
- data/specs/nodes/local_spec.rb +2 -2
- data/specs/nodes/multi_spec.rb +1 -1
- data/specs/nodes/tcp_spec.rb +116 -27
- data/specs/nodes/web_spec.rb +1 -1
- data/specs/nodes/ws_spec.rb +1 -1
- data/specs/request_spec.rb +19 -0
- metadata +83 -80
data/specs/node_spec.rb
CHANGED
@@ -177,7 +177,9 @@ module RJR
|
|
177
177
|
n = Node.new
|
178
178
|
n.on(:error) {}
|
179
179
|
n.clear_event_handlers
|
180
|
-
n.connection_event_handlers.should == {:
|
180
|
+
n.connection_event_handlers.should == { :opened => [],
|
181
|
+
:closed => [],
|
182
|
+
:error => [] }
|
181
183
|
end
|
182
184
|
end
|
183
185
|
|
@@ -186,7 +188,9 @@ module RJR
|
|
186
188
|
bl = proc{}
|
187
189
|
n = Node.new
|
188
190
|
n.on(:error, &bl)
|
189
|
-
n.connection_event_handlers.should == {:
|
191
|
+
n.connection_event_handlers.should == { :opened => [],
|
192
|
+
:closed => [],
|
193
|
+
:error => [bl] }
|
190
194
|
end
|
191
195
|
end
|
192
196
|
|
@@ -247,6 +251,10 @@ module RJR
|
|
247
251
|
connection = Object.new
|
248
252
|
request = Messages::Request.new :id => 1, :method => 'foo'
|
249
253
|
request_str = request.to_s
|
254
|
+
inter = Messages::Intermediate.parse request_str
|
255
|
+
Messages::Intermediate.should_receive(:parse).
|
256
|
+
with(request_str).
|
257
|
+
and_return(inter)
|
250
258
|
|
251
259
|
node = Node.new
|
252
260
|
node.tp.should_receive(:<<) { |job|
|
@@ -254,7 +262,7 @@ module RJR
|
|
254
262
|
job.exec Mutex.new
|
255
263
|
}
|
256
264
|
|
257
|
-
node.should_receive(:handle_request).with(
|
265
|
+
node.should_receive(:handle_request).with(inter, false, connection)
|
258
266
|
node.send :handle_message, request_str, connection
|
259
267
|
end
|
260
268
|
end
|
@@ -264,6 +272,10 @@ module RJR
|
|
264
272
|
connection = Object.new
|
265
273
|
notification = Messages::Notification.new :method => 'foo'
|
266
274
|
notification_str = notification.to_s
|
275
|
+
inter = Messages::Intermediate.parse notification_str
|
276
|
+
Messages::Intermediate.should_receive(:parse).
|
277
|
+
with(notification_str).
|
278
|
+
and_return(inter)
|
267
279
|
|
268
280
|
node = Node.new
|
269
281
|
node.tp.should_receive(:<<) { |job|
|
@@ -271,8 +283,7 @@ module RJR
|
|
271
283
|
job.exec Mutex.new
|
272
284
|
}
|
273
285
|
|
274
|
-
node.should_receive(:handle_request).
|
275
|
-
with(notification_str, true, connection)
|
286
|
+
node.should_receive(:handle_request).with(inter, true, connection)
|
276
287
|
node.send :handle_message, notification_str, connection
|
277
288
|
end
|
278
289
|
end
|
@@ -281,9 +292,13 @@ module RJR
|
|
281
292
|
it "handles response" do
|
282
293
|
response = Messages::Response.new :result => Result.new
|
283
294
|
response_str = response.to_s
|
295
|
+
inter = Messages::Intermediate.parse response_str
|
296
|
+
Messages::Intermediate.should_receive(:parse).
|
297
|
+
with(response_str).
|
298
|
+
and_return(inter)
|
284
299
|
|
285
300
|
node = Node.new
|
286
|
-
node.should_receive(:handle_response).with(
|
301
|
+
node.should_receive(:handle_response).with(inter)
|
287
302
|
node.send :handle_message, response_str
|
288
303
|
end
|
289
304
|
end
|
@@ -297,6 +312,14 @@ module RJR
|
|
297
312
|
node.send :handle_message, "{}"
|
298
313
|
end
|
299
314
|
end
|
315
|
+
|
316
|
+
it "returns intermediate message" do
|
317
|
+
node = Node.new
|
318
|
+
connection = Object.new
|
319
|
+
inter = Messages::Intermediate.new
|
320
|
+
Messages::Intermediate.should_receive(:parse).and_return(inter)
|
321
|
+
node.send(:handle_message, '', connection).should == inter
|
322
|
+
end
|
300
323
|
end
|
301
324
|
|
302
325
|
describe "#handle_request" do
|
@@ -305,13 +328,14 @@ module RJR
|
|
305
328
|
Messages::Notification.new :method => 'rjr_method1',
|
306
329
|
:args => ['method', 'args'],
|
307
330
|
:headers => {'msg' => 'headers'}
|
308
|
-
|
331
|
+
notification_str = notification.to_s
|
332
|
+
@intermediate = Messages::Intermediate.parse notification_str
|
309
333
|
@node = Node.new
|
310
334
|
end
|
311
335
|
|
312
336
|
it "invokes dispatcher.dispatch" do
|
313
337
|
@node.dispatcher.should_receive(:dispatch)
|
314
|
-
@node.send :handle_request, @
|
338
|
+
@node.send :handle_request, @intermediate, true
|
315
339
|
end
|
316
340
|
|
317
341
|
describe "dispatcher.dispatch args" do
|
@@ -319,21 +343,21 @@ module RJR
|
|
319
343
|
@node.dispatcher.should_receive(:dispatch) { |args|
|
320
344
|
args[:rjr_method].should == 'rjr_method1'
|
321
345
|
}
|
322
|
-
@node.send :handle_request, @
|
346
|
+
@node.send :handle_request, @intermediate, true
|
323
347
|
end
|
324
348
|
|
325
349
|
it "includes :rjr_method_args => msg.jr_args" do
|
326
350
|
@node.dispatcher.should_receive(:dispatch) { |args|
|
327
351
|
args[:rjr_method_args].should == ['method', 'args']
|
328
352
|
}
|
329
|
-
@node.send :handle_request, @
|
353
|
+
@node.send :handle_request, @intermediate, true
|
330
354
|
end
|
331
355
|
|
332
356
|
it "includes :rjr_headers => msg.headers" do
|
333
357
|
@node.dispatcher.should_receive(:dispatch) { |args|
|
334
358
|
args[:rjr_headers].should == {'msg' => 'headers'}
|
335
359
|
}
|
336
|
-
@node.send :handle_request, @
|
360
|
+
@node.send :handle_request, @intermediate, true
|
337
361
|
end
|
338
362
|
|
339
363
|
it "includes :rjr_client_ip => extracted client ip" do
|
@@ -343,7 +367,7 @@ module RJR
|
|
343
367
|
@node.dispatcher.should_receive(:dispatch) { |args|
|
344
368
|
args[:rjr_client_ip].should == '127.0.0.1'
|
345
369
|
}
|
346
|
-
@node.send :handle_request, @
|
370
|
+
@node.send :handle_request, @intermediate, true, connection
|
347
371
|
end
|
348
372
|
|
349
373
|
it "includes :rjr_client_port => extracted client port" do
|
@@ -353,14 +377,14 @@ module RJR
|
|
353
377
|
@node.dispatcher.should_receive(:dispatch) { |args|
|
354
378
|
args[:rjr_client_port].should == 9999
|
355
379
|
}
|
356
|
-
@node.send :handle_request, @
|
380
|
+
@node.send :handle_request, @intermediate, true, connection
|
357
381
|
end
|
358
382
|
|
359
383
|
it "includes :rjr_node => self" do
|
360
384
|
@node.dispatcher.should_receive(:dispatch) { |args|
|
361
385
|
args[:rjr_node].should == @node
|
362
386
|
}
|
363
|
-
@node.send :handle_request, @
|
387
|
+
@node.send :handle_request, @intermediate, true
|
364
388
|
end
|
365
389
|
|
366
390
|
it "includes :rjr_node_id => self.node_id" do
|
@@ -368,7 +392,7 @@ module RJR
|
|
368
392
|
@node.dispatcher.should_receive(:dispatch) { |args|
|
369
393
|
args[:rjr_node_id].should == 'node_id'
|
370
394
|
}
|
371
|
-
@node.send :handle_request, @
|
395
|
+
@node.send :handle_request, @intermediate, true
|
372
396
|
end
|
373
397
|
|
374
398
|
it "includes :rjr_node_type => self.node_type" do
|
@@ -376,14 +400,14 @@ module RJR
|
|
376
400
|
@node.dispatcher.should_receive(:dispatch) { |args|
|
377
401
|
args[:rjr_node_type].should == :nt
|
378
402
|
}
|
379
|
-
@node.send :handle_request, @
|
403
|
+
@node.send :handle_request, @intermediate, true
|
380
404
|
end
|
381
405
|
|
382
406
|
it "includes :rjr_callback => constructed node callback" do
|
383
407
|
@node.dispatcher.should_receive(:dispatch) { |args|
|
384
408
|
args[:rjr_callback].should be_an_instance_of(NodeCallback)
|
385
409
|
}
|
386
|
-
@node.send :handle_request, @
|
410
|
+
@node.send :handle_request, @intermediate, true
|
387
411
|
end
|
388
412
|
|
389
413
|
describe "specified node callback" do
|
@@ -391,7 +415,7 @@ module RJR
|
|
391
415
|
@node.dispatcher.should_receive(:dispatch) { |args|
|
392
416
|
args[:rjr_callback].node.should == @node
|
393
417
|
}
|
394
|
-
@node.send :handle_request, @
|
418
|
+
@node.send :handle_request, @intermediate, true
|
395
419
|
end
|
396
420
|
|
397
421
|
it "has a handle to the connection" do
|
@@ -399,17 +423,18 @@ module RJR
|
|
399
423
|
@node.dispatcher.should_receive(:dispatch) { |args|
|
400
424
|
args[:rjr_callback].connection.should == connection
|
401
425
|
}
|
402
|
-
@node.send :handle_request, @
|
426
|
+
@node.send :handle_request, @intermediate, true, connection
|
403
427
|
end
|
404
428
|
end
|
405
429
|
end
|
406
430
|
|
407
431
|
context "handling request / not a notification" do
|
408
432
|
before(:each) do
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
433
|
+
request = Messages::Request.new :method => 'method1',
|
434
|
+
:args => ['method', 'args'],
|
435
|
+
:headers => {'msg' => 'headers'}
|
436
|
+
request_str = request.to_s
|
437
|
+
@intermediate = Messages::Intermediate.parse request_str
|
413
438
|
|
414
439
|
result = Result.new
|
415
440
|
@expected = Messages::Response.new :result => result,
|
@@ -426,19 +451,19 @@ module RJR
|
|
426
451
|
response.should == @expected.to_s
|
427
452
|
econnection.should == connection
|
428
453
|
}
|
429
|
-
@node.send :handle_request, @
|
454
|
+
@node.send :handle_request, @intermediate, false, connection
|
430
455
|
end
|
431
456
|
|
432
457
|
it "returns response" do
|
433
458
|
@node.should_receive(:send_msg) # stub out
|
434
|
-
response = @node.send(:handle_request, @
|
459
|
+
response = @node.send(:handle_request, @intermediate, false)
|
435
460
|
response.should be_an_instance_of(Messages::Response)
|
436
461
|
response.to_s.should == @expected.to_s
|
437
462
|
end
|
438
463
|
end
|
439
464
|
|
440
465
|
it "returns nil" do
|
441
|
-
@node.send(:handle_request, @
|
466
|
+
@node.send(:handle_request, @intermediate, true).should be_nil
|
442
467
|
end
|
443
468
|
end
|
444
469
|
|
@@ -448,7 +473,8 @@ module RJR
|
|
448
473
|
|
449
474
|
@result = Result.new :result => 42
|
450
475
|
response = Messages::Response.new :id => 'msg1', :result => @result
|
451
|
-
|
476
|
+
response = response.to_s
|
477
|
+
@intermediate = Messages::Intermediate.parse response
|
452
478
|
end
|
453
479
|
|
454
480
|
it "invokes dispatcher.handle_response with response result" do
|
@@ -456,11 +482,11 @@ module RJR
|
|
456
482
|
r.should be_an_instance_of(Result)
|
457
483
|
r.result.should == 42
|
458
484
|
}
|
459
|
-
@node.send :handle_response, @
|
485
|
+
@node.send :handle_response, @intermediate
|
460
486
|
end
|
461
487
|
|
462
488
|
it "adds response msg_id and result to response queue" do
|
463
|
-
@node.send :handle_response, @
|
489
|
+
@node.send :handle_response, @intermediate
|
464
490
|
responses = @node.instance_variable_get(:@responses)
|
465
491
|
responses.size.should == 1
|
466
492
|
responses.first[0].should == 'msg1'
|
@@ -470,7 +496,7 @@ module RJR
|
|
470
496
|
context "response contains error" do
|
471
497
|
it "adds response error to response queue" do
|
472
498
|
@node.dispatcher.should_receive(:handle_response).and_raise(Exception)
|
473
|
-
@node.send :handle_response, @
|
499
|
+
@node.send :handle_response, @intermediate
|
474
500
|
responses = @node.instance_variable_get(:@responses)
|
475
501
|
responses.first[2].should be_an_instance_of(Exception)
|
476
502
|
end
|
@@ -478,7 +504,7 @@ module RJR
|
|
478
504
|
|
479
505
|
it "signals response cv" do
|
480
506
|
@node.instance_variable_get(:@response_cv).should_receive(:broadcast)
|
481
|
-
@node.send :handle_response, @
|
507
|
+
@node.send :handle_response, @intermediate
|
482
508
|
end
|
483
509
|
end
|
484
510
|
|
@@ -513,6 +539,28 @@ module RJR
|
|
513
539
|
node.instance_variable_get(:@response_cv).should_receive(:wait).once
|
514
540
|
node.send(:wait_for_result, @msg)
|
515
541
|
end
|
542
|
+
it "throws an error if it times out" do
|
543
|
+
node = Node.new :timeout => 0.01
|
544
|
+
node.instance_variable_get(:@response_cv).should_receive(:wait).at_least(:once)
|
545
|
+
expect { node.send(:wait_for_result, @msg) }.to raise_error(Exception)
|
546
|
+
end
|
547
|
+
end
|
548
|
+
|
549
|
+
context "pruning messages" do
|
550
|
+
it "deletes unrequested messages" do
|
551
|
+
responses = [@response, ['foobar', 'baz']]
|
552
|
+
@node.instance_variable_set(:@responses, responses)
|
553
|
+
@node.send :wait_for_result, @msg
|
554
|
+
@node.instance_variable_get(:@responses).should be_empty
|
555
|
+
end
|
556
|
+
it "deletes timed out messages" do
|
557
|
+
responses = [@response, ['foobar', 'baz']]
|
558
|
+
@node.instance_variable_set(:@timeout, 0.01)
|
559
|
+
@node.instance_variable_set(:@pending, { 'foobar' => Time.at(0) })
|
560
|
+
@node.instance_variable_set(:@responses, responses)
|
561
|
+
@node.send :wait_for_result, @msg
|
562
|
+
@node.instance_variable_get(:@responses).should be_empty
|
563
|
+
end
|
516
564
|
end
|
517
565
|
end
|
518
566
|
end # describe Node
|
data/specs/nodes/amqp_spec.rb
CHANGED
data/specs/nodes/local_spec.rb
CHANGED
data/specs/nodes/multi_spec.rb
CHANGED
@@ -51,7 +51,7 @@ module RJR::Nodes
|
|
51
51
|
:broker => 'localhost'
|
52
52
|
res = amqp_client.invoke 'amqp-queue', 'method1', 'myparam1'
|
53
53
|
res.should == 'retval1'
|
54
|
-
invoked1.should
|
54
|
+
invoked1.should be_truthy
|
55
55
|
rni1.should == 'amqp'
|
56
56
|
rnt1.should == :amqp
|
57
57
|
p1.should == 'myparam1'
|
data/specs/nodes/tcp_spec.rb
CHANGED
@@ -35,41 +35,130 @@ module RJR::Nodes
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
38
|
+
context "simple server" do
|
39
|
+
let(:server) do
|
40
|
+
TCP.new(:node_id => 'tcp',
|
41
|
+
:host => 'localhost',
|
42
|
+
:port => 9987)
|
43
|
+
end
|
44
|
+
|
45
|
+
let(:host) { 'jsonrpc://localhost:9987' }
|
46
|
+
|
47
|
+
let(:client) { TCP.new }
|
48
|
+
|
49
|
+
before(:each) do
|
50
|
+
server.dispatcher.handle('foobar') { |param| 'retval' }
|
45
51
|
server.listen
|
52
|
+
end
|
46
53
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
54
|
+
describe "#invoke" do
|
55
|
+
it "should invoke request" do
|
56
|
+
res = client.invoke host, 'foobar', 'myparam'
|
57
|
+
server.halt.join
|
58
|
+
res.should == 'retval'
|
59
|
+
end
|
51
60
|
end
|
52
|
-
end
|
53
61
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
server.listen
|
62
|
+
describe "#notify" do
|
63
|
+
it "should send notification" do
|
64
|
+
res = client.notify host, 'foobar', 'myparam'
|
65
|
+
server.halt.join
|
66
|
+
res.should == nil
|
67
|
+
end
|
68
|
+
end
|
62
69
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
70
|
+
describe "a node's connections array" do
|
71
|
+
it "should be updated when a connection is opened" do
|
72
|
+
client.invoke host, 'foobar', 'myparam'
|
73
|
+
server.connections.size.should be == 1
|
74
|
+
client.connections.size.should be == 1
|
75
|
+
server.halt.join
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should be updated when a connection is closed" do
|
79
|
+
client.invoke host, 'foobar', 'myparam'
|
80
|
+
client.connections.first.unbind
|
81
|
+
server.connections.first.unbind
|
82
|
+
server.connections.should be_empty
|
83
|
+
client.connections.should be_empty
|
84
|
+
server.halt.join
|
85
|
+
end
|
86
|
+
|
87
|
+
context "with a client already connected" do
|
88
|
+
let(:another_client) { TCP.new }
|
89
|
+
|
90
|
+
before(:each) do
|
91
|
+
client.invoke host, 'foobar', 'myparam'
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should be updated when a new connection is opened" do
|
95
|
+
another_client.invoke host, 'foobar', 'myparam'
|
96
|
+
server.connections.size.should be == 2
|
97
|
+
client.connections.size.should be == 1
|
98
|
+
another_client.connections.size.should be == 1
|
99
|
+
server.halt.join
|
100
|
+
end
|
101
|
+
|
102
|
+
context "with two clients connected" do
|
103
|
+
before(:each) do
|
104
|
+
client.invoke host, 'foobar', 'myparam'
|
105
|
+
another_client.invoke host, 'foobar', 'myparam'
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should be updated when a connection is closed" do
|
109
|
+
another_client.connections.first.unbind
|
110
|
+
server.connections.last.unbind
|
111
|
+
server.connections.size.should be == 1
|
112
|
+
client.connections.size.should be == 1
|
113
|
+
another_client.connections.should be_empty
|
114
|
+
server.halt.join
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe "event handlers" do
|
121
|
+
shared_examples "an event handler" do |event|
|
122
|
+
it "should be invoked when event is triggered" do
|
123
|
+
handler_invoked = false
|
124
|
+
server.on(event) { handler_invoked = true }
|
125
|
+
client.invoke host, 'foobar', 'myparam'
|
126
|
+
server.halt.join
|
127
|
+
handler_invoked.should be_truthy
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should receive the node" do
|
131
|
+
node_received = false
|
132
|
+
server.on(event) do |node|
|
133
|
+
node_received = true if node.is_a?(RJR::Node)
|
134
|
+
end
|
135
|
+
client.invoke host, 'foobar', 'myparam'
|
136
|
+
server.halt.join
|
137
|
+
node_received.should be_truthy
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should receive the connection" do
|
141
|
+
connection_received = false
|
142
|
+
server.on(event) do |_, connection|
|
143
|
+
connection_received = true if connection.is_a?(EM::Connection)
|
144
|
+
end
|
145
|
+
client.invoke host, 'foobar', 'myparam'
|
146
|
+
server.halt.join
|
147
|
+
connection_received.should be_truthy
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
describe "opened event handler" do
|
152
|
+
it_behaves_like "an event handler", :opened
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "closed event handler" do
|
156
|
+
it_behaves_like "an event handler", :closed
|
157
|
+
end
|
67
158
|
end
|
68
159
|
end
|
69
160
|
|
70
161
|
# TODO test callbacks over tcp interface
|
71
|
-
# TODO ensure
|
162
|
+
# TODO ensure error event handler is invoked
|
72
163
|
end
|
73
164
|
end
|
74
|
-
|
75
|
-
|
data/specs/nodes/web_spec.rb
CHANGED
data/specs/nodes/ws_spec.rb
CHANGED
data/specs/request_spec.rb
CHANGED
@@ -63,11 +63,30 @@ module RJR
|
|
63
63
|
request.rjr_args.args.should == args
|
64
64
|
end
|
65
65
|
|
66
|
+
it "sets default env to nil" do
|
67
|
+
Request.new.rjr_env.should be_nil
|
68
|
+
end
|
69
|
+
|
66
70
|
it "sets default result to nil" do
|
67
71
|
Request.new.result.should be_nil
|
68
72
|
end
|
69
73
|
end
|
70
74
|
|
75
|
+
describe "#set_env" do
|
76
|
+
it "extends request w/ specified module" do
|
77
|
+
request = Request.new
|
78
|
+
request.should_receive(:extend).with('foobar')
|
79
|
+
request.set_env('foobar')
|
80
|
+
end
|
81
|
+
|
82
|
+
it "sets rjr_env" do
|
83
|
+
request = Request.new
|
84
|
+
request.should_receive(:extend)
|
85
|
+
request.set_env('foobar')
|
86
|
+
request.rjr_env.should == 'foobar'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
71
90
|
describe "#handle" do
|
72
91
|
it "invokes the registered handler with args" do
|
73
92
|
received = nil
|