blob: d89cd03efa50a5f4be6b16299c36b241b2e368cf [file] [log] [blame]
Fang Deng69498c32017-03-02 14:29:30 -08001#!/usr/bin/env python
2#
3# Copyright 2016 - 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.
Fang Deng69498c32017-03-02 14:29:30 -080016"""Tests for acloud.internal.lib.utils."""
17
Fang Deng26e4dc12018-03-04 19:01:59 -080018import errno
Fang Deng69498c32017-03-02 14:29:30 -080019import getpass
herbertxue07293a32018-11-05 20:40:11 +080020import grp
Fang Deng69498c32017-03-02 14:29:30 -080021import os
Fang Deng26e4dc12018-03-04 19:01:59 -080022import shutil
Fang Deng69498c32017-03-02 14:29:30 -080023import subprocess
Fang Deng26e4dc12018-03-04 19:01:59 -080024import tempfile
Fang Dengf24be082018-02-10 10:09:55 -080025import time
Fang Deng69498c32017-03-02 14:29:30 -080026
cylan0d77ae12018-05-18 08:36:48 +000027import unittest
Fang Deng69498c32017-03-02 14:29:30 -080028import mock
29
Fang Deng69498c32017-03-02 14:29:30 -080030from acloud.internal.lib import driver_test_lib
31from acloud.internal.lib import utils
32
Kevin Cheng53aa5a52018-12-03 01:33:55 -080033# Tkinter may not be supported so mock it out.
34try:
35 import Tkinter
36except ImportError:
37 Tkinter = mock.Mock()
38
Kevin Cheng358fb3e2018-11-13 14:05:54 -080039class FakeTkinter(object):
40 """Fake implementation of Tkinter.Tk()"""
41
42 def __init__(self, width=None, height=None):
43 self.width = width
44 self.height = height
45
46 # pylint: disable=invalid-name
47 def winfo_screenheight(self):
48 """Return the screen height."""
49 return self.height
50
51 # pylint: disable=invalid-name
52 def winfo_screenwidth(self):
53 """Return the screen width."""
54 return self.width
55
Fang Deng69498c32017-03-02 14:29:30 -080056
57class UtilsTest(driver_test_lib.BaseDriverTest):
cylan0d77ae12018-05-18 08:36:48 +000058 """Test Utils."""
Fang Deng69498c32017-03-02 14:29:30 -080059
cylan0d77ae12018-05-18 08:36:48 +000060 def TestTempDirSuccess(self):
61 """Test create a temp dir."""
62 self.Patch(os, "chmod")
63 self.Patch(tempfile, "mkdtemp", return_value="/tmp/tempdir")
64 self.Patch(shutil, "rmtree")
65 with utils.TempDir():
66 pass
67 # Verify.
68 tempfile.mkdtemp.assert_called_once() # pylint: disable=no-member
69 shutil.rmtree.assert_called_with("/tmp/tempdir") # pylint: disable=no-member
Fang Deng26e4dc12018-03-04 19:01:59 -080070
cylan0d77ae12018-05-18 08:36:48 +000071 def TestTempDirExceptionRaised(self):
72 """Test create a temp dir and exception is raised within with-clause."""
73 self.Patch(os, "chmod")
74 self.Patch(tempfile, "mkdtemp", return_value="/tmp/tempdir")
75 self.Patch(shutil, "rmtree")
Fang Deng26e4dc12018-03-04 19:01:59 -080076
cylan0d77ae12018-05-18 08:36:48 +000077 class ExpectedException(Exception):
78 """Expected exception."""
79 pass
Fang Deng26e4dc12018-03-04 19:01:59 -080080
cylan0d77ae12018-05-18 08:36:48 +000081 def _Call():
82 with utils.TempDir():
83 raise ExpectedException("Expected exception.")
Fang Deng26e4dc12018-03-04 19:01:59 -080084
cylan0d77ae12018-05-18 08:36:48 +000085 # Verify. ExpectedException should be raised.
86 self.assertRaises(ExpectedException, _Call)
87 tempfile.mkdtemp.assert_called_once() # pylint: disable=no-member
88 shutil.rmtree.assert_called_with("/tmp/tempdir") #pylint: disable=no-member
Fang Deng26e4dc12018-03-04 19:01:59 -080089
cylan0d77ae12018-05-18 08:36:48 +000090 def testTempDirWhenDeleteTempDirNoLongerExist(self): # pylint: disable=invalid-name
91 """Test create a temp dir and dir no longer exists during deletion."""
92 self.Patch(os, "chmod")
93 self.Patch(tempfile, "mkdtemp", return_value="/tmp/tempdir")
94 expected_error = EnvironmentError()
95 expected_error.errno = errno.ENOENT
96 self.Patch(shutil, "rmtree", side_effect=expected_error)
Fang Deng26e4dc12018-03-04 19:01:59 -080097
cylan0d77ae12018-05-18 08:36:48 +000098 def _Call():
99 with utils.TempDir():
100 pass
Fang Deng26e4dc12018-03-04 19:01:59 -0800101
cylan0d77ae12018-05-18 08:36:48 +0000102 # Verify no exception should be raised when rmtree raises
103 # EnvironmentError with errno.ENOENT, i.e.
104 # directory no longer exists.
105 _Call()
106 tempfile.mkdtemp.assert_called_once() #pylint: disable=no-member
107 shutil.rmtree.assert_called_with("/tmp/tempdir") #pylint: disable=no-member
Fang Deng26e4dc12018-03-04 19:01:59 -0800108
cylan0d77ae12018-05-18 08:36:48 +0000109 def testTempDirWhenDeleteEncounterError(self):
110 """Test create a temp dir and encoutered error during deletion."""
111 self.Patch(os, "chmod")
112 self.Patch(tempfile, "mkdtemp", return_value="/tmp/tempdir")
113 expected_error = OSError("Expected OS Error")
114 self.Patch(shutil, "rmtree", side_effect=expected_error)
Fang Deng26e4dc12018-03-04 19:01:59 -0800115
cylan0d77ae12018-05-18 08:36:48 +0000116 def _Call():
117 with utils.TempDir():
118 pass
Fang Deng26e4dc12018-03-04 19:01:59 -0800119
cylan0d77ae12018-05-18 08:36:48 +0000120 # Verify OSError should be raised.
121 self.assertRaises(OSError, _Call)
122 tempfile.mkdtemp.assert_called_once() #pylint: disable=no-member
123 shutil.rmtree.assert_called_with("/tmp/tempdir") #pylint: disable=no-member
Fang Deng26e4dc12018-03-04 19:01:59 -0800124
cylan0d77ae12018-05-18 08:36:48 +0000125 def testTempDirOrininalErrorRaised(self):
126 """Test original error is raised even if tmp dir deletion failed."""
127 self.Patch(os, "chmod")
128 self.Patch(tempfile, "mkdtemp", return_value="/tmp/tempdir")
129 expected_error = OSError("Expected OS Error")
130 self.Patch(shutil, "rmtree", side_effect=expected_error)
Fang Deng69498c32017-03-02 14:29:30 -0800131
cylan0d77ae12018-05-18 08:36:48 +0000132 class ExpectedException(Exception):
133 """Expected exception."""
134 pass
Fang Deng69498c32017-03-02 14:29:30 -0800135
cylan0d77ae12018-05-18 08:36:48 +0000136 def _Call():
137 with utils.TempDir():
138 raise ExpectedException("Expected Exception")
Fang Dengf24be082018-02-10 10:09:55 -0800139
cylan0d77ae12018-05-18 08:36:48 +0000140 # Verify.
141 # ExpectedException should be raised, and OSError
142 # should not be raised.
143 self.assertRaises(ExpectedException, _Call)
144 tempfile.mkdtemp.assert_called_once() #pylint: disable=no-member
145 shutil.rmtree.assert_called_with("/tmp/tempdir") #pylint: disable=no-member
Fang Dengf24be082018-02-10 10:09:55 -0800146
cylan0d77ae12018-05-18 08:36:48 +0000147 def testCreateSshKeyPairKeyAlreadyExists(self): #pylint: disable=invalid-name
148 """Test when the key pair already exists."""
149 public_key = "/fake/public_key"
150 private_key = "/fake/private_key"
cylan4f73c1f2018-07-19 16:40:31 +0800151 self.Patch(os.path, "exists", side_effect=[True, True])
cylan0d77ae12018-05-18 08:36:48 +0000152 self.Patch(subprocess, "check_call")
cylan4f73c1f2018-07-19 16:40:31 +0800153 self.Patch(os, "makedirs", return_value=True)
cylan0d77ae12018-05-18 08:36:48 +0000154 utils.CreateSshKeyPairIfNotExist(private_key, public_key)
155 self.assertEqual(subprocess.check_call.call_count, 0) #pylint: disable=no-member
Fang Dengf24be082018-02-10 10:09:55 -0800156
cylan0d77ae12018-05-18 08:36:48 +0000157 def testCreateSshKeyPairKeyAreCreated(self):
158 """Test when the key pair created."""
159 public_key = "/fake/public_key"
160 private_key = "/fake/private_key"
161 self.Patch(os.path, "exists", return_value=False)
cylan4f73c1f2018-07-19 16:40:31 +0800162 self.Patch(os, "makedirs", return_value=True)
cylan0d77ae12018-05-18 08:36:48 +0000163 self.Patch(subprocess, "check_call")
164 self.Patch(os, "rename")
165 utils.CreateSshKeyPairIfNotExist(private_key, public_key)
166 self.assertEqual(subprocess.check_call.call_count, 1) #pylint: disable=no-member
167 subprocess.check_call.assert_called_with( #pylint: disable=no-member
168 utils.SSH_KEYGEN_CMD +
169 ["-C", getpass.getuser(), "-f", private_key],
170 stdout=mock.ANY,
171 stderr=mock.ANY)
Fang Dengf24be082018-02-10 10:09:55 -0800172
cylan4f73c1f2018-07-19 16:40:31 +0800173 def testCreatePublicKeyAreCreated(self):
174 """Test when the PublicKey created."""
175 public_key = "/fake/public_key"
176 private_key = "/fake/private_key"
177 self.Patch(os.path, "exists", side_effect=[False, True, True])
178 self.Patch(os, "makedirs", return_value=True)
179 mock_open = mock.mock_open(read_data=public_key)
cylan4f73c1f2018-07-19 16:40:31 +0800180 self.Patch(subprocess, "check_output")
181 self.Patch(os, "rename")
Kevin Chengda4f07a2018-06-26 10:25:05 -0700182 with mock.patch("__builtin__.open", mock_open):
183 utils.CreateSshKeyPairIfNotExist(private_key, public_key)
cylan4f73c1f2018-07-19 16:40:31 +0800184 self.assertEqual(subprocess.check_output.call_count, 1) #pylint: disable=no-member
185 subprocess.check_output.assert_called_with( #pylint: disable=no-member
186 utils.SSH_KEYGEN_PUB_CMD +["-f", private_key])
187
cylan0d77ae12018-05-18 08:36:48 +0000188 def TestRetryOnException(self):
189 """Test Retry."""
Fang Dengf24be082018-02-10 10:09:55 -0800190
cylan0d77ae12018-05-18 08:36:48 +0000191 def _IsValueError(exc):
192 return isinstance(exc, ValueError)
Fang Dengf24be082018-02-10 10:09:55 -0800193
cylan0d77ae12018-05-18 08:36:48 +0000194 num_retry = 5
Fang Dengf24be082018-02-10 10:09:55 -0800195
cylan0d77ae12018-05-18 08:36:48 +0000196 @utils.RetryOnException(_IsValueError, num_retry)
197 def _RaiseAndRetry(sentinel):
198 sentinel.alert()
199 raise ValueError("Fake error.")
200
201 sentinel = mock.MagicMock()
202 self.assertRaises(ValueError, _RaiseAndRetry, sentinel)
203 self.assertEqual(1 + num_retry, sentinel.alert.call_count)
204
205 def testRetryExceptionType(self):
206 """Test RetryExceptionType function."""
207
208 def _RaiseAndRetry(sentinel):
209 sentinel.alert()
210 raise ValueError("Fake error.")
211
212 num_retry = 5
213 sentinel = mock.MagicMock()
214 self.assertRaises(
215 ValueError,
216 utils.RetryExceptionType, (KeyError, ValueError),
217 num_retry,
218 _RaiseAndRetry,
Kevin Chengd25feee2018-05-24 10:15:20 -0700219 0, # sleep_multiplier
220 1, # retry_backoff_factor
cylan0d77ae12018-05-18 08:36:48 +0000221 sentinel=sentinel)
222 self.assertEqual(1 + num_retry, sentinel.alert.call_count)
223
224 def testRetry(self):
225 """Test Retry."""
Kevin Chengd25feee2018-05-24 10:15:20 -0700226 mock_sleep = self.Patch(time, "sleep")
cylan0d77ae12018-05-18 08:36:48 +0000227
228 def _RaiseAndRetry(sentinel):
229 sentinel.alert()
230 raise ValueError("Fake error.")
231
232 num_retry = 5
233 sentinel = mock.MagicMock()
234 self.assertRaises(
235 ValueError,
236 utils.RetryExceptionType, (ValueError, KeyError),
237 num_retry,
238 _RaiseAndRetry,
Kevin Chengd25feee2018-05-24 10:15:20 -0700239 1, # sleep_multiplier
240 2, # retry_backoff_factor
cylan0d77ae12018-05-18 08:36:48 +0000241 sentinel=sentinel)
242
243 self.assertEqual(1 + num_retry, sentinel.alert.call_count)
Kevin Chengd25feee2018-05-24 10:15:20 -0700244 mock_sleep.assert_has_calls(
cylan0d77ae12018-05-18 08:36:48 +0000245 [
246 mock.call(1),
247 mock.call(2),
248 mock.call(4),
249 mock.call(8),
250 mock.call(16)
251 ])
Fang Dengf24be082018-02-10 10:09:55 -0800252
Kevin Chengeb85e862018-10-09 15:35:13 -0700253 @mock.patch("__builtin__.raw_input")
254 def testGetAnswerFromList(self, mock_raw_input):
255 """Test GetAnswerFromList."""
256 answer_list = ["image1.zip", "image2.zip", "image3.zip"]
257 mock_raw_input.return_value = 0
258 with self.assertRaises(SystemExit):
259 utils.GetAnswerFromList(answer_list)
260 mock_raw_input.side_effect = [1, 2, 3, 1]
261 self.assertEqual(utils.GetAnswerFromList(answer_list),
262 ["image1.zip"])
263 self.assertEqual(utils.GetAnswerFromList(answer_list),
264 ["image2.zip"])
265 self.assertEqual(utils.GetAnswerFromList(answer_list),
266 ["image3.zip"])
267 self.assertEqual(utils.GetAnswerFromList(answer_list,
268 enable_choose_all=True),
269 answer_list)
270
Kevin Cheng53aa5a52018-12-03 01:33:55 -0800271 @unittest.skipIf(isinstance(Tkinter, mock.Mock), "Tkinter mocked out, test case not needed.")
Kevin Cheng358fb3e2018-11-13 14:05:54 -0800272 @mock.patch.object(Tkinter, "Tk")
273 def testCalculateVNCScreenRatio(self, mock_tk):
Sam Chiu7a477f52018-10-22 11:20:36 +0800274 """Test Calculating the scale ratio of VNC display."""
Sam Chiu7a477f52018-10-22 11:20:36 +0800275 # Get scale-down ratio if screen height is smaller than AVD height.
Kevin Cheng358fb3e2018-11-13 14:05:54 -0800276 mock_tk.return_value = FakeTkinter(height=800, width=1200)
Sam Chiu7a477f52018-10-22 11:20:36 +0800277 avd_h = 1920
278 avd_w = 1080
279 self.assertEqual(utils.CalculateVNCScreenRatio(avd_w, avd_h), 0.4)
280
281 # Get scale-down ratio if screen width is smaller than AVD width.
Kevin Cheng358fb3e2018-11-13 14:05:54 -0800282 mock_tk.return_value = FakeTkinter(height=800, width=1200)
Sam Chiu7a477f52018-10-22 11:20:36 +0800283 avd_h = 900
284 avd_w = 1920
285 self.assertEqual(utils.CalculateVNCScreenRatio(avd_w, avd_h), 0.6)
286
287 # Scale ratio = 1 if screen is larger than AVD.
Kevin Cheng358fb3e2018-11-13 14:05:54 -0800288 mock_tk.return_value = FakeTkinter(height=1080, width=1920)
Sam Chiu7a477f52018-10-22 11:20:36 +0800289 avd_h = 800
290 avd_w = 1280
291 self.assertEqual(utils.CalculateVNCScreenRatio(avd_w, avd_h), 1)
292
293 # Get the scale if ratio of width is smaller than the
294 # ratio of height.
Kevin Cheng358fb3e2018-11-13 14:05:54 -0800295 mock_tk.return_value = FakeTkinter(height=1200, width=800)
Sam Chiu7a477f52018-10-22 11:20:36 +0800296 avd_h = 1920
297 avd_w = 1080
298 self.assertEqual(utils.CalculateVNCScreenRatio(avd_w, avd_h), 0.6)
299
herbertxue07293a32018-11-05 20:40:11 +0800300 # pylint: disable=protected-access
301 def testCheckUserInGroups(self):
302 """Test CheckUserInGroups."""
303 self.Patch(os, "getgroups", return_value=[1, 2, 3])
304 gr1 = mock.MagicMock()
305 gr1.gr_name = "fake_gr_1"
306 gr2 = mock.MagicMock()
307 gr2.gr_name = "fake_gr_2"
308 gr3 = mock.MagicMock()
309 gr3.gr_name = "fake_gr_3"
310 self.Patch(grp, "getgrgid", side_effect=[gr1, gr2, gr3])
311
312 # User in all required groups should return true.
313 self.assertTrue(
314 utils.CheckUserInGroups(
315 ["fake_gr_1", "fake_gr_2"]))
316
317 # User not in all required groups should return False.
318 self.Patch(grp, "getgrgid", side_effect=[gr1, gr2, gr3])
319 self.assertFalse(
320 utils.CheckUserInGroups(
321 ["fake_gr_1", "fake_gr_4"]))
322
323 @mock.patch.object(utils, "CheckUserInGroups")
324 def testAddUserGroupsToCmd(self, mock_user_group):
325 """Test AddUserGroupsToCmd."""
326 command = "test_command"
327 groups = ["group1", "group2"]
328 # Don't add user group in command
329 mock_user_group.return_value = True
330 expected_value = "test_command"
331 self.assertEqual(expected_value, utils.AddUserGroupsToCmd(command,
332 groups))
333
334 # Add user group in command
335 mock_user_group.return_value = False
336 expected_value = "sg group1 <<EOF\nsg group2\ntest_command\nEOF"
337 self.assertEqual(expected_value, utils.AddUserGroupsToCmd(command,
338 groups))
339
Fang Deng69498c32017-03-02 14:29:30 -0800340
341if __name__ == "__main__":
342 unittest.main()