Bug 793739 - mirror mozbase to m-c for week of Sept 24, 2012 @ https://github.com/mozilla/mozbase/commit/b12b008846bc14744d43899ab356f5441e2416ea;r=jhammel
authorEd Morley <bmo@edmorley.co.uk>
Tue, 25 Sep 2012 09:35:14 -0700
changeset 114333 83881c59bc27d2a1ca6ee34b144b6e848e5b092a
parent 114332 48b43bd3fb17db6998e52d81b7b3c0bb5c0d637e
child 114334 0fc339d6174c02a1b8a52ebea1df63a6d74fd9e7
push idunknown
push userunknown
push dateunknown
reviewersjhammel
bugs793739
milestone18.0a1
Bug 793739 - mirror mozbase to m-c for week of Sept 24, 2012 @ https://github.com/mozilla/mozbase/commit/b12b008846bc14744d43899ab356f5441e2416ea;r=jhammel
testing/mozbase/manifestdestiny/manifestparser/manifestparser.py
testing/mozbase/mozdevice/mozdevice/devicemanager.py
testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py
testing/mozbase/mozdevice/mozdevice/devicemanagerSUT.py
testing/mozbase/mozdevice/mozdevice/droid.py
testing/mozbase/mozdevice/mozdevice/sutcli.py
testing/mozbase/mozdevice/tests/sut.py
--- a/testing/mozbase/manifestdestiny/manifestparser/manifestparser.py
+++ b/testing/mozbase/manifestdestiny/manifestparser/manifestparser.py
@@ -14,22 +14,16 @@ Mozilla universal manifest parser
 
 import os
 import re
 import shutil
 import sys
 from fnmatch import fnmatch
 from optparse import OptionParser
 
