raktr 0.0.1

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 (58) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1 -0
  3. data/LICENSE.md +29 -0
  4. data/README.md +77 -0
  5. data/Rakefile +53 -0
  6. data/lib/raktr/connection/callbacks.rb +71 -0
  7. data/lib/raktr/connection/error.rb +120 -0
  8. data/lib/raktr/connection/peer_info.rb +90 -0
  9. data/lib/raktr/connection/tls.rb +164 -0
  10. data/lib/raktr/connection.rb +339 -0
  11. data/lib/raktr/global.rb +24 -0
  12. data/lib/raktr/iterator.rb +249 -0
  13. data/lib/raktr/queue.rb +89 -0
  14. data/lib/raktr/tasks/base.rb +57 -0
  15. data/lib/raktr/tasks/delayed.rb +33 -0
  16. data/lib/raktr/tasks/one_off.rb +30 -0
  17. data/lib/raktr/tasks/periodic.rb +58 -0
  18. data/lib/raktr/tasks/persistent.rb +29 -0
  19. data/lib/raktr/tasks.rb +105 -0
  20. data/lib/raktr/version.rb +13 -0
  21. data/lib/raktr.rb +707 -0
  22. data/spec/raktr/connection/tls_spec.rb +348 -0
  23. data/spec/raktr/connection_spec.rb +74 -0
  24. data/spec/raktr/iterator_spec.rb +203 -0
  25. data/spec/raktr/queue_spec.rb +91 -0
  26. data/spec/raktr/tasks/base.rb +8 -0
  27. data/spec/raktr/tasks/delayed_spec.rb +71 -0
  28. data/spec/raktr/tasks/one_off_spec.rb +66 -0
  29. data/spec/raktr/tasks/periodic_spec.rb +57 -0
  30. data/spec/raktr/tasks/persistent_spec.rb +54 -0
  31. data/spec/raktr/tasks_spec.rb +155 -0
  32. data/spec/raktr_spec.rb +20 -0
  33. data/spec/raktr_tls_spec.rb +20 -0
  34. data/spec/spec_helper.rb +17 -0
  35. data/spec/support/fixtures/handlers/echo_client.rb +34 -0
  36. data/spec/support/fixtures/handlers/echo_client_tls.rb +10 -0
  37. data/spec/support/fixtures/handlers/echo_server.rb +12 -0
  38. data/spec/support/fixtures/handlers/echo_server_tls.rb +8 -0
  39. data/spec/support/fixtures/pems/cacert.pem +37 -0
  40. data/spec/support/fixtures/pems/client/cert.pem +37 -0
  41. data/spec/support/fixtures/pems/client/foo-cert.pem +39 -0
  42. data/spec/support/fixtures/pems/client/foo-key.pem +51 -0
  43. data/spec/support/fixtures/pems/client/key.pem +51 -0
  44. data/spec/support/fixtures/pems/server/cert.pem +37 -0
  45. data/spec/support/fixtures/pems/server/key.pem +51 -0
  46. data/spec/support/helpers/paths.rb +23 -0
  47. data/spec/support/helpers/utilities.rb +135 -0
  48. data/spec/support/lib/server_option_parser.rb +29 -0
  49. data/spec/support/lib/servers/runner.rb +13 -0
  50. data/spec/support/lib/servers.rb +133 -0
  51. data/spec/support/servers/echo.rb +14 -0
  52. data/spec/support/servers/echo_tls.rb +22 -0
  53. data/spec/support/servers/echo_unix.rb +14 -0
  54. data/spec/support/servers/echo_unix_tls.rb +22 -0
  55. data/spec/support/shared/connection.rb +696 -0
  56. data/spec/support/shared/raktr.rb +834 -0
  57. data/spec/support/shared/task.rb +21 -0
  58. metadata +140 -0
