1#!/usr/bin/env python3
2#
3#   Copyright 2016 - 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.
16
17import logging
18import mock
19import os
20import shutil
21import tempfile
22import unittest
23
24from acts import logger
25from acts.controllers import android_device
26
27# Mock log path for a test run.
28MOCK_LOG_PATH = "/tmp/logs/MockTest/xx-xx-xx_xx-xx-xx/"
29
30# Mock start and end time of the adb cat.
31MOCK_ADB_LOGCAT_BEGIN_TIME = "1970-01-02 21:03:20.123"
32MOCK_ADB_LOGCAT_END_TIME = "1970-01-02 21:22:02.000"
33MOCK_ADB_EPOCH_BEGIN_TIME = 191000123
34
35MOCK_SERIAL = 1
36MOCK_RELEASE_BUILD_ID = "ABC1.123456.007"
37MOCK_DEV_BUILD_ID = "ABC-MR1"
38MOCK_NYC_BUILD_ID = "N4F27P"
39
40
41def get_mock_ads(num):
42    """Generates a list of mock AndroidDevice objects.
43
44    The serial number of each device will be integer 0 through num - 1.
45
46    Args:
47        num: An integer that is the number of mock AndroidDevice objects to
48            create.
49    """
50    ads = []
51    for i in range(num):
52        ad = mock.MagicMock(name="AndroidDevice", serial=i, h_port=None)
53        ad.ensure_screen_on = mock.MagicMock(return_value=True)
54        ads.append(ad)
55    return ads
56
57
58def mock_get_all_instances():
59    return get_mock_ads(5)
60
61
62def mock_list_adb_devices():
63    return [ad.serial for ad in get_mock_ads(5)]
64
65
66class MockAdbProxy(object):
67    """Mock class that swaps out calls to adb with mock calls."""
68
69    def __init__(self,
70                 serial,
71                 fail_br=False,
72                 fail_br_before_N=False,
73                 build_id=MOCK_RELEASE_BUILD_ID):
74        self.serial = serial
75        self.fail_br = fail_br
76        self.fail_br_before_N = fail_br_before_N
77        self.return_value = None
78        self.return_multiple = False
79        self.build_id = build_id
80
81    def shell(self, params, ignore_status=False, timeout=60):
82        if params == "id -u":
83            return "root"
84        elif params == "bugreportz":
85            if self.fail_br:
86                return "OMG I died!\n"
87            return "OK:/path/bugreport.zip\n"
88        elif params == "bugreportz -v":
89            if self.fail_br_before_N:
90                return "/system/bin/sh: bugreportz: not found"
91            return "1.1"
92        else:
93            if self.return_multiple:
94                return self.return_value.pop(0)
95            else:
96                return self.return_value
97
98    def getprop(self, params):
99        if params == "ro.build.id":
100            return self.build_id
101        elif params == "ro.build.version.incremental":
102            return "123456789"
103        elif params == "ro.build.type":
104            return "userdebug"
105        elif params == "ro.build.product" or params == "ro.product.name":
106            return "FakeModel"
107        elif params == "sys.boot_completed":
108            return "1"
109
110    def devices(self):
111        return "\t".join([str(self.serial), "device"])
112
113    def bugreport(self, params, timeout=android_device.BUG_REPORT_TIMEOUT):
114        expected = os.path.join(
115            logging.log_path, "AndroidDevice%s" % self.serial,
116            "test_something", "AndroidDevice%s_%s" %
117            (self.serial,
118             logger.normalize_log_line_timestamp(MOCK_ADB_LOGCAT_BEGIN_TIME)))
119        assert expected in params, "Expected '%s', got '%s'." % (expected,
120                                                                 params)
121
122    def __getattr__(self, name):
123        """All calls to the none-existent functions in adb proxy would
124        simply return the adb command string.
125        """
126
127        def adb_call(*args, **kwargs):
128            arg_str = ' '.join(str(elem) for elem in args)
129            return arg_str
130
131        return adb_call
132
133
134class MockFastbootProxy():
135    """Mock class that swaps out calls to adb with mock calls."""
136
137    def __init__(self, serial):
138        self.serial = serial
139
140    def devices(self):
141        return "xxxx\tdevice\nyyyy\tdevice"
142
143    def __getattr__(self, name):
144        def fastboot_call(*args):
145            arg_str = ' '.join(str(elem) for elem in args)
146            return arg_str
147
148        return fastboot_call
149
150
151class ActsAndroidDeviceTest(unittest.TestCase):
152    """This test class has unit tests for the implementation of everything
153    under acts.controllers.android_device.
154    """
155
156    def setUp(self):
157        # Set log_path to logging since acts logger setup is not called.
158        if not hasattr(logging, "log_path"):
159            setattr(logging, "log_path", "/tmp/logs")
160        # Creates a temp dir to be used by tests in this test class.
161        self.tmp_dir = tempfile.mkdtemp()
162
163    def tearDown(self):
164        """Removes the temp dir.
165        """
166        shutil.rmtree(self.tmp_dir)
167
168    # Tests for android_device module functions.
169    # These tests use mock AndroidDevice instances.
170
171    @mock.patch.object(
172        android_device, "get_all_instances", new=mock_get_all_instances)
173    @mock.patch.object(
174        android_device, "list_adb_devices", new=mock_list_adb_devices)
175    def test_create_with_pickup_all(self):
176        pick_all_token = android_device.ANDROID_DEVICE_PICK_ALL_TOKEN
177        actual_ads = android_device.create(pick_all_token)
178        for actual, expected in zip(actual_ads, get_mock_ads(5)):
179            self.assertEqual(actual.serial, expected.serial)
180
181    def test_create_with_empty_config(self):
182        expected_msg = android_device.ANDROID_DEVICE_EMPTY_CONFIG_MSG
183        with self.assertRaisesRegex(android_device.AndroidDeviceError,
184                                    expected_msg):
185            android_device.create([])
186
187    def test_create_with_not_list_config(self):
188        expected_msg = android_device.ANDROID_DEVICE_NOT_LIST_CONFIG_MSG
189        with self.assertRaisesRegex(android_device.AndroidDeviceError,
190                                    expected_msg):
191            android_device.create("HAHA")
192
193    def test_get_device_success_with_serial(self):
194        ads = get_mock_ads(5)
195        expected_serial = 0
196        ad = android_device.get_device(ads, serial=expected_serial)
197        self.assertEqual(ad.serial, expected_serial)
198
199    def test_get_device_success_with_serial_and_extra_field(self):
200        ads = get_mock_ads(5)
201        expected_serial = 1
202        expected_h_port = 5555
203        ads[1].h_port = expected_h_port
204        ad = android_device.get_device(
205            ads, serial=expected_serial, h_port=expected_h_port)
206        self.assertEqual(ad.serial, expected_serial)
207        self.assertEqual(ad.h_port, expected_h_port)
208
209    def test_get_device_no_match(self):
210        ads = get_mock_ads(5)
211        expected_msg = ("Could not find a target device that matches condition"
212                        ": {'serial': 5}.")
213        with self.assertRaisesRegex(android_device.AndroidDeviceError,
214                                    expected_msg):
215            ad = android_device.get_device(ads, serial=len(ads))
216
217    def test_get_device_too_many_matches(self):
218        ads = get_mock_ads(5)
219        target_serial = ads[1].serial = ads[0].serial
220        expected_msg = "More than one device matched: \[0, 0\]"
221        with self.assertRaisesRegex(android_device.AndroidDeviceError,
222                                    expected_msg):
223            ad = android_device.get_device(ads, serial=target_serial)
224
225    def test_start_services_on_ads(self):
226        """Makes sure when an AndroidDevice fails to start some services, all
227        AndroidDevice objects get cleaned up.
228        """
229        msg = "Some error happened."
230        ads = get_mock_ads(3)
231        ads[0].start_services = mock.MagicMock()
232        ads[0].clean_up = mock.MagicMock()
233        ads[1].start_services = mock.MagicMock()
234        ads[1].clean_up = mock.MagicMock()
235        ads[2].start_services = mock.MagicMock(
236            side_effect=android_device.AndroidDeviceError(msg))
237        ads[2].clean_up = mock.MagicMock()
238        with self.assertRaisesRegex(android_device.AndroidDeviceError, msg):
239            android_device._start_services_on_ads(ads)
240        ads[0].clean_up.assert_called_once_with()
241        ads[1].clean_up.assert_called_once_with()
242        ads[2].clean_up.assert_called_once_with()
243
244    # Tests for android_device.AndroidDevice class.
245    # These tests mock out any interaction with the OS and real android device
246    # in AndroidDeivce.
247
248    @mock.patch(
249        'acts.controllers.adb.AdbProxy',
250        return_value=MockAdbProxy(MOCK_SERIAL))
251    @mock.patch(
252        'acts.controllers.fastboot.FastbootProxy',
253        return_value=MockFastbootProxy(MOCK_SERIAL))
254    def test_AndroidDevice_instantiation(self, MockFastboot, MockAdbProxy):
255        """Verifies the AndroidDevice object's basic attributes are correctly
256        set after instantiation.
257        """
258        ad = android_device.AndroidDevice(serial=MOCK_SERIAL)
259        self.assertEqual(ad.serial, 1)
260        self.assertEqual(ad.model, "fakemodel")
261        self.assertIsNone(ad.adb_logcat_process)
262        self.assertIsNone(ad.adb_logcat_file_path)
263        expected_lp = os.path.join(logging.log_path,
264                                   "AndroidDevice%s" % MOCK_SERIAL)
265        self.assertEqual(ad.log_path, expected_lp)
266
267    @mock.patch(
268        'acts.controllers.adb.AdbProxy',
269        return_value=MockAdbProxy(MOCK_SERIAL))
270    @mock.patch(
271        'acts.controllers.fastboot.FastbootProxy',
272        return_value=MockFastbootProxy(MOCK_SERIAL))
273    def test_AndroidDevice_build_info_release(self, MockFastboot,
274                                              MockAdbProxy):
275        """Verifies the AndroidDevice object's basic attributes are correctly
276        set after instantiation.
277        """
278        ad = android_device.AndroidDevice(serial=1)
279        build_info = ad.build_info
280        self.assertEqual(build_info["build_id"], "ABC1.123456.007")
281        self.assertEqual(build_info["build_type"], "userdebug")
282
283    @mock.patch(
284        'acts.controllers.adb.AdbProxy',
285        return_value=MockAdbProxy(MOCK_SERIAL, build_id=MOCK_DEV_BUILD_ID))
286    @mock.patch(
287        'acts.controllers.fastboot.FastbootProxy',
288        return_value=MockFastbootProxy(MOCK_SERIAL))
289    def test_AndroidDevice_build_info_release(self, MockFastboot,
290                                              MockAdbProxy):
291        """Verifies the AndroidDevice object's basic attributes are correctly
292        set after instantiation.
293        """
294        global MOCK_BUILD_ID
295        ad = android_device.AndroidDevice(serial=1)
296        old_mock_build_id = MOCK_BUILD_ID
297        MOCK_BUILD_ID = "ABC-MR1"
298        build_info = ad.build_info
299        self.assertEqual(build_info["build_id"], "123456789")
300        self.assertEqual(build_info["build_type"], "userdebug")
301        MOCK_BUILD_ID = old_mock_build_id
302
303    @mock.patch(
304        'acts.controllers.adb.AdbProxy',
305        return_value=MockAdbProxy(MOCK_SERIAL))
306    @mock.patch(
307        'acts.controllers.fastboot.FastbootProxy',
308        return_value=MockFastbootProxy(MOCK_SERIAL))
309    def test_AndroidDevice_build_info_dev(self, MockFastboot, MockAdbProxy):
310        """Verifies the AndroidDevice object's basic attributes are correctly
311        set after instantiation.
312        """
313        ad = android_device.AndroidDevice(serial=1)
314        build_info = ad.build_info
315        self.assertEqual(build_info["build_id"], "123456789")
316        self.assertEqual(build_info["build_type"], "userdebug")
317
318    @mock.patch(
319        'acts.controllers.adb.AdbProxy',
320        return_value=MockAdbProxy(MOCK_SERIAL, build_id=MOCK_NYC_BUILD_ID))
321    @mock.patch(
322        'acts.controllers.fastboot.FastbootProxy',
323        return_value=MockFastbootProxy(MOCK_SERIAL))
324    def test_AndroidDevice_build_info_nyc(self, MockFastboot, MockAdbProxy):
325        """Verifies the AndroidDevice object's build id is set correctly for
326        NYC releases.
327        """
328        ad = android_device.AndroidDevice(serial=1)
329        build_info = ad.build_info
330        self.assertEqual(build_info["build_id"], MOCK_NYC_BUILD_ID)
331
332    @mock.patch(
333        'acts.controllers.adb.AdbProxy',
334        return_value=MockAdbProxy(MOCK_SERIAL))
335    @mock.patch(
336        'acts.controllers.fastboot.FastbootProxy',
337        return_value=MockFastbootProxy(MOCK_SERIAL))
338
339    @mock.patch('acts.utils.create_dir')
340    @mock.patch('acts.utils.exe_cmd')
341    def test_AndroidDevice_take_bug_report(self, exe_mock, create_dir_mock,
342                                           FastbootProxy, MockAdbProxy):
343        """Verifies AndroidDevice.take_bug_report calls the correct adb command
344        and writes the bugreport file to the correct path.
345        """
346        ad = android_device.AndroidDevice(serial=MOCK_SERIAL)
347        ad.take_bug_report("test_something", 234325.32)
348        expected_path = os.path.join(
349            logging.log_path, "AndroidDevice%s" % ad.serial, "test_something")
350        create_dir_mock.assert_called_with(expected_path)
351
352    @mock.patch(
353        'acts.controllers.adb.AdbProxy',
354        return_value=MockAdbProxy(MOCK_SERIAL, fail_br=True))
355    @mock.patch(
356        'acts.controllers.fastboot.FastbootProxy',
357        return_value=MockFastbootProxy(MOCK_SERIAL))
358    @mock.patch('acts.utils.create_dir')
359    @mock.patch('acts.utils.exe_cmd')
360    def test_AndroidDevice_take_bug_report_fail(
361            self, exe_mock, create_dir_mock, FastbootProxy, MockAdbProxy):
362        """Verifies AndroidDevice.take_bug_report writes out the correct message
363        when taking bugreport fails.
364        """
365        ad = android_device.AndroidDevice(serial=MOCK_SERIAL)
366        expected_msg = "Failed to take bugreport on 1: OMG I died!"
367        with self.assertRaisesRegex(android_device.AndroidDeviceError,
368                                    expected_msg):
369            ad.take_bug_report("test_something", 4346343.23)
370
371    @mock.patch(
372        'acts.controllers.adb.AdbProxy',
373        return_value=MockAdbProxy(MOCK_SERIAL, fail_br_before_N=True))
374    @mock.patch(
375        'acts.controllers.fastboot.FastbootProxy',
376        return_value=MockFastbootProxy(MOCK_SERIAL))
377    @mock.patch('acts.utils.create_dir')
378    @mock.patch('acts.utils.exe_cmd')
379    def test_AndroidDevice_take_bug_report_fallback(
380            self, exe_mock, create_dir_mock, FastbootProxy, MockAdbProxy):
381        """Verifies AndroidDevice.take_bug_report falls back to traditional
382        bugreport on builds that do not have bugreportz.
383        """
384        ad = android_device.AndroidDevice(serial=MOCK_SERIAL)
385        ad.take_bug_report("test_something", MOCK_ADB_EPOCH_BEGIN_TIME)
386        expected_path = os.path.join(
387            logging.log_path, "AndroidDevice%s" % ad.serial, "test_something")
388        create_dir_mock.assert_called_with(expected_path)
389
390    @mock.patch(
391        'acts.controllers.adb.AdbProxy',
392        return_value=MockAdbProxy(MOCK_SERIAL))
393    @mock.patch(
394        'acts.controllers.fastboot.FastbootProxy',
395        return_value=MockFastbootProxy(MOCK_SERIAL))
396    @mock.patch('acts.utils.create_dir')
397    @mock.patch('acts.utils.start_standing_subprocess', return_value="process")
398    @mock.patch('acts.utils.stop_standing_subprocess')
399    @mock.patch('acts.utils._assert_subprocess_running')
400    def test_AndroidDevice_take_logcat(self, check_proc_mock, stop_proc_mock,
401                                       start_proc_mock, creat_dir_mock,
402                                       FastbootProxy, MockAdbProxy):
403        """Verifies the steps of collecting adb logcat on an AndroidDevice
404        object, including various function calls and the expected behaviors of
405        the calls.
406        """
407        ad = android_device.AndroidDevice(serial=MOCK_SERIAL)
408        expected_msg = ("Android device .* does not have an ongoing adb logcat"
409                        " collection.")
410        # Expect error if stop is called before start.
411        with self.assertRaisesRegex(android_device.AndroidDeviceError,
412                                    expected_msg):
413            ad.stop_adb_logcat()
414        ad.start_adb_logcat()
415        # Verify start did the correct operations.
416        self.assertTrue(ad.adb_logcat_process)
417        expected_log_path = os.path.join(logging.log_path,
418                                         "AndroidDevice%s" % ad.serial,
419                                         "adblog,fakemodel,%s.txt" % ad.serial)
420        creat_dir_mock.assert_called_with(os.path.dirname(expected_log_path))
421        adb_cmd = 'adb -s %s logcat -T 1 -v year -b all >> %s'
422        start_proc_mock.assert_called_with(adb_cmd % (ad.serial,
423                                                      expected_log_path))
424        self.assertEqual(ad.adb_logcat_file_path, expected_log_path)
425        expected_msg = ("Android device .* already has an adb logcat thread "
426                        "going on. Cannot start another one.")
427        # Expect error if start is called back to back.
428        with self.assertRaisesRegex(android_device.AndroidDeviceError,
429                                    expected_msg):
430            ad.start_adb_logcat()
431        # Verify stop did the correct operations.
432        ad.stop_adb_logcat()
433        stop_proc_mock.assert_called_with("process")
434        self.assertIsNone(ad.adb_logcat_process)
435        self.assertEqual(ad.adb_logcat_file_path, expected_log_path)
436
437    @mock.patch(
438        'acts.controllers.adb.AdbProxy',
439        return_value=MockAdbProxy(MOCK_SERIAL))
440    @mock.patch(
441        'acts.controllers.fastboot.FastbootProxy',
442        return_value=MockFastbootProxy(MOCK_SERIAL))
443    @mock.patch('acts.utils.create_dir')
444    @mock.patch('acts.utils.start_standing_subprocess', return_value="process")
445    @mock.patch('acts.utils.stop_standing_subprocess')
446    @mock.patch('acts.utils._assert_subprocess_running')
447    def test_AndroidDevice_take_logcat_with_user_param(
448            self, check_proc_mock, stop_proc_mock, start_proc_mock,
449            creat_dir_mock, FastbootProxy, MockAdbProxy):
450        """Verifies the steps of collecting adb logcat on an AndroidDevice
451        object, including various function calls and the expected behaviors of
452        the calls.
453        """
454        ad = android_device.AndroidDevice(serial=MOCK_SERIAL)
455        ad.adb_logcat_param = "-b radio"
456        expected_msg = ("Android device .* does not have an ongoing adb logcat"
457                        " collection.")
458        # Expect error if stop is called before start.
459        with self.assertRaisesRegex(android_device.AndroidDeviceError,
460                                    expected_msg):
461            ad.stop_adb_logcat()
462        ad.start_adb_logcat()
463        # Verify start did the correct operations.
464        self.assertTrue(ad.adb_logcat_process)
465        expected_log_path = os.path.join(logging.log_path,
466                                         "AndroidDevice%s" % ad.serial,
467                                         "adblog,fakemodel,%s.txt" % ad.serial)
468        creat_dir_mock.assert_called_with(os.path.dirname(expected_log_path))
469        adb_cmd = 'adb -s %s logcat -T 1 -v year -b radio >> %s'
470        start_proc_mock.assert_called_with(adb_cmd % (ad.serial,
471                                                      expected_log_path))
472        self.assertEqual(ad.adb_logcat_file_path, expected_log_path)
473
474    @mock.patch(
475        'acts.controllers.adb.AdbProxy',
476        return_value=MockAdbProxy(MOCK_SERIAL))
477    def test_get_apk_process_id_process_cannot_find(self, adb_proxy):
478        ad = android_device.AndroidDevice(serial=MOCK_SERIAL)
479        ad.adb.return_value = "does_not_contain_value"
480        self.assertEqual(None, ad.get_package_pid("some_package"))
481
482    @mock.patch(
483        'acts.controllers.adb.AdbProxy',
484        return_value=MockAdbProxy(MOCK_SERIAL))
485    def test_get_apk_process_id_process_exists_second_try(self, adb_proxy):
486        ad = android_device.AndroidDevice(serial=MOCK_SERIAL)
487        ad.adb.return_multiple = True
488        ad.adb.return_value = ["", "system 1 2 3 4  S com.some_package"]
489        self.assertEqual(1, ad.get_package_pid("some_package"))
490
491    @mock.patch(
492        'acts.controllers.adb.AdbProxy',
493        return_value=MockAdbProxy(MOCK_SERIAL))
494    def test_get_apk_process_id_bad_return(self, adb_proxy):
495        ad = android_device.AndroidDevice(serial=MOCK_SERIAL)
496        ad.adb.return_value = "bad_return_index_error"
497        self.assertEqual(None, ad.get_package_pid("some_package"))
498
499    @mock.patch(
500        'acts.controllers.adb.AdbProxy',
501        return_value=MockAdbProxy(MOCK_SERIAL))
502    def test_get_apk_process_id_bad_return(self, adb_proxy):
503        ad = android_device.AndroidDevice(serial=MOCK_SERIAL)
504        ad.adb.return_value = "bad return value error"
505        self.assertEqual(None, ad.get_package_pid("some_package"))
506
507
508if __name__ == "__main__":
509    unittest.main()
510