-version = '0.5.4' # package version
-try:
-    from setuptools import setup
-except:
-    setup = None
-
 # we need relpath, but it is introduced in python 2.6
 # http://docs.python.org/library/os.path.html
 try:
     relpath = os.path.relpath
 except AttributeError:
     def relpath(path, start):
         """
         Return a relative version of a path
--- a/testing/mozbase/mozdevice/mozdevice/devicemanager.py
+++ b/testing/mozbase/mozdevice/mozdevice/devicemanager.py
@@ -4,634 +4,638 @@
 
 import hashlib
 import socket
 import os
 import re
 import StringIO
 
 class FileError(Exception):
-  " Signifies an error which occurs while doing a file operation."
+    " Signifies an error which occurs while doing a file operation."
 
-  def __init__(self, msg = ''):
-    self.msg = msg
+    def __init__(self, msg = ''):
+        self.msg = msg
 
-  def __str__(self):
-    return self.msg
+    def __str__(self):
+        return self.msg
 
 class DMError(Exception):
-  "generic devicemanager exception."
+    "generic devicemanager exception."
 
-  def __init__(self, msg= ''):
-    self.msg = msg
+    def __init__(self, msg= ''):
+        self.msg = msg
 
-  def __str__(self):
-    return self.msg
+    def __str__(self):
+        return self.msg
 
 def abstractmethod(method):
-  line = method.func_code.co_firstlineno
-  filename = method.func_code.co_filename
-  def not_implemented(*args, **kwargs):
-    raise NotImplementedError('Abstract method %s at File "%s", line %s '
-                              'should be implemented by a concrete class' %
-                              (repr(method), filename,line))
-  return not_implemented
+    line = method.func_code.co_firstlineno
+    filename = method.func_code.co_filename
+    def not_implemented(*args, **kwargs):
+        raise NotImplementedError('Abstract method %s at File "%s", line %s '
+                                   'should be implemented by a concrete class' %
+                                   (repr(method), filename,line))
+    return not_implemented
 
 class DeviceManager:
 
-  @abstractmethod
-  def shell(self, cmd, outputfile, env=None, cwd=None, timeout=None):
-    """
-    executes shell command on device
+    @abstractmethod
+    def shell(self, cmd, outputfile, env=None, cwd=None, timeout=None):
+        """
+        executes shell command on device
 
-    timeout is specified in seconds, and if no timeout is given, 
-    we will run until the script returns
-    returns:
-    success: Return code from command
-    failure: None
-    """
+        timeout is specified in seconds, and if no timeout is given, 
+        we will run until the script returns
+        returns:
+        success: Return code from command
+        failure: None
+        """
 
-  @abstractmethod
-  def pushFile(self, localname, destname):
-    """
-    external function
-    returns:
-    success: True
-    failure: False
-    """
+    @abstractmethod
+    def pushFile(self, localname, destname):
+        """
+        external function
+        returns:
+        success: True
+        failure: False
+        """
 
-  @abstractmethod
-  def mkDir(self, name):
-    """
-    external function
-    returns:
-    success: directory name
-    failure: None
-    """
+    @abstractmethod
+    def mkDir(self, name):
+        """
+        external function
+        returns:
+        success: directory name
+        failure: None
+        """
 
-  def mkDirs(self, filename):
-    """
-    make directory structure on the device
-    WARNING: does not create last part of the path
-    external function
-    returns:
-    success: directory structure that we created
-    failure: None
-    """
-    parts = filename.split('/')
-    name = ""
-    for part in parts:
-        if (part == parts[-1]): break
-        if (part != ""):
-            name += '/' + part
-            if (not self.dirExists(name)):
-                if (self.mkDir(name) == None):
-                    print "failed making directory: " + str(name)
-                    return None
-    return name
+    def mkDirs(self, filename):
+        """
+        make directory structure on the device
+        WARNING: does not create last part of the path
+        external function
+        returns:
+        success: directory structure that we created
+        failure: None
+        """
+        parts = filename.split('/')
+        name = ""
+        for part in parts:
+            if (part == parts[-1]):
+                break
+            if (part != ""):
+                name += '/' + part
+                if (not self.dirExists(name)):
+                    if (self.mkDir(name) == None):
+                        print "Automation Error: failed making directory: " + str(name)
+                        return None
+        return name
 
-  @abstractmethod
-  def pushDir(self, localDir, remoteDir):
-    """
-    push localDir from host to remoteDir on the device
-    external function
-    returns:
-    success: remoteDir
-    failure: None
-    """
+    @abstractmethod
+    def pushDir(self, localDir, remoteDir):
+        """
+        push localDir from host to remoteDir on the device
+        external function
+        returns:
+        success: remoteDir
+        failure: None
+        """
 
-  @abstractmethod
-  def dirExists(self, dirname):
-    """
-    external function
-    returns:
-    success: True
-    failure: False
-    """
+    @abstractmethod
+    def dirExists(self, dirname):
+        """
+        external function
+        returns:
+        success: True
+        failure: False
+        """
 
-  @abstractmethod
-  def fileExists(self, filepath):
-    """
-    Because we always have / style paths we make this a lot easier with some
-    assumptions
-    external function
-    returns:
-    success: True
-    failure: False
-    """
+    @abstractmethod
+    def fileExists(self, filepath):
+        """
+        Because we always have / style paths we make this a lot easier with some
+        assumptions
+        external function
+        returns:
+        success: True
+        failure: False
+        """
 
-  @abstractmethod
-  def listFiles(self, rootdir):
-    """
-    list files on the device, requires cd to directory first
-    external function
-    returns:
-    success: array of filenames, ['file1', 'file2', ...]
-    failure: None
-    """
+    @abstractmethod
+    def listFiles(self, rootdir):
+        """
+        list files on the device, requires cd to directory first
+        external function
+        returns:
+        success: array of filenames, ['file1', 'file2', ...]
+        failure: None
+        """
 
-  @abstractmethod
-  def removeFile(self, filename):
-    """
-    external function
-    returns:
-    success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
-    failure: None
-    """
+    @abstractmethod
+    def removeFile(self, filename):
+        """
+        external function
+        returns:
+        success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
+        failure: None
+        """
 
-  @abstractmethod
-  def removeDir(self, remoteDir):
-    """
-    does a recursive delete of directory on the device: rm -Rf remoteDir
-    external function
-    returns:
-    success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
-    failure: None
-    """
+    @abstractmethod
+    def removeDir(self, remoteDir):
+        """
+        does a recursive delete of directory on the device: rm -Rf remoteDir
+        external function
+        returns:
+        success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
+        failure: None
+        """
 
-  @abstractmethod
-  def getProcessList(self):
-    """
-    external function
-    returns:
-    success: array of process tuples
-    failure: None
-    """
+    @abstractmethod
+    def getProcessList(self):
+        """
+        external function
+        returns:
+        success: array of process tuples
+        failure: None
+        """
 
-  @abstractmethod
-  def fireProcess(self, appname, failIfRunning=False):
-    """
-    external function
-    DEPRECATED: Use shell() or launchApplication() for new code
-    returns:
-    success: pid
-    failure: None
-    """
+    @abstractmethod
+    def fireProcess(self, appname, failIfRunning=False):
+        """
+        external function
+        DEPRECATED: Use shell() or launchApplication() for new code
+        returns:
+        success: pid
+        failure: None
+        """
 
-  @abstractmethod
-  def launchProcess(self, cmd, outputFile = "process.txt", cwd = '', env = '', failIfRunning=False):
-    """
-    external function
-    DEPRECATED: Use shell() or launchApplication() for new code
-    returns:
-    success: output filename
-    failure: None
-    """
+    @abstractmethod
+    def launchProcess(self, cmd, outputFile = "process.txt", cwd = '', env = '', failIfRunning=False):
+        """
+        external function
+        DEPRECATED: Use shell() or launchApplication() for new code
+        returns:
+        success: output filename
+        failure: None
+        """
 
-  def processExist(self, appname):
-    """
-    iterates process list and returns pid if exists, otherwise None
-    external function
-    returns:
-    success: pid
-    failure: None
-    """
+    def processExist(self, appname):
+        """
+        iterates process list and returns pid if exists, otherwise None
+        external function
+        returns:
+        success: pid
+        failure: None
+        """
 
-    pid = None
+        pid = None
 
-    #filter out extra spaces
-    parts = filter(lambda x: x != '', appname.split(' '))
-    appname = ' '.join(parts)
+        #filter out extra spaces
+        parts = filter(lambda x: x != '', appname.split(' '))
+        appname = ' '.join(parts)
 
-    #filter out the quoted env string if it exists
-    #ex: '"name=value;name2=value2;etc=..." process args' -> 'process args'
-    parts = appname.split('"')
-    if (len(parts) > 2):
-      appname = ' '.join(parts[2:]).strip()
+        #filter out the quoted env string if it exists
+        #ex: '"name=value;name2=value2;etc=..." process args' -> 'process args'
+        parts = appname.split('"')
+        if (len(parts) > 2):
+            appname = ' '.join(parts[2:]).strip()
 
-    pieces = appname.split(' ')
-    parts = pieces[0].split('/')
-    app = parts[-1]
+        pieces = appname.split(' ')
+        parts = pieces[0].split('/')
+        app = parts[-1]
 
-    procList = self.getProcessList()
-    if (procList == []):
-      return None
+        procList = self.getProcessList()
+        if (procList == []):
+            return None
 
-    for proc in procList:
-      procName = proc[1].split('/')[-1]
-      if (procName == app):
-        pid = proc[0]
-        break
-    return pid
+        for proc in procList:
+            procName = proc[1].split('/')[-1]
+            if (procName == app):
+                pid = proc[0]
+                break
+        return pid
 
 
-  @abstractmethod
-  def killProcess(self, appname, forceKill=False):
-    """
-    external function
-    returns:
-    success: True
-    failure: False
-    """
+    @abstractmethod
+    def killProcess(self, appname, forceKill=False):
+        """
+        external function
+        returns:
+        success: True
+        failure: False
+        """
 
-  @abstractmethod
-  def catFile(self, remoteFile):
-    """
-    external function
-    returns:
-    success: filecontents
-    failure: None
-    """
+    @abstractmethod
+    def catFile(self, remoteFile):
+        """
+        external function
+        returns:
+        success: filecontents
+        failure: None
+        """
 
-  @abstractmethod
-  def pullFile(self, remoteFile):
-    """
-    external function
-    returns:
-    success: output of pullfile, string
-    failure: None
-    """
+    @abstractmethod
+    def pullFile(self, remoteFile):
+        """
+        external function
+        returns:
+        success: output of pullfile, string
+        failure: None
+        """
 
-  @abstractmethod
-  def getFile(self, remoteFile, localFile = ''):
-    """
-    copy file from device (remoteFile) to host (localFile)
-    external function
-    returns:
-    success: output of pullfile, string
-    failure: None
-    """
+    @abstractmethod
+    def getFile(self, remoteFile, localFile = ''):
+        """
+        copy file from device (remoteFile) to host (localFile)
+        external function
+        returns:
+        success: output of pullfile, string
+        failure: None
+        """
 
-  @abstractmethod
-  def getDirectory(self, remoteDir, localDir, checkDir=True):
-    """
-    copy directory structure from device (remoteDir) to host (localDir)
-    external function
-    checkDir exists so that we don't create local directories if the
-    remote directory doesn't exist but also so that we don't call isDir
-    twice when recursing.
-    returns:
-    success: list of files, string
-    failure: None
-    """
+    @abstractmethod
+    def getDirectory(self, remoteDir, localDir, checkDir=True):
+        """
+        copy directory structure from device (remoteDir) to host (localDir)
+        external function
+        checkDir exists so that we don't create local directories if the
+        remote directory doesn't exist but also so that we don't call isDir
+        twice when recursing.
+        returns:
+        success: list of files, string
+        failure: None
+        """
 
-  @abstractmethod
-  def isDir(self, remotePath):
-    """
-    external function
-    returns:
-    success: True
-    failure: False
-    Throws a FileError exception when null (invalid dir/filename)
-    """
+    @abstractmethod
+    def isDir(self, remotePath):
+        """
+        external function
+        returns:
+        success: True
+        failure: False
+        Throws a FileError exception when null (invalid dir/filename)
+        """
 
-  @abstractmethod
-  def validateFile(self, remoteFile, localFile):
-    """
-    true/false check if the two files have the same md5 sum
-    external function
-    returns:
-    success: True
-    failure: False
-    """
+    @abstractmethod
+    def validateFile(self, remoteFile, localFile):
+        """
+        true/false check if the two files have the same md5 sum
+        external function
+        returns:
+        success: True
+        failure: False
+        """
 
-  @abstractmethod
-  def getRemoteHash(self, filename):
-    """
-    return the md5 sum of a remote file
-    internal function
-    returns:
-    success: MD5 hash for given filename
-    failure: None
-    """
+    @abstractmethod
+    def getRemoteHash(self, filename):
+        """
+        return the md5 sum of a remote file
+        internal function
+        returns:
+        success: MD5 hash for given filename
+        failure: None
+        """
 
-  def getLocalHash(self, filename):
-    """
-    return the md5 sum of a file on the host
-    internal function
-    returns:
-    success: MD5 hash for given filename
-    failure: None
-    """
+    def getLocalHash(self, filename):
+        """
+        return the md5 sum of a file on the host
+        internal function
+        returns:
+        success: MD5 hash for given filename
+        failure: None
+        """
 
-    file = open(filename, 'rb')
-    if (file == None):
-      return None
+        f = open(filename, 'rb')
+        if (f == None):
+            return None
 
-    try:
-      mdsum = hashlib.md5()
-    except:
-      return None
+        try:
+            mdsum = hashlib.md5()
+        except:
+            return None
 
-    while 1:
-      data = file.read(1024)
-      if not data:
-        break
-      mdsum.update(data)
+        while 1:
+            data = f.read(1024)
+            if not data:
+                break
+            mdsum.update(data)
 
-    file.close()
-    hexval = mdsum.hexdigest()
-    if (self.debug >= 3): print "local hash returned: '" + hexval + "'"
-    return hexval
+        f.close()
+        hexval = mdsum.hexdigest()
+        if (self.debug >= 3):
+            print "local hash returned: '" + hexval + "'"
+        return hexval
 
-  @abstractmethod
-  def getDeviceRoot(self):
-    """
-    Gets the device root for the testing area on the device
-    For all devices we will use / type slashes and depend on the device-agent
-    to sort those out.  The agent will return us the device location where we
-    should store things, we will then create our /tests structure relative to
-    that returned path.
-    Structure on the device is as follows:
-    /tests
-          /<fennec>|<firefox>  --> approot
-          /profile
-          /xpcshell
-          /reftest
-          /mochitest
-    external
-    returns:
-    success: path for device root
-    failure: None
-    """
+    @abstractmethod
+    def getDeviceRoot(self):
+        """
+        Gets the device root for the testing area on the device
+        For all devices we will use / type slashes and depend on the device-agent
+        to sort those out.  The agent will return us the device location where we
+        should store things, we will then create our /tests structure relative to
+        that returned path.
+        Structure on the device is as follows:
+        /tests
+            /<fennec>|<firefox>  --> approot
+            /profile
+            /xpcshell
+            /reftest
+            /mochitest
+        external
+        returns:
+        success: path for device root
+        failure: None
+        """
 
-  @abstractmethod
-  def getAppRoot(self):
-    """
-    Either we will have /tests/fennec or /tests/firefox but we will never have
-    both.  Return the one that exists
-    TODO: ensure we can support org.mozilla.firefox
-    external function
-    returns:
-    success: path for app root
-    failure: None
-    """
+    @abstractmethod
+    def getAppRoot(self):
+        """
+        Either we will have /tests/fennec or /tests/firefox but we will never have
+        both.  Return the one that exists
+        TODO: ensure we can support org.mozilla.firefox
+        external function
+        returns:
+        success: path for app root
+        failure: None
+        """
 
-  def getTestRoot(self, type):
-    """
-    Gets the directory location on the device for a specific test type
-    Type is one of: xpcshell|reftest|mochitest
-    external function
-    returns:
-    success: path for test root
-    failure: None
-    """
+    def getTestRoot(self, harness):
+        """
+        Gets the directory location on the device for a specific test type
+        Harness is one of: xpcshell|reftest|mochitest
+        external function
+        returns:
+        success: path for test root
+        failure: None
+        """
 
-    devroot = self.getDeviceRoot()
-    if (devroot == None):
-      return None
+        devroot = self.getDeviceRoot()
+        if (devroot == None):
+            return None
 
-    if (re.search('xpcshell', type, re.I)):
-      self.testRoot = devroot + '/xpcshell'
-    elif (re.search('?(i)reftest', type)):
-      self.testRoot = devroot + '/reftest'
-    elif (re.search('?(i)mochitest', type)):
-      self.testRoot = devroot + '/mochitest'
-    return self.testRoot
+        if (re.search('xpcshell', harness, re.I)):
+            self.testRoot = devroot + '/xpcshell'
+        elif (re.search('?(i)reftest', harness)):
+            self.testRoot = devroot + '/reftest'
+        elif (re.search('?(i)mochitest', harness)):
+            self.testRoot = devroot + '/mochitest'
+        return self.testRoot
 
-  def signal(self, processID, signalType, signalAction):
-    """
-    Sends a specific process ID a signal code and action.
-    For Example: SIGINT and SIGDFL to process x
-    """
-    #currently not implemented in device agent - todo
+    def signal(self, processID, signalType, signalAction):
+        """
+        Sends a specific process ID a signal code and action.
+        For Example: SIGINT and SIGDFL to process x
+        """
+        #currently not implemented in device agent - todo
 
-    pass
+        pass
 
-  def getReturnCode(self, processID):
-    """Get a return code from process ending -- needs support on device-agent"""
-    # TODO: make this real
+    def getReturnCode(self, processID):
+        """Get a return code from process ending -- needs support on device-agent"""
+        # TODO: make this real
 
-    return 0
+        return 0
 
-  @abstractmethod
-  def unpackFile(self, file_path, dest_dir=None):
-    """
-    external function
-    returns:
-    success: output of unzip command
-    failure: None
-    """
+    @abstractmethod
+    def unpackFile(self, file_path, dest_dir=None):
+        """
+        external function
+        returns:
+        success: output of unzip command
+        failure: None
+        """
 
-  @abstractmethod
-  def reboot(self, ipAddr=None, port=30000):
-    """
-    external function
-    returns:
-    success: status from test agent
-    failure: None
-    """
+    @abstractmethod
+    def reboot(self, ipAddr=None, port=30000):
+        """
+        external function
+        returns:
+        success: status from test agent
+        failure: None
+        """
 
-  def validateDir(self, localDir, remoteDir):
-    """
-    validate localDir from host to remoteDir on the device
-    external function
-    returns:
-    success: True
-    failure: False
-    """
-
-    if (self.debug >= 2): print "validating directory: " + localDir + " to " + remoteDir
-    for root, dirs, files in os.walk(localDir):
-      parts = root.split(localDir)
-      for file in files:
-        remoteRoot = remoteDir + '/' + parts[1]
-        remoteRoot = remoteRoot.replace('/', '/')
-        if (parts[1] == ""): remoteRoot = remoteDir
-        remoteName = remoteRoot + '/' + file
-        if (self.validateFile(remoteName, os.path.join(root, file)) <> True):
-            return False
-    return True
+    def validateDir(self, localDir, remoteDir):
+        """
+        validate localDir from host to remoteDir on the device
+        external function
+        returns:
+        success: True
+        failure: False
+        """
 
-  @abstractmethod
-  def getInfo(self, directive=None):
-    """
-    Returns information about the device:
-    Directive indicates the information you want to get, your choices are:
-    os - name of the os
-    id - unique id of the device
-    uptime - uptime of the device
-    uptimemillis - uptime of the device in milliseconds (NOT supported on all
-                   implementations)
-    systime - system time of the device
-    screen - screen resolution
-    memory - memory stats
-    process - list of running processes (same as ps)
-    disk - total, free, available bytes on disk
-    power - power status (charge, battery temp)
-    all - all of them - or call it with no parameters to get all the information
-    returns:
-    success: dict of info strings by directive name
-    failure: None
-    """
+        if (self.debug >= 2):
+            print "validating directory: " + localDir + " to " + remoteDir
+        for root, dirs, files in os.walk(localDir):
+            parts = root.split(localDir)
+            for f in files:
+                remoteRoot = remoteDir + '/' + parts[1]
+                remoteRoot = remoteRoot.replace('/', '/')
+                if (parts[1] == ""):
+                    remoteRoot = remoteDir
+                remoteName = remoteRoot + '/' + f
+                if (self.validateFile(remoteName, os.path.join(root, f)) <> True):
+                        return False
+        return True
+
+    @abstractmethod
+    def getInfo(self, directive=None):
+        """
+        Returns information about the device:
+        Directive indicates the information you want to get, your choices are:
+        os - name of the os
+        id - unique id of the device
+        uptime - uptime of the device
+        uptimemillis - uptime of the device in milliseconds (NOT supported on all
+                                      implementations)
+        systime - system time of the device
+        screen - screen resolution
+        memory - memory stats
+        process - list of running processes (same as ps)
+        disk - total, free, available bytes on disk
+        power - power status (charge, battery temp)
+        all - all of them - or call it with no parameters to get all the information
+        returns:
+        success: dict of info strings by directive name
+        failure: None
+        """
 
-  @abstractmethod
-  def installApp(self, appBundlePath, destPath=None):
-    """
-    external function
-    returns:
-    success: output from agent for inst command
-    failure: None
-    """
+    @abstractmethod
+    def installApp(self, appBundlePath, destPath=None):
+        """
+        external function
+        returns:
+        success: output from agent for inst command
+        failure: None
+        """
 
-  @abstractmethod
-  def uninstallAppAndReboot(self, appName, installPath=None):
-    """
-    external function
-    returns:
-    success: True
-    failure: None
-    """
+    @abstractmethod
+    def uninstallAppAndReboot(self, appName, installPath=None):
+        """
+        external function
+        returns:
+        success: True
+        failure: None
+        """
 
-  @abstractmethod
-  def updateApp(self, appBundlePath, processName=None,
-                destPath=None, ipAddr=None, port=30000):
-    """
-    external function
-    returns:
-    success: text status from command or callback server
-    failure: None
-    """
+    @abstractmethod
+    def updateApp(self, appBundlePath, processName=None,
+                                destPath=None, ipAddr=None, port=30000):
+        """
+        external function
+        returns:
+        success: text status from command or callback server
+        failure: None
+        """
 
-  @abstractmethod
-  def getCurrentTime(self):
-    """
-    external function
-    returns:
-    success: time in ms
-    failure: None
-    """
+    @abstractmethod
+    def getCurrentTime(self):
+        """
+        external function
+        returns:
+        success: time in ms
+        failure: None
+        """
 
-  def recordLogcat(self):
-    """
-    external function
-    returns:
-    success: file is created in <testroot>/logcat.log
-    failure:
-    """
-    #TODO: spawn this off in a separate thread/process so we can collect all the logcat information
+    def recordLogcat(self):
+        """
+        external function
+        returns:
+        success: file is created in <testroot>/logcat.log
+        failure:
+        """
+        #TODO: spawn this off in a separate thread/process so we can collect all the logcat information
 
-    # Right now this is just clearing the logcat so we can only see what happens after this call.
-    buf = StringIO.StringIO()
-    self.shell(['/system/bin/logcat', '-c'], buf, root=True)
+        # Right now this is just clearing the logcat so we can only see what happens after this call.
+        buf = StringIO.StringIO()
+        self.shell(['/system/bin/logcat', '-c'], buf, root=True)
 
-  def getLogcat(self):
-    """
-    external function
-    returns: data from the local file
-    success: file is in 'filename'
-    failure: None
-    """
-    buf = StringIO.StringIO()
-    if self.shell(["/system/bin/logcat", "-d", "dalvikvm:S", "ConnectivityService:S", "WifiMonitor:S", "WifiStateTracker:S", "wpa_supplicant:S", "NetworkStateTracker:S"], buf, root=True) != 0:
-      return None
+    def getLogcat(self):
+        """
+        external function
+        returns: data from the local file
+        success: file is in 'filename'
+        failure: None
+        """
+        buf = StringIO.StringIO()
+        if self.shell(["/system/bin/logcat", "-d", "dalvikvm:S", "ConnectivityService:S", "WifiMonitor:S", "WifiStateTracker:S", "wpa_supplicant:S", "NetworkStateTracker:S"], buf, root=True) != 0:
+            return None
 
-    return str(buf.getvalue()[0:-1]).rstrip().split('\r')
+        return str(buf.getvalue()[0:-1]).rstrip().split('\r')
 
-  @abstractmethod
-  def chmodDir(self, remoteDir):
-    """
-    external function
-    returns:
-    success: True
-    failure: False
-    """
+    @abstractmethod
+    def chmodDir(self, remoteDir):
+        """
+        external function
+        returns:
+        success: True
+        failure: False
+        """
 
-  @staticmethod
-  def _escapedCommandLine(cmd):
-    """ Utility function to return escaped and quoted version of command line """
-    quotedCmd = []
+    @staticmethod
+    def _escapedCommandLine(cmd):
+        """ Utility function to return escaped and quoted version of command line """
+        quotedCmd = []
 
-    for arg in cmd:
-      arg.replace('&', '\&')
+        for arg in cmd:
+            arg.replace('&', '\&')
 
-      needsQuoting = False
-      for char in [ ' ', '(', ')', '"', '&' ]:
-        if arg.find(char) >= 0:
-          needsQuoting = True
-          break
-      if needsQuoting:
-        arg = '\'%s\'' % arg
+            needsQuoting = False
+            for char in [ ' ', '(', ')', '"', '&' ]:
+                if arg.find(char) >= 0:
+                    needsQuoting = True
+                    break
+            if needsQuoting:
+                arg = '\'%s\'' % arg
 
-      quotedCmd.append(arg)
+            quotedCmd.append(arg)
 
-    return " ".join(quotedCmd)
+        return " ".join(quotedCmd)
 
 
 class NetworkTools:
-  def __init__(self):
-    pass
+    def __init__(self):
+        pass
 
-  # Utilities to get the local ip address
-  def getInterfaceIp(self, ifname):
-    if os.name != "nt":
-      import fcntl
-      import struct
-      s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
-      return socket.inet_ntoa(fcntl.ioctl(
-                              s.fileno(),
-                              0x8915,  # SIOCGIFADDR
-                              struct.pack('256s', ifname[:15])
-                              )[20:24])
-    else:
-      return None
+    # Utilities to get the local ip address
+    def getInterfaceIp(self, ifname):
+        if os.name != "nt":
+            import fcntl
+            import struct
+            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+            return socket.inet_ntoa(fcntl.ioctl(
+                                    s.fileno(),
+                                    0x8915,  # SIOCGIFADDR
+                                    struct.pack('256s', ifname[:15])
+                                    )[20:24])
+        else:
+            return None
 
-  def getLanIp(self):
-    try:
-      ip = socket.gethostbyname(socket.gethostname())
-    except socket.gaierror:
-      ip = socket.gethostbyname(socket.gethostname() + ".local") # for Mac OS X
-    if ip.startswith("127.") and os.name != "nt":
-      interfaces = ["eth0","eth1","eth2","wlan0","wlan1","wifi0","ath0","ath1","ppp0"]
-      for ifname in interfaces:
+    def getLanIp(self):
         try:
-          ip = self.getInterfaceIp(ifname)
-          break;
-        except IOError:
-          pass
-    return ip
+            ip = socket.gethostbyname(socket.gethostname())
+        except socket.gaierror:
+            ip = socket.gethostbyname(socket.gethostname() + ".local") # for Mac OS X
+        if ip.startswith("127.") and os.name != "nt":
+            interfaces = ["eth0","eth1","eth2","wlan0","wlan1","wifi0","ath0","ath1","ppp0"]
+            for ifname in interfaces:
+                try:
+                    ip = self.getInterfaceIp(ifname)
+                    break;
+                except IOError:
+                    pass
+        return ip
 
-  # Gets an open port starting with the seed by incrementing by 1 each time
-  def findOpenPort(self, ip, seed):
-    try:
-      s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-      s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-      connected = False
-      if isinstance(seed, basestring):
-        seed = int(seed)
-      maxportnum = seed + 5000 # We will try at most 5000 ports to find an open one
-      while not connected:
+    # Gets an open port starting with the seed by incrementing by 1 each time
+    def findOpenPort(self, ip, seed):
         try:
-          s.bind((ip, seed))
-          connected = True
-          s.close()
-          break
+            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+            connected = False
+            if isinstance(seed, basestring):
+                seed = int(seed)
+            maxportnum = seed + 5000 # We will try at most 5000 ports to find an open one
+            while not connected:
+                try:
+                    s.bind((ip, seed))
+                    connected = True
+                    s.close()
+                    break
+                except:
+                    if seed > maxportnum:
+                        print "Automation Error: Could not find open port after checking 5000 ports"
+                    raise
+                seed += 1
         except:
-          if seed > maxportnum:
-            print "Could not find open port after checking 5000 ports"
-          raise
-        seed += 1
-    except:
-      print "Socket error trying to find open port"
+            print "Automation Error: Socket error trying to find open port"
 
-    return seed
+        return seed
 
-def _pop_last_line(file):
-  '''
-  Utility function to get the last line from a file (shared between ADB and
-  SUT device managers). Function also removes it from the file. Intended to
-  strip off the return code from a shell command.
-  '''
-  bytes_from_end = 1
-  file.seek(0, 2)
-  length = file.tell() + 1
-  while bytes_from_end < length:
-    file.seek((-1)*bytes_from_end, 2)
-    data = file.read()
+def _pop_last_line(file_obj):
+    '''
+    Utility function to get the last line from a file (shared between ADB and
+    SUT device managers). Function also removes it from the file. Intended to
+    strip off the return code from a shell command.
+    '''
+    bytes_from_end = 1
+    file_obj.seek(0, 2)
+    length = file_obj.tell() + 1
+    while bytes_from_end < length:
+        file_obj.seek((-1)*bytes_from_end, 2)
+        data = file_obj.read()
 
-    if bytes_from_end == length-1 and len(data) == 0: # no data, return None
-      return None
+        if bytes_from_end == length-1 and len(data) == 0: # no data, return None
+            return None
 
-    if data[0] == '\n' or bytes_from_end == length-1:
-      # found the last line, which should have the return value
-      if data[0] == '\n':
-        data = data[1:]
+        if data[0] == '\n' or bytes_from_end == length-1:
+            # found the last line, which should have the return value
+            if data[0] == '\n':
+                data = data[1:]
 
-      # truncate off the return code line
-      file.truncate(length - bytes_from_end)
-      file.seek(0,2)
-      file.write('\0')
+            # truncate off the return code line
+            file_obj.truncate(length - bytes_from_end)
+            file_obj.seek(0,2)
+            file_obj.write('\0')
 
-      return data
+            return data
 
-    bytes_from_end += 1
+        bytes_from_end += 1
 
-  return None
+    return None
--- a/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py
+++ b/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py
@@ -6,895 +6,930 @@ import subprocess
 from devicemanager import DeviceManager, DMError, _pop_last_line
 import re
 import os
 import tempfile
 import time
 
 class DeviceManagerADB(DeviceManager):
 
-  def __init__(self, host=None, port=20701, retrylimit=5, packageName='fennec',
-               adbPath='adb', deviceSerial=None, deviceRoot=None):
-    self.host = host
-    self.port = port
-    self.retrylimit = retrylimit
-    self.retries = 0
-    self._sock = None
-    self.useRunAs = False
-    self.haveRoot = False
-    self.useDDCopy = False
-    self.useZip = False
-    self.packageName = None
-    self.tempDir = None
-    self.deviceRoot = deviceRoot
+    def __init__(self, host=None, port=20701, retrylimit=5, packageName='fennec',
+                 adbPath='adb', deviceSerial=None, deviceRoot=None):
+        self.host = host
+        self.port = port
+        self.retrylimit = retrylimit
+        self.retries = 0
+        self._sock = None
+        self.useRunAs = False
+        self.haveRoot = False
+        self.useDDCopy = False
+        self.useZip = False
+        self.packageName = None
+        self.tempDir = None
+        self.deviceRoot = deviceRoot
+        self.default_timeout = 300
 
-    # the path to adb, or 'adb' to assume that it's on the PATH
-    self.adbPath = adbPath
+        # the path to adb, or 'adb' to assume that it's on the PATH
+        self.adbPath = adbPath
 
-    # The serial number of the device to use with adb, used in cases
-    # where multiple devices are being managed by the same adb instance.
-    self.deviceSerial = deviceSerial
+        # The serial number of the device to use with adb, used in cases
+        # where multiple devices are being managed by the same adb instance.
+        self.deviceSerial = deviceSerial
 
-    if packageName == 'fennec':
-      if os.getenv('USER'):
-        self.packageName = 'org.mozilla.fennec_' + os.getenv('USER')
-      else:
-        self.packageName = 'org.mozilla.fennec_'
-    elif packageName:
-      self.packageName = packageName
+        if packageName == 'fennec':
+            if os.getenv('USER'):
+                self.packageName = 'org.mozilla.fennec_' + os.getenv('USER')
+            else:
+                self.packageName = 'org.mozilla.fennec_'
+        elif packageName:
+            self.packageName = packageName
 
-    # verify that we can run the adb command. can't continue otherwise
-    self.verifyADB()
+        # verify that we can run the adb command. can't continue otherwise
+        self.verifyADB()
 
-    # try to connect to the device over tcp/ip if we have a hostname
-    if self.host:
-      self.connectRemoteADB()
+        # try to connect to the device over tcp/ip if we have a hostname
+        if self.host:
+            self.connectRemoteADB()
 
-    # verify that we can connect to the device. can't continue
-    self.verifyDevice()
+        # verify that we can connect to the device. can't continue
+        self.verifyDevice()
 
-    # set up device root
-    self.setupDeviceRoot()
+        # set up device root
+        self.setupDeviceRoot()
 
-    # Can we use run-as? (currently not required)
-    try:
-      self.verifyRunAs()
-    except DMError:
-      pass
+        # Can we use run-as? (currently not required)
+        try:
+            self.verifyRunAs()
+        except DMError:
+            pass
 
-    # Can we run things as root? (currently not required)
-    useRunAsTmp = self.useRunAs
-    self.useRunAs = False
-    try:
-      self.verifyRoot()
-    except DMError:
-      try:
-        self.checkCmd(["root"])
-        # The root command does not fail even if ADB cannot get
-        # root rights (e.g. due to production builds), so we have
-        # to check again ourselves that we have root now.
-        self.verifyRoot()
-      except DMError:
-        if useRunAsTmp:
-          print "restarting as root failed, but run-as available"
-        else:
-          print "restarting as root failed"
-    self.useRunAs = useRunAsTmp
+        # Can we run things as root? (currently not required)
+        useRunAsTmp = self.useRunAs
+        self.useRunAs = False
+        try:
+            self.verifyRoot()
+        except DMError:
+            try:
+                self.checkCmd(["root"])
+                # The root command does not fail even if ADB cannot get
+                # root rights (e.g. due to production builds), so we have
+                # to check again ourselves that we have root now.
+                self.verifyRoot()
+            except DMError:
+                if useRunAsTmp:
+                    print "restarting as root failed, but run-as available"
+                else:
+                    print "restarting as root failed"
+        self.useRunAs = useRunAsTmp
 
-    # can we use zip to speed up some file operations? (currently not
-    # required)
-    try:
-      self.verifyZip()
-    except DMError:
-      pass
+        # can we use zip to speed up some file operations? (currently not
+        # required)
+        try:
+            self.verifyZip()
+        except DMError:
+            pass
 
-  def __del__(self):
-    if self.host:
-      self.disconnectRemoteADB()
+    def __del__(self):
+        if self.host:
+            self.disconnectRemoteADB()
 
-  # external function: executes shell command on device.
-  # timeout is specified in seconds, and if no timeout is given, 
-  # we will run until the script returns
-  # returns:
-  # success: <return code>
-  # failure: None
-  def shell(self, cmd, outputfile, env=None, cwd=None, timeout=None, root=False):
-    # FIXME: this function buffers all output of the command into memory,
-    # always. :(
+    def shell(self, cmd, outputfile, env=None, cwd=None, timeout=None, root=False):
+        """
+        external function: executes shell command on device.
+        timeout is specified in seconds, and if no timeout is given, 
+        we will run until we hit the default_timeout specified in __init__
+        returns:
+        success: <return code>
+        failure: None
+        """
+        # FIXME: this function buffers all output of the command into memory,
+        # always. :(
 
-    # Getting the return code is more complex than you'd think because adb
-    # doesn't actually return the return code from a process, so we have to
-    # capture the output to get it
-    if root:
-      cmdline = "su -c \"%s\"" % self._escapedCommandLine(cmd)
-    else:
-      cmdline = self._escapedCommandLine(cmd)
-    cmdline += "; echo $?"
+        # Getting the return code is more complex than you'd think because adb
+        # doesn't actually return the return code from a process, so we have to
+        # capture the output to get it
+        if root:
+            cmdline = "su -c \"%s\"" % self._escapedCommandLine(cmd)
+        else:
+            cmdline = self._escapedCommandLine(cmd)
+        cmdline += "; echo $?"
 
-    # prepend cwd and env to command if necessary
-    if cwd:
-      cmdline = "cd %s; %s" % (cwd, cmdline)
-    if env:
-      envstr = '; '.join(map(lambda x: 'export %s=%s' % (x[0], x[1]), env.iteritems()))
-      cmdline = envstr + "; " + cmdline
+        # prepend cwd and env to command if necessary
+        if cwd:
+            cmdline = "cd %s; %s" % (cwd, cmdline)
+        if env:
+            envstr = '; '.join(map(lambda x: 'export %s=%s' % (x[0], x[1]), env.iteritems()))
+            cmdline = envstr + "; " + cmdline
 
-    # all output should be in stdout
-    args=[self.adbPath]
-    if self.deviceSerial:
-        args.extend(['-s', self.deviceSerial])
-    args.extend(["shell", cmdline])
-    proc = subprocess.Popen(args,
-                            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-    if timeout:
+        # all output should be in stdout
+        args=[self.adbPath]
+        if self.deviceSerial:
+            args.extend(['-s', self.deviceSerial])
+        args.extend(["shell", cmdline])
+        proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+        if not timeout:
+            # We are asserting that all commands will complete in this time unless otherwise specified
+            timeout = self.default_timeout
+
         timeout = int(timeout)
         start_time = time.time()
         ret_code = proc.poll()
         while ((time.time() - start_time) <= timeout) and ret_code == None:
             time.sleep(1)
             ret_code = proc.poll()
         if ret_code == None:
             proc.kill()
             raise DMError("Timeout exceeded for shell call")
-    (stdout, stderr) = proc.communicate()
-    outputfile.write(stdout.rstrip('\n'))
+        (stdout, stderr) = proc.communicate()
+        outputfile.write(stdout.rstrip('\n'))
 
-    lastline = _pop_last_line(outputfile)
-    if lastline:
-      m = re.search('([0-9]+)', lastline)
-      if m:
-        return_code = m.group(1)
-        outputfile.seek(-2, 2)
-        outputfile.truncate() # truncate off the return code
-        return int(return_code)
+        lastline = _pop_last_line(outputfile)
+        if lastline:
+            m = re.search('([0-9]+)', lastline)
+            if m:
+                return_code = m.group(1)
+                outputfile.seek(-2, 2)
+                outputfile.truncate() # truncate off the return code
+                return int(return_code)
 
-    return None
+        return None
 
-  def connectRemoteADB(self):
-    self.checkCmd(["connect", self.host + ":" + str(self.port)])
+    def connectRemoteADB(self):
+        self.checkCmd(["connect", self.host + ":" + str(self.port)])
 
-  def disconnectRemoteADB(self):
-    self.checkCmd(["disconnect", self.host + ":" + str(self.port)])
+    def disconnectRemoteADB(self):
+        self.checkCmd(["disconnect", self.host + ":" + str(self.port)])
 
-  # external function
-  # returns:
-  #  success: True
-  #  failure: False
-  def pushFile(self, localname, destname):
-    try:
-      if (os.name == "nt"):
-        destname = destname.replace('\\', '/')
-      if (self.useRunAs):
-        remoteTmpFile = self.getTempDir() + "/" + os.path.basename(localname)
-        self.checkCmd(["push", os.path.realpath(localname), remoteTmpFile])
-        if self.useDDCopy:
-          self.checkCmdAs(["shell", "dd", "if=" + remoteTmpFile, "of=" + destname])
-        else:
-          self.checkCmdAs(["shell", "cp", remoteTmpFile, destname])
-        self.checkCmd(["shell", "rm", remoteTmpFile])
-      else:
-        self.checkCmd(["push", os.path.realpath(localname), destname])
-      if (self.isDir(destname)):
-        destname = destname + "/" + os.path.basename(localname)
-      return True
-    except:
-      return False
+    def pushFile(self, localname, destname):
+        """
+        external function
+        returns:
+          success: True
+          failure: False
+        """
+        try:
+            if (os.name == "nt"):
+                destname = destname.replace('\\', '/')
+            if (self.useRunAs):
+                remoteTmpFile = self.getTempDir() + "/" + os.path.basename(localname)
+                self.checkCmd(["push", os.path.realpath(localname), remoteTmpFile])
+                if self.useDDCopy:
+                    self.checkCmdAs(["shell", "dd", "if=" + remoteTmpFile, "of=" + destname])
+                else:
+                    self.checkCmdAs(["shell", "cp", remoteTmpFile, destname])
+                self.checkCmd(["shell", "rm", remoteTmpFile])
+            else:
+                self.checkCmd(["push", os.path.realpath(localname), destname])
+            if (self.isDir(destname)):
+                destname = destname + "/" + os.path.basename(localname)
+            return True
+        except:
+            return False
 
-  # external function
-  # returns:
-  #  success: directory name
-  #  failure: None
-  def mkDir(self, name):
-    try:
-      result = self.runCmdAs(["shell", "mkdir", name]).stdout.read()
-      if 'read-only file system' in result.lower():
-        return None
-      if 'file exists' in result.lower():
-        return name
-      return name
-    except:
-      return None
-
-  # push localDir from host to remoteDir on the device
-  # external function
-  # returns:
-  #  success: remoteDir
-  #  failure: None
-  def pushDir(self, localDir, remoteDir):
-    # adb "push" accepts a directory as an argument, but if the directory
-    # contains symbolic links, the links are pushed, rather than the linked
-    # files; we either zip/unzip or push file-by-file to get around this 
-    # limitation
-    try:
-      if (not self.dirExists(remoteDir)):
-        self.mkDirs(remoteDir+"/x")
-      if (self.useZip):
+    def mkDir(self, name):
+        """
+        external function
+        returns:
+          success: directory name
+          failure: None
+        """
         try:
-          localZip = tempfile.mktemp()+".zip"
-          remoteZip = remoteDir + "/adbdmtmp.zip"
-          subprocess.check_output(["zip", "-r", localZip, '.'], cwd=localDir)
-          self.pushFile(localZip, remoteZip)
-          os.remove(localZip)
-          data = self.runCmdAs(["shell", "unzip", "-o", remoteZip, "-d", remoteDir]).stdout.read()
-          self.checkCmdAs(["shell", "rm", remoteZip])
-          if (re.search("unzip: exiting", data) or re.search("Operation not permitted", data)):
-            raise Exception("unzip failed, or permissions error")
+            result = self.runCmdAs(["shell", "mkdir", name]).stdout.read()
+            if 'read-only file system' in result.lower():
+                return None
+            if 'file exists' in result.lower():
+                return name
+            return name
         except:
-          print "zip/unzip failure: falling back to normal push"
-          self.useZip = False
-          self.pushDir(localDir, remoteDir)
-      else:
-        for root, dirs, files in os.walk(localDir, followlinks=True):
-          relRoot = os.path.relpath(root, localDir)
-          for file in files:
-            localFile = os.path.join(root, file)
-            remoteFile = remoteDir + "/"
-            if (relRoot!="."):
-              remoteFile = remoteFile + relRoot + "/"
-            remoteFile = remoteFile + file
-            self.pushFile(localFile, remoteFile)
-          for dir in dirs:
-            targetDir = remoteDir + "/"
-            if (relRoot!="."):
-              targetDir = targetDir + relRoot + "/"
-            targetDir = targetDir + dir
-            if (not self.dirExists(targetDir)):
-              self.mkDir(targetDir)
-      return remoteDir
-    except:
-      print "pushing " + localDir + " to " + remoteDir + " failed"
-      return None
+            return None
 
-  # external function
-  # returns:
-  #  success: True
-  #  failure: False
-  def dirExists(self, dirname):
-    return self.isDir(dirname)
+    def pushDir(self, localDir, remoteDir):
+        """
+        push localDir from host to remoteDir on the device
+        external function
+        returns:
+          success: remoteDir
+          failure: None
+        """
+        # adb "push" accepts a directory as an argument, but if the directory
+        # contains symbolic links, the links are pushed, rather than the linked
+        # files; we either zip/unzip or push file-by-file to get around this 
+        # limitation
+        try:
+            if (not self.dirExists(remoteDir)):
+                self.mkDirs(remoteDir+"/x")
+            if (self.useZip):
+                try:
+                    localZip = tempfile.mktemp()+".zip"
+                    remoteZip = remoteDir + "/adbdmtmp.zip"
+                    subprocess.check_output(["zip", "-r", localZip, '.'], cwd=localDir)
+                    self.pushFile(localZip, remoteZip)
+                    os.remove(localZip)
+                    data = self.runCmdAs(["shell", "unzip", "-o", remoteZip, "-d", remoteDir]).stdout.read()
+                    self.checkCmdAs(["shell", "rm", remoteZip])
+                    if (re.search("unzip: exiting", data) or re.search("Operation not permitted", data)):
+                        raise Exception("unzip failed, or permissions error")
+                except:
+                    print "zip/unzip failure: falling back to normal push"
+                    self.useZip = False
+                    self.pushDir(localDir, remoteDir)
+            else:
+                for root, dirs, files in os.walk(localDir, followlinks=True):
+                    relRoot = os.path.relpath(root, localDir)
+                    for f in files:
+                        localFile = os.path.join(root, f)
+                        remoteFile = remoteDir + "/"
+                        if (relRoot!="."):
+                            remoteFile = remoteFile + relRoot + "/"
+                        remoteFile = remoteFile + f
+                        self.pushFile(localFile, remoteFile)
+                    for d in dirs:
+                        targetDir = remoteDir + "/"
+                        if (relRoot!="."):
+                            targetDir = targetDir + relRoot + "/"
+                        targetDir = targetDir + d
+                        if (not self.dirExists(targetDir)):
+                            self.mkDir(targetDir)
+            return remoteDir
+        except:
+            print "pushing " + localDir + " to " + remoteDir + " failed"
+            return None
 
-  # Because we always have / style paths we make this a lot easier with some
-  # assumptions
-  # external function
-  # returns:
-  #  success: True
-  #  failure: False
-  def fileExists(self, filepath):
-    p = self.runCmd(["shell", "ls", "-a", filepath])
-    data = p.stdout.readlines()
-    if (len(data) == 1):
-      if (data[0].rstrip() == filepath):
-        return True
-    return False
+    def dirExists(self, dirname):
+        """
+        external function
+        returns:
+          success: True
+          failure: False
+        """
+        return self.isDir(dirname)
 
-  def removeFile(self, filename):
-    return self.runCmd(["shell", "rm", filename]).stdout.read()
+    # Because we always have / style paths we make this a lot easier with some
+    # assumptions
+    def fileExists(self, filepath):
+        """
+        external function
+        returns:
+          success: True
+          failure: False
+        """
+        p = self.runCmd(["shell", "ls", "-a", filepath])
+        data = p.stdout.readlines()
+        if (len(data) == 1):
+            if (data[0].rstrip() == filepath):
+                return True
+        return False
 
-  # does a recursive delete of directory on the device: rm -Rf remoteDir
-  # external function
-  # returns:
-  #  success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
-  #  failure: None
-  def removeSingleDir(self, remoteDir):
-    return self.runCmd(["shell", "rmdir", remoteDir]).stdout.read()
+    def removeFile(self, filename):
+        return self.runCmd(["shell", "rm", filename]).stdout.read()
 
-  # does a recursive delete of directory on the device: rm -Rf remoteDir
-  # external function
-  # returns:
-  #  success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
-  #  failure: None
-  def removeDir(self, remoteDir):
-      out = ""
-      if (self.isDir(remoteDir)):
-          files = self.listFiles(remoteDir.strip())
-          for f in files:
-              if (self.isDir(remoteDir.strip() + "/" + f.strip())):
-                  out += self.removeDir(remoteDir.strip() + "/" + f.strip())
-              else:
-                  out += self.removeFile(remoteDir.strip() + "/" + f.strip())
-          out += self.removeSingleDir(remoteDir.strip())
-      else:
-          out += self.removeFile(remoteDir.strip())
-      return out
+    def removeSingleDir(self, remoteDir):
+        """
+        does a recursive delete of directory on the device: rm -Rf remoteDir
+        external function
+        returns:
+          success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
+          failure: None
+        """
+        return self.runCmd(["shell", "rmdir", remoteDir]).stdout.read()
 
-  def isDir(self, remotePath):
-      p = self.runCmd(["shell", "ls", "-a", remotePath + '/'])
+    def removeDir(self, remoteDir):
+        """
+        does a recursive delete of directory on the device: rm -Rf remoteDir
+        external function
+        returns:
+          success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
+          failure: None
+        """
+        out = ""
+        if (self.isDir(remoteDir)):
+            files = self.listFiles(remoteDir.strip())
+            for f in files:
+                if (self.isDir(remoteDir.strip() + "/" + f.strip())):
+                    out += self.removeDir(remoteDir.strip() + "/" + f.strip())
+                else:
+                    out += self.removeFile(remoteDir.strip() + "/" + f.strip())
+            out += self.removeSingleDir(remoteDir.strip())
+        else:
+            out += self.removeFile(remoteDir.strip())
+        return out
 
-      data = p.stdout.readlines()
-      if len(data) == 1:
-          res = data[0]
-          if "Not a directory" in res or "No such file or directory" in res:
-              return False
+    def isDir(self, remotePath):
+        p = self.runCmd(["shell", "ls", "-a", remotePath + '/'])
 
-      return True
+        data = p.stdout.readlines()
+        if len(data) == 1:
+            res = data[0]
+            if "Not a directory" in res or "No such file or directory" in res:
+                return False
+        return True
 
-  def listFiles(self, rootdir):
-      p = self.runCmd(["shell", "ls", "-a", rootdir])
-      data = p.stdout.readlines()
-      data[:] = [item.rstrip('\r\n') for item in data]
-      if (len(data) == 1):
-          if (data[0] == rootdir):
-              return []
-          if (data[0].find("No such file or directory") != -1):
-              return []
-          if (data[0].find("Not a directory") != -1):
-              return []
-          if (data[0].find("Permission denied") != -1):
-              return []
-          if (data[0].find("opendir failed") != -1):
-              return []
-      return data
+    def listFiles(self, rootdir):
+        p = self.runCmd(["shell", "ls", "-a", rootdir])
+        data = p.stdout.readlines()
+        data[:] = [item.rstrip('\r\n') for item in data]
+        if (len(data) == 1):
+            if (data[0] == rootdir):
+                return []
+            if (data[0].find("No such file or directory") != -1):
+                return []
+            if (data[0].find("Not a directory") != -1):
+                return []
+            if (data[0].find("Permission denied") != -1):
+                return []
+            if (data[0].find("opendir failed") != -1):
+                return []
+        return data
 
-  # external function
-  # returns:
-  #  success: array of process tuples
-  #  failure: []
-  def getProcessList(self):
-    p = self.runCmd(["shell", "ps"])
-      # first line is the headers
-    p.stdout.readline()
-    proc = p.stdout.readline()
-    ret = []
-    while (proc):
-      els = proc.split()
-      ret.append(list([els[1], els[len(els) - 1], els[0]]))
-      proc =  p.stdout.readline()
-    return ret
+    def getProcessList(self):
+        """
+        external function
+        returns:
+          success: array of process tuples
+          failure: []
+        """
+        p = self.runCmd(["shell", "ps"])
+            # first line is the headers
+        p.stdout.readline()
+        proc = p.stdout.readline()
+        ret = []
+        while (proc):
+            els = proc.split()
+            ret.append(list([els[1], els[len(els) - 1], els[0]]))
+            proc =  p.stdout.readline()
+        return ret
 
-  # external function
-  # DEPRECATED: Use shell() or launchApplication() for new code
-  # returns:
-  #  success: pid
-  #  failure: None
-  def fireProcess(self, appname, failIfRunning=False):
-    #strip out env vars
-    parts = appname.split('"');
-    if (len(parts) > 2):
-      parts = parts[2:]
-    return self.launchProcess(parts, failIfRunning)
+    def fireProcess(self, appname, failIfRunning=False):
+        """
+        external function
+        DEPRECATED: Use shell() or launchApplication() for new code
+        returns:
+          success: pid
+          failure: None
+        """
+        #strip out env vars
+        parts = appname.split('"');
+        if (len(parts) > 2):
+            parts = parts[2:]
+        return self.launchProcess(parts, failIfRunning)
 
-  # external function
-  # DEPRECATED: Use shell() or launchApplication() for new code
-  # returns:
-  #  success: output filename
-  #  failure: None
-  def launchProcess(self, cmd, outputFile = "process.txt", cwd = '', env = '', failIfRunning=False):
-    if cmd[0] == "am":
-      self.checkCmd(["shell"] + cmd)
-      return outputFile
+    def launchProcess(self, cmd, outputFile = "process.txt", cwd = '', env = '', failIfRunning=False):
+        """
+        external function
+        DEPRECATED: Use shell() or launchApplication() for new code
+        returns:
+          success: output filename
+          failure: None
+        """
+        if cmd[0] == "am":
+            self.checkCmd(["shell"] + cmd)
+            return outputFile
 
-    acmd = ["shell", "am", "start", "-W"]
-    cmd = ' '.join(cmd).strip()
-    i = cmd.find(" ")
-    # SUT identifies the URL by looking for :\\ -- another strategy to consider
-    re_url = re.compile('^[http|file|chrome|about].*')
-    last = cmd.rfind(" ")
-    uri = ""
-    args = ""
-    if re_url.match(cmd[last:].strip()):
-      args = cmd[i:last].strip()
-      uri = cmd[last:].strip()
-    else:
-      args = cmd[i:].strip()
-    acmd.append("-n")
-    acmd.append(cmd[0:i] + "/.App")
-    if args != "":
-      acmd.append("--es")
-      acmd.append("args")
-      acmd.append(args)
-    if env != '' and env != None:
-      envCnt = 0
-      # env is expected to be a dict of environment variables
-      for envkey, envval in env.iteritems():
-        acmd.append("--es")
-        acmd.append("env" + str(envCnt))
-        acmd.append(envkey + "=" + envval);
-        envCnt += 1
-    if uri != "":
-      acmd.append("-d")
-      acmd.append(''.join(['\'',uri, '\'']));
-    print acmd
-    self.checkCmd(acmd)
-    return outputFile
+        acmd = ["shell", "am", "start", "-W"]
+        cmd = ' '.join(cmd).strip()
+        i = cmd.find(" ")
+        # SUT identifies the URL by looking for :\\ -- another strategy to consider
+        re_url = re.compile('^[http|file|chrome|about].*')
+        last = cmd.rfind(" ")
+        uri = ""
+        args = ""
+        if re_url.match(cmd[last:].strip()):
+            args = cmd[i:last].strip()
+            uri = cmd[last:].strip()
+        else:
+            args = cmd[i:].strip()
+        acmd.append("-n")
+        acmd.append(cmd[0:i] + "/.App")
+        if args != "":
+            acmd.append("--es")
+            acmd.append("args")
+            acmd.append(args)
+        if env != '' and env != None:
+            envCnt = 0
+            # env is expected to be a dict of environment variables
+            for envkey, envval in env.iteritems():
+                acmd.append("--es")
+                acmd.append("env" + str(envCnt))
+                acmd.append(envkey + "=" + envval);
+                envCnt += 1
+        if uri != "":
+            acmd.append("-d")
+            acmd.append(''.join(['\'',uri, '\'']));
+        print acmd
+        self.checkCmd(acmd)
+        return outputFile
 
-  # external function
-  # returns:
-  #  success: True
-  #  failure: False
-  def killProcess(self, appname, forceKill=False):
-    procs = self.getProcessList()
-    didKillProcess = False
-    for (pid, name, user) in procs:
-      if name == appname:
-         args = ["shell", "kill"]
-         if forceKill:
-           args.append("-9")
-         args.append(pid)
-         p = self.runCmdAs(args)
-         p.communicate()
-         if p.returncode == 0:
-             didKillProcess = True
+    def killProcess(self, appname, forceKill=False):
+        """
+        external function
+        returns:
+          success: True
+          failure: False
+        """
+        procs = self.getProcessList()
+        didKillProcess = False
+        for (pid, name, user) in procs:
+            if name == appname:
+                args = ["shell", "kill"]
+                if forceKill:
+                    args.append("-9")
+                args.append(pid)
+                p = self.runCmdAs(args)
+                p.communicate()
+                if p.returncode == 0:
+                    didKillProcess = True
 
-    return didKillProcess
+        return didKillProcess
 
-  # external function
-  # returns:
-  #  success: filecontents
-  #  failure: None
-  def catFile(self, remoteFile):
-    #p = self.runCmd(["shell", "cat", remoteFile])
-    #return p.stdout.read()
-    return self.getFile(remoteFile)
+    def catFile(self, remoteFile):
+        """
+        external function
+        returns:
+          success: filecontents
+          failure: None
+        """
+        #p = self.runCmd(["shell", "cat", remoteFile])
+        #return p.stdout.read()
+        return self.getFile(remoteFile)
 
-  # external function
-  # returns:
-  #  success: output of pullfile, string
-  #  failure: None
-  def pullFile(self, remoteFile):
-    #return self.catFile(remoteFile)
-    return self.getFile(remoteFile)
+    def pullFile(self, remoteFile):
+        """
+        external function
+        returns:
+          success: output of pullfile, string
+          failure: None
+        """
+        return self.getFile(remoteFile)
 
-  # copy file from device (remoteFile) to host (localFile)
-  # external function
-  # returns:
-  #  success: output of pullfile, string
-  #  failure: None
-  def getFile(self, remoteFile, localFile = 'tmpfile_dm_adb'):
-    # TODO: add debug flags and allow for printing stdout
-    # self.runCmd(["pull", remoteFile, localFile])
-    try:
+    def getFile(self, remoteFile, localFile = 'tmpfile_dm_adb'):
+        """
+        copy file from device (remoteFile) to host (localFile)
+        external function
+        returns:
+          success: output of pullfile, string
+          failure: None
+        """
+        # TODO: add debug flags and allow for printing stdout
+        # self.runCmd(["pull", remoteFile, localFile])
+        try:
 
-      # First attempt to pull file regularly
-      outerr = self.runCmd(["pull",  remoteFile, localFile]).communicate()
+            # First attempt to pull file regularly
+            outerr = self.runCmd(["pull",  remoteFile, localFile]).communicate()
 
-      # Now check stderr for errors
-      if outerr[1]:
-        errl = outerr[1].splitlines()
-        if (len(errl) == 1):
-          if (((errl[0].find("Permission denied") != -1)
-            or (errl[0].find("does not exist") != -1))
-            and self.useRunAs):
-            # If we lack permissions to read but have run-as, then we should try
-            # to copy the file to a world-readable location first before attempting
-            # to pull it again.
-            remoteTmpFile = self.getTempDir() + "/" + os.path.basename(remoteFile)
-            self.checkCmdAs(["shell", "dd", "if=" + remoteFile, "of=" + remoteTmpFile])
-            self.checkCmdAs(["shell", "chmod", "777", remoteTmpFile])
-            self.runCmd(["pull",  remoteTmpFile, localFile]).stdout.read()
-            # Clean up temporary file
-            self.checkCmdAs(["shell", "rm", remoteTmpFile])
+            # Now check stderr for errors
+            if outerr[1]:
+                errl = outerr[1].splitlines()
+                if (len(errl) == 1):
+                    if (((errl[0].find("Permission denied") != -1)
+                        or (errl[0].find("does not exist") != -1))
+                        and self.useRunAs):
+                        # If we lack permissions to read but have run-as, then we should try
+                        # to copy the file to a world-readable location first before attempting
+                        # to pull it again.
+                        remoteTmpFile = self.getTempDir() + "/" + os.path.basename(remoteFile)
+                        self.checkCmdAs(["shell", "dd", "if=" + remoteFile, "of=" + remoteTmpFile])
+                        self.checkCmdAs(["shell", "chmod", "777", remoteTmpFile])
+                        self.runCmd(["pull",  remoteTmpFile, localFile]).stdout.read()
+                        # Clean up temporary file
+                        self.checkCmdAs(["shell", "rm", remoteTmpFile])
 
-      f = open(localFile)
-      ret = f.read()
-      f.close()
-      return ret
-    except:
-      return None
+            f = open(localFile)
+            ret = f.read()
+            f.close()
+            return ret
+        except:
+            return None
 
-  # copy directory structure from device (remoteDir) to host (localDir)
-  # external function
-  # checkDir exists so that we don't create local directories if the
-  # remote directory doesn't exist but also so that we don't call isDir
-  # twice when recursing.
-  # returns:
-  #  success: list of files, string
-  #  failure: None
-  def getDirectory(self, remoteDir, localDir, checkDir=True):
-    ret = []
-    p = self.runCmd(["pull", remoteDir, localDir])
-    p.stdout.readline()
-    line = p.stdout.readline()
-    while (line):
-      els = line.split()
-      f = els[len(els) - 1]
-      i = f.find(localDir)
-      if (i != -1):
-        if (localDir[len(localDir) - 1] != '/'):
-          i = i + 1
-        f = f[i + len(localDir):]
-      i = f.find("/")
-      if (i > 0):
-        f = f[0:i]
-      ret.append(f)
-      line =  p.stdout.readline()
-    #the last line is a summary
-    if (len(ret) > 0):
-      ret.pop()
-    return ret
+    def getDirectory(self, remoteDir, localDir, checkDir=True):
+        """
+        copy directory structure from device (remoteDir) to host (localDir)
+        external function
+        checkDir exists so that we don't create local directories if the
+        remote directory doesn't exist but also so that we don't call isDir
+        twice when recursing.
+        returns:
+          success: list of files, string
+          failure: None
+        """
+        ret = []
+        p = self.runCmd(["pull", remoteDir, localDir])
+        p.stdout.readline()
+        line = p.stdout.readline()
+        while (line):
+            els = line.split()
+            f = els[len(els) - 1]
+            i = f.find(localDir)
+            if (i != -1):
+                if (localDir[len(localDir) - 1] != '/'):
+                    i = i + 1
+                f = f[i + len(localDir):]
+            i = f.find("/")
+            if (i > 0):
+                f = f[0:i]
+            ret.append(f)
+            line =  p.stdout.readline()
+        #the last line is a summary
+        if (len(ret) > 0):
+            ret.pop()
+        return ret
 
 
 
-  # true/false check if the two files have the same md5 sum
-  # external function
-  # returns:
-  #  success: True
-  #  failure: False
-  def validateFile(self, remoteFile, localFile):
-    return self.getRemoteHash(remoteFile) == self.getLocalHash(localFile)
+    def validateFile(self, remoteFile, localFile):
+        """
+        true/false check if the two files have the same md5 sum
+        external function
+        returns:
+          success: True
+          failure: False
+        """
+        return self.getRemoteHash(remoteFile) == self.getLocalHash(localFile)
 
-  # return the md5 sum of a remote file
-  # internal function
-  # returns:
-  #  success: MD5 hash for given filename
-  #  failure: None
-  def getRemoteHash(self, filename):
-    data = self.runCmd(["shell", "ls", "-l", filename]).stdout.read()
-    return data.split()[3]
+    def getRemoteHash(self, filename):
+        """
+        return the md5 sum of a remote file
+        internal function
+        returns:
+          success: MD5 hash for given filename
+          failure: None
+        """
+        data = self.runCmd(["shell", "ls", "-l", filename]).stdout.read()
+        return data.split()[3]
 
-  def getLocalHash(self, filename):
-    data = subprocess.Popen(["ls", "-l", filename], stdout=subprocess.PIPE).stdout.read()
-    return data.split()[4]
+    def getLocalHash(self, filename):
+        data = subprocess.Popen(["ls", "-l", filename], stdout=subprocess.PIPE).stdout.read()
+        return data.split()[4]
 
-  # Internal method to setup the device root and cache its value
-  def setupDeviceRoot(self):
-    # if self.deviceRoot is already set, create it if necessary, and use it
-    if self.deviceRoot:
-      if not self.dirExists(self.deviceRoot):
-        if not self.mkDir(self.deviceRoot):
-          raise DMError("Unable to create device root %s" % self.deviceRoot)
-      return
+    # Internal method to setup the device root and cache its value
+    def setupDeviceRoot(self):
+        # if self.deviceRoot is already set, create it if necessary, and use it
+        if self.deviceRoot:
+            if not self.dirExists(self.deviceRoot):
+                if not self.mkDir(self.deviceRoot):
+                    raise DMError("Unable to create device root %s" % self.deviceRoot)
+            return
 
-    # /mnt/sdcard/tests is preferred to /data/local/tests, but this can be
-    # over-ridden by creating /data/local/tests
-    testRoot = "/data/local/tests"
-    if (self.dirExists(testRoot)):
-      self.deviceRoot = testRoot
-      return
+        # /mnt/sdcard/tests is preferred to /data/local/tests, but this can be
+        # over-ridden by creating /data/local/tests
+        testRoot = "/data/local/tests"
+        if (self.dirExists(testRoot)):
+            self.deviceRoot = testRoot
+            return
 
-    for (basePath, subPath) in [('/mnt/sdcard', 'tests'),
-                                ('/data/local', 'tests')]:
-      if self.dirExists(basePath):
-        testRoot = os.path.join(basePath, subPath)
-        if self.mkDir(testRoot):
-          self.deviceRoot = testRoot
-          return
+        for (basePath, subPath) in [('/mnt/sdcard', 'tests'),
+                                                                ('/data/local', 'tests')]:
+            if self.dirExists(basePath):
+                testRoot = os.path.join(basePath, subPath)
+                if self.mkDir(testRoot):
+                    self.deviceRoot = testRoot
+                    return
 
-    raise DMError("Unable to set up device root as /mnt/sdcard/tests "
-                  "or /data/local/tests")
+        raise DMError("Unable to set up device root as /mnt/sdcard/tests "
+                                    "or /data/local/tests")
 
-  # Gets the device root for the testing area on the device
-  # For all devices we will use / type slashes and depend on the device-agent
-  # to sort those out.  The agent will return us the device location where we
-  # should store things, we will then create our /tests structure relative to
-  # that returned path.
-  # Structure on the device is as follows:
-  # /tests
-  #       /<fennec>|<firefox>  --> approot
-  #       /profile
-  #       /xpcshell
-  #       /reftest
-  #       /mochitest
-  #
-  # external function
-  # returns:
-  #  success: path for device root
-  #  failure: None
-  def getDeviceRoot(self):
-    return self.deviceRoot
+    def getDeviceRoot(self):
+        """
+        Gets the device root for the testing area on the device
+        For all devices we will use / type slashes and depend on the device-agent
+        to sort those out.  The agent will return us the device location where we
+        should store things, we will then create our /tests structure relative to
+        that returned path.
+        Structure on the device is as follows:
+        /tests
+            /<fennec>|<firefox>  --> approot
+            /profile
+            /xpcshell
+            /reftest
+            /mochitest
 
