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