1#!/usr/bin/env python3.4 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 base_test 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# The expected result of the cat adb operation. 30MOCK_ADB_LOGCAT_CAT_RESULT = [ 31 "02-29 14:02:21.456 4454 Something\n", 32 "02-29 14:02:21.789 4454 Something again\n"] 33# A mockd piece of adb logcat output. 34MOCK_ADB_LOGCAT = ( 35 "02-29 14:02:19.123 4454 Nothing\n" 36 "%s" 37 "02-29 14:02:22.123 4454 Something again and again\n" 38 ) % ''.join(MOCK_ADB_LOGCAT_CAT_RESULT) 39# Mock start and end time of the adb cat. 40MOCK_ADB_LOGCAT_BEGIN_TIME = "02-29 14:02:20.123" 41MOCK_ADB_LOGCAT_END_TIME = "02-29 14:02:22.000" 42 43def get_mock_ads(num, logger=None): 44 """Generates a list of mock AndroidDevice objects. 45 46 The serial number of each device will be integer 0 through num - 1. 47 48 Args: 49 num: An integer that is the number of mock AndroidDevice objects to 50 create. 51 """ 52 ads = [] 53 for i in range(num): 54 ad = mock.MagicMock(name="AndroidDevice", 55 logger=logger, 56 serial=i, 57 h_port=None) 58 ads.append(ad) 59 return ads 60 61def get_mock_logger(): 62 return mock.MagicMock(name="Logger", log_path=MOCK_LOG_PATH) 63 64def mock_get_all_instances(logger=None): 65 return get_mock_ads(5, logger=logger) 66 67def mock_list_adb_devices(): 68 return [ad.serial for ad in get_mock_ads(5)] 69 70class MockAdbProxy(): 71 """Mock class that swaps out calls to adb with mock calls.""" 72 73 def __init__(self, serial): 74 self.serial = serial 75 76 def shell(self, params): 77 if params == "id -u": 78 return b"root" 79 if (params == "getprop | grep ro.build.product" or 80 params == "getprop | grep ro.product.name"): 81 return b"[ro.build.product]: [FakeModel]" 82 elif params == "getprop sys.boot_completed": 83 return b"1" 84 elif params == "bugreportz": 85 return b'OK:/path/bugreport.zip\n' 86 87 def bugreport(self, params): 88 expected = os.path.join(logging.log_path, 89 "AndroidDevice%s" % self.serial, "BugReports", 90 "test_something,sometime,%s" % (self.serial)) 91 assert expected in params, "Expected '%s', got '%s'." % (expected, 92 params) 93 94 def __getattr__(self, name): 95 """All calls to the none-existent functions in adb proxy would 96 simply return the adb command string. 97 """ 98 def adb_call(*args): 99 clean_name = name.replace('_', '-') 100 arg_str = ' '.join(str(elem) for elem in args) 101 return arg_str 102 return adb_call 103 104class ActsAndroidDeviceTest(unittest.TestCase): 105 """This test class has unit tests for the implementation of everything 106 under acts.controllers.android_device. 107 """ 108 109 def setUp(self): 110 """Creates a temp dir to be used by tests in this test class. 111 """ 112 self.tmp_dir = tempfile.mkdtemp() 113 114 def tearDown(self): 115 """Removes the temp dir. 116 """ 117 shutil.rmtree(self.tmp_dir) 118 119 # Tests for android_device module functions. 120 # These tests use mock AndroidDevice instances. 121 122 @mock.patch.object(android_device, "get_all_instances", 123 new=mock_get_all_instances) 124 @mock.patch.object(android_device, "list_adb_devices", 125 new=mock_list_adb_devices) 126 def test_create_with_pickup_all(self): 127 pick_all_token = android_device.ANDROID_DEVICE_PICK_ALL_TOKEN 128 actual_ads = android_device.create(pick_all_token, logging) 129 for actual, expected in zip(actual_ads, get_mock_ads(5)): 130 self.assertEqual(actual.serial, expected.serial) 131 132 def test_create_with_empty_config(self): 133 expected_msg = android_device.ANDROID_DEVICE_EMPTY_CONFIG_MSG 134 with self.assertRaisesRegexp(android_device.AndroidDeviceError, 135 expected_msg): 136 android_device.create([], logging) 137 138 def test_create_with_not_list_config(self): 139 expected_msg = android_device.ANDROID_DEVICE_NOT_LIST_CONFIG_MSG 140 with self.assertRaisesRegexp(android_device.AndroidDeviceError, 141 expected_msg): 142 android_device.create("HAHA", logging) 143 144 def test_get_device_success_with_serial(self): 145 ads = get_mock_ads(5) 146 expected_serial = 0 147 ad = android_device.get_device(ads, serial=expected_serial) 148 self.assertEqual(ad.serial, expected_serial) 149 150 def test_get_device_success_with_serial_and_extra_field(self): 151 ads = get_mock_ads(5) 152 expected_serial = 1 153 expected_h_port = 5555 154 ads[1].h_port = expected_h_port 155 ad = android_device.get_device(ads, 156 serial=expected_serial, 157 h_port=expected_h_port) 158 self.assertEqual(ad.serial, expected_serial) 159 self.assertEqual(ad.h_port, expected_h_port) 160 161 def test_get_device_no_match(self): 162 ads = get_mock_ads(5) 163 expected_msg = ("Could not find a target device that matches condition" 164 ": {'serial': 5}.") 165 with self.assertRaisesRegexp(android_device.AndroidDeviceError, 166 expected_msg): 167 ad = android_device.get_device(ads, serial=len(ads)) 168 169 def test_get_device_too_many_matches(self): 170 ads = get_mock_ads(5) 171 target_serial = ads[1].serial = ads[0].serial 172 expected_msg = "More than one device matched: \[0, 0\]" 173 with self.assertRaisesRegexp(android_device.AndroidDeviceError, 174 expected_msg): 175 ad = android_device.get_device(ads, serial=target_serial) 176 177 def test_start_services_on_ads(self): 178 """Makes sure when an AndroidDevice fails to start some services, all 179 AndroidDevice objects get cleaned up. 180 """ 181 msg = "Some error happened." 182 ads = get_mock_ads(3) 183 ads[0].start_services = mock.MagicMock() 184 ads[0].clean_up = mock.MagicMock() 185 ads[1].start_services = mock.MagicMock() 186 ads[1].clean_up = mock.MagicMock() 187 ads[2].start_services = mock.MagicMock( 188 side_effect=android_device.AndroidDeviceError(msg)) 189 ads[2].clean_up = mock.MagicMock() 190 with self.assertRaisesRegexp(android_device.AndroidDeviceError, msg): 191 android_device._start_services_on_ads(ads) 192 ads[0].clean_up.assert_called_once_with() 193 ads[1].clean_up.assert_called_once_with() 194 ads[2].clean_up.assert_called_once_with() 195 196 # Tests for android_device.AndroidDevice class. 197 # These tests mock out any interaction with the OS and real android device 198 # in AndroidDeivce. 199 200 @mock.patch('acts.controllers.adb.AdbProxy', return_value=MockAdbProxy(1)) 201 def test_AndroidDevice_instantiation(self, MockAdbProxy): 202 """Verifies the AndroidDevice object's basic attributes are correctly 203 set after instantiation. 204 """ 205 mock_serial = 1 206 ml = get_mock_logger() 207 ad = android_device.AndroidDevice(serial=mock_serial, logger=ml) 208 self.assertEqual(ad.serial, 1) 209 self.assertEqual(ad.model, "fakemodel") 210 self.assertIsNone(ad.adb_logcat_process) 211 self.assertIsNone(ad.adb_logcat_file_path) 212 expected_lp = os.path.join(ml.log_path, 213 "AndroidDevice%s" % mock_serial) 214 self.assertEqual(ad.log_path, expected_lp) 215 216 @mock.patch('acts.controllers.adb.AdbProxy', return_value=MockAdbProxy(1)) 217 @mock.patch('acts.utils.create_dir') 218 @mock.patch('acts.utils.exe_cmd') 219 def test_AndroidDevice_take_bug_report(self, 220 exe_mock, 221 create_dir_mock, 222 MockAdbProxy): 223 """Verifies AndroidDevice.take_bug_report calls the correct adb command 224 and writes the bugreport file to the correct path. 225 """ 226 mock_serial = 1 227 ml = get_mock_logger() 228 ad = android_device.AndroidDevice(serial=mock_serial, logger=ml) 229 ad.take_bug_report("test_something", "sometime") 230 expected_path = os.path.join(MOCK_LOG_PATH, 231 "AndroidDevice%s" % ad.serial, 232 "BugReports") 233 create_dir_mock.assert_called_with(expected_path) 234 235 @mock.patch('acts.controllers.adb.AdbProxy', return_value=MockAdbProxy(1)) 236 @mock.patch('acts.utils.create_dir') 237 @mock.patch('acts.utils.start_standing_subprocess', return_value="process") 238 @mock.patch('acts.utils.stop_standing_subprocess') 239 def test_AndroidDevice_take_logcat(self, 240 stop_proc_mock, 241 start_proc_mock, 242 creat_dir_mock, 243 MockAdbProxy): 244 """Verifies the steps of collecting adb logcat on an AndroidDevice 245 object, including various function calls and the expected behaviors of 246 the calls. 247 """ 248 mock_serial = 1 249 ml = get_mock_logger() 250 ad = android_device.AndroidDevice(serial=mock_serial, logger=ml) 251 expected_msg = ("Android device .* does not have an ongoing adb logcat" 252 " collection.") 253 # Expect error if stop is called before start. 254 with self.assertRaisesRegexp(android_device.AndroidDeviceError, 255 expected_msg): 256 ad.stop_adb_logcat() 257 ad.start_adb_logcat() 258 # Verify start did the correct operations. 259 self.assertTrue(ad.adb_logcat_process) 260 expected_log_path = os.path.join( 261 MOCK_LOG_PATH, 262 "AndroidDevice%s" % ad.serial, 263 "adblog,fakemodel,%s.txt" % ad.serial) 264 creat_dir_mock.assert_called_with(os.path.dirname(expected_log_path)) 265 adb_cmd = 'adb -s %s logcat -v threadtime >> %s' 266 start_proc_mock.assert_called_with(adb_cmd % (ad.serial, 267 expected_log_path)) 268 self.assertEqual(ad.adb_logcat_file_path, expected_log_path) 269 expected_msg = ("Android device .* already has an adb logcat thread " 270 "going on. Cannot start another one.") 271 # Expect error if start is called back to back. 272 with self.assertRaisesRegexp(android_device.AndroidDeviceError, 273 expected_msg): 274 ad.start_adb_logcat() 275 # Verify stop did the correct operations. 276 ad.stop_adb_logcat() 277 stop_proc_mock.assert_called_with("process") 278 self.assertIsNone(ad.adb_logcat_process) 279 self.assertEqual(ad.adb_logcat_file_path, expected_log_path) 280 281 @mock.patch('acts.controllers.adb.AdbProxy', return_value=MockAdbProxy(1)) 282 @mock.patch('acts.utils.start_standing_subprocess', return_value="process") 283 @mock.patch('acts.utils.stop_standing_subprocess') 284 @mock.patch('acts.logger.get_log_line_timestamp', 285 return_value=MOCK_ADB_LOGCAT_END_TIME) 286 def test_AndroidDevice_cat_adb_log(self, 287 mock_timestamp_getter, 288 stop_proc_mock, 289 start_proc_mock, 290 MockAdbProxy): 291 """Verifies that AndroidDevice.cat_adb_log loads the correct adb log 292 file, locates the correct adb log lines within the given time range, 293 and writes the lines to the correct output file. 294 """ 295 mock_serial = 1 296 ml = get_mock_logger() 297 ad = android_device.AndroidDevice(serial=mock_serial, logger=ml) 298 # Expect error if attempted to cat adb log before starting adb logcat. 299 expected_msg = ("Attempting to cat adb log when none has been " 300 "collected on Android device .*") 301 with self.assertRaisesRegexp(android_device.AndroidDeviceError, 302 expected_msg): 303 ad.cat_adb_log("some_test", MOCK_ADB_LOGCAT_BEGIN_TIME) 304 ad.start_adb_logcat() 305 # Direct the log path of the ad to a temp dir to avoid racing. 306 ad.log_path = os.path.join(self.tmp_dir, ad.log_path) 307 mock_adb_log_path = os.path.join(ad.log_path, "adblog,%s,%s.txt" % 308 (ad.model, ad.serial)) 309 with open(mock_adb_log_path, 'w') as f: 310 f.write(MOCK_ADB_LOGCAT) 311 ad.cat_adb_log("some_test", MOCK_ADB_LOGCAT_BEGIN_TIME) 312 cat_file_path = os.path.join(ad.log_path, 313 "AdbLogExcerpts", 314 ("some_test,02-29 14:02:20.123,%s,%s.txt" 315 ) % (ad.model, ad.serial)) 316 with open(cat_file_path, 'r') as f: 317 actual_cat = f.read() 318 self.assertEqual(actual_cat, ''.join(MOCK_ADB_LOGCAT_CAT_RESULT)) 319 # Stops adb logcat. 320 ad.stop_adb_logcat() 321 322if __name__ == "__main__": 323 unittest.main() 324