-  # Gets the temporary directory we are using on this device
-  # base on our device root, ensuring also that it exists.
-  #
-  # internal function
-  # returns:
-  #  success: path for temporary directory
-  #  failure: None
-  def getTempDir(self):
-    # Cache result to speed up operations depending
-    # on the temporary directory.
-    if self.tempDir == None:
-      self.tempDir = self.getDeviceRoot() + "/tmp"
-      if (not self.dirExists(self.tempDir)):
-        return self.mkDir(self.tempDir)
+        external function
+        returns:
+          success: path for device root
+          failure: None
+        """
+        return self.deviceRoot
+
+    def getTempDir(self):
+        """
+        Gets the temporary directory we are using on this device
+        base on our device root, ensuring also that it exists.
 
-    return self.tempDir
+        internal function
+        returns:
+          success: path for temporary directory
+          failure: None
+        """
+        # Cache result to speed up operations depending
+        # on the temporary directory.
+        if self.tempDir == None:
+            self.tempDir = self.getDeviceRoot() + "/tmp"
+            if (not self.dirExists(self.tempDir)):
+                return self.mkDir(self.tempDir)
 
-  # Either we will have /tests/fennec or /tests/firefox but we will never have
-  # both.  Return the one that exists
-  # TODO: ensure we can support org.mozilla.firefox
-  # external function
-  # returns:
-  #  success: path for app root
-  #  failure: None
-  def getAppRoot(self, packageName):
-    devroot = self.getDeviceRoot()
-    if (devroot == None):
-      return None
+        return self.tempDir
 
-    if (packageName and self.dirExists('/data/data/' + packageName)):
-      self.packageName = packageName
-      return '/data/data/' + packageName
-    elif (self.packageName and self.dirExists('/data/data/' + self.packageName)):
-      return '/data/data/' + self.packageName
-
-    # Failure (either not installed or not a recognized platform)
-    print "devicemanagerADB: getAppRoot failed"
-    return None
+    def getAppRoot(self, packageName):
+        """
+        Either we will have /tests/fennec or /tests/firefox but we will never have
+        both.  Return the one that exists
+        TODO: ensure we can support org.mozilla.firefox
+        external function
+        returns:
+          success: path for app root
+          failure: None
+        """
+        devroot = self.getDeviceRoot()
+        if (devroot == None):
+            return None
 
-  # Gets the directory location on the device for a specific test type
-  # Type is one of: xpcshell|reftest|mochitest
-  # external function
-  # returns:
-  #  success: path for test root
-  #  failure: None
-  def getTestRoot(self, type):
-    devroot = self.getDeviceRoot()
-    if (devroot == None):
-      return None
+        if (packageName and self.dirExists('/data/data/' + packageName)):
+            self.packageName = packageName
+            return '/data/data/' + packageName
+        elif (self.packageName and self.dirExists('/data/data/' + self.packageName)):
+            return '/data/data/' + self.packageName
+
+        # Failure (either not installed or not a recognized platform)
+        print "devicemanagerADB: getAppRoot failed"
+        return None
 
-    if (re.search('xpcshell', type, re.I)):
-      self.testRoot = devroot + '/xpcshell'
-    elif (re.search('?(i)reftest', type)):
-      self.testRoot = devroot + '/reftest'
-    elif (re.search('?(i)mochitest', type)):
-      self.testRoot = devroot + '/mochitest'
-    return self.testRoot
-
+    def reboot(self, wait = False):
+        """
+        external function
+        returns:
+          success: status from test agent
+          failure: None
+        """
+        ret = self.runCmd(["reboot"]).stdout.read()
+        if (not wait):
+            return "Success"
+        countdown = 40
+        while (countdown > 0):
+            countdown
+            try:
+                self.checkCmd(["wait-for-device", "shell", "ls", "/sbin"])
+                return ret
+            except:
+                try:
+                    self.checkCmd(["root"])
+                except:
+                    time.sleep(1)
+                    print "couldn't get root"
+        return "Success"
 
-  # external function
-  # returns:
-  #  success: status from test agent
-  #  failure: None
-  def reboot(self, wait = False):
-    ret = self.runCmd(["reboot"]).stdout.read()
-    if (not wait):
-      return "Success"
-    countdown = 40
-    while (countdown > 0):
-      countdown
-      try:
-        self.checkCmd(["wait-for-device", "shell", "ls", "/sbin"])
-        return ret
-      except:
-        try:
-          self.checkCmd(["root"])
-        except:
-          time.sleep(1)
-          print "couldn't get root"
-    return "Success"
+    def updateApp(self, appBundlePath, processName=None, destPath=None, ipAddr=None, port=30000):
+        """
+        external function
+        returns:
+          success: text status from command or callback server
+          failure: None
+        """
+        return self.runCmd(["install", "-r", appBundlePath]).stdout.read()
 
-  # external function
-  # returns:
-  #  success: text status from command or callback server
-  #  failure: None
-  def updateApp(self, appBundlePath, processName=None, destPath=None, ipAddr=None, port=30000):
-    return self.runCmd(["install", "-r", appBundlePath]).stdout.read()
-
-  # external function
-  # returns:
-  #  success: time in ms
-  #  failure: None
-  def getCurrentTime(self):
-    timestr = self.runCmd(["shell", "date", "+%s"]).stdout.read().strip()
-    if (not timestr or not timestr.isdigit()):
-        return None
-    return str(int(timestr)*1000)
+    def getCurrentTime(self):
+        """
+        external function
+        returns:
+          success: time in ms
+          failure: None
+        """
+        timestr = self.runCmd(["shell", "date", "+%s"]).stdout.read().strip()
+        if (not timestr or not timestr.isdigit()):
+            return None
+        return str(int(timestr)*1000)
 
-  # Returns information about the device:
-  # Directive indicates the information you want to get, your choices are:
-  # os - name of the os
-  # id - unique id of the device
-  # uptime - uptime of the device
-  # systime - system time of the device
-  # screen - screen resolution
-  # memory - memory stats
-  # process - list of running processes (same as ps)
-  # disk - total, free, available bytes on disk
-  # power - power status (charge, battery temp)
-  # all - all of them - or call it with no parameters to get all the information
-  ### Note that uptimemillis is NOT supported, as there is no way to get this
-  ### data from the shell.
-  # returns:
-  #   success: dict of info strings by directive name
-  #   failure: {}
-  def getInfo(self, directive="all"):
-    ret = {}
-    if (directive == "id" or directive == "all"):
-      ret["id"] = self.runCmd(["get-serialno"]).stdout.read()
-    if (directive == "os" or directive == "all"):
-      ret["os"] = self.runCmd(["shell", "getprop", "ro.build.display.id"]).stdout.read()
-    if (directive == "uptime" or directive == "all"):
-      utime = self.runCmd(["shell", "uptime"]).stdout.read()
-      if (not utime):
-        raise DMError("error getting uptime")
-      utime = utime[9:]
-      hours = utime[0:utime.find(":")]
-      utime = utime[utime[1:].find(":") + 2:]
-      minutes = utime[0:utime.find(":")]
-      utime = utime[utime[1:].find(":") +  2:]
-      seconds = utime[0:utime.find(",")]
-      ret["uptime"] = ["0 days " + hours + " hours " + minutes + " minutes " + seconds + " seconds"]
-    if (directive == "process" or directive == "all"):
-      ret["process"] = self.runCmd(["shell", "ps"]).stdout.read()
-    if (directive == "systime" or directive == "all"):
-      ret["systime"] = self.runCmd(["shell", "date"]).stdout.read()
-    print ret
-    return ret
+    def getInfo(self, directive="all"):
+        """
+        Returns information about the device:
+        Directive indicates the information you want to get, your choices are:
+          os - name of the os
+          id - unique id of the device
+          uptime - uptime of the device
+          systime - system time of the device
+          screen - screen resolution
+          memory - memory stats
+          process - list of running processes (same as ps)
+          disk - total, free, available bytes on disk
+          power - power status (charge, battery temp)
+          all - all of them - or call it with no parameters to get all the information
+        ### Note that uptimemillis is NOT supported, as there is no way to get this
+        ### data from the shell.
+        returns:
+           success: dict of info strings by directive name
+           failure: {}
+        """
+        ret = {}
+        if (directive == "id" or directive == "all"):
+            ret["id"] = self.runCmd(["get-serialno"]).stdout.read()
+        if (directive == "os" or directive == "all"):
+            ret["os"] = self.runCmd(["shell", "getprop", "ro.build.display.id"]).stdout.read()
+        if (directive == "uptime" or directive == "all"):
+            utime = self.runCmd(["shell", "uptime"]).stdout.read()
+            if (not utime):
+                raise DMError("error getting uptime")
+            utime = utime[9:]
+            hours = utime[0:utime.find(":")]
+            utime = utime[utime[1:].find(":") + 2:]
+            minutes = utime[0:utime.find(":")]
+            utime = utime[utime[1:].find(":") +  2:]
+            seconds = utime[0:utime.find(",")]
+            ret["uptime"] = ["0 days " + hours + " hours " + minutes + " minutes " + seconds + " seconds"]
+        if (directive == "process" or directive == "all"):
+            ret["process"] = self.runCmd(["shell", "ps"]).stdout.read()
+        if (directive == "systime" or directive == "all"):
+            ret["systime"] = self.runCmd(["shell", "date"]).stdout.read()
+        print ret
+        return ret
 
-  def runCmd(self, args):
-    # If we are not root but have run-as, and we're trying to execute
-    # a shell command then using run-as is the best we can do
-    finalArgs = [self.adbPath]
-    if self.deviceSerial:
-      finalArgs.extend(['-s', self.deviceSerial])
-    if (not self.haveRoot and self.useRunAs and args[0] == "shell" and args[1] != "run-as"):
-      args.insert(1, "run-as")
-      args.insert(2, self.packageName)
-    finalArgs.extend(args)
-    return subprocess.Popen(finalArgs, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+    def runCmd(self, args):
+        # If we are not root but have run-as, and we're trying to execute
+        # a shell command then using run-as is the best we can do
+        finalArgs = [self.adbPath]
+        if self.deviceSerial:
+            finalArgs.extend(['-s', self.deviceSerial])
+        if (not self.haveRoot and self.useRunAs and args[0] == "shell" and args[1] != "run-as"):
+            args.insert(1, "run-as")
+            args.insert(2, self.packageName)
+        finalArgs.extend(args)
+        return subprocess.Popen(finalArgs, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
 
-  def runCmdAs(self, args):
-    if self.useRunAs:
-      args.insert(1, "run-as")
-      args.insert(2, self.packageName)
-    return self.runCmd(args)
+    def runCmdAs(self, args):
+        if self.useRunAs:
+            args.insert(1, "run-as")
+            args.insert(2, self.packageName)
+        return self.runCmd(args)
 
-  # timeout is specified in seconds, and if no timeout is given, 
-  # we will run until the script returns
-  def checkCmd(self, args, timeout=None):
-    # If we are not root but have run-as, and we're trying to execute
-    # a shell command then using run-as is the best we can do
-    finalArgs = [self.adbPath]
-    if self.deviceSerial:
-      finalArgs.extend(['-s', self.deviceSerial])
-    if (not self.haveRoot and self.useRunAs and args[0] == "shell" and args[1] != "run-as"):
-      args.insert(1, "run-as")
-      args.insert(2, self.packageName)
-    finalArgs.extend(args)
-    if timeout:
+    # timeout is specified in seconds, and if no timeout is given, 
+    # we will run until we hit the default_timeout specified in the __init__
+    def checkCmd(self, args, timeout=None):
+        # If we are not root but have run-as, and we're trying to execute
+        # a shell command then using run-as is the best we can do
+        finalArgs = [self.adbPath]
+        if self.deviceSerial:
+            finalArgs.extend(['-s', self.deviceSerial])
+        if (not self.haveRoot and self.useRunAs and args[0] == "shell" and args[1] != "run-as"):
+            args.insert(1, "run-as")
+            args.insert(2, self.packageName)
+        finalArgs.extend(args)
+        if not timeout:
+            # We are asserting that all commands will complete in this time unless otherwise specified
+            timeout = self.default_timeout
+
         timeout = int(timeout)
         proc = subprocess.Popen(finalArgs)
         start_time = time.time()
         ret_code = proc.poll()
         while ((time.time() - start_time) <= timeout) and ret_code == None:
             time.sleep(1)
             ret_code = proc.poll()
         if ret_code == None:
             proc.kill()
             raise DMError("Timeout exceeded for checkCmd call")
         return ret_code
-    return subprocess.check_call(finalArgs)
 
-  def checkCmdAs(self, args, timeout=None):
-    if (self.useRunAs):
-      args.insert(1, "run-as")
-      args.insert(2, self.packageName)
-    return self.checkCmd(args, timeout)
+    def checkCmdAs(self, args, timeout=None):
+        if (self.useRunAs):
+            args.insert(1, "run-as")
+            args.insert(2, self.packageName)
+        return self.checkCmd(args, timeout)
 
-  # external function
-  # returns:
-  #  success: True
-  #  failure: False
-  def chmodDir(self, remoteDir):
-    if (self.isDir(remoteDir)):
-      files = self.listFiles(remoteDir.strip())
-      for f in files:
-        remoteEntry = remoteDir.strip() + "/" + f.strip()
-        if (self.isDir(remoteEntry)):
-          self.chmodDir(remoteEntry)
+    def chmodDir(self, remoteDir):
+        """
+        external function
+        returns:
+          success: True
+          failure: False
+        """
+        if (self.isDir(remoteDir)):
+            files = self.listFiles(remoteDir.strip())
+            for f in files:
+                remoteEntry = remoteDir.strip() + "/" + f.strip()
+                if (self.isDir(remoteEntry)):
+                    self.chmodDir(remoteEntry)
+                else:
+                    self.checkCmdAs(["shell", "chmod", "777", remoteEntry])
+                    print "chmod " + remoteEntry
+            self.checkCmdAs(["shell", "chmod", "777", remoteDir])
+            print "chmod " + remoteDir
         else:
-          self.checkCmdAs(["shell", "chmod", "777", remoteEntry])
-          print "chmod " + remoteEntry
-      self.checkCmdAs(["shell", "chmod", "777", remoteDir])
-      print "chmod " + remoteDir
-    else:
-      self.checkCmdAs(["shell", "chmod", "777", remoteDir.strip()])
-      print "chmod " + remoteDir.strip()
-    return True
+            self.checkCmdAs(["shell", "chmod", "777", remoteDir.strip()])
+            print "chmod " + remoteDir.strip()
+        return True
 
-  def verifyADB(self):
-    # Check to see if adb itself can be executed.
-    if self.adbPath != 'adb':
-      if not os.access(self.adbPath, os.X_OK):
-        raise DMError("invalid adb path, or adb not executable: %s", self.adbPath)
+    def verifyADB(self):
+        # Check to see if adb itself can be executed.
+        if self.adbPath != 'adb':
+            if not os.access(self.adbPath, os.X_OK):
+                raise DMError("invalid adb path, or adb not executable: %s", self.adbPath)
 
-    try:
-      self.checkCmd(["version"])
-    except os.error, err:
-      raise DMError("unable to execute ADB (%s): ensure Android SDK is installed and adb is in your $PATH" % err)
-    except subprocess.CalledProcessError:
-      raise DMError("unable to execute ADB: ensure Android SDK is installed and adb is in your $PATH")
+        try:
+            self.checkCmd(["version"])
+        except os.error, err:
+            raise DMError("unable to execute ADB (%s): ensure Android SDK is installed and adb is in your $PATH" % err)
+        except subprocess.CalledProcessError:
+            raise DMError("unable to execute ADB: ensure Android SDK is installed and adb is in your $PATH")
 
-  def verifyDevice(self):
-    # If there is a device serial number, see if adb is connected to it
-    if self.deviceSerial:
-      deviceStatus = None
-      proc = subprocess.Popen([self.adbPath, "devices"],
-                              stdout=subprocess.PIPE,
-                              stderr=subprocess.STDOUT)
-      for line in proc.stdout:
-        m = re.match('(.+)?\s+(.+)$', line)
-        if m:
-          if self.deviceSerial == m.group(1):
-            deviceStatus = m.group(2)
-      if deviceStatus == None:
-        raise DMError("device not found: %s" % self.deviceSerial)
-      elif deviceStatus != "device":
-        raise DMError("bad status for device %s: %s" % (self.deviceSerial,
-                                                        deviceStatus))
+    def verifyDevice(self):
+        # If there is a device serial number, see if adb is connected to it
+        if self.deviceSerial:
+            deviceStatus = None
+            proc = subprocess.Popen([self.adbPath, "devices"],
+                                    stdout=subprocess.PIPE,
+                                    stderr=subprocess.STDOUT)
+            for line in proc.stdout:
+                m = re.match('(.+)?\s+(.+)$', line)
+                if m:
+                    if self.deviceSerial == m.group(1):
+                        deviceStatus = m.group(2)
+            if deviceStatus == None:
+                raise DMError("device not found: %s" % self.deviceSerial)
+            elif deviceStatus != "device":
+                raise DMError("bad status for device %s: %s" % (self.deviceSerial,
+                                                                                                                deviceStatus))
 
-    # Check to see if we can connect to device and run a simple command
-    try:
-      self.checkCmd(["shell", "echo"])
-    except subprocess.CalledProcessError:
-      raise DMError("unable to connect to device: is it plugged in?")
+        # Check to see if we can connect to device and run a simple command
+        try:
+            self.checkCmd(["shell", "echo"])
+        except subprocess.CalledProcessError:
+            raise DMError("unable to connect to device: is it plugged in?")
 
-  def verifyRoot(self):
-    # a test to see if we have root privs
-    p = self.runCmd(["shell", "id"])
-    response = p.stdout.readline()
-    response = response.rstrip()
-    response = response.split(' ')
-    if (response[0].find('uid=0') < 0 or response[1].find('gid=0') < 0):
-      print "NOT running as root ", response[0].find('uid=0')
-      raise DMError("not running as root")
+    def verifyRoot(self):
+        # a test to see if we have root privs
+        p = self.runCmd(["shell", "id"])
+        response = p.stdout.readline()
+        response = response.rstrip()
+        response = response.split(' ')
+        if (response[0].find('uid=0') < 0 or response[1].find('gid=0') < 0):
+            print "NOT running as root ", response[0].find('uid=0')
+            raise DMError("not running as root")
 
-    self.haveRoot = True
+        self.haveRoot = True
 
-  def isCpAvailable(self):
-    # Some Android systems may not have a cp command installed,
-    # or it may not be executable by the user.
-    data = self.runCmd(["shell", "cp"]).stdout.read()
-    if (re.search('Usage', data)):
-      return True
-    else:
-      data = self.runCmd(["shell", "dd", "-"]).stdout.read()
-      if (re.search('unknown operand', data)):
-        print "'cp' not found, but 'dd' was found as a replacement"
-        self.useDDCopy = True
-        return True
-      print "unable to execute 'cp' on device; consider installing busybox from Android Market"
-      return False
+    def isCpAvailable(self):
+        # Some Android systems may not have a cp command installed,
+        # or it may not be executable by the user.
+        data = self.runCmd(["shell", "cp"]).stdout.read()
+        if (re.search('Usage', data)):
+            return True
+        else:
+            data = self.runCmd(["shell", "dd", "-"]).stdout.read()
+            if (re.search('unknown operand', data)):
+                print "'cp' not found, but 'dd' was found as a replacement"
+                self.useDDCopy = True
+                return True
+            print "unable to execute 'cp' on device; consider installing busybox from Android Market"
+            return False
 
-  def verifyRunAs(self):
-    # If a valid package name is available, and certain other
-    # conditions are met, devicemanagerADB can execute file operations
-    # via the "run-as" command, so that pushed files and directories 
-    # are created by the uid associated with the package, more closely
-    # echoing conditions encountered by Fennec at run time.
-    # Check to see if run-as can be used here, by verifying a 
-    # file copy via run-as.
-    self.useRunAs = False
-    devroot = self.getDeviceRoot()
-    if (self.packageName and self.isCpAvailable() and devroot):
-      tmpDir = self.getTempDir()
+    def verifyRunAs(self):
+        # If a valid package name is available, and certain other
+        # conditions are met, devicemanagerADB can execute file operations
+        # via the "run-as" command, so that pushed files and directories 
+        # are created by the uid associated with the package, more closely
+        # echoing conditions encountered by Fennec at run time.
+        # Check to see if run-as can be used here, by verifying a 
+        # file copy via run-as.
+        self.useRunAs = False
+        devroot = self.getDeviceRoot()
+        if (self.packageName and self.isCpAvailable() and devroot):
+            tmpDir = self.getTempDir()
 
-      # The problem here is that run-as doesn't cause a non-zero exit code
-      # when failing because of a non-existent or non-debuggable package :(
-      runAsOut = self.runCmd(["shell", "run-as", self.packageName, "mkdir", devroot + "/sanity"]).communicate()[0]
-      if runAsOut.startswith("run-as:") and ("not debuggable" in runAsOut or
-                                             "is unknown" in runAsOut):
-        raise DMError("run-as failed sanity check")
+            # The problem here is that run-as doesn't cause a non-zero exit code
+            # when failing because of a non-existent or non-debuggable package :(
+            runAsOut = self.runCmd(["shell", "run-as", self.packageName, "mkdir", devroot + "/sanity"]).communicate()[0]
+            if runAsOut.startswith("run-as:") and ("not debuggable" in runAsOut or "is unknown" in runAsOut):
+                raise DMError("run-as failed sanity check")
 
-      tmpfile = tempfile.NamedTemporaryFile()
-      self.checkCmd(["push", tmpfile.name, tmpDir + "/tmpfile"])
-      if self.useDDCopy:
-        self.checkCmd(["shell", "run-as", self.packageName, "dd", "if=" + tmpDir + "/tmpfile", "of=" + devroot + "/sanity/tmpfile"])
-      else:
-        self.checkCmd(["shell", "run-as", self.packageName, "cp", tmpDir + "/tmpfile", devroot + "/sanity"])
-      if (self.fileExists(devroot + "/sanity/tmpfile")):
-        print "will execute commands via run-as " + self.packageName
-        self.useRunAs = True
-      self.checkCmd(["shell", "rm", devroot + "/tmp/tmpfile"])
-      self.checkCmd(["shell", "run-as", self.packageName, "rm", "-r", devroot + "/sanity"])
+            tmpfile = tempfile.NamedTemporaryFile()
+            self.checkCmd(["push", tmpfile.name, tmpDir + "/tmpfile"])
+            if self.useDDCopy:
+                self.checkCmd(["shell", "run-as", self.packageName, "dd", "if=" + tmpDir + "/tmpfile", "of=" + devroot + "/sanity/tmpfile"])
+            else:
+                self.checkCmd(["shell", "run-as", self.packageName, "cp", tmpDir + "/tmpfile", devroot + "/sanity"])
+            if (self.fileExists(devroot + "/sanity/tmpfile")):
+                print "will execute commands via run-as " + self.packageName
+                self.useRunAs = True
+            self.checkCmd(["shell", "rm", devroot + "/tmp/tmpfile"])
+            self.checkCmd(["shell", "run-as", self.packageName, "rm", "-r", devroot + "/sanity"])
 
-  def isUnzipAvailable(self):
-    data = self.runCmdAs(["shell", "unzip"]).stdout.read()
-    if (re.search('Usage', data)):
-      return True
-    else:
-      return False
+    def isUnzipAvailable(self):
+        data = self.runCmdAs(["shell", "unzip"]).stdout.read()
+        if (re.search('Usage', data)):
+            return True
+        else:
+            return False
 
-  def isLocalZipAvailable(self):
-    try:
-      subprocess.check_call(["zip", "-?"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-    except:
-      return False
-    return True
+    def isLocalZipAvailable(self):
+        try:
+            subprocess.check_call(["zip", "-?"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+        except:
+            return False
+        return True
 
-  def verifyZip(self):
-    # If "zip" can be run locally, and "unzip" can be run remotely, then pushDir
-    # can use these to push just one file per directory -- a significant
-    # optimization for large directories.
-    self.useZip = False
-    if (self.isUnzipAvailable() and self.isLocalZipAvailable()):
-      print "will use zip to push directories"
-      self.useZip = True
-    else:
-      raise DMError("zip not available")
+    def verifyZip(self):
+        # If "zip" can be run locally, and "unzip" can be run remotely, then pushDir
+        # can use these to push just one file per directory -- a significant
+        # optimization for large directories.
+        self.useZip = False
+        if (self.isUnzipAvailable() and self.isLocalZipAvailable()):
+            print "will use zip to push directories"
+            self.useZip = True
+        else:
+            raise DMError("zip not available")
--- a/testing/mozbase/mozdevice/mozdevice/devicemanagerSUT.py
+++ b/testing/mozbase/mozdevice/mozdevice/devicemanagerSUT.py
@@ -12,1236 +12,1358 @@ import posixpath
 import subprocess
 from threading import Thread
 import StringIO
 from devicemanager import DeviceManager, FileError, NetworkTools, _pop_last_line
 import errno
 from distutils.version import StrictVersion
 
 class AgentError(Exception):
-  "SUTAgent-specific exception."
+    "SUTAgent-specific exception."
 
-  def __init__(self, msg= '', fatal = False):
-    self.msg = msg
-    self.fatal = fatal
+    def __init__(self, msg= '', fatal = False):
+        self.msg = msg
+        self.fatal = fatal
 
-  def __str__(self):
-    return self.msg
+    def __str__(self):
+        return self.msg
 
 class DeviceManagerSUT(DeviceManager):
-  host = ''
-  port = 0
-  debug = 2
-  retries = 0
-  tempRoot = os.getcwd()
-  base_prompt = '$>'
-  base_prompt_re = '\$\>'
-  prompt_sep = '\x00'
-  prompt_regex = '.*(' + base_prompt_re + prompt_sep + ')'
-  agentErrorRE = re.compile('^##AGENT-WARNING##\ ?(.*)')
-
-  # TODO: member variable to indicate error conditions.
-  # This should be set to a standard error from the errno module.
-  # So, for example, when an error occurs because of a missing file/directory,
-  # before returning, the function would do something like 'self.error = errno.ENOENT'.
-  # The error would be set where appropriate--so sendCMD() could set socket errors,
-  # pushFile() and other file-related commands could set filesystem errors, etc.
+    host = ''
+    port = 0
+    debug = 2
+    tempRoot = os.getcwd()
+    base_prompt = '$>'
+    base_prompt_re = '\$\>'
+    prompt_sep = '\x00'
+    prompt_regex = '.*(' + base_prompt_re + prompt_sep + ')'
+    agentErrorRE = re.compile('^##AGENT-WARNING##\ ?(.*)')
+    default_timeout = 300
 
-  def __init__(self, host, port = 20701, retrylimit = 5, deviceRoot = None):
-    self.host = host
-    self.port = port
-    self.retrylimit = retrylimit
-    self.retries = 0
-    self._sock = None
-    self.deviceRoot = deviceRoot
-    if self.getDeviceRoot() == None:
-      raise BaseException("Failed to connect to SUT Agent and retrieve the device root.")
-    try:
-      verstring = self.runCmds([{ 'cmd': 'ver' }])
-      self.agentVersion = re.sub('SUTAgentAndroid Version ', '', verstring)
-    except AgentError, err:
-      raise BaseException("Failed to get SUTAgent version")
-
-  def _cmdNeedsResponse(self, cmd):
-    """ Not all commands need a response from the agent:
-        * rebt obviously doesn't get a response
-        * uninstall performs a reboot to ensure starting in a clean state and
-          so also doesn't look for a response
-    """
-    noResponseCmds = [re.compile('^rebt'),
-                      re.compile('^uninst .*$'),
-                      re.compile('^pull .*$')]
+    # TODO: member variable to indicate error conditions.
+    # This should be set to a standard error from the errno module.
+    # So, for example, when an error occurs because of a missing file/directory,
+    # before returning, the function would do something like 'self.error = errno.ENOENT'.
+    # The error would be set where appropriate--so sendCMD() could set socket errors,
+    # pushFile() and other file-related commands could set filesystem errors, etc.
 
-    for c in noResponseCmds:
-      if (c.match(cmd)):
-        return False
-
-    # If the command is not in our list, then it gets a response
-    return True
+    def __init__(self, host, port = 20701, retrylimit = 5, deviceRoot = None):
+        self.host = host
+        self.port = port
+        self.retrylimit = retrylimit
+        self._sock = None
+        self.deviceRoot = deviceRoot
+        if self.getDeviceRoot() == None:
+            raise BaseException("Failed to connect to SUT Agent and retrieve the device root.")
+        try:
+            verstring = self.runCmds([{ 'cmd': 'ver' }])
+            self.agentVersion = re.sub('SUTAgentAndroid Version ', '', verstring)
+        except AgentError, err:
+            raise BaseException("Failed to get SUTAgent version")
 
-  def _stripPrompt(self, data):
-    '''
-    internal function
-    take a data blob and strip instances of the prompt '$>\x00'
-    '''
-    promptre = re.compile(self.prompt_regex + '.*')
-    retVal = []
-    lines = data.split('\n')
-    for line in lines:
-      foundPrompt = False
-      try:
-        while (promptre.match(line)):
-          foundPrompt = True
-          pieces = line.split(self.prompt_sep)
-          index = pieces.index('$>')
-          pieces.pop(index)
-          line = self.prompt_sep.join(pieces)
-      except(ValueError):
-        pass
+    def _cmdNeedsResponse(self, cmd):
+        """ Not all commands need a response from the agent:
+            * rebt obviously doesn't get a response
+            * uninstall performs a reboot to ensure starting in a clean state and
+              so also doesn't look for a response
+        """
+        noResponseCmds = [re.compile('^rebt'),
+                          re.compile('^uninst .*$'),
+                          re.compile('^pull .*$')]
 
-      # we don't want to append lines that are blank after stripping the
-      # prompt (those are basically "prompts")
-      if not foundPrompt or line:
-        retVal.append(line)
-
-    return '\n'.join(retVal)
+        for c in noResponseCmds:
+            if (c.match(cmd)):
+                return False
 
-  def _shouldCmdCloseSocket(self, cmd):
-    """ Some commands need to close the socket after they are sent:
-    * rebt
-    * uninst
-    * quit
-    """
-    socketClosingCmds = [re.compile('^quit.*'),
-                         re.compile('^rebt.*'),
-                         re.compile('^uninst .*$')]
-
-    for c in socketClosingCmds:
-      if (c.match(cmd)):
+        # If the command is not in our list, then it gets a response
         return True
 
-    return False
-
-  def sendCmds(self, cmdlist, outputfile, timeout = None):
-    '''
-    a wrapper for _doCmds that loops up to self.retrylimit iterations.
-    this allows us to move the retry logic outside of the _doCmds() to make it
-    easier for debugging in the future.
-    note that since cmdlist is a list of commands, they will all be retried if
-    one fails.  this is necessary in particular for pushFile(), where we don't want
-    to accidentally send extra data if a failure occurs during data transmission.
-    '''
-    if timeout:
-      raise NotImplementedError("'timeout' parameter is not yet supported")
-    while self.retries < self.retrylimit:
-      try:
-        self._doCmds(cmdlist, outputfile, timeout)
-        return
-      except AgentError, err:
-        # re-raise error if it's fatal (i.e. the device got the command but
-        # couldn't execute it). retry otherwise
-        if err.fatal:
-          raise err
-        if self.debug >= 2:
-          print err
-        self.retries += 1
-
-    raise AgentError("unable to connect to %s after %s attempts" % (self.host, self.retrylimit))
-
-  def runCmds(self, cmdlist, timeout = None):
-    '''
-    similar to sendCmds, but just returns any output as a string instead of
-    writing to a file. this is normally what you want to call to send a set
-    of commands to the agent
-    '''
-    outputfile = StringIO.StringIO()
-    self.sendCmds(cmdlist, outputfile, timeout)
-    outputfile.seek(0)
-    return outputfile.read()
+    def _stripPrompt(self, data):
+        """ 
+        internal function
+        take a data blob and strip instances of the prompt '$>\x00'
+        """ 
+        promptre = re.compile(self.prompt_regex + '.*')
+        retVal = []
+        lines = data.split('\n')
+        for line in lines:
+            foundPrompt = False
+            try:
+                while (promptre.match(line)):
+                    foundPrompt = True
+                    pieces = line.split(self.prompt_sep)
+                    index = pieces.index('$>')
+                    pieces.pop(index)
+                    line = self.prompt_sep.join(pieces)
+            except(ValueError):
+                pass
 
-  def _doCmds(self, cmdlist, outputfile, timeout):
-    promptre = re.compile(self.prompt_regex + '$')
-    shouldCloseSocket = False
-    recvGuard = 1000
+            # we don't want to append lines that are blank after stripping the
+            # prompt (those are basically "prompts")
+            if not foundPrompt or line:
+                retVal.append(line)
 
-    if not self._sock:
-      try:
-        if self.debug >= 1:
-          print "reconnecting socket"
-        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-      except socket.error, msg:
-        self._sock = None
-        raise AgentError("unable to create socket: "+str(msg))
+        return '\n'.join(retVal)
 
-      try:
-        self._sock.connect((self.host, int(self.port)))
-        self._sock.recv(1024)
-      except socket.error, msg:
-        self._sock.close()
-        self._sock = None
-        raise AgentError("unable to connect socket: "+str(msg))
-
-    for cmd in cmdlist:
-      cmdline = '%s\r\n' % cmd['cmd']
+    def _shouldCmdCloseSocket(self, cmd):
+        """ Some commands need to close the socket after they are sent:
+            * rebt
+            * uninst
+            * quit
+        """
+        socketClosingCmds = [re.compile('^quit.*'),
+                             re.compile('^rebt.*'),
+                             re.compile('^uninst .*$')]
 
-      try:
-        sent = self._sock.send(cmdline)
-        if sent != len(cmdline):
-          raise AgentError("ERROR: our cmd was %s bytes and we "
-                           "only sent %s" % (len(cmdline), sent))
-        if cmd.get('data'):
-          sent = self._sock.send(cmd['data'])
-          if sent != len(cmd['data']):
-              raise AgentError("ERROR: we had %s bytes of data to send, but "
-                               "only sent %s" % (len(cmd['data']), sent))
+        for c in socketClosingCmds:
+            if (c.match(cmd)):
+                return True
 
-        if (self.debug >= 4): print "sent cmd: " + str(cmd['cmd'])
-      except socket.error, msg:
-        self._sock.close()
-        self._sock = None
-        if self.debug >= 1:
-          print "Error sending data to socket. cmd="+str(cmd['cmd'])+"; err="+str(msg)
         return False
 
-      # Check if the command should close the socket
-      shouldCloseSocket = self._shouldCmdCloseSocket(cmd['cmd'])
-
-      # Handle responses from commands
-      if (self._cmdNeedsResponse(cmd['cmd'])):
-        found = False
-        loopguard = 0
-        data = ""
-        commandFailed = False
-
-        while (found == False and (loopguard < recvGuard)):
-          temp = ''
-          if (self.debug >= 4): print "recv'ing..."
-
-          # Get our response
-          try:
-             # Wait up to a second for socket to become ready for reading...
-            if select.select([self._sock], [], [], 1)[0]:
-                temp = self._sock.recv(1024)
-            if (self.debug >= 4): print "response: " + str(temp)
-          except socket.error, err:
-            self._sock.close()
-            self._sock = None
-            # This error shows up with we have our tegra rebooted.
-            if err[0] == errno.ECONNRESET:
-              raise AgentError("Automation error: Error receiving data from socket (possible reboot). cmd=%s; err=%s" % (cmd, err))
-            raise AgentError("Error receiving data from socket. cmd=%s; err=%s" % (cmd, err))
-
-          data += temp
+    def sendCmds(self, cmdlist, outputfile, timeout = None):
+        """
+        a wrapper for _doCmds that loops up to self.retrylimit iterations.
+        this allows us to move the retry logic outside of the _doCmds() to make it
+        easier for debugging in the future.
+        note that since cmdlist is a list of commands, they will all be retried if
+        one fails.  this is necessary in particular for pushFile(), where we don't want
+        to accidentally send extra data if a failure occurs during data transmission.
+        """
+        retries = 0
+        while retries < self.retrylimit:
+            try:
+                self._doCmds(cmdlist, outputfile, timeout)
+                return
+            except AgentError, err:
+                # re-raise error if it's fatal (i.e. the device got the command but
+                # couldn't execute it). retry otherwise
+                if err.fatal:
+                    raise err
+                if self.debug >= 2:
+                    print err
+                retries += 1
+                # if we lost the connection or failed to establish one, wait a bit
+                if retries < self.retrylimit and not self._sock:
+                    sleep_time = 5 * retries
+                    print 'Could not connect; sleeping for %d seconds.' % sleep_time
+                    time.sleep(sleep_time)
 
-          # If something goes wrong in the agent it will send back a string that
-          # starts with '##AGENT-WARNING##'
-          if not commandFailed:
-            errorMatch = self.agentErrorRE.match(data)
-            if errorMatch:
-              # We still need to consume the prompt, so raise an error after
-              # draining the rest of the buffer.
-              commandFailed = True
-
-          for line in data.splitlines():
-            if promptre.match(line):
-              found = True
-              data = self._stripPrompt(data)
-              break
+        raise AgentError("Remote Device Error: unable to connect to %s after %s attempts" % (self.host, self.retrylimit))
 
-          # periodically flush data to output file to make sure it doesn't get
-          # too big/unwieldly
-          if len(data) > 1024:
-              outputfile.write(data[0:1024])
-              data = data[1024:]
-
-          # If we violently lose the connection to the device, this loop tends to spin,
-          # this guard prevents that
-          if (temp == ''):
-            loopguard += 1
+    def runCmds(self, cmdlist, timeout = None):
+        """ 
+        similar to sendCmds, but just returns any output as a string instead of
+        writing to a file. this is normally what you want to call to send a set
+        of commands to the agent
+        """
+        outputfile = StringIO.StringIO()
+        self.sendCmds(cmdlist, outputfile, timeout)
+        outputfile.seek(0)
+        return outputfile.read()
 
-        if commandFailed:
-          raise AgentError("Agent Error processing command '%s'; err='%s'" %
-                           (cmd['cmd'], errorMatch.group(1)), fatal=True)
-
-        # Write any remaining data to outputfile
-        outputfile.write(data)
+    def _doCmds(self, cmdlist, outputfile, timeout):
+        promptre = re.compile(self.prompt_regex + '$')
+        shouldCloseSocket = False
 
-    if shouldCloseSocket:
-      try:
-        self._sock.close()
-        self._sock = None
-      except:
-        self._sock = None
-        raise AgentError("Error closing socket")
+        if not timeout:
+            # We are asserting that all commands will complete in this time unless otherwise specified
+            timeout = self.default_timeout
 
-  # external function: executes shell command on device
-  # returns:
-  # success: <return code>
-  # failure: None
-  def shell(self, cmd, outputfile, env=None, cwd=None, timeout=None, root=False):
-    cmdline = self._escapedCommandLine(cmd)
-    if env:
-      cmdline = '%s %s' % (self.formatEnvString(env), cmdline)
-
-    haveExecSu = (StrictVersion(self.agentVersion) >= StrictVersion('1.13'))
-
-    # Depending on agent version we send one of the following commands here:
-    # * exec (run as normal user)
-    # * execsu (run as privileged user)
-    # * execcwd (run as normal user from specified directory)
-    # * execcwdsu (run as privileged user from specified directory)
+        if not self._sock:
+            try:
+                if self.debug >= 1:
+                    print "reconnecting socket"
+                self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            except socket.error, msg:
+                self._sock = None
+                raise AgentError("Automation Error: unable to create socket: "+str(msg))
 
-    cmd = "exec"
-    if cwd:
-      cmd += "cwd"
-    if root and haveExecSu:
-      cmd += "su"
+            try:
+                self._sock.connect((self.host, int(self.port)))
+                if select.select([self._sock], [], [], timeout)[0]:
+                    self._sock.recv(1024)
+                else:
+                    raise AgentError("Remote Device Error: Timeout in connecting", fatal=True)
+                    return False
+            except socket.error, msg:
+                self._sock.close()
+                self._sock = None
+                raise AgentError("Remote Device Error: unable to connect socket: "+str(msg))
 
-    try:
-      if cwd:
-        self.sendCmds([{ 'cmd': '%s %s %s' % (cmd, cwd, cmdline) }], outputfile, timeout)
-      else:
-        if (not root) or haveExecSu:
-          self.sendCmds([{ 'cmd': '%s %s' % (cmd, cmdline) }], outputfile, timeout)
-        else:
-          # need to manually inject su -c for backwards compatibility (this may
-          # not work on ICS or above!!)
-          # (FIXME: this backwards compatibility code is really ugly and should
-          # be deprecated at some point in the future)
-          self.sendCmds([ { 'cmd': '%s su -c "%s"' % (cmd, cmdline) }], outputfile,
-                          timeout)
-    except AgentError:
-      return None
+        for cmd in cmdlist:
+            cmdline = '%s\r\n' % cmd['cmd']
 
-    # dig through the output to get the return code
-    lastline = _pop_last_line(outputfile)
-    if lastline:
-      m = re.search('return code \[([0-9]+)\]', lastline)
-      if m:
-        return int(m.group(1))
-
-    # woops, we couldn't find an end of line/return value
-    return None
-
-  # external function
-  # returns:
-  #  success: True
-  #  failure: False
-  def pushFile(self, localname, destname):
-    if (os.name == "nt"):
-      destname = destname.replace('\\', '/')
+            try:
+                sent = self._sock.send(cmdline)
+                if sent != len(cmdline):
+                    raise AgentError("ERROR: our cmd was %s bytes and we "
+                                                      "only sent %s" % (len(cmdline), sent))
+                if cmd.get('data'):
+                    sent = self._sock.send(cmd['data'])
+                    if sent != len(cmd['data']):
+                            raise AgentError("ERROR: we had %s bytes of data to send, but "
+                                                              "only sent %s" % (len(cmd['data']), sent))
 
-    if (self.debug >= 3): print "in push file with: " + localname + ", and: " + destname
-    if (self.dirExists(destname)):
-      if (not destname.endswith('/')):
-        destname = destname + '/'
-      destname = destname + os.path.basename(localname)
-    if (self.validateFile(destname, localname) == True):
-      if (self.debug >= 3): print "files are validated"
-      return True
+                if self.debug >= 4:
+                    print "sent cmd: " + str(cmd['cmd'])
+            except socket.error, msg:
+                self._sock.close()
+                self._sock = None
+                if self.debug >= 1:
+                    print "Remote Device Error: Error sending data to socket. cmd="+str(cmd['cmd'])+"; err="+str(msg)
+                return False
 
-    if self.mkDirs(destname) == None:
-      print "unable to make dirs: " + destname
-      return False
+            # Check if the command should close the socket
+            shouldCloseSocket = self._shouldCmdCloseSocket(cmd['cmd'])
 
-    if (self.debug >= 3): print "sending: push " + destname
-
-    filesize = os.path.getsize(localname)
-    f = open(localname, 'rb')
-    data = f.read()
-    f.close()
+            # Handle responses from commands
+            if self._cmdNeedsResponse(cmd['cmd']):
+                foundPrompt = False
+                data = ""
+                timer = 0
+                select_timeout = 1
+                commandFailed = False
 
-    try:
-      retVal = self.runCmds([{ 'cmd': 'push ' + destname + ' ' + str(filesize),
-                               'data': data }])
-    except AgentError, e:
-      print "error pushing file: %s" % e.msg
-      return False
-
-    if (self.debug >= 3): print "push returned: " + str(retVal)
+                while not foundPrompt:
+                    socketClosed = False
+                    errStr = ''
+                    temp = ''
+                    if self.debug >= 4:
+                        print "recv'ing..."
 
-    validated = False
-    if (retVal):
-      retline = retVal.strip()
-      if (retline == None):
-        # Then we failed to get back a hash from agent, try manual validation
-        validated = self.validateFile(destname, localname)
-      else:
-        # Then we obtained a hash from push
-        localHash = self.getLocalHash(localname)
-        if (str(localHash) == str(retline)):
-          validated = True
-    else:
-      # We got nothing back from sendCMD, try manual validation
-      validated = self.validateFile(destname, localname)
+                    # Get our response
+                    try:
+                          # Wait up to a second for socket to become ready for reading...
+                        if select.select([self._sock], [], [], select_timeout)[0]:
+                            temp = self._sock.recv(1024)
+                            if self.debug >= 4:
+                                print "response: " + str(temp)
+                            timer = 0
+                            if not temp:
+                                socketClosed = True
+                                errStr = 'connection closed'
+                        timer += select_timeout
+                        if timer > timeout:
+                            raise AgentError("Automation Error: Timeout in command %s" % cmd['cmd'], fatal=True)
+                    except socket.error, err:
+                        socketClosed = True
+                        errStr = str(err)
+                        # This error shows up with we have our tegra rebooted.
+                        if err[0] == errno.ECONNRESET:
+                            errStr += ' - possible reboot'
 
-    if (validated):
-      if (self.debug >= 3): print "Push File Validated!"
-      return True
-    else:
-      if (self.debug >= 2): print "Push File Failed to Validate!"
-      return False
+                    if socketClosed:
+                        self._sock.close()
+                        self._sock = None
+                        raise AgentError("Automation Error: Error receiving data from socket. cmd=%s; err=%s" % (cmd, errStr))
 
-  # external function
-  # returns:
-  #  success: directory name
-  #  failure: None
-  def mkDir(self, name):
-    if (self.dirExists(name)):
-      return name
-    else:
-      try:
-        retVal = self.runCmds([{ 'cmd': 'mkdr ' + name }])
-      except AgentError:
-        retVal = None
-      return retVal
+                    data += temp
 
-  # push localDir from host to remoteDir on the device
-  # external function
-  # returns:
-  #  success: remoteDir
-  #  failure: None
-  def pushDir(self, localDir, remoteDir):
-    if (self.debug >= 2): print "pushing directory: %s to %s" % (localDir, remoteDir)
-    for root, dirs, files in os.walk(localDir, followlinks=True):
-      parts = root.split(localDir)
-      for file in files:
-        remoteRoot = remoteDir + '/' + parts[1]
-        if (remoteRoot.endswith('/')):
-          remoteName = remoteRoot + file
-        else:
-          remoteName = remoteRoot + '/' + file
-        if (parts[1] == ""): remoteRoot = remoteDir
-        if (self.pushFile(os.path.join(root, file), remoteName) == False):
-          # retry once
-          self.removeFile(remoteName)
-          if (self.pushFile(os.path.join(root, file), remoteName) == False):
-            return None
-    return remoteDir
+                    # If something goes wrong in the agent it will send back a string that
+                    # starts with '##AGENT-WARNING##'
+                    if not commandFailed:
+                        errorMatch = self.agentErrorRE.match(data)
+                        if errorMatch:
+                            # We still need to consume the prompt, so raise an error after
+                            # draining the rest of the buffer.
+                            commandFailed = True
 
-  # external function
-  # returns:
-  #  success: True
-  #  failure: False
-  def dirExists(self, dirname):
-    match = ".*" + dirname.replace('^', '\^') + "$"
-    dirre = re.compile(match)
-    try:
-      data = self.runCmds([ { 'cmd': 'cd ' + dirname }, { 'cmd': 'cwd' }])
-    except AgentError:
-      return False
+                    for line in data.splitlines():
+                        if promptre.match(line):
+                            foundPrompt = True
+                            data = self._stripPrompt(data)
+                            break
 
-    found = False
-    for d in data.splitlines():
-      if (dirre.match(d)):
-        found = True
+                    # periodically flush data to output file to make sure it doesn't get
+                    # too big/unwieldly
+                    if len(data) > 1024:
+                            outputfile.write(data[0:1024])
+                            data = data[1024:]
 
-    return found
+                if commandFailed:
+                    raise AgentError("Automation Error: Agent Error processing command '%s'; err='%s'" %
+                                                      (cmd['cmd'], errorMatch.group(1)), fatal=True)
+
+                # Write any remaining data to outputfile
+                outputfile.write(data)
 
-  # Because we always have / style paths we make this a lot easier with some
-  # assumptions
-  # external function
-  # returns:
-  #  success: True
-  #  failure: False
-  def fileExists(self, filepath):
-    s = filepath.split('/')
-    containingpath = '/'.join(s[:-1])
-    listfiles = self.listFiles(containingpath)
-    for f in listfiles:
-      if (f == s[-1]):
-        return True
-    return False
+        if shouldCloseSocket:
+            try:
+                self._sock.close()
+                self._sock = None
+            except:
+                self._sock = None
+                raise AgentError("Automation Error: Error closing socket")
 
-  # list files on the device, requires cd to directory first
-  # external function
-  # returns:
-  #  success: array of filenames, ['file1', 'file2', ...]
-  #  failure: []
-  def listFiles(self, rootdir):
-    rootdir = rootdir.rstrip('/')
-    if (self.dirExists(rootdir) == False):
-      return []
-    try:
-      data = self.runCmds([{ 'cmd': 'cd ' + rootdir }, { 'cmd': 'ls' }])
-    except AgentError:
-      return []
+    def shell(self, cmd, outputfile, env=None, cwd=None, timeout=None, root=False):
+        """
+        external function: executes shell command on device
+        returns:
+         success: <return code>
+         failure: None
+        """
+        cmdline = self._escapedCommandLine(cmd)
+        if env:
+            cmdline = '%s %s' % (self.formatEnvString(env), cmdline)
 
-    files = filter(lambda x: x, data.splitlines())
-    if len(files) == 1 and files[0] == '<empty>':
-      # special case on the agent: empty directories return just the string "<empty>"
-      return []
-    return files
+        haveExecSu = (StrictVersion(self.agentVersion) >= StrictVersion('1.13'))
 
-  # external function
-  # returns:
-  #  success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
-  #  failure: None
-  def removeFile(self, filename):
-    if (self.debug>= 2): print "removing file: " + filename
-    try:
-      retVal = self.runCmds([{ 'cmd': 'rm ' + filename }])
-    except AgentError:
-      return None
-
-    return retVal
+        # Depending on agent version we send one of the following commands here:
+        # * exec (run as normal user)
+        # * execsu (run as privileged user)
+        # * execcwd (run as normal user from specified directory)
+        # * execcwdsu (run as privileged user from specified directory)
 
-  # does a recursive delete of directory on the device: rm -Rf remoteDir
-  # external function
-  # returns:
-  #  success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
-  #  failure: None
-  def removeDir(self, remoteDir):
-    try:
-      retVal = self.runCmds([{ 'cmd': 'rmdr ' + remoteDir }])
-    except AgentError:
-      return None
-
-    return retVal
-
-  # external function
-  # returns:
-  #  success: array of process tuples
-  #  failure: []
-  def getProcessList(self):
-    try:
-      data = self.runCmds([{ 'cmd': 'ps' }])
-    except AgentError:
-      return []
+        cmd = "exec"
+        if cwd:
+            cmd += "cwd"
+        if root and haveExecSu:
+            cmd += "su"
 
-    files = []
-    for line in data.splitlines():
-      if line:
-        pidproc = line.strip().split()
-        if (len(pidproc) == 2):
-          files += [[pidproc[0], pidproc[1]]]
-        elif (len(pidproc) == 3):
-          #android returns <userID> <procID> <procName>
-          files += [[pidproc[1], pidproc[2], pidproc[0]]]
-    return files
+        try:
+            if cwd:
+                self.sendCmds([{ 'cmd': '%s %s %s' % (cmd, cwd, cmdline) }], outputfile, timeout)
+            else:
+                if (not root) or haveExecSu:
+                    self.sendCmds([{ 'cmd': '%s %s' % (cmd, cmdline) }], outputfile, timeout)
+                else:
+                    # need to manually inject su -c for backwards compatibility (this may
+                    # not work on ICS or above!!)
+                    # (FIXME: this backwards compatibility code is really ugly and should
+                    # be deprecated at some point in the future)
+                    self.sendCmds([ { 'cmd': '%s su -c "%s"' % (cmd, cmdline) }], outputfile,
+                                                    timeout)
+        except AgentError:
+            return None
 
-  # external function
-  # DEPRECATED: Use shell() or launchApplication() for new code
-  # returns:
-  #  success: pid
-  #  failure: None
-  def fireProcess(self, appname, failIfRunning=False):
-    if (not appname):
-      if (self.debug >= 1): print "WARNING: fireProcess called with no command to run"
-      return None
+        # dig through the output to get the return code
+        lastline = _pop_last_line(outputfile)
+        if lastline:
+            m = re.search('return code \[([0-9]+)\]', lastline)
+            if m:
+                return int(m.group(1))
 
-    if (self.debug >= 2): print "FIRE PROC: '" + appname + "'"
-
-    if (self.processExist(appname) != None):
-      print "WARNING: process %s appears to be running already\n" % appname
-      if (failIfRunning):
+        # woops, we couldn't find an end of line/return value
         return None
 
-    try:
-      self.runCmds([{ 'cmd': 'exec ' + appname }])
-    except AgentError:
-      return None
+    def pushFile(self, localname, destname):
+        """
+        external function
+        returns:
+          success: True
+          failure: False
+        """
+        if (os.name == "nt"):
+            destname = destname.replace('\\', '/')
 
-    # The 'exec' command may wait for the process to start and end, so checking
-    # for the process here may result in process = None.
-    process = self.processExist(appname)
-    if (self.debug >= 4): print "got pid: %s for process: %s" % (process, appname)
+        if (self.debug >= 3):
+            print "in push file with: " + localname + ", and: " + destname
+        if (self.dirExists(destname)):
+            if (not destname.endswith('/')):
+                destname = destname + '/'
+            destname = destname + os.path.basename(localname)
+        if (self.validateFile(destname, localname) == True):
+            if (self.debug >= 3):
+                print "files are validated"
+            return True
+
+        if self.mkDirs(destname) == None:
+            print "Automation Error: unable to make dirs: " + destname
+            return False
+
+        if (self.debug >= 3):
+            print "sending: push " + destname
+
+        filesize = os.path.getsize(localname)
+        f = open(localname, 'rb')
+        data = f.read()
+        f.close()
+
+        try:
+            retVal = self.runCmds([{ 'cmd': 'push ' + destname + ' ' + str(filesize),
+                                                              'data': data }])
+        except AgentError, e:
+            print "Automation Error: error pushing file: %s" % e.msg
+            return False
 
-    return process
+        if (self.debug >= 3):
+            print "push returned: " + str(retVal)
+
+        validated = False
+        if (retVal):
+            retline = retVal.strip()
+            if (retline == None):
+                # Then we failed to get back a hash from agent, try manual validation
+                validated = self.validateFile(destname, localname)
+            else:
+                # Then we obtained a hash from push
+                localHash = self.getLocalHash(localname)
+                if (str(localHash) == str(retline)):
+                    validated = True
+        else:
+            # We got nothing back from sendCMD, try manual validation
+            validated = self.validateFile(destname, localname)
 
-  # external function
-  # DEPRECATED: Use shell() or launchApplication() for new code
-  # returns:
-  #  success: output filename
-  #  failure: None
-  def launchProcess(self, cmd, outputFile = "process.txt", cwd = '', env = '', failIfRunning=False):
-    if not cmd:
-      if (self.debug >= 1): print "WARNING: launchProcess called without command to run"
-      return None
+        if (validated):
+            if (self.debug >= 3):
+                print "Push File Validated!"
+            return True
+        else:
+            if (self.debug >= 2):
+                print "Automation Error: Push File Failed to Validate!"
+            return False
+
+    def mkDir(self, name):
+        """
+        external function
+        returns:
+          success: directory name
+          failure: None
+        """
+        if (self.dirExists(name)):
+            return name
+        else:
+            try:
+                retVal = self.runCmds([{ 'cmd': 'mkdr ' + name }])
+            except AgentError:
+                retVal = None
+            return retVal
 
-    cmdline = subprocess.list2cmdline(cmd)
-    if (outputFile == "process.txt" or outputFile == None):
-      outputFile = self.getDeviceRoot();
-      if outputFile is None:
-        return None
-      outputFile += "/process.txt"
-      cmdline += " > " + outputFile
+    def pushDir(self, localDir, remoteDir):
+        """
+        push localDir from host to remoteDir on the device
+        external function
+        returns:
+          success: remoteDir
+          failure: None
+        """
+        if (self.debug >= 2):
+            print "pushing directory: %s to %s" % (localDir, remoteDir)
+        for root, dirs, files in os.walk(localDir, followlinks=True):
+            parts = root.split(localDir)
+            for f in files:
+                remoteRoot = remoteDir + '/' + parts[1]
+                if (remoteRoot.endswith('/')):
+                    remoteName = remoteRoot + f
+                else:
+                    remoteName = remoteRoot + '/' + f
+                if (parts[1] == ""):
+                    remoteRoot = remoteDir
+                if (self.pushFile(os.path.join(root, f), remoteName) == False):
+                    # retry once
+                    self.removeFile(remoteName)
+                    if (self.pushFile(os.path.join(root, f), remoteName) == False):
+                        return None
+        return remoteDir
 
-    # Prepend our env to the command
-    cmdline = '%s %s' % (self.formatEnvString(env), cmdline)
+    def dirExists(self, dirname):
+        """
+        external function
+        returns:
+          success: True
+          failure: False
+        """
+        match = ".*" + dirname.replace('^', '\^') + "$"
+        dirre = re.compile(match)
+        try:
+            data = self.runCmds([ { 'cmd': 'cd ' + dirname }, { 'cmd': 'cwd' }])
+        except AgentError:
+            return False
+
+        found = False
+        for d in data.splitlines():
+            if (dirre.match(d)):
+                found = True
+
+        return found
 
-    if self.fireProcess(cmdline, failIfRunning) is None:
-      return None
-    return outputFile
+    # Because we always have / style paths we make this a lot easier with some
+    # assumptions
+    def fileExists(self, filepath):
+        """
+        external function
+        returns:
+          success: True
+          failure: False
+        """
+        s = filepath.split('/')
+        containingpath = '/'.join(s[:-1])
+        listfiles = self.listFiles(containingpath)
+        for f in listfiles:
+            if (f == s[-1]):
+                return True
+        return False
 
-  # external function
-  # returns:
-  #  success: True
-  #  failure: False
-  def killProcess(self, appname, forceKill=False):
-    if forceKill:
-      print "WARNING: killProcess(): forceKill parameter unsupported on SUT"
-    try:
-      self.runCmds([{ 'cmd': 'kill ' + appname }])
-    except AgentError:
-      return False
+    def listFiles(self, rootdir):
+        """
+        list files on the device, requires cd to directory first
+        external function
+        returns:
+          success: array of filenames, ['file1', 'file2', ...]
+          failure: []
+        """
+        rootdir = rootdir.rstrip('/')
+        if (self.dirExists(rootdir) == False):
+            return []
+        try:
+            data = self.runCmds([{ 'cmd': 'cd ' + rootdir }, { 'cmd': 'ls' }])
+        except AgentError:
+            return []
 
-    return True
+        files = filter(lambda x: x, data.splitlines())
+        if len(files) == 1 and files[0] == '<empty>':
+            # special case on the agent: empty directories return just the string "<empty>"
+            return []
+        return files
+
+    def removeFile(self, filename):
+        """
+        external function
+        returns:
+          success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
+          failure: None
+        """
+        if (self.debug>= 2):
+            print "removing file: " + filename
+        try:
+            retVal = self.runCmds([{ 'cmd': 'rm ' + filename }])
+        except AgentError:
+            return None
+
+        return retVal
 
-  # external function
-  # returns:
-  #  success: tmpdir, string
-  #  failure: None
-  def getTempDir(self):
-    try:
-      data = self.runCmds([{ 'cmd': 'tmpd' }])
-    except AgentError:
-      return None
+    def removeDir(self, remoteDir):
+        """
+        does a recursive delete of directory on the device: rm -Rf remoteDir
+        external function
+        returns:
+          success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
+          failure: None
+        """
+        try:
+            retVal = self.runCmds([{ 'cmd': 'rmdr ' + remoteDir }])
+        except AgentError:
+            return None
+
+        return retVal
 
-    return data.strip()
+    def getProcessList(self):
+        """
+        external function
+        returns:
+          success: array of process tuples
+          failure: []
+        """
+        try:
+            data = self.runCmds([{ 'cmd': 'ps' }])
+        except AgentError:
+            return []
+
+        files = []
+        for line in data.splitlines():
+            if line:
+                pidproc = line.strip().split()
+                if (len(pidproc) == 2):
+                    files += [[pidproc[0], pidproc[1]]]
+                elif (len(pidproc) == 3):
+                    #android returns <userID> <procID> <procName>
+                    files += [[pidproc[1], pidproc[2], pidproc[0]]]
+        return files
 
-  # external function
-  # returns:
-  #  success: filecontents
-  #  failure: None
-  def catFile(self, remoteFile):
-    try:
-      data = self.runCmds([{ 'cmd': 'cat ' + remoteFile }])
-    except AgentError:
-      return None
+    def fireProcess(self, appname, failIfRunning=False):
+        """
+        external function
+        DEPRECATED: Use shell() or launchApplication() for new code
+        returns:
+          success: pid
+          failure: None
+        """
+        if (not appname):
+            if (self.debug >= 1):
+                print "WARNING: fireProcess called with no command to run"
+            return None
+
+        if (self.debug >= 2):
+            print "FIRE PROC: '" + appname + "'"
+
+        if (self.processExist(appname) != None):
+            print "WARNING: process %s appears to be running already\n" % appname
+            if (failIfRunning):
+                return None
+
+        try:
+            self.runCmds([{ 'cmd': 'exec ' + appname }])
+        except AgentError:
+            return None
 
-    return data
+        # The 'exec' command may wait for the process to start and end, so checking
+        # for the process here may result in process = None.
+        process = self.processExist(appname)
+        if (self.debug >= 4):
+            print "got pid: %s for process: %s" % (process, appname)
+
+        return process
+
+    def launchProcess(self, cmd, outputFile = "process.txt", cwd = '', env = '', failIfRunning=False):
+        """
+        external function
+        DEPRECATED: Use shell() or launchApplication() for new code
+        returns:
+          success: output filename
+          failure: None
+        """
+        if not cmd:
+            if (self.debug >= 1):
+                print "WARNING: launchProcess called without command to run"
+            return None
+
+        cmdline = subprocess.list2cmdline(cmd)
+        if (outputFile == "process.txt" or outputFile == None):
+            outputFile = self.getDeviceRoot();
+            if outputFile is None:
+                return None
+            outputFile += "/process.txt"
+            cmdline += " > " + outputFile
+
+        # Prepend our env to the command
+        cmdline = '%s %s' % (self.formatEnvString(env), cmdline)
+
+        if self.fireProcess(cmdline, failIfRunning) is None:
+            return None
+        return outputFile
 
-  # external function
-  # returns:
-  #  success: output of pullfile, string
-  #  failure: None
-  def pullFile(self, remoteFile):
-    """Returns contents of remoteFile using the "pull" command.
-    The "pull" command is different from other commands in that DeviceManager
-    has to read a certain number of bytes instead of just reading to the
-    next prompt.  This is more robust than the "cat" command, which will be
-    confused if the prompt string exists within the file being catted.
-    However it means we can't use the response-handling logic in sendCMD().
-    """
+    def killProcess(self, appname, forceKill=False):
+        """
+        external function
+        returns:
+          success: True
+          failure: False
+        """
+        if forceKill:
+            print "WARNING: killProcess(): forceKill parameter unsupported on SUT"
+        try:
+            self.runCmds([{ 'cmd': 'kill ' + appname }])
+        except AgentError:
+            return False
+
+        return True
+
+    def getTempDir(self):
+        """
+        external function
+        returns:
+          success: tmpdir, string
+          failure: None
+        """
+        try:
+            data = self.runCmds([{ 'cmd': 'tmpd' }])
+        except AgentError:
+            return None
+
+        return data.strip()
+
+    def catFile(self, remoteFile):
+        """
+        external function
+        returns:
+          success: filecontents
+          failure: None
+        """
+        try:
+            data = self.runCmds([{ 'cmd': 'cat ' + remoteFile }])
+        except AgentError:
+            return None
+
+        return data
 
-    def err(error_msg):
-        err_str = 'error returned from pull: %s' % error_msg
-        print err_str
-        self._sock = None
-        raise FileError(err_str)
+    def pullFile(self, remoteFile):
+        """Returns contents of remoteFile using the "pull" command.
+        The "pull" command is different from other commands in that DeviceManager
+        has to read a certain number of bytes instead of just reading to the
+        next prompt.  This is more robust than the "cat" command, which will be
+        confused if the prompt string exists within the file being catted.
+        However it means we can't use the response-handling logic in sendCMD().
+        
+        external function
+        returns:
+          success: output of pullfile, string
+          failure: None
+        """
 
-    # FIXME: We could possibly move these socket-reading functions up to
-    # the class level if we wanted to refactor sendCMD().  For now they are
-    # only used to pull files.
+        def err(error_msg):
+            err_str = 'DeviceManager: pull unsuccessful: %s' % error_msg
+            print err_str
+            self._sock = None
+            raise FileError(err_str)
+
+        # FIXME: We could possibly move these socket-reading functions up to
+        # the class level if we wanted to refactor sendCMD().  For now they are
+        # only used to pull files.
 
-    def uread(to_recv, error_msg):
-      """ unbuffered read """
-      try:
-        data = self._sock.recv(to_recv)
-        if not data:
-          err(error_msg)
-          return None
-        return data
-      except:
-        err(error_msg)
-        return None
+        def uread(to_recv, error_msg, timeout=None):
+            """ unbuffered read """
+            timer = 0
+            select_timeout = 1
+            if not timeout:
+                timeout = self.default_timeout
+
+            try:
+                if select.select([self._sock], [], [], select_timeout)[0]:
+                    data = self._sock.recv(to_recv)
+                    timer = 0
+                timer += select_timeout
+                if timer > timeout:
+                    err('timeout in uread while retrieving file')
+                    return None
+
+                if not data:
+                    err(error_msg)
+                    return None
+                return data
+            except:
+                err(error_msg)
+                return None
+
+        def read_until_char(c, buf, error_msg):
+            """ read until 'c' is found; buffer rest """
+            while not '\n' in buf:
+                data = uread(1024, error_msg)
+                if data == None:
+                    err(error_msg)
+                    return ('', '', '')
+                buf += data
+            return buf.partition(c)
 
-    def read_until_char(c, buffer, error_msg):
-      """ read until 'c' is found; buffer rest """
-      while not '\n' in buffer:
-        data = uread(1024, error_msg)
-        if data == None:
-          err(error_msg)
-          return ('', '', '')
-        buffer += data
-      return buffer.partition(c)
+        def read_exact(total_to_recv, buf, error_msg):
+            """ read exact number of 'total_to_recv' bytes """
+            while len(buf) < total_to_recv:
+                to_recv = min(total_to_recv - len(buf), 1024)
+                data = uread(to_recv, error_msg)
+                if data == None:
+                    return None
+                buf += data
+            return buf
+
+        prompt = self.base_prompt + self.prompt_sep
+        buf = ''
+
+        # expected return value:
+        # <filename>,<filesize>\n<filedata>
+        # or, if error,
+        # <filename>,-1\n<error message>
+        try:
+            # just send the command first, we read the response inline below
+            self.runCmds([{ 'cmd': 'pull ' + remoteFile }])
+        except AgentError:
+            return None
+
+        # read metadata; buffer the rest
+        metadata, sep, buf = read_until_char('\n', buf, 'could not find metadata')
+        if not metadata:
+            return None
+        if self.debug >= 3:
+            print 'metadata: %s' % metadata
+
+        filename, sep, filesizestr = metadata.partition(',')
+        if sep == '':
+            err('could not find file size in returned metadata')
+            return None
+        try:
+            filesize = int(filesizestr)
+        except ValueError:
+            err('invalid file size in returned metadata')
+            return None
 
-    def read_exact(total_to_recv, buffer, error_msg):
-      """ read exact number of 'total_to_recv' bytes """
-      while len(buffer) < total_to_recv:
-        to_recv = min(total_to_recv - len(buffer), 1024)
-        data = uread(to_recv, error_msg)
-        if data == None:
-          return None
-        buffer += data
-      return buffer
+        if filesize == -1:
+            # read error message
+            error_str, sep, buf = read_until_char('\n', buf, 'could not find error message')
+            if not error_str:
+                return None
+            # prompt should follow
+            read_exact(len(prompt), buf, 'could not find prompt')
+            # failures are expected, so don't use "Remote Device Error" or we'll RETRY
+            print "DeviceManager: pulling file '%s' unsuccessful: %s" % (remoteFile, error_str)
+            return None
+
+        # read file data
+        total_to_recv = filesize + len(prompt)
+        buf = read_exact(total_to_recv, buf, 'could not get all file data')
+        if buf == None:
+            return None
+        if buf[-len(prompt):] != prompt:
+            err('no prompt found after file data--DeviceManager may be out of sync with agent')
+            return buf
+        return buf[:-len(prompt)]
 
-    prompt = self.base_prompt + self.prompt_sep
-    buffer = ''
+    def getFile(self, remoteFile, localFile = ''):
+        """
+        copy file from device (remoteFile) to host (localFile)
+        external function
+        returns:
+          success: output of pullfile, string
+          failure: None
+        """
+        if localFile == '':
+            localFile = os.path.join(self.tempRoot, "temp.txt")
+
+        try:
+            retVal = self.pullFile(remoteFile)
+        except:
+            return None
+
+        if (retVal is None):
+            return None
+
+        fhandle = open(localFile, 'wb')
+        fhandle.write(retVal)
+        fhandle.close()
+        if not self.validateFile(remoteFile, localFile):
+            print 'DeviceManager: failed to validate file when downloading %s' % remoteFile
+            return None
+        return retVal
 
-    # expected return value:
-    # <filename>,<filesize>\n<filedata>
-    # or, if error,
-    # <filename>,-1\n<error message>
-    try:
-      # just send the command first, we read the response inline below
-      self.runCmds([{ 'cmd': 'pull ' + remoteFile }])
-    except AgentError:
-      return None
+    def getDirectory(self, remoteDir, localDir, checkDir=True):
+        """
+        copy directory structure from device (remoteDir) to host (localDir)
+        external function
+        checkDir exists so that we don't create local directories if the
+        remote directory doesn't exist but also so that we don't call isDir
+        twice when recursing.
+        returns:
+          success: list of files, string
+          failure: None
+        """
+        if (self.debug >= 2):
+            print "getting files in '" + remoteDir + "'"
+        if checkDir:
+            try:
+                is_dir = self.isDir(remoteDir)
+            except FileError:
+                return None
+            if not is_dir:
+                return None
+
+        filelist = self.listFiles(remoteDir)
+        if (self.debug >= 3):
+            print filelist
+        if not os.path.exists(localDir):
+            os.makedirs(localDir)
+
+        for f in filelist:
+            if f == '.' or f == '..':
+                continue
+            remotePath = remoteDir + '/' + f
+            localPath = os.path.join(localDir, f)
+            try:
+                is_dir = self.isDir(remotePath)
+            except FileError:
+                print 'isdir failed on file "%s"; continuing anyway...' % remotePath
+                continue
+            if is_dir:
+                if (self.getDirectory(remotePath, localPath, False) == None):
+                    print 'Remote Device Error: failed to get directory "%s"' % remotePath
+                    return None
+            else:
+                # It's sometimes acceptable to have getFile() return None, such as
+                # when the agent encounters broken symlinks.
+                # FIXME: This should be improved so we know when a file transfer really
+                # failed.
+                if self.getFile(remotePath, localPath) == None:
+                    print 'failed to get file "%s"; continuing anyway...' % remotePath
+        return filelist
 
-    # read metadata; buffer the rest
-    metadata, sep, buffer = read_until_char('\n', buffer, 'could not find metadata')
-    if not metadata:
-      return None
-    if self.debug >= 3:
-      print 'metadata: %s' % metadata
+    def isDir(self, remotePath):
+        """
+        external function
+        returns:
+          success: True
+          failure: False
+        Throws a FileError exception when null (invalid dir/filename)
+        """
+        try:
+            data = self.runCmds([{ 'cmd': 'isdir ' + remotePath }])
+        except AgentError:
+            # normally there should be no error here; a nonexistent file/directory will
+            # return the string "<filename>: No such file or directory".
+            # However, I've seen AGENT-WARNING returned before.
+            return False
+
+        retVal = data.strip()
+        if not retVal:
+            raise FileError('isdir returned null')
+        return retVal == 'TRUE'
 
-    filename, sep, filesizestr = metadata.partition(',')
-    if sep == '':
-      err('could not find file size in returned metadata')
-      return None
-    try:
-        filesize = int(filesizestr)
-    except ValueError:
-      err('invalid file size in returned metadata')
-      return None
+    def validateFile(self, remoteFile, localFile):
+        """
+        true/false check if the two files have the same md5 sum
+        external function
+        returns:
+          success: True
+          failure: False
+        """
+        remoteHash = self.getRemoteHash(remoteFile)
+        localHash = self.getLocalHash(localFile)
+
+        if (remoteHash == None):
+            return False
+
+        if (remoteHash == localHash):
+            return True
+
+        return False
+
+    def getRemoteHash(self, filename):
+        """
+        return the md5 sum of a remote file
+        internal function
+        returns:
+          success: MD5 hash for given filename
+          failure: None
+        """
+        try:
+            data = self.runCmds([{ 'cmd': 'hash ' + filename }])
+        except AgentError:
+            return None
 
-    if filesize == -1:
-      # read error message
-      error_str, sep, buffer = read_until_char('\n', buffer, 'could not find error message')
-      if not error_str:
-        return None
-      # prompt should follow
-      read_exact(len(prompt), buffer, 'could not find prompt')
-      print "DeviceManager: error pulling file '%s': %s" % (remoteFile, error_str)
-      return None
+        retVal = None
+        if data:
+            retVal = data.strip()
+        if self.debug >= 3:
+            print "remote hash returned: '%s'" % retVal
+        return retVal
+
+    def getDeviceRoot(self):
+        """
+        Gets the device root for the testing area on the device
+        For all devices we will use / type slashes and depend on the device-agent
+        to sort those out.  The agent will return us the device location where we
+        should store things, we will then create our /tests structure relative to
+        that returned path.
+        Structure on the device is as follows:
+        /tests
+            /<fennec>|<firefox>  --> approot
+            /profile
+            /xpcshell
+            /reftest
+            /mochitest
+
+        external function
+        returns:
+          success: path for device root
+          failure: None
+        """
+        if self.deviceRoot:
+            deviceRoot = self.deviceRoot
+        else:
+            try:
+                data = self.runCmds([{ 'cmd': 'testroot' }])
+            except:
+                return None
+
+            deviceRoot = data.strip() + '/tests'
+
+        if (not self.dirExists(deviceRoot)):
+            if (self.mkDir(deviceRoot) == None):
+                return None
+
+        self.deviceRoot = deviceRoot
+        return self.deviceRoot
 
-    # read file data
-    total_to_recv = filesize + len(prompt)
-    buffer = read_exact(total_to_recv, buffer, 'could not get all file data')
-    if buffer == None:
-      return None
-    if buffer[-len(prompt):] != prompt:
-      err('no prompt found after file data--DeviceManager may be out of sync with agent')
-      return buffer
-    return buffer[:-len(prompt)]
+    def getAppRoot(self, packageName):
+        try:
+            data = self.runCmds([{ 'cmd': 'getapproot '+packageName }])
+        except:
+            return None
+
+        return data.strip()
+
+    def unpackFile(self, file_path, dest_dir=None):
+        """
+        external function
+        returns:
+          success: output of unzip command
+          failure: None
+        """
+        devroot = self.getDeviceRoot()
+        if (devroot == None):
+            return None
+
+        # if no dest_dir is passed in just set it to file_path's folder
+        if not dest_dir:
+            dest_dir = posixpath.dirname(file_path)
+
+        if dest_dir[-1] != '/':
+            dest_dir += '/'
 
-  # copy file from device (remoteFile) to host (localFile)
-  # external function
-  # returns:
-  #  success: output of pullfile, string
-  #  failure: None
-  def getFile(self, remoteFile, localFile = ''):
-    if localFile == '':
-      localFile = os.path.join(self.tempRoot, "temp.txt")
+        try:
+            data = self.runCmds([{ 'cmd': 'unzp %s %s' % (file_path, dest_dir)}])
+        except AgentError:
+            return None
+
+        return data
+
+    def reboot(self, ipAddr=None, port=30000):
+        """
+        external function
+        returns:
+          success: status from test agent
+          failure: None
+        """
+        cmd = 'rebt'
+
+        if (self.debug > 3):
+            print "INFO: sending rebt command"
+
+        if (ipAddr is not None):
+        #create update.info file:
+            try:
+                destname = '/data/data/com.mozilla.SUTAgentAndroid/files/update.info'
+                data = "%s,%s\rrebooting\r" % (ipAddr, port)
+                self.runCmds([{ 'cmd': 'push %s %s' % (destname, len(data)),
+                                                'data': data }])
+            except AgentError:
+                return None
 
-    try:
-      retVal = self.pullFile(remoteFile)
-    except:
-      return None
+            ip, port = self.getCallbackIpAndPort(ipAddr, port)
+            cmd += " %s %s" % (ip, port)
+            # Set up our callback server
+            callbacksvr = callbackServer(ip, port, self.debug)
 
-    if (retVal is None):
-      return None
+        try:
+            status = self.runCmds([{ 'cmd': cmd }])
+        except AgentError:
+            return None
+
+        if (ipAddr is not None):
+            status = callbacksvr.disconnect()
+
+        if (self.debug > 3):
+            print "INFO: rebt- got status back: " + str(status)
+        return status
 
-    fhandle = open(localFile, 'wb')
-    fhandle.write(retVal)
-    fhandle.close()
-    if not self.validateFile(remoteFile, localFile):
-      print 'failed to validate file when downloading %s!' % remoteFile
-      return None
-    return retVal
+    def getInfo(self, directive=None):
+        """
+        Returns information about the device:
+        Directive indicates the information you want to get, your choices are:
+          os - name of the os
+          id - unique id of the device
+          uptime - uptime of the device
+          uptimemillis - uptime of the device in milliseconds (SUTAgent 1.11+)
+          systime - system time of the device
+          screen - screen resolution
+          rotation - rotation of the device (in degrees)
+          memory - memory stats
+          process - list of running processes (same as ps)
+          disk - total, free, available bytes on disk
+          power - power status (charge, battery temp)
+          all - all of them - or call it with no parameters to get all the information
+        returns:
+          success: dict of info strings by directive name
+          failure: {}
+        """
+        data = None
+        result = {}
+        collapseSpaces = re.compile('  +')
+
+        directives = ['os','id','uptime','uptimemillis','systime','screen',
+                                    'rotation','memory','process','disk','power']
+        if (directive in directives):
+            directives = [directive]
 
-  # copy directory structure from device (remoteDir) to host (localDir)
-  # external function
-  # checkDir exists so that we don't create local directories if the
-  # remote directory doesn't exist but also so that we don't call isDir
-  # twice when recursing.
-  # returns:
-  #  success: list of files, string
-  #  failure: None
-  def getDirectory(self, remoteDir, localDir, checkDir=True):
-    if (self.debug >= 2): print "getting files in '" + remoteDir + "'"
-    if checkDir:
-      try:
-        is_dir = self.isDir(remoteDir)
-      except FileError:
-        return None
-      if not is_dir:
+        for d in directives:
+            try:
+                data = self.runCmds([{ 'cmd': 'info ' + d }])
+            except AgentError:
+                return result
+
+            if (data is None):
+                continue
+            data = collapseSpaces.sub(' ', data)
+            result[d] = data.split('\n')
+
+        # Get rid of any 0 length members of the arrays
+        for k, v in result.iteritems():
+            result[k] = filter(lambda x: x != '', result[k])
+
+        # Format the process output
+        if 'process' in result:
+            proclist = []
+            for l in result['process']:
+                if l:
+                    proclist.append(l.split('\t'))
+            result['process'] = proclist
+
+        if (self.debug >= 3):
+            print "results: " + str(result)
+        return result
+
+    def installApp(self, appBundlePath, destPath=None):
+        """
+        Installs the application onto the device
+        Application bundle - path to the application bundle on the device
+        Destination - destination directory of where application should be
+                                    installed to (optional)
+        Returns None for success, or output if known failure
+
+        external function
+        returns:
+          success: None
+          failure: error string
+        """
+        cmd = 'inst ' + appBundlePath
+        if destPath:
+            cmd += ' ' + destPath
+
+        try:
+            data = self.runCmds([{ 'cmd': cmd }])
+        except AgentError, err:
+            print "Remote Device Error: Error installing app: %s" % err
+            return "%s" % err
+
+        f = re.compile('Failure')
+        for line in data.split():
+            if (f.match(line)):
+                return line
         return None
 
-    filelist = self.listFiles(remoteDir)
-    if (self.debug >= 3): print filelist
-    if not os.path.exists(localDir):
-      os.makedirs(localDir)
-
-    for f in filelist:
-      if f == '.' or f == '..':
-        continue
-      remotePath = remoteDir + '/' + f
-      localPath = os.path.join(localDir, f)
-      try:
-        is_dir = self.isDir(remotePath)
-      except FileError:
-        print 'isdir failed on file "%s"; continuing anyway...' % remotePath
-        continue
-      if is_dir:
-        if (self.getDirectory(remotePath, localPath, False) == None):
-          print 'failed to get directory "%s"' % remotePath
-          return None
-      else:
-        # It's sometimes acceptable to have getFile() return None, such as
-        # when the agent encounters broken symlinks.
-        # FIXME: This should be improved so we know when a file transfer really
-        # failed.
-        if self.getFile(remotePath, localPath) == None:
-          print 'failed to get file "%s"; continuing anyway...' % remotePath
-    return filelist
-
-  # external function
-  # returns:
-  #  success: True
-  #  failure: False
-  #  Throws a FileError exception when null (invalid dir/filename)
-  def isDir(self, remotePath):
-    try:
-      data = self.runCmds([{ 'cmd': 'isdir ' + remotePath }])
-    except AgentError:
-      # normally there should be no error here; a nonexistent file/directory will
-      # return the string "<filename>: No such file or directory".
-      # However, I've seen AGENT-WARNING returned before.
-      return False
-
-    retVal = data.strip()
-    if not retVal:
-      raise FileError('isdir returned null')
-    return retVal == 'TRUE'
+    def uninstallAppAndReboot(self, appName, installPath=None):
+        """
+        Uninstalls the named application from device and causes a reboot.
+        Takes an optional argument of installation path - the path to where the application
+        was installed.
+        Returns True, but it doesn't mean anything other than the command was sent,
+        the reboot happens and we don't know if this succeeds or not.
+        
+        external function
+        returns:
+          success: True
+          failure: None
+        """
+        cmd = 'uninst ' + appName
+        if installPath:
+            cmd += ' ' + installPath
+        try:
+            data = self.runCmds([{ 'cmd': cmd }])
+        except AgentError:
+            return None
 
-  # true/false check if the two files have the same md5 sum
-  # external function
-  # returns:
-  #  success: True
-  #  failure: False
-  def validateFile(self, remoteFile, localFile):
-    remoteHash = self.getRemoteHash(remoteFile)
-    localHash = self.getLocalHash(localFile)
-
-    if (remoteHash == None):
-      return False
-
-    if (remoteHash == localHash):
-      return True
-
-    return False
-
-  # return the md5 sum of a remote file
-  # internal function
-  # returns:
-  #  success: MD5 hash for given filename
-  #  failure: None
-  def getRemoteHash(self, filename):
-    try:
-      data = self.runCmds([{ 'cmd': 'hash ' + filename }])
-    except AgentError:
-      return None
+        if (self.debug > 3):
+            print "uninstallAppAndReboot: " + str(data)
+        return True
 
-    retVal = None
-    if data:
-      retVal = data.strip()
-    if self.debug >= 3:
-      print "remote hash returned: '%s'" % retVal
-    return retVal
-
-  # Gets the device root for the testing area on the device
-  # For all devices we will use / type slashes and depend on the device-agent
-  # to sort those out.  The agent will return us the device location where we
-  # should store things, we will then create our /tests structure relative to
-  # that returned path.
-  # Structure on the device is as follows:
-  # /tests
-  #       /<fennec>|<firefox>  --> approot
-  #       /profile
-  #       /xpcshell
-  #       /reftest
-  #       /mochitest
-  #
-  # external function
-  # returns:
-  #  success: path for device root
-  #  failure: None
-  def getDeviceRoot(self):
-    if self.deviceRoot:
-      deviceRoot = self.deviceRoot
-    else:
-      try:
-        data = self.runCmds([{ 'cmd': 'testroot' }])
-      except:
-        return None
+    def updateApp(self, appBundlePath, processName=None, destPath=None, ipAddr=None, port=30000):
+        """
+        Updates the application on the device.
+        Application bundle - path to the application bundle on the device
+        Process name of application - used to end the process if the applicaiton is
+                                                                    currently running
+        Destination - Destination directory to where the application should be
+                                    installed (optional)
+        ipAddr - IP address to await a callback ping to let us know that the device has updated
+                          properly - defaults to current IP.
+        port - port to await a callback ping to let us know that the device has updated properly
+                      defaults to 30000, and counts up from there if it finds a conflict
+        Returns True if succeeds, False if not
 
