arachni-reactor 0.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +15 -0
  2. data/CHANGELOG.md +5 -0
  3. data/LICENSE.md +29 -0
  4. data/README.md +79 -0
  5. data/Rakefile +53 -0
  6. data/lib/arachni/reactor.rb +679 -0
  7. data/lib/arachni/reactor/connection.rb +302 -0
  8. data/lib/arachni/reactor/connection/callbacks.rb +73 -0
  9. data/lib/arachni/reactor/connection/error.rb +114 -0
  10. data/lib/arachni/reactor/connection/peer_info.rb +92 -0
  11. data/lib/arachni/reactor/connection/tls.rb +107 -0
  12. data/lib/arachni/reactor/global.rb +26 -0
  13. data/lib/arachni/reactor/iterator.rb +251 -0
  14. data/lib/arachni/reactor/queue.rb +91 -0
  15. data/lib/arachni/reactor/tasks.rb +107 -0
  16. data/lib/arachni/reactor/tasks/base.rb +59 -0
  17. data/lib/arachni/reactor/tasks/delayed.rb +35 -0
  18. data/lib/arachni/reactor/tasks/one_off.rb +30 -0
  19. data/lib/arachni/reactor/tasks/periodic.rb +60 -0
  20. data/lib/arachni/reactor/tasks/persistent.rb +31 -0
  21. data/lib/arachni/reactor/version.rb +15 -0
  22. data/spec/arachni/reactor/connection/tls_spec.rb +332 -0
  23. data/spec/arachni/reactor/connection_spec.rb +58 -0
  24. data/spec/arachni/reactor/iterator_spec.rb +203 -0
  25. data/spec/arachni/reactor/queue_spec.rb +91 -0
  26. data/spec/arachni/reactor/tasks/base.rb +8 -0
  27. data/spec/arachni/reactor/tasks/delayed_spec.rb +54 -0
  28. data/spec/arachni/reactor/tasks/one_off_spec.rb +51 -0
  29. data/spec/arachni/reactor/tasks/periodic_spec.rb +40 -0
  30. data/spec/arachni/reactor/tasks/persistent_spec.rb +39 -0
  31. data/spec/arachni/reactor/tasks_spec.rb +136 -0
  32. data/spec/arachni/reactor_spec.rb +20 -0
  33. data/spec/arachni/reactor_tls_spec.rb +20 -0
  34. data/spec/spec_helper.rb +16 -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 +117 -0
  48. data/spec/support/lib/server_option_parser.rb +29 -0
  49. data/spec/support/lib/servers.rb +133 -0
  50. data/spec/support/lib/servers/runner.rb +13 -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 +778 -0
  56. data/spec/support/shared/reactor.rb +785 -0
  57. data/spec/support/shared/task.rb +21 -0
  58. metadata +141 -0
