Bug 1034406 - [mozdevice] Do not allow ADBCommand to be instantiated, r=wlach, DONTBUILD.
authorBob Clary <bclary@bclary.com>
Mon, 07 Jul 2014 08:19:35 -0700
changeset 192615 b2535c273dc4b2e532831f81a8339ec33728fcbd
parent 192614 51895ae51261625cd663f1a6acffc7b8153f765f
child 192616 3904c720efa432b024e0344e6b827427303c70cd
push id45884
push userbclary@mozilla.com
push dateMon, 07 Jul 2014 15:20:06 +0000
treeherdermozilla-inbound@b2535c273dc4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerswlach, DONTBUILD
bugs1034406
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1034406 - [mozdevice] Do not allow ADBCommand to be instantiated, r=wlach, DONTBUILD.
testing/mozbase/docs/mozdevice.rst
testing/mozbase/mozdevice/mozdevice/adb.py
--- a/testing/mozbase/docs/mozdevice.rst
+++ b/testing/mozbase/docs/mozdevice.rst
@@ -146,17 +146,17 @@ ADBCommand
 
 ADBHost
 ```````
 .. autoclass:: mozdevice.ADBHost
 
 .. automethod:: ADBHost.command(self, cmds, timeout=None)
 .. automethod:: ADBHost.command_output(self, cmds, timeout=None)
 .. automethod:: ADBHost.start_server(self, timeout=None)
-.. automethod:: ADBHost.kill_server(self, cmds, timeout=None)
+.. automethod:: ADBHost.kill_server(self, timeout=None)
 .. automethod:: ADBHost.devices(self, timeout=None)
 
 ADBDevice
 `````````
 .. autoclass:: mozdevice.ADBDevice
 
 Host Command methods
 ++++++++++++++++++++
--- a/testing/mozbase/mozdevice/mozdevice/adb.py
+++ b/testing/mozbase/mozdevice/mozdevice/adb.py
@@ -94,25 +94,33 @@ class ADBTimeoutError(Exception):
     * Rebooting the device manually.
     * Rebooting the host.
 
     """
     pass
 
 
 class ADBCommand(object):
-    """ADBCommand provides a basic interface to adb commands.
+    """ADBCommand provides a basic interface to adb commands
+    which is used to provide the 'command' methods for the
+    classes ADBHost and ADBDevice.
+
+    ADBCommand should only be used as the base class for other
+    classes and should not be instantiated directly. To enforce this
+    restriction calling ADBCommand's constructor will raise a
+    NonImplementedError exception.
 
     ::
 
        from mozdevice import ADBCommand
 
-       adbcommand = ADBCommand(...)
-       print adbcommand.command_output(["devices"])
-
+       try:
+           adbcommand = ADBCommand(...)
+       except NotImplementedError:
+           print "ADBCommand can not be instantiated."
 
     """
 
     def __init__(self,
                  adb='adb',
                  logger_name='adb',
                  log_level=logging.INFO,
                  timeout=300):
@@ -121,70 +129,74 @@ class ADBCommand(object):
         :param adb: path to adb executable. Defaults to 'adb'.
         :param logger_name: logging logger name. Defaults to 'adb'.
         :param log_level: logging level. Defaults to logging.INFO.
 
         :raises: * ADBError
                  * ADBTimeoutError
 
         """
+        if self.__class__ == ADBCommand:
+            raise NotImplementedError
+
         self._logger = logging.getLogger(logger_name)
         self._adb_path = adb
         self._log_level = log_level
         self._timeout = timeout
         self._polling_interval = 0.1
 
-        self._logger.debug("ADBCommand: %s" % self.__dict__)
+        self._logger.debug("%s: %s" % (self.__class__.__name__,
+                                       self.__dict__))
 
     # Host Command methods
 
     def command(self, cmds, device_serial=None, timeout=None):
         """Executes an adb command on the host.
 
         :param cmds: list containing the command and its arguments to be
             executed.