@@ -0,0 +1,834 @@
1
+ shared_examples_for 'Raktr' do
2
+ after(:each) do
3
+ @socket.close if @socket
4
+ @socket = nil
5
+
6
+ next if !@raktr
7
+
8
+ if @raktr.running?
9
+ @raktr.stop
10
+ end
11
+
12
+ @raktr = nil
13
+ end
14
+
15
+ klass = Raktr
16
+
17
+ subject { @raktr ||= klass.new }
18
+ let(:raktr) { subject }
19
+ let(:data) { ('blah' * 999999) + "\n\n" }
20
+
21
+ describe '.global' do
22
+ it 'returns a Reactor' do
23
+ klass.global.should be_kind_of klass
24
+ end
25
+
26
+ it 'caches the instance' do
27
+ global = klass.global
28
+ klass.global.should == global
29
+ end
30
+ end
31
+
32
+ describe '.stop' do
33
+ it 'stops the global reactor' do
34
+ global = klass.global
35
+ klass.global.run_in_thread
36
+ klass.stop
37
+
38
+ global.wait
39
+ end
40
+
41
+ it 'destroys the global instance' do
42
+ global = klass.global
43
+ klass.stop
44
+
45
+ klass.object_id.should_not == global.object_id
46
+ end
47
+ end
48
+
49
+ describe '#initialize' do
50
+ describe :max_tick_interval do
51
+ it 'sets the maximum amount of time for each loop interval'
52
+ end
53
+
54
+ describe :select_timeout do
55
+ it 'sets the max waiting time for socket activity'
56
+ end
57
+ end
58
+
59
+ describe '#create_iterator' do
60
+ let(:iterator) { subject.create_iterator( 1..10 ) }
61
+
62
+ it 'creates a new Iterator' do
63
+ iterator.should be_kind_of klass::Iterator
64
+ end
65
+
66
+ it 'assigns this Reactor as the scheduler' do
67
+ iterator.raktr.should == subject
68
+ end
69
+ end
70
+
71
+ describe '#create_queue' do
72
+ let(:queue) { subject.create_queue }
73
+
74
+ it 'creates a new Queue' do
75
+ queue.should be_kind_of klass::Queue
76
+ end
77
+
78
+ it 'assigns this Reactor as the scheduler' do
79
+ queue.raktr.should == subject
80
+ end
81
+ end
82
+
83
+ describe '#ticks' do
84
+ context 'when the reactor is' do
85
+ context 'not running' do
86
+ it 'returns 0' do
87
+ subject.ticks.should == 0
88
+ end
89
+ end
90
+
91
+ context 'running' do
92
+ it 'returns the amount of loop iterations' do
93
+ run_reactor_in_thread
94
+ sleep 1
95
+ subject.ticks.should > 1
96
+ end
97
+ end
98
+
99
+ context 'stopped' do
100
+ it 'sets it to 0' do
101
+ run_reactor_in_thread
102
+ sleep 1
103
+ subject.stop
104
+ sleep 0.1 while subject.running?
105
+
106
+ subject.ticks.should == 0
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ describe '#run' do
113
+ it 'runs the reactor loop' do
114
+ run_reactor_in_thread
115
+ sleep 1
116
+ subject.ticks.should > 0
117
+ end
118
+
119
+ context 'when a block is given' do
120
+ it 'is called ASAP' do
121
+ subject.run do
122
+ subject.should be_running
123
+ subject.ticks.should == 0
124
+ subject.stop
125
+ end
126
+ end
127
+ end
128
+
129
+ context 'when already running' do
130
+ it "raises #{klass::Error::AlreadyRunning}" do
131
+ subject.run_in_thread
132
+ expect { subject.run }.to raise_error klass::Error::AlreadyRunning
133
+ end
134
+ end
135
+ end
136
+
137
+ describe '#run_in_thread' do
138
+ it 'runs the Reactor in a Thread' do
139
+ thread = subject.run_in_thread
140
+ subject.should be_running
141
+ thread.should_not == Thread.current
142
+ subject.thread.should == thread
143
+ end
144
+
145
+ context 'when already running' do
146
+ it "raises #{klass::Error::AlreadyRunning}" do
147
+ subject.run_in_thread
148
+ expect { subject.run_in_thread }.to raise_error klass::Error::AlreadyRunning
149
+ end
150
+ end
151
+ end
152
+
153
+ describe '#run_block' do
154
+ it 'runs the reactor loop just for the given block' do
155
+ running = false
156
+ subject.run_block do
157
+ running = subject.running?
158
+ end
159
+
160
+ subject.should_not be_running
161
+ running.should be_truthy
162
+ end
163
+
164
+ context 'when no block is given' do
165
+ it "raises #{ArgumentError}" do
166
+ expect { subject.run_block }.to raise_error ArgumentError
167
+ end
168
+ end
169
+
170
+ context 'when already running' do
171
+ it "raises #{klass::Error::AlreadyRunning}" do
172
+ run_reactor_in_thread
173
+ expect { subject.run_block{} }.to raise_error klass::Error::AlreadyRunning
174
+ end
175
+ end
176
+ end
177
+
178
+ describe '#wait' do
179
+ it 'waits for the reactor to stop running' do
180
+ subject.run_in_thread
181
+
182
+ start = Time.now
183
+ subject.delay 2 do
184
+ subject.stop
185
+ end
186
+ subject.wait
187
+
188
+ subject.should_not be_running
189
+ (Time.now - start).to_i.should == 2
190
+ end
191
+
192
+ context 'when the reactor is not running' do
193
+ it "raises #{klass::Error::NotRunning}" do
194
+ expect do
195
+ subject.wait{}
196
+ end.to raise_error klass::Error::NotRunning
197
+ end
198
+ end
199
+ end
200
+
201
+ describe '#on_error' do
202
+ it 'sets a task to be passed raised exceptions' do
203
+ run_reactor_in_thread
204
+
205
+ e = nil
206
+ subject.on_error do |_, error|
207
+ e = error
208
+ end
209
+
210
+ subject.next_tick do
211
+ raise
212
+ end
213
+
214
+ sleep 0.1 while !e
215
+
216
+ e.should be_kind_of RuntimeError
217
+ end
218
+
219
+ context 'when the reactor is not running' do
220
+ it "raises #{klass::Error::NotRunning}" do
221
+ expect do
222
+ subject.next_tick{}
223
+ end.to raise_error klass::Error::NotRunning
224
+ end
225
+ end
226
+ end
227
+
228
+ describe '#on_shutdown' do
229
+ it 'calls the given blocks during shutdown' do
230
+ subject.run_in_thread
231
+
232
+ count = 0
233
+ 2.times do
234
+ subject.on_shutdown do
235
+ count += 1
236
+ end
237
+ end
238
+
239
+ sleep 1
240
+
241
+ count.should == 0
242
+
243
+ subject.stop
244
+ subject.wait
245
+
246
+ count.should == 2
247
+ end
248
+
249
+ context 'when the reactor is not running' do
250
+ it "raises #{klass::Error::NotRunning}" do
251
+ expect do
252
+ subject.on_shutdown{}
253
+ end.to raise_error klass::Error::NotRunning
254
+ end
255
+ end
256
+ end
257
+
258
+ describe '#on_tick' do
259
+ it "schedules a task to be run at each tick in the #{klass}#thread" do
260
+ counted_ticks = 0
261
+ reactor_thread = nil
262
+
263
+ thread = run_reactor_in_thread
264
+
265
+ ticks = []
266
+ subject.on_tick do
267
+ reactor_thread = Thread.current
268
+ ticks << subject.ticks
269
+ end
270
+
271
+ sleep 1
272
+
273
+ # Logged ticks should be sequential.
274
+ ticks.size.times do |i|
275
+ next if !ticks[i+1]
276
+
277
+ (ticks[i+1] - ticks[i]).should == 1
278
+ end
279
+
280
+ reactor_thread.should be_kind_of Thread
281
+ reactor_thread.should_not == Thread.current
282
+ thread.should == reactor_thread
283
+ end
284
+
285
+ context 'when the reactor is not running' do
286
+ it "raises #{klass::Error::NotRunning}" do
287
+ expect do
288
+ subject.on_tick{}
289
+ end.to raise_error klass::Error::NotRunning
290
+ end
291
+ end
292
+ end
293
+
294
+ describe '#schedule' do
295
+ context 'when the reactor is running' do
296
+ context 'in the same thread' do
297
+ it 'calls the block right away' do
298
+ subject.run_block do
299
+ out_tick = subject.ticks
300
+ in_tick = nil
301
+
302
+ subject.schedule do
303
+ in_tick = subject.ticks
304
+ end
305
+
306
+ out_tick.should == in_tick
307
+ end
308
+ end
309
+ end
310
+
311
+ context 'in a different thread' do
312
+ it 'calls the block at the next tick' do
313
+ t = run_reactor_in_thread
314
+
315
+ subject.schedule do
316
+ subject.should be_in_same_thread
317
+ subject.stop
318
+ end
319
+ t.join
320
+ end
321
+ end
322
+ end
323
+
324
+ context 'when the reactor is not running' do
325
+ it "raises #{klass::Error::NotRunning}" do
326
+ expect do
327
+ subject.schedule{}
328
+ end.to raise_error klass::Error::NotRunning
329
+ end
330
+ end
331
+ end
332
+
333
+ describe '#next_tick' do
334
+ it "schedules a task to be run at the next tick in the #{klass}#thread" do
335
+ thread = run_reactor_in_thread
336
+
337
+ reactor_thread = nil
338
+ subject.next_tick do
339
+ reactor_thread = Thread.current
340
+ end
341
+
342
+ sleep 0.1 while !reactor_thread
343
+
344
+ reactor_thread.should be_kind_of Thread
345
+ reactor_thread.should_not == Thread.current
346
+ thread.should == reactor_thread
347
+ end
348
+
349
+ context 'when the reactor is not running' do
350
+ it "raises #{klass::Error::NotRunning}" do
351
+ expect do
352
+ subject.next_tick{}
353
+ end.to raise_error klass::Error::NotRunning
354
+ end
355
+ end
356
+ end
357
+
358
+ describe '#at_interval' do
359
+ it "schedules a task to be run at the given interval in the #{klass}#thread" do
360
+ counted_ticks = 0
361
+ reactor_thread = nil
362
+
363
+ thread = run_reactor_in_thread
364
+
365
+ subject.at_interval 0.5 do
366
+ reactor_thread = Thread.current
367
+ counted_ticks += 1
368
+ end
369
+
370
+ sleep 2
371
+
372
+ counted_ticks.should == 3
373
+
374
+ reactor_thread.should be_kind_of Thread
375
+ reactor_thread.should_not == Thread.current
376
+ thread.should == reactor_thread
377
+ end
378
+
379
+ context 'when the reactor is not running' do
380
+ it "raises #{klass::Error::NotRunning}" do
381
+ expect do
382
+ subject.at_interval(1){}
383
+ end.to raise_error klass::Error::NotRunning
384
+ end
385
+ end
386
+ end
387
+
388
+ describe '#delay' do
389
+ it "schedules a task to be run at the given time in the #{klass}#thread" do
390
+ counted_ticks = 0
391
+ reactor_thread = nil
392
+ call_time = nil
393
+
394
+ thread = run_reactor_in_thread
395
+
396
+ subject.delay 1 do
397
+ reactor_thread = Thread.current
398
+ call_time = Time.now
399
+ counted_ticks += 1
400
+ end
401
+
402
+ sleep 3
403
+
404
+ (Time.now - call_time).to_i.should == 1
405
+ counted_ticks.should == 1
406
+
407
+ reactor_thread.should be_kind_of Thread
408
+ reactor_thread.should_not == Thread.current
409
+ thread.should == reactor_thread
410
+ end
411
+
412
+ context 'when the reactor is not running' do
413
+ it "raises #{klass::Error::NotRunning}" do
414
+ expect do
415
+ subject.delay(1){}
416
+ end.to raise_error klass::Error::NotRunning
417
+ end
418
+ end
419
+ end
420
+
421
+ describe '#thread' do
422
+ context 'when the reactor is' do
423
+ context 'not running' do
424
+ it 'returns nil' do
425
+ subject.thread.should be_nil
426
+ end
427
+ end
428
+
429
+ context 'running' do
430
+ it 'returns the thread of the reactor loop' do
431
+ thread = raktr.run_in_thread
432
+
433
+ subject.thread.should == thread
434
+ subject.thread.should_not == Thread.current
435
+ end
436
+ end
437
+
438
+ context 'stopped' do
439
+ it 'sets it to nil' do
440
+ raktr.run_in_thread
441
+ sleep 1
442
+ subject.stop
443
+ sleep 0.1 while subject.running?
444
+
445
+ subject.thread.should be_nil
446
+ end
447
+ end
448
+ end
449
+ end
450
+
451
+ describe '#in_same_thread?' do
452
+ context 'when running in the same thread as the reactor loop' do
453
+ it 'returns true' do
454
+ t = run_reactor_in_thread
455
+ sleep 0.1
456
+
457
+ subject.next_tick do
458
+ subject.should be_in_same_thread
459
+ subject.stop
460
+ end
461
+
462
+ t.join
463
+ end
464
+ end
465
+ context 'when not running in the same thread as the reactor loop' do
466
+ it 'returns false' do
467
+ run_reactor_in_thread
468
+ sleep 0.1
469
+
470
+ subject.should_not be_in_same_thread
471
+ end
472
+ end
473
+ context 'when the reactor is not running' do
474
+ it "raises #{klass::Error::NotRunning}" do
475
+ expect { subject.in_same_thread? }.to raise_error klass::Error::NotRunning
476
+ end
477
+ end
478
+ end
479
+
480
+ describe '#running?' do
481
+ context 'when the reactor is running' do
482
+ it 'returns true' do
483
+ run_reactor_in_thread
484
+
485
+ subject.should be_running
486
+ end
487
+ end
488
+
489
+ context 'when the reactor is not running' do
490
+ it 'returns false' do
491
+ subject.should_not be_running
492
+ end
493
+ end
494
+
495
+ context 'when the reactor has been stopped' do
496
+ it 'returns false' do
497
+ run_reactor_in_thread
498
+
499
+ Timeout.timeout 10 do
500
+ sleep 0.1 while !subject.running?
501
+ end
502
+
503
+ subject.should be_running
504
+ subject.stop
505
+
506
+ Timeout.timeout 10 do
507
+ sleep 0.1 while subject.running?
508
+ end
509
+
510
+ subject.should_not be_running
511
+ end
512
+ end
513
+
514
+ context 'when the reactor thread has been killed' do
515
+ it 'returns false' do
516
+ run_reactor_in_thread
517
+
518
+ Timeout.timeout 10 do
519
+ sleep 0.1 while !subject.running?
520
+ end
521
+
522
+ subject.should be_running
523
+ subject.thread.kill
524
+
525
+ Timeout.timeout 10 do
526
+ sleep 0.1 while subject.thread.alive?
527
+ end
528
+
529
+ subject.should_not be_running
530
+ end
531
+ end
532
+ end
533
+
534
+ describe '#stop' do
535
+ it 'stops the reactor' do
536
+ subject.run_in_thread
537
+
538
+ subject.should be_running
539
+ subject.stop
540
+
541
+ Timeout.timeout 10 do
542
+ sleep 0.1 while subject.running?
543
+ end
544
+
545
+ subject.should_not be_running
546
+ end
547
+ end
548
+
549
+ describe '#connect' do
550
+ context 'when using UNIX domain sockets',
551
+ if: Raktr.supports_unix_sockets? do
552
+
553
+ it "returns #{klass::Connection}" do
554
+ subject.run_block do
555
+ subject.connect( @unix_socket, echo_client_handler ).should be_kind_of klass::Connection
556
+ end
557
+ end
558
+
559
+ it 'establishes a connection' do
560
+ outside_thread = Thread.current
561
+ subject.run do
562
+ Thread.current[:outside_thread] = outside_thread
563
+ Thread.current[:data] = data
564
+
565
+ subject.connect( @unix_socket, echo_client_handler ) do |c|
566
+ def c.on_connect
567
+ super
568
+ write Thread.current[:data]
569
+ end
570
+
571
+ def c.on_close( _ )
572
+ Thread.current[:outside_thread][:received_data] = received_data
573
+ end
574
+ end
575
+ end
576
+
577
+ outside_thread[:received_data].should == data
578
+ end
579
+
580
+ context 'when the socket is invalid' do
581
+ it "calls #on_close with #{klass::Connection::Error::HostNotFound}" do
582
+ outside_thread = Thread.current
583
+ subject.run do
584
+ Thread.current[:outside_thread] = outside_thread
585
+
586
+ subject.connect( 'blahblah', echo_client_handler ) do |c|
587
+ def c.on_close( reason )
588
+ Thread.current[:outside_thread][:error] = reason
589
+ raktr.stop
590
+ end
591
+ end
592
+ end
593
+
594
+ Thread.current[:outside_thread][:error].should be_a_kind_of klass::Connection::Error::HostNotFound
595
+ end
596
+ end
597
+ end
598
+
599
+ context 'when using TCP sockets' do
600
+ it "returns #{klass::Connection}" do
601
+ subject.run_block do
602
+ subject.connect( @host, @port, echo_client_handler ).should be_kind_of klass::Connection
603
+ end
604
+ end
605
+
606
+ it 'establishes a connection' do
607
+ outside_thread = Thread.current
608
+ subject.run do
609
+ Thread.current[:outside_thread] = outside_thread
610
+ Thread.current[:data] = data
611
+
612
+ subject.connect( @host, @port, echo_client_handler ) do |c|
613
+ def c.on_connect
614
+ super
615
+ write Thread.current[:data]
616
+ end
617
+
618
+ def c.on_close( _ )
619
+ Thread.current[:outside_thread][:received_data] = received_data
620
+ end
621
+ end
622
+ end
623
+
624
+ outside_thread[:received_data].should == data
625
+ end
626
+
627
+ context 'when the host is invalid' do
628
+ it "calls #on_close with #{klass::Connection::Error::HostNotFound}" do
629
+ outside_thread = Thread.current
630
+ subject.run do
631
+ Thread.current[:outside_thread] = outside_thread
632
+
633
+ subject.connect( 'blahblah', 9876, echo_client_handler ) do |c|
634
+ def c.on_close( reason )
635
+ Thread.current[:outside_thread][:error] = reason
636
+ raktr.stop
637
+ end
638
+ end
639
+ end
640
+
641
+ Thread.current[:outside_thread][:error].should be_a_kind_of klass::Connection::Error::HostNotFound
642
+ end
643
+ end
644
+
645
+ context 'when the port is invalid' do
646
+ it "calls #on_close with #{klass::Connection::Error::Refused}" do
647
+ outside_thread = Thread.current
648
+ subject.run do
649
+ Thread.current[:outside_thread] = outside_thread
650
+
651
+ subject.connect( @host, @port + 1, echo_client_handler ) do |c|
652
+ def c.on_close( reason )
653
+ # Depending on when the error was caught, there
654
+ # may not be a reason available.
655
+ if reason
656
+ Thread.current[:outside_thread][:error] = reason.class
657
+ else
658
+ Thread.current[:outside_thread][:error] = :error
659
+ end
660
+
661
+ raktr.stop
662
+ end
663
+ end
664
+ end
665
+
666
+ [:error, klass::Connection::Error::Closed,
667
+ klass::Connection::Error::Refused,
668
+ Raktr::Connection::Error::BrokenPipe
669
+ ].should include Thread.current[:outside_thread][:error]
670
+ end
671
+ end
672
+ end
673
+
674
+ context 'when handler options have been provided' do
675
+ it 'initializes the handler with them' do
676
+ options = [:blah, { some: 'stuff' }]
677
+
678
+ subject.run_block do
679
+ subject.connect( @host, @port, echo_client_handler, *options ).
680
+ initialization_args.should == options
681
+ end
682
+ end
683
+ end
684
+
685
+ context 'when the reactor is not running' do
686
+ it "raises #{klass::Error::NotRunning}" do
687
+ expect do
688
+ subject.connect( 'blahblah', echo_client_handler )
689
+ end.to raise_error klass::Error::NotRunning
690
+ end
691
+ end
692
+ end
693
+
694
+ describe '#listen' do
695
+ let(:host) { '127.0.0.1' }
696
+ let(:port) { Servers.available_port }
697
+ let(:unix_socket) { port_to_socket Servers.available_port }
698
+
699
+ context 'when using UNIX domain sockets',
700
+ if: Raktr.supports_unix_sockets? do
701
+
702
+ it "returns #{klass::Connection}" do
703
+ subject.run_block do
704
+ subject.listen( unix_socket, echo_server_handler ).should be_kind_of klass::Connection
705
+ end
706
+ end
707
+
708
+ it 'listens for incoming connections' do
709
+ subject.run_in_thread
710
+
711
+ subject.listen( unix_socket, echo_server_handler )
712
+
713
+ @socket = unix_writer.call( unix_socket, data )
714
+ @socket.read( data.size ).should == data
715
+ end
716
+
717
+ context 'when the socket is invalid' do
718
+ it 'calls #on_close' do
719
+ outside_thread = Thread.current
720
+ subject.run do
721
+ Thread.current[:outside_thread] = outside_thread
722
+
723
+ subject.listen( '/socket', echo_server_handler ) do |c|
724
+ def c.on_close( reason )
725
+ # Depending on when the error was caught, there
726
+ # may not be a reason available.
727
+ if reason
728
+ Thread.current[:outside_thread][:error] = reason.class
729
+ else
730
+ Thread.current[:outside_thread][:error] = :error
731
+ end
732
+
733
+ raktr.stop
734
+ end
735
+ end
736
+ end
737
+
738
+ [:error, klass::Connection::Error::Permission].should include Thread.current[:outside_thread][:error]
739
+ end
740
+ end
741
+ end
742
+
743
+ context 'when using TCP sockets' do
744
+ it "returns #{klass::Connection}" do
745
+ subject.run_block do
746
+ subject.listen( host, port, echo_server_handler ).should be_kind_of klass::Connection
747
+ end
748
+ end
749
+
750
+ it 'listens for incoming connections' do
751
+ subject.run_in_thread
752
+
753
+ subject.listen( host, port, echo_server_handler )
754
+
755
+ @socket = tcp_writer.call( host, port, data )
756
+ @socket.read( data.size ).should == data
757
+ end
758
+
759
+ context 'when the host is invalid' do
760
+ it 'calls #on_close' do
761
+ outside_thread = Thread.current
762
+ subject.run do
763
+ Thread.current[:outside_thread] = outside_thread
764
+
765
+ subject.listen( 'host', port, echo_server_handler ) do |c|
766
+ def c.on_close( reason )
767
+ # Depending on when the error was caught, there
768
+ # may not be a reason available.
769
+ if reason
770
+ Thread.current[:outside_thread][:error] = reason.class
771
+ else
772
+ Thread.current[:outside_thread][:error] = :error
773
+ end
774
+
775
+ raktr.stop
776
+ end
777
+ end
778
+ end
779
+
780
+ [:error, klass::Connection::Error::HostNotFound].should include Thread.current[:outside_thread][:error]
781
+ end
782
+ end
783
+
784
+ context 'when the port is invalid', if: !Gem.win_platform? do
785
+ it 'calls #on_close' do
786
+ outside_thread = Thread.current
787
+ subject.run do
788
+ Thread.current[:outside_thread] = outside_thread
789
+
790
+ subject.listen( host, 50, echo_server_handler ) do |c|
791
+ def c.on_close( reason )
792
+ # Depending on when the error was caught, there
793
+ # may not be a reason available.
794
+ if reason
795
+ Thread.current[:outside_thread][:error] = reason.class
796
+ else
797
+ Thread.current[:outside_thread][:error] = :error
798
+ end
799
+
800
+ raktr.stop
801
+ end
802
+ end
803
+ end
804
+
805
+ [:error, klass::Connection::Error::Permission].should include Thread.current[:outside_thread][:error]
806
+ end
807
+ end
808
+ end
809
+
810
+ context 'when handler options have been provided' do
811
+ it 'initializes the handler with them' do
812
+ options = [:blah, { some: 'stuff' }]
813
+
814
+ subject.run_in_thread
815
+
816
+ subject.listen( host, port, echo_server_handler, *options )
817
+
818
+ @socket = tcp_writer.call( host, port, data )
819
+
820
+ sleep 5
821
+
822
+ subject.connections.values.first.initialization_args.should == options
823
+ end
824
+ end
825
+
826
+ context 'when the reactor is not running' do
827
+ it "raises #{klass::Error::NotRunning}" do
828
+ expect do
829
+ subject.listen( host, port, echo_server_handler )
830
+ end.to raise_error klass::Error::NotRunning
831
+ end
832
+ end
833
+ end
834
+ end