-      deviceRoot = data.strip() + '/tests'
-
-    if (not self.dirExists(deviceRoot)):
-      if (self.mkDir(deviceRoot) == None):
-        return None
-
-    self.deviceRoot = deviceRoot
-    return self.deviceRoot
-
-  def getAppRoot(self, packageName):
-    try:
-      data = self.runCmds([{ 'cmd': 'getapproot '+packageName }])
-    except:
-      return None
-
-    return data.strip()
+        external function
+        returns:
+          success: text status from command or callback server
+          failure: None
+        """
+        status = None
+        cmd = 'updt '
+        if (processName == None):
+            # Then we pass '' for processName
+            cmd += "'' " + appBundlePath
+        else:
+            cmd += processName + ' ' + appBundlePath
 
-  # external function
-  # returns:
-  #  success: output of unzip command
-  #  failure: None
-  def unpackFile(self, file_path, dest_dir=None):
-    devroot = self.getDeviceRoot()
-    if (devroot == None):
-      return None
-
-    # if no dest_dir is passed in just set it to file_path's folder
-    if not dest_dir:
-      dest_dir = posixpath.dirname(file_path)
-
-    if dest_dir[-1] != '/':
-      dest_dir += '/'
+        if (destPath):
+            cmd += " " + destPath
 
-    try:
-      data = self.runCmds([{ 'cmd': 'unzp %s %s' % (file_path, dest_dir)}])
-    except AgentError:
-      return None
-
-    return data
+        if (ipAddr is not None):
+            ip, port = self.getCallbackIpAndPort(ipAddr, port)
+            cmd += " %s %s" % (ip, port)
+            # Set up our callback server
+            callbacksvr = callbackServer(ip, port, self.debug)
 
-  # external function
-  # returns:
-  #  success: status from test agent
-  #  failure: None
-  def reboot(self, ipAddr=None, port=30000):
-    cmd = 'rebt'
-
-    if (self.debug > 3): print "INFO: sending rebt command"
+        if (self.debug >= 3):
+            print "INFO: updateApp using command: " + str(cmd)
 
-    if (ipAddr is not None):
-    #create update.info file:
-      try:
-        destname = '/data/data/com.mozilla.SUTAgentAndroid/files/update.info'
-        data = "%s,%s\rrebooting\r" % (ipAddr, port)
-        self.runCmds([{ 'cmd': 'push %s %s' % (destname, len(data)),
-                        'data': data }])
-      except AgentError:
-        return None
+        try:
+            status = self.runCmds([{ 'cmd': cmd }])
+        except AgentError:
+            return None
 
-      ip, port = self.getCallbackIpAndPort(ipAddr, port)
-      cmd += " %s %s" % (ip, port)
-      # Set up our callback server
-      callbacksvr = callbackServer(ip, port, self.debug)
+        if ipAddr is not None:
+            status = callbacksvr.disconnect()
 
-    try:
-      status = self.runCmds([{ 'cmd': cmd }])
-    except AgentError:
-      return None
+        if (self.debug >= 3):
+            print "INFO: updateApp: got status back: " + str(status)
 
-    if (ipAddr is not None):
-      status = callbacksvr.disconnect()
-
-    if (self.debug > 3): print "INFO: rebt- got status back: " + str(status)
-    return status
+        return status
 
-  # Returns information about the device:
-  # Directive indicates the information you want to get, your choices are:
-  # os - name of the os
-  # id - unique id of the device
-  # uptime - uptime of the device
-  # uptimemillis - uptime of the device in milliseconds (SUTAgent 1.11+)
-  # systime - system time of the device
-  # screen - screen resolution
-  # rotation - rotation of the device (in degrees)
-  # memory - memory stats
-  # process - list of running processes (same as ps)
-  # disk - total, free, available bytes on disk
-  # power - power status (charge, battery temp)
-  # all - all of them - or call it with no parameters to get all the information
-  # returns:
-  #   success: dict of info strings by directive name
-  #   failure: {}
-  def getInfo(self, directive=None):
-    data = None
-    result = {}
-    collapseSpaces = re.compile('  +')
+    def getCurrentTime(self):
+        """
+        return the current time on the device
 
