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,68 @@
1
+ require 'spec_helper'
2
+
3
+ require 'timeout'
4
+ require 'eventmachine'
5
+
6
+ describe "Eventmachine" do
7
+
8
+ describe "#defer" do
9
+
10
+ it "2 cascaded defers" do
11
+ buffer = []
12
+ blocks = []
13
+ EM.run_test do
14
+ blocks[4] = lambda{|a| buffer << [4, a]; EM.stop }
15
+ blocks[3] = lambda{ buffer << 3; 3 }
16
+ blocks[2] = lambda{|a| buffer << [2, a]; EM.defer(blocks[3], blocks[4])}
17
+ blocks[1] = lambda{ buffer << 1; 1 }
18
+ blocks[0] = lambda{ buffer << 0; EM.defer(blocks[1], blocks[2])}
19
+ blocks[0].call
20
+ end
21
+ buffer.should == [0, 1, [2, 1], 3, [4, 3]]
22
+ end
23
+
24
+ it "4 cascaded defers" do
25
+ buffer = []
26
+ blocks = []
27
+ EM.run_test do
28
+ blocks[8] = lambda{|a| buffer << [8, a]; EM.stop }
29
+ blocks[7] = lambda{ buffer << 7; 7 }
30
+ blocks[6] = lambda{|a| buffer << [6, a]; EM.defer(blocks[7], blocks[8])}
31
+ blocks[5] = lambda{ buffer << 5; 5 }
32
+ blocks[4] = lambda{|a| buffer << [4, a]; EM.defer(blocks[5], blocks[6])}
33
+ blocks[3] = lambda{ buffer << 3; 3 }
34
+ blocks[2] = lambda{|a| buffer << [2, a]; EM.defer(blocks[3], blocks[4])}
35
+ blocks[1] = lambda{ buffer << 1; 1 }
36
+ blocks[0] = lambda{ buffer << 0; EM.defer(blocks[1], blocks[2])}
37
+ blocks[0].call
38
+ end
39
+ buffer.should == [0, 1, [2, 1], 3, [4, 3], 5, [6, 5], 7, [8, 7]]
40
+ end
41
+
42
+ it "15 cascaded defers" do
43
+ buffer = []
44
+ blocks = []
45
+ EM.threadpool_size.should == 20
46
+ EM.run_test do
47
+ count = 30
48
+ blocks[count ] = lambda{|a| buffer << [count, a]; EM.stop }
49
+ blocks[count - 1] = lambda{ buffer << (count - 1); count - 1 }
50
+ (count/ 2 - 1).times do |idx|
51
+ m = idx * 2 + 1
52
+ n = idx * 2 + 2
53
+ blocks[n] = lambda{|a| buffer << [n, a]; EM.defer(blocks[n + 1], blocks[n + 2])}
54
+ blocks[m] = lambda{ buffer << m; m }
55
+ end
56
+ blocks[0] = lambda{ buffer << 0; EM.defer(blocks[1], blocks[2])}
57
+ blocks[0].call
58
+ end
59
+ buffer.should == [
60
+ 0, 1, [2, 1], 3, [4, 3], 5, [6, 5], 7, [8, 7], 9, [10, 9],
61
+ 11, [12, 11], 13, [14, 13], 15, [16, 15], 17, [18, 17], 19, [20, 19],
62
+ 21, [22, 21], 23, [24, 23], 25, [26, 25], 27, [28, 27], 29, [30, 29]
63
+ ]
64
+ end
65
+
66
+ end
67
+
68
+ end
@@ -0,0 +1,13 @@
1
+ connection:
2
+ host: 'localhost'
3
+ port: 5672
4
+ # vhost:
5
+ # user:
6
+ # pass:
7
+ exchange:
8
+ name: 'tengine_event_test_exchange'
9
+ type: 'direct'
10
+ durable: true
11
+ queue:
12
+ name: 'tengine_event_test_queue'
13
+ durable: true
@@ -0,0 +1,27 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'tengine_event'
5
+ require 'timeout'
6
+
7
+ require 'simplecov'
8
+ SimpleCov.start if ENV["COVERAGE"]
9
+
10
+ # Requires supporting files with custom matchers and macros, etc,
11
+ # in ./support/ and its subdirectories.
12
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
13
+
14
+ RSpec.configure do |config|
15
+
16
+ unless ENV['MANUAL'] == 'true'
17
+ config.filter_run_excluding manual: true
18
+ end
19
+
20
+ if ENV['TRAVIS'] == 'true'
21
+ config.filter_run_excluding skip_travis: true
22
+ end
23
+
24
+ if ENV['TEST_RABBITMQ_DISABLED'] =~ /^true$|^on$/i
25
+ config.filter_run_excluding test_rabbitmq_required: true
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'timeout'
3
+
4
+ require 'eventmachine'
5
+
6
+ module EventMachine
7
+
8
+ DEFAULT_TIMEOUT = 10 # seconds
9
+
10
+ class << self
11
+
12
+ # call run with timeout
13
+ # @option options [Numeric] :timeout timeout for EventMachine running.
14
+ #
15
+ # other arguments and block are passed to run method.
16
+ def run_test(*args, &block)
17
+ options = args.last.is_a?(Hash) ? args.pop : {}
18
+ t = (options[:timeout] || ENV['DEFAULT_TIMEOUT'] || DEFAULT_TIMEOUT).to_i
19
+ timeout(t) do
20
+ return run(*args, &block)
21
+ end
22
+ end
23
+ end
24
+
25
+ end
@@ -0,0 +1,184 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'fileutils'
3
+ require 'tmpdir'
4
+
5
+ class TestRabbitmq
6
+ class << self
7
+ def launched_pids
8
+ @launched_pids ||= []
9
+ end
10
+
11
+ def kill_launched_processes
12
+ launched_pids.uniq!
13
+ puts "no pid found " if launched_pids.empty?
14
+ kill_processes(launched_pids.dup)
15
+ end
16
+
17
+ def kill_remain_processes
18
+ ps_lines = `ps aux`.split(/\n/).select{|line| line =~ /^.+?\s+\d+\s+.+\-sname rspec/}
19
+ pids = ps_lines.map do |line|
20
+ line.scan(/^.+?\s+(\d+)\s+.+\-sname rspec/).flatten.first.to_i
21
+ end
22
+ puts "Now rabbitmq-server processes remain: #{pids.inspect}"
23
+ kill_processes(pids.compact)
24
+ end
25
+
26
+ def kill_processes(pids)
27
+ pids.each do |pid|
28
+ begin
29
+ puts "kill INT #{pid}"
30
+ Process.kill "INT", pid
31
+ Process.waitpid pid
32
+ launched_pids.delete(pid)
33
+ rescue Errno::ECHILD => e
34
+ puts "Warning: failed to kill process #{pid}. ignore [#{e.class}] #{e.message}\n " << e.backtrace.join("\n ")
35
+ end
36
+ end
37
+ end
38
+
39
+ def run(*args)
40
+ unless system(*args)
41
+ fail("command failed:\n#{args.inspect}\n#{$?.inspect}")
42
+ end
43
+ end
44
+
45
+ def backups
46
+ @backups ||= []
47
+ end
48
+
49
+ # % rabbitmq-plugins list
50
+ # [e] amqp_client 2.8.7
51
+ # [ ] eldap 2.8.7-gite309de4
52
+ # [ ] erlando 2.8.7
53
+ # [e] mochiweb 2.3.1-rmq2.8.7-gitd541e9a
54
+ # [ ] rabbitmq_auth_backend_ldap 2.8.7
55
+ # [ ] rabbitmq_auth_mechanism_ssl 2.8.7
56
+ # [ ] rabbitmq_consistent_hash_exchange 2.8.7
57
+ # [ ] rabbitmq_federation 2.8.7
58
+ # [ ] rabbitmq_federation_management 2.8.7
59
+ # [ ] rabbitmq_jsonrpc 2.8.7
60
+ # [ ] rabbitmq_jsonrpc_channel 2.8.7
61
+ # [ ] rabbitmq_jsonrpc_channel_examples 2.8.7
62
+ # [E] rabbitmq_management 2.8.7
63
+ # [e] rabbitmq_management_agent 2.8.7
64
+ # [E] rabbitmq_management_visualiser 2.8.7
65
+ # [e] rabbitmq_mochiweb 2.8.7
66
+ # [ ] rabbitmq_shovel 2.8.7
67
+ # [ ] rabbitmq_shovel_management 2.8.7
68
+ # [ ] rabbitmq_stomp 2.8.7
69
+ # [ ] rabbitmq_tracing 2.8.7
70
+ # [ ] rfc4627_jsonrpc 2.8.7-gita5e7ad7
71
+ # [e] webmachine 1.9.1-rmq2.8.7-git52e62bc
72
+ #
73
+ # http://www.rabbitmq.com/man/rabbitmq-plugins.1.man.html
74
+
75
+ def backup_plugins
76
+ backups << `rabbitmq-plugins list`.scan(/^\[[E]\]\s*([^\s]+)\s*.+?\s*$/).flatten
77
+ end
78
+
79
+ def restore_plugins
80
+ enable_plugins(*backups.pop)
81
+ end
82
+
83
+ def enable_plugins(*plugins)
84
+ run("rabbitmq-plugins enable " << plugins.join(" "))
85
+ end
86
+ end
87
+
88
+
89
+ attr_reader :pid
90
+ attr_accessor :port, :keep_port
91
+ attr_accessor :base_dir, :nodename, :rabbitmq_server_path
92
+ attr_accessor :max_attempts, :max_wait_limit, :wait_interval
93
+ attr_accessor :disabled
94
+
95
+ DEFAULT_PORT = 5672
96
+
97
+ def initialize(options = {})
98
+ @pid = nil
99
+ @port = DEFAULT_PORT
100
+ # @base_dir ||= File.expand_path("../../../tmp/rabbitmq", __FILE__)
101
+ @base_dir ||= Dir.mktmpdir
102
+ FileUtils.mkdir_p(@base_dir)
103
+ @rabbitmq_server_path = find_rabbitmq_server
104
+ @keep_port = false
105
+ @max_attempts = 10
106
+ @max_wait_limit = 16
107
+ @wait_interval = 0.5
108
+ @disabled = (ENV['TRAVIS'] == 'true') || (ENV['TEST_RABBITMQ_DISABLED'].to_s =~ /^true$|^on$/i)
109
+ (options || {}).each{|key, value| send("#{key}=", value) }
110
+ end
111
+
112
+ def find_rabbitmq_server
113
+ ENV["PATH"].split(/:/).find do |dir|
114
+ Dir.glob("#{dir}/rabbitmq-server") do |path|
115
+ return path if File.executable?(path)
116
+ end
117
+ end
118
+ end
119
+
120
+ def launch
121
+ return self if disabled
122
+ raise "Something wrong! launched process is still living." unless self.class.launched_pids.empty?
123
+ ps_line = `ps aux`.split(/\n/).select{|line| line =~ /^#{ENV['USER']}\s+\d+\s+.+\-sname rspec/}
124
+ raise "process is still remain\n#{ps_line}" unless ps_line.empty?
125
+ raise "rabbitmq-server not found" unless @rabbitmq_server_path
126
+ return self if try_launch
127
+ raise "failed to invoke rabbitmq-server."
128
+ end
129
+
130
+ # see http://ja.wikipedia.org/wiki/ポート番号
131
+ UNRESERVED_PORT_MIN = 1024
132
+
133
+ def try_launch
134
+ # puts "#{__FILE__}##{__LINE__}\n " << caller.join("\n ")
135
+
136
+ unless @keep_port
137
+ @port = rand(32768 - UNRESERVED_PORT_MIN) + UNRESERVED_PORT_MIN
138
+ end
139
+
140
+ envp = {
141
+ "RABBITMQ_NODENAME" => "rspec",
142
+ "RABBITMQ_NODE_PORT" => @port.to_s,
143
+ "RABBITMQ_NODE_IP_ADDRESS" => "auto",
144
+ "RABBITMQ_MNESIA_BASE" => @base_dir.to_s,
145
+ "RABBITMQ_LOG_BASE" => @base_dir.to_s,
146
+ }
147
+ args = [envp, rabbitmq_server_path, {pgroup: true, chdir: @base_dir, in: :close}]
148
+ Process.spawn(*args)
149
+
150
+ x = Time.now
151
+ limit = x + max_wait_limit
152
+
153
+ while Time.now < limit do
154
+ sleep wait_interval
155
+ line = `ps aux`.split(/\n/).select{|line| line =~ /^#{ENV['USER']}\s+\d+\s+.+\-sname\ rspec.+tcp_listeners \[\{\"auto\",#{@port}\}\]/}.flatten.sort.first
156
+ next unless line
157
+ pid = line.scan(/^#{ENV['USER']}\s+(\d+)\s+/).flatten.first
158
+ next unless pid
159
+ @pid = pid.to_i
160
+ self.class.launched_pids << @pid
161
+
162
+ Process.waitpid2(@pid, Process::WNOHANG) # 起動されているはず
163
+ Process.kill 0, @pid # プロセスの存在確認。プロセスが起動してたらスルー、いなかったら Errno::ESRCH がraiseされる。
164
+ break
165
+ end
166
+ x = Time.now
167
+ limit = x + max_wait_limit
168
+ while Time.now < limit do
169
+ sleep wait_interval
170
+
171
+ # netstat -an は Linux / BSD ともに有効
172
+ # どちらかに限ればもう少し効率的な探し方はある。たとえば Linux 限定でよければ netstat -lnt ...
173
+ cmd = "netstat -an | fgrep LISTEN | fgrep #{@port}"
174
+ y = `#{cmd}`
175
+ return true if y.lines.to_a.size > 0
176
+ end
177
+
178
+ puts "launch timed out"
179
+ return false
180
+ rescue Errno::ECHILD, Errno::ESRCH => e
181
+ puts "[#{e.class}] #{e.message}\n " << e.backtrace.join("\n ")
182
+ false
183
+ end
184
+ end
@@ -0,0 +1,80 @@
1
+ # -*- coding: utf-8 -*-
2
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
3
+
4
+ describe "Tengine::Event::ModelNotifiable" do
5
+
6
+ module TengineTest
7
+ class MockModel
8
+ attr_accessor :attributes, :changes
9
+ def initialize(attributes)
10
+ @attributes = attributes
11
+ end
12
+ end
13
+ end
14
+
15
+ class TestModelNotifier
16
+ include Tengine::Event::ModelNotifiable
17
+
18
+ def initialize(sender)
19
+ @sender= sender
20
+ end
21
+
22
+ def event_sender
23
+ @sender
24
+ end
25
+ end
26
+
27
+ context "ActiveModelなどのObserverでそれぞれメソッドが呼び出されます" do
28
+ before do
29
+ @mock_sender = mock(:sender)
30
+ @notifier = TestModelNotifier.new(@mock_sender)
31
+ end
32
+
33
+ it "after_create" do
34
+ @mock_sender.should_receive(:fire).with("TengineTest::MockModel.created.TestModelNotifier", {
35
+ :level_key => :info,
36
+ :properties => {
37
+ :class_name => "TengineTest::MockModel",
38
+ :attributes => {
39
+ :name => "GariGariKun",
40
+ :price => 50
41
+ }
42
+ }
43
+ })
44
+ @notifier.after_create(TengineTest::MockModel.new(:name => "GariGariKun", :price => 50))
45
+ end
46
+
47
+ it "after_update" do
48
+ @mock_sender.should_receive(:fire).with("TengineTest::MockModel.updated.TestModelNotifier", {
49
+ :level_key => :info,
50
+ :properties => {
51
+ :class_name => "TengineTest::MockModel",
52
+ :attributes => {
53
+ :name => "GariGariKun",
54
+ :price => 60
55
+ },
56
+ :changes => {"price" => [50, 60]},
57
+ }
58
+ })
59
+ model = TengineTest::MockModel.new(:name => "GariGariKun", :price => 60)
60
+ model.changes = {"price" => [50, 60]}
61
+ @notifier.after_update(model)
62
+ end
63
+
64
+ it "after_destroy" do
65
+ @mock_sender.should_receive(:fire).with("TengineTest::MockModel.destroyed.TestModelNotifier", {
66
+ :level_key => :info,
67
+ :properties => {
68
+ :class_name => "TengineTest::MockModel",
69
+ :attributes => {
70
+ :name => "GariGariKun",
71
+ :price => 60
72
+ }
73
+ }
74
+ })
75
+ @notifier.after_destroy(TengineTest::MockModel.new(:name => "GariGariKun", :price => 60))
76
+ end
77
+
78
+ end
79
+
80
+ end
@@ -0,0 +1,124 @@
1
+ # -*- coding: utf-8 -*-
2
+ require File.expand_path('../../../spec_helper', __FILE__)
3
+
4
+ # fire の機能面(何を送信するか等)に関してはmq/suiteに記載済み
5
+ describe "Tengine::Event::Sender" do
6
+ describe "#initialize" do
7
+ context "no args" do
8
+ subject{ Tengine::Event::Sender.new.mq_suite }
9
+ it { should_not be_nil }
10
+ it { should be_kind_of Tengine::Mq::Suite }
11
+ end
12
+
13
+ context "with options" do
14
+ subject{ Tengine::Event::Sender.new(:exchange => {:name => "another_exhange"}).mq_suite.config[:exchange][:name] }
15
+ it { should == "another_exhange" }
16
+ end
17
+
18
+ context "with mq_suite" do
19
+ before{ @mq_suite = Tengine::Mq::Suite.new }
20
+ subject{ Tengine::Event::Sender.new(@mq_suite).mq_suite }
21
+ it { should == @mq_suite }
22
+ end
23
+
24
+ context "with mq_suite and options" do
25
+ before do
26
+ @mq_suite = Tengine::Mq::Suite.new
27
+ @logger = mock(:logger)
28
+ end
29
+ subject { Tengine::Event::Sender.new(@mq_suite, :logger => @logger) }
30
+ its (:mq_suite) { should == @mq_suite }
31
+ its (:logger) { should == @logger }
32
+ end
33
+ end
34
+
35
+ describe "#stop" do
36
+ before { @mq_suite = Tengine::Mq::Suite.new }
37
+ subject { Tengine::Event::Sender.new(@mq_suite) }
38
+
39
+ it "stops suite" do
40
+ @mq_suite.should_receive :stop
41
+ subject.stop
42
+ end
43
+
44
+ it "calls the given block (if any) afterwards" do
45
+ @mq_suite.should_receive(:stop).and_yield
46
+ block_called = false
47
+ subject.stop { block_called = true }
48
+ block_called.should == true
49
+ end
50
+ end
51
+
52
+ describe "#pending_events" do
53
+ before do
54
+ @mq_suite = Tengine::Mq::Suite.new
55
+ @event = Tengine::Event.new
56
+ @mq_suite.stub(:pending_events_for).with(an_instance_of(Tengine::Event::Sender)).and_return([@event])
57
+ end
58
+ subject { Tengine::Event::Sender.new(@mq_suite).pending_events }
59
+
60
+ it { should be_kind_of Array }
61
+ it { should have(1).events }
62
+ it { should include(@event) }
63
+ end
64
+
65
+ describe "#wait_for_connection" do
66
+ it "does nothing" do
67
+ expect {
68
+ Tengine::Event::Sender.new.wait_for_connection { }
69
+ }.to_not raise_error
70
+ end
71
+ end
72
+
73
+ describe "#fire" do
74
+ before do
75
+ @event = Tengine::Event.new
76
+ @mq_suite = Tengine::Mq::Suite.new
77
+ @mq_suite.stub(:fire).
78
+ with(an_instance_of(Tengine::Event::Sender),
79
+ an_instance_of(Tengine::Event),
80
+ an_instance_of(Hash),
81
+ an_instance_of(Proc)) {
82
+ |q, w, e, r|
83
+ r.yield
84
+ }
85
+ @mq_suite.stub(:fire).
86
+ with(an_instance_of(Tengine::Event::Sender),
87
+ an_instance_of(Tengine::Event),
88
+ an_instance_of(Hash),
89
+ an_instance_of(NilClass))
90
+ end
91
+
92
+ context "mandatory one arg" do
93
+ it { expect { Tengine::Event::Sender.new(@mq_suite).fire }.to raise_exception(ArgumentError) }
94
+ end
95
+
96
+ context "one, string arg" do
97
+ subject { Tengine::Event::Sender.new(@mq_suite).fire("foo") }
98
+ it { should be_kind_of(Tengine::Event) }
99
+ its (:event_type_name) { should == "foo" }
100
+ end
101
+
102
+ context "one, event arg" do
103
+ subject { Tengine::Event::Sender.new(@mq_suite).fire(@event) }
104
+ it { should be_kind_of(Tengine::Event) }
105
+ it { should == @event }
106
+ end
107
+
108
+ context "event arg + option" do
109
+ subject { Tengine::Event::Sender.new(@mq_suite).fire(@event, :keep_connection => true) }
110
+ it { should == @event }
111
+ end
112
+
113
+ context "block argument" do
114
+ before { @event = Tengine::Event.new }
115
+ it "is called" do
116
+ block_called = false
117
+ Tengine::Event::Sender.new(@mq_suite).fire(@event) do
118
+ block_called = true
119
+ end
120
+ block_called.should == true
121
+ end
122
+ end
123
+ end
124
+ end