perfectqueue 0.8.44.1 → 0.8.45

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,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