Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(201)

Side by Side Diff: build/android/pylib/device/device_utils.py

Issue 751063002: Allow RunShellCommand to work even with very large commands (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: clients of DeviceTempFile should pass adb Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 # Copyright 2014 The Chromium Authors. All rights reserved. 1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """Provides a variety of device interactions based on adb. 5 """Provides a variety of device interactions based on adb.
6 6
7 Eventually, this will be based on adb_wrapper. 7 Eventually, this will be based on adb_wrapper.
8 """ 8 """
9 # pylint: disable=W0613 9 # pylint: disable=W0613
10 10
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
51 """Restarts the adb server. 51 """Restarts the adb server.
52 52
53 Raises: 53 Raises:
54 CommandFailedError if we fail to kill or restart the server. 54 CommandFailedError if we fail to kill or restart the server.
55 """ 55 """
56 pylib.android_commands.AndroidCommands().RestartAdbServer() 56 pylib.android_commands.AndroidCommands().RestartAdbServer()
57 57
58 58
59 class DeviceUtils(object): 59 class DeviceUtils(object):
60 60
61 _MAX_ADB_COMMAND_LENGTH = 512
61 _VALID_SHELL_VARIABLE = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$') 62 _VALID_SHELL_VARIABLE = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$')
62 63
63 def __init__(self, device, default_timeout=_DEFAULT_TIMEOUT, 64 def __init__(self, device, default_timeout=_DEFAULT_TIMEOUT,
64 default_retries=_DEFAULT_RETRIES): 65 default_retries=_DEFAULT_RETRIES):
65 """DeviceUtils constructor. 66 """DeviceUtils constructor.
66 67
67 Args: 68 Args:
68 device: Either a device serial, an existing AdbWrapper instance, an 69 device: Either a device serial, an existing AdbWrapper instance, an
69 an existing AndroidCommands instance, or nothing. 70 an existing AndroidCommands instance, or nothing.
70 default_timeout: An integer containing the default number of seconds to 71 default_timeout: An integer containing the default number of seconds to
(...skipping 291 matching lines...) Expand 10 before | Expand all | Expand 10 after
362 if should_install: 363 if should_install:
363 try: 364 try:
364 out = self.old_interface.Install(apk_path, reinstall=reinstall) 365 out = self.old_interface.Install(apk_path, reinstall=reinstall)
365 for line in out.splitlines(): 366 for line in out.splitlines():
366 if 'Failure' in line: 367 if 'Failure' in line:
367 raise device_errors.CommandFailedError(line.strip(), str(self)) 368 raise device_errors.CommandFailedError(line.strip(), str(self))
368 except AssertionError as e: 369 except AssertionError as e:
369 raise device_errors.CommandFailedError( 370 raise device_errors.CommandFailedError(
370 str(e), str(self)), None, sys.exc_info()[2] 371 str(e), str(self)), None, sys.exc_info()[2]
371 372
373 def _PrepareShellCommand(self, cmd, cwd=None, env=None, as_root=False):
jbudorick 2014/11/27 17:17:34 Why is this separate from RunShellCommand? Would w
perezju 2014/11/28 15:39:34 see below vv
374 def env_quote(key, value):
375 if not DeviceUtils._VALID_SHELL_VARIABLE.match(key):
376 raise KeyError('Invalid shell variable name %r' % key)
377 # using double quotes here to allow interpolation of shell variables
378 return '%s=%s' % (key, cmd_helper.DoubleQuote(value))
379
380 if not isinstance(cmd, basestring):
381 cmd = ' '.join(cmd_helper.SingleQuote(s) for s in cmd)
382 if env:
383 env = ' '.join(env_quote(k, v) for k, v in env.iteritems())
384 cmd = '%s %s' % (env, cmd)
385 if cwd:
386 cmd = 'cd %s && %s' % (cmd_helper.SingleQuote(cwd), cmd)
387 if as_root and self.NeedsSU():
388 # "su -c sh -c" allows using shell features in |cmd|
389 cmd = 'su -c sh -c %s' % cmd_helper.SingleQuote(cmd)
390 return cmd
391
372 @decorators.WithTimeoutAndRetriesFromInstance() 392 @decorators.WithTimeoutAndRetriesFromInstance()
373 def RunShellCommand(self, cmd, check_return=False, cwd=None, env=None, 393 def RunShellCommand(self, cmd, check_return=False, cwd=None, env=None,
374 as_root=False, single_line=False, 394 as_root=False, single_line=False,
375 timeout=None, retries=None): 395 timeout=None, retries=None):
376 """Run an ADB shell command. 396 """Run an ADB shell command.
377 397
378 The command to run |cmd| should be a sequence of program arguments or else 398 The command to run |cmd| should be a sequence of program arguments or else
379 a single string. 399 a single string.
380 400
381 When |cmd| is a sequence, it is assumed to contain the name of the command 401 When |cmd| is a sequence, it is assumed to contain the name of the command
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
414 (with the optional newline at the end stripped). 434 (with the optional newline at the end stripped).
415 435
416 Raises: 436 Raises:
417 AdbCommandFailedError if check_return is True and the exit code of 437 AdbCommandFailedError if check_return is True and the exit code of
418 the command run on the device is non-zero. 438 the command run on the device is non-zero.
419 CommandFailedError if single_line is True but the output contains two or 439 CommandFailedError if single_line is True but the output contains two or
420 more lines. 440 more lines.
421 CommandTimeoutError on timeout. 441 CommandTimeoutError on timeout.
422 DeviceUnreachableError on missing device. 442 DeviceUnreachableError on missing device.
423 """ 443 """
424 def env_quote(key, value): 444 def do_run(cmd):
425 if not DeviceUtils._VALID_SHELL_VARIABLE.match(key): 445 try:
426 raise KeyError('Invalid shell variable name %r' % key) 446 return self.adb.Shell(cmd)
427 # using double quotes here to allow interpolation of shell variables 447 except device_errors.AdbCommandFailedError as exc:
428 return '%s=%s' % (key, cmd_helper.DoubleQuote(value)) 448 if check_return:
449 raise
450 else:
451 return exc.output
429 452
430 if not isinstance(cmd, basestring):
431 cmd = ' '.join(cmd_helper.SingleQuote(s) for s in cmd)
432 if env:
433 env = ' '.join(env_quote(k, v) for k, v in env.iteritems())
434 cmd = '%s %s' % (env, cmd)
435 if cwd:
436 cmd = 'cd %s && %s' % (cmd_helper.SingleQuote(cwd), cmd)
437 if as_root and self.NeedsSU():
438 # "su -c sh -c" allows using shell features in |cmd|
439 cmd = 'su -c sh -c %s' % cmd_helper.SingleQuote(cmd)
440 if timeout is None: 453 if timeout is None:
441 timeout = self._default_timeout 454 timeout = self._default_timeout
442 455
443 try: 456 cmd = self._PrepareShellCommand(cmd, cwd=cwd, env=env, as_root=as_root)
444 output = self.adb.Shell(cmd) 457 if len(cmd) < self._MAX_ADB_COMMAND_LENGTH:
445 except device_errors.AdbCommandFailedError as e: 458 output = do_run(cmd)
446 if check_return: 459 else:
447 raise 460 with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script:
448 else: 461 self.PushContents(cmd, script.name)
449 output = e.output 462 output = do_run('sh %s' % script.name_quoted)
450 463
451 output = output.splitlines() 464 output = output.splitlines()
452 if single_line: 465 if single_line:
453 if not output: 466 if not output:
454 return '' 467 return ''
455 elif len(output) == 1: 468 elif len(output) == 1:
456 return output[0] 469 return output[0]
457 else: 470 else:
458 msg = 'one line of output was expected, but got: %s' 471 msg = 'one line of output was expected, but got: %s'
459 raise device_errors.CommandFailedError(msg % output, str(self)) 472 raise device_errors.CommandFailedError(msg % output, str(self))
(...skipping 417 matching lines...) Expand 10 before | Expand all | Expand 10 after
877 # the implementation switch, and if file not found should raise exception. 890 # the implementation switch, and if file not found should raise exception.
878 if as_root: 891 if as_root:
879 if not self.old_interface.CanAccessProtectedFileContents(): 892 if not self.old_interface.CanAccessProtectedFileContents():
880 raise device_errors.CommandFailedError( 893 raise device_errors.CommandFailedError(
881 'Cannot read from %s with root privileges.' % device_path) 894 'Cannot read from %s with root privileges.' % device_path)
882 return self.old_interface.GetProtectedFileContents(device_path) 895 return self.old_interface.GetProtectedFileContents(device_path)
883 else: 896 else:
884 return self.old_interface.GetFileContents(device_path) 897 return self.old_interface.GetFileContents(device_path)
885 898
886 @decorators.WithTimeoutAndRetriesFromInstance() 899 @decorators.WithTimeoutAndRetriesFromInstance()
900 def PushContents(self, contents, device_path, timeout=None, retries=None):
jbudorick 2014/11/27 17:17:34 I don't think this should be part of the public in
perezju 2014/11/28 15:39:34 The argument order is consistent with that of Push
jbudorick 2014/11/28 18:49:01 The three should probably be consistent. Perhaps w
perezju 2014/11/28 19:43:37 I'm not sure we want to do that. That would requir
901 """Pushes |contents| to a file on the device.
902
903 Note: If the contents are small, or root access is needed, then |WriteFile|
904 is a better alternative.
905
906 Args:
907 contents: A string containing the data to write to the device.
908 device_path: A string containing the absolute path to the file to write
909 on the device.
910 timeout: timeout in seconds
911 retries: number of retries
912
913 Raises:
914 CommandFailedError if the file could not be written on the device.
915 CommandTimeoutError on timeout.
916 DeviceUnreachableError on missing device.
917 """
918 with tempfile.NamedTemporaryFile() as host_temp:
919 host_temp.write(contents)
920 host_temp.flush()
921 self.adb.Push(host_temp.name, device_path)
922
923 @decorators.WithTimeoutAndRetriesFromInstance()
887 def WriteFile(self, device_path, contents, as_root=False, force_push=False, 924 def WriteFile(self, device_path, contents, as_root=False, force_push=False,
888 timeout=None, retries=None): 925 timeout=None, retries=None):
889 """Writes |contents| to a file on the device. 926 """Writes |contents| to a file on the device.
890 927
891 Args: 928 Args:
892 device_path: A string containing the absolute path to the file to write 929 device_path: A string containing the absolute path to the file to write
893 on the device. 930 on the device.
894 contents: A string containing the data to write to the device. 931 contents: A string containing the data to write to the device.
895 as_root: A boolean indicating whether the write should be executed with 932 as_root: A boolean indicating whether the write should be executed with
896 root privileges (if available). 933 root privileges (if available).
897 force_push: A boolean indicating whether to force the operation to be 934 force_push: A boolean indicating whether to force the operation to be
898 performed by pushing a file to the device. The default is, when the 935 performed by pushing a file to the device. The default is, when the
899 contents are short, to pass the contents using a shell script instead. 936 contents are short, to pass the contents using a shell script instead.
900 timeout: timeout in seconds 937 timeout: timeout in seconds
901 retries: number of retries 938 retries: number of retries
902 939
903 Raises: 940 Raises:
904 CommandFailedError if the file could not be written on the device. 941 CommandFailedError if the file could not be written on the device.
905 CommandTimeoutError on timeout. 942 CommandTimeoutError on timeout.
906 DeviceUnreachableError on missing device. 943 DeviceUnreachableError on missing device.
907 """ 944 """
908 if len(contents) < 512 and not force_push: 945 if not force_push and len(contents) < self._MAX_ADB_COMMAND_LENGTH:
909 cmd = 'echo -n %s > %s' % (cmd_helper.SingleQuote(contents), 946 cmd = self._PrepareShellCommand(
910 cmd_helper.SingleQuote(device_path)) 947 'echo -n %s > %s' % (cmd_helper.SingleQuote(contents),
911 self.RunShellCommand(cmd, as_root=as_root, check_return=True) 948 cmd_helper.SingleQuote(device_path)),
949 as_root=as_root)
perezju 2014/11/28 15:39:34 ^^ this is where I need to prepare the command bef
jbudorick 2014/11/28 18:49:01 If you "prepare" a shell command twice -- as you w
perezju 2014/11/28 19:43:37 Nothing happens. Because the second "prepare" gets
950 if len(cmd) < self._MAX_ADB_COMMAND_LENGTH:
951 self.RunShellCommand(cmd, check_return=True)
952 return
953 if as_root and self.NeedsSU():
954 with device_temp_file.DeviceTempFile(self.adb) as device_temp:
955 self.PushContents(contents, device_temp.name)
956 # Here we need 'cp' rather than 'mv' because the temp and
957 # destination files might be on different file systems (e.g.
958 # on internal storage and an external sd card)
959 self.RunShellCommand(['cp', device_temp.name, device_path],
960 as_root=True, check_return=True)
912 else: 961 else:
913 with tempfile.NamedTemporaryFile() as host_temp: 962 self.PushContents(contents, device_path)
914 host_temp.write(contents)
915 host_temp.flush()
916 if as_root and self.NeedsSU():
917 with device_temp_file.DeviceTempFile(self) as device_temp:
918 self.adb.Push(host_temp.name, device_temp.name)
919 # Here we need 'cp' rather than 'mv' because the temp and
920 # destination files might be on different file systems (e.g.
921 # on internal storage and an external sd card)
922 self.RunShellCommand(['cp', device_temp.name, device_path],
923 as_root=True, check_return=True)
924 else:
925 self.adb.Push(host_temp.name, device_path)
926 963
927 @decorators.WithTimeoutAndRetriesFromInstance() 964 @decorators.WithTimeoutAndRetriesFromInstance()
928 def Ls(self, device_path, timeout=None, retries=None): 965 def Ls(self, device_path, timeout=None, retries=None):
929 """Lists the contents of a directory on the device. 966 """Lists the contents of a directory on the device.
930 967
931 Args: 968 Args:
932 device_path: A string containing the path of the directory on the device 969 device_path: A string containing the path of the directory on the device
933 to list. 970 to list.
934 timeout: timeout in seconds 971 timeout: timeout in seconds
935 retries: number of retries 972 retries: number of retries
(...skipping 220 matching lines...) Expand 10 before | Expand all | Expand 10 after
1156 Returns: 1193 Returns:
1157 A Parallelizer operating over |devices|. 1194 A Parallelizer operating over |devices|.
1158 """ 1195 """
1159 if not devices or len(devices) == 0: 1196 if not devices or len(devices) == 0:
1160 devices = pylib.android_commands.GetAttachedDevices() 1197 devices = pylib.android_commands.GetAttachedDevices()
1161 parallelizer_type = (parallelizer.Parallelizer if async 1198 parallelizer_type = (parallelizer.Parallelizer if async
1162 else parallelizer.SyncParallelizer) 1199 else parallelizer.SyncParallelizer)
1163 return parallelizer_type([ 1200 return parallelizer_type([
1164 d if isinstance(d, DeviceUtils) else DeviceUtils(d) 1201 d if isinstance(d, DeviceUtils) else DeviceUtils(d)
1165 for d in devices]) 1202 for d in devices])
OLDNEW
« no previous file with comments | « no previous file | build/android/pylib/device/device_utils_test.py » ('j') | build/android/pylib/utils/device_temp_file.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698