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.
- checksums.yaml +15 -0
- data/Gemfile.lock +48 -30
- data/lib/tengine/event/sender.rb +3 -3
- data/lib/tengine/mq/suite.rb +113 -68
- data/lib/tengine/mq/suite/default_config.rb +52 -0
- data/spec/actual_publisher1.rb +54 -0
- data/spec/actual_publisher2.rb +142 -0
- data/spec/actual_spec.rb +103 -0
- data/spec/amqp_spec.rb +144 -0
- data/spec/eventmachine_spec.rb +68 -0
- data/spec/mq_config.yml.example +13 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/support/test_em.rb +25 -0
- data/spec/support/test_rabbitmq.rb +184 -0
- data/spec/tengine/event/model_notifiable_spec.rb +80 -0
- data/spec/tengine/event/sender_spec.rb +124 -0
- data/spec/tengine/event_spec.rb +419 -0
- data/spec/tengine/mq/connect_actually_spec.rb +38 -0
- data/spec/tengine/mq/suite_spec.rb +945 -0
- data/spec/tengine_spec.rb +17 -0
- metadata +69 -54
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'tengine/mq/suite'
|
2
|
+
require 'tengine/support/core_ext/enumerable/deep_freeze'
|
3
|
+
|
4
|
+
class Tengine::Mq::Suite
|
5
|
+
DEFAULT_CONFIG = {
|
6
|
+
:sender => {
|
7
|
+
:keep_connection => false,
|
8
|
+
:retry_interval => 1, # in seconds
|
9
|
+
:retry_count => 30,
|
10
|
+
},
|
11
|
+
:connection => {
|
12
|
+
:user => 'guest',
|
13
|
+
:pass => 'guest',
|
14
|
+
:vhost => '/',
|
15
|
+
:logging => false,
|
16
|
+
:insist => false,
|
17
|
+
:host => 'localhost',
|
18
|
+
:port => 5672,
|
19
|
+
:auto_reconnect_delay => 1, # in seconds
|
20
|
+
},
|
21
|
+
:channel => {
|
22
|
+
:prefetch => 1,
|
23
|
+
:auto_recovery => true,
|
24
|
+
},
|
25
|
+
:exchange => {
|
26
|
+
:name => 'tengine_event_exchange',
|
27
|
+
:type => :direct,
|
28
|
+
:passive => false,
|
29
|
+
:durable => true,
|
30
|
+
:auto_delete => false,
|
31
|
+
:internal => false,
|
32
|
+
:nowait => false,
|
33
|
+
:publish => {
|
34
|
+
:content_type => "application/json", # RFC4627
|
35
|
+
:persistent => true,
|
36
|
+
},
|
37
|
+
},
|
38
|
+
:queue => {
|
39
|
+
:name => 'tengine_event_queue',
|
40
|
+
:passive => false,
|
41
|
+
:durable => true,
|
42
|
+
:auto_delete => false,
|
43
|
+
:exclusive => false,
|
44
|
+
:nowait => false,
|
45
|
+
:subscribe => {
|
46
|
+
:ack => true,
|
47
|
+
:nowait => false,
|
48
|
+
:confirm => nil,
|
49
|
+
},
|
50
|
+
},
|
51
|
+
}.deep_freeze
|
52
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
require 'logger'
|
5
|
+
require'eventmachine'
|
6
|
+
|
7
|
+
__DIR__ = File.dirname(__FILE__)
|
8
|
+
base_dir = File.expand_path('../', __DIR__)
|
9
|
+
$LOAD_PATH << File.expand_path('lib', base_dir)
|
10
|
+
require 'tengine/event'
|
11
|
+
|
12
|
+
logger = Logger.new(File.expand_path("tmp/log/actual_publisher1.log", base_dir))
|
13
|
+
Tengine.logger = logger
|
14
|
+
|
15
|
+
sender = Tengine::Event::Sender.new(logger: logger, sender: {keep_connection: true} )
|
16
|
+
|
17
|
+
nest = ARGV.any?{|arg| arg =~ /^nest/ }
|
18
|
+
|
19
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
20
|
+
EM.run do
|
21
|
+
|
22
|
+
if nest
|
23
|
+
|
24
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
25
|
+
sender.fire("foo") do
|
26
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
27
|
+
sender.fire("bar") do
|
28
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
29
|
+
sender.fire("baz") do
|
30
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
31
|
+
sender.stop
|
32
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
33
|
+
end
|
34
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
35
|
+
end
|
36
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
37
|
+
end
|
38
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
39
|
+
|
40
|
+
else
|
41
|
+
|
42
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
43
|
+
sender.fire("foo")
|
44
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
45
|
+
sender.fire("bar")
|
46
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
47
|
+
sender.fire("baz")
|
48
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
49
|
+
sender.stop
|
50
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
@@ -0,0 +1,142 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
require 'logger'
|
5
|
+
require'eventmachine'
|
6
|
+
require 'amqp'
|
7
|
+
require'json'
|
8
|
+
|
9
|
+
__DIR__ = File.dirname(__FILE__)
|
10
|
+
base_dir = File.expand_path('..', __DIR__)
|
11
|
+
|
12
|
+
logger = Logger.new(File.expand_path("tmp/log/actual_publisher2.log", base_dir))
|
13
|
+
|
14
|
+
|
15
|
+
nest = ARGV.any?{|arg| arg =~ /^nest/ }
|
16
|
+
|
17
|
+
|
18
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
19
|
+
EM.run do
|
20
|
+
|
21
|
+
config = {
|
22
|
+
:sender => {
|
23
|
+
:keep_connection => false,
|
24
|
+
:retry_interval => 1, # in seconds
|
25
|
+
:retry_count => 30,
|
26
|
+
},
|
27
|
+
:connection => {
|
28
|
+
:user => 'guest',
|
29
|
+
:pass => 'guest',
|
30
|
+
:vhost => '/',
|
31
|
+
:logging => false,
|
32
|
+
:insist => false,
|
33
|
+
:host => 'localhost',
|
34
|
+
:port => 5672,
|
35
|
+
:auto_reconnect_delay => 1, # in seconds
|
36
|
+
},
|
37
|
+
:channel => {
|
38
|
+
:prefetch => 1,
|
39
|
+
:auto_recovery => true,
|
40
|
+
},
|
41
|
+
:exchange => {
|
42
|
+
:name => 'tengine_event_exchange',
|
43
|
+
:type => :direct,
|
44
|
+
:passive => false,
|
45
|
+
:durable => true,
|
46
|
+
:auto_delete => false,
|
47
|
+
:internal => false,
|
48
|
+
:nowait => false,
|
49
|
+
:publish => {
|
50
|
+
:content_type => "application/json", # RFC4627
|
51
|
+
:persistent => true,
|
52
|
+
},
|
53
|
+
},
|
54
|
+
:queue => {
|
55
|
+
:name => 'tengine_event_queue',
|
56
|
+
:passive => false,
|
57
|
+
:durable => true,
|
58
|
+
:auto_delete => false,
|
59
|
+
:exclusive => false,
|
60
|
+
:nowait => false,
|
61
|
+
:subscribe => {
|
62
|
+
:ack => true,
|
63
|
+
:nowait => false,
|
64
|
+
:confirm => nil,
|
65
|
+
},
|
66
|
+
},
|
67
|
+
}
|
68
|
+
|
69
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
70
|
+
AMQP.connect(config[:connection]) do |conn|
|
71
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
72
|
+
id = AMQP::Channel.next_channel_id
|
73
|
+
AMQP::Channel.new(conn, id, config[:channel]) do |ch|
|
74
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
75
|
+
cfg = config[:exchange].dup
|
76
|
+
name = cfg.delete :name
|
77
|
+
type = cfg.delete :type
|
78
|
+
cfg.delete :publish # not needed here
|
79
|
+
AMQP::Exchange.new ch, type.intern, name, cfg do |xchg|
|
80
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
81
|
+
|
82
|
+
if nest
|
83
|
+
xchg.publish({"event_type_name" => "foo"}.to_json) do
|
84
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
85
|
+
xchg.publish({"event_type_name" => "bar"}.to_json) do
|
86
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
87
|
+
xchg.publish({"event_type_name" => "baz"}.to_json) do
|
88
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
89
|
+
conn.close{
|
90
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
91
|
+
EM.stop{
|
92
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
93
|
+
exit
|
94
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
95
|
+
}
|
96
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
97
|
+
}
|
98
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
99
|
+
end
|
100
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
101
|
+
end
|
102
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
103
|
+
end
|
104
|
+
else
|
105
|
+
xchg.publish({"event_type_name" => "foo"}.to_json)
|
106
|
+
# xchg.publish({"event_type_name" => "foo"}.to_json) do
|
107
|
+
# logger.debug("#{__FILE__}##{__LINE__}")
|
108
|
+
# end
|
109
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
110
|
+
|
111
|
+
# sleep 0.1
|
112
|
+
|
113
|
+
xchg.publish({"event_type_name" => "bar"}.to_json)
|
114
|
+
|
115
|
+
xchg.publish({"event_type_name" => "baz"}.to_json) do
|
116
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
117
|
+
conn.close{
|
118
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
119
|
+
EM.stop{
|
120
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
121
|
+
exit
|
122
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
123
|
+
}
|
124
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
125
|
+
}
|
126
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
127
|
+
end
|
128
|
+
# xchg.publish({"event_type_name" => "bar"}.to_json) do
|
129
|
+
# logger.debug("#{__FILE__}##{__LINE__}")
|
130
|
+
# end
|
131
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
end
|
136
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
137
|
+
end
|
138
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
139
|
+
end
|
140
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
141
|
+
end
|
142
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
data/spec/actual_spec.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
require 'logger'
|
5
|
+
require'eventmachine'
|
6
|
+
|
7
|
+
describe "tengine_event" do
|
8
|
+
|
9
|
+
before(:all) do
|
10
|
+
TestRabbitmq.kill_remain_processes
|
11
|
+
@test_rabbitmq = TestRabbitmq.new(keep_port: true).launch
|
12
|
+
end
|
13
|
+
|
14
|
+
after(:all) do
|
15
|
+
TestRabbitmq.kill_launched_processes
|
16
|
+
end
|
17
|
+
|
18
|
+
before do
|
19
|
+
system(File.expand_path("../../bin/tengine_event_sucks", __FILE__))
|
20
|
+
end
|
21
|
+
|
22
|
+
let(:logger){ Logger.new(File.expand_path("../../tmp/log/actual_spec.log", __FILE__)) }
|
23
|
+
|
24
|
+
before{ logger.info("-" * 100) }
|
25
|
+
after(:all){ logger.info("=" * 100) }
|
26
|
+
|
27
|
+
shared_examples_for "publisher and subscriber are in same process" do |block|
|
28
|
+
let(:buffer){ [] }
|
29
|
+
let(:suite ){ Tengine::Mq::Suite.new }
|
30
|
+
let(:sender ){ Tengine::Event::Sender.new(logger: logger, sender: { keep_connection: true}) }
|
31
|
+
|
32
|
+
before do
|
33
|
+
logger.info("=" * 100)
|
34
|
+
Tengine.logger = logger
|
35
|
+
EM.run_test(timeout: 60) do # 1分はかからないと思うんだけど・・・
|
36
|
+
suite.subscribe do |header, payload|
|
37
|
+
header.ack
|
38
|
+
hash = JSON.parse(payload)
|
39
|
+
buffer << hash["event_type_name"]
|
40
|
+
end
|
41
|
+
block.call(sender)
|
42
|
+
EM.add_timer(10){ suite.stop }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it "receives foo, bar and baz" do
|
47
|
+
buffer.should =~ %w[foo bar baz]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "sequential call" do
|
52
|
+
it_should_behave_like "publisher and subscriber are in same process", ->(sender){
|
53
|
+
sender.fire("foo")
|
54
|
+
sender.fire("bar")
|
55
|
+
sender.fire("baz")
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
context "nested call" do
|
60
|
+
it_should_behave_like "publisher and subscriber are in same process", ->(sender){
|
61
|
+
sender.fire("foo") do
|
62
|
+
sender.fire("bar") do
|
63
|
+
sender.fire("baz")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
# 2つのイベント発火の場合には失敗したり成功したりが混じっていましたが、
|
70
|
+
# 3つのイベント発火の場合には100%失敗するので繰り返しは1回だけでOKです。
|
71
|
+
repeat = (ENV['REPEAT'] || 1).to_i
|
72
|
+
repeat.times do |idx|
|
73
|
+
context "#{idx + 1}/#{repeat} publisher is in another process" do
|
74
|
+
let(:timeout){ 10 }
|
75
|
+
let(:buffer){ [] }
|
76
|
+
let(:suite ){ Tengine::Mq::Suite.new }
|
77
|
+
|
78
|
+
before do
|
79
|
+
Tengine.logger = logger
|
80
|
+
EM.run_test do
|
81
|
+
# EM.next_tick do
|
82
|
+
# puts "now waiting 2 events in #{timeout} seconds."
|
83
|
+
# end
|
84
|
+
EM.add_timer(timeout) { suite.stop } # timeoutを設定
|
85
|
+
|
86
|
+
suite.subscribe do |header, payload|
|
87
|
+
header.ack
|
88
|
+
hash = JSON.parse(payload)
|
89
|
+
# puts hash.inspect
|
90
|
+
buffer << hash["event_type_name"]
|
91
|
+
suite.stop if buffer.length >= 3
|
92
|
+
end
|
93
|
+
cmd = File.expand_path("../actual_publisher1.rb", __FILE__)
|
94
|
+
system(cmd)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
it "receives foo, bar and baz", skip_travis: true do
|
99
|
+
buffer.should =~ %w[foo bar baz]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/spec/amqp_spec.rb
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
require 'logger'
|
5
|
+
require 'eventmachine'
|
6
|
+
require 'amqp'
|
7
|
+
require'json'
|
8
|
+
|
9
|
+
describe "amqp" do
|
10
|
+
|
11
|
+
before(:all) do
|
12
|
+
TestRabbitmq.kill_remain_processes
|
13
|
+
@test_rabbitmq = TestRabbitmq.new(keep_port: true).launch
|
14
|
+
end
|
15
|
+
after(:all) do
|
16
|
+
TestRabbitmq.kill_launched_processes
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:logger){ Logger.new(File.expand_path("../../tmp/log/amqp_spec.log", __FILE__)) }
|
20
|
+
|
21
|
+
before do
|
22
|
+
system(File.expand_path("../../bin/tengine_event_sucks", __FILE__))
|
23
|
+
end
|
24
|
+
|
25
|
+
before{ logger.debug("-" * 100) }
|
26
|
+
after(:all){ logger.debug("=" * 100) }
|
27
|
+
|
28
|
+
[
|
29
|
+
["actual_publisher1.rb sequential", {skip_travis: true}],
|
30
|
+
["actual_publisher1.rb nested" , {skip_travis: true}],
|
31
|
+
["actual_publisher2.rb sequential", {}],
|
32
|
+
["actual_publisher2.rb nested" , {}],
|
33
|
+
].each do |(publisher_command, opts)|
|
34
|
+
# 2つのイベント発火の場合には失敗したり成功したりが混じっていましたが、
|
35
|
+
# 3つのイベント発火の場合には100%失敗するので繰り返しは1回だけでOKです。
|
36
|
+
repeat = (ENV['REPEAT'] || 1).to_i
|
37
|
+
repeat.times do |idx|
|
38
|
+
|
39
|
+
context "#{idx + 1}/#{repeat} publisher is in another process with #{publisher_command}", opts do
|
40
|
+
let(:timeout){ 10 }
|
41
|
+
let(:buffer){ [] }
|
42
|
+
|
43
|
+
let(:config) do
|
44
|
+
{
|
45
|
+
:sender => {
|
46
|
+
:keep_connection => false,
|
47
|
+
:retry_interval => 1, # in seconds
|
48
|
+
:retry_count => 30,
|
49
|
+
},
|
50
|
+
:connection => {
|
51
|
+
:user => 'guest',
|
52
|
+
:pass => 'guest',
|
53
|
+
:vhost => '/',
|
54
|
+
:logging => false,
|
55
|
+
:insist => false,
|
56
|
+
:host => 'localhost',
|
57
|
+
:port => 5672,
|
58
|
+
:auto_reconnect_delay => 1, # in seconds
|
59
|
+
},
|
60
|
+
:channel => {
|
61
|
+
:prefetch => 1,
|
62
|
+
:auto_recovery => true,
|
63
|
+
},
|
64
|
+
:exchange => {
|
65
|
+
:name => 'tengine_event_exchange',
|
66
|
+
:type => :direct,
|
67
|
+
:passive => false,
|
68
|
+
:durable => true,
|
69
|
+
:auto_delete => false,
|
70
|
+
:internal => false,
|
71
|
+
:nowait => false,
|
72
|
+
:publish => {
|
73
|
+
:content_type => "application/json", # RFC4627
|
74
|
+
:persistent => true,
|
75
|
+
},
|
76
|
+
},
|
77
|
+
:queue => {
|
78
|
+
:name => 'tengine_event_queue',
|
79
|
+
:passive => false,
|
80
|
+
:durable => true,
|
81
|
+
:auto_delete => false,
|
82
|
+
:exclusive => false,
|
83
|
+
:nowait => false,
|
84
|
+
:subscribe => {
|
85
|
+
:ack => true,
|
86
|
+
:nowait => false,
|
87
|
+
:confirm => nil,
|
88
|
+
},
|
89
|
+
},
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
before do
|
94
|
+
EM.run_test(timeout: timeout * 3) do # timeoutの3倍あれば大丈夫でしょう
|
95
|
+
# EM.next_tick do
|
96
|
+
# puts "now waiting 2 events in #{timeout} seconds."
|
97
|
+
# end
|
98
|
+
EM.add_timer(timeout) { EM.stop } # timeoutを設定
|
99
|
+
|
100
|
+
AMQP.connect(config[:connection]) do |conn|
|
101
|
+
id = AMQP::Channel.next_channel_id
|
102
|
+
AMQP::Channel.new(conn, id, config[:channel]) do |ch|
|
103
|
+
|
104
|
+
cfg = config[:exchange].dup
|
105
|
+
name = cfg.delete :name
|
106
|
+
type = cfg.delete :type
|
107
|
+
cfg.delete :publish # not needed here
|
108
|
+
AMQP::Exchange.new ch, type.intern, name, cfg do |xchg|
|
109
|
+
|
110
|
+
cfg = config[:queue].dup
|
111
|
+
name = cfg.delete :name
|
112
|
+
ch.queue name, cfg do |que|
|
113
|
+
que.bind xchg, :nowait => false do
|
114
|
+
|
115
|
+
opts = config[:queue][:subscribe]
|
116
|
+
que.subscribe opts do |h, b|
|
117
|
+
h.ack
|
118
|
+
# puts b
|
119
|
+
hash = JSON.parse(b)
|
120
|
+
buffer << hash["event_type_name"]
|
121
|
+
conn.close{ EM.stop } if buffer.length >= 3
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
end
|
129
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
130
|
+
end
|
131
|
+
logger.debug("#{__FILE__}##{__LINE__}")
|
132
|
+
|
133
|
+
Process.spawn(File.expand_path("../#{publisher_command}", __FILE__))
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
it "receives foo, bar and baz" do
|
138
|
+
buffer.should == %w[foo bar baz]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|