@@ -0,0 +1,785 @@
1
+ shared_examples_for 'Arachni::Reactor' do
2
+ after(:each) do
3
+ @socket.close if @socket
4
+ @socket = nil
5
+
6
+ next if !@reactor
7
+
8
+ if @reactor.running?
9
+ @reactor.stop
10
+ end
11
+
12
+ @reactor = nil
13
+ end
14
+
15
+ klass = Arachni::Reactor
16
+
17
+ subject { @reactor ||= klass.new }
18
+ let(:reactor) { 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.reactor.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.reactor.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_true
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_shutdown' do
202
+ it 'calls the given blocks during shutdown' do
203
+ subject.run_in_thread
204
+
205
+ count = 0
206
+ 2.times do
207
+ subject.on_shutdown do
208
+ count += 1
209
+ end
210
+ end
211
+
212
+ sleep 1
213
+
214
+ count.should == 0
215
+
216
+ subject.stop
217
+ subject.wait
218
+
219
+ count.should == 2
220
+ end
221
+
222
+ context 'when the reactor is not running' do
223
+ it "raises #{klass::Error::NotRunning}" do
224
+ expect do
225
+ subject.on_shutdown{}
226
+ end.to raise_error klass::Error::NotRunning
227
+ end
228
+ end
229
+ end
230
+
231
+ describe '#on_tick' do
232
+ it "schedules a task to be run at each tick in the #{klass}#thread" do
233
+ counted_ticks = 0
234
+ reactor_thread = nil
235
+
236
+ thread = run_reactor_in_thread
237
+
238
+ ticks = []
239
+ subject.on_tick do
240
+ reactor_thread = Thread.current
241
+ ticks << subject.ticks
242
+ end
243
+
244
+ sleep 1
245
+
246
+ # Logged ticks should be sequential.
247
+ ticks.size.times do |i|
248
+ next if !ticks[i+1]
249
+
250
+ (ticks[i+1] - ticks[i]).should == 1
251
+ end
252
+
253
+ reactor_thread.should be_kind_of Thread
254
+ reactor_thread.should_not == Thread.current
255
+ thread.should == reactor_thread
256
+ end
257
+
258
+ context 'when the reactor is not running' do
259
+ it "raises #{klass::Error::NotRunning}" do
260
+ expect do
261
+ subject.on_tick{}
262
+ end.to raise_error klass::Error::NotRunning
263
+ end
264
+ end
265
+ end
266
+
267
+ describe '#schedule' do
268
+ context 'when the reactor is running' do
269
+ context 'in the same thread' do
270
+ it 'calls the block right away' do
271
+ subject.run_block do
272
+ out_tick = subject.ticks
273
+ in_tick = nil
274
+
275
+ subject.schedule do
276
+ in_tick = subject.ticks
277
+ end
278
+
279
+ out_tick.should == in_tick
280
+ end
281
+ end
282
+ end
283
+
284
+ context 'in a different thread' do
285
+ it 'calls the block at the next tick' do
286
+ t = run_reactor_in_thread
287
+
288
+ subject.schedule do
289
+ subject.should be_in_same_thread
290
+ subject.stop
291
+ end
292
+ t.join
293
+ end
294
+ end
295
+ end
296
+
297
+ context 'when the reactor is not running' do
298
+ it "raises #{klass::Error::NotRunning}" do
299
+ expect do
300
+ subject.schedule{}
301
+ end.to raise_error klass::Error::NotRunning
302
+ end
303
+ end
304
+ end
305
+
306
+ describe '#next_tick' do
307
+ it "schedules a task to be run at the next tick in the #{klass}#thread" do
308
+ thread = run_reactor_in_thread
309
+
310
+ reactor_thread = nil
311
+ subject.next_tick do
312
+ reactor_thread = Thread.current
313
+ end
314
+
315
+ sleep 0.1 while !reactor_thread
316
+
317
+ reactor_thread.should be_kind_of Thread
318
+ reactor_thread.should_not == Thread.current
319
+ thread.should == reactor_thread
320
+ end
321
+
322
+ context 'when the reactor is not running' do
323
+ it "raises #{klass::Error::NotRunning}" do
324
+ expect do
325
+ subject.next_tick{}
326
+ end.to raise_error klass::Error::NotRunning
327
+ end
328
+ end
329
+ end
330
+
331
+ describe '#at_interval' do
332
+ it "schedules a task to be run at the given interval in the #{klass}#thread" do
333
+ counted_ticks = 0
334
+ reactor_thread = nil
335
+
336
+ thread = run_reactor_in_thread
337
+
338
+ subject.at_interval 0.5 do
339
+ reactor_thread = Thread.current
340
+ counted_ticks += 1
341
+ end
342
+
343
+ sleep 2
344
+
345
+ counted_ticks.should == 3
346
+
347
+ reactor_thread.should be_kind_of Thread
348
+ reactor_thread.should_not == Thread.current
349
+ thread.should == reactor_thread
350
+ end
351
+
352
+ context 'when the reactor is not running' do
353
+ it "raises #{klass::Error::NotRunning}" do
354
+ expect do
355
+ subject.at_interval(1){}
356
+ end.to raise_error klass::Error::NotRunning
357
+ end
358
+ end
359
+ end
360
+
361
+ describe '#delay' do
362
+ it "schedules a task to be run at the given time in the #{klass}#thread" do
363
+ counted_ticks = 0
364
+ reactor_thread = nil
365
+ call_time = nil
366
+
367
+ thread = run_reactor_in_thread
368
+
369
+ subject.delay 1 do
370
+ reactor_thread = Thread.current
371
+ call_time = Time.now
372
+ counted_ticks += 1
373
+ end
374
+
375
+ sleep 3
376
+
377
+ (Time.now - call_time).to_i.should == 1
378
+ counted_ticks.should == 1
379
+
380
+ reactor_thread.should be_kind_of Thread
381
+ reactor_thread.should_not == Thread.current
382
+ thread.should == reactor_thread
383
+ end
384
+
385
+ context 'when the reactor is not running' do
386
+ it "raises #{klass::Error::NotRunning}" do
387
+ expect do
388
+ subject.delay(1){}
389
+ end.to raise_error klass::Error::NotRunning
390
+ end
391
+ end
392
+ end
393
+
394
+ describe '#thread' do
395
+ context 'when the reactor is' do
396
+ context 'not running' do
397
+ it 'returns nil' do
398
+ subject.thread.should be_nil
399
+ end
400
+ end
401
+
402
+ context 'running' do
403
+ it 'returns the thread of the reactor loop' do
404
+ thread = reactor.run_in_thread
405
+
406
+ subject.thread.should == thread
407
+ subject.thread.should_not == Thread.current
408
+ end
409
+ end
410
+
411
+ context 'stopped' do
412
+ it 'sets it to nil' do
413
+ reactor.run_in_thread
414
+ sleep 1
415
+ subject.stop
416
+ sleep 0.1 while subject.running?
417
+
418
+ subject.thread.should be_nil
419
+ end
420
+ end
421
+ end
422
+ end
423
+
424
+ describe '#in_same_thread?' do
425
+ context 'when running in the same thread as the reactor loop' do
426
+ it 'returns true' do
427
+ t = run_reactor_in_thread
428
+ sleep 0.1
429
+
430
+ subject.next_tick do
431
+ subject.should be_in_same_thread
432
+ subject.stop
433
+ end
434
+
435
+ t.join
436
+ end
437
+ end
438
+ context 'when not running in the same thread as the reactor loop' do
439
+ it 'returns false' do
440
+ run_reactor_in_thread
441
+ sleep 0.1
442
+
443
+ subject.should_not be_in_same_thread
444
+ end
445
+ end
446
+ context 'when the reactor is not running' do
447
+ it "raises #{klass::Error::NotRunning}" do
448
+ expect { subject.in_same_thread? }.to raise_error klass::Error::NotRunning
449
+ end
450
+ end
451
+ end
452
+
453
+ describe '#running?' do
454
+ context 'when the reactor is running' do
455
+ it 'returns true' do
456
+ run_reactor_in_thread
457
+
458
+ subject.should be_running
459
+ end
460
+ end
461
+
462
+ context 'when the reactor is not running' do
463
+ it 'returns false' do
464
+ subject.should_not be_running
465
+ end
466
+ end
467
+
468
+ context 'when the reactor has been stopped' do
469
+ it 'returns false' do
470
+ run_reactor_in_thread
471
+
472
+ Timeout.timeout 10 do
473
+ sleep 0.1 while !subject.running?
474
+ end
475
+
476
+ subject.should be_running
477
+ subject.stop
478
+
479
+ Timeout.timeout 10 do
480
+ sleep 0.1 while subject.running?
481
+ end
482
+
483
+ subject.should_not be_running
484
+ end
485
+ end
486
+ end
487
+
488
+ describe '#stop' do
489
+ it 'stops the reactor' do
490
+ subject.run_in_thread
491
+
492
+ subject.should be_running
493
+ subject.stop
494
+
495
+ Timeout.timeout 10 do
496
+ sleep 0.1 while subject.running?
497
+ end
498
+
499
+ subject.should_not be_running
500
+ end
501
+ end
502
+
503
+ describe '#connect' do
504
+ context 'when using UNIX domain sockets',
505
+ if: Arachni::Reactor.supports_unix_sockets? do
506
+
507
+ it "returns #{klass::Connection}" do
508
+ subject.run_block do
509
+ subject.connect( @unix_socket, echo_client_handler ).should be_kind_of klass::Connection
510
+ end
511
+ end
512
+
513
+ it 'establishes a connection' do
514
+ outside_thread = Thread.current
515
+ subject.run do
516
+ Thread.current[:outside_thread] = outside_thread
517
+ Thread.current[:data] = data
518
+
519
+ subject.connect( @unix_socket, echo_client_handler ) do |c|
520
+ def c.on_connect
521
+ super
522
+ write Thread.current[:data]
523
+ end
524
+
525
+ def c.on_close( _ )
526
+ Thread.current[:outside_thread][:received_data] = received_data
527
+ end
528
+ end
529
+ end
530
+
531
+ outside_thread[:received_data].should == data
532
+ end
533
+
534
+ context 'when the socket is invalid' do
535
+ it "calls #on_close with #{klass::Connection::Error::HostNotFound}" do
536
+ outside_thread = Thread.current
537
+ subject.run do
538
+ Thread.current[:outside_thread] = outside_thread
539
+
540
+ subject.connect( 'blahblah', echo_client_handler ) do |c|
541
+ def c.on_close( reason )
542
+ Thread.current[:outside_thread][:error] = reason
543
+ reactor.stop
544
+ end
545
+ end
546
+ end
547
+
548
+ Thread.current[:outside_thread][:error].should be_a_kind_of klass::Connection::Error::HostNotFound
549
+ end
550
+ end
551
+ end
552
+
553
+ context 'when using TCP sockets' do
554
+ it "returns #{klass::Connection}" do
555
+ subject.run_block do
556
+ subject.connect( @host, @port, echo_client_handler ).should be_kind_of klass::Connection
557
+ end
558
+ end
559
+
560
+ it 'establishes a connection' do
561
+ outside_thread = Thread.current
562
+ subject.run do
563
+ Thread.current[:outside_thread] = outside_thread
564
+ Thread.current[:data] = data
565
+
566
+ subject.connect( @host, @port, echo_client_handler ) do |c|
567
+ def c.on_connect
568
+ super
569
+ write Thread.current[:data]
570
+ end
571
+
572
+ def c.on_close( _ )
573
+ Thread.current[:outside_thread][:received_data] = received_data
574
+ end
575
+ end
576
+ end
577
+
578
+ outside_thread[:received_data].should == data
579
+ end
580
+
581
+ context 'when the host is invalid' do
582
+ it "calls #on_close with #{klass::Connection::Error::HostNotFound}" do
583
+ outside_thread = Thread.current
584
+ subject.run do
585
+ Thread.current[:outside_thread] = outside_thread
586
+
587
+ subject.connect( 'blahblah', 9876, echo_client_handler ) do |c|
588
+ def c.on_close( reason )
589
+ Thread.current[:outside_thread][:error] = reason
590
+ reactor.stop
591
+ end
592
+ end
593
+ end
594
+
595
+ Thread.current[:outside_thread][:error].should be_a_kind_of klass::Connection::Error::HostNotFound
596
+ end
597
+ end
598
+
599
+ context 'when the port is invalid' do
600
+ it "calls #on_close with #{klass::Connection::Error::Refused}" do
601
+ outside_thread = Thread.current
602
+ subject.run do
603
+ Thread.current[:outside_thread] = outside_thread
604
+
605
+ subject.connect( @host, @port + 1, echo_client_handler ) do |c|
606
+ def c.on_close( reason )
607
+ # Depending on when the error was caught, there
608
+ # may not be a reason available.
609
+ if reason
610
+ Thread.current[:outside_thread][:error] = reason.class
611
+ else
612
+ Thread.current[:outside_thread][:error] = :error
613
+ end
614
+
615
+ reactor.stop
616
+ end
617
+ end
618
+ end
619
+
620
+ [:error, klass::Connection::Error::Closed,
621
+ klass::Connection::Error::Refused,
622
+ Arachni::Reactor::Connection::Error::BrokenPipe
623
+ ].should include Thread.current[:outside_thread][:error]
624
+ end
625
+ end
626
+ end
627
+
628
+ context 'when handler options have been provided' do
629
+ it 'initializes the handler with them' do
630
+ options = [:blah, { some: 'stuff' }]
631
+
632
+ subject.run_block do
633
+ subject.connect( @host, @port, echo_client_handler, *options ).
634
+ initialization_args.should == options
635
+ end
636
+ end
637
+ end
638
+
639
+ context 'when the reactor is not running' do
640
+ it "raises #{klass::Error::NotRunning}" do
641
+ expect do
642
+ subject.connect( 'blahblah', echo_client_handler )
643
+ end.to raise_error klass::Error::NotRunning
644
+ end
645
+ end
646
+ end
647
+
648
+ describe '#listen' do
649
+ let(:host) { '127.0.0.1' }
650
+ let(:port) { Servers.available_port }
651
+ let(:unix_socket) { port_to_socket Servers.available_port }
652
+
653
+ context 'when using UNIX domain sockets',
654
+ if: Arachni::Reactor.supports_unix_sockets? do
655
+
656
+ it "returns #{klass::Connection}" do
657
+ subject.run_block do
658
+ subject.listen( unix_socket, echo_server_handler ).should be_kind_of klass::Connection
659
+ end
660
+ end
661
+
662
+ it 'listens for incoming connections' do
663
+ subject.run_in_thread
664
+
665
+ subject.listen( unix_socket, echo_server_handler )
666
+
667
+ @socket = unix_writer.call( unix_socket, data )
668
+ @socket.read( data.size ).should == data
669
+ end
670
+
671
+ context 'when the socket is invalid' do
672
+ it 'calls #on_close' do
673
+ outside_thread = Thread.current
674
+ subject.run do
675
+ Thread.current[:outside_thread] = outside_thread
676
+
677
+ subject.listen( '/socket', echo_server_handler ) do |c|
678
+ def c.on_close( reason )
679
+ # Depending on when the error was caught, there
680
+ # may not be a reason available.
681
+ if reason
682
+ Thread.current[:outside_thread][:error] = reason.class
683
+ else
684
+ Thread.current[:outside_thread][:error] = :error
685
+ end
686
+
687
+ reactor.stop
688
+ end
689
+ end
690
+ end
691
+
692
+ [:error, klass::Connection::Error::Permission].should include Thread.current[:outside_thread][:error]
693
+ end
694
+ end
695
+ end
696
+
697
+ context 'when using TCP sockets' do
698
+ it "returns #{klass::Connection}" do
699
+ subject.run_block do
700
+ subject.listen( host, port, echo_server_handler ).should be_kind_of klass::Connection
701
+ end
702
+ end
703
+
704
+ it 'listens for incoming connections' do
705
+ subject.run_in_thread
706
+
707
+ subject.listen( host, port, echo_server_handler )
708
+
709
+ @socket = tcp_writer.call( host, port, data )
710
+ @socket.read( data.size ).should == data
711
+ end
712
+
713
+ context 'when the host is invalid' do
714
+ it 'calls #on_close' do
715
+ outside_thread = Thread.current
716
+ subject.run do
717
+ Thread.current[:outside_thread] = outside_thread
718
+
719
+ subject.listen( 'host', port, echo_server_handler ) do |c|
720
+ def c.on_close( reason )
721
+ # Depending on when the error was caught, there
722
+ # may not be a reason available.
723
+ if reason
724
+ Thread.current[:outside_thread][:error] = reason.class
725
+ else
726
+ Thread.current[:outside_thread][:error] = :error
727
+ end
728
+
729
+ reactor.stop
730
+ end
731
+ end
732
+ end
733
+
734
+ [:error, klass::Connection::Error::HostNotFound].should include Thread.current[:outside_thread][:error]
735
+ end
736
+ end
737
+
738
+ context 'when the port is invalid', if: !Gem.win_platform? do
739
+ it 'calls #on_close' do
740
+ outside_thread = Thread.current
741
+ subject.run do
742
+ Thread.current[:outside_thread] = outside_thread
743
+
744
+ subject.listen( host, 50, echo_server_handler ) do |c|
745
+ def c.on_close( reason )
746
+ # Depending on when the error was caught, there
747
+ # may not be a reason available.
748
+ if reason
749
+ Thread.current[:outside_thread][:error] = reason.class
750
+ else
751
+ Thread.current[:outside_thread][:error] = :error
752
+ end
753
+
754
+ reactor.stop
755
+ end
756
+ end
757
+ end
758
+
759
+ [:error, klass::Connection::Error::Permission].should include Thread.current[:outside_thread][:error]
760
+ end
761
+ end
762
+ end
763
+
764
+ context 'when handler options have been provided' do
765
+ it 'initializes the handler with them' do
766
+ options = [:blah, { some: 'stuff' }]
767
+
768
+ subject.run_in_thread
769
+
770
+ subject.listen( host, port, echo_server_handler, *options )
771
+
772
+ @socket = tcp_writer.call( host, port, data )
773
+ subject.connections.values.first.initialization_args.should == options
774
+ end
775
+ end
776
+
777
+ context 'when the reactor is not running' do
778
+ it "raises #{klass::Error::NotRunning}" do
779
+ expect do
780
+ subject.listen( host, port, echo_server_handler )
781
+ end.to raise_error klass::Error::NotRunning
782
+ end
783
+ end
784
+ end
785
+ end