• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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-5554-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": "sdk_tools_linux",
52    }
53    _AVD_SPEC_ATTRS = {
54        "cfg": None,
55        "remote_image": _X86_64_BUILD_INFO,
56        "image_download_dir": None,
57        "host_user": "user",
58        "remote_host": "192.0.2.1",
59        "host_ssh_private_key_path": None,
60        "emulator_build_id": None,
61        "emulator_build_target": None,
62        "system_build_info": {},
63        "kernel_build_info": {},
64        "boot_timeout_secs": None,
65        "hw_customize": False,
66        "hw_property": {},
67        "gpu": "auto",
68    }
69    _LOGS = [{"path": "acloud_gf/instance/kernel.log", "type": "KERNEL_LOG"},
70             {"path": "acloud_gf/instance/logcat.txt", "type": "LOGCAT"}]
71    _SSH_COMMAND = (
72        "'export ANDROID_PRODUCT_OUT=~/acloud_gf/image/x86_64 "
73        "ANDROID_TMP=~/acloud_gf/instance "
74        "ANDROID_BUILD_TOP=~/acloud_gf/instance ; "
75        "touch acloud_gf/instance/kernel.log ; "
76        "nohup acloud_gf/emulator/x86_64/emulator -verbose "
77        "-show-kernel -read-only -ports 5554,5555 -no-window "
78        "-logcat-output acloud_gf/instance/logcat.txt "
79        "-stdouterr-file acloud_gf/instance/kernel.log -gpu auto &'"
80    )
81
82    def setUp(self):
83        super().setUp()
84        self._mock_ssh = mock.Mock()
85        self.Patch(gf_factory.ssh, "Ssh", return_value=self._mock_ssh)
86        self.Patch(gf_factory.goldfish_remote_host_client,
87                   "GoldfishRemoteHostClient")
88        self.Patch(gf_factory.auth, "CreateCredentials")
89        # Emulator console
90        self._mock_console = mock.MagicMock()
91        self._mock_console.__enter__.return_value = self._mock_console
92        self._mock_console.Kill.side_effect = errors.DeviceConnectionError
93        ping_results = [False, True]
94        self._mock_poll_and_wait = self.Patch(
95            gf_factory.utils,
96            "PollAndWait",
97            side_effect=lambda func, **kwargs: [func() for _ in ping_results])
98        self._mock_console.Ping.side_effect = ping_results
99        self.Patch(gf_factory.emulator_console, "RemoteEmulatorConsole",
100                   return_value=self._mock_console)
101        # Android build client.
102        self._mock_android_build_client = mock.Mock()
103        self._mock_android_build_client.DownloadArtifact.side_effect = (
104            self._MockDownloadArtifact)
105        self.Patch(gf_factory.android_build_client, "AndroidBuildClient",
106                   return_value=self._mock_android_build_client)
107        # AVD spec.
108        mock_cfg = mock.Mock(spec=list(self._CFG_ATTRS.keys()),
109                             **self._CFG_ATTRS)
110        self._mock_avd_spec = mock.Mock(spec=list(self._AVD_SPEC_ATTRS.keys()),
111                                        **self._AVD_SPEC_ATTRS)
112        self._mock_avd_spec.cfg = mock_cfg
113
114    @staticmethod
115    def _CreateSdkRepoZip(path):
116        """Create a zip file that contains a subdirectory."""
117        with zipfile.ZipFile(path, "w") as zip_file:
118            zip_file.writestr("x86_64/build.prop", "")
119            zip_file.writestr("x86_64/test", "")
120
121    @staticmethod
122    def _CreateImageZip(path):
123        """Create a zip file containing images."""
124        with zipfile.ZipFile(path, "w") as zip_file:
125            zip_file.writestr("system.img", "")
126
127    def _MockDownloadArtifact(self, _build_target, _build_id, resource_id,
128                              local_path, _attempt):
129        if resource_id.endswith(".zip"):
130            if (resource_id.startswith("sdk-repo-") or
131                    resource_id.startswith("emu-extra-")):
132                self._CreateSdkRepoZip(local_path)
133            else:
134                self._CreateImageZip(local_path)
135        elif resource_id == "emulator-info.txt":
136            with open(local_path, "w") as file:
137                file.write(self._EMULATOR_INFO)
138        else:
139            with open(local_path, "w") as file:
140                pass
141
142    def testCreateInstanceWithCfg(self):
143        """Test RemoteHostGoldfishDeviceFactory with default config."""
144        factory = gf_factory.RemoteHostGoldfishDeviceFactory(
145            self._mock_avd_spec)
146        instance_name = factory.CreateInstance()
147
148        self.assertEqual(self._X86_64_INSTANCE_NAME, instance_name)
149        self.assertEqual(self._X86_64_BUILD_INFO, factory.GetBuildInfoDict())
150        self.assertEqual({}, factory.GetFailures())
151        self.assertEqual({instance_name: self._LOGS}, factory.GetLogs())
152        # Artifacts.
153        self._mock_android_build_client.DownloadArtifact.assert_any_call(
154            "sdk_tools_linux", "111111",
155            "sdk-repo-linux-emulator-111111.zip", mock.ANY, mock.ANY)
156        self._mock_android_build_client.DownloadArtifact.assert_any_call(
157            "sdk_x86_64-sdk", "123456",
158            "sdk-repo-linux-system-images-123456.zip", mock.ANY, mock.ANY)
159        self._mock_android_build_client.DownloadArtifact.assert_any_call(
160            "sdk_x86_64-sdk", "123456",
161            "emulator-info.txt", mock.ANY, mock.ANY)
162        self.assertEqual(
163            3, self._mock_android_build_client.DownloadArtifact.call_count)
164        # Commands.
165        self._mock_ssh.Run.assert_called_with(self._SSH_COMMAND)
166        self._mock_console.Kill.assert_called_once()
167        self.assertEqual(self._mock_console.Ping.call_count,
168                         self._mock_console.Reconnect.call_count + 1)
169        self._mock_console.Reconnect.assert_called()
170
171    def testCreateInstanceWithAvdSpec(self):
172        """Test RemoteHostGoldfishDeviceFactory with command options."""
173        self._mock_avd_spec.remote_image = self._ARM64_BUILD_INFO
174        self._mock_avd_spec.host_ssh_private_key_path = "key_path"
175        self._mock_avd_spec.emulator_build_id = "999999"
176        self._mock_avd_spec.emulator_build_target = "aarch64_sdk_tools_mac"
177        self._mock_avd_spec.boot_timeout_secs = 1
178        self._mock_avd_spec.hw_customize = True
179        self._mock_avd_spec.hw_property = {"disk": "4096"}
180        self._mock_android_build_client.DownloadArtifact.side_effect = (
181            AssertionError("DownloadArtifact should not be called."))
182        # All artifacts are cached.
183        with tempfile.TemporaryDirectory() as download_dir:
184            self._mock_avd_spec.image_download_dir = download_dir
185            artifact_paths = (
186                os.path.join(download_dir, "999999",
187                             "aarch64_sdk_tools_mac",
188                             "sdk-repo-darwin_aarch64-emulator-999999.zip"),
189                os.path.join(download_dir, "123456",
190                             "sdk_arm64-sdk",
191                             "sdk-repo-linux-system-images-123456.zip"),
192            )
193            for artifact_path in artifact_paths:
194                os.makedirs(os.path.dirname(artifact_path), exist_ok=True)
195                self._CreateSdkRepoZip(artifact_path)
196
197            factory = gf_factory.RemoteHostGoldfishDeviceFactory(
198                self._mock_avd_spec)
199            instance_name = factory.CreateInstance()
200
201        self.assertEqual(self._ARM64_INSTANCE_NAME, instance_name)
202        self.assertEqual(self._ARM64_BUILD_INFO, factory.GetBuildInfoDict())
203        self.assertEqual({}, factory.GetFailures())
204
205    @mock.patch("acloud.public.actions.remote_host_gf_device_factory."
206                "goldfish_utils")
207    def testCreateInstanceWithSystemBuild(self, mock_gf_utils):
208        """Test RemoteHostGoldfishDeviceFactory with system build."""
209        self._mock_avd_spec.system_build_info = {
210            constants.BUILD_ID: "111111",
211            constants.BUILD_TARGET: "aosp_x86_64-userdebug"}
212        mock_gf_utils.ConvertAvdSpecToArgs.return_value = ["-gpu", "auto"]
213        mock_gf_utils.MixWithSystemImage.return_value = "/mixed/disk"
214        mock_gf_utils.SYSTEM_QEMU_IMAGE_NAME = "system-qemu.img"
215
216        factory = gf_factory.RemoteHostGoldfishDeviceFactory(
217            self._mock_avd_spec)
218        instance_name = factory.CreateInstance()
219        # Artifacts.
220        self._mock_android_build_client.DownloadArtifact.assert_any_call(
221            "sdk_x86_64-sdk", "123456",
222            "emu-extra-linux-system-images-123456.zip", mock.ANY, mock.ANY)
223        self._mock_android_build_client.DownloadArtifact.assert_any_call(
224            "aosp_x86_64-userdebug", "111111",
225            "aosp_x86_64-img-111111.zip", mock.ANY, mock.ANY)
226        self._mock_android_build_client.DownloadArtifact.assert_any_call(
227            "sdk_x86_64-sdk", "123456",
228            "otatools.zip", mock.ANY, mock.ANY)
229        self.assertEqual(
230            5, self._mock_android_build_client.DownloadArtifact.call_count)
231        # Images.
232        mock_gf_utils.MixWithSystemImage.assert_called_once()
233        self._mock_ssh.ScpPushFile.assert_called_with(
234            "/mixed/disk", "acloud_gf/image/x86_64/system-qemu.img")
235
236        self.assertEqual(self._X86_64_INSTANCE_NAME, instance_name)
237        self.assertEqual(self._X86_64_BUILD_INFO, factory.GetBuildInfoDict())
238        self.assertEqual({}, factory.GetFailures())
239
240    @mock.patch("acloud.public.actions.remote_host_gf_device_factory."
241                "goldfish_utils")
242    def testCreateInstanceWithKernelBuild(self, mock_gf_utils):
243        """Test RemoteHostGoldfishDeviceFactory with kernel build."""
244        self._mock_avd_spec.kernel_build_info = {
245            constants.BUILD_ID: "111111",
246            constants.BUILD_TARGET: "aosp_x86_64-userdebug",
247            constants.BUILD_ARTIFACT: "boot-5.10.img"}
248        mock_gf_utils.ConvertAvdSpecToArgs.return_value = ["-gpu", "auto"]
249        mock_gf_utils.MixWithBootImage.return_value = (
250            "/path/to/kernel", "/path/to/ramdisk")
251
252        factory = gf_factory.RemoteHostGoldfishDeviceFactory(
253            self._mock_avd_spec)
254        instance_name = factory.CreateInstance()
255        # Artifacts.
256        self._mock_android_build_client.DownloadArtifact.assert_any_call(
257            "sdk_x86_64-sdk", "123456",
258            "sdk-repo-linux-system-images-123456.zip", mock.ANY, mock.ANY)
259        self._mock_android_build_client.DownloadArtifact.assert_any_call(
260            "aosp_x86_64-userdebug", "111111",
261            "boot-5.10.img", mock.ANY, mock.ANY)
262        self._mock_android_build_client.DownloadArtifact.assert_any_call(
263            "sdk_x86_64-sdk", "123456",
264            "otatools.zip", mock.ANY, mock.ANY)
265        self.assertEqual(
266            5, self._mock_android_build_client.DownloadArtifact.call_count)
267        # Images.
268        mock_gf_utils.MixWithBootImage.assert_called_once()
269        self._mock_ssh.ScpPushFile.assert_any_call(
270            "/path/to/kernel", "acloud_gf/kernel")
271        self._mock_ssh.ScpPushFile.assert_any_call(
272            "/path/to/ramdisk", "acloud_gf/mixed_ramdisk")
273
274        self.assertEqual(self._X86_64_INSTANCE_NAME, instance_name)
275        self.assertEqual(self._X86_64_BUILD_INFO, factory.GetBuildInfoDict())
276        self.assertEqual({}, factory.GetFailures())
277
278    def testCreateInstanceError(self):
279        """Test RemoteHostGoldfishDeviceFactory with boot error."""
280        self._mock_console.Reconnect.side_effect = (
281            errors.DeviceConnectionError)
282
283        factory = gf_factory.RemoteHostGoldfishDeviceFactory(
284            self._mock_avd_spec)
285        factory.CreateInstance()
286
287        failures = factory.GetFailures()
288        self.assertIsInstance(failures.get(self._X86_64_INSTANCE_NAME),
289                              errors.DeviceBootError)
290        self.assertEqual({self._X86_64_INSTANCE_NAME: self._LOGS},
291                         factory.GetLogs())
292
293    def testCreateInstanceTimeout(self):
294        """Test RemoteHostGoldfishDeviceFactory with timeout."""
295        self._mock_avd_spec.boot_timeout_secs = 1
296        self._mock_poll_and_wait.side_effect = errors.DeviceBootTimeoutError()
297
298        factory = gf_factory.RemoteHostGoldfishDeviceFactory(
299            self._mock_avd_spec)
300        factory.CreateInstance()
301
302        self._mock_poll_and_wait.assert_called_once_with(
303            func=mock.ANY,
304            expected_return=True,
305            timeout_exception=errors.DeviceBootTimeoutError,
306            timeout_secs=1,
307            sleep_interval_secs=mock.ANY)
308        failures = factory.GetFailures()
309        self.assertIsInstance(failures.get(self._X86_64_INSTANCE_NAME),
310                              errors.DeviceBootTimeoutError)
311        self.assertEqual({self._X86_64_INSTANCE_NAME: self._LOGS},
312                         factory.GetLogs())
313
314
315if __name__ == "__main__":
316    unittest.main()
317