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
20import mock
21
22from acloud import errors
23import acloud.create.goldfish_local_image_local_instance as instance_module
24
25
26class GoldfishLocalImageLocalInstance(unittest.TestCase):
27    """Test GoldfishLocalImageLocalInstance methods."""
28
29    _EXPECTED_DEVICES_IN_REPORT = [
30        {
31            "instance_name": "local-goldfish-instance",
32            "ip": "127.0.0.1:5555",
33            "adb_port": 5555
34        }
35    ]
36
37    def setUp(self):
38        self._goldfish = instance_module.GoldfishLocalImageLocalInstance()
39        self._temp_dir = tempfile.mkdtemp()
40        self._image_dir = os.path.join(self._temp_dir, "images")
41        self._tool_dir = os.path.join(self._temp_dir, "tool")
42        self._instance_dir = os.path.join(self._temp_dir, "instance")
43        self._emulator_is_running = False
44        self._mock_proc = mock.Mock()
45        self._mock_proc.poll.side_effect = (
46            lambda: None if self._emulator_is_running else 0)
47
48        os.mkdir(self._image_dir)
49        os.mkdir(self._tool_dir)
50
51        # Create emulator binary
52        self._emulator_path = os.path.join(self._tool_dir, "emulator",
53                                           "emulator")
54        self._CreateEmptyFile(self._emulator_path)
55
56    def tearDown(self):
57        shutil.rmtree(self._temp_dir, ignore_errors=True)
58
59    @staticmethod
60    def _CreateEmptyFile(path):
61        parent_dir = os.path.dirname(path)
62        if not os.path.exists(parent_dir):
63            os.makedirs(parent_dir)
64        with open(path, "w") as _:
65            pass
66
67    def _MockPopen(self, *_args, **_kwargs):
68        self._emulator_is_running = True
69        return self._mock_proc
70
71    def _MockEmuCommand(self, *args):
72        if not self._emulator_is_running:
73            # Connection refused
74            return 1
75
76        if args == ("kill",):
77            self._emulator_is_running = False
78            return 0
79
80        if args == ():
81            return 0
82
83        raise ValueError("Unexpected arguments " + str(args))
84
85    def _SetUpMocks(self, mock_popen, mock_adb_tools, mock_utils,
86                    mock_instance):
87        mock_utils.IsSupportedPlatform.return_value = True
88
89        mock_instance_object = mock.Mock(ip="127.0.0.1",
90                                         adb_port=5555,
91                                         console_port="5554",
92                                         device_serial="unittest",
93                                         instance_dir=self._instance_dir)
94        # name is a positional argument of Mock().
95        mock_instance_object.name = "local-goldfish-instance"
96        mock_instance.return_value = mock_instance_object
97
98        mock_adb_tools_object = mock.Mock()
99        mock_adb_tools_object.EmuCommand.side_effect = self._MockEmuCommand
100        mock_adb_tools.return_value = mock_adb_tools_object
101
102        mock_popen.side_effect = self._MockPopen
103
104    def _GetExpectedEmulatorArgs(self, *extra_args):
105        cmd = [
106            self._emulator_path, "-verbose", "-show-kernel", "-read-only",
107            "-ports", "5554,5555",
108            "-logcat-output",
109            os.path.join(self._instance_dir, "logcat.txt"),
110            "-stdouterr-file",
111            os.path.join(self._instance_dir, "stdouterr.txt")
112        ]
113        cmd.extend(extra_args)
114        return cmd
115
116    # pylint: disable=protected-access
117    @mock.patch("acloud.create.goldfish_local_image_local_instance.instance."
118                "LocalGoldfishInstance")
119    @mock.patch("acloud.create.goldfish_local_image_local_instance.utils")
120    @mock.patch("acloud.create.goldfish_local_image_local_instance."
121                "adb_tools.AdbTools")
122    @mock.patch("acloud.create.goldfish_local_image_local_instance."
123                "subprocess.Popen")
124    def testCreateAVDInBuildEnvironment(self, mock_popen, mock_adb_tools,
125                                        mock_utils, mock_instance):
126        """Test _CreateAVD with build environment variables and files."""
127        self._SetUpMocks(mock_popen, mock_adb_tools, mock_utils, mock_instance)
128
129        self._CreateEmptyFile(os.path.join(self._image_dir,
130                                           "system-qemu.img"))
131        self._CreateEmptyFile(os.path.join(self._image_dir, "system",
132                                           "build.prop"))
133
134        mock_environ = {"ANDROID_EMULATOR_PREBUILTS":
135                        os.path.join(self._tool_dir, "emulator")}
136
137        mock_avd_spec = mock.Mock(flavor="phone",
138                                  boot_timeout_secs=100,
139                                  gpu=None,
140                                  autoconnect=True,
141                                  local_instance_id=1,
142                                  local_image_dir=self._image_dir,
143                                  local_system_image_dir=None,
144                                  local_tool_dirs=[])
145
146        # Test deleting an existing instance.
147        self._emulator_is_running = True
148
149        with mock.patch.dict("acloud.create."
150                             "goldfish_local_image_local_instance.os.environ",
151                             mock_environ, clear=True):
152            report = self._goldfish._CreateAVD(mock_avd_spec, no_prompts=False)
153
154        self.assertEqual(report.data.get("devices"),
155                         self._EXPECTED_DEVICES_IN_REPORT)
156
157        mock_instance.assert_called_once_with(1, avd_flavor="phone")
158
159        self.assertTrue(os.path.isdir(self._instance_dir))
160
161        mock_popen.assert_called_once()
162        self.assertEqual(mock_popen.call_args[0][0],
163                         self._GetExpectedEmulatorArgs())
164        self._mock_proc.poll.assert_called()
165
166    # pylint: disable=protected-access
167    @mock.patch("acloud.create.goldfish_local_image_local_instance.instance."
168                "LocalGoldfishInstance")
169    @mock.patch("acloud.create.goldfish_local_image_local_instance.utils")
170    @mock.patch("acloud.create.goldfish_local_image_local_instance."
171                "adb_tools.AdbTools")
172    @mock.patch("acloud.create.goldfish_local_image_local_instance."
173                "subprocess.Popen")
174    def testCreateAVDFromSdkRepository(self, mock_popen, mock_adb_tools,
175                                       mock_utils, mock_instance):
176        """Test _CreateAVD with SDK repository files."""
177        self._SetUpMocks(mock_popen, mock_adb_tools, mock_utils, mock_instance)
178
179        self._CreateEmptyFile(os.path.join(self._image_dir, "system.img"))
180        self._CreateEmptyFile(os.path.join(self._image_dir, "build.prop"))
181
182        mock_avd_spec = mock.Mock(flavor="phone",
183                                  boot_timeout_secs=None,
184                                  gpu=None,
185                                  autoconnect=True,
186                                  local_instance_id=2,
187                                  local_image_dir=self._image_dir,
188                                  local_system_image_dir=None,
189                                  local_tool_dirs=[self._tool_dir])
190
191        with mock.patch.dict("acloud.create."
192                             "goldfish_local_image_local_instance.os.environ",
193                             dict(), clear=True):
194            report = self._goldfish._CreateAVD(mock_avd_spec, no_prompts=True)
195
196        self.assertEqual(report.data.get("devices"),
197                         self._EXPECTED_DEVICES_IN_REPORT)
198
199        mock_instance.assert_called_once_with(2, avd_flavor="phone")
200
201        self.assertTrue(os.path.isdir(self._instance_dir))
202
203        mock_popen.assert_called_once()
204        self.assertEqual(mock_popen.call_args[0][0],
205                         self._GetExpectedEmulatorArgs())
206        self._mock_proc.poll.assert_called()
207
208        self.assertTrue(os.path.isfile(
209            os.path.join(self._image_dir, "system", "build.prop")))
210
211    # pylint: disable=protected-access
212    @mock.patch("acloud.create.goldfish_local_image_local_instance.instance."
213                "LocalGoldfishInstance")
214    @mock.patch("acloud.create.goldfish_local_image_local_instance.utils")
215    @mock.patch("acloud.create.goldfish_local_image_local_instance."
216                "adb_tools.AdbTools")
217    @mock.patch("acloud.create.goldfish_local_image_local_instance."
218                "subprocess.Popen")
219    def testCreateAVDTimeout(self, mock_popen, mock_adb_tools,
220                             mock_utils, mock_instance):
221        """Test _CreateAVD with SDK repository files and timeout error."""
222        self._SetUpMocks(mock_popen, mock_adb_tools, mock_utils, mock_instance)
223        mock_utils.PollAndWait.side_effect = errors.DeviceBootTimeoutError(
224            "timeout")
225
226        self._CreateEmptyFile(os.path.join(self._image_dir, "system.img"))
227        self._CreateEmptyFile(os.path.join(self._image_dir, "build.prop"))
228
229        mock_avd_spec = mock.Mock(flavor="phone",
230                                  boot_timeout_secs=None,
231                                  gpu=None,
232                                  autoconnect=True,
233                                  local_instance_id=2,
234                                  local_image_dir=self._image_dir,
235                                  local_system_image_dir=None,
236                                  local_tool_dirs=[self._tool_dir])
237
238        with mock.patch.dict("acloud.create."
239                             "goldfish_local_image_local_instance.os.environ",
240                             dict(), clear=True):
241            report = self._goldfish._CreateAVD(mock_avd_spec, no_prompts=True)
242
243        self.assertEqual(report.data.get("devices_failing_boot"),
244                         self._EXPECTED_DEVICES_IN_REPORT)
245        self.assertEqual(report.errors, ["timeout"])
246
247    # pylint: disable=protected-access
248    @mock.patch("acloud.create.goldfish_local_image_local_instance.instance."
249                "LocalGoldfishInstance")
250    @mock.patch("acloud.create.goldfish_local_image_local_instance.utils")
251    @mock.patch("acloud.create.goldfish_local_image_local_instance."
252                "adb_tools.AdbTools")
253    @mock.patch("acloud.create.goldfish_local_image_local_instance."
254                "subprocess.Popen")
255    @mock.patch("acloud.create.goldfish_local_image_local_instance.ota_tools")
256    def testCreateAVDWithMixedImages(self, mock_ota_tools, mock_popen,
257                                     mock_adb_tools, mock_utils,
258                                     mock_instance):
259        """Test _CreateAVD with mixed images in build environment."""
260        mock_ota_tools.FindOtaTools.return_value = self._tool_dir
261        mock_ota_tools_object = mock.Mock()
262        mock_ota_tools.OtaTools.return_value = mock_ota_tools_object
263        mock_ota_tools_object.MkCombinedImg.side_effect = (
264            lambda out_path, _conf, _get_img: self._CreateEmptyFile(out_path))
265
266        self._SetUpMocks(mock_popen, mock_adb_tools, mock_utils, mock_instance)
267
268        self._CreateEmptyFile(os.path.join(self._image_dir,
269                                           "system-qemu.img"))
270        self._CreateEmptyFile(os.path.join(self._image_dir, "system",
271                                           "build.prop"))
272
273        mock_environ = {"ANDROID_EMULATOR_PREBUILTS":
274                        os.path.join(self._tool_dir, "emulator")}
275
276        mock_utils.GetBuildEnvironmentVariable.side_effect = (
277            lambda key: mock_environ[key])
278
279        mock_avd_spec = mock.Mock(flavor="phone",
280                                  boot_timeout_secs=None,
281                                  gpu="auto",
282                                  autoconnect=False,
283                                  local_instance_id=3,
284                                  local_image_dir=self._image_dir,
285                                  local_system_image_dir="/unit/test",
286                                  local_tool_dirs=[])
287
288        with mock.patch.dict("acloud.create."
289                             "goldfish_local_image_local_instance.os.environ",
290                             mock_environ, clear=True):
291            report = self._goldfish._CreateAVD(mock_avd_spec, no_prompts=True)
292
293        self.assertEqual(report.data.get("devices"),
294                         self._EXPECTED_DEVICES_IN_REPORT)
295
296        mock_instance.assert_called_once_with(3, avd_flavor="phone")
297
298        self.assertTrue(os.path.isdir(self._instance_dir))
299
300        mock_ota_tools.FindOtaTools.assert_called_once()
301        mock_ota_tools.OtaTools.assert_called_with(self._tool_dir)
302
303        mock_ota_tools_object.BuildSuperImage.assert_called_once()
304        self.assertEqual(mock_ota_tools_object.BuildSuperImage.call_args[0][1],
305                         os.path.join(self._image_dir, "misc_info.txt"))
306
307        mock_ota_tools_object.MakeDisabledVbmetaImage.assert_called_once()
308
309        mock_ota_tools_object.MkCombinedImg.assert_called_once()
310        self.assertEqual(
311            mock_ota_tools_object.MkCombinedImg.call_args[0][1],
312            os.path.join(self._image_dir, "system-qemu-config.txt"))
313
314        mock_popen.assert_called_once()
315        self.assertEqual(
316            mock_popen.call_args[0][0],
317            self._GetExpectedEmulatorArgs(
318                "-gpu", "auto", "-no-window", "-qemu", "-append",
319                "androidboot.verifiedbootstate=orange"))
320        self._mock_proc.poll.assert_called()
321
322
323if __name__ == "__main__":
324    unittest.main()
325