-    directives = ['os','id','uptime','uptimemillis','systime','screen',
-                  'rotation','memory','process','disk','power']
-    if (directive in directives):
-      directives = [directive]
+        external function
+        returns:
+          success: time in ms
+          failure: None
+        """
+        try:
+            data = self.runCmds([{ 'cmd': 'clok' }])
+        except AgentError:
+            return None
 
-    for d in directives:
-      try:
-        data = self.runCmds([{ 'cmd': 'info ' + d }])
-      except AgentError:
-        return result
+        return data.strip()
 
-      if (data is None):
-        continue
-      data = collapseSpaces.sub(' ', data)
-      result[d] = data.split('\n')
-
-    # Get rid of any 0 length members of the arrays
-    for k, v in result.iteritems():
-      result[k] = filter(lambda x: x != '', result[k])
-
-    # Format the process output
-    if 'process' in result:
-      proclist = []
-      for l in result['process']:
-        if l:
-          proclist.append(l.split('\t'))
-      result['process'] = proclist
-
-    if (self.debug >= 3): print "results: " + str(result)
-    return result
-
-  """
-  Installs the application onto the device
-  Application bundle - path to the application bundle on the device
-  Destination - destination directory of where application should be
-                installed to (optional)
-  Returns None for success, or output if known failure
-  """
-  # external function
-  # returns:
-  #  success: output from agent for inst command
-  #  failure: None
-  def installApp(self, appBundlePath, destPath=None):
-    cmd = 'inst ' + appBundlePath
-    if destPath:
-      cmd += ' ' + destPath
-    try:
-      data = self.runCmds([{ 'cmd': cmd }])
-    except AgentError:
-      return None
-
-    f = re.compile('Failure')
-    for line in data.split():
-      if (f.match(line)):
-        return data
-    return None
+    def getCallbackIpAndPort(self, aIp, aPort):
+        """
+        Connect the ipaddress and port for a callback ping.  Defaults to current IP address
+        And ports starting at 30000.
+        NOTE: the detection for current IP address only works on Linux!
+        """
+        ip = aIp
+        nettools = NetworkTools()
+        if (ip == None):
+            ip = nettools.getLanIp()
+        if (aPort != None):
+            port = nettools.findOpenPort(ip, aPort)
+        else:
+            port = nettools.findOpenPort(ip, 30000)
+        return ip, port
 
-  """
-  Uninstalls the named application from device and causes a reboot.
-  Takes an optional argument of installation path - the path to where the application
-  was installed.
-  Returns True, but it doesn't mean anything other than the command was sent,
-  the reboot happens and we don't know if this succeeds or not.
-  """
-  # external function
-  # returns:
-  #  success: True
-  #  failure: None
-  def uninstallAppAndReboot(self, appName, installPath=None):
-    cmd = 'uninst ' + appName
-    if installPath:
-      cmd += ' ' + installPath
-    try:
-      data = self.runCmds([{ 'cmd': cmd }])
-    except AgentError:
-      return None
+    def formatEnvString(self, env):
+        """
+        Returns a properly formatted env string for the agent.
+        Input - env, which is either None, '', or a dict
+        Output - a quoted string of the form: '"envvar1=val1,envvar2=val2..."'
+        If env is None or '' return '' (empty quoted string)
+        """
+        if (env == None or env == ''):
+            return ''
 
-    if (self.debug > 3): print "uninstallAppAndReboot: " + str(data)
-    return True
+        retVal = '"%s"' % ','.join(map(lambda x: '%s=%s' % (x[0], x[1]), env.iteritems()))
+        if (retVal == '""'):
+            return ''
 
-  """
-  Updates the application on the device.
-  Application bundle - path to the application bundle on the device
-  Process name of application - used to end the process if the applicaiton is
-                                currently running
-  Destination - Destination directory to where the application should be
-                installed (optional)
-  ipAddr - IP address to await a callback ping to let us know that the device has updated
-           properly - defaults to current IP.
-  port - port to await a callback ping to let us know that the device has updated properly
-         defaults to 30000, and counts up from there if it finds a conflict
-  Returns True if succeeds, False if not
-  """
-  # external function
-  # returns:
-  #  success: text status from command or callback server
-  #  failure: None
-  def updateApp(self, appBundlePath, processName=None, destPath=None, ipAddr=None, port=30000):
-    status = None
-    cmd = 'updt '
-    if (processName == None):
-      # Then we pass '' for processName
-      cmd += "'' " + appBundlePath
-    else:
-      cmd += processName + ' ' + appBundlePath
+        return retVal
 
-    if (destPath):
-      cmd += " " + destPath
+    def adjustResolution(self, width=1680, height=1050, type='hdmi'):
+        """
+        adjust the screen resolution on the device, REBOOT REQUIRED
+        NOTE: this only works on a tegra ATM
+        success: True
+        failure: False
 
