1#!/usr/bin/env python3
2#
3#   Copyright 2019 - 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 logging
17import unittest
18
19import mock
20import os
21
22from acts.controllers import iperf_server
23from acts.controllers.iperf_server import IPerfServer
24from acts.controllers.iperf_server import IPerfServerOverAdb
25from acts.controllers.iperf_server import IPerfServerOverSsh
26
27# The position in the call tuple that represents the args array.
28ARGS = 0
29
30# The position in the call tuple that represents the kwargs dict.
31KWARGS = 1
32
33MOCK_LOGFILE_PATH = '/path/to/foo'
34
35
36class IPerfServerModuleTest(unittest.TestCase):
37    """Tests the acts.controllers.iperf_server module."""
38
39    def test_create_creates_local_iperf_server_with_int(self):
40        self.assertIsInstance(
41            iperf_server.create([12345])[0],
42            IPerfServer,
43            'create() failed to create IPerfServer for integer input.'
44        )
45
46    def test_create_creates_local_iperf_server_with_str(self):
47        self.assertIsInstance(
48            iperf_server.create(['12345'])[0],
49            IPerfServer,
50            'create() failed to create IPerfServer for integer input.'
51        )
52
53    def test_create_cannot_create_local_iperf_server_with_bad_str(self):
54        with self.assertRaises(ValueError):
55            iperf_server.create(['12345BAD_STRING'])
56
57    @mock.patch('acts.controllers.iperf_server.utils')
58    def test_create_creates_server_over_ssh_with_ssh_config_and_port(self, _):
59        self.assertIsInstance(
60            iperf_server.create([{'ssh_config': {'user': '', 'host': ''},
61                                  'port': ''}])[0],
62            IPerfServerOverSsh,
63            'create() failed to create IPerfServerOverSsh for a valid config.'
64        )
65
66    def test_create_creates_server_over_adb_with_proper_config(self):
67        self.assertIsInstance(
68            iperf_server.create([{'AndroidDevice': '53R147', 'port': 0}])[0],
69            IPerfServerOverAdb,
70            'create() failed to create IPerfServerOverAdb for a valid config.'
71        )
72
73    def test_create_raises_value_error_on_bad_config_dict(self):
74        with self.assertRaises(ValueError):
75            iperf_server.create([{'AndroidDevice': '53R147', 'ssh_config': {}}])
76
77    def test_get_port_from_ss_output_returns_correct_port_ipv4(self):
78        ss_output = ('tcp LISTEN  0 5 127.0.0.1:<PORT>  *:*'
79                     ' users:(("cmd",pid=<PID>,fd=3))')
80        self.assertEqual(
81            iperf_server._get_port_from_ss_output(ss_output, '<PID>'), '<PORT>')
82
83    def test_get_port_from_ss_output_returns_correct_port_ipv6(self):
84        ss_output = ('tcp LISTEN  0 5 ff:ff:ff:ff:ff:ff:<PORT>  *:*'
85                     ' users:(("cmd",pid=<PID>,fd=3))')
86        self.assertEqual(
87            iperf_server._get_port_from_ss_output(ss_output, '<PID>'), '<PORT>')
88
89
90class IPerfServerBaseTest(unittest.TestCase):
91    """Tests acts.controllers.iperf_server.IPerfServerBase."""
92
93    @mock.patch('os.makedirs')
94    def test_get_full_file_path_creates_parent_directory(self, mock_makedirs):
95        # Will never actually be created/used.
96        logging.log_path = '/tmp/unit_test_garbage'
97
98        server = IPerfServer('port')
99
100        full_file_path = server._get_full_file_path()
101
102        self.assertTrue(
103            mock_makedirs.called,
104            'Did not attempt to create a directory.'
105        )
106        self.assertEqual(
107            os.path.dirname(full_file_path),
108            mock_makedirs.call_args[ARGS][0],
109            'The parent directory of the full file path was not created.'
110        )
111
112
113class IPerfServerTest(unittest.TestCase):
114    """Tests acts.controllers.iperf_server.IPerfServer."""
115
116    PID = 123456
117
118    def setUp(self):
119        iperf_server._get_port_from_ss_output = lambda *_: IPerfServerTest.PID
120
121    @mock.patch('builtins.open')
122    @mock.patch('acts.controllers.iperf_server.subprocess')
123    @mock.patch('acts.controllers.iperf_server.job')
124    def test_start_makes_started_true(self, mock_job, __, ___):
125        """Tests calling start() without calling stop() makes started True."""
126        server = IPerfServer('port')
127        server._get_full_file_path = lambda _: MOCK_LOGFILE_PATH
128        server.start()
129
130        self.assertTrue(server.started)
131
132    @mock.patch('builtins.open')
133    @mock.patch('acts.controllers.iperf_server.subprocess')
134    @mock.patch('acts.controllers.iperf_server.job')
135    def test_start_stop_makes_started_false(self, _, __, ___):
136        """Tests calling start() without calling stop() makes started True."""
137        server = IPerfServer('port')
138        server._get_full_file_path = lambda _: MOCK_LOGFILE_PATH
139
140        server.start()
141        server.stop()
142
143        self.assertFalse(server.started)
144
145    @mock.patch('builtins.open')
146    @mock.patch('acts.controllers.iperf_server.subprocess')
147    @mock.patch('acts.controllers.iperf_server.job')
148    def test_start_sets_current_log_file(self, _, __, ___):
149        server = IPerfServer('port')
150        server._get_full_file_path = lambda _: MOCK_LOGFILE_PATH
151
152        server.start()
153
154        self.assertEqual(
155            server._current_log_file,
156            MOCK_LOGFILE_PATH,
157            'The _current_log_file was not received from _get_full_file_path.'
158        )
159
160    @mock.patch('builtins.open')
161    @mock.patch('acts.controllers.iperf_server.subprocess')
162    def test_stop_returns_current_log_file(self, _, __):
163        server = IPerfServer('port')
164        server._get_full_file_path = lambda _: MOCK_LOGFILE_PATH
165        server._current_log_file = MOCK_LOGFILE_PATH
166        server._iperf_process = mock.Mock()
167
168        log_file = server.stop()
169
170        self.assertEqual(
171            log_file,
172            MOCK_LOGFILE_PATH,
173            'The _current_log_file was not returned by stop().'
174        )
175
176    @mock.patch('builtins.open')
177    @mock.patch('acts.controllers.iperf_server.subprocess')
178    @mock.patch('acts.controllers.iperf_server.job')
179    def test_start_does_not_run_two_concurrent_processes(self, start_proc, _, __):
180        server = IPerfServer('port')
181        server._get_full_file_path = lambda _: MOCK_LOGFILE_PATH
182        server._iperf_process = mock.Mock()
183
184        server.start()
185
186        self.assertFalse(
187            start_proc.called,
188            'start() should not begin a second process if another is running.'
189        )
190
191    @mock.patch('acts.utils.stop_standing_subprocess')
192    def test_stop_exits_early_if_no_process_has_started(self, stop_proc):
193        server = IPerfServer('port')
194        server._get_full_file_path = lambda _: MOCK_LOGFILE_PATH
195        server._iperf_process = None
196
197        server.stop()
198
199        self.assertFalse(
200            stop_proc.called,
201            'stop() should not kill a process if no process is running.'
202        )
203
204
205class IPerfServerOverSshTest(unittest.TestCase):
206    """Tests acts.controllers.iperf_server.IPerfServerOverSsh."""
207
208    INIT_ARGS = [{'host': 'TEST_HOST', 'user': 'test'}, 'PORT']
209
210    @mock.patch('acts.controllers.iperf_server.connection')
211    def test_start_makes_started_true(self, _):
212        """Tests calling start() without calling stop() makes started True."""
213        server = IPerfServerOverSsh(*self.INIT_ARGS)
214        server._ssh_session = mock.Mock()
215        server._get_full_file_path = lambda _: MOCK_LOGFILE_PATH
216
217        server.start()
218
219        self.assertTrue(server.started)
220
221    @mock.patch('builtins.open')
222    @mock.patch('acts.controllers.iperf_server.connection')
223    def test_start_stop_makes_started_false(self, _, __):
224        """Tests calling start() without calling stop() makes started True."""
225        server = IPerfServerOverSsh(*self.INIT_ARGS)
226        server._ssh_session = mock.Mock()
227        server._get_full_file_path = lambda _: MOCK_LOGFILE_PATH
228
229        server.start()
230        server.stop()
231
232        self.assertFalse(server.started)
233
234    @mock.patch('builtins.open')
235    @mock.patch('acts.controllers.iperf_server.connection')
236    def test_stop_returns_expected_log_file(self, _, __):
237        server = IPerfServerOverSsh(*self.INIT_ARGS)
238        server._ssh_session = mock.Mock()
239        server._get_full_file_path = lambda _: MOCK_LOGFILE_PATH
240        server._iperf_pid = mock.Mock()
241
242        log_file = server.stop()
243
244        self.assertEqual(
245            log_file,
246            MOCK_LOGFILE_PATH,
247            'The expected log file was not returned by stop().'
248        )
249
250    @mock.patch('acts.controllers.iperf_server.connection')
251    def test_start_does_not_run_two_concurrent_processes(self, _):
252        server = IPerfServerOverSsh(*self.INIT_ARGS)
253        server._ssh_session = mock.Mock()
254        server._get_full_file_path = lambda _: MOCK_LOGFILE_PATH
255        server._iperf_pid = mock.Mock()
256
257        server.start()
258
259        self.assertFalse(
260            server._ssh_session.run_async.called,
261            'start() should not begin a second process if another is running.'
262        )
263
264    @mock.patch('acts.utils.stop_standing_subprocess')
265    @mock.patch('acts.controllers.iperf_server.connection')
266    def test_stop_exits_early_if_no_process_has_started(self, _, __):
267        server = IPerfServerOverSsh(*self.INIT_ARGS)
268        server._ssh_session = mock.Mock()
269        server._get_full_file_path = lambda _: MOCK_LOGFILE_PATH
270        server._iperf_pid = None
271
272        server.stop()
273
274        self.assertFalse(
275            server._ssh_session.run_async.called,
276            'stop() should not kill a process if no process is running.'
277        )
278
279
280class IPerfServerOverAdbTest(unittest.TestCase):
281    """Tests acts.controllers.iperf_server.IPerfServerOverSsh."""
282
283    ANDROID_DEVICE_PROP = ('acts.controllers.iperf_server.'
284                           'IPerfServerOverAdb._android_device')
285
286    @mock.patch(ANDROID_DEVICE_PROP)
287    def test_start_makes_started_true(self, mock_ad):
288        """Tests calling start() without calling stop() makes started True."""
289        server = IPerfServerOverAdb('53R147', 'PORT')
290        server._get_full_file_path = lambda _: MOCK_LOGFILE_PATH
291        mock_ad.adb.shell.return_value = '<PID>'
292
293        server.start()
294
295        self.assertTrue(server.started)
296
297    @mock.patch('acts.libs.proc.job.run')
298    @mock.patch('builtins.open')
299    @mock.patch(ANDROID_DEVICE_PROP)
300    def test_start_stop_makes_started_false(self, mock_ad, _, __):
301        """Tests calling start() without calling stop() makes started True."""
302        server = IPerfServerOverAdb('53R147', 'PORT')
303        server._get_full_file_path = lambda _: MOCK_LOGFILE_PATH
304        mock_ad.adb.shell.side_effect = ['<PID>', '', '', '']
305
306        server.start()
307        server.stop()
308
309        self.assertFalse(server.started)
310
311    @mock.patch('acts.libs.proc.job.run')
312    @mock.patch('builtins.open')
313    @mock.patch(ANDROID_DEVICE_PROP)
314    def test_stop_returns_expected_log_file(self, mock_ad, _, __):
315        server = IPerfServerOverAdb('53R147', 'PORT')
316        server._get_full_file_path = lambda _: MOCK_LOGFILE_PATH
317        server._iperf_process = mock.Mock()
318        server._iperf_process_adb_pid = '<PID>'
319        mock_ad.adb.shell.side_effect = ['', '', '']
320
321        log_file = server.stop()
322
323        self.assertEqual(
324            log_file,
325            MOCK_LOGFILE_PATH,
326            'The expected log file was not returned by stop().'
327        )
328
329    @mock.patch(ANDROID_DEVICE_PROP)
330    def test_start_does_not_run_two_concurrent_processes(self, android_device):
331        server = IPerfServerOverAdb('53R147', 'PORT')
332        server._get_full_file_path = lambda _: MOCK_LOGFILE_PATH
333        server._iperf_process = mock.Mock()
334
335        server.start()
336
337        self.assertFalse(
338            android_device.adb.shell_nb.called,
339            'start() should not begin a second process if another is running.'
340        )
341
342    @mock.patch('acts.libs.proc.job.run')
343    @mock.patch('builtins.open')
344    @mock.patch(ANDROID_DEVICE_PROP)
345    def test_stop_exits_early_if_no_process_has_started(self, android_device, _,
346                                                        __):
347        server = IPerfServerOverAdb('53R147', 'PORT')
348        server._get_full_file_path = lambda _: MOCK_LOGFILE_PATH
349        server._iperf_pid = None
350
351        server.stop()
352
353        self.assertFalse(
354            android_device.adb.shell_nb.called,
355            'stop() should not kill a process if no process is running.'
356        )
357
358
359if __name__ == '__main__':
360    unittest.main()
361