perfectqueue 0.8.44.1 → 0.8.45

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe PerfectQueue::Application::Dispatch do
4
+ describe '.new' do
5
+ before do
6
+ router = Application::Dispatch.router
7
+ handler = double('handler')
8
+ allow(handler).to receive(:new).and_return(nil)
9
+ router.add(/\Afoo\z/, handler, nil)
10
+ end
11
+ it 'returns a PerfectQueue::Application::Dispatch' do
12
+ task = double('task', type: 'foo')
13
+ dispatch = Application::Dispatch.new(task)
14
+ expect(dispatch).to be_an_instance_of(Application::Dispatch)
15
+ end
16
+ it 'raises RuntimeError if the task type doesn\'t match' do
17
+ task = double('task', type: 'bar')
18
+ expect(task).to receive(:retry!).exactly(:once)
19
+ expect{Application::Dispatch.new(task)}.to raise_error(RuntimeError)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+
3
+ describe PerfectQueue::Application::Router do
4
+ describe '.new' do
5
+ it 'returns a PerfectQueue::Application::Router' do
6
+ router = Application::Router.new
7
+ expect(router).to be_an_instance_of(Application::Router)
8
+ end
9
+ end
10
+
11
+ describe '#add' do
12
+ let (:router){ Application::Router.new }
13
+ let (:sym){ double('sym') }
14
+ it 'accepts Regexp' do
15
+ router.add(/\Afoo\z/, sym, double)
16
+ expect(router.patterns[0]).to eq([/\Afoo\z/, sym])
17
+ end
18
+ it 'accepts String' do
19
+ router.add('foo', sym, double)
20
+ expect(router.patterns[0]).to eq([/\Afoo\z/, sym])
21
+ end
22
+ it 'accepts Symbol' do
23
+ router.add(:foo, sym, double)
24
+ expect(router.patterns[0]).to eq([/\Afoo\z/, sym])
25
+ end
26
+ it 'raises for others' do
27
+ expect{router.add(nil, nil, nil)}.to raise_error(ArgumentError)
28
+ end
29
+ end
30
+
31
+ describe '#route' do
32
+ let (:router) do
33
+ rt = Application::Router.new
34
+ rt.add(/\Afoo\z/, :TestHandler, double)
35
+ rt
36
+ end
37
+ let (:handler){ double('handler') }
38
+ before do
39
+ Application::Router::TestHandler = handler
40
+ end
41
+ after do
42
+ Application::Router.__send__(:remove_const, :TestHandler)
43
+ end
44
+ it 'return related handler' do
45
+ expect(router.route('foo')).to eq(handler)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe PerfectQueue::Backend do
4
+ describe '.new_backend' do
5
+ it 'raises error if config[:type] is nil' do
6
+ expect{Backend.new_backend(nil, {})}.to raise_error(ConfigError)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+
3
+ describe PerfectQueue::BlockingFlag do
4
+ describe '.new' do
5
+ it 'returns a BlockingFlag' do
6
+ flag = BlockingFlag.new
7
+ expect(flag).to be_an_instance_of(BlockingFlag)
8
+ end
9
+ end
10
+
11
+ describe '#set!' do
12
+ let (:flag){ BlockingFlag.new }
13
+ it 'returns true if it was false' do
14
+ expect(flag.set?).to eq false
15
+ expect(flag.set!).to eq true
16
+ expect(flag.set?).to eq true
17
+ end
18
+ it 'returns false if it was already true' do
19
+ flag.set!
20
+ expect(flag.set?).to eq true
21
+ expect(flag.set!).to eq false
22
+ expect(flag.set?).to eq true
23
+ end
24
+ end
25
+
26
+ describe '#reset!' do
27
+ let (:flag){ BlockingFlag.new }
28
+ it 'returns false if it was already false' do
29
+ expect(flag.set?).to eq false
30
+ expect(flag.reset!).to eq false
31
+ expect(flag.set?).to eq false
32
+ end
33
+ it 'returns false if it was true' do
34
+ flag.set!
35
+ expect(flag.set?).to eq true
36
+ expect(flag.reset!).to eq true
37
+ expect(flag.set?).to eq false
38
+ end
39
+ end
40
+
41
+ describe '#set_region' do
42
+ let (:flag){ BlockingFlag.new }
43
+ it 'set in the block and reset it was set' do
44
+ flag.set!
45
+ flag.set_region do
46
+ expect(flag.set?).to eq true
47
+ end
48
+ expect(flag.set?).to eq false
49
+ end
50
+ it 'set in the block and reset if it was reset' do
51
+ flag.reset!
52
+ flag.set_region do
53
+ expect(flag.set?).to eq true
54
+ end
55
+ expect(flag.set?).to eq false
56
+ end
57
+ it 'set in the block and reset even if it raiess error' do
58
+ flag.set_region do
59
+ expect(flag.set?).to eq true
60
+ raise
61
+ end rescue nil
62
+ expect(flag.set?).to eq false
63
+ end
64
+ end
65
+
66
+ describe '#reset_region' do
67
+ let (:flag){ BlockingFlag.new }
68
+ it 'reset in the block and set it was set' do
69
+ flag.set!
70
+ flag.reset_region do
71
+ expect(flag.set?).to eq false
72
+ end
73
+ expect(flag.set?).to eq true
74
+ end
75
+ it 'reset in the block and set if it was reset' do
76
+ flag.reset!
77
+ flag.reset_region do
78
+ expect(flag.set?).to eq false
79
+ end
80
+ expect(flag.set?).to eq true
81
+ end
82
+ it 'set in the block and reset even if it raiess error' do
83
+ flag.reset_region do
84
+ expect(flag.set?).to eq false
85
+ raise
86
+ end rescue nil
87
+ expect(flag.set?).to eq true
88
+ end
89
+ end
90
+
91
+ describe '#wait' do
92
+ let (:flag){ BlockingFlag.new }
93
+ it 'wait until a thread set/reset the flag' do
94
+ th1 = Thread.start do
95
+ flag.wait(5)
96
+ expect(flag.set?).to eq true
97
+ end
98
+ Thread.pass until th1.stop?
99
+ flag.set!
100
+ th1.join(2)
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe PerfectQueue::Client do
4
+ describe '#preempt' do
5
+ it '(key)' do
6
+ backend = double('backend')
7
+ alive_time = double('alive_time')
8
+ object_double('PerfectQueue::Backend', new_backend: backend).as_stubbed_const
9
+ client = Client.new({alive_time: alive_time})
10
+ ret = double('ret')
11
+ key = double('key')
12
+ expect(backend).to receive(:preempt).with(key, alive_time, {}).and_return(ret)
13
+ expect(client.preempt(key)).to eq(ret)
14
+ end
15
+
16
+ it '(key, options)' do
17
+ backend = double('backend')
18
+ alive_time = double('alive_time')
19
+ object_double('PerfectQueue::Backend', new_backend: backend).as_stubbed_const
20
+ client = Client.new({alive_time: alive_time})
21
+ ret = double('ret')
22
+ key = double('key')
23
+ options = {alive_time: alive_time}
24
+ expect(backend).to receive(:preempt).with(key, alive_time, options).and_return(ret)
25
+ expect(client.preempt(key, options)).to eq(ret)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe DaemonsLogger do
4
+ context 'new' do
5
+ it 'creates logger with path string' do
6
+ Tempfile.open('daemons_logger') do |io|
7
+ logger = DaemonsLogger.new(io.path)
8
+ expect(logger.class).to eq(DaemonsLogger)
9
+ logger.close
10
+ logger.close
11
+ end
12
+ end
13
+
14
+ it 'creates logger with IO object' do
15
+ io = double('dummy io', write: nil, close: nil)
16
+ expect(DaemonsLogger.new(io).class).to eq(DaemonsLogger)
17
+ end
18
+ end
19
+
20
+ context 'reopen' do
21
+ it 'reopens IOs' do
22
+ Tempfile.open('daemons_logger') do |f|
23
+ logger = DaemonsLogger.new(f.path)
24
+ expect(STDOUT).to receive(:reopen).twice
25
+ logger.hook_stdout!
26
+ expect(STDERR).to receive(:reopen).twice
27
+ logger.hook_stderr!
28
+ logger.reopen
29
+ io = logger.instance_variable_get(:@log)
30
+ allow(logger).to receive(:reopen!) { raise }
31
+ logger.reopen
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,159 @@
1
+ require 'spec_helper'
2
+
3
+ describe PerfectQueue::Engine do
4
+ let (:logger){ double('logger').as_null_object }
5
+ let (:engine) do
6
+ config = {logger: logger, processor_type: :thread}
7
+ Engine.new(double, config)
8
+ end
9
+
10
+ describe '.new' do
11
+ it 'returns an Engine with ForkProcessor for processor_type: nil' do
12
+ config = {logger: double('logger'), processor_type: nil}
13
+ engine = Engine.new(double, config)
14
+ expect(engine).to be_an_instance_of(Engine)
15
+ expect(engine.processors).to be_a(Array)
16
+ expect(engine.processors.size).to eq(1)
17
+ expect(engine.processors[0]).to be_an_instance_of(Multiprocess::ForkProcessor)
18
+ end
19
+ it 'returns an Engine with ForkProcessor for processor_type: :process' do
20
+ config = {logger: double('logger'), processor_type: :process}
21
+ engine = Engine.new(double, config)
22
+ expect(engine).to be_an_instance_of(Engine)
23
+ expect(engine.processors).to be_a(Array)
24
+ expect(engine.processors.size).to eq(1)
25
+ expect(engine.processors[0]).to be_an_instance_of(Multiprocess::ForkProcessor)
26
+ end
27
+ it 'returns an Engine with ThreadProcessor for processor_type: :thread' do
28
+ config = {logger: double('logger'), processor_type: :thread}
29
+ engine = Engine.new(double, config)
30
+ expect(engine).to be_an_instance_of(Engine)
31
+ expect(engine.processors).to be_a(Array)
32
+ expect(engine.processors.size).to eq(1)
33
+ expect(engine.processors[0]).to be_an_instance_of(Multiprocess::ThreadProcessor)
34
+ end
35
+ it 'returns an Engine with ForkProcessor for processor_type: :invalid' do
36
+ config = {logger: double('logger'), processor_type: :invalid}
37
+ expect{Engine.new(double, config)}.to raise_error(ConfigError)
38
+ end
39
+ end
40
+
41
+ describe '#run' do
42
+ before do
43
+ processor_klass = (PerfectQueue::Multiprocess::ThreadProcessor)
44
+ allow(processor_klass).to receive(:new) do
45
+ processor = double('processor')
46
+ expect(processor).to receive(:keepalive).exactly(:twice)
47
+ expect(processor).to receive(:stop)
48
+ expect(processor).to receive(:join)
49
+ processor
50
+ end
51
+ expect(engine).to receive(:sleep).with(0...2)
52
+ end
53
+ it 'runs until stopped' do
54
+ Thread.start{sleep 1; engine.stop(true) }
55
+ engine.run
56
+ end
57
+ end
58
+
59
+ describe '#restart' do
60
+ context 'previous num_processors is small' do
61
+ it 'increase the number of processors' do
62
+ config = {logger: logger, processor_type: :thread}
63
+ engine = Engine.new(double, config)
64
+ expect(engine.processors.size).to eq(1)
65
+ config[:processors] = 3
66
+ expect(engine.restart(true, config)).to eq(engine)
67
+ expect(engine.processors.size).to eq(3)
68
+ end
69
+ end
70
+ context 'previous num_processors is large' do
71
+ it 'decrease the number of processors' do
72
+ config = {logger: logger, processor_type: :thread, processors: 2}
73
+ engine = Engine.new(double, config)
74
+ config[:processors] = 1
75
+ expect(engine.restart(true, config)).to eq(engine)
76
+ expect(engine.processors.size).to eq(1)
77
+ end
78
+ end
79
+ context 'same number of processors' do
80
+ it 'decrease the number of processors' do
81
+ config = {logger: logger, processor_type: :thread}
82
+ engine = Engine.new(double, config)
83
+ expect(engine.restart(true, config)).to eq(engine)
84
+ expect(engine.processors.size).to eq(1)
85
+ end
86
+ end
87
+ end
88
+
89
+ describe '#stop' do
90
+ let (:immediate){ double('immediate') }
91
+ before do
92
+ engine.processors.each do |c|
93
+ expect(c).to receive(:stop).with(immediate)
94
+ end
95
+ end
96
+ it '@processors.each {|c| c.stop(immediate) }' do
97
+ expect(engine.stop(immediate)).to eq(engine)
98
+ expect(engine.instance_variable_get(:@finish_flag).set?).to be true
99
+ end
100
+ end
101
+
102
+ describe '#join' do
103
+ before do
104
+ engine.processors.each do |c|
105
+ expect(c).to receive(:join)
106
+ end
107
+ end
108
+ it '@processors.each {|c| c.join }' do
109
+ expect(engine.join).to eq(engine)
110
+ end
111
+ end
112
+
113
+ describe '#shutdown' do
114
+ it 'calls stop and join' do
115
+ immediate = double('immediate')
116
+ expect(engine).to receive(:stop).with(immediate)
117
+ expect(engine).to receive(:join)
118
+ engine.shutdown(immediate)
119
+ end
120
+ end
121
+
122
+ describe '#replace' do
123
+ context 'already replaced' do
124
+ before do
125
+ engine.instance_variable_set(:@replaced_pid, double)
126
+ end
127
+ it 'returns nil' do
128
+ expect(engine).not_to receive(:stop)
129
+ expect(engine.replace(double, double)).to be_nil
130
+ end
131
+ end
132
+ context 'not replaced yet' do
133
+ it 'calls spawn with [$0]+ARGV' do
134
+ immediate = double('immediate')
135
+ expect(engine).to receive(:stop).with(immediate)
136
+ expect(Process).to receive(:spawn).with(*([$0]+ARGV))
137
+ engine.replace(immediate)
138
+ end
139
+ it 'calls spawn with given command' do
140
+ immediate = double('immediate')
141
+ command = double('command')
142
+ expect(engine).to receive(:stop).with(immediate)
143
+ expect(Process).to receive(:spawn).with(command)
144
+ engine.replace(immediate, command)
145
+ end
146
+ end
147
+ end
148
+
149
+ describe '#logrotated' do
150
+ before do
151
+ engine.processors.each do |c|
152
+ expect(c).to receive(:logrotated)
153
+ end
154
+ end
155
+ it '@processors.each {|c| c.logrotated }' do
156
+ engine.logrotated
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,300 @@
1
+ require 'spec_helper'
2
+
3
+ describe PerfectQueue::Multiprocess::ChildProcessMonitor do
4
+ let (:rpipe){ double('rpipe') }
5
+ let (:last_heartbeat){ 42 }
6
+ let (:last_kill_time){ 42 }
7
+ let (:processor_id){ double('processor_id') }
8
+ let (:log){ double('log').as_null_object }
9
+ let (:cpm) {
10
+ cpm = Multiprocess::ChildProcessMonitor.new(log, processor_id, rpipe)
11
+ cpm.instance_variable_set(:@last_heartbeat, last_heartbeat)
12
+ cpm
13
+ }
14
+ let (:now){ 72 }
15
+ describe '.new' do
16
+ it 'returns a PerfectQueue::Multiprocess::ChildProcessMonitor' do
17
+ processor = Multiprocess::ChildProcessMonitor.new(log, processor_id, rpipe)
18
+ expect(processor).to be_an_instance_of(Multiprocess::ChildProcessMonitor)
19
+ end
20
+ end
21
+
22
+ describe '#check_heartbeat' do
23
+ before do
24
+ allow(object_double('Time').as_stubbed_const).to \
25
+ receive_message_chain(:now, :to_i).and_return(now)
26
+ end
27
+ context 'rpipe returns value' do
28
+ before do
29
+ expect(rpipe).to receive(:read_nonblock)
30
+ end
31
+ it 'returns true' do
32
+ limit = double('limit')
33
+ expect(cpm.check_heartbeat(limit)).to be true
34
+ expect(cpm.instance_variable_get(:@last_heartbeat)).to eq(now)
35
+ end
36
+ end
37
+ context 'rpipe.read_nonblock raises EINTR' do
38
+ before do
39
+ expect(rpipe).to receive(:read_nonblock).and_raise(Errno::EINTR)
40
+ end
41
+ it 'returns false if last_heartbeat is too old on interupt' do
42
+ expect(cpm.check_heartbeat(now-last_heartbeat-1)).to be false
43
+ expect(cpm.instance_variable_get(:@last_heartbeat)).to eq(last_heartbeat)
44
+ end
45
+ it 'returns true if last_heartbeat is enough new on interupt' do
46
+ expect(cpm.check_heartbeat(now-last_heartbeat)).to be true
47
+ expect(cpm.instance_variable_get(:@last_heartbeat)).to eq(last_heartbeat)
48
+ end
49
+ end
50
+ end
51
+
52
+ describe '#start_killing' do
53
+ before do
54
+ allow(object_double('Time').as_stubbed_const).to \
55
+ receive_message_chain(:now, :to_i).and_return(now)
56
+ end
57
+ context 'initial state' do
58
+ it 'calls kill_children immediately if immediate: true' do
59
+ expect(cpm).to receive(:kill_children).with(now, nil).exactly(:once)
60
+ cpm.start_killing(true)
61
+ expect(cpm.instance_variable_get(:@kill_immediate)).to eq(true)
62
+ expect(cpm.instance_variable_get(:@last_kill_time)).to eq(now)
63
+ expect(cpm.instance_variable_get(:@kill_start_time)).to eq(now)
64
+ end
65
+ it 'sets @last_kill_time if immediate: true, delay!=0' do
66
+ delay = 3
67
+ expect(cpm).not_to receive(:kill_children)
68
+ cpm.start_killing(true, delay)
69
+ expect(cpm.instance_variable_get(:@kill_immediate)).to eq(true)
70
+ expect(cpm.instance_variable_get(:@last_kill_time)).to eq(now+delay)
71
+ expect(cpm.instance_variable_get(:@kill_start_time)).to eq(now+delay)
72
+ end
73
+ end
74
+ context 'already killed immediately' do
75
+ before do
76
+ cpm.instance_variable_set(:@kill_immediate, true)
77
+ cpm.instance_variable_set(:@last_kill_time, now)
78
+ cpm.instance_variable_set(:@kill_start_time, now)
79
+ end
80
+ it 'returns without do anything if immediate: true' do
81
+ expect(cpm).not_to receive(:kill_children)
82
+ cpm.start_killing(true)
83
+ end
84
+ it 'returns without do anything if immediate: false' do
85
+ expect(cpm).not_to receive(:kill_children)
86
+ cpm.start_killing(false)
87
+ end
88
+ end
89
+ context 'already started killing' do
90
+ before do
91
+ cpm.instance_variable_set(:@kill_start_time, double)
92
+ end
93
+ it 'return with do nothing if immediate: false' do
94
+ cpm.start_killing(false, double)
95
+ end
96
+ end
97
+ end
98
+
99
+ describe '#killing_status' do
100
+ context '@kill_start_time: nil' do
101
+ before { cpm.instance_variable_set(:@kill_start_time, nil) }
102
+ it 'returns nil' do
103
+ expect(cpm.killing_status).to be_nil
104
+ end
105
+ end
106
+ context '@kill_start_time: <time>' do
107
+ before { cpm.instance_variable_set(:@kill_start_time, double) }
108
+ context '@kill_immediate: true' do
109
+ before { cpm.instance_variable_set(:@kill_immediate, true) }
110
+ it 'returns nil' do
111
+ expect(cpm.killing_status).to be true
112
+ end
113
+ end
114
+ context '@kill_immediate: false' do
115
+ before { cpm.instance_variable_set(:@kill_immediate, false) }
116
+ it 'returns nil' do
117
+ expect(cpm.killing_status).to be false
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ describe '#try_join' do
124
+ context 'not killed yet' do
125
+ it 'returns nil' do
126
+ expect(cpm).not_to receive(:kill_children)
127
+ expect(cpm.try_join(double, double)).to be_nil
128
+ end
129
+ end
130
+ context 'killing' do
131
+ let (:cProcess) do
132
+ allow(Process).to receive(:waitpid).with(processor_id, Process::WNOHANG)
133
+ end
134
+ before do
135
+ cpm.instance_variable_set(:@kill_start_time, double)
136
+ end
137
+ context 'waitpid returns pid' do
138
+ before do
139
+ cProcess.and_return(processor_id)
140
+ expect(cpm).not_to receive(:kill_children)
141
+ end
142
+ it 'returns true' do
143
+ expect(cpm.try_join(double, double)).to be true
144
+ end
145
+ end
146
+ context 'waitpid raises ECHILD' do
147
+ before do
148
+ cProcess.and_raise(Errno::ECHILD)
149
+ expect(cpm).not_to receive(:kill_children)
150
+ end
151
+ it 'returns true' do
152
+ expect(cpm.try_join(double, double)).to be true
153
+ end
154
+ end
155
+ context 'waitpid returns nil' do
156
+ before do
157
+ cProcess.and_return(nil)
158
+ allow(object_double('Time').as_stubbed_const).to \
159
+ receive_message_chain(:now, :to_i).and_return(now)
160
+ cpm.instance_variable_set(:@last_kill_time, last_kill_time)
161
+ end
162
+ it 'returns true if last_kill_time is new' do
163
+ graceful_kill_limit = double('graceful_kill_limit')
164
+ expect(cpm).to receive(:kill_children).with(now, graceful_kill_limit).exactly(:once)
165
+ expect(cpm.try_join(30, graceful_kill_limit)).to be false
166
+ expect(cpm.instance_variable_get(:@last_kill_time)).to eq(now)
167
+ end
168
+ it 'returns false if last_kill_time is old' do
169
+ expect(cpm).not_to receive(:kill_children)
170
+ expect(cpm.try_join(31, double)).to be false
171
+ end
172
+ end
173
+ end
174
+ end
175
+
176
+ describe '#cleanup' do
177
+ context 'rpipe is open' do
178
+ it 'closes rpipe' do
179
+ allow(rpipe).to receive(:closed?).and_return(false)
180
+ expect(rpipe).to receive(:close).exactly(:once)
181
+ cpm.cleanup
182
+ end
183
+ end
184
+ context 'rpipe is closed' do
185
+ it 'doesn\'t close rpipe' do
186
+ allow(rpipe).to receive(:closed?).and_return(true)
187
+ expect(rpipe).not_to receive(:close)
188
+ cpm.cleanup
189
+ end
190
+ end
191
+ end
192
+
193
+ describe '#send_signal' do
194
+ let (:sig){ double('sig') }
195
+ let (:cProcess) do
196
+ allow(Process).to receive(:kill).with(sig, processor_id)
197
+ end
198
+ context 'kill returnes pid' do
199
+ before do
200
+ cProcess.and_return(processor_id)
201
+ end
202
+ it { cpm.send_signal(sig) }
203
+ end
204
+ context 'kill raises ESRCH' do
205
+ before{ cProcess.and_raise(Errno::ESRCH) }
206
+ it { cpm.send_signal(sig) }
207
+ end
208
+ context 'kill raises EPERM' do
209
+ before{ cProcess.and_raise(Errno::EPERM) }
210
+ it { cpm.send_signal(sig) }
211
+ end
212
+ end
213
+
214
+ describe '#kill_children' do
215
+ context '@kill_start_time: nil' do
216
+ # don't happen
217
+ end
218
+ context '@kill_start_time: <time>' do
219
+ before do
220
+ cpm.instance_variable_set(:@kill_start_time, 42)
221
+ end
222
+ context '@kill_immediate: true' do
223
+ before do
224
+ cpm.instance_variable_set(:@kill_immediate, true)
225
+ expect(cpm).to receive(:get_ppid_pids_map).with(no_args).and_return({1=>processor_id}).exactly(:once)
226
+ expect(cpm).to receive(:collect_child_pids).with({1=>processor_id}, [processor_id], processor_id) \
227
+ .and_return([processor_id]).exactly(:once)
228
+ expect(cpm).to receive(:kill_process).with(processor_id, true)
229
+ end
230
+ it 'calls kill_process immediately' do
231
+ cpm.__send__(:kill_children, now, double)
232
+ end
233
+ end
234
+ context '@kill_immediate: false' do
235
+ before do
236
+ cpm.instance_variable_set(:@kill_immediate, false)
237
+ end
238
+ it 'calls kill_process immediately' do
239
+ expect(cpm).to receive(:get_ppid_pids_map).with(no_args).and_return({1=>processor_id}).exactly(:once)
240
+ expect(cpm).to receive(:collect_child_pids).with({1=>processor_id}, [processor_id], processor_id) \
241
+ .and_return([processor_id]).exactly(:once)
242
+ expect(cpm).to receive(:kill_process).with(processor_id, true)
243
+ cpm.__send__(:kill_children, now, 29)
244
+ end
245
+ it 'calls kill_process' do
246
+ expect(cpm).not_to receive(:get_ppid_pids_map)
247
+ expect(cpm).not_to receive(:collect_child_pids)
248
+ expect(cpm).to receive(:kill_process).with(processor_id, false)
249
+ cpm.__send__(:kill_children, now, 30)
250
+ end
251
+ end
252
+ end
253
+ end
254
+
255
+ describe '#get_ppid_pids_map' do
256
+ before do
257
+ expect(cpm).to receive(:`).with('ps axo pid,ppid') \
258
+ .and_return <<eom
259
+ PID PPID
260
+ 1 0
261
+ 2 1
262
+ 3 1
263
+ 4 2
264
+ 5 3
265
+ eom
266
+ end
267
+ it 'returns a tree of hash' do
268
+ expect(cpm.__send__(:get_ppid_pids_map)).to eq({0=>[1], 1=>[2, 3], 2=>[4], 3=>[5]})
269
+ end
270
+ end
271
+
272
+ describe '#collect_child_pids' do
273
+ it 'returns a flat array of given children' do
274
+ ppid_pids = {0=>[1], 1=>[2, 3], 2=>[4], 3=>[5]}
275
+ parent_pid = 1
276
+ results = cpm.__send__(:collect_child_pids, ppid_pids, [parent_pid], parent_pid)
277
+ expect(results).to eq([1, 2, 4, 3, 5])
278
+ end
279
+ end
280
+
281
+ describe '#kill_process' do
282
+ let (:pid){ double('pid') }
283
+ it 'kill(:KILL, pid) for immediate:true' do
284
+ expect(Process).to receive(:kill).with(:KILL, pid).and_return(pid).exactly(:once)
285
+ expect(cpm.__send__(:kill_process, pid, true)).to eq(pid)
286
+ end
287
+ it 'kill(:TERM, pid) for immediate:false' do
288
+ expect(Process).to receive(:kill).with(:TERM, pid).and_return(pid).exactly(:once)
289
+ expect(cpm.__send__(:kill_process, pid, false)).to eq(pid)
290
+ end
291
+ it 'rescues ESRCH' do
292
+ expect(Process).to receive(:kill).with(:KILL, pid).and_raise(Errno::ESRCH).exactly(:once)
293
+ expect(cpm.__send__(:kill_process, pid, true)).to be_nil
294
+ end
295
+ it 'rescues EPERM' do
296
+ expect(Process).to receive(:kill).with(:KILL, pid).and_raise(Errno::EPERM).exactly(:once)
297
+ expect(cpm.__send__(:kill_process, pid, true)).to be_nil
298
+ end
299
+ end
300
+ end