blob: 792ed8a67b1ee5f7c73d6cc47300444fadc05f05 [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
Sam Chiu7de3b232018-12-06 19:45:52 +080030from acloud import errors
Fang Deng69498c32017-03-02 14:29:30 -080031from acloud.internal.lib import driver_test_lib
32from acloud.internal.lib import utils
33
herbertxue1512f8a2019-06-27 13:56:23 +080034
Kevin Cheng53aa5a52018-12-03 01:33:55 -080035# Tkinter may not be supported so mock it out.
36try:
37 import Tkinter
38except ImportError:
39 Tkinter = mock.Mock()
40
herbertxue1512f8a2019-06-27 13:56:23 +080041
Kevin Cheng358fb3e2018-11-13 14:05:54 -080042class FakeTkinter(object):
43 """Fake implementation of Tkinter.Tk()"""
44
45 def __init__(self, width=None, height=None):
46 self.width = width
47 self.height = height
48
49 # pylint: disable=invalid-name
50 def winfo_screenheight(self):
51 """Return the screen height."""
52 return self.height
53
54 # pylint: disable=invalid-name
55 def winfo_screenwidth(self):
56 """Return the screen width."""
57 return self.width
58
Fang Deng69498c32017-03-02 14:29:30 -080059
chojoyceefafc022018-11-08 17:22:16 +080060# pylint: disable=too-many-public-methods
Fang Deng69498c32017-03-02 14:29:30 -080061class UtilsTest(driver_test_lib.BaseDriverTest):
cylan0d77ae12018-05-18 08:36:48 +000062 """Test Utils."""
Fang Deng69498c32017-03-02 14:29:30 -080063
cylan0d77ae12018-05-18 08:36:48 +000064 def TestTempDirSuccess(self):
65 """Test create a temp dir."""
66 self.Patch(os, "chmod")
67 self.Patch(tempfile, "mkdtemp", return_value="/tmp/tempdir")
68 self.Patch(shutil, "rmtree")
69 with utils.TempDir():
70 pass
71 # Verify.
72 tempfile.mkdtemp.assert_called_once() # pylint: disable=no-member
73 shutil.rmtree.assert_called_with("/tmp/tempdir") # pylint: disable=no-member
Fang Deng26e4dc12018-03-04 19:01:59 -080074
cylan0d77ae12018-05-18 08:36:48 +000075 def TestTempDirExceptionRaised(self):
76 """Test create a temp dir and exception is raised within with-clause."""
77 self.Patch(os, "chmod")
78 self.Patch(tempfile, "mkdtemp", return_value="/tmp/tempdir")
79 self.Patch(shutil, "rmtree")
Fang Deng26e4dc12018-03-04 19:01:59 -080080
cylan0d77ae12018-05-18 08:36:48 +000081 class ExpectedException(Exception):
82 """Expected exception."""
83 pass
Fang Deng26e4dc12018-03-04 19:01:59 -080084
cylan0d77ae12018-05-18 08:36:48 +000085 def _Call():
86 with utils.TempDir():
87 raise ExpectedException("Expected exception.")
Fang Deng26e4dc12018-03-04 19:01:59 -080088
cylan0d77ae12018-05-18 08:36:48 +000089 # Verify. ExpectedException should be raised.
90 self.assertRaises(ExpectedException, _Call)
91 tempfile.mkdtemp.assert_called_once() # pylint: disable=no-member
92 shutil.rmtree.assert_called_with("/tmp/tempdir") #pylint: disable=no-member
Fang Deng26e4dc12018-03-04 19:01:59 -080093
cylan0d77ae12018-05-18 08:36:48 +000094 def testTempDirWhenDeleteTempDirNoLongerExist(self): # pylint: disable=invalid-name
95 """Test create a temp dir and dir no longer exists during deletion."""
96 self.Patch(os, "chmod")
97 self.Patch(tempfile, "mkdtemp", return_value="/tmp/tempdir")
98 expected_error = EnvironmentError()
99 expected_error.errno = errno.ENOENT
100 self.Patch(shutil, "rmtree", side_effect=expected_error)
Fang Deng26e4dc12018-03-04 19:01:59 -0800101
cylan0d77ae12018-05-18 08:36:48 +0000102 def _Call():
103 with utils.TempDir():
104 pass
Fang Deng26e4dc12018-03-04 19:01:59 -0800105
cylan0d77ae12018-05-18 08:36:48 +0000106 # Verify no exception should be raised when rmtree raises
107 # EnvironmentError with errno.ENOENT, i.e.
108 # directory no longer exists.
109 _Call()
110 tempfile.mkdtemp.assert_called_once() #pylint: disable=no-member
111 shutil.rmtree.assert_called_with("/tmp/tempdir") #pylint: disable=no-member
Fang Deng26e4dc12018-03-04 19:01:59 -0800112
cylan0d77ae12018-05-18 08:36:48 +0000113 def testTempDirWhenDeleteEncounterError(self):
114 """Test create a temp dir and encoutered error during deletion."""
115 self.Patch(os, "chmod")
116 self.Patch(tempfile, "mkdtemp", return_value="/tmp/tempdir")
117 expected_error = OSError("Expected OS Error")
118 self.Patch(shutil, "rmtree", side_effect=expected_error)
Fang Deng26e4dc12018-03-04 19:01:59 -0800119
cylan0d77ae12018-05-18 08:36:48 +0000120 def _Call():
121 with utils.TempDir():
122 pass
Fang Deng26e4dc12018-03-04 19:01:59 -0800123
cylan0d77ae12018-05-18 08:36:48 +0000124 # Verify OSError should be raised.
125 self.assertRaises(OSError, _Call)
126 tempfile.mkdtemp.assert_called_once() #pylint: disable=no-member
127 shutil.rmtree.assert_called_with("/tmp/tempdir") #pylint: disable=no-member
Fang Deng26e4dc12018-03-04 19:01:59 -0800128
cylan0d77ae12018-05-18 08:36:48 +0000129 def testTempDirOrininalErrorRaised(self):
130 """Test original error is raised even if tmp dir deletion failed."""
131 self.Patch(os, "chmod")
132 self.Patch(tempfile, "mkdtemp", return_value="/tmp/tempdir")
133 expected_error = OSError("Expected OS Error")
134 self.Patch(shutil, "rmtree", side_effect=expected_error)
Fang Deng69498c32017-03-02 14:29:30 -0800135
cylan0d77ae12018-05-18 08:36:48 +0000136 class ExpectedException(Exception):
137 """Expected exception."""
138 pass
Fang Deng69498c32017-03-02 14:29:30 -0800139
cylan0d77ae12018-05-18 08:36:48 +0000140 def _Call():
141 with utils.TempDir():
142 raise ExpectedException("Expected Exception")
Fang Dengf24be082018-02-10 10:09:55 -0800143
cylan0d77ae12018-05-18 08:36:48 +0000144 # Verify.
145 # ExpectedException should be raised, and OSError
146 # should not be raised.
147 self.assertRaises(ExpectedException, _Call)
148 tempfile.mkdtemp.assert_called_once() #pylint: disable=no-member
149 shutil.rmtree.assert_called_with("/tmp/tempdir") #pylint: disable=no-member
Fang Dengf24be082018-02-10 10:09:55 -0800150
cylan0d77ae12018-05-18 08:36:48 +0000151 def testCreateSshKeyPairKeyAlreadyExists(self): #pylint: disable=invalid-name
152 """Test when the key pair already exists."""
153 public_key = "/fake/public_key"
154 private_key = "/fake/private_key"
cylan4f73c1f2018-07-19 16:40:31 +0800155 self.Patch(os.path, "exists", side_effect=[True, True])
cylan0d77ae12018-05-18 08:36:48 +0000156 self.Patch(subprocess, "check_call")
cylan4f73c1f2018-07-19 16:40:31 +0800157 self.Patch(os, "makedirs", return_value=True)
cylan0d77ae12018-05-18 08:36:48 +0000158 utils.CreateSshKeyPairIfNotExist(private_key, public_key)
159 self.assertEqual(subprocess.check_call.call_count, 0) #pylint: disable=no-member
Fang Dengf24be082018-02-10 10:09:55 -0800160
cylan0d77ae12018-05-18 08:36:48 +0000161 def testCreateSshKeyPairKeyAreCreated(self):
162 """Test when the key pair created."""
163 public_key = "/fake/public_key"
164 private_key = "/fake/private_key"
165 self.Patch(os.path, "exists", return_value=False)
cylan4f73c1f2018-07-19 16:40:31 +0800166 self.Patch(os, "makedirs", return_value=True)
cylan0d77ae12018-05-18 08:36:48 +0000167 self.Patch(subprocess, "check_call")
168 self.Patch(os, "rename")
169 utils.CreateSshKeyPairIfNotExist(private_key, public_key)
170 self.assertEqual(subprocess.check_call.call_count, 1) #pylint: disable=no-member
171 subprocess.check_call.assert_called_with( #pylint: disable=no-member
172 utils.SSH_KEYGEN_CMD +
173 ["-C", getpass.getuser(), "-f", private_key],
174 stdout=mock.ANY,
175 stderr=mock.ANY)
Fang Dengf24be082018-02-10 10:09:55 -0800176
cylan4f73c1f2018-07-19 16:40:31 +0800177 def testCreatePublicKeyAreCreated(self):
178 """Test when the PublicKey created."""
179 public_key = "/fake/public_key"
180 private_key = "/fake/private_key"
181 self.Patch(os.path, "exists", side_effect=[False, True, True])
182 self.Patch(os, "makedirs", return_value=True)
183 mock_open = mock.mock_open(read_data=public_key)
cylan4f73c1f2018-07-19 16:40:31 +0800184 self.Patch(subprocess, "check_output")
185 self.Patch(os, "rename")
Kevin Chengda4f07a2018-06-26 10:25:05 -0700186 with mock.patch("__builtin__.open", mock_open):
187 utils.CreateSshKeyPairIfNotExist(private_key, public_key)
cylan4f73c1f2018-07-19 16:40:31 +0800188 self.assertEqual(subprocess.check_output.call_count, 1) #pylint: disable=no-member
189 subprocess.check_output.assert_called_with( #pylint: disable=no-member
190 utils.SSH_KEYGEN_PUB_CMD +["-f", private_key])
191
cylan0d77ae12018-05-18 08:36:48 +0000192 def TestRetryOnException(self):
193 """Test Retry."""
Fang Dengf24be082018-02-10 10:09:55 -0800194
cylan0d77ae12018-05-18 08:36:48 +0000195 def _IsValueError(exc):
196 return isinstance(exc, ValueError)
Fang Dengf24be082018-02-10 10:09:55 -0800197
cylan0d77ae12018-05-18 08:36:48 +0000198 num_retry = 5
Fang Dengf24be082018-02-10 10:09:55 -0800199
cylan0d77ae12018-05-18 08:36:48 +0000200 @utils.RetryOnException(_IsValueError, num_retry)
201 def _RaiseAndRetry(sentinel):
202 sentinel.alert()
203 raise ValueError("Fake error.")
204
205 sentinel = mock.MagicMock()
206 self.assertRaises(ValueError, _RaiseAndRetry, sentinel)
207 self.assertEqual(1 + num_retry, sentinel.alert.call_count)
208
209 def testRetryExceptionType(self):
210 """Test RetryExceptionType function."""
211
212 def _RaiseAndRetry(sentinel):
213 sentinel.alert()
214 raise ValueError("Fake error.")
215
216 num_retry = 5
217 sentinel = mock.MagicMock()
218 self.assertRaises(
219 ValueError,
220 utils.RetryExceptionType, (KeyError, ValueError),
221 num_retry,
222 _RaiseAndRetry,
Kevin Chengd25feee2018-05-24 10:15:20 -0700223 0, # sleep_multiplier
224 1, # retry_backoff_factor
cylan0d77ae12018-05-18 08:36:48 +0000225 sentinel=sentinel)
226 self.assertEqual(1 + num_retry, sentinel.alert.call_count)
227
228 def testRetry(self):
229 """Test Retry."""
Kevin Chengd25feee2018-05-24 10:15:20 -0700230 mock_sleep = self.Patch(time, "sleep")
cylan0d77ae12018-05-18 08:36:48 +0000231
232 def _RaiseAndRetry(sentinel):
233 sentinel.alert()
234 raise ValueError("Fake error.")
235
236 num_retry = 5
237 sentinel = mock.MagicMock()
238 self.assertRaises(
239 ValueError,
240 utils.RetryExceptionType, (ValueError, KeyError),
241 num_retry,
242 _RaiseAndRetry,
Kevin Chengd25feee2018-05-24 10:15:20 -0700243 1, # sleep_multiplier
244 2, # retry_backoff_factor
cylan0d77ae12018-05-18 08:36:48 +0000245 sentinel=sentinel)
246
247 self.assertEqual(1 + num_retry, sentinel.alert.call_count)
Kevin Chengd25feee2018-05-24 10:15:20 -0700248 mock_sleep.assert_has_calls(
cylan0d77ae12018-05-18 08:36:48 +0000249 [
250 mock.call(1),
251 mock.call(2),
252 mock.call(4),
253 mock.call(8),
254 mock.call(16)
255 ])
Fang Dengf24be082018-02-10 10:09:55 -0800256
Kevin Chengeb85e862018-10-09 15:35:13 -0700257 @mock.patch("__builtin__.raw_input")
258 def testGetAnswerFromList(self, mock_raw_input):
259 """Test GetAnswerFromList."""
260 answer_list = ["image1.zip", "image2.zip", "image3.zip"]
261 mock_raw_input.return_value = 0
262 with self.assertRaises(SystemExit):
263 utils.GetAnswerFromList(answer_list)
Sam Chiu71691342019-03-27 17:30:12 +0800264 mock_raw_input.side_effect = [1, 2, 3, 4]
Kevin Chengeb85e862018-10-09 15:35:13 -0700265 self.assertEqual(utils.GetAnswerFromList(answer_list),
266 ["image1.zip"])
267 self.assertEqual(utils.GetAnswerFromList(answer_list),
268 ["image2.zip"])
269 self.assertEqual(utils.GetAnswerFromList(answer_list),
270 ["image3.zip"])
271 self.assertEqual(utils.GetAnswerFromList(answer_list,
272 enable_choose_all=True),
273 answer_list)
274
Kevin Cheng53aa5a52018-12-03 01:33:55 -0800275 @unittest.skipIf(isinstance(Tkinter, mock.Mock), "Tkinter mocked out, test case not needed.")
Kevin Cheng358fb3e2018-11-13 14:05:54 -0800276 @mock.patch.object(Tkinter, "Tk")
277 def testCalculateVNCScreenRatio(self, mock_tk):
Sam Chiu7a477f52018-10-22 11:20:36 +0800278 """Test Calculating the scale ratio of VNC display."""
Sam Chiu7a477f52018-10-22 11:20:36 +0800279 # Get scale-down ratio if screen height is smaller than AVD height.
Kevin Cheng358fb3e2018-11-13 14:05:54 -0800280 mock_tk.return_value = FakeTkinter(height=800, width=1200)
Sam Chiu7a477f52018-10-22 11:20:36 +0800281 avd_h = 1920
282 avd_w = 1080
283 self.assertEqual(utils.CalculateVNCScreenRatio(avd_w, avd_h), 0.4)
284
285 # Get scale-down ratio if screen width is smaller than AVD width.
Kevin Cheng358fb3e2018-11-13 14:05:54 -0800286 mock_tk.return_value = FakeTkinter(height=800, width=1200)
Sam Chiu7a477f52018-10-22 11:20:36 +0800287 avd_h = 900
288 avd_w = 1920
289 self.assertEqual(utils.CalculateVNCScreenRatio(avd_w, avd_h), 0.6)
290
291 # Scale ratio = 1 if screen is larger than AVD.
Kevin Cheng358fb3e2018-11-13 14:05:54 -0800292 mock_tk.return_value = FakeTkinter(height=1080, width=1920)
Sam Chiu7a477f52018-10-22 11:20:36 +0800293 avd_h = 800
294 avd_w = 1280
295 self.assertEqual(utils.CalculateVNCScreenRatio(avd_w, avd_h), 1)
296
297 # Get the scale if ratio of width is smaller than the
298 # ratio of height.
Kevin Cheng358fb3e2018-11-13 14:05:54 -0800299 mock_tk.return_value = FakeTkinter(height=1200, width=800)
Sam Chiu7a477f52018-10-22 11:20:36 +0800300 avd_h = 1920
301 avd_w = 1080
302 self.assertEqual(utils.CalculateVNCScreenRatio(avd_w, avd_h), 0.6)
303
herbertxue07293a32018-11-05 20:40:11 +0800304 # pylint: disable=protected-access
305 def testCheckUserInGroups(self):
306 """Test CheckUserInGroups."""
307 self.Patch(os, "getgroups", return_value=[1, 2, 3])
308 gr1 = mock.MagicMock()
309 gr1.gr_name = "fake_gr_1"
310 gr2 = mock.MagicMock()
311 gr2.gr_name = "fake_gr_2"
312 gr3 = mock.MagicMock()
313 gr3.gr_name = "fake_gr_3"
314 self.Patch(grp, "getgrgid", side_effect=[gr1, gr2, gr3])
315
316 # User in all required groups should return true.
317 self.assertTrue(
318 utils.CheckUserInGroups(
319 ["fake_gr_1", "fake_gr_2"]))
320
321 # User not in all required groups should return False.
322 self.Patch(grp, "getgrgid", side_effect=[gr1, gr2, gr3])
323 self.assertFalse(
324 utils.CheckUserInGroups(
325 ["fake_gr_1", "fake_gr_4"]))
326
327 @mock.patch.object(utils, "CheckUserInGroups")
328 def testAddUserGroupsToCmd(self, mock_user_group):
329 """Test AddUserGroupsToCmd."""
330 command = "test_command"
331 groups = ["group1", "group2"]
332 # Don't add user group in command
333 mock_user_group.return_value = True
334 expected_value = "test_command"
335 self.assertEqual(expected_value, utils.AddUserGroupsToCmd(command,
336 groups))
337
338 # Add user group in command
339 mock_user_group.return_value = False
340 expected_value = "sg group1 <<EOF\nsg group2\ntest_command\nEOF"
341 self.assertEqual(expected_value, utils.AddUserGroupsToCmd(command,
342 groups))
343
Kevin Chengce6cfb02018-12-04 13:21:31 -0800344 @staticmethod
345 def testScpPullFileSuccess():
346 """Test scp pull file successfully."""
347 subprocess.check_call = mock.MagicMock()
348 utils.ScpPullFile("/tmp/test", "/tmp/test_1.log", "192.168.0.1")
349 subprocess.check_call.assert_called_with(utils.SCP_CMD + [
350 "192.168.0.1:/tmp/test", "/tmp/test_1.log"])
351
352 @staticmethod
353 def testScpPullFileWithUserNameSuccess():
354 """Test scp pull file successfully."""
355 subprocess.check_call = mock.MagicMock()
356 utils.ScpPullFile("/tmp/test", "/tmp/test_1.log", "192.168.0.1",
357 user_name="abc")
358 subprocess.check_call.assert_called_with(utils.SCP_CMD + [
359 "abc@192.168.0.1:/tmp/test", "/tmp/test_1.log"])
360
361 # pylint: disable=invalid-name
362 @staticmethod
363 def testScpPullFileWithUserNameWithRsaKeySuccess():
364 """Test scp pull file successfully."""
365 subprocess.check_call = mock.MagicMock()
366 utils.ScpPullFile("/tmp/test", "/tmp/test_1.log", "192.168.0.1",
367 user_name="abc", rsa_key_file="/tmp/my_key")
368 subprocess.check_call.assert_called_with(utils.SCP_CMD + [
369 "-i", "/tmp/my_key", "abc@192.168.0.1:/tmp/test",
370 "/tmp/test_1.log"])
371
372 def testScpPullFileScpFailure(self):
373 """Test scp pull file failure."""
374 subprocess.check_call = mock.MagicMock(
375 side_effect=subprocess.CalledProcessError(123, "fake",
376 "fake error"))
377 self.assertRaises(
378 errors.DeviceConnectionError,
379 utils.ScpPullFile, "/tmp/test", "/tmp/test_1.log", "192.168.0.1")
380
Fang Deng69498c32017-03-02 14:29:30 -0800381
chojoyceefafc022018-11-08 17:22:16 +0800382 def testTimeoutException(self):
383 """Test TimeoutException."""
384 @utils.TimeoutException(1, "should time out")
385 def functionThatWillTimeOut():
386 """Test decorator of @utils.TimeoutException should timeout."""
387 time.sleep(5)
388
389 self.assertRaises(errors.FunctionTimeoutError,
390 functionThatWillTimeOut)
391
392
393 def testTimeoutExceptionNoTimeout(self):
394 """Test No TimeoutException."""
395 @utils.TimeoutException(5, "shouldn't time out")
396 def functionThatShouldNotTimeout():
397 """Test decorator of @utils.TimeoutException shouldn't timeout."""
398 return None
399 try:
400 functionThatShouldNotTimeout()
401 except errors.FunctionTimeoutError:
402 self.fail("shouldn't timeout")
403
cylan4b4bbd02019-01-16 14:49:17 +0800404 def testAutoConnectCreateSSHTunnelFail(self):
cyland370db22019-07-17 16:04:00 +0800405 """Test auto connect."""
cylan4b4bbd02019-01-16 14:49:17 +0800406 fake_ip_addr = "1.1.1.1"
407 fake_rsa_key_file = "/tmp/rsa_file"
408 fake_target_vnc_port = 8888
409 target_adb_port = 9999
410 ssh_user = "fake_user"
411 call_side_effect = subprocess.CalledProcessError(123, "fake",
412 "fake error")
413 result = utils.ForwardedPorts(vnc_port=None, adb_port=None)
414 self.Patch(subprocess, "check_call", side_effect=call_side_effect)
415 self.assertEqual(result, utils.AutoConnect(fake_ip_addr,
416 fake_rsa_key_file,
417 fake_target_vnc_port,
418 target_adb_port,
419 ssh_user))
420
cyland370db22019-07-17 16:04:00 +0800421 # pylint: disable=protected-access,no-member
422 def testExtraArgsSSHTunnel(self):
423 """Tesg extra args will be the same with expanded args."""
424 fake_ip_addr = "1.1.1.1"
425 fake_rsa_key_file = "/tmp/rsa_file"
426 fake_target_vnc_port = 8888
427 target_adb_port = 9999
428 ssh_user = "fake_user"
429 fake_port = 12345
430 self.Patch(utils, "PickFreePort", return_value=fake_port)
431 self.Patch(utils, "_ExecuteCommand")
432 self.Patch(subprocess, "check_call", return_value=True)
433 extra_args_ssh_tunnel = "-o command='shell %s %h' -o command1='ls -la'"
434 utils.AutoConnect(ip_addr=fake_ip_addr,
435 rsa_key_file=fake_rsa_key_file,
436 target_vnc_port=fake_target_vnc_port,
437 target_adb_port=target_adb_port,
438 ssh_user=ssh_user,
439 client_adb_port=fake_port,
440 extra_args_ssh_tunnel=extra_args_ssh_tunnel)
441 args_list = ["-i", "/tmp/rsa_file",
442 "-o", "UserKnownHostsFile=/dev/null",
443 "-o", "StrictHostKeyChecking=no",
444 "-L", "12345:127.0.0.1:8888",
445 "-L", "12345:127.0.0.1:9999",
446 "-N", "-f", "-l", "fake_user", "1.1.1.1",
447 "-o", "command=shell %s %h",
448 "-o", "command1=ls -la"]
449 first_call_args = utils._ExecuteCommand.call_args_list[0][0]
450 self.assertEqual(first_call_args[1], args_list)
451
chojoyceefafc022018-11-08 17:22:16 +0800452
Fang Deng69498c32017-03-02 14:29:30 -0800453if __name__ == "__main__":
454 unittest.main()