-        :param device_serial: optional string specifying the device'
+        :param device_serial: optional string specifying the device's
             serial number if the adb command is to be executed against
             a specific device.
         :param timeout: optional integer specifying the maximum time in
             seconds for any spawned adb process to complete before
             throwing an ADBTimeoutError.  This timeout is per adb call. The
             total time spent may exceed this value. If it is not
             specified, the value set in the ADBCommand constructor is used.
         :returns: :class:`mozdevice.ADBProcess`
 
         command() provides a low level interface for executing
         commands on the host via adb.
 
-        For commands targeting specific devices, ADBDevice.command is
-        preferred. To execute shell commands on specific devices, 
-        ADBDevice.shell is preferred.
+        command() executes on the host in such a fashion that stdout
+        and stderr of the adb process are file handles on the host and
+        the exit code is available as the exit code of the adb
+        process.
 
         The caller provides a list containing commands, as well as a
         timeout period in seconds.
 
         A subprocess is spawned to execute adb with stdout and stderr
-        directed to named temporary files. If the process takes longer
-        than the specified timeout, the process is terminated.
+        directed to temporary files. If the process takes longer than
+        the specified timeout, the process is terminated.
 
         It is the caller's responsibilty to clean up by closing
         the stdout and stderr temporary files.
 
         """
         args = [self._adb_path]
         if device_serial:
             args.extend(['-s', device_serial, 'wait-for-device'])
         args.extend(cmds)
 
         adb_process = ADBProcess(args)
 
         if timeout is None:
             timeout = self._timeout
 
-        timeout = int(timeout)
         start_time = time.time()
         adb_process.exitcode = adb_process.proc.poll()
         while ((time.time() - start_time) <= timeout and
                adb_process.exitcode == None):
             time.sleep(self._polling_interval)
             adb_process.exitcode = adb_process.proc.poll()
         if adb_process.exitcode == None:
             adb_process.proc.kill()
@@ -196,17 +208,17 @@ class ADBCommand(object):
 
         return adb_process
 
     def command_output(self, cmds, device_serial=None, timeout=None):
         """Executes an adb command on the host returning stdout.
 
         :param cmds: list containing the command and its arguments to be
             executed.
-        :param device_serial: optional string specifying the device'
+        :param device_serial: optional string specifying the device's
             serial number if the adb command is to be executed against
             a specific device.
         :param timeout: optional integer specifying the maximum time in seconds
             for any spawned adb process to complete before throwing
             an ADBTimeoutError.
             This timeout is per adb call. The total time spent
             may exceed this value. If it is not specified, the value
             set in the ADBCommand constructor is used.
@@ -243,26 +255,99 @@ class ADBCommand(object):
             return output
         finally:
             if adb_process and isinstance(adb_process.stdout_file, file):
                 adb_process.stdout_file.close()
                 adb_process.stderr_file.close()
 
 
 class ADBHost(ADBCommand):
