tengine_event 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,945 @@
1
+ # -*- coding: utf-8 -*-
2
+ require_relative '../../spec_helper'
3
+
4
+ require_relative '../../../lib/tengine/mq/suite'
5
+
6
+ # ログを黙らせたり喋らせたりする
7
+ require 'amq/client'
8
+ if $DEBUG
9
+ require 'logger'
10
+ AMQP::Session.logger = Tengine.logger = Logger.new(STDERR)
11
+ else
12
+ AMQP::Session.logger = Tengine.logger = Tengine::Support::NullLogger.new
13
+ end
14
+
15
+ require 'amq/client/callbacks'
16
+
17
+ describe Tengine::Mq::Suite do
18
+ shared_examples "Tengine::Mq::Suite" do
19
+ describe "#initialize" do
20
+ context "no args" do
21
+ subject { Tengine::Mq::Suite.new }
22
+ its(:config) { should == {
23
+ :sender => {
24
+ :keep_connection => false,
25
+ :retry_interval => 1,
26
+ :retry_count => 30,
27
+ },
28
+ :connection => {
29
+ :user => 'guest',
30
+ :pass => 'guest',
31
+ :vhost => '/',
32
+ :logging => false,
33
+ :insist => false,
34
+ :host => 'localhost',
35
+ :port => 5672,
36
+ :auto_reconnect_delay => 1,
37
+ },
38
+ :channel => {
39
+ :prefetch => 1,
40
+ :auto_recovery => true,
41
+ },
42
+ :exchange => {
43
+ :name => 'tengine_event_exchange',
44
+ :type => :direct,
45
+ :passive => false,
46
+ :durable => true,
47
+ :auto_delete => false,
48
+ :internal => false,
49
+ :nowait => false,
50
+ :publish => {
51
+ :content_type => "application/json",
52
+ :persistent => true,
53
+ },
54
+ },
55
+ :queue => {
56
+ :name => 'tengine_event_queue',
57
+ :passive => false,
58
+ :durable => true,
59
+ :auto_delete => false,
60
+ :exclusive => false,
61
+ :nowait => false,
62
+ :subscribe => {
63
+ :ack => true,
64
+ :nowait => false,
65
+ :confirm => nil,
66
+ },
67
+ },
68
+ }
69
+ }
70
+ end
71
+
72
+ context "hash arg" do
73
+ subject { Tengine::Mq::Suite.new :sender => { :keep_connection => false } }
74
+ its(:config) { should have_key(:sender) }
75
+ it "merges the argument" do
76
+ subject.config[:sender][:keep_connection].should be_false
77
+ subject.config[:sender].should have_key(:retry_interval)
78
+ end
79
+ end
80
+ end
81
+
82
+ describe "#config" do
83
+ subject { Tengine::Mq::Suite.new.config }
84
+ it { should be_kind_of(Hash) }
85
+ it { should be_frozen }
86
+ end
87
+
88
+ describe "#add_hook" do
89
+ subject { Tengine::Mq::Suite.new the_config }
90
+
91
+ context "no arg" do
92
+ it { expect { subject.add_hook }.to raise_error(ArgumentError) }
93
+ end
94
+
95
+ context "many arg" do
96
+ it { expect { subject.add_hook :foo, :bar }.to raise_error(ArgumentError) }
97
+ end
98
+
99
+ context "no block" do
100
+ it { expect { subject.add_hook :foo }.to raise_error(ArgumentError) }
101
+ end
102
+
103
+ context "one arg, one block" do
104
+ it { expect { subject.add_hook(:foo){ } }.to_not raise_error(ArgumentError) }
105
+ end
106
+
107
+ context "connection.on_closed" do
108
+ it "called" do
109
+ block_called = false
110
+ subject.add_hook "connection.on_closed" do
111
+ block_called = true
112
+ end
113
+
114
+ EM.run_test do
115
+ subject.subscribe {|x, y| }
116
+ sleep 0.1
117
+ EM.add_timer(0.1) { subject.stop }
118
+ end
119
+ block_called.should be_true
120
+ end
121
+ end
122
+
123
+ context "connection.on_tcp_connection_failure" do
124
+ subject {
125
+ port = the_config[:connection] ? the_config[:connection][:port] : 5672
126
+ Tengine::Mq::Suite.new :connection => { :port => port + rand(1024) }
127
+ }
128
+
129
+ it "https://www.pivotaltracker.com/story/show/18317933" do
130
+ block_called = false
131
+ subject.add_hook "connection.on_tcp_connection_failure" do
132
+ block_called = true
133
+ end
134
+ expect {
135
+ EM.run_block do
136
+ subject.subscribe {|x, y| }
137
+ EM.add_timer(0.2) { subject.stop }
138
+ end
139
+ }.to raise_exception
140
+ block_called.should be_true
141
+ end
142
+ end
143
+
144
+ # context "channel.on_error" ...
145
+ # context "connection.after_recovery" ...
146
+
147
+ it "hookを保持する" do
148
+ mq = nil
149
+ # 1st time
150
+ yielded = 0
151
+ EM.run_test do
152
+ mq = subject
153
+ mq.send :ensures, :connection do
154
+ mq.add_hook(:"connection.on_closed") do
155
+ yielded += 1
156
+ end
157
+ mq.stop
158
+ end
159
+ end
160
+
161
+ # 2nd time
162
+ yielded = 0
163
+ EM.run_test do
164
+ mq.send :ensures, :connection do
165
+ mq.stop
166
+ end
167
+ end
168
+ yielded.should == 1
169
+ end
170
+
171
+ it "hookを保持する #2" do
172
+ mq = nil
173
+
174
+ # 1st time
175
+ yielded = 0
176
+ EM.run_test do
177
+ mq = subject
178
+ mq.send :ensures, :channel do
179
+ mq.add_hook(:"channel.on_error") do
180
+ yielded += 1
181
+ end
182
+ mq.channel.exec_callback_once_yielding_self(:error, "channel close reason object")
183
+ mq.stop
184
+ end
185
+ end
186
+
187
+ # 2nd time
188
+ yielded = 0
189
+ EM.run_test do
190
+ mq.send :ensures, :channel do
191
+ mq.channel.exec_callback_once_yielding_self(:error, "channel close reason object")
192
+ mq.stop
193
+ end
194
+ end
195
+ yielded.should == 1
196
+ end
197
+ end
198
+
199
+ describe "#subscribe" do
200
+ subject { Tengine::Mq::Suite.new the_config }
201
+
202
+ context "no block" do
203
+ it { expect { subject.subscribe }.to raise_error(ArgumentError) }
204
+ end
205
+
206
+ context "no reactor, nowait: false" do
207
+ it "raises" do
208
+ block_called = false
209
+ expect {
210
+ subject.subscribe(:nowait => true) {
211
+ block_called = true
212
+ }
213
+ }.to raise_error(RuntimeError)
214
+ block_called.should be_false
215
+ end
216
+ end
217
+
218
+ context "no reactor, nowait: true" do
219
+ it "raises" do
220
+ block_called = false
221
+ expect {
222
+ subject.subscribe(:nowait=>false) {
223
+ block_called = true
224
+ }
225
+ }.to raise_error(RuntimeError)
226
+ block_called.should be_false
227
+ end
228
+ end
229
+
230
+ context "with reactor, with 1 message in queue" do
231
+ it "runs the block" do
232
+ block_called = false
233
+ body = nil
234
+ header = nil
235
+ expect {
236
+ EM.run_test do
237
+ subject.subscribe do |hdr, bdy|
238
+ block_called = true
239
+ body = bdy
240
+ header = hdr
241
+ hdr.ack
242
+ subject.stop
243
+ end
244
+
245
+ sender = Tengine::Event::Sender.new subject
246
+ ev = Tengine::Event.new :event_type_name => "foo"
247
+ subject.fire sender, ev, { :keep_connection => true }, nil
248
+ end
249
+ }.to_not raise_error(RuntimeError)
250
+ block_called.should be_true
251
+ header.should be_kind_of(AMQP::Header)
252
+ body.should be_kind_of(String)
253
+ body.should =~ /foo/
254
+ end
255
+ end
256
+
257
+ context "many messages in queue" do
258
+ before do
259
+ # キューにイベントがすでに溜まってるとおかしくなるので、吸い出しておく
260
+ EM.run_test do
261
+ i = 0
262
+ subject.subscribe do |hdr, bdy|
263
+ hdr.ack
264
+ i += 1
265
+ end
266
+ EM.add_periodic_timer(0.1) do
267
+ subject.stop if i.zero?
268
+ i = 0
269
+ end
270
+ end
271
+ end
272
+
273
+ it "runs the block every time" do
274
+ block_called = 0
275
+ expect {
276
+ EM.run_test do
277
+ subject.subscribe do |hdr, bdy|
278
+ block_called += 1
279
+ hdr.ack
280
+ end
281
+
282
+ sender = Tengine::Event::Sender.new subject
283
+ EM.add_timer(0.1) {
284
+ ev = Tengine::Event.new :event_type_name => "foo"
285
+ subject.fire sender, ev, { :keep_connection => true }, nil
286
+ EM.add_timer(0.1) {
287
+ ev = Tengine::Event.new :event_type_name => "foo"
288
+ subject.fire sender, ev, { :keep_connection => true }, nil
289
+ EM.add_timer(0.1) {
290
+ ev = Tengine::Event.new :event_type_name => "foo"
291
+ subject.fire sender, ev, { :keep_connection => true }, nil
292
+ EM.add_timer(0.3) {
293
+ subject.stop
294
+ }
295
+ }
296
+ }
297
+ }
298
+ end
299
+ }.to_not raise_error(RuntimeError)
300
+ block_called.should == 3
301
+ end
302
+ end
303
+ end
304
+
305
+ describe "#unsubscribe" do
306
+ subject { Tengine::Mq::Suite.new the_config }
307
+
308
+ context "no block" do
309
+ it { expect { subject.unsubscribe }.to raise_error(ArgumentError) }
310
+ end
311
+
312
+ context "no queue" do
313
+ it "runs the block" do
314
+ block_called = false
315
+ expect {
316
+ subject.unsubscribe {
317
+ block_called = true
318
+ subject.stop
319
+ }
320
+ }.to_not raise_error(RuntimeError)
321
+ block_called.should be_true
322
+ end
323
+ end
324
+
325
+ context "queue not subscribed" do
326
+ it "runs the block" do
327
+ block_called = false
328
+ expect {
329
+ EM.run_test do
330
+ subject.send :ensures, :queue do
331
+ subject.unsubscribe do
332
+ block_called = true
333
+ subject.stop
334
+ end
335
+ end
336
+ end
337
+ }.to_not raise_error(RuntimeError)
338
+ block_called.should be_true
339
+ end
340
+ end
341
+
342
+ context "subscribed" do
343
+ it "runs the block" do
344
+ block_called = false
345
+ expect {
346
+ EM.run_test do
347
+ subject.subscribe :nowait => false, :confirm => lambda {|x|
348
+ subject.unsubscribe {|y|
349
+ block_called = true
350
+ subject.stop
351
+ }
352
+ } do |hdr, bdy|
353
+ hdr.ack
354
+ end
355
+ end
356
+ }.to_not raise_error(RuntimeError)
357
+ block_called.should be_true
358
+ end
359
+ end
360
+
361
+ context "nowait:true" do
362
+ it "runs the block" do
363
+ block_called = false
364
+ expect {
365
+ EM.run_test do
366
+ subject.subscribe :nowait => false, :confirm => lambda {|x|
367
+ subject.unsubscribe({:nowait => true}) {|y|
368
+ block_called = true
369
+ subject.stop
370
+ }
371
+ } do |hdr, bdy|
372
+ hdr.ack
373
+ end
374
+ end
375
+ }.to_not raise_error(RuntimeError)
376
+ block_called.should be_true
377
+ end
378
+ end
379
+ end
380
+
381
+ describe "#fire" do
382
+ let(:default_wait){ 5 } # TODO この値を自動的に取得する?
383
+
384
+ subject { Tengine::Mq::Suite.new the_config }
385
+ def sender; Tengine::Event::Sender.new subject end
386
+ def expected_event; Tengine::Event.new(:event_type_name => :foo, :key => "uniq_key") end
387
+
388
+ context "without reactor" do
389
+ it "raises" do
390
+ block_called = false
391
+ expect {
392
+ ev = Tengine::Event.new :event_type_name => "foo"
393
+ subject.fire sender, ev, { :keep_connection => true }, nil
394
+ }.to raise_error(RuntimeError)
395
+ block_called.should be_false
396
+ end
397
+ end
398
+
399
+ context "with reactor" do
400
+ after do
401
+ # キューにイベントがたまるのでてきとうに吸い出す
402
+ if @port
403
+ EM.run_test do
404
+ subject.send :ensures, :connection do
405
+ i = 0
406
+ subject.subscribe do |hdr, bdy|
407
+ hdr.ack
408
+ i += 1
409
+ end
410
+ @timer = EM.add_periodic_timer(0.1) do
411
+ if i.zero?
412
+ EM.cancel_timer @timer
413
+ subject.unsubscribe do
414
+ subject.stop
415
+ end
416
+ else
417
+ i = 0
418
+ end
419
+ end
420
+ end
421
+ end
422
+ end
423
+ end
424
+
425
+ it "JSON形式にserializeしてexchangeにpublishする" do
426
+ Thread.current[:expected_event] = expected_event
427
+ EM.run_test do
428
+ subject.send :ensures, :exchange do |xchg|
429
+ def xchg.publish json, hash
430
+ json.should == Thread.current[:expected_event].to_json
431
+ super
432
+ end
433
+ subject.fire sender, Thread.current[:expected_event], {:keep_connection => false}, nil
434
+ end
435
+ end
436
+ end
437
+
438
+ it "Tengine::Eventオブジェクトを直接指定する" do
439
+ # 上と同じ…過去には意味があった
440
+ Thread.current[:expected_event] = expected_event
441
+ EM.run_test do
442
+ subject.send :ensures, :exchange do |xchg|
443
+ def xchg.publish json, hash
444
+ json.should == Thread.current[:expected_event].to_json
445
+ super
446
+ end
447
+ subject.fire sender, Thread.current[:expected_event], {:keep_connection => false}, nil
448
+ end
449
+ end
450
+ end
451
+
452
+ context "publish後に特定の処理を行う" do
453
+ it "カスタム処理" do
454
+ block_called = false
455
+ EM.run_test do
456
+ sender.fire "foo" do
457
+ block_called = true
458
+ end
459
+ end
460
+ block_called.should be_true
461
+ end
462
+ end
463
+
464
+ context "keep_connection: true" do
465
+ it "do not stop the reactor" do
466
+ block_called = false
467
+ EM.run_test do
468
+ sender.fire "foo", :keep_connection => true
469
+ EM.add_timer(0.5) do
470
+ block_called = true
471
+ subject.stop
472
+ end
473
+ end
474
+ block_called.should be_true
475
+ end
476
+ end
477
+
478
+ context "keep_connection: false" do
479
+ it "stop the reactor" do
480
+ block_called = false
481
+ EM.run_test do
482
+ sender.fire "foo", :keep_connection => false
483
+ sleep 0.1
484
+ EM.add_timer(default_wait) do
485
+ block_called = true
486
+ subject.stop
487
+ end
488
+ end
489
+ block_called.should be_false
490
+ end
491
+ end
492
+
493
+ context "AMQP::TCPConnectionFailed 以外のエラー" do
494
+ it "メッセージ送信ができなくてpublishに渡したブロックが呼び出されず、インターバルが過ぎて、EM.add_timeに渡したブロックが呼び出された場合" do
495
+ block_called = false
496
+ EM.run_test do
497
+ subject.send :ensures, :exchange do |xchg|
498
+ xchg.stub(:publish).with(expected_event.to_json, :content_type => "application/json", :persistent => true).exactly(31).times.and_raise(StandardError)
499
+ subject.fire sender, expected_event, {:keep_connection => false, :retry_interval => 0}, lambda { block_called = true }
500
+ subject.stop
501
+ end
502
+ end
503
+ block_called.should_not be_true
504
+ end
505
+
506
+ it "エラーが発生しても設定のリトライが行われる" do
507
+ EM.run_test do
508
+ subject.send :ensures, :exchange do |xchg|
509
+ # 正規のfireとリトライのfireなので、リトライ回数+1
510
+ xchg.stub(:publish).with(expected_event.to_json, :content_type => "application/json", :persistent => true).exactly(31).times.and_raise(StandardError)
511
+ subject.fire sender, expected_event, {:keep_connection => false, :retry_interval => 0}, nil
512
+ subject.stop
513
+ end
514
+ end
515
+ end
516
+
517
+ it "エラーが発生してもオプションで指定したリトライ回数分のリトライが行われる" do
518
+ EM.run_test do
519
+ subject.send :ensures, :exchange do |xchg|
520
+ # 正規のfireとリトライのfireなので、リトライ回数+1
521
+ xchg.stub(:publish).with(expected_event.to_json, :content_type => "application/json", :persistent => true).exactly(2).times.and_raise(StandardError)
522
+ subject.fire sender, expected_event, {:keep_connection => false, :retry_interval => 0, :retry_count => 1}, nil
523
+ subject.stop
524
+ end
525
+ end
526
+ end
527
+
528
+ it "エラーが発生してもオプションで指定したリトライ間隔でリトライが行われる" do
529
+ t0 = Time.now
530
+ timeout(60) do # 60秒
531
+ EM.run_test do
532
+ subject.send :ensures, :exchange do |xchg|
533
+ # 正規のfireとリトライのfireなので、リトライ回数+1
534
+ x = sender
535
+ xchg.stub(:publish).with(expected_event.to_json, :content_type => "application/json", :persistent => true).exactly(3).times.and_raise(StandardError)
536
+ subject.fire x, expected_event, {:keep_connection => false, :retry_interval => 1, :retry_count => 2}, nil
537
+ subject.stop
538
+ end
539
+ end
540
+ end
541
+ t1 = Time.now
542
+ (t1 - t0).tap do |d|
543
+ d.should >= 1.0
544
+ d.should <= 1.0 + default_wait
545
+ end
546
+ end
547
+
548
+ it "ちょうどretry_count回めのリトライして成功の場合は例外にならない" do
549
+ block_called = false
550
+ EM.run_test do
551
+ subject.send :ensures, :exchange do |xchg|
552
+ # 正規のfireとリトライのfireなので、リトライ回数+1
553
+ Thread.current[:expected_event] = expected_event
554
+ Thread.current[:x] = false
555
+ def xchg.publish str, hash
556
+ if Thread.current[:x] = !Thread.current[:x]
557
+ raise "foo"
558
+ else
559
+ super
560
+ end
561
+ end
562
+ subject.fire sender, expected_event, {:keep_connection => false, :retry_interval => 0, :retry_count => 2}, lambda { block_called = true }
563
+ subject.stop
564
+ end
565
+ end
566
+ block_called.should be_true
567
+ end
568
+ end
569
+ end
570
+
571
+ context "複数のEM event loopにまたがったfire" do
572
+ it "https://www.pivotaltracker.com/story/show/21252625" do
573
+ EM.run_test do sender.fire("foo") end
574
+ EM.run_test do sender.fire("foo") end
575
+ # ここまでくればOK
576
+ end
577
+ end
578
+
579
+ context "入り乱れたfireにおけるretryの回数" do
580
+ it "https://www.pivotaltracker.com/story/show/20236589" do
581
+ Thread.current[:n1] = 0
582
+ Thread.current[:n2] = 0
583
+ Thread.current[:ev1] = ev1 = Tengine::Event.new(:event_type_name => :foo, :key => "uniq_key")
584
+ Thread.current[:ev2] = ev2 = Tengine::Event.new(:event_type_name => :foo, :key => "another_uniq_key")
585
+ EM.run_test do
586
+ subject.send :ensures, :exchange do |xchg|
587
+ def xchg.publish json, hash
588
+ case json
589
+ when Thread.current[:ev1].to_json
590
+ Thread.current[:n1] += 1
591
+ raise "ev1"
592
+ when Thread.current[:ev2].to_json
593
+ Thread.current[:n2] += 1
594
+ raise "ev2"
595
+ end
596
+ end
597
+
598
+ subject.fire sender, ev1, {:keep_connection => true, :retry_interval => 0}, nil
599
+ subject.fire sender, ev2, {:keep_connection => true, :retry_interval => 0}, nil
600
+ subject.stop
601
+ end
602
+ end
603
+ n1 = Thread.current[:n1]
604
+ n2 = Thread.current[:n2]
605
+ if n1 == 31
606
+ n2.should <= 31
607
+ n2.should >= 2
608
+ elsif n2 == 31
609
+ n1.should <= 31
610
+ n1.should >= 2
611
+ else
612
+ raise "neither n1(#{n1}) nor n2(#{n2})"
613
+ end
614
+ end
615
+
616
+ it "無限にメモリを消費しない" do
617
+ n = 256 # 1024 # 4096
618
+ EM.run_test do
619
+ subject.send :ensures, :exchange do |xchg|
620
+ xchg.stub(:publish).with(an_instance_of(String), :content_type => "application/json", :persistent => true).exactly(31).times.and_raise(StandardError)
621
+ n.times do
622
+ EM.next_tick do
623
+ ev = Tengine::Event.new(:event_type_name => :foo, :key => "uniq_key")
624
+ subject.fire sender, ev, {:keep_connection => true, :retry_cont => 3, :retry_interval => 0}, nil
625
+ end
626
+ end
627
+ EM.next_tick do
628
+ subject.stop
629
+ end
630
+ end
631
+ end
632
+ GC.start
633
+ subject.pending_events{true}.size.should < n
634
+ end
635
+ end
636
+ end
637
+
638
+ describe "#stop" do
639
+ subject { Tengine::Mq::Suite.new the_config }
640
+ let(:sender) { Tengine::Event::Sender.new subject }
641
+ let(:expected_event) { Tengine::Event.new(:event_type_name => :foo, :key => "uniq_key") }
642
+
643
+ after do
644
+ # キューにイベントがたまるのでてきとうに吸い出す
645
+ if @port
646
+ EM.run_test do
647
+ subject.send :ensures, :connection do
648
+ i = 0
649
+ subject.subscribe do |hdr, bdy|
650
+ hdr.ack
651
+ i += 1
652
+ end
653
+ EM.add_periodic_timer(0.1) do
654
+ subject.stop if i.zero?
655
+ i = 0
656
+ end
657
+ end
658
+ end
659
+ end
660
+ end
661
+
662
+ it "EMのイベントループを抜ける" do
663
+ EM.run_test do
664
+ subject.stop
665
+ end
666
+ # ここに到達すればOK
667
+ end
668
+
669
+ it "ペンディングのイベントが送信されるまではEMのイベントループにとどまる" do
670
+ block_called = false
671
+ EM.run_test do
672
+ subject.send :ensures, :exchange do |xchg|
673
+ Thread.current[:expected_event] = expected_event
674
+ Thread.current[:x] = false
675
+ def xchg.publish str, hash
676
+ if Thread.current[:x] = !Thread.current[:x]
677
+ raise "foo"
678
+ else
679
+ super
680
+ end
681
+ end
682
+ subject.fire sender, expected_event, {:keep_connection => false, :retry_interval => 0, :retry_count => 2}, lambda { block_called = true }
683
+ subject.stop
684
+ end
685
+ end
686
+ block_called.should be_true
687
+ end
688
+ end
689
+
690
+ describe "#initiate_termination" do
691
+ subject { Tengine::Mq::Suite.new the_config }
692
+ after do
693
+ # キューにイベントがたまるのでてきとうに吸い出す
694
+ if @port
695
+ EM.run_test do
696
+ i = 0
697
+ j = false
698
+ subject.subscribe :confirm=>proc{j = true} do |hdr, bdy|
699
+ hdr.ack
700
+ i += 1
701
+ end
702
+ EM.add_periodic_timer(0.1) do
703
+ if j
704
+ subject.stop if i.zero?
705
+ i = 0
706
+ end
707
+ end
708
+ end
709
+ end
710
+ end
711
+
712
+ it "再接続しない" do
713
+ block_called = false
714
+ EM.run_test do
715
+ subject.add_hook("connection.after_recovery") { block_called = true }
716
+ subject.initiate_termination do
717
+ EM.defer(proc { relaunch },
718
+ proc { subject.stop })
719
+ end
720
+ end
721
+ block_called.should_not be_true
722
+ end
723
+ end
724
+
725
+ describe "#callback_entity" do
726
+ subject { Tengine::Mq::Suite.new the_config }
727
+ it "registers callbacks" do
728
+ EM.run_test do
729
+ subject.send :ensures, :queue do
730
+ { :queue => [
731
+ :before_recovery,
732
+ :after_recovery,
733
+ :after_connection_interruption,
734
+ ],
735
+ :exchange => [
736
+ :before_recovery,
737
+ :after_recovery,
738
+ :after_connection_interruption,
739
+ ],
740
+ :channel => [
741
+ :before_recovery,
742
+ :after_recovery,
743
+ :after_connection_interruption,
744
+ :error,
745
+ ],
746
+ :connection => [
747
+ :before_recovery,
748
+ :after_recovery,
749
+ :after_connection_interruption,
750
+ :error,
751
+ # :on_closed,
752
+ # :on_possible_authentication_failure,
753
+ # :on_tcp_connection_failure,
754
+ # :on_tcp_connection_loss,
755
+ ]
756
+ }.each_pair do |klass, mids|
757
+ obj = subject.send klass
758
+ mids.each do |mid|
759
+ case obj when AMQ::Client::Callbacks
760
+ obj.should be_has_callback(mid) # 英語おかしいがしょうがない
761
+ else
762
+ # mockかも
763
+ end
764
+ end
765
+ end
766
+ subject.stop
767
+ end
768
+ end
769
+ end
770
+ end
771
+ end
772
+
773
+ context "実際にMQに接続する試験", skip_travis: true, test_rabbitmq_required: true do
774
+ next if RUBY_VERSION < "1.9.2"
775
+
776
+ def launch
777
+ TestRabbitmq.kill_remain_processes
778
+ @test_rabbitmq = TestRabbitmq.new(keep_port: true).launch
779
+ end
780
+
781
+ def relaunch
782
+ TestRabbitmq.kill_launched_processes
783
+ @test_rabbitmq.keep_port = true
784
+ @test_rabbitmq.launch
785
+ end
786
+
787
+ def finish
788
+ TestRabbitmq.kill_launched_processes
789
+ end
790
+
791
+ before(:all) do
792
+ launch
793
+ end
794
+
795
+ after(:all) do
796
+ finish
797
+ end
798
+
799
+ let(:the_config) {
800
+ {
801
+ :connection => {
802
+ :port => @test_rabbitmq.port,
803
+ },
804
+ }
805
+ }
806
+ it_should_behave_like "Tengine::Mq::Suite"
807
+ end
808
+
809
+ context "mock/stubによる試験" do
810
+ def launch
811
+ end
812
+ def relaunch
813
+ end
814
+ def finish
815
+ end
816
+
817
+ class Mocker
818
+ class << self
819
+ alias_method :[], :new
820
+ end
821
+ def initialize inspect
822
+ @inspect = inspect
823
+ end
824
+ attr_reader :inspect
825
+ end
826
+
827
+ # for exchange
828
+ class RSpec::Mocks::Mock
829
+ def publish str, opt
830
+ $the_messages.push str
831
+ end
832
+ end
833
+
834
+ class RSpec::Mocks::MessageExpectation
835
+ def and_emyield *val
836
+ index = Object.new
837
+ @emvals ||= Hash.new
838
+ @emvals[index] = val
839
+ def self.invoke_with_yield(&block)
840
+ if block.nil?
841
+ @error_generator.raise_missing_block_error @args_to_yield
842
+ end
843
+ value = nil
844
+ @args_to_yield.each do |args_to_yield_this_time|
845
+ if Array === args_to_yield_this_time[0] and @emvals.key?(args_to_yield_this_time[0][0])
846
+ value = EM.next_tick do
847
+ block.yield(*args_to_yield_this_time[0][1])
848
+ end
849
+ else
850
+ if block.arity > -1 && args_to_yield_this_time.length != block.arity
851
+ @error_generator.raise_wrong_arity_error args_to_yield_this_time, block.arity
852
+ end
853
+ value = eval_block(*args_to_yield_this_time, &block)
854
+ end
855
+ end
856
+ value
857
+ end
858
+
859
+ case RSpec::Version::STRING
860
+ when "2.9.0", "2.10.0"
861
+ and_yield(*val)
862
+ else
863
+ and_yield([index, val])
864
+ end
865
+ end
866
+ end
867
+
868
+ before do
869
+ $the_messages = EM::Queue.new
870
+ @the_connection = mock(Mocker["connection"])
871
+ @the_channel_id = Numeric.new
872
+ @the_channel = mock(Mocker["channel"])
873
+ @the_exchange = mock(Mocker["exchange"])
874
+ @the_queue = mock(Mocker["queue"])
875
+ @callbacks = Hash.new do |h, k|
876
+ h[k] = Hash.new
877
+ end
878
+ AMQP.stub(:connect).with(an_instance_of(Hash)) do |h, &block|
879
+ EM.next_tick do
880
+ block.yield @the_connection
881
+ if h[:port] != 5672
882
+ @callbacks[@the_connection][:on_tcp_connection_failure].yield @the_connection if @callbacks[@the_connection][:on_tcp_connection_failure]
883
+ raise AMQP::Error, "fake error."
884
+ end
885
+ end
886
+ end
887
+ AMQP::Channel.stub(:next_channel_id).and_return(@the_channel_id)
888
+ AMQP::Channel.stub(:new).with(@the_connection, @the_channel_id, an_instance_of(Hash)).and_emyield(@the_channel).and_return(@the_channel)
889
+ AMQP::Exchange.stub(:new).with(@the_channel, an_instance_of(Symbol), an_instance_of(String), an_instance_of(Hash)).and_emyield(@the_exchange).and_return(@the_exchange)
890
+ @the_connection.stub(:connected?).and_return(true)
891
+ @the_connection.stub(:disconnect) do |&block|
892
+ EM.next_tick do
893
+ @callbacks[@the_connection][:on_closed].yield @the_connection if @callbacks[@the_connection][:on_closed]
894
+ block.yield
895
+ $the_messages = EM::Queue.new # reset
896
+ end
897
+ end
898
+ @the_connection.stub(:server_capabilities).and_return(Hash.new)
899
+ @the_connection.stub(:before_recovery)
900
+ @the_connection.stub(:after_recovery)
901
+ @the_connection.stub(:on_connection_interruption)
902
+ @the_connection.stub(:on_closed) do |&block| @callbacks[@the_connection][:on_closed] = block end
903
+ @the_connection.stub(:on_possible_authentication_failure)
904
+ @the_connection.stub(:on_tcp_connection_failure) do |&block| @callbacks[@the_connection][:on_tcp_connection_failure] = block end
905
+ @the_connection.stub(:on_tcp_connection_loss)
906
+ @the_channel.stub(:queue).with(an_instance_of(String), an_instance_of(Hash)).and_emyield(@the_queue).and_return(@the_queue)
907
+ @the_channel.stub(:close).and_emyield
908
+ @the_channel.stub(:before_recovery)
909
+ @the_channel.stub(:after_recovery)
910
+ @the_channel.stub(:on_connection_interruption)
911
+ @the_channel.stub(:on_error) do |&block| @callbacks[@the_channel][:on_error] = block end
912
+ @the_channel.stub(:exec_callback_once_yielding_self).with(:error, "channel close reason object") do
913
+ EM.next_tick do
914
+ @callbacks[@the_channel][:on_error].yield @the_channel if @callbacks[@the_channel][:on_error]
915
+ end
916
+ end
917
+ @the_exchange.stub(:publish).with(an_instance_of(String), an_instance_of(Hash)) do |str, opt|
918
+ $the_messages.push str
919
+ end
920
+ @the_exchange.stub(:before_recovery)
921
+ @the_exchange.stub(:after_recovery)
922
+ @the_exchange.stub(:on_connection_interruption)
923
+ @the_queue.stub(:bind).with(@the_exchange, an_instance_of(Hash)).and_emyield
924
+ @the_queue.stub(:subscribe).with(an_instance_of(Hash)) do |h, &block|
925
+ h[:confirm].call mock(Mocker["confirm-ok"]) if h[:confirm] and not h[:nowait]
926
+ cb = lambda do |ev|
927
+ header = AMQP::Header.new @the_channel, nil, Hash.new
928
+ header.stub(:ack)
929
+ block.yield header, ev
930
+ $the_messages.pop(&cb)
931
+ end
932
+ EM.next_tick do
933
+ $the_messages.pop(&cb)
934
+ end
935
+ end
936
+ @the_queue.stub(:before_recovery)
937
+ @the_queue.stub(:after_recovery)
938
+ @the_queue.stub(:on_connection_interruption)
939
+ @the_queue.stub(:default_consumer)
940
+ end
941
+
942
+ let(:the_config) { Hash.new }
943
+ it_should_behave_like "Tengine::Mq::Suite"
944
+ end
945
+ end