mamiya 0.0.1.alpha2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +16 -0
  5. data/Gemfile +8 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +43 -0
  8. data/Rakefile +6 -0
  9. data/bin/mamiya +17 -0
  10. data/config.example.yml +11 -0
  11. data/docs/sequences/deploy.png +0 -0
  12. data/docs/sequences/deploy.uml +58 -0
  13. data/example.rb +74 -0
  14. data/lib/mamiya.rb +5 -0
  15. data/lib/mamiya/agent.rb +181 -0
  16. data/lib/mamiya/agent/actions.rb +12 -0
  17. data/lib/mamiya/agent/fetcher.rb +137 -0
  18. data/lib/mamiya/agent/handlers/abstract.rb +20 -0
  19. data/lib/mamiya/agent/handlers/fetch.rb +68 -0
  20. data/lib/mamiya/cli.rb +322 -0
  21. data/lib/mamiya/cli/client.rb +172 -0
  22. data/lib/mamiya/config.rb +57 -0
  23. data/lib/mamiya/dsl.rb +192 -0
  24. data/lib/mamiya/helpers/git.rb +75 -0
  25. data/lib/mamiya/logger.rb +190 -0
  26. data/lib/mamiya/master.rb +118 -0
  27. data/lib/mamiya/master/agent_monitor.rb +146 -0
  28. data/lib/mamiya/master/agent_monitor_handlers.rb +44 -0
  29. data/lib/mamiya/master/web.rb +148 -0
  30. data/lib/mamiya/package.rb +122 -0
  31. data/lib/mamiya/script.rb +117 -0
  32. data/lib/mamiya/steps/abstract.rb +19 -0
  33. data/lib/mamiya/steps/build.rb +72 -0
  34. data/lib/mamiya/steps/extract.rb +26 -0
  35. data/lib/mamiya/steps/fetch.rb +24 -0
  36. data/lib/mamiya/steps/push.rb +34 -0
  37. data/lib/mamiya/storages.rb +17 -0
  38. data/lib/mamiya/storages/abstract.rb +48 -0
  39. data/lib/mamiya/storages/mock.rb +61 -0
  40. data/lib/mamiya/storages/s3.rb +127 -0
  41. data/lib/mamiya/util/label_matcher.rb +38 -0
  42. data/lib/mamiya/version.rb +3 -0
  43. data/mamiya.gemspec +35 -0
  44. data/misc/logger_test.rb +12 -0
  45. data/spec/agent/actions_spec.rb +37 -0
  46. data/spec/agent/fetcher_spec.rb +199 -0
  47. data/spec/agent/handlers/fetch_spec.rb +121 -0
  48. data/spec/agent_spec.rb +255 -0
  49. data/spec/config_spec.rb +50 -0
  50. data/spec/dsl_spec.rb +291 -0
  51. data/spec/fixtures/dsl_test_load.rb +1 -0
  52. data/spec/fixtures/dsl_test_use.rb +1 -0
  53. data/spec/fixtures/helpers/foo.rb +1 -0
  54. data/spec/fixtures/test-package-source/.mamiya.meta.json +1 -0
  55. data/spec/fixtures/test-package-source/greeting +1 -0
  56. data/spec/fixtures/test-package.tar.gz +0 -0
  57. data/spec/fixtures/test.yml +4 -0
  58. data/spec/logger_spec.rb +68 -0
  59. data/spec/master/agent_monitor_spec.rb +269 -0
  60. data/spec/master/web_spec.rb +121 -0
  61. data/spec/master_spec.rb +94 -0
  62. data/spec/package_spec.rb +394 -0
  63. data/spec/script_spec.rb +78 -0
  64. data/spec/spec_helper.rb +38 -0
  65. data/spec/steps/build_spec.rb +261 -0
  66. data/spec/steps/extract_spec.rb +68 -0
  67. data/spec/steps/fetch_spec.rb +96 -0
  68. data/spec/steps/push_spec.rb +73 -0
  69. data/spec/storages/abstract_spec.rb +22 -0
  70. data/spec/storages/s3_spec.rb +342 -0
  71. data/spec/storages_spec.rb +33 -0
  72. data/spec/support/dummy_serf.rb +70 -0
  73. data/spec/util/label_matcher_spec.rb +85 -0
  74. metadata +272 -0