-    if (ipAddr is not None):
-      ip, port = self.getCallbackIpAndPort(ipAddr, port)
-      cmd += " %s %s" % (ip, port)
-      # Set up our callback server
-      callbacksvr = callbackServer(ip, port, self.debug)
-
-    if (self.debug >= 3): print "INFO: updateApp using command: " + str(cmd)
+        supported resolutions: 640x480, 800x600, 1024x768, 1152x864, 1200x1024, 1440x900, 1680x1050, 1920x1080
+        """
+        if self.getInfo('os')['os'][0].split()[0] != 'harmony-eng':
+            if (self.debug >= 2):
+                print "WARNING: unable to adjust screen resolution on non Tegra device"
+            return False
 
-    try:
-      status = self.runCmds([{ 'cmd': cmd }])
-    except AgentError:
-      return None
-
-    if ipAddr is not None:
-      status = callbacksvr.disconnect()
-
-    if (self.debug >= 3): print "INFO: updateApp: got status back: " + str(status)
-
-    return status
-
-  """
-    return the current time on the device
-  """
-  # external function
-  # returns:
-  #  success: time in ms
-  #  failure: None
-  def getCurrentTime(self):
-    try:
-      data = self.runCmds([{ 'cmd': 'clok' }])
-    except AgentError:
-      return None
-
-    return data.strip()
+        results = self.getInfo('screen')
+        parts = results['screen'][0].split(':')
+        if (self.debug >= 3):
+            print "INFO: we have a current resolution of %s, %s" % (parts[1].split()[0], parts[2].split()[0])
 
-  """
-    Connect the ipaddress and port for a callback ping.  Defaults to current IP address
-    And ports starting at 30000.
-    NOTE: the detection for current IP address only works on Linux!
-  """
-  def getCallbackIpAndPort(self, aIp, aPort):
-    ip = aIp
-    nettools = NetworkTools()
-    if (ip == None):
-      ip = nettools.getLanIp()
-    if (aPort != None):
-      port = nettools.findOpenPort(ip, aPort)
-    else:
-      port = nettools.findOpenPort(ip, 30000)
-    return ip, port
+        #verify screen type is valid, and set it to the proper value (https://bugzilla.mozilla.org/show_bug.cgi?id=632895#c4)
+        screentype = -1
+        if (type == 'hdmi'):
+            screentype = 5
+        elif (type == 'vga' or type == 'crt'):
+            screentype = 3
+        else:
+            return False
 
-  """
-    Returns a properly formatted env string for the agent.
-    Input - env, which is either None, '', or a dict
-    Output - a quoted string of the form: '"envvar1=val1,envvar2=val2..."'
-    If env is None or '' return '' (empty quoted string)
-  """
-  def formatEnvString(self, env):
-    if (env == None or env == ''):
-      return ''
+        #verify we have numbers
+        if not (isinstance(width, int) and isinstance(height, int)):
+            return False
 
-    retVal = '"%s"' % ','.join(map(lambda x: '%s=%s' % (x[0], x[1]), env.iteritems()))
-    if (retVal == '""'):
-      return ''
+        if (width < 100 or width > 9999):
+            return False
 
-    return retVal
-
-  """
-    adjust the screen resolution on the device, REBOOT REQUIRED
-    NOTE: this only works on a tegra ATM
-    success: True
-    failure: False
+        if (height < 100 or height > 9999):
+            return False
 
