blob: 95f34702f779e6e4b4c16981dd8849b7058d4df3 [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright 2021 - The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Unit tests for RemoteHostGoldfishDeviceFactory."""
import os
import tempfile
import unittest
import zipfile
from unittest import mock
from acloud import errors
from acloud.internal import constants
from acloud.internal.lib import driver_test_lib
from acloud.public.actions import remote_host_gf_device_factory as gf_factory
class RemoteHostGoldfishDeviceFactoryTest(driver_test_lib.BaseDriverTest):
"""Unit tests for RemoteHostGoldfishDeviceFactory."""
_EMULATOR_INFO = "require version-emulator=111111\n"
_X86_64_BUILD_INFO = {
constants.BUILD_ID: "123456",
constants.BUILD_TARGET: "sdk_x86_64-sdk",
}
_X86_64_INSTANCE_NAME = (
"host-goldfish-192.0.2.1-5554-123456-sdk_x86_64-sdk")
_ARM64_BUILD_INFO = {
constants.BUILD_ID: "123456",
constants.BUILD_TARGET: "sdk_arm64-sdk",
}
_ARM64_INSTANCE_NAME = (
"host-goldfish-192.0.2.1-5554-123456-sdk_arm64-sdk")
_CFG_ATTRS = {
"ssh_private_key_path": "cfg_key_path",
"extra_args_ssh_tunnel": "extra args",
"emulator_build_target": "sdk_tools_linux",
}
_AVD_SPEC_ATTRS = {
"cfg": None,
"remote_image": _X86_64_BUILD_INFO,
"image_download_dir": None,
"host_user": "user",
"remote_host": "192.0.2.1",
"host_ssh_private_key_path": None,
"emulator_build_id": None,
"emulator_build_target": None,
"boot_timeout_secs": None,
"gpu": "auto",
}
_SSH_COMMAND = (
"'export ANDROID_PRODUCT_OUT=~/acloud_gf/image/x86_64 "
"ANDROID_TMP=~/acloud_gf/instance "
"ANDROID_BUILD_TOP=~/acloud_gf/instance ; "
"nohup acloud_gf/emulator/x86_64/emulator -verbose "
"-show-kernel -read-only -ports 5554,5555 -no-window "
"-logcat-output acloud_gf/instance/logcat.txt -gpu auto "
"1> acloud_gf/instance/stdout.txt "
"2> acloud_gf/instance/stderr.txt &'"
)
def setUp(self):
super().setUp()
self._mock_ssh = mock.Mock()
self.Patch(gf_factory.ssh, "Ssh", return_value=self._mock_ssh)
self.Patch(gf_factory.goldfish_remote_host_client,
"GoldfishRemoteHostClient")
self.Patch(gf_factory.auth, "CreateCredentials")
# Emulator console
self._mock_console = mock.MagicMock()
self._mock_console.__enter__.return_value = self._mock_console
self._mock_console.Kill.side_effect = errors.DeviceConnectionError
ping_results = [False, True]
self._mock_poll_and_wait = self.Patch(
gf_factory.utils,
"PollAndWait",
side_effect=lambda func, **kwargs: [func() for _ in ping_results])
self._mock_console.Ping.side_effect = ping_results
self.Patch(gf_factory.emulator_console, "RemoteEmulatorConsole",
return_value=self._mock_console)
# Android build client.
self._mock_android_build_client = mock.Mock()
self._mock_android_build_client.DownloadArtifact.side_effect = (
self._MockDownloadArtifact)
self.Patch(gf_factory.android_build_client, "AndroidBuildClient",
return_value=self._mock_android_build_client)
# AVD spec.
mock_cfg = mock.Mock(spec=list(self._CFG_ATTRS.keys()),
**self._CFG_ATTRS)
self._mock_avd_spec = mock.Mock(spec=list(self._AVD_SPEC_ATTRS.keys()),
**self._AVD_SPEC_ATTRS)
self._mock_avd_spec.cfg = mock_cfg
@staticmethod
def _CreateZip(path):
"""Create a zip file that contains a subdirectory."""
with zipfile.ZipFile(path, "w") as zip_file:
zip_file.writestr("x86_64/build.prop", "")
zip_file.writestr("x86_64/test", "")
def _MockDownloadArtifact(self, _build_target, _build_id, resource_id,
local_path, _attempt):
if resource_id.endswith(".zip"):
self._CreateZip(local_path)
elif resource_id == "emulator-info.txt":
with open(local_path, "w") as file:
file.write(self._EMULATOR_INFO)
else:
with open(local_path, "w") as file:
pass
def testCreateInstanceWithCfg(self):
"""Test RemoteHostGoldfishDeviceFactory with default config."""
factory = gf_factory.RemoteHostGoldfishDeviceFactory(
self._mock_avd_spec)
instance_name = factory.CreateInstance()
self.assertEqual(self._X86_64_INSTANCE_NAME, instance_name)
self.assertEqual(self._X86_64_BUILD_INFO, factory.GetBuildInfoDict())
self.assertEqual({}, factory.GetFailures())
# Artifacts.
self._mock_android_build_client.DownloadArtifact.assert_any_call(
"sdk_tools_linux", "111111",
"sdk-repo-linux-emulator-111111.zip", mock.ANY, mock.ANY)
self._mock_android_build_client.DownloadArtifact.assert_any_call(
"sdk_x86_64-sdk", "123456",
"sdk-repo-linux-system-images-123456.zip", mock.ANY, mock.ANY)
self._mock_android_build_client.DownloadArtifact.assert_any_call(
"sdk_x86_64-sdk", "123456",
"emulator-info.txt", mock.ANY, mock.ANY)
self.assertEqual(
3, self._mock_android_build_client.DownloadArtifact.call_count)
# Commands.
self._mock_ssh.Run.assert_called_with(self._SSH_COMMAND)
self._mock_console.Kill.assert_called_once()
self.assertEqual(self._mock_console.Ping.call_count,
self._mock_console.Reconnect.call_count + 1)
self._mock_console.Reconnect.assert_called()
def testCreateInstanceWithAvdSpec(self):
"""Test RemoteHostGoldfishDeviceFactory with command options."""
self._mock_avd_spec.remote_image = self._ARM64_BUILD_INFO
self._mock_avd_spec.host_ssh_private_key_path = "key_path"
self._mock_avd_spec.emulator_build_id = "999999"
self._mock_avd_spec.emulator_build_target = "aarch64_sdk_tools_mac"
self._mock_avd_spec.boot_timeout_secs = 1
self._mock_android_build_client.DownloadArtifact.side_effect = (
AssertionError("DownloadArtifact should not be called."))
# All artifacts are cached.
with tempfile.TemporaryDirectory() as download_dir:
self._mock_avd_spec.image_download_dir = download_dir
artifact_paths = (
os.path.join(download_dir, "999999",
"aarch64_sdk_tools_mac",
"sdk-repo-darwin_aarch64-emulator-999999.zip"),
os.path.join(download_dir, "123456",
"sdk_arm64-sdk",
"sdk-repo-linux-system-images-123456.zip"),
)
for artifact_path in artifact_paths:
os.makedirs(os.path.dirname(artifact_path), exist_ok=True)
self._CreateZip(artifact_path)
factory = gf_factory.RemoteHostGoldfishDeviceFactory(
self._mock_avd_spec)
instance_name = factory.CreateInstance()
self.assertEqual(self._ARM64_INSTANCE_NAME, instance_name)
self.assertEqual(self._ARM64_BUILD_INFO, factory.GetBuildInfoDict())
self.assertEqual({}, factory.GetFailures())
def testCreateInstanceError(self):
"""Test RemoteHostGoldfishDeviceFactory with boot error."""
self._mock_console.Reconnect.side_effect = (
errors.DeviceConnectionError)
factory = gf_factory.RemoteHostGoldfishDeviceFactory(
self._mock_avd_spec)
factory.CreateInstance()
failures = factory.GetFailures()
self.assertIsInstance(failures.get(self._X86_64_INSTANCE_NAME),
errors.DeviceBootError)
def testCreateInstanceTimeout(self):
"""Test RemoteHostGoldfishDeviceFactory with timeout."""
self._mock_avd_spec.boot_timeout_secs = 1
self._mock_poll_and_wait.side_effect = errors.DeviceBootTimeoutError()
factory = gf_factory.RemoteHostGoldfishDeviceFactory(
self._mock_avd_spec)
factory.CreateInstance()
self._mock_poll_and_wait.assert_called_once_with(
func=mock.ANY,
expected_return=True,
timeout_exception=errors.DeviceBootTimeoutError,
timeout_secs=1,
sleep_interval_secs=mock.ANY)
failures = factory.GetFailures()
self.assertIsInstance(failures.get(self._X86_64_INSTANCE_NAME),
errors.DeviceBootTimeoutError)
if __name__ == "__main__":
unittest.main()