1#!/usr/bin/env python3 2# 3# Copyright 2021 - 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 17"""Unit tests for RemoteHostGoldfishDeviceFactory.""" 18 19import os 20import tempfile 21import unittest 22import zipfile 23 24from unittest import mock 25 26from acloud import errors 27from acloud.internal import constants 28from acloud.internal.lib import driver_test_lib 29from acloud.public.actions import remote_host_gf_device_factory as gf_factory 30 31 32class RemoteHostGoldfishDeviceFactoryTest(driver_test_lib.BaseDriverTest): 33 """Unit tests for RemoteHostGoldfishDeviceFactory.""" 34 35 _EMULATOR_INFO = "require version-emulator=111111\n" 36 _X86_64_BUILD_INFO = { 37 constants.BUILD_ID: "123456", 38 constants.BUILD_TARGET: "sdk_x86_64-sdk", 39 } 40 _X86_64_INSTANCE_NAME = ( 41 "host-goldfish-192.0.2.1-5554-123456-sdk_x86_64-sdk") 42 _ARM64_BUILD_INFO = { 43 constants.BUILD_ID: "123456", 44 constants.BUILD_TARGET: "sdk_arm64-sdk", 45 } 46 _ARM64_INSTANCE_NAME = ( 47 "host-goldfish-192.0.2.1-5556-123456-sdk_arm64-sdk") 48 _CFG_ATTRS = { 49 "ssh_private_key_path": "cfg_key_path", 50 "extra_args_ssh_tunnel": "extra args", 51 "emulator_build_target": "emulator-linux_x64_nolocationui", 52 } 53 _AVD_SPEC_ATTRS = { 54 "cfg": None, 55 "image_source": constants.IMAGE_SRC_REMOTE, 56 "remote_image": _X86_64_BUILD_INFO, 57 "image_download_dir": None, 58 "host_user": "user", 59 "remote_host": "192.0.2.1", 60 "host_ssh_private_key_path": None, 61 "emulator_build_id": None, 62 "emulator_build_target": None, 63 "emulator_zip": None, 64 "system_build_info": {}, 65 "boot_build_info": {}, 66 "local_image_artifact": None, 67 "local_kernel_image": None, 68 "local_system_image": None, 69 "local_system_dlkm_image": None, 70 "local_tool_dirs": [], 71 "base_instance_num": None, 72 "boot_timeout_secs": None, 73 "hw_customize": False, 74 "hw_property": {}, 75 "gpu": "auto", 76 } 77 _LOGS = [{"path": "acloud_gf_1/instance/kernel.log", "type": "KERNEL_LOG"}, 78 {"path": "acloud_gf_1/instance/emu_stderr.txt", "type": "TEXT"}, 79 {"path": "acloud_gf_1/instance/logcat.txt", "type": "LOGCAT"}] 80 _VERIFIED_BOOT_PARAMS_COMMAND = ( 81 "'test -f acloud_gf_1/image/x86_64/VerifiedBootParams.textproto && " 82 r'echo -e \\nparam: \"androidboot.verifiedbootstate=orange\" >> ' 83 "acloud_gf_1/image/x86_64/VerifiedBootParams.textproto'" 84 ) 85 _SSH_COMMAND = ( 86 "'export ANDROID_PRODUCT_OUT=~/acloud_gf_1/image/x86_64 " 87 "ANDROID_TMP=~/acloud_gf_1/instance " 88 "ANDROID_BUILD_TOP=~/acloud_gf_1/instance ; " 89 "nohup acloud_gf_1/emulator/emulator/emulator -verbose " 90 "-show-kernel -read-only -ports 5554,5555 -no-window " 91 "-logcat-output acloud_gf_1/instance/logcat.txt -gpu auto " 92 "1> acloud_gf_1/instance/kernel.log " 93 "2> acloud_gf_1/instance/emu_stderr.txt &'" 94 ) 95 96 def setUp(self): 97 super().setUp() 98 self._mock_ssh = mock.Mock() 99 self.Patch(gf_factory.ssh, "Ssh", return_value=self._mock_ssh) 100 self._mock_remote_host_client = mock.Mock() 101 self.Patch(gf_factory.remote_host_client, "RemoteHostClient", 102 return_value=self._mock_remote_host_client) 103 self._mock_create_credentials = self.Patch( 104 gf_factory.auth, "CreateCredentials") 105 # Emulator console 106 self._mock_console = mock.MagicMock() 107 self._mock_console.__enter__.return_value = self._mock_console 108 self._mock_console.Kill.side_effect = errors.DeviceConnectionError 109 ping_results = [False, True] 110 self._mock_poll_and_wait = self.Patch( 111 gf_factory.utils, 112 "PollAndWait", 113 side_effect=lambda func, **kwargs: [func() for _ in ping_results]) 114 self._mock_console.Ping.side_effect = ping_results 115 self.Patch(gf_factory.emulator_console, "RemoteEmulatorConsole", 116 return_value=self._mock_console) 117 # Android build client. 118 self._mock_android_build_client = mock.Mock() 119 self._mock_android_build_client.DownloadArtifact.side_effect = ( 120 self._MockDownloadArtifact) 121 self.Patch(gf_factory.android_build_client, "AndroidBuildClient", 122 return_value=self._mock_android_build_client) 123 # AVD spec. 124 mock_cfg = mock.Mock(spec=list(self._CFG_ATTRS.keys()), 125 **self._CFG_ATTRS) 126 self._mock_avd_spec = mock.Mock(spec=list(self._AVD_SPEC_ATTRS.keys()), 127 **self._AVD_SPEC_ATTRS) 128 self._mock_avd_spec.cfg = mock_cfg 129 130 @staticmethod 131 def _CreateSdkRepoZip(path): 132 """Create a zip file that contains a subdirectory.""" 133 with zipfile.ZipFile(path, "w") as zip_file: 134 zip_file.writestr("x86_64/build.prop", "") 135 zip_file.writestr("x86_64/test", "") 136 137 @staticmethod 138 def _CreateImageZip(path): 139 """Create a zip file containing images.""" 140 with zipfile.ZipFile(path, "w") as zip_file: 141 zip_file.writestr("system.img", "") 142 143 def _MockDownloadArtifact(self, _build_target, _build_id, resource_id, 144 local_path, _attempt): 145 if resource_id.endswith(".zip"): 146 if (resource_id.startswith("sdk-repo-") or 147 resource_id.startswith("emu-extra-")): 148 self._CreateSdkRepoZip(local_path) 149 else: 150 self._CreateImageZip(local_path) 151 elif resource_id == "emulator-info.txt": 152 with open(local_path, "w", encoding="utf-8") as file: 153 file.write(self._EMULATOR_INFO) 154 else: 155 with open(local_path, "w", encoding="utf-8") as file: 156 pass 157 158 def testCreateInstanceWithCfg(self): 159 """Test RemoteHostGoldfishDeviceFactory with default config.""" 160 factory = gf_factory.RemoteHostGoldfishDeviceFactory( 161 self._mock_avd_spec) 162 instance_name = factory.CreateInstance() 163 164 self.assertEqual(self._X86_64_INSTANCE_NAME, instance_name) 165 self.assertEqual(self._X86_64_BUILD_INFO, factory.GetBuildInfoDict()) 166 self.assertEqual([5555], factory.GetAdbPorts()) 167 self.assertEqual([None], factory.GetVncPorts()) 168 self.assertEqual({}, factory.GetFailures()) 169 self.assertEqual({instance_name: self._LOGS}, factory.GetLogs()) 170 # Artifacts. 171 self._mock_android_build_client.DownloadArtifact.assert_any_call( 172 "emulator-linux_x64_nolocationui", "111111", 173 "sdk-repo-linux-emulator-111111.zip", mock.ANY, mock.ANY) 174 self._mock_android_build_client.DownloadArtifact.assert_any_call( 175 "sdk_x86_64-sdk", "123456", 176 "sdk-repo-linux-system-images-123456.zip", mock.ANY, mock.ANY) 177 self._mock_android_build_client.DownloadArtifact.assert_any_call( 178 "sdk_x86_64-sdk", "123456", 179 "emulator-info.txt", mock.ANY, mock.ANY) 180 self.assertEqual( 181 3, self._mock_android_build_client.DownloadArtifact.call_count) 182 # Commands. 183 self._mock_ssh.Run.assert_called_with(self._SSH_COMMAND) 184 self._mock_console.Kill.assert_called_once() 185 self.assertEqual(self._mock_console.Ping.call_count, 186 self._mock_console.Reconnect.call_count + 1) 187 self._mock_console.Reconnect.assert_called() 188 # RemoteHostClient. 189 self._mock_remote_host_client.RecordTime.assert_has_calls([ 190 mock.call(constants.TIME_GCE, mock.ANY), 191 mock.call(constants.TIME_ARTIFACT, mock.ANY), 192 mock.call(constants.TIME_LAUNCH, mock.ANY)]) 193 self.assertEqual(3, 194 self._mock_remote_host_client.RecordTime.call_count) 195 self._mock_remote_host_client.SetStage.assert_has_calls([ 196 mock.call(constants.STAGE_SSH_CONNECT), 197 mock.call(constants.STAGE_ARTIFACT), 198 mock.call(constants.STAGE_BOOT_UP)]) 199 self.assertEqual(3, self._mock_remote_host_client.SetStage.call_count) 200 201 def testCreateInstanceWithAvdSpec(self): 202 """Test RemoteHostGoldfishDeviceFactory with command options.""" 203 self._mock_avd_spec.remote_image = self._ARM64_BUILD_INFO 204 self._mock_avd_spec.host_ssh_private_key_path = "key_path" 205 self._mock_avd_spec.emulator_build_id = "999999" 206 self._mock_avd_spec.emulator_build_target = "aarch64_sdk_tools_mac" 207 self._mock_avd_spec.base_instance_num = 2 208 self._mock_avd_spec.boot_timeout_secs = 1 209 self._mock_avd_spec.hw_customize = True 210 self._mock_avd_spec.hw_property = {"disk": "4096"} 211 self._mock_create_credentials.side_effect = AssertionError( 212 "CreateCredentials should not be called.") 213 # All artifacts are cached. 214 with tempfile.TemporaryDirectory() as download_dir: 215 self._mock_avd_spec.image_download_dir = download_dir 216 artifact_paths = ( 217 os.path.join(download_dir, "999999", 218 "aarch64_sdk_tools_mac", 219 "sdk-repo-darwin_aarch64-emulator-999999.zip"), 220 os.path.join(download_dir, "123456", 221 "sdk_arm64-sdk", 222 "sdk-repo-linux-system-images-123456.zip"), 223 ) 224 for artifact_path in artifact_paths: 225 os.makedirs(os.path.dirname(artifact_path), exist_ok=True) 226 self._CreateSdkRepoZip(artifact_path) 227 228 factory = gf_factory.RemoteHostGoldfishDeviceFactory( 229 self._mock_avd_spec) 230 instance_name = factory.CreateInstance() 231 232 self.assertEqual(self._ARM64_INSTANCE_NAME, instance_name) 233 self.assertEqual(self._ARM64_BUILD_INFO, factory.GetBuildInfoDict()) 234 self.assertEqual([5557], factory.GetAdbPorts()) 235 self.assertEqual([None], factory.GetVncPorts()) 236 self.assertEqual({}, factory.GetFailures()) 237 238 @mock.patch("acloud.public.actions.remote_host_gf_device_factory." 239 "goldfish_utils") 240 def testCreateInstanceWithSystemBuild(self, mock_gf_utils): 241 """Test RemoteHostGoldfishDeviceFactory with system build.""" 242 self._mock_avd_spec.system_build_info = { 243 constants.BUILD_ID: "111111", 244 constants.BUILD_TARGET: "aosp_x86_64-userdebug"} 245 mock_gf_utils.ConvertAvdSpecToArgs.return_value = ["-gpu", "auto"] 246 mock_gf_utils.MixDiskImage.return_value = "/mixed/disk" 247 mock_gf_utils.SYSTEM_QEMU_IMAGE_NAME = "system-qemu.img" 248 mock_gf_utils.VERIFIED_BOOT_PARAMS_FILE_NAME = ( 249 "VerifiedBootParams.textproto") 250 251 factory = gf_factory.RemoteHostGoldfishDeviceFactory( 252 self._mock_avd_spec) 253 factory.CreateInstance() 254 # Artifacts. 255 self._mock_android_build_client.DownloadArtifact.assert_any_call( 256 "sdk_x86_64-sdk", "123456", 257 "emu-extra-linux-system-images-123456.zip", mock.ANY, mock.ANY) 258 self._mock_android_build_client.DownloadArtifact.assert_any_call( 259 "aosp_x86_64-userdebug", "111111", 260 "aosp_x86_64-img-111111.zip", mock.ANY, mock.ANY) 261 self._mock_android_build_client.DownloadArtifact.assert_any_call( 262 "sdk_x86_64-sdk", "123456", 263 "otatools.zip", mock.ANY, mock.ANY) 264 self.assertEqual( 265 5, self._mock_android_build_client.DownloadArtifact.call_count) 266 # Images. 267 mock_gf_utils.MixDiskImage.assert_called_once() 268 self._mock_ssh.ScpPushFile.assert_called_with( 269 "/mixed/disk", "acloud_gf_1/image/x86_64/system-qemu.img") 270 self._mock_ssh.Run.assert_any_call(self._VERIFIED_BOOT_PARAMS_COMMAND) 271 272 mock_gf_utils.FormatRemoteHostInstanceName.assert_called() 273 self.assertEqual(self._X86_64_BUILD_INFO, factory.GetBuildInfoDict()) 274 self.assertEqual([5555], factory.GetAdbPorts()) 275 self.assertEqual([None], factory.GetVncPorts()) 276 self.assertEqual({}, factory.GetFailures()) 277 278 @mock.patch("acloud.public.actions.remote_host_gf_device_factory." 279 "goldfish_utils") 280 def testCreateInstanceWithBootBuild(self, mock_gf_utils): 281 """Test RemoteHostGoldfishDeviceFactory with boot build.""" 282 self._mock_avd_spec.boot_build_info = { 283 constants.BUILD_ID: "111111", 284 constants.BUILD_TARGET: "gki_x86_64-userdebug", 285 constants.BUILD_ARTIFACT: "boot-5.10.img"} 286 mock_gf_utils.ConvertAvdSpecToArgs.return_value = ["-gpu", "auto"] 287 mock_gf_utils.MixWithBootImage.return_value = ( 288 "/path/to/kernel", "/path/to/ramdisk") 289 290 factory = gf_factory.RemoteHostGoldfishDeviceFactory( 291 self._mock_avd_spec) 292 factory.CreateInstance() 293 # Artifacts. 294 self._mock_android_build_client.DownloadArtifact.assert_any_call( 295 "sdk_x86_64-sdk", "123456", 296 "sdk-repo-linux-system-images-123456.zip", mock.ANY, mock.ANY) 297 self._mock_android_build_client.DownloadArtifact.assert_any_call( 298 "gki_x86_64-userdebug", "111111", 299 "boot-5.10.img", mock.ANY, mock.ANY) 300 self._mock_android_build_client.DownloadArtifact.assert_any_call( 301 "sdk_x86_64-sdk", "123456", 302 "otatools.zip", mock.ANY, mock.ANY) 303 self.assertEqual( 304 5, self._mock_android_build_client.DownloadArtifact.call_count) 305 # Images. 306 mock_gf_utils.MixWithBootImage.assert_called_once() 307 self._mock_ssh.ScpPushFile.assert_any_call( 308 "/path/to/kernel", "acloud_gf_1/kernel") 309 self._mock_ssh.ScpPushFile.assert_any_call( 310 "/path/to/ramdisk", "acloud_gf_1/mixed_ramdisk") 311 312 mock_gf_utils.FormatRemoteHostInstanceName.assert_called() 313 self.assertEqual(self._X86_64_BUILD_INFO, factory.GetBuildInfoDict()) 314 self.assertEqual([5555], factory.GetAdbPorts()) 315 self.assertEqual([None], factory.GetVncPorts()) 316 self.assertEqual({}, factory.GetFailures()) 317 318 @mock.patch("acloud.public.actions.remote_host_gf_device_factory." 319 "ota_tools") 320 @mock.patch("acloud.public.actions.remote_host_gf_device_factory." 321 "goldfish_utils") 322 def testCreateInstanceWithLocalFiles(self, mock_gf_utils, mock_ota_tools): 323 """Test RemoteHostGoldfishDeviceFactory with local files.""" 324 with tempfile.TemporaryDirectory() as temp_dir: 325 emulator_zip_path = os.path.join(temp_dir, "emulator.zip") 326 self._CreateSdkRepoZip(emulator_zip_path) 327 image_zip_path = os.path.join(temp_dir, "image.zip") 328 self._CreateSdkRepoZip(image_zip_path) 329 boot_image_path = os.path.join(temp_dir, "boot.img") 330 self.CreateFile(boot_image_path, b"ANDROID!") 331 system_image_path = os.path.join(temp_dir, "system.img") 332 self.CreateFile(system_image_path) 333 system_dlkm_image_path = os.path.join(temp_dir, "system_dlkm.img") 334 self.CreateFile(system_dlkm_image_path) 335 self._mock_avd_spec.emulator_zip = emulator_zip_path 336 self._mock_avd_spec.image_source = constants.IMAGE_SRC_LOCAL 337 self._mock_avd_spec.remote_image = {} 338 self._mock_avd_spec.local_image_artifact = image_zip_path 339 self._mock_avd_spec.local_kernel_image = boot_image_path 340 self._mock_avd_spec.local_system_image = system_image_path 341 self._mock_avd_spec.local_system_dlkm_image = ( 342 system_dlkm_image_path) 343 self._mock_avd_spec.local_tool_dirs.append("/otatools") 344 mock_gf_utils.ConvertAvdSpecToArgs.return_value = ["-gpu", "auto"] 345 mock_gf_utils.MixWithBootImage.return_value = ( 346 "/path/to/kernel", "/path/to/ramdisk") 347 self._mock_create_credentials.side_effect = AssertionError( 348 "CreateCredentials should not be called.") 349 350 factory = gf_factory.RemoteHostGoldfishDeviceFactory( 351 self._mock_avd_spec) 352 factory.CreateInstance() 353 354 mock_gf_utils.FindSystemDlkmImage.assert_called_once() 355 mock_gf_utils.MixWithBootImage.assert_called_once() 356 mock_gf_utils.MixDiskImage.assert_called_once() 357 mock_ota_tools.FindOtaToolsDir.assert_called_once() 358 self.assertEqual("/otatools", 359 mock_ota_tools.FindOtaToolsDir.call_args[0][0][0]) 360 361 mock_gf_utils.FormatRemoteHostInstanceName.assert_called() 362 self.assertEqual({}, factory.GetBuildInfoDict()) 363 self.assertEqual([5555], factory.GetAdbPorts()) 364 self.assertEqual([None], factory.GetVncPorts()) 365 self.assertEqual({}, factory.GetFailures()) 366 367 def testCreateInstanceInitError(self): 368 """Test RemoteHostGoldfishDeviceFactory with SSH error.""" 369 self._mock_ssh.Run.side_effect = errors.DeviceConnectionError 370 371 factory = gf_factory.RemoteHostGoldfishDeviceFactory( 372 self._mock_avd_spec) 373 factory.CreateInstance() 374 375 failures = factory.GetFailures() 376 self.assertIsInstance(failures.get(self._X86_64_INSTANCE_NAME), 377 errors.DeviceConnectionError) 378 self.assertEqual({}, factory.GetLogs()) 379 self._mock_remote_host_client.RecordTime.assert_called_once_with( 380 constants.TIME_GCE, mock.ANY) 381 self._mock_remote_host_client.SetStage.assert_called_once_with( 382 constants.STAGE_SSH_CONNECT) 383 384 def testCreateInstanceDownloadError(self): 385 """Test RemoteHostGoldfishDeviceFactory with download error.""" 386 self._mock_android_build_client.DownloadArtifact.side_effect = ( 387 errors.DriverError) 388 389 factory = gf_factory.RemoteHostGoldfishDeviceFactory( 390 self._mock_avd_spec) 391 factory.CreateInstance() 392 393 failures = factory.GetFailures() 394 self.assertIsInstance(failures.get(self._X86_64_INSTANCE_NAME), 395 errors.DriverError) 396 self.assertEqual({}, factory.GetLogs()) 397 self._mock_remote_host_client.RecordTime.assert_has_calls([ 398 mock.call(constants.TIME_GCE, mock.ANY), 399 mock.call(constants.TIME_ARTIFACT, mock.ANY)]) 400 self.assertEqual(2, 401 self._mock_remote_host_client.RecordTime.call_count) 402 self._mock_remote_host_client.SetStage.assert_has_calls([ 403 mock.call(constants.STAGE_SSH_CONNECT), 404 mock.call(constants.STAGE_ARTIFACT)]) 405 self.assertEqual(2, self._mock_remote_host_client.SetStage.call_count) 406 407 def testCreateInstanceBootError(self): 408 """Test RemoteHostGoldfishDeviceFactory with boot error.""" 409 self._mock_console.Reconnect.side_effect = ( 410 errors.DeviceConnectionError) 411 412 factory = gf_factory.RemoteHostGoldfishDeviceFactory( 413 self._mock_avd_spec) 414 factory.CreateInstance() 415 416 failures = factory.GetFailures() 417 self.assertIsInstance(failures.get(self._X86_64_INSTANCE_NAME), 418 errors.DeviceBootError) 419 self.assertEqual({self._X86_64_INSTANCE_NAME: self._LOGS}, 420 factory.GetLogs()) 421 self.assertEqual(3, 422 self._mock_remote_host_client.RecordTime.call_count) 423 self.assertEqual(3, self._mock_remote_host_client.SetStage.call_count) 424 425 def testCreateInstanceTimeout(self): 426 """Test RemoteHostGoldfishDeviceFactory with timeout.""" 427 self._mock_avd_spec.boot_timeout_secs = 1 428 self._mock_poll_and_wait.side_effect = errors.DeviceBootTimeoutError() 429 430 factory = gf_factory.RemoteHostGoldfishDeviceFactory( 431 self._mock_avd_spec) 432 factory.CreateInstance() 433 434 self._mock_poll_and_wait.assert_called_once_with( 435 func=mock.ANY, 436 expected_return=True, 437 timeout_exception=errors.DeviceBootTimeoutError, 438 timeout_secs=1, 439 sleep_interval_secs=mock.ANY) 440 failures = factory.GetFailures() 441 self.assertIsInstance(failures.get(self._X86_64_INSTANCE_NAME), 442 errors.DeviceBootTimeoutError) 443 self.assertEqual({self._X86_64_INSTANCE_NAME: self._LOGS}, 444 factory.GetLogs()) 445 self.assertEqual(3, 446 self._mock_remote_host_client.RecordTime.call_count) 447 self.assertEqual(3, self._mock_remote_host_client.SetStage.call_count) 448 449 450if __name__ == "__main__": 451 unittest.main() 452