• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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