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.
- checksums.yaml +15 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +17 -0
- data/.travis.yml +8 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +33 -0
- data/Rakefile +18 -0
- data/bin/trident +8 -0
- data/lib/trident.rb +8 -0
- data/lib/trident/cli.rb +130 -0
- data/lib/trident/pool.rb +108 -0
- data/lib/trident/pool_handler.rb +31 -0
- data/lib/trident/pool_manager.rb +79 -0
- data/lib/trident/signal_handler.rb +167 -0
- data/lib/trident/utils.rb +9 -0
- data/lib/trident/version.rb +3 -0
- data/test/fixtures/integration_project/config/trident.yml +51 -0
- data/test/integration/trident_test.rb +105 -0
- data/test/test_helper.rb +144 -0
- data/test/unit/trident/cli_test.rb +253 -0
- data/test/unit/trident/pool_handler_test.rb +70 -0
- data/test/unit/trident/pool_manager_test.rb +131 -0
- data/test/unit/trident/pool_test.rb +233 -0
- data/test/unit/trident/signal_handler_test.rb +262 -0
- data/test/unit/trident/utils_test.rb +20 -0
- data/trident.example.yml +49 -0
- data/trident.gemspec +29 -0
- metadata +180 -0
@@ -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
|