trident 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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