@@ -0,0 +1,3 @@
1
+ module Mamiya
2
+ VERSION = "0.0.1.alpha2"
3
+ end
data/mamiya.gemspec ADDED
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mamiya/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "mamiya"
8
+ spec.version = Mamiya::VERSION
9
+ spec.authors = ["Shota Fukumori (sora_h)"]
10
+ spec.email = ["her@sorah.jp"]
11
+ spec.summary = %q{Fast deploy tool using tarballs and serf}
12
+ spec.description = %q{Deploy tool using tarballs and serf for lot of servers}
13
+ spec.homepage = "https://github.com/sorah/mamiya"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "thor", "~> 0.18.1"
22
+ spec.add_runtime_dependency "aws-sdk-core", "2.0.0.rc6"
23
+ spec.add_runtime_dependency "term-ansicolor", ">= 1.3.0"
24
+ unless ENV["MAMIYA_VILLEIN_PATH"]
25
+ spec.add_runtime_dependency "villein", ">= 0.3.1"
26
+ end
27
+
28
+ spec.add_runtime_dependency "sinatra", ">= 1.4.5"
29
+
30
+ spec.add_development_dependency "rspec", "2.14.1"
31
+ spec.add_development_dependency 'rack-test', '~> 0.6.2'
32
+
33
+ spec.add_development_dependency "bundler", "~> 1.5"
34
+ spec.add_development_dependency "rake"
35
+ end
@@ -0,0 +1,12 @@
1
+ require 'bundler/setup'
2
+ $:.unshift File.join(__dir__, '..' ,'lib')
3
+ require 'mamiya/logger'
4
+
5
+ [true,false].each {|color|
6
+ l = Mamiya::Logger.new(level: Logger::DEBUG, color: color)
7
+ [nil, "app"].each {|n|
8
+ (0..5).each {|s|
9
+ l.log s, "hi", n
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ require 'json'
4
+ require 'villein/event'
5
+
6
+ require 'mamiya/agent'
7
+ require 'mamiya/agent/actions'
8
+
9
+ require_relative '../support/dummy_serf.rb'
10
+
11
+ describe Mamiya::Agent::Actions do
12
+ let(:serf) { DummySerf.new }
13
+ let(:fetcher) { double('fetcher', start!: nil) }
14
+
15
+ let(:config) do
16
+ {serf: {agent: {rpc_addr: '127.0.0.1:17373', bind: '127.0.0.1:17946'}}}
17
+ end
18
+
19
+ before do
20
+ allow(Villein::Agent).to receive(:new).and_return(serf)
21
+ allow(Mamiya::Agent::Fetcher).to receive(:new).and_return(fetcher)
22
+ end
23
+
24
+ subject(:agent) { Mamiya::Agent.new(config) }
25
+
26
+
27
+ describe "#distribute" do
28
+ it "sends fetch request" do
29
+ expect(serf).to receive(:event).with(
30
+ 'mamiya:fetch',
31
+ {application: 'app', package: 'pkg'}.to_json
32
+ )
33
+
34
+ agent.distribute('app', 'pkg')
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,199 @@
1
+ require 'spec_helper'
2
+ require 'pathname'
3
+ require 'tmpdir'
4
+ require 'fileutils'
5
+
6
+ require 'mamiya/agent/fetcher'
7
+ require 'mamiya/steps/fetch'
8
+
9
+ describe Mamiya::Agent::Fetcher do
10
+ let!(:tmpdir) { Dir.mktmpdir('mamiya-agent-fetcher-spec') }
11
+ after { FileUtils.remove_entry_secure(tmpdir) if File.exist?(tmpdir) }
12
+
13
+ let(:config) do
14
+ {packages_dir: tmpdir, keep_packages: 2}
15
+ end
16
+
17
+ subject(:fetcher) { described_class.new(config) }
18
+
19
+ describe "lifecycle" do
20
+ it "can start and stop" do
21
+ expect(fetcher.thread).to be_nil
22
+ expect(fetcher).not_to be_running
23
+
24
+ fetcher.start!
25
+
26
+ expect(fetcher).to be_running
27
+ expect(fetcher.thread).to be_a(Thread)
28
+ expect(fetcher.thread).to be_alive
29
+ th = fetcher.thread
30
+
31
+ fetcher.stop!
32
+
33
+ 10.times { break unless th.alive?; sleep 0.1 }
34
+ expect(th).not_to be_alive
35
+
36
+ expect(fetcher.thread).to be_nil
37
+ expect(fetcher).not_to be_running
38
+ end
39
+
40
+ it "can graceful stop"
41
+ end
42
+
43
+ describe "#cleanup" do
44
+ before do
45
+ path = Pathname.new(tmpdir)
46
+
47
+ path.join('a').mkdir
48
+ File.write path.join('a', "a.tar.gz"), "\n"
49
+ File.write path.join('a', "a.json"), "\n"
50
+ File.write path.join('a', "b.json"), "\n"
51
+ File.write path.join('a', "b.tar.gz"), "\n"
52
+ File.write path.join('a', "c.json"), "\n"
53
+ File.write path.join('a', "c.tar.gz"), "\n"
54
+ path.join('b').mkdir
55
+ File.write path.join('b', "a.tar.gz"), "\n"
56
+ File.write path.join('b', "a.json"), "\n"
57
+
58
+ path.join('c').mkdir
59
+ File.write path.join('c', "a.tar.gz"), "\n"
60
+ File.write path.join('c', "b.json"), "\n"
61
+ end
62
+
63
+ it "cleans up" do
64
+ called = []
65
+ fetcher.cleanup_hook = proc { |a,b| called << [a,b] }
66
+
67
+ fetcher.cleanup
68
+
69
+ path = Pathname.new(tmpdir)
70
+ existences = Hash[
71
+ [
72
+ path.join('a', 'a.tar.gz'),
73
+ path.join('a', 'a.json'),
74
+ path.join('a', 'b.tar.gz'),
75
+ path.join('a', 'b.json'),
76
+ path.join('a', 'c.tar.gz'),
77
+ path.join('a', 'c.json'),
78
+ ].map { |file|
79
+ [file, file.exist?]
80
+ }
81
+ ]
82
+
83
+ expect(called).to eq([['a', 'a']])
84
+ expect(existences).to eq(
85
+ path.join('a', 'a.tar.gz') => false,
86
+ path.join('a', 'a.json') => false,
87
+ path.join('a', 'b.tar.gz') => true,
88
+ path.join('a', 'b.json') => true,
89
+ path.join('a', 'c.tar.gz') => true,
90
+ path.join('a', 'c.json') => true,
91
+ )
92
+ end
93
+ end
94
+
95
+ describe "mainloop" do
96
+ before do
97
+ allow(step).to receive(:run!)
98
+ allow(Mamiya::Steps::Fetch).to receive(:new).with(
99
+ application: 'myapp',
100
+ package: 'package',
101
+ destination: File.join(tmpdir, 'myapp'),
102
+ config: config,
103
+ ).and_return(step)
104
+
105
+ fetcher.start!
106
+ end
107
+
108
+ let(:step) { double('fetch-step') }
109
+
110
+ it "starts fetch step for each order" do
111
+ flag = false
112
+
113
+ expect(step).to receive(:run!) do
114
+ flag = true
115
+ end
116
+
117
+ fetcher.enqueue('myapp', 'package')
118
+ fetcher.stop!(:graceful)
119
+ end
120
+
121
+ it "calls callback" do
122
+ received = true
123
+
124
+ fetcher.enqueue('myapp', 'package') do |succeeded|
125
+ received = succeeded
126
+ end
127
+
128
+ fetcher.stop!(:graceful)
129
+
130
+ expect(received).to be_nil
131
+ end
132
+
133
+ it "calls cleanup" do
134
+ expect(fetcher).to receive(:cleanup)
135
+ fetcher.enqueue('myapp', 'package')
136
+ fetcher.stop!(:graceful)
137
+ end
138
+
139
+ it "claims itself as working" do
140
+ expect(fetcher.working?).to be_false
141
+ expect(fetcher.current_job).to be_nil
142
+
143
+ received = false
144
+ fetcher.enqueue('myapp', 'package') do |error|
145
+ received = true
146
+ expect(fetcher.working?).to be_true
147
+ expect(fetcher.current_job).to eq %w(myapp package)
148
+ end
149
+
150
+ fetcher.stop!(:graceful)
151
+ expect(received).to be_true
152
+ expect(fetcher.working?).to be_false
153
+ expect(fetcher.current_job).to be_nil
154
+ end
155
+
156
+ context "with before hook" do
157
+ it "calls callback" do
158
+ run = false
159
+ received = false
160
+
161
+ allow(step).to receive(:run!) do
162
+ run = true
163
+ end
164
+
165
+ fetcher.enqueue('myapp', 'package', before: proc {
166
+ received = true
167
+ expect(run).to be_false
168
+ })
169
+ fetcher.stop!(:graceful)
170
+
171
+ expect(received).to be_true
172
+ end
173
+ end
174
+
175
+ context "when fetch step raised error" do
176
+ let(:exception) { Exception.new("he he...") }
177
+
178
+ before do
179
+ allow(step).to receive(:run!).and_raise(exception)
180
+ end
181
+
182
+ it "calls callback with error" do
183
+ received = nil
184
+
185
+ fetcher.enqueue('myapp', 'package') do |error|
186
+ received = error
187
+ end
188
+
189
+ fetcher.stop!(:graceful)
190
+
191
+ expect(received).to eq exception
192
+ end
193
+ end
194
+
195
+ after do
196
+ fetcher.stop!
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,121 @@
1
+ require 'spec_helper'
2
+
3
+ require 'json'
4
+ require 'villein/event'
5
+
6
+ require 'mamiya/agent/handlers/fetch'
7
+
8
+ describe Mamiya::Agent::Handlers::Fetch do
9
+ let(:event) do
10
+ Villein::Event.new(
11
+ {
12
+ 'SERF_EVENT' => 'user',
13
+ 'SERF_USER_EVENT' => 'mamiya:fetch',
14
+ },
15
+ payload: {
16
+ application: 'app',
17
+ package: 'package',
18
+ }.to_json,
19
+ )
20
+ end
21
+
22
+ let(:fetcher) { double('fetcher', start!: nil) }
23
+ let(:serf) { double('serf', name: 'myname', event: nil) }
24
+
25
+ let(:agent) do
26
+ double('agent', fetcher: fetcher, serf: serf, update_tags!: nil)
27
+ end
28
+
29
+ subject(:handler) { described_class.new(agent, event) }
30
+
31
+ before do
32
+ allow(fetcher).to receive(:queue_size).and_return(0)
33
+ allow(fetcher).to receive(:enqueue) do |&block|
34
+ block.call
35
+ end
36
+ end
37
+
38
+ it "enqueue a request" do
39
+ expect(fetcher).to receive(:enqueue).with('app', 'package', kind_of(Hash))
40
+
41
+ handler.run!
42
+ end
43
+
44
+ it "responds ack" do
45
+ allow(fetcher).to receive(:enqueue).with('app', 'package')
46
+ expect(serf).to receive(:event).with('mamiya:fetch-result:ack',
47
+ {name: serf.name, application: 'app', package: 'package', pending: 1}.to_json)
48
+
49
+ handler.run!
50
+ end
51
+
52
+ it "kicks update_tag! on start" do
53
+ allow(fetcher).to receive(:enqueue) do |app, package, options, &block|
54
+ options[:before].call
55
+ end
56
+ expect(agent).to receive(:update_tags!)
57
+ handler.run!
58
+ end
59
+
60
+ it "reports start" do
61
+ allow(fetcher).to receive(:enqueue) do |app, package, options, &block|
62
+ options[:before].call
63
+ end
64
+ expect(serf).to receive(:event).with('mamiya:fetch-result:start',
65
+ {name: serf.name, application: 'app', package: 'package', pending: 1}.to_json)
66
+
67
+ handler.run!
68
+ end
69
+
70
+ it "responds success" do
71
+ callback = nil
72
+ allow(fetcher).to receive(:enqueue).with('app', 'package', kind_of(Hash)) do |&block|
73
+ callback = block
74
+ end
75
+
76
+ handler.run!
77
+
78
+ expect(serf).to receive(:event).with(
79
+ 'mamiya:fetch-result:success',
80
+ {name: serf.name, application: 'app', package: 'package', pending: 0}.to_json
81
+ )
82
+
83
+ callback.call
84
+ end
85
+
86
+ it "updates tag" do
87
+ expect(agent).to receive(:update_tags!)
88
+ handler.run!
89
+ end
90
+
91
+ context "when failed" do
92
+ it "notifies error" do
93
+ callback = nil
94
+ allow(fetcher).to receive(:enqueue).with('app', 'package', kind_of(Hash)) do |&block|
95
+ callback = block
96
+ end
97
+
98
+ handler.run!
99
+
100
+ error = RuntimeError.new('test')
101
+ expect(serf).to receive(:event).with(
102
+ 'mamiya:fetch-result:error',
103
+ {
104
+ name: serf.name, application: 'app', package: 'package',
105
+ error: error.inspect, pending: 0,
106
+ }.to_json,
107
+ )
108
+
109
+ callback.call(error)
110
+ end
111
+
112
+ it "updates tag" do
113
+ allow(fetcher).to receive(:enqueue) do |&block|
114
+ block.call(Exception.new)
115
+ end
116
+
117
+ expect(agent).to receive(:update_tags!)
118
+ handler.run!
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,255 @@
1
+ require 'spec_helper'
2
+
3
+ require 'tmpdir'
4
+ require 'fileutils'
5
+ require 'json'
6
+ require 'villein/event'
7
+
8
+ require 'mamiya/agent'
9
+ require 'mamiya/agent/fetcher'
10
+ require 'mamiya/agent/actions'
11
+
12
+ require_relative './support/dummy_serf.rb'
13
+
14
+ describe Mamiya::Agent do
15
+
16
+ let(:serf) { DummySerf.new }
17
+ let(:fetcher) do
18
+ double('fetcher', start!: nil, working?: false).tap do |f|
19
+ cleanup_hook = nil
20
+ allow(f).to receive(:cleanup_hook=) { |_| cleanup_hook = _ }
21
+ allow(f).to receive(:cleanup_hook) { cleanup_hook }
22
+ end
23
+ end
24
+
25
+ let(:config) do
26
+ {serf: {agent: {rpc_addr: '127.0.0.1:17373', bind: '127.0.0.1:17946'}}}
27
+ end
28
+
29
+ before do
30
+ allow(Villein::Agent).to receive(:new).and_return(serf)
31
+ allow(Mamiya::Agent::Fetcher).to receive(:new).and_return(fetcher)
32
+ end
33
+
34
+ subject(:agent) { described_class.new(config) }
35
+
36
+ it "includes actions" do
37
+ expect(described_class.ancestors).to include(Mamiya::Agent::Actions)
38
+ end
39
+
40
+ describe "fetcher" do
41
+ it "sends events on cleanup hook" do
42
+ expect(serf).to receive(:event).with(
43
+ 'mamiya:fetch-result:remove',
44
+ {
45
+ name: serf.name, application: 'foo', package: 'bar',
46
+ }.to_json,
47
+ )
48
+
49
+ agent.fetcher.cleanup_hook.call('foo', 'bar')
50
+ end
51
+ end
52
+
53
+ describe "#run!" do
54
+ it "starts serf and fetcher" do
55
+ begin
56
+ flag = false
57
+
58
+ expect(fetcher).to receive(:start!)
59
+ expect(serf).to receive(:start!)
60
+ expect(serf).to receive(:auto_stop) do
61
+ flag = true
62
+ end
63
+
64
+ th = Thread.new { agent.run! }
65
+
66
+ 10.times { break if flag; sleep 0.1 }
67
+ ensure
68
+ th.kill if th && th.alive?
69
+ end
70
+ end
71
+ end
72
+
73
+ describe "#update_tags!" do
74
+ describe "(status)" do
75
+ context "when it is not busy" do
76
+ it "shows ready" do
77
+ agent.update_tags!
78
+
79
+ expect(serf.tags['mamiya']).to eq ',ready,'
80
+ end
81
+ end
82
+
83
+ context "when it is fetching" do
84
+ before do
85
+ allow(fetcher).to receive(:working?).and_return(true)
86
+ end
87
+
88
+ it "shows fetching" do
89
+ agent.update_tags!
90
+
91
+ expect(serf.tags['mamiya']).to eq ',fetching,'
92
+ end
93
+ end
94
+
95
+ context "when it is running multiple jobs" do
96
+ pending
97
+ end
98
+ end
99
+
100
+ describe "(prepared)" do
101
+ pending
102
+ end
103
+
104
+ describe "(current)" do
105
+ pending
106
+ end
107
+ end
108
+
109
+ describe "#status" do
110
+ before do
111
+ allow(agent).to receive(:existing_packages).and_return("app" => ["pkg"])
112
+ allow(fetcher).to receive(:queue_size).and_return(42)
113
+ allow(fetcher).to receive(:working?).and_return(false)
114
+ allow(fetcher).to receive(:current_job).and_return(nil)
115
+ end
116
+
117
+ subject(:status) { agent.status }
118
+
119
+ it "includes agent name" do
120
+ expect(status[:name]).to eq serf.name
121
+ end
122
+
123
+ it "includes packages" do
124
+ expect(status[:packages]).to eq agent.existing_packages
125
+ end
126
+
127
+ describe "(fetcher)" do
128
+ it "includes queue_size as pending" do
129
+ expect(status[:fetcher][:pending]).to eq 42
130
+ end
131
+
132
+ it "shows fetching status" do
133
+ expect(status[:fetcher][:fetching]).to be_nil
134
+ end
135
+
136
+ context "when it's fetching" do
137
+ before do
138
+ allow(fetcher).to receive(:working?).and_return(true)
139
+ allow(fetcher).to receive(:current_job).and_return(%w(foo bar))
140
+ end
141
+
142
+ it "shows fetching true" do
143
+ expect(status[:fetcher][:fetching]).to eq ['foo', 'bar']
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ describe "#existing_packages" do
150
+ let!(:packages_dir) { Dir.mktmpdir('mamiya-agent-spec') }
151
+ after { FileUtils.remove_entry_secure(packages_dir) }
152
+
153
+ let(:config) { {packages_dir: packages_dir} }
154
+
155
+ subject(:existing_packages) { agent.existing_packages }
156
+
157
+ before do
158
+ dir = Pathname.new(packages_dir)
159
+ %w(a b).each do |app|
160
+ dir.join(app).mkdir
161
+ %w(valid.tar.gz valid.json
162
+ valid-2.tar.gz valid-2.json
163
+ invalid-1.tar.gz invalid-2.json invalid-3.txt).each do |name|
164
+ File.write dir.join(app, name), "\n"
165
+ end
166
+ end
167
+ end
168
+
169
+ it "returns valid packages" do
170
+ expect(existing_packages).to eq(
171
+ "a" => ["valid", "valid-2"],
172
+ "b" => ["valid", "valid-2"],
173
+ )
174
+ end
175
+ end
176
+
177
+ describe "query responder" do
178
+ it "responds to 'mamiya:status'" do
179
+ allow(agent).to receive(:status).and_return("my" => "status")
180
+
181
+ response = serf.trigger_query('mamiya:status', '')
182
+ expect(JSON.parse(response)).to eq("my" => "status")
183
+ end
184
+ end
185
+
186
+ describe "event handler" do
187
+ let(:handler_class) do
188
+ Class.new(Mamiya::Agent::Handlers::Abstract) do
189
+ end
190
+ end
191
+
192
+ def trigger(name, payload={})
193
+ serf.trigger('user_event', Villein::Event.new(
194
+ {
195
+ 'SERF_EVENT' => 'user',
196
+ 'SERF_USER_EVENT' => "mamiya:#{name}",
197
+ },
198
+ payload: payload.to_json,
199
+ ))
200
+ end
201
+
202
+ before do
203
+ stub_const("Mamiya::Agent::Handlers::Test", handler_class)
204
+ agent # to create
205
+ end
206
+
207
+ it "finds handler class then call #run!" do
208
+ expect_any_instance_of(handler_class).to receive(:run!)
209
+
210
+ trigger('test')
211
+ end
212
+
213
+ it "passes proper argument to handler"
214
+
215
+ context "when handler not found" do
216
+ it "ignores event"
217
+ end
218
+
219
+ context "with events_only" do
220
+ subject(:agent) { described_class.new(config, events_only: [/foo/]) }
221
+
222
+ let(:handler_foo) do
223
+ Class.new(Mamiya::Agent::Handlers::Abstract) do
224
+ end
225
+ end
226
+
227
+ let(:handler_bar) do
228
+ Class.new(Mamiya::Agent::Handlers::Abstract) do
229
+ def run!
230
+ raise 'oops?'
231
+ end
232
+ end
233
+ end
234
+
235
+ before do
236
+ stub_const("Mamiya::Agent::Handlers::Foo", handler_foo)
237
+ stub_const("Mamiya::Agent::Handlers::Bar", handler_bar)
238
+ end
239
+
240
+ it "handles events only matches any of them" do
241
+ expect_any_instance_of(handler_foo).to receive(:run!)
242
+
243
+ trigger('foo')
244
+ trigger('bar')
245
+ end
246
+ end
247
+
248
+ context "with action" do
249
+ it "calls another method instead of run!" do
250
+ expect_any_instance_of(handler_class).to receive(:hello)
251
+ trigger('test:hello')
252
+ end
253
+ end
254
+ end
255
+ end