1# Copyright 2019 - The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""Tests for GoldfishLocalImageLocalInstance.""" 15 16import os 17import shutil 18import tempfile 19import unittest 20 21from unittest import mock 22 23from acloud import errors 24import acloud.create.goldfish_local_image_local_instance as instance_module 25 26 27class GoldfishLocalImageLocalInstance(unittest.TestCase): 28 """Test GoldfishLocalImageLocalInstance methods.""" 29 30 _EXPECTED_DEVICES_IN_REPORT = [ 31 { 32 "instance_name": "local-goldfish-instance", 33 "ip": "127.0.0.1:5555", 34 "adb_port": 5555, 35 "device_serial": "unittest" 36 } 37 ] 38 39 def setUp(self): 40 self._goldfish = instance_module.GoldfishLocalImageLocalInstance() 41 self._temp_dir = tempfile.mkdtemp() 42 self._image_dir = os.path.join(self._temp_dir, "images") 43 self._tool_dir = os.path.join(self._temp_dir, "tool") 44 self._instance_dir = os.path.join(self._temp_dir, "instance") 45 self._emulator_is_running = False 46 self._mock_lock = mock.Mock() 47 self._mock_lock.Lock.return_value = True 48 self._mock_lock.LockIfNotInUse.side_effect = (False, True) 49 self._mock_proc = mock.Mock() 50 self._mock_proc.poll.side_effect = ( 51 lambda: None if self._emulator_is_running else 0) 52 53 os.mkdir(self._image_dir) 54 os.mkdir(self._tool_dir) 55 56 # Create emulator binary 57 self._emulator_path = os.path.join(self._tool_dir, "emulator", 58 "emulator") 59 self._CreateEmptyFile(self._emulator_path) 60 61 def tearDown(self): 62 shutil.rmtree(self._temp_dir, ignore_errors=True) 63 64 @staticmethod 65 def _CreateEmptyFile(path): 66 parent_dir = os.path.dirname(path) 67 if not os.path.exists(parent_dir): 68 os.makedirs(parent_dir) 69 with open(path, "w") as _: 70 pass 71 72 def _MockPopen(self, *_args, **_kwargs): 73 self._emulator_is_running = True 74 return self._mock_proc 75 76 def _MockEmuCommand(self, *args): 77 if not self._emulator_is_running: 78 # Connection refused 79 return 1 80 81 if args == ("kill",): 82 self._emulator_is_running = False 83 return 0 84 85 if args == (): 86 return 0 87 88 raise ValueError("Unexpected arguments " + str(args)) 89 90 def _SetUpMocks(self, mock_popen, mock_utils, mock_instance): 91 mock_utils.IsSupportedPlatform.return_value = True 92 93 mock_adb_tools = mock.Mock(side_effect=self._MockEmuCommand) 94 95 mock_instance_object = mock.Mock(ip="127.0.0.1", 96 adb_port=5555, 97 console_port="5554", 98 device_serial="unittest", 99 instance_dir=self._instance_dir, 100 adb=mock_adb_tools) 101 # name is a positional argument of Mock(). 102 mock_instance_object.name = "local-goldfish-instance" 103 104 mock_instance.return_value = mock_instance_object 105 mock_instance.GetLockById.return_value = self._mock_lock 106 mock_instance.GetMaxNumberOfInstances.return_value = 2 107 108 mock_popen.side_effect = self._MockPopen 109 110 def _GetExpectedEmulatorArgs(self, *extra_args): 111 cmd = [ 112 self._emulator_path, "-verbose", "-show-kernel", "-read-only", 113 "-ports", "5554,5555", 114 "-logcat-output", 115 os.path.join(self._instance_dir, "logcat.txt"), 116 "-stdouterr-file", 117 os.path.join(self._instance_dir, "stdouterr.txt") 118 ] 119 cmd.extend(extra_args) 120 return cmd 121 122 # pylint: disable=protected-access 123 @mock.patch("acloud.create.goldfish_local_image_local_instance.instance." 124 "LocalGoldfishInstance") 125 @mock.patch("acloud.create.goldfish_local_image_local_instance.utils") 126 @mock.patch("acloud.create.goldfish_local_image_local_instance." 127 "subprocess.Popen") 128 def testCreateAVDInBuildEnvironment(self, mock_popen, mock_utils, 129 mock_instance): 130 """Test _CreateAVD with build environment variables and files.""" 131 self._SetUpMocks(mock_popen, mock_utils, mock_instance) 132 133 self._CreateEmptyFile(os.path.join(self._image_dir, 134 "system-qemu.img")) 135 self._CreateEmptyFile(os.path.join(self._image_dir, "system", 136 "build.prop")) 137 138 mock_environ = {"ANDROID_EMULATOR_PREBUILTS": 139 os.path.join(self._tool_dir, "emulator")} 140 141 mock_avd_spec = mock.Mock(flavor="phone", 142 boot_timeout_secs=100, 143 gpu=None, 144 autoconnect=True, 145 local_instance_id=1, 146 local_instance_dir=None, 147 local_image_dir=self._image_dir, 148 local_system_image=None, 149 local_tool_dirs=[]) 150 151 # Test deleting an existing instance. 152 self._emulator_is_running = True 153 154 with mock.patch.dict("acloud.create." 155 "goldfish_local_image_local_instance.os.environ", 156 mock_environ, clear=True): 157 report = self._goldfish._CreateAVD(mock_avd_spec, no_prompts=False) 158 159 self.assertEqual(report.data.get("devices"), 160 self._EXPECTED_DEVICES_IN_REPORT) 161 162 self._mock_lock.Lock.assert_called_once() 163 self._mock_lock.SetInUse.assert_called_once_with(True) 164 self._mock_lock.Unlock.assert_called_once() 165 166 mock_instance.assert_called_once_with(1, avd_flavor="phone") 167 168 self.assertTrue(os.path.isdir(self._instance_dir)) 169 170 mock_utils.SetExecutable.assert_called_with(self._emulator_path) 171 mock_popen.assert_called_once() 172 self.assertEqual(mock_popen.call_args[0][0], 173 self._GetExpectedEmulatorArgs()) 174 self._mock_proc.poll.assert_called() 175 176 # pylint: disable=protected-access 177 @mock.patch("acloud.create.goldfish_local_image_local_instance.instance." 178 "LocalGoldfishInstance") 179 @mock.patch("acloud.create.goldfish_local_image_local_instance.utils") 180 @mock.patch("acloud.create.goldfish_local_image_local_instance." 181 "subprocess.Popen") 182 def testCreateAVDFromSdkRepository(self, mock_popen, 183 mock_utils, mock_instance): 184 """Test _CreateAVD with SDK repository files.""" 185 self._SetUpMocks(mock_popen, mock_utils, mock_instance) 186 187 self._CreateEmptyFile(os.path.join(self._image_dir, "x86", 188 "system.img")) 189 self._CreateEmptyFile(os.path.join(self._image_dir, "x86", 190 "build.prop")) 191 192 instance_dir = os.path.join(self._temp_dir, "local_instance_dir") 193 os.mkdir(instance_dir) 194 195 mock_avd_spec = mock.Mock(flavor="phone", 196 boot_timeout_secs=None, 197 gpu=None, 198 autoconnect=True, 199 local_instance_id=2, 200 local_instance_dir=instance_dir, 201 local_image_dir=self._image_dir, 202 local_system_image=None, 203 local_tool_dirs=[self._tool_dir]) 204 205 with mock.patch.dict("acloud.create." 206 "goldfish_local_image_local_instance.os.environ", 207 dict(), clear=True): 208 report = self._goldfish._CreateAVD(mock_avd_spec, no_prompts=True) 209 210 self.assertEqual(report.data.get("devices"), 211 self._EXPECTED_DEVICES_IN_REPORT) 212 213 self._mock_lock.Lock.assert_called_once() 214 self._mock_lock.SetInUse.assert_called_once_with(True) 215 self._mock_lock.Unlock.assert_called_once() 216 217 mock_instance.assert_called_once_with(2, avd_flavor="phone") 218 219 self.assertTrue(os.path.isdir(self._instance_dir) and 220 os.path.islink(self._instance_dir)) 221 222 mock_utils.SetExecutable.assert_called_with(self._emulator_path) 223 mock_popen.assert_called_once() 224 self.assertEqual(mock_popen.call_args[0][0], 225 self._GetExpectedEmulatorArgs()) 226 self._mock_proc.poll.assert_called() 227 228 self.assertTrue(os.path.isfile( 229 os.path.join(self._image_dir, "x86", "system", "build.prop"))) 230 231 # pylint: disable=protected-access 232 @mock.patch("acloud.create.goldfish_local_image_local_instance.instance." 233 "LocalGoldfishInstance") 234 @mock.patch("acloud.create.goldfish_local_image_local_instance.utils") 235 @mock.patch("acloud.create.goldfish_local_image_local_instance." 236 "subprocess.Popen") 237 def testCreateAVDTimeout(self, mock_popen, mock_utils, mock_instance): 238 """Test _CreateAVD with SDK repository files and timeout error.""" 239 self._SetUpMocks(mock_popen, mock_utils, mock_instance) 240 mock_utils.PollAndWait.side_effect = errors.DeviceBootTimeoutError( 241 "timeout") 242 243 self._CreateEmptyFile(os.path.join(self._image_dir, "system.img")) 244 self._CreateEmptyFile(os.path.join(self._image_dir, "build.prop")) 245 246 mock_avd_spec = mock.Mock(flavor="phone", 247 boot_timeout_secs=None, 248 gpu=None, 249 autoconnect=True, 250 local_instance_id=2, 251 local_instance_dir=None, 252 local_image_dir=self._image_dir, 253 local_system_image=None, 254 local_tool_dirs=[self._tool_dir]) 255 256 with mock.patch.dict("acloud.create." 257 "goldfish_local_image_local_instance.os.environ", 258 dict(), clear=True): 259 report = self._goldfish._CreateAVD(mock_avd_spec, no_prompts=True) 260 261 self._mock_lock.Lock.assert_called_once() 262 self._mock_lock.SetInUse.assert_called_once_with(True) 263 self._mock_lock.Unlock.assert_called_once() 264 265 self.assertEqual(report.data.get("devices_failing_boot"), 266 self._EXPECTED_DEVICES_IN_REPORT) 267 self.assertEqual(report.errors, ["timeout"]) 268 269 # pylint: disable=protected-access 270 @mock.patch("acloud.create.goldfish_local_image_local_instance.instance." 271 "LocalGoldfishInstance") 272 @mock.patch("acloud.create.goldfish_local_image_local_instance.utils") 273 @mock.patch("acloud.create.goldfish_local_image_local_instance." 274 "subprocess.Popen") 275 def testCreateAVDWithoutReport(self, mock_popen, mock_utils, 276 mock_instance): 277 """Test _CreateAVD with SDK repository files and no report.""" 278 self._SetUpMocks(mock_popen, mock_utils, mock_instance) 279 280 mock_avd_spec = mock.Mock(flavor="phone", 281 boot_timeout_secs=None, 282 gpu=None, 283 autoconnect=True, 284 local_instance_id=0, 285 local_instance_dir=None, 286 local_image_dir=self._image_dir, 287 local_system_image=None, 288 local_tool_dirs=[self._tool_dir]) 289 290 with mock.patch.dict("acloud.create." 291 "goldfish_local_image_local_instance.os.environ", 292 dict(), clear=True): 293 with self.assertRaises(errors.GetLocalImageError): 294 self._goldfish._CreateAVD(mock_avd_spec, no_prompts=True) 295 296 self._mock_lock.Lock.assert_not_called() 297 self.assertEqual(2, self._mock_lock.LockIfNotInUse.call_count) 298 self._mock_lock.SetInUse.assert_not_called() 299 self._mock_lock.Unlock.assert_called_once() 300 301 # pylint: disable=protected-access 302 @mock.patch("acloud.create.goldfish_local_image_local_instance.instance." 303 "LocalGoldfishInstance") 304 @mock.patch("acloud.create.goldfish_local_image_local_instance.utils") 305 @mock.patch("acloud.create.goldfish_local_image_local_instance." 306 "subprocess.Popen") 307 @mock.patch("acloud.create.goldfish_local_image_local_instance.ota_tools") 308 def testCreateAVDWithMixedImages(self, mock_ota_tools, mock_popen, 309 mock_utils, mock_instance): 310 """Test _CreateAVD with mixed images and SDK repository files.""" 311 mock_ota_tools.FindOtaTools.return_value = self._tool_dir 312 mock_ota_tools_object = mock.Mock() 313 mock_ota_tools.OtaTools.return_value = mock_ota_tools_object 314 mock_ota_tools_object.MkCombinedImg.side_effect = ( 315 lambda out_path, _conf, _get_img: self._CreateEmptyFile(out_path)) 316 317 self._SetUpMocks(mock_popen, mock_utils, mock_instance) 318 319 self._CreateEmptyFile(os.path.join(self._image_dir, "x86", 320 "system.img")) 321 self._CreateEmptyFile(os.path.join(self._image_dir, "x86", "system", 322 "build.prop")) 323 324 mock_avd_spec = mock.Mock(flavor="phone", 325 boot_timeout_secs=None, 326 gpu="auto", 327 autoconnect=False, 328 local_instance_id=3, 329 local_instance_dir=None, 330 local_image_dir=self._image_dir, 331 local_system_image=os.path.join( 332 self._image_dir, "x86", "system.img"), 333 local_tool_dirs=[self._tool_dir]) 334 335 with mock.patch.dict("acloud.create." 336 "goldfish_local_image_local_instance.os.environ", 337 dict(), clear=True): 338 report = self._goldfish._CreateAVD(mock_avd_spec, no_prompts=True) 339 340 self.assertEqual(report.data.get("devices"), 341 self._EXPECTED_DEVICES_IN_REPORT) 342 343 mock_instance.assert_called_once_with(3, avd_flavor="phone") 344 345 self.assertTrue(os.path.isdir(self._instance_dir)) 346 347 mock_ota_tools.FindOtaTools.assert_called_once() 348 mock_ota_tools.OtaTools.assert_called_with(self._tool_dir) 349 350 mock_ota_tools_object.BuildSuperImage.assert_called_once() 351 self.assertEqual(mock_ota_tools_object.BuildSuperImage.call_args[0][1], 352 os.path.join(self._image_dir, "x86", "misc_info.txt")) 353 354 mock_ota_tools_object.MakeDisabledVbmetaImage.assert_called_once() 355 356 mock_ota_tools_object.MkCombinedImg.assert_called_once() 357 self.assertEqual( 358 mock_ota_tools_object.MkCombinedImg.call_args[0][1], 359 os.path.join(self._image_dir, "x86", "system-qemu-config.txt")) 360 361 mock_utils.SetExecutable.assert_called_with(self._emulator_path) 362 mock_popen.assert_called_once() 363 self.assertEqual( 364 mock_popen.call_args[0][0], 365 self._GetExpectedEmulatorArgs( 366 "-gpu", "auto", "-no-window", "-qemu", "-append", 367 "androidboot.verifiedbootstate=orange")) 368 self._mock_proc.poll.assert_called() 369 370 # pylint: disable=protected-access 371 @mock.patch("acloud.create.goldfish_local_image_local_instance.instance." 372 "LocalGoldfishInstance") 373 @mock.patch("acloud.create.goldfish_local_image_local_instance.utils") 374 @mock.patch("acloud.create.goldfish_local_image_local_instance." 375 "subprocess.Popen") 376 @mock.patch("acloud.create.goldfish_local_image_local_instance.ota_tools") 377 def testCreateAVDWithMixedImageDirs(self, mock_ota_tools, mock_popen, 378 mock_utils, mock_instance): 379 """Test _CreateAVD with mixed images in build environment.""" 380 mock_ota_tools.FindOtaTools.return_value = self._tool_dir 381 mock_ota_tools_object = mock.Mock() 382 mock_ota_tools.OtaTools.return_value = mock_ota_tools_object 383 mock_ota_tools_object.MkCombinedImg.side_effect = ( 384 lambda out_path, _conf, _get_img: self._CreateEmptyFile(out_path)) 385 386 self._SetUpMocks(mock_popen, mock_utils, mock_instance) 387 388 self._CreateEmptyFile(os.path.join(self._image_dir, 389 "system-qemu.img")) 390 self._CreateEmptyFile(os.path.join(self._image_dir, 391 "system.img")) 392 self._CreateEmptyFile(os.path.join(self._image_dir, "system", 393 "build.prop")) 394 395 mock_environ = {"ANDROID_EMULATOR_PREBUILTS": 396 os.path.join(self._tool_dir, "emulator")} 397 398 mock_avd_spec = mock.Mock(flavor="phone", 399 boot_timeout_secs=None, 400 gpu="auto", 401 autoconnect=False, 402 local_instance_id=3, 403 local_instance_dir=None, 404 local_image_dir=self._image_dir, 405 local_system_image=self._image_dir, 406 local_tool_dirs=[]) 407 408 with mock.patch.dict("acloud.create." 409 "goldfish_local_image_local_instance.os.environ", 410 mock_environ, clear=True): 411 report = self._goldfish._CreateAVD(mock_avd_spec, no_prompts=True) 412 413 self.assertEqual(report.data.get("devices"), 414 self._EXPECTED_DEVICES_IN_REPORT) 415 416 mock_instance.assert_called_once_with(3, avd_flavor="phone") 417 418 self.assertTrue(os.path.isdir(self._instance_dir)) 419 420 mock_ota_tools.FindOtaTools.assert_called_once() 421 mock_ota_tools.OtaTools.assert_called_with(self._tool_dir) 422 423 mock_ota_tools_object.BuildSuperImage.assert_called_once() 424 self.assertEqual(mock_ota_tools_object.BuildSuperImage.call_args[0][1], 425 os.path.join(self._image_dir, "misc_info.txt")) 426 427 mock_ota_tools_object.MakeDisabledVbmetaImage.assert_called_once() 428 429 mock_ota_tools_object.MkCombinedImg.assert_called_once() 430 self.assertEqual( 431 mock_ota_tools_object.MkCombinedImg.call_args[0][1], 432 os.path.join(self._image_dir, "system-qemu-config.txt")) 433 434 mock_utils.SetExecutable.assert_called_with(self._emulator_path) 435 mock_popen.assert_called_once() 436 self.assertEqual( 437 mock_popen.call_args[0][0], 438 self._GetExpectedEmulatorArgs( 439 "-gpu", "auto", "-no-window", "-qemu", "-append", 440 "androidboot.verifiedbootstate=orange")) 441 self._mock_proc.poll.assert_called() 442 443 444if __name__ == "__main__": 445 unittest.main() 446