-    """ADBHost provides a basic interface to adb host commands.
+    """ADBHost provides a basic interface to adb host commands
+    which do not target a specific device.
 
     ::
 
        from mozdevice import ADBHost
 
        adbhost = ADBHost(...)
        adbhost.start_server()
 
     """
+    def __init__(self,
+                 adb='adb',
+                 logger_name='adb',
+                 log_level=logging.INFO,
+                 timeout=300):
+        """Initializes the ADBHost object.
+
+        :param adb: path to adb executable. Defaults to 'adb'.
+        :param logger_name: logging logger name. Defaults to 'adb'.
+        :param log_level: logging level. Defaults to logging.INFO.
+
+        :raises: * ADBError
+                 * ADBTimeoutError
+
+        """
+        ADBCommand.__init__(self, adb=adb, logger_name=logger_name,
+                            log_level=log_level, timeout=timeout)
+
+    def command(self, cmds, timeout=None):
+        """Executes an adb command on the host.
+
+        :param cmds: list containing the command and its arguments to be
+            executed.
+        :param timeout: optional integer specifying the maximum time in
+            seconds for any spawned adb process to complete before
+            throwing an ADBTimeoutError.  This timeout is per adb call. The
+            total time spent may exceed this value. If it is not
+            specified, the value set in the ADBHost constructor is used.
+        :returns: :class:`mozdevice.ADBProcess`
+
+        command() provides a low level interface for executing
+        commands on the host via adb.
+
+        command() executes on the host in such a fashion that stdout
+        and stderr of the adb process are file handles on the host and
+        the exit code is available as the exit code of the adb
+        process.
+
+        The caller provides a list containing commands, as well as a
+        timeout period in seconds.
+
+        A subprocess is spawned to execute adb with stdout and stderr
+        directed to temporary files. If the process takes longer than
+        the specified timeout, the process is terminated.
+
+        It is the caller's responsibilty to clean up by closing
+        the stdout and stderr temporary files.
+
+        """
+        return ADBCommand.command(self, cmds, timeout=timeout)
+
+    def command_output(self, cmds, timeout=None):
+        """Executes an adb command on the host returning stdout.
+
+        :param cmds: list containing the command and its arguments to be
+            executed.
+        :param timeout: optional integer specifying the maximum time in seconds
+            for any spawned adb process to complete before throwing
+            an ADBTimeoutError.
+            This timeout is per adb call. The total time spent
+            may exceed this value. If it is not specified, the value
+            set in the ADBHost constructor is used.
+        :returns: string - content of stdout.
+
+        :raises: * ADBTimeoutError - raised if the command takes longer than
+                   timeout seconds.
+                 * ADBError - raised if the command exits with a
+                   non-zero exit code.
+
+        """
+        return ADBCommand.command_output(self, cmds, timeout=timeout)
+
     def start_server(self, timeout=None):
         """Starts the adb server.
 
         :param timeout: optional integer specifying the maximum time in
             seconds for any spawned adb process to complete before
             throwing an ADBTimeoutError.  This timeout is per adb call. The
             total time spent may exceed this value. If it is not
             specified, the value set in the ADBHost constructor is used.
@@ -336,19 +421,20 @@ class ADBHost(ADBCommand):
                         device.update(dict([j.split(':')
                                             for j in remainder.split(' ')]))
                     except ValueError:
                         self._logger.warning('devices: Unable to parse '
                                              'remainder for device %s' % line)
                 devices.append(device)
         return devices
 
+
 class ADBDevice(ADBCommand):
     """ADBDevice provides methods which can be used to interact with
-    Android-based devices.
+    the associated Android-based device.
 
     Android specific features such as Application management are not
     included but are provided via the ADBAndroid interface.
 
     ::
 
        from mozdevice import ADBDevice
 
@@ -548,49 +634,56 @@ class ADBDevice(ADBCommand):
 
         raise ADBError("Unable to set up device root using paths: [%s]"
                        % ", ".join(["'%s'" % os.path.join(b, s)
                                     for b, s in paths]))
 
     # Host Command methods
 
     def command(self, cmds, timeout=None):
-        """Executes an adb command on the host.
+        """Executes an adb command on the host against the device.
 
         :param cmds: list containing the command and its arguments to be
             executed.
         :param timeout: optional integer specifying the maximum time in
             seconds for any spawned adb process to complete before
             throwing an ADBTimeoutError.  This timeout is per adb call. The
             total time spent may exceed this value. If it is not
             specified, the value set in the ADBDevice constructor is used.
         :returns: :class:`mozdevice.ADBProcess`
 
         command() provides a low level interface for executing
-        commands on the host via adb.  For executing shell commands on
-        the device, use ADBDevice.shell().  The caller provides a list
-        containing commands, as well as a timeout period in seconds.
+        commands for a specific device on the host via adb.
+
+        command() executes on the host in such a fashion that stdout
+        and stderr of the adb process are file handles on the host and
+        the exit code is available as the exit code of the adb
+        process.
+
+        For executing shell commands on the device, use
+        ADBDevice.shell().  The caller provides a list containing
+        commands, as well as a timeout period in seconds.
 
         A subprocess is spawned to execute adb for the device with
-        stdout and stderr directed to named temporary files. If the
-        process takes longer than the specified timeout, the process
-        is terminated.
+        stdout and stderr directed to temporary files. If the process
+        takes longer than the specified timeout, the process is
+        terminated.
 
         It is the caller's responsibilty to clean up by closing
         the stdout and stderr temporary files.
 
         """
 
         return ADBCommand.command(self, cmds,
                                   device_serial=self._device_serial,
                                   timeout=timeout)
 
     def command_output(self, cmds, timeout=None):
