• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2#
3#   Copyright 2018 - The Android Open Source Project
4#
5#   Licensed under the Apache License, Version 2.0 (the "License");
6#   you may not use this file except in compliance with the License.
7#   You may obtain a copy of the License at
8#
9#       http://www.apache.org/licenses/LICENSE-2.0
10#
11#   Unless required by applicable law or agreed to in writing, software
12#   distributed under the License is distributed on an "AS IS" BASIS,
13#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14#   See the License for the specific language governing permissions and
15#   limitations under the License.
16import unittest
17
18import mock
19import subprocess
20from acts.libs.proc.process import Process
21from acts.libs.proc.process import ProcessError
22
23class FakeThread(object):
24    def __init__(self, target=None):
25        self.target = target
26        self.alive = False
27
28    def _on_start(self):
29        pass
30
31    def start(self):
32        self.alive = True
33        if self._on_start:
34            self._on_start()
35
36    def stop(self):
37        self.alive = False
38
39    def join(self):
40        pass
41
42
43class ProcessTest(unittest.TestCase):
44    """Tests the acts.libs.proc.process.Process class."""
45
46    def setUp(self):
47        self._Process__start_process = Process._Process__start_process
48
49    def tearDown(self):
50        Process._Process__start_process = self._Process__start_process
51
52    @staticmethod
53    def patch(imported_name, *args, **kwargs):
54        return mock.patch('acts.libs.proc.process.%s' % imported_name,
55                          *args, **kwargs)
56
57    # set_on_output_callback
58
59    def test_set_on_output_callback(self):
60        """Tests that set_on_output_callback sets on_output_callback."""
61        callback = mock.Mock()
62
63        process = Process('cmd').set_on_output_callback(callback)
64        process._on_output_callback()
65
66        self.assertTrue(callback.called)
67
68    # set_on_terminate_callback
69
70    def test_set_on_terminate_callback(self):
71        """Tests that set_on_terminate_callback sets _on_terminate_callback."""
72        callback = mock.Mock()
73
74        process = Process('cmd').set_on_terminate_callback(callback)
75        process._on_terminate_callback()
76
77        self.assertTrue(callback.called)
78
79    # start
80
81    def test_start_raises_if_called_back_to_back(self):
82        """Tests that start raises an exception if it has already been called
83        prior.
84
85        This is required to prevent references to processes and threads from
86        being overwritten, potentially causing ACTS to hang."""
87        process = Process('cmd')
88
89        # Here we need the thread to start the process object.
90        class FakeThreadImpl(FakeThread):
91            def _on_start(self):
92                process._process = mock.Mock()
93
94        with self.patch('Thread', FakeThreadImpl):
95            process.start()
96            expected_msg = 'Process has already started.'
97            with self.assertRaisesRegex(ProcessError, expected_msg):
98                process.start()
99
100    def test_start_starts_listening_thread(self):
101        """Tests that start starts the _exec_popen_loop function."""
102        process = Process('cmd')
103
104        # Here we need the thread to start the process object.
105        class FakeThreadImpl(FakeThread):
106            def _on_start(self):
107                process._process = mock.Mock()
108
109        with self.patch('Thread', FakeThreadImpl):
110            process.start()
111
112        self.assertTrue(process._listening_thread.alive)
113        self.assertEqual(process._listening_thread.target, process._exec_loop)
114
115    # wait
116
117    def test_wait_raises_if_called_back_to_back(self):
118        """Tests that wait raises an exception if it has already been called
119        prior."""
120        process = Process('cmd')
121        process._process = mock.Mock()
122
123        process.wait(0)
124        expected_msg = 'Process is already being stopped.'
125        with self.assertRaisesRegex(ProcessError, expected_msg):
126            process.wait(0)
127
128    @mock.patch.object(Process, '_kill_process')
129    def test_wait_kills_after_timeout(self, *_):
130        """Tests that if a TimeoutExpired error is thrown during wait, the
131        process is killed."""
132        process = Process('cmd')
133        process._process = mock.Mock()
134        process._process.wait.side_effect = subprocess.TimeoutExpired('', '')
135
136        process.wait(0)
137
138        self.assertEqual(process._kill_process.called, True)
139
140    @mock.patch.object(Process, '_kill_process')
141    def test_wait_sets_stopped_to_true_before_process_kill(self, *_):
142        """Tests that stop() sets the _stopped attribute to True.
143
144        This order is required to prevent the _exec_loop from calling
145        _on_terminate_callback when the user has killed the process.
146        """
147        verifier = mock.Mock()
148        verifier.passed = False
149
150        def test_call_order():
151            self.assertTrue(process._stopped)
152            verifier.passed = True
153
154        process = Process('cmd')
155        process._process = mock.Mock()
156        process._process.poll.return_value = None
157        process._process.wait.side_effect = subprocess.TimeoutExpired('', '')
158        process._kill_process = test_call_order
159
160        process.wait()
161
162        self.assertEqual(verifier.passed, True)
163
164    def test_wait_joins_listening_thread_if_it_exists(self):
165        """Tests wait() joins _listening_thread if it exists."""
166        process = Process('cmd')
167        process._process = mock.Mock()
168        mocked_thread = mock.Mock()
169        process._listening_thread = mocked_thread
170
171        process.wait(0)
172
173        self.assertEqual(mocked_thread.join.called, True)
174
175    def test_wait_clears_listening_thread_if_it_exists(self):
176        """Tests wait() joins _listening_thread if it exists.
177
178        Threads can only be started once, so after wait has been called, we
179        want to make sure we clear the listening thread.
180        """
181        process = Process('cmd')
182        process._process = mock.Mock()
183        process._listening_thread = mock.Mock()
184
185        process.wait(0)
186
187        self.assertEqual(process._listening_thread, None)
188
189    def test_wait_joins_redirection_thread_if_it_exists(self):
190        """Tests wait() joins _listening_thread if it exists."""
191        process = Process('cmd')
192        process._process = mock.Mock()
193        mocked_thread = mock.Mock()
194        process._redirection_thread = mocked_thread
195
196        process.wait(0)
197
198        self.assertEqual(mocked_thread.join.called, True)
199
200    def test_wait_clears_redirection_thread_if_it_exists(self):
201        """Tests wait() joins _listening_thread if it exists.
202
203        Threads can only be started once, so after wait has been called, we
204        want to make sure we clear the listening thread.
205        """
206        process = Process('cmd')
207        process._process = mock.Mock()
208        process._redirection_thread = mock.Mock()
209
210        process.wait(0)
211
212        self.assertEqual(process._redirection_thread, None)
213
214    # stop
215
216    def test_stop_sets_stopped_to_true(self):
217        """Tests that stop() sets the _stopped attribute to True."""
218        process = Process('cmd')
219        process._process = mock.Mock()
220
221        process.stop()
222
223        self.assertTrue(process._stopped)
224
225    def test_stop_sets_stopped_to_true_before_process_kill(self):
226        """Tests that stop() sets the _stopped attribute to True.
227
228        This order is required to prevent the _exec_loop from calling
229        _on_terminate_callback when the user has killed the process.
230        """
231        verifier = mock.Mock()
232        verifier.passed = False
233
234        def test_call_order():
235            self.assertTrue(process._stopped)
236            verifier.passed = True
237
238        process = Process('cmd')
239        process._process = mock.Mock()
240        process._process.poll.return_value = None
241        process._kill_process = test_call_order
242        process._process.wait.side_effect = subprocess.TimeoutExpired('', '')
243
244        process.stop()
245
246        self.assertEqual(verifier.passed, True)
247
248    def test_stop_calls_wait(self):
249        """Tests that stop() also has the functionality of wait()."""
250        process = Process('cmd')
251        process._process = mock.Mock()
252        process.wait = mock.Mock()
253
254        process.stop()
255
256        self.assertEqual(process.wait.called, True)
257
258    # _redirect_output
259
260    def test_redirect_output_feeds_all_lines_to_on_output_callback(self):
261        """Tests that _redirect_output loops until all lines are parsed."""
262        received_list = []
263
264        def appender(line):
265            received_list.append(line)
266
267        process = Process('cmd')
268        process.set_on_output_callback(appender)
269        process._process = mock.Mock()
270        process._process.stdout.readline.side_effect = [b'a\n', b'b\n', b'']
271
272        process._redirect_output()
273
274        self.assertEqual(received_list[0], 'a')
275        self.assertEqual(received_list[1], 'b')
276        self.assertEqual(len(received_list), 2)
277
278    # __start_process
279
280    def test_start_process_returns_a_popen_object(self):
281        """Tests that a Popen object is returned by __start_process."""
282        with self.patch('subprocess.Popen', return_value='verification'):
283            self.assertEqual(Process._Process__start_process('cmd'),
284                             'verification')
285
286    # _exec_loop
287
288    def test_exec_loop_redirections_output(self):
289        """Tests that the _exec_loop function calls to redirect the output."""
290        process = Process('cmd')
291        Process._Process__start_process = mock.Mock()
292
293        with self.patch('Thread', FakeThread):
294            process._exec_loop()
295
296        self.assertEqual(process._redirection_thread.target,
297                         process._redirect_output)
298        self.assertEqual(process._redirection_thread.alive, True)
299
300    def test_exec_loop_waits_for_process(self):
301        """Tests that the _exec_loop waits for the process to complete before
302        returning."""
303        process = Process('cmd')
304        Process._Process__start_process = mock.Mock()
305
306        with self.patch('Thread', FakeThread):
307            process._exec_loop()
308
309        self.assertEqual(process._process.wait.called, True)
310
311    def test_exec_loop_loops_if_not_stopped(self):
312        process = Process('1st')
313        Process._Process__start_process = mock.Mock()
314        process._on_terminate_callback = mock.Mock(side_effect=[['2nd'], None])
315
316        with self.patch('Thread', FakeThread):
317            process._exec_loop()
318
319        self.assertEqual(Process._Process__start_process.call_count, 2)
320        self.assertEqual(Process._Process__start_process.call_args_list[0][0],
321                         (['1st'], ))
322        self.assertEqual(Process._Process__start_process.call_args_list[1][0],
323                         (['2nd'], ))
324
325    def test_exec_loop_does_not_loop_if_stopped(self):
326        process = Process('1st')
327        Process._Process__start_process = mock.Mock()
328        process._on_terminate_callback = mock.Mock(
329            side_effect=['2nd', None])
330        process._stopped = True
331
332        with self.patch('Thread', FakeThread):
333            process._exec_loop()
334
335        self.assertEqual(Process._Process__start_process.call_count, 1)
336        self.assertEqual(
337            Process._Process__start_process.call_args_list[0][0],
338            (['1st'],))
339
340
341if __name__ == '__main__':
342    unittest.main()
343