trident 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,253 @@
1
+ require_relative '../../test_helper'
2
+
3
+ class Trident::CLITest < MiniTest::Should::TestCase
4
+
5
+ class Handler
6
+ def initialize(options={})
7
+ end
8
+ def start
9
+ end
10
+ end
11
+
12
+ setup do
13
+ @project_root = File.expand_path('../../../..', __FILE__)
14
+ @cli = "#{@project_root}/bin/trident"
15
+ end
16
+
17
+ context "#help" do
18
+
19
+ should "generate readable usage" do
20
+ out = `#{@cli} --help`
21
+ assert $? == 0
22
+ assert out.lines.all? {|l| l.size <= 81 }
23
+ end
24
+
25
+ end
26
+
27
+ context "#project_root" do
28
+
29
+ should "use bundler env for root" do
30
+ cli = Trident::CLI.new([])
31
+ assert_equal @project_root, File.dirname(ENV['BUNDLE_GEMFILE'])
32
+ assert_equal @project_root, cli.send(:project_root)
33
+ end
34
+
35
+ should "use cwd for root when no bundler" do
36
+ cli = Trident::CLI.new([])
37
+ Bundler.with_clean_env do
38
+ assert_nil ENV['BUNDLE_GEMFILE']
39
+ assert_equal '.', cli.send(:project_root)
40
+ end
41
+ end
42
+
43
+ end
44
+
45
+ context "#expand_path" do
46
+
47
+ should "expand path relative to project_root" do
48
+ cli = Trident::CLI.new([])
49
+ assert_equal "#{@project_root}/bin", cli.send(:expand_path, "bin")
50
+ end
51
+
52
+ should "not expand path if absolute" do
53
+ cli = Trident::CLI.new([])
54
+ assert_equal "/bin", cli.send(:expand_path, "/bin")
55
+ end
56
+
57
+ should "handle nil path" do
58
+ cli = Trident::CLI.new([])
59
+ assert_nil cli.send(:expand_path, nil)
60
+ end
61
+
62
+ end
63
+
64
+ context "#load_config" do
65
+
66
+ should "load yml from file" do
67
+ data = <<-ENDDATA
68
+ foo: bar
69
+ baz: [1, 2, 3]
70
+ ENDDATA
71
+ IO.expects(:read).with("/foo").returns(data)
72
+ cli = Trident::CLI.new([])
73
+ config = cli.send(:load_config, "/foo")
74
+ assert_equal "bar", config['foo']
75
+ assert_equal [1, 2, 3], config['baz']
76
+ end
77
+
78
+ should "expand erb" do
79
+ data = <<-ENDDATA
80
+ foo: <%= 1 +1 %>
81
+ ENDDATA
82
+ IO.expects(:read).with("/foo").returns(data)
83
+ cli = Trident::CLI.new([])
84
+ config = cli.send(:load_config, "/foo")
85
+ assert_equal 2, config['foo']
86
+ end
87
+
88
+ should "use nested environments" do
89
+ begin
90
+ class ::Rails; def self.env; "test"; end; end
91
+ data = <<-ENDDATA
92
+ foo: bar
93
+ test:
94
+ foo: baz
95
+ ENDDATA
96
+ IO.expects(:read).with("/foo").returns(data)
97
+ cli = Trident::CLI.new([])
98
+ config = cli.send(:load_config, "/foo")
99
+ assert_equal "baz", config['foo']
100
+ ensure
101
+ Object.send(:remove_const, :Rails)
102
+ end
103
+ end
104
+
105
+ end
106
+
107
+ context "#create_handlers" do
108
+
109
+ should "create handlers from config" do
110
+ cli = Trident::CLI.new([])
111
+ handlers_config = {
112
+ "handler1" => {
113
+ "environment" => "require 'something'",
114
+ "class" => "Trident::CLITest::Handler",
115
+ "options" => {"foo" => "bar"},
116
+ "signals" => {"stop_forcefully" => "TERM", "stop_gracefully" => "TERM"}
117
+ },
118
+ "handler2" => {
119
+ "environment" => "require 'something'",
120
+ "class" => "Trident::CLITest::Handler",
121
+ "options" => {"foo" => "bar"},
122
+ "signals" => {"stop_forcefully" => "TERM", "stop_gracefully" => "TERM"}
123
+ }
124
+ }
125
+ handlers = cli.send(:create_handlers, handlers_config)
126
+ assert_equal 2, handlers.size
127
+ assert_equal 'handler1', handlers['handler1'].name
128
+ assert_equal "require 'something'", handlers['handler1'].environment
129
+ assert_equal 'Trident::CLITest::Handler', handlers['handler1'].worker_class_name
130
+ assert_equal({"stop_forcefully" => "TERM", "stop_gracefully" => "TERM"}, handlers['handler2'].signal_mappings)
131
+ assert_equal({"foo" => "bar"}, handlers['handler1'].options)
132
+ assert_equal 'handler2', handlers['handler2'].name
133
+ end
134
+
135
+ end
136
+
137
+ context "#create_pools" do
138
+
139
+ setup do
140
+ @handlers = {
141
+ "handler1" => Trident::PoolHandler.new("handler1", nil, nil, nil),
142
+ "handler2" => Trident::PoolHandler.new("handler2", nil, nil, nil)
143
+ }
144
+ @pools_config = {
145
+ "pool1" => {
146
+ "size" => 5,
147
+ "options" => {"foo" => "bar"},
148
+ "handler" => "handler1"
149
+ },
150
+ "pool2" => {
151
+ "size" => 3,
152
+ "options" => {"baz" => "bum"},
153
+ "handler" => "handler2"
154
+ }
155
+ }
156
+ end
157
+
158
+ should "create handlers from config" do
159
+ cli = Trident::CLI.new([])
160
+ pools = cli.send(:create_pools, @pools_config, @handlers)
161
+ assert_equal 2, pools.size
162
+ assert_equal 'pool1', pools['pool1'].name
163
+ assert_equal 'pool2', pools['pool2'].name
164
+
165
+ assert_equal 5, pools['pool1'].size
166
+ assert_equal @handlers['handler1'], pools['pool1'].handler
167
+ assert_equal({"foo" => "bar"}, pools['pool1'].options)
168
+ end
169
+
170
+ should "filter pools if given" do
171
+ cli = Trident::CLI.new([])
172
+ pools = cli.send(:create_pools, @pools_config, @handlers, ['pool1'])
173
+ assert_equal 1, pools.size
174
+ assert_equal 'pool1', pools.values.first.name
175
+ end
176
+
177
+ end
178
+
179
+ context "#execute" do
180
+
181
+ setup do
182
+ data = <<-ENDDATA
183
+ application: test
184
+ handlers:
185
+ handler1:
186
+ environment: ""
187
+ class: Worker
188
+ options:
189
+ signals:
190
+ default: TERM
191
+ stop_forcefully: INT
192
+ stop_gracefully: TERM
193
+ reload: HUP
194
+ pools:
195
+ qless:
196
+ size: 5
197
+ handler: handler1
198
+ options:
199
+ ENDDATA
200
+ IO.stubs(:read).with("/foo").returns(data)
201
+ end
202
+
203
+ should "fail if no logfile and pidfile when daemonizing" do
204
+
205
+ Trident::SignalHandler.expects(:start).never
206
+ Trident::Pool.any_instance.expects(:fork).never
207
+ Trident::CLI.any_instance.expects(:daemonize).never
208
+
209
+ ex = assert_raises(Clamp::UsageError) do
210
+ Trident::CLI.new("").run(["--config",
211
+ "/foo",
212
+ "--daemon"])
213
+ end
214
+ assert_match "--logfile and --pidfile are required", ex.message
215
+
216
+ ex = assert_raises(Clamp::UsageError) do
217
+ Trident::CLI.new("").run(["--config",
218
+ "/foo",
219
+ "--daemon",
220
+ "--pidfile",
221
+ Tempfile.new('pid').path])
222
+ end
223
+ assert_match "--logfile and --pidfile are required", ex.message
224
+
225
+ ex = assert_raises(Clamp::UsageError) do
226
+ Trident::CLI.new("").run(["--config",
227
+ "/foo",
228
+ "--daemon",
229
+ "--logfile",
230
+ Tempfile.new('log').path])
231
+ end
232
+ assert_match "--logfile and --pidfile are required", ex.message
233
+ end
234
+
235
+ should "run if given logfile and pidfile when daemonizing" do
236
+
237
+ Trident::SignalHandler.expects(:start).once
238
+ Trident::Pool.any_instance.stubs(:fork)
239
+ Trident::CLI.any_instance.expects(:daemonize).once
240
+
241
+ Trident::CLI.new("").run(["--config",
242
+ "/foo",
243
+ "--daemon",
244
+ "--logfile",
245
+ Tempfile.new('log').path,
246
+ "--pidfile",
247
+ Tempfile.new('pid').path])
248
+
249
+ end
250
+
251
+ end
252
+
253
+ end
@@ -0,0 +1,70 @@
1
+ require_relative '../../test_helper'
2
+
3
+ class Trident::PoolHandlerTest < MiniTest::Should::TestCase
4
+
5
+ setup do
6
+ PoolHandler.constants(false).each do |c|
7
+ PoolHandler.send(:remove_const, c) if c =~ /^Test/
8
+ end
9
+ end
10
+
11
+ context "#load" do
12
+
13
+ should "eval the environment" do
14
+ env = <<-EOS
15
+ class TestPoolWorker; def start; end; end
16
+ EOS
17
+ assert_raises(NameError) { PoolHandler.const_get("TestPoolWorker") }
18
+ handler = PoolHandler.new("foo", "TestPoolWorker", env, {})
19
+ handler.load
20
+ assert PoolHandler.const_get("TestPoolWorker")
21
+ end
22
+
23
+ end
24
+
25
+ context "#worker_class" do
26
+
27
+ should "find the worker class" do
28
+ env = <<-EOS
29
+ class TestPoolWorker; def start; end; end
30
+ EOS
31
+ handler = PoolHandler.new("foo", "TestPoolWorker", env, {})
32
+ handler.load
33
+ assert_match "TestPoolWorker", handler.worker_class.name
34
+ end
35
+
36
+ end
37
+
38
+ context "#start" do
39
+
40
+ should "initialize the handler class with options and call start" do
41
+ env = <<-EOS
42
+ class TestPoolWorker; def initialize(o); @o = o; end; def start; @o.merge("x" => "y"); end; end
43
+ EOS
44
+ handler = PoolHandler.new("foo", "TestPoolWorker", env, {}, {"a" => "b", "c" => "d"})
45
+ handler.load
46
+ assert_equal({"a"=>"z", "c"=>"d", "e"=>"f", "x"=>"y"}, handler.start("e" => "f", "a" => "z"))
47
+ end
48
+
49
+ end
50
+
51
+ context "#signal_for" do
52
+
53
+ should "return the signal for a given action" do
54
+ handler = PoolHandler.new("foo", "TestPoolWorker", nil, {"stop_forcefully" => "KILL"})
55
+ assert_equal "KILL", handler.signal_for("stop_forcefully")
56
+ end
57
+
58
+ should "use the default signal if present" do
59
+ handler = PoolHandler.new("foo", "TestPoolWorker", nil, {"default" => "INT", "stop_forcefully" => "KILL"})
60
+ assert_equal "INT", handler.signal_for("stop_gracefully")
61
+ end
62
+
63
+ should "default the signal to TERM if no default" do
64
+ handler = PoolHandler.new("foo", "TestPoolWorker", nil, {"stop_forcefully" => "KILL"})
65
+ assert_equal "SIGTERM", handler.signal_for("stop_gracefully")
66
+ end
67
+
68
+ end
69
+
70
+ end
@@ -0,0 +1,131 @@
1
+ require_relative '../../test_helper'
2
+
3
+ class Trident::PoolManagerTest < MiniTest::Should::TestCase
4
+
5
+ setup do
6
+ SignalHandler.stubs(:reset_for_fork)
7
+
8
+ PoolHandler.constants(false).each do |c|
9
+ PoolHandler.send(:remove_const, c) if c =~ /^Test/
10
+ end
11
+
12
+ $counter = FileCounter.new
13
+
14
+ env = <<-EOS
15
+ $counter.increment
16
+
17
+ class TestPoolWorker
18
+ def initialize(o)
19
+ @o = o
20
+ end
21
+ def start
22
+ sleep(@o['sleep']) if @o['sleep']
23
+ end
24
+ end
25
+ EOS
26
+
27
+ signal_mappings = {'stop_forcefully' => 'KILL', 'stop_gracefully' => 'TERM'}
28
+ @handler1 = PoolHandler.new("foo", "TestPoolWorker", env, signal_mappings, {})
29
+ @handler2 = PoolHandler.new("bar", "TestPoolWorker", env, signal_mappings, {})
30
+ @pool1 = Pool.new("foo", @handler1, 2, 'sleep' => 0.1)
31
+ @pool2 = Pool.new("bar", @handler2, 3, 'sleep' => 0.1)
32
+ end
33
+
34
+ context "#start" do
35
+
36
+ should "start workers for each pool" do
37
+ manager = PoolManager.new("mymanager", [@pool1, @pool2], false)
38
+ manager.expects(:load_handlers).never
39
+ manager.start
40
+ assert_equal 2, @pool1.workers.size
41
+ assert_equal 3, @pool2.workers.size
42
+ Process.waitall
43
+ # once for each worker
44
+ assert_equal 5, $counter.read
45
+ end
46
+
47
+ should "preload env for all handlers if prefork" do
48
+ manager = PoolManager.new("mymanager", [@pool1, @pool2], true)
49
+ manager.start
50
+ assert_equal 2, @pool1.workers.size
51
+ assert_equal 3, @pool2.workers.size
52
+ Process.waitall
53
+ # once for each worker plus once for each handler
54
+ assert_equal 7, $counter.read
55
+ end
56
+
57
+ end
58
+
59
+ context "#stop" do
60
+
61
+ should "stop workers for each pool" do
62
+ manager = PoolManager.new("mymanager", [@pool1, @pool2], false)
63
+ manager.start
64
+ assert_equal 2, @pool1.workers.size
65
+ assert_equal 3, @pool2.workers.size
66
+
67
+ manager.send(:stop, "stop_forcefully")
68
+ Process.waitall
69
+
70
+ assert_empty @pool1.workers
71
+ assert_empty @pool2.workers
72
+ end
73
+
74
+ should "send stop_forcefully" do
75
+ manager = PoolManager.new("mymanager", [@pool1, @pool2], false)
76
+ manager.start
77
+
78
+ manager.expects(:stop).with("stop_forcefully")
79
+ manager.stop_forcefully
80
+ end
81
+
82
+ should "send stop_gracefully" do
83
+ manager = PoolManager.new("mymanager", [@pool1, @pool2], false)
84
+ manager.start
85
+
86
+ manager.expects(:stop).with("stop_gracefully")
87
+ manager.stop_gracefully
88
+ end
89
+
90
+ end
91
+
92
+ context "#wait" do
93
+
94
+ should "wait for processes to exit" do
95
+ manager = PoolManager.new("mymanager", [@pool1, @pool2], false)
96
+ manager.start
97
+ assert_equal 2, @pool1.workers.size
98
+ assert_equal 3, @pool2.workers.size
99
+
100
+ thread = Thread.new { manager.wait }
101
+ sleep 0.01
102
+ assert_equal 2, @pool1.workers.size
103
+ assert_equal 3, @pool2.workers.size
104
+
105
+ thread.join
106
+ assert_empty @pool1.workers
107
+ assert_empty @pool2.workers
108
+ end
109
+
110
+ end
111
+
112
+ context "#update" do
113
+
114
+ should "update status of all pools" do
115
+ manager = PoolManager.new("mymanager", [@pool1, @pool2], false)
116
+ manager.start
117
+ orig_pool1 = @pool1.workers.dup
118
+ orig_pool2 = @pool2.workers.dup
119
+
120
+ assert_equal orig_pool1, @pool1.workers
121
+ assert_equal orig_pool2, @pool2.workers
122
+ sleep 0.3
123
+
124
+ manager.update
125
+ refute_equal orig_pool1, @pool1.workers
126
+ refute_equal orig_pool2, @pool2.workers
127
+ end
128
+
129
+ end
130
+
131
+ end