-    supported resolutions: 640x480, 800x600, 1024x768, 1152x864, 1200x1024, 1440x900, 1680x1050, 1920x1080
-  """
-  def adjustResolution(self, width=1680, height=1050, type='hdmi'):
-    if self.getInfo('os')['os'][0].split()[0] != 'harmony-eng':
-      if (self.debug >= 2): print "WARNING: unable to adjust screen resolution on non Tegra device"
-      return False
+        if (self.debug >= 3):
+            print "INFO: adjusting screen resolution to %s, %s and rebooting" % (width, height)
+        try:
+            self.runCmds([{ 'cmd': "exec setprop persist.tegra.dpy%s.mode.width %s" % (screentype, width) }])
+            self.runCmds([{ 'cmd': "exec setprop persist.tegra.dpy%s.mode.height %s" % (screentype, height) }])
+        except AgentError:
+            return False
 
-    results = self.getInfo('screen')
-    parts = results['screen'][0].split(':')
-    if (self.debug >= 3): print "INFO: we have a current resolution of %s, %s" % (parts[1].split()[0], parts[2].split()[0])
-
-    #verify screen type is valid, and set it to the proper value (https://bugzilla.mozilla.org/show_bug.cgi?id=632895#c4)
-    screentype = -1
-    if (type == 'hdmi'):
-      screentype = 5
-    elif (type == 'vga' or type == 'crt'):
-      screentype = 3
-    else:
-      return False
+        return True
 
-    #verify we have numbers
-    if not (isinstance(width, int) and isinstance(height, int)):
-      return False
-
-    if (width < 100 or width > 9999):
-      return False
-
-    if (height < 100 or height > 9999):
-      return False
-
-    if (self.debug >= 3): print "INFO: adjusting screen resolution to %s, %s and rebooting" % (width, height)
-    try:
-      self.runCmds([{ 'cmd': "exec setprop persist.tegra.dpy%s.mode.width %s" % (screentype, width) }])
-      self.runCmds([{ 'cmd': "exec setprop persist.tegra.dpy%s.mode.height %s" % (screentype, height) }])
-    except AgentError:
-      return False
-
-    return True
-
-  # external function
-  # returns:
-  #  success: True
-  #  failure: False
-  def chmodDir(self, remoteDir):
-    try:
-      self.runCmds([{ 'cmd': "chmod "+remoteDir }])
-    except AgentError:
-      return False
-    return True
+    def chmodDir(self, remoteDir):
+        """
+        external function
+        returns:
+          success: True
+          failure: False
+        """
+        try:
+            self.runCmds([{ 'cmd': "chmod "+remoteDir }])
+        except AgentError:
+            return False
+        return True
 
 gCallbackData = ''
 
 class myServer(SocketServer.TCPServer):
-  allow_reuse_address = True
+    allow_reuse_address = True
 
 class callbackServer():
-  def __init__(self, ip, port, debuglevel):
-    global gCallbackData
-    if (debuglevel >= 1): print "DEBUG: gCallbackData is: %s on port: %s" % (gCallbackData, port)
-    gCallbackData = ''
-    self.ip = ip
-    self.port = port
-    self.connected = False
-    self.debug = debuglevel
-    if (self.debug >= 3): print "Creating server with " + str(ip) + ":" + str(port)
-    self.server = myServer((ip, port), self.myhandler)
-    self.server_thread = Thread(target=self.server.serve_forever)
-    self.server_thread.setDaemon(True)
-    self.server_thread.start()
+    def __init__(self, ip, port, debuglevel):
+        global gCallbackData
+        if (debuglevel >= 1):
+            print "DEBUG: gCallbackData is: %s on port: %s" % (gCallbackData, port)
+        gCallbackData = ''
+        self.ip = ip
+        self.port = port
+        self.connected = False
+        self.debug = debuglevel
+        if (self.debug >= 3):
+            print "Creating server with " + str(ip) + ":" + str(port)
+        self.server = myServer((ip, port), self.myhandler)
+        self.server_thread = Thread(target=self.server.serve_forever)
+        self.server_thread.setDaemon(True)
+        self.server_thread.start()
 
-  def disconnect(self, step = 60, timeout = 600):
-    t = 0
-    if (self.debug >= 3): print "Calling disconnect on callback server"
-    while t < timeout:
-      if (gCallbackData):
-        # Got the data back
-        if (self.debug >= 3): print "Got data back from agent: " + str(gCallbackData)
-        break
-      else:
-        if (self.debug >= 0): print '.',
-      time.sleep(step)
-      t += step
+    def disconnect(self, step = 60, timeout = 600):
+        t = 0
+        if (self.debug >= 3):
+            print "Calling disconnect on callback server"
+        while t < timeout:
+            if (gCallbackData):
+                # Got the data back
+                if (self.debug >= 3):
+                    print "Got data back from agent: " + str(gCallbackData)
+                break
+            else:
+                if (self.debug >= 0):
+                    print '.',
+            time.sleep(step)
+            t += step
 
-    try:
-      if (self.debug >= 3): print "Shutting down server now"
-      self.server.shutdown()
-    except:
-      if (self.debug >= 1): print "Unable to shutdown callback server - check for a connection on port: " + str(self.port)
+        try:
+            if (self.debug >= 3):
+                print "Shutting down server now"
+            self.server.shutdown()
+        except:
+            if (self.debug >= 1):
+                print "Automation Error: Unable to shutdown callback server - check for a connection on port: " + str(self.port)
 
-    #sleep 1 additional step to ensure not only we are online, but all our services are online
-    time.sleep(step)
-    return gCallbackData
+        #sleep 1 additional step to ensure not only we are online, but all our services are online
+        time.sleep(step)
+        return gCallbackData
 
-  class myhandler(SocketServer.BaseRequestHandler):
-    def handle(self):
-      global gCallbackData
-      gCallbackData = self.request.recv(1024)
-      #print "Callback Handler got data: " + str(gCallbackData)
-      self.request.send("OK")
-
+    class myhandler(SocketServer.BaseRequestHandler):
+        def handle(self):
+            global gCallbackData
+            gCallbackData = self.request.recv(1024)
+            #print "Callback Handler got data: " + str(gCallbackData)
+            self.request.send("OK")
--- a/testing/mozbase/mozdevice/mozdevice/droid.py
+++ b/testing/mozbase/mozdevice/mozdevice/droid.py
@@ -2,82 +2,82 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from devicemanagerADB import DeviceManagerADB
 from devicemanagerSUT import DeviceManagerSUT
 import StringIO
 
 class DroidMixin(object):
-  """Mixin to extend DeviceManager with Android-specific functionality"""
+    """Mixin to extend DeviceManager with Android-specific functionality"""
 
-  def launchApplication(self, appName, activityName, intent, url=None,
-                        extras=None):
-    """
-    Launches an Android application
-    returns:
-    success: True
-    failure: False
-    """
-    # only one instance of an application may be running at once
-    if self.processExist(appName):
-      return False
+    def launchApplication(self, appName, activityName, intent, url=None,
+                          extras=None):
+        """
+        Launches an Android application
+        returns:
+        success: True
+        failure: False
+        """
+        # only one instance of an application may be running at once
+        if self.processExist(appName):
+            return False
 
-    acmd = [ "am", "start", "-W", "-n", "%s/%s" % (appName, activityName)]
+        acmd = [ "am", "start", "-W", "-n", "%s/%s" % (appName, activityName)]
 
-    if intent:
-      acmd.extend(["-a", intent])
+        if intent:
+            acmd.extend(["-a", intent])
 
-    if extras:
-      for (key, val) in extras.iteritems():
-        if type(val) is int:
-          extraTypeParam = "--ei"
-        elif type(val) is bool:
-          extraTypeParam = "--ez"
-        else:
-          extraTypeParam = "--es"
-        acmd.extend([extraTypeParam, str(key), str(val)])
+        if extras:
+            for (key, val) in extras.iteritems():
+                if type(val) is int:
+                    extraTypeParam = "--ei"
+                elif type(val) is bool:
+                    extraTypeParam = "--ez"
+                else:
+                    extraTypeParam = "--es"
+                acmd.extend([extraTypeParam, str(key), str(val)])
 
-    if url:
-      acmd.extend(["-d", url])
+        if url:
+            acmd.extend(["-d", url])
 
-    # shell output not that interesting and debugging logs should already
-    # show what's going on here... so just create an empty memory buffer
-    # and ignore
-    shellOutput = StringIO.StringIO()
-    if self.shell(acmd, shellOutput) == 0:
-      return True
+        # shell output not that interesting and debugging logs should already
+        # show what's going on here... so just create an empty memory buffer
+        # and ignore
+        shellOutput = StringIO.StringIO()
+        if self.shell(acmd, shellOutput) == 0:
+            return True
 
-    return False
+        return False
 
-  def launchFennec(self, appName, intent="android.intent.action.VIEW",
-                   mozEnv=None, extraArgs=None, url=None):
-    """
-    Convenience method to launch Fennec on Android with various debugging
-    arguments
-    WARNING: FIXME: This would go better in mozrunner. Please do not
-    use this method if you are not comfortable with it going away sometime
-    in the near future
-    returns:
-    success: True
-    failure: False
-    """
-    extras = {}
+    def launchFennec(self, appName, intent="android.intent.action.VIEW",
+                                      mozEnv=None, extraArgs=None, url=None):
+        """
+        Convenience method to launch Fennec on Android with various debugging
+        arguments
+        WARNING: FIXME: This would go better in mozrunner. Please do not
+        use this method if you are not comfortable with it going away sometime
+        in the near future
+        returns:
+        success: True
+        failure: False
+        """
+        extras = {}
 
-    if mozEnv:
-      # mozEnv is expected to be a dictionary of environment variables: Fennec
-      # itself will set them when launched
-      for (envCnt, (envkey, envval)) in enumerate(mozEnv.iteritems()):
-        extras["env" + str(envCnt)] = envkey + "=" + envval
+        if mozEnv:
+            # mozEnv is expected to be a dictionary of environment variables: Fennec
+            # itself will set them when launched
+            for (envCnt, (envkey, envval)) in enumerate(mozEnv.iteritems()):
+                extras["env" + str(envCnt)] = envkey + "=" + envval
 
-    # Additional command line arguments that fennec will read and use (e.g.
-    # with a custom profile)
-    if extraArgs:
-      extras['args'] = " ".join(extraArgs)
+        # Additional command line arguments that fennec will read and use (e.g.
+        # with a custom profile)
+        if extraArgs:
+            extras['args'] = " ".join(extraArgs)
 
-    return self.launchApplication(appName, ".App", intent, url=url,
-                                  extras=extras)
+        return self.launchApplication(appName, ".App", intent, url=url,
+                                                                    extras=extras)
 
 class DroidADB(DeviceManagerADB, DroidMixin):
-  pass
+    pass
 
 class DroidSUT(DeviceManagerSUT, DroidMixin):
-  pass
+    pass
--- a/testing/mozbase/mozdevice/mozdevice/sutcli.py
+++ b/testing/mozbase/mozdevice/mozdevice/sutcli.py
@@ -63,23 +63,23 @@ class SUTCli(object):
                                     'help': 'get information on running processes on device'
                                 },
                           'ls': { 'function': self.listfiles,
                                   'min_args': 1,
                                   'max_args': 1,
                                   'help_args': '<remote>',
                                   'help': 'list files on device'
                                 },
-                          'rm': { 'function': lambda file: self.dm.removeFile(file),
+                          'rm': { 'function': lambda f: self.dm.removeFile(f),
                                     'min_args': 1,
                                     'max_args': 1,
                                     'help_args': '<remote>',
                                     'help': 'remove file from device'
                                 },
-                          'rmdir': { 'function': lambda dir: self.dm.removeDir(dir),
+                          'rmdir': { 'function': lambda d: self.dm.removeDir(d),
                                     'min_args': 1,
                                     'max_args': 1,
                                     'help_args': '<remote>',
                                     'help': 'recursively remove directory from device'
                                 }
                           }
 
         for (commandname, command) in self.commands.iteritems():
--- a/testing/mozbase/mozdevice/tests/sut.py
+++ b/testing/mozbase/mozdevice/tests/sut.py
@@ -2,29 +2,42 @@
 
 # Any copyright is dedicated to the Public Domain.
 # http://creativecommons.org/publicdomain/zero/1.0/
 
 import socket
 import mozdevice
 from threading import Thread
 import unittest
+import sys
+import time
 
 class BasicTest(unittest.TestCase):
 
     def _serve_thread(self):
-        conn, addr = self._sock.accept()
-        conn.send("$>\x00")
+        conn = None
         while self.commands:
+            if not conn:
+                conn, addr = self._sock.accept()
+                conn.send("$>\x00")
             (command, response) = self.commands.pop(0)
             data = conn.recv(1024).strip()
             self.assertEqual(data, command)
             # send response and prompt separately to test for bug 789496
-            conn.send("%s\n" % response)
-            conn.send("$>\x00")
+            # FIXME: Improve the mock agent, since overloading the meaning
+            # of 'response' is getting confusing.
+            if response is None:
+                conn.shutdown(socket.SHUT_RDWR)
+                conn.close()
+                conn = None
+            elif type(response) is int:
+                time.sleep(response)
+            else:
+                conn.send("%s\n" % response)
+                conn.send("$>\x00")
 
     def _serve(self, commands):
         self.commands = commands
         thread = Thread(target=self._serve_thread)
         thread.start()
         return thread
 
     def test_init(self):
@@ -32,17 +45,35 @@ class BasicTest(unittest.TestCase):
         self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         self._sock.bind(("127.0.0.1", 0))
         self._sock.listen(1)
 
         thread = self._serve([("testroot", "/mnt/sdcard"),
                               ("cd /mnt/sdcard/tests", ""),
                               ("cwd", "/mnt/sdcard/tests"),
                               ("ver", "SUTAgentAndroid Version XX")])
-        
+
+        port = self._sock.getsockname()[1]
+        mozdevice.DroidSUT.debug = 4
+        d = mozdevice.DroidSUT("127.0.0.1", port=port)
+        thread.join()
+
+    def test_reconnect(self):
+        """Tests DeviceManager initialization."""
+        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self._sock.bind(("127.0.0.1", 0))
+        self._sock.listen(1)
+
+        thread = self._serve([("testroot", "/mnt/sdcard"),
+                              ("cd /mnt/sdcard/tests", ""),
+                              ("cwd", None),
+                              ("cd /mnt/sdcard/tests", ""),
+                              ("cwd", "/mnt/sdcard/tests"),
+                              ("ver", "SUTAgentAndroid Version XX")])
+
         port = self._sock.getsockname()[1]
         mozdevice.DroidSUT.debug = 4
         d = mozdevice.DroidSUT("127.0.0.1", port=port)
         thread.join()
 
     def test_err(self):
         """Tests error handling during initialization."""
         self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -55,10 +86,49 @@ class BasicTest(unittest.TestCase):
                               ("mkdr /mnt/sdcard/tests", "/mnt/sdcard/tests successfully created"),
                               ("ver", "SUTAgentAndroid Version XX")])
 
         port = self._sock.getsockname()[1]
         mozdevice.DroidSUT.debug = 4
         dm = mozdevice.DroidSUT("127.0.0.1", port=port)
         thread.join()
 
+    def test_timeout_normal(self):
+        """Tests DeviceManager timeout, normal case."""
+        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self._sock.bind(("127.0.0.1", 0))
+        self._sock.listen(1)
+
+        thread = self._serve([("testroot", "/mnt/sdcard"),
+                              ("cd /mnt/sdcard/tests", ""),
+                              ("cwd", "/mnt/sdcard/tests"),
+                              ("ver", "SUTAgentAndroid Version XX"),
+                              ("rm /mnt/sdcard/tests/test.txt", "Removed the file")])
+
+        port = self._sock.getsockname()[1]
+        mozdevice.DroidSUT.debug = 4
+        d = mozdevice.DroidSUT("127.0.0.1", port=port)
+        data = d.removeFile('/mnt/sdcard/tests/test.txt')
+        self.assertEqual(data, "Removed the file")
+        thread.join()
+
+    def test_timeout_timeout(self):
+        """Tests DeviceManager timeout, timeout case."""
+        self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self._sock.bind(("127.0.0.1", 0))
+        self._sock.listen(1)
+
+        thread = self._serve([("testroot", "/mnt/sdcard"),
+                              ("cd /mnt/sdcard/tests", ""),
+                              ("cwd", "/mnt/sdcard/tests"),
+                              ("ver", "SUTAgentAndroid Version XX"),
+                              ("rm /mnt/sdcard/tests/test.txt", 3)])
+
+        port = self._sock.getsockname()[1]
+        mozdevice.DroidSUT.debug = 4
+        d = mozdevice.DroidSUT("127.0.0.1", port=port)
+        d.default_timeout = 1
+        data = d.removeFile('/mnt/sdcard/tests/test.txt')
+        self.assertEqual(data, None)
+        thread.join()
+
 if __name__ == '__main__':
     unittest.main()