-        """Executes an adb command on the host returning stdout.
-
+        """Executes an adb command on the host against the device returning
+        stdout.
 
         :param cmds: list containing the command and its arguments to be
             executed.
         :param timeout: optional integer specifying the maximum time in seconds
             for any spawned adb process to complete before throwing
             an ADBTimeoutError.
             This timeout is per adb call. The total time spent
             may exceed this value. If it is not specified, the value
@@ -624,21 +717,32 @@ class ADBDevice(ADBCommand):
             specified, the value set in the ADBDevice constructor is used.
         :param root: optional boolean specifying if the command should
             be executed as root.
         :returns: :class:`mozdevice.ADBProcess`
         :raises: ADBRootError - raised if root is requested but the
                  device is not rooted.
 
         shell() provides a low level interface for executing commands
-        on the device via adb shell.  the caller provides a flag
-        indicating if the command is to be executed as root, a string
-        for any requested working directory, a hash defining the
-        environment, a string containing shell commands, as well as a
-        timeout period in seconds.
+        on the device via adb shell.
+
+        shell() executes on the host in such as fashion that stdout
+        contains the stdout of the host abd process combined with the
+        combined stdout/stderr of the shell command on the device
+        while stderr is still the stderr of the adb process on the
+        host. The exit code of shell() is the exit code of
+        the adb command if it was non-zero or the extracted exit code
+        from the stdout/stderr of the shell command executed on the
+        device.
+
+        The caller provides a flag indicating if the command is to be
+        executed as root, a string for any requested working
+        directory, a hash defining the environment, a string
+        containing shell commands, as well as a timeout period in
+        seconds.
 
         The command line to be executed is created to set the current
         directory, set the required environment variables, optionally
         execute the command using su and to output the return code of
         the command to stdout. The command list is created as a
         command sequence separated by && which will terminate the
         command sequence on the first command which returns a non-zero
         exit code.
@@ -646,16 +750,17 @@ class ADBDevice(ADBCommand):
         A subprocess is spawned to execute adb shell for the device
         with stdout and stderr directed to temporary files. If the
         process takes longer than the specified timeout, the process
         is terminated. The return code is extracted from the stdout
         and is then removed from the file.
 
         It is the caller's responsibilty to clean up by closing
         the stdout and stderr temporary files.
+
         """
         if root:
             ld_library_path='LD_LIBRARY_PATH=/vendor/lib:/system/lib'
             cmd = '%s %s' % (ld_library_path, cmd)
             if self._have_root_shell:
                 pass
             elif self._have_su:
                 cmd = "su -c \"%s\"" % cmd
@@ -678,28 +783,29 @@ class ADBDevice(ADBCommand):
             args.extend(['-s', self._device_serial])
         args.extend(["wait-for-device", "shell", cmd])
 
         adb_process = ADBProcess(args)
 
         if timeout is None:
             timeout = self._timeout
 
-        timeout = int(timeout)
         start_time = time.time()
         exitcode = adb_process.proc.poll()
         while ((time.time() - start_time) <= timeout) and exitcode == None:
             time.sleep(self._polling_interval)
             exitcode = adb_process.proc.poll()
         if exitcode == None:
             adb_process.proc.kill()
             adb_process.timedout = True
             adb_process.exitcode = adb_process.proc.poll()
+        elif exitcode == 0:
+            adb_process.exitcode = self._get_exitcode(adb_process.stdout_file)
         else:
-            adb_process.exitcode = self._get_exitcode(adb_process.stdout_file)
+            adb_process.exitcode = exitcode
 
         adb_process.stdout_file.seek(0, os.SEEK_SET)
         adb_process.stderr_file.seek(0, os.SEEK_SET)
 
         return adb_process
 
     def shell_bool(self, cmd, env=None, cwd=None, timeout=None, root=False):
         """Executes a shell command on the device returning True on success