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 83 def bugreport(self, params): 84 expected = os.path.join(MOCK_LOG_PATH, 85 "AndroidDevice%s" % self.serial, 86 "BugReports", 87 "test_something,sometime,%s.txt" % ( 88 self.serial)) 89 expected = " > %s" % expected 90 assert params == expected, "Expected '%s', got '%s'." % (expected, 91 params) 92 93 def __getattr__(self, name): 94 """All calls to the none-existent functions in adb proxy would 95 simply return the adb command string. 96 """ 97 def adb_call(*args): 98 clean_name = name.replace('_', '-') 99 arg_str = ' '.join(str(elem) for elem in args) 100 return arg_str 101 return adb_call 102 103class ActsAndroidDeviceTest(unittest.TestCase): 104 """This test class has unit tests for the implementation of everything 105 under acts.controllers.android_device. 106 """ 107 108 def setUp(self): 109 """Creates a temp dir to be used by tests in this test class. 110 """ 111 self.tmp_dir = tempfile.mkdtemp() 112 113 def tearDown(self): 114 """Removes the temp dir. 115 """ 116 shutil.rmtree(self.tmp_dir) 117 118 # Tests for android_device module functions. 119 # These tests use mock AndroidDevice instances. 120 121 @mock.patch.object(android_device, "get_all_instances", 122 new=mock_get_all_instances) 123 @mock.patch.object(android_device, "list_adb_devices", 124 new=mock_list_adb_devices) 125 def test_create_with_pickup_all(self): 126 pick_all_token = android_device.ANDROID_DEVICE_PICK_ALL_TOKEN 127 actual_ads = android_device.create(pick_all_token, logging) 128 for actual, expected in zip(actual_ads, get_mock_ads(5)): 129 self.assertEqual(actual.serial, expected.serial) 130 131 def test_create_with_empty_config(self): 132 expected_msg = android_device.ANDROID_DEVICE_EMPTY_CONFIG_MSG 133 with self.assertRaisesRegexp(android_device.AndroidDeviceError, 134 expected_msg): 135 android_device.create([], logging) 136 137 def test_create_with_not_list_config(self): 138 expected_msg = android_device.ANDROID_DEVICE_NOT_LIST_CONFIG_MSG 139 with self.assertRaisesRegexp(android_device.AndroidDeviceError, 140 expected_msg): 141 android_device.create("HAHA", logging) 142 143 def test_get_device_success_with_serial(self): 144 ads = get_mock_ads(5) 145 expected_serial = 0 146 ad = android_device.get_device(ads, serial=expected_serial) 147 self.assertEqual(ad.serial, expected_serial) 148 149 def test_get_device_success_with_serial_and_extra_field(self): 150 ads = get_mock_ads(5) 151 expected_serial = 1 152 expected_h_port = 5555 153 ads[1].h_port = expected_h_port 154 ad = android_device.get_device(ads, 155 serial=expected_serial, 156 h_port=expected_h_port) 157 self.assertEqual(ad.serial, expected_serial) 158 self.assertEqual(ad.h_port, expected_h_port) 159 160 def test_get_device_no_match(self): 161 ads = get_mock_ads(5) 162 expected_msg = ("Could not find a target device that matches condition" 163 ": {'serial': 5}.") 164 with self.assertRaisesRegexp(android_device.AndroidDeviceError, 165 expected_msg): 166 ad = android_device.get_device(ads, serial=len(ads)) 167 168 def test_get_device_too_many_matches(self): 169 ads = get_mock_ads(5) 170 target_serial = ads[1].serial = ads[0].serial 171 expected_msg = "More than one device matched: \[0, 0\]" 172 with self.assertRaisesRegexp(android_device.AndroidDeviceError, 173 expected_msg): 174 ad = android_device.get_device(ads, serial=target_serial) 175 176 # Tests for android_device.AndroidDevice class. 177 # These tests mock out any interaction with the OS and real android device 178 # in AndroidDeivce. 179 180 @mock.patch('acts.controllers.adb.AdbProxy', return_value=MockAdbProxy(1)) 181 def test_AndroidDevice_instantiation(self, MockAdbProxy): 182 """Verifies the AndroidDevice object's basic attributes are correctly 183 set after instantiation. 184 """ 185 mock_serial = 1 186 ml = get_mock_logger() 187 ad = android_device.AndroidDevice(serial=mock_serial, logger=ml) 188 self.assertEqual(ad.serial, 1) 189 self.assertEqual(ad.model, "fakemodel") 190 self.assertIsNone(ad.adb_logcat_process) 191 self.assertIsNone(ad.adb_logcat_file_path) 192 expected_lp = os.path.join(ml.log_path, 193 "AndroidDevice%s" % mock_serial) 194 self.assertEqual(ad.log_path, expected_lp) 195 196 @mock.patch('acts.controllers.adb.AdbProxy', return_value=MockAdbProxy(1)) 197 @mock.patch('acts.utils.create_dir') 198 @mock.patch('acts.utils.exe_cmd') 199 def test_AndroidDevice_take_bug_report(self, 200 exe_mock, 201 create_dir_mock, 202 MockAdbProxy): 203 """Verifies AndroidDevice.take_bug_report calls the correct adb command 204 and writes the bugreport file to the correct path. 205 """ 206 mock_serial = 1 207 ml = get_mock_logger() 208 ad = android_device.AndroidDevice(serial=mock_serial, logger=ml) 209 ad.take_bug_report("test_something", "sometime") 210 expected_path = os.path.join(MOCK_LOG_PATH, 211 "AndroidDevice%s" % ad.serial, 212 "BugReports") 213 create_dir_mock.assert_called_with(expected_path) 214 215 @mock.patch('acts.controllers.adb.AdbProxy', return_value=MockAdbProxy(1)) 216 @mock.patch('acts.utils.create_dir') 217 @mock.patch('acts.utils.start_standing_subprocess', return_value="process") 218 @mock.patch('acts.utils.stop_standing_subprocess') 219 def test_AndroidDevice_take_logcat(self, 220 stop_proc_mock, 221 start_proc_mock, 222 creat_dir_mock, 223 MockAdbProxy): 224 """Verifies the steps of collecting adb logcat on an AndroidDevice 225 object, including various function calls and the expected behaviors of 226 the calls. 227 """ 228 mock_serial = 1 229 ml = get_mock_logger() 230 ad = android_device.AndroidDevice(serial=mock_serial, logger=ml) 231 expected_msg = ("Android device .* does not have an ongoing adb logcat" 232 " collection.") 233 # Expect error if stop is called before start. 234 with self.assertRaisesRegexp(android_device.AndroidDeviceError, 235 expected_msg): 236 ad.stop_adb_logcat() 237 ad.start_adb_logcat() 238 # Verify start did the correct operations. 239 self.assertTrue(ad.adb_logcat_process) 240 expected_log_path = os.path.join( 241 MOCK_LOG_PATH, 242 "AndroidDevice%s" % ad.serial, 243 "adblog,fakemodel,%s.txt" % ad.serial) 244 creat_dir_mock.assert_called_with(os.path.dirname(expected_log_path)) 245 adb_cmd = 'adb -s %s logcat -v threadtime >> %s' 246 start_proc_mock.assert_called_with(adb_cmd % (ad.serial, 247 expected_log_path)) 248 self.assertEqual(ad.adb_logcat_file_path, expected_log_path) 249 expected_msg = ("Android device .* already has an adb logcat thread " 250 "going on. Cannot start another one.") 251 # Expect error if start is called back to back. 252 with self.assertRaisesRegexp(android_device.AndroidDeviceError, 253 expected_msg): 254 ad.start_adb_logcat() 255 # Verify stop did the correct operations. 256 ad.stop_adb_logcat() 257 stop_proc_mock.assert_called_with("process") 258 self.assertIsNone(ad.adb_logcat_process) 259 self.assertEqual(ad.adb_logcat_file_path, expected_log_path) 260 261 @mock.patch('acts.controllers.adb.AdbProxy', return_value=MockAdbProxy(1)) 262 @mock.patch('acts.utils.start_standing_subprocess', return_value="process") 263 @mock.patch('acts.utils.stop_standing_subprocess') 264 @mock.patch('acts.logger.get_log_line_timestamp', 265 return_value=MOCK_ADB_LOGCAT_END_TIME) 266 def test_AndroidDevice_cat_adb_log(self, 267 mock_timestamp_getter, 268 stop_proc_mock, 269 start_proc_mock, 270 MockAdbProxy): 271 """Verifies that AndroidDevice.cat_adb_log loads the correct adb log 272 file, locates the correct adb log lines within the given time range, 273 and writes the lines to the correct output file. 274 """ 275 mock_serial = 1 276 ml = get_mock_logger() 277 ad = android_device.AndroidDevice(serial=mock_serial, logger=ml) 278 # Expect error if attempted to cat adb log before starting adb logcat. 279 expected_msg = ("Attempting to cat adb log when none has been " 280 "collected on Android device .*") 281 with self.assertRaisesRegexp(android_device.AndroidDeviceError, 282 expected_msg): 283 ad.cat_adb_log("some_test", MOCK_ADB_LOGCAT_BEGIN_TIME) 284 ad.start_adb_logcat() 285 # Direct the log path of the ad to a temp dir to avoid racing. 286 ad.log_path = os.path.join(self.tmp_dir, ad.log_path) 287 mock_adb_log_path = os.path.join(ad.log_path, "adblog,%s,%s.txt" % 288 (ad.model, ad.serial)) 289 with open(mock_adb_log_path, 'w') as f: 290 f.write(MOCK_ADB_LOGCAT) 291 ad.cat_adb_log("some_test", MOCK_ADB_LOGCAT_BEGIN_TIME) 292 cat_file_path = os.path.join(ad.log_path, 293 "AdbLogExcerpts", 294 ("some_test,02-29 14:02:20.123,%s,%s.txt" 295 ) % (ad.model, ad.serial)) 296 with open(cat_file_path, 'r') as f: 297 actual_cat = f.read() 298 self.assertEqual(actual_cat, ''.join(MOCK_ADB_LOGCAT_CAT_RESULT)) 299 # Stops adb logcat. 300 ad.stop_adb_logcat() 301 302if __name__ == "__main__": 303 unittest.main()