Bug 1036530 - Instantiate / initialize device root only when needed in mozdevice. r=bc
authorWilliam Lachance <wlachance@mozilla.com>
Fri, 11 Jul 2014 15:29:30 -0400
changeset 214953 4d30c250d375e9f5afb57565061a93507f33a419
parent 214952 7c238ae102ab89015046294d7c42f20ff3a1b294
child 214954 3365fa0a849c478be30727ef96f58756ce69c0a7
push id3857
push userraliiev@mozilla.com
push dateTue, 02 Sep 2014 16:39:23 +0000
treeherdermozilla-beta@5638b907b505 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbc
bugs1036530
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1036530 - Instantiate / initialize device root only when needed in mozdevice. r=bc
layout/tools/reftest/remotereftest.py
layout/tools/reftest/runreftestb2g.py
testing/mochitest/runtestsremote.py
testing/mozbase/docs/mozdevice.rst
testing/mozbase/mozdevice/mozdevice/devicemanager.py
testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py
testing/mozbase/mozdevice/mozdevice/devicemanagerSUT.py
testing/mozbase/mozdevice/mozdevice/dmcli.py
testing/mozbase/mozdevice/mozdevice/droid.py
testing/mozbase/mozdevice/sut_tests/test_exec.py
testing/mozbase/mozdevice/sut_tests/test_exec_env.py
testing/mozbase/mozdevice/sut_tests/test_fileExists.py
testing/mozbase/mozdevice/sut_tests/test_getdir.py
testing/mozbase/mozdevice/sut_tests/test_pull.py
testing/mozbase/mozdevice/sut_tests/test_push1.py
testing/mozbase/mozdevice/sut_tests/test_push2.py
testing/mozbase/mozdevice/sut_tests/test_pushbinary.py
testing/mozbase/mozdevice/sut_tests/test_pushsmalltext.py
testing/mozbase/mozdevice/tests/sut.py
testing/mozbase/mozdevice/tests/sut_basic.py
testing/mozbase/mozdevice/tests/sut_unpackfile.py
testing/remotecppunittests.py
testing/xpcshell/remotexpcshelltests.py
testing/xpcshell/runtestsb2g.py
--- a/layout/tools/reftest/remotereftest.py
+++ b/layout/tools/reftest/remotereftest.py
@@ -100,17 +100,17 @@ class RemoteOptions(ReftestOptions):
         self.set_defaults(**defaults)
 
     def verifyRemoteOptions(self, options):
         if options.runTestsInParallel:
             self.error("Cannot run parallel tests here")
 
         # Ensure our defaults are set properly for everything we can infer
         if not options.remoteTestRoot:
-            options.remoteTestRoot = self.automation._devicemanager.getDeviceRoot() + '/reftest'
+            options.remoteTestRoot = self.automation._devicemanager.deviceRoot + '/reftest'
         options.remoteProfile = options.remoteTestRoot + "/profile"
 
         # Verify that our remotewebserver is set properly
         if (options.remoteWebServer == None or
             options.remoteWebServer == '127.0.0.1'):
             print "ERROR: Either you specified the loopback for the remote webserver or ",
             print "your local IP cannot be detected.  Please provide the local ip in --remote-webserver"
             return None
@@ -397,17 +397,17 @@ class RemoteReftest(RefTest):
         return path
 
     def printDeviceInfo(self, printLogcat=False):
         try:
             if printLogcat:
                 logcat = self._devicemanager.getLogcat(filterOutRegexps=fennecLogcatFilters)
                 print ''.join(logcat)
             print "Device info: %s" % self._devicemanager.getInfo()
-            print "Test root: %s" % self._devicemanager.getDeviceRoot()
+            print "Test root: %s" % self._devicemanager.deviceRoot
         except devicemanager.DMError:
             print "WARNING: Error getting device information"
 
     def cleanup(self, profileDir):
         # Pull results back from device
         if self.remoteLogFile and \
                 self._devicemanager.fileExists(self.remoteLogFile):
             self._devicemanager.getFile(self.remoteLogFile, self.localLogName)
--- a/layout/tools/reftest/runreftestb2g.py
+++ b/layout/tools/reftest/runreftestb2g.py
@@ -135,17 +135,17 @@ class B2GOptions(ReftestOptions):
 
         self.set_defaults(**defaults)
 
     def verifyRemoteOptions(self, options, auto):
         if options.runTestsInParallel:
             self.error("Cannot run parallel tests here")
 
         if not options.remoteTestRoot:
-            options.remoteTestRoot = auto._devicemanager.getDeviceRoot() + "/reftest"
+            options.remoteTestRoot = auto._devicemanager.deviceRoot + "/reftest"
 
         options.remoteProfile = options.remoteTestRoot + "/profile"
 
         productRoot = options.remoteTestRoot + "/" + auto._product
         if options.utilityPath == auto.DIST_BIN:
             options.utilityPath = productRoot + "/bin"
 
         if options.remoteWebServer == None:
--- a/testing/mochitest/runtestsremote.py
+++ b/testing/mochitest/runtestsremote.py
@@ -118,17 +118,17 @@ class RemoteOptions(MochitestOptions):
         defaults["testPath"] = ""
         defaults["app"] = None
         defaults["utilityPath"] = None
 
         self.set_defaults(**defaults)
 
     def verifyRemoteOptions(self, options, automation):
         if not options.remoteTestRoot:
-            options.remoteTestRoot = automation._devicemanager.getDeviceRoot()
+            options.remoteTestRoot = automation._devicemanager.deviceRoot
 
         if options.remoteWebServer == None:
             if os.name != "nt":
                 options.remoteWebServer = moznetwork.get_ip()
             else:
                 log.error("you must specify a --remote-webserver=<ip address>")
                 return None
 
@@ -495,22 +495,22 @@ class MochiRemote(Mochitest):
         log.info("SCREENSHOT: TOTAL PRINTED: [%s]", printed)
 
     def printDeviceInfo(self, printLogcat=False):
         try:
             if printLogcat:
                 logcat = self._dm.getLogcat(filterOutRegexps=fennecLogcatFilters)
                 log.info('\n'+(''.join(logcat)))
             log.info("Device info: %s", self._dm.getInfo())
-            log.info("Test root: %s", self._dm.getDeviceRoot())
+            log.info("Test root: %s", self._dm.deviceRoot)
         except devicemanager.DMError:
             log.warn("Error getting device information")
 
     def buildRobotiumConfig(self, options, browserEnv):
-        deviceRoot = self._dm.getDeviceRoot()
+        deviceRoot = self._dm.deviceRoot
         fHandle = tempfile.NamedTemporaryFile(suffix='.config',
                                               prefix='robotium-',
                                               dir=os.getcwd(),
                                               delete=False)
         fHandle.write("profile=%s\n" % (self.remoteProfile))
         fHandle.write("logfile=%s\n" % (options.remoteLogFile))
         fHandle.write("host=http://mochi.test:8888/tests\n")
         fHandle.write("rawhost=http://%s:%s/tests\n" % (options.remoteWebServer, options.httpPort))
@@ -592,17 +592,17 @@ def main():
     mochitest.printDeviceInfo()
 
     # Add Android version (SDK level) to mozinfo so that manifest entries
     # can be conditional on android_version.
     androidVersion = dm.shellCheckOutput(['getprop', 'ro.build.version.sdk'])
     log.info("Android sdk version '%s'; will use this to filter manifests" % str(androidVersion))
     mozinfo.info['android_version'] = androidVersion
 
-    deviceRoot = dm.getDeviceRoot()
+    deviceRoot = dm.deviceRoot
     if options.dmdPath:
         dmdLibrary = "libdmd.so"
         dmdPathOnDevice = os.path.join(deviceRoot, dmdLibrary)
         dm.removeFile(dmdPathOnDevice)
         dm.pushFile(os.path.join(options.dmdPath, dmdLibrary), dmdPathOnDevice)
         options.dmdPath = deviceRoot
 
     options.dumpOutputDirectory = deviceRoot
--- a/testing/mozbase/docs/mozdevice.rst
+++ b/testing/mozbase/docs/mozdevice.rst
@@ -42,33 +42,32 @@ Informational methods
 .. automethod:: DeviceManager.getCurrentTime(self)
 .. automethod:: DeviceManager.getIP
 .. automethod:: DeviceManager.saveScreenshot
 .. automethod:: DeviceManager.recordLogcat
 .. automethod:: DeviceManager.getLogcat
 
 File management methods
 ```````````````````````
+.. autoattribute:: DeviceManager.deviceRoot
+.. automethod:: DeviceManager.getDeviceRoot(self)
 .. automethod:: DeviceManager.pushFile(self, localFilename, remoteFilename, retryLimit=1)
 .. automethod:: DeviceManager.pushDir(self, localDirname, remoteDirname, retryLimit=1)
 .. automethod:: DeviceManager.pullFile(self, remoteFilename)
 .. automethod:: DeviceManager.getFile(self, remoteFilename, localFilename)
 .. automethod:: DeviceManager.getDirectory(self, remoteDirname, localDirname, checkDir=True)
 .. automethod:: DeviceManager.validateFile(self, remoteFilename, localFilename)
 .. automethod:: DeviceManager.mkDir(self, remoteDirname)
 .. automethod:: DeviceManager.mkDirs(self, filename)
 .. automethod:: DeviceManager.dirExists(self, dirpath)
 .. automethod:: DeviceManager.fileExists(self, filepath)
 .. automethod:: DeviceManager.listFiles(self, rootdir)
 .. automethod:: DeviceManager.removeFile(self, filename)
 .. automethod:: DeviceManager.removeDir(self, remoteDirname)
 .. automethod:: DeviceManager.chmodDir(self, remoteDirname, mask="777")
-.. automethod:: DeviceManager.getDeviceRoot(self)
-.. automethod:: DeviceManager.getAppRoot(self, packageName=None)
-.. automethod:: DeviceManager.getTestRoot(self, harnessName)
 .. automethod:: DeviceManager.getTempDir(self)
 
 Process management methods
 ``````````````````````````
 .. automethod:: DeviceManager.shell(self, cmd, outputfile, env=None, cwd=None, timeout=None, root=False)
 .. automethod:: DeviceManager.shellCheckOutput(self, cmd, env=None, cwd=None, timeout=None, root=False)
 .. automethod:: DeviceManager.getProcessList(self)
 .. automethod:: DeviceManager.processExist(self, processName)
@@ -120,16 +119,17 @@ Android extensions
 For Android, we provide two variants of the `DeviceManager` interface
 with extensions useful for that platform. These classes are called
 DroidADB and DroidSUT. They inherit all methods from DeviceManagerADB
 and DeviceManagerSUT. Here is the interface for DroidADB:
 
 .. automethod:: mozdevice.DroidADB.launchApplication
 .. automethod:: mozdevice.DroidADB.launchFennec
 .. automethod:: mozdevice.DroidADB.getInstalledApps
+.. automethod:: mozdevice.DroidADB.getAppRoot
 
 These methods are also found in the DroidSUT class.
 
 ADB Interface
 -------------
 
 The following classes provide a basic interface to interact with the
 Android Debug Tool (adb) and Android-based devices.  It is intended to
--- a/testing/mozbase/mozdevice/mozdevice/devicemanager.py
+++ b/testing/mozbase/mozdevice/mozdevice/devicemanager.py
@@ -41,21 +41,23 @@ class DeviceManager(object):
     applications from the device.
 
     Never instantiate this class directly! Instead, instantiate an
     implementation of it like DeviceManagerADB or DeviceManagerSUT.
     """
 
     _logcatNeedsRoot = True
 
-    def __init__(self, logLevel=mozlog.ERROR):
+    def __init__(self, logLevel=mozlog.ERROR, deviceRoot=None):
         self._logger = mozlog.getLogger("DeviceManager")
         self._logLevel = logLevel
         self._logger.setLevel(logLevel)
         self._remoteIsWin = None
+        self._isDeviceRootSetup = False
+        self._deviceRoot = deviceRoot
 
     @property
     def remoteIsWin(self):
         if self._remoteIsWin is None:
             self._remoteIsWin = self.getInfo("os")["os"][0] == "windows"
         return self._remoteIsWin
 
     @property
@@ -157,17 +159,17 @@ class DeviceManager(object):
         """
         screencap = '/system/bin/screencap'
         if not self.fileExists(screencap):
             raise DMError("Unable to capture screenshot on device: no screencap utility")
 
         with open(filename, 'w') as pngfile:
             # newer versions of screencap can write directly to a png, but some
             # older versions can't
-            tempScreenshotFile = self.getDeviceRoot() + "/ss-dm.tmp"
+            tempScreenshotFile = self.deviceRoot + "/ss-dm.tmp"
             self.shellCheckOutput(["sh", "-c", "%s > %s" %
                                    (screencap, tempScreenshotFile)],
                                   root=True)
             buf = self.pullFile(tempScreenshotFile)
             width = int(struct.unpack("I", buf[0:4])[0])
             height = int(struct.unpack("I", buf[4:8])[0])
             with open(filename, 'w') as pngfile:
                 pngfile.write(self._writePNG(buf[12:], width, height))
@@ -306,65 +308,44 @@ class DeviceManager(object):
          """
 
     @abstractmethod
     def chmodDir(self, remoteDirname, mask="777"):
         """
         Recursively changes file permissions in a directory.
         """
 
-    @abstractmethod
-    def getDeviceRoot(self):
+    @property
+    def deviceRoot(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.
+        The device root on the device filesystem for putting temporary
+        testing files.
+        """
+        # derive deviceroot value if not set
+        if not self._deviceRoot or not self._isDeviceRootSetup:
+            self._deviceRoot = self._setupDeviceRoot(self._deviceRoot)
+            self._isDeviceRootSetup = True
 
-        Structure on the device is as follows:
-
-        ::
-
-          /tests
-              /<fennec>|<firefox>  --> approot
-              /profile
-              /xpcshell
-              /reftest
-              /mochitest
-        """
+        return self._deviceRoot
 
     @abstractmethod
-    def getAppRoot(self, packageName=None):
-        """
-        Returns the app root directory.
-
-        E.g /tests/fennec or /tests/firefox
+    def _setupDeviceRoot(self):
         """
-        # TODO Support org.mozilla.firefox and B2G
-
-    def getTestRoot(self, harnessName):
-        """
-        Gets the directory location on the device for a specific test type.
-
-        :param harnessName: one of: "xpcshell", "reftest", "mochitest"
+        Sets up and returns a device root location that can be written to by tests.
         """
 
-        devroot = self.getDeviceRoot()
-        if (devroot == None):
-            return None
+    def getDeviceRoot(self):
+        """
+        Get the device root on the device filesystem for putting temporary
+        testing files.
 
-        if (re.search('xpcshell', harnessName, re.I)):
-            self.testRoot = devroot + '/xpcshell'
-        elif (re.search('?(i)reftest', harnessName)):
-            self.testRoot = devroot + '/reftest'
-        elif (re.search('?(i)mochitest', harnessName)):
-            self.testRoot = devroot + '/mochitest'
-        return self.testRoot
+        .. deprecated:: 0.38
+          Use the :py:attr:`deviceRoot` property instead.
+        """
+        return self.deviceRoot
 
     @abstractmethod
     def getTempDir(self):
         """
         Returns a temporary directory we can use on this device, ensuring
         also that it exists.
         """
 
--- a/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py
+++ b/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py
@@ -30,21 +30,21 @@ class DeviceManagerADB(DeviceManager):
     _packageName = None
     _tempDir = None
     connected = False
     default_timeout = 300
 
     def __init__(self, host=None, port=5555, retryLimit=5, packageName='fennec',
                  adbPath='adb', deviceSerial=None, deviceRoot=None,
                  logLevel=mozlog.ERROR, autoconnect=True, **kwargs):
-        DeviceManager.__init__(self, logLevel)
+        DeviceManager.__init__(self, logLevel=logLevel,
+                               deviceRoot=deviceRoot)
         self.host = host
         self.port = port
         self.retryLimit = retryLimit
-        self.deviceRoot = deviceRoot
 
         # 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
 
@@ -66,19 +66,16 @@ class DeviceManagerADB(DeviceManager):
         if not self.connected:
             # 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()
 
-            # set up device root
-            self._setupDeviceRoot()
-
             # Some commands require root to work properly, even with ADB (e.g.
             # grabbing APKs out of /data). For these cases, we check whether
             # we're running as root. If that isn't true, check for the
             # existence of an su binary
             self._checkForRoot()
 
             # can we use zip to speed up some file operations? (currently not
             # required)
@@ -444,74 +441,49 @@ class DeviceManagerADB(DeviceManager):
         if localFile is None:
             return None
 
         md5 = self._getLocalHash(localFile)
         mozfile.remove(localFile)
 
         return md5
 
-    def _setupDeviceRoot(self):
-        """
-        setup the device root and cache its value
-        """
-        # if self.deviceRoot is already set, create it if necessary, and use it
-        if self.deviceRoot:
-            if not self.dirExists(self.deviceRoot):
-                try:
-                    self.mkDir(self.deviceRoot)
-                except:
-                    self._logger.error("Unable to create device root %s" % self.deviceRoot)
-                    raise
-            return
+    def _setupDeviceRoot(self, deviceRoot):
+        # user-specified device root, create it and return it
+        if deviceRoot:
+            self.mkDir(deviceRoot)
+            return deviceRoot
 
+        # we must determine the device root ourselves
         paths = [('/storage/sdcard0', 'tests'),
                  ('/storage/sdcard1', 'tests'),
                  ('/sdcard', 'tests'),
                  ('/mnt/sdcard', 'tests'),
                  ('/data/local', 'tests')]
         for (basePath, subPath) in paths:
             if self.dirExists(basePath):
-                testRoot = os.path.join(basePath, subPath)
+                root = os.path.join(basePath, subPath)
                 try:
-                    self.mkDir(testRoot)
-                    self.deviceRoot = testRoot
-                    return
+                    self.mkDir(root)
+                    return root
                 except:
                     pass
 
         raise DMError("Unable to set up device root using paths: [%s]"
                         % ", ".join(["'%s'" % os.path.join(b, s) for b, s in paths]))
 
-    def getDeviceRoot(self):
-        return self.deviceRoot
-
     def getTempDir(self):
         # Cache result to speed up operations depending
         # on the temporary directory.
         if not self._tempDir:
-            self._tempDir = self.getDeviceRoot() + "/tmp"
+            self._tempDir = "%s/tmp" % self.deviceRoot
             self.mkDir(self._tempDir)
 
         return self._tempDir
 
-    def getAppRoot(self, packageName):
-        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)
-        raise DMError("Failed to get application root for: %s" % packageName)
-
     def reboot(self, wait = False, **kwargs):
         self._checkCmd(["reboot"])
         if wait:
             self._checkCmd(["wait-for-device", "shell", "ls", "/sbin"])
 
     def updateApp(self, appBundlePath, **kwargs):
         return self._runCmd(["install", "-r", appBundlePath]).stdout.read()
 
--- a/testing/mozbase/mozdevice/mozdevice/devicemanagerSUT.py
+++ b/testing/mozbase/mozdevice/mozdevice/devicemanagerSUT.py
@@ -32,26 +32,23 @@ class DeviceManagerSUT(DeviceManager):
     _agentErrorRE = re.compile('^##AGENT-WARNING##\ ?(.*)')
     default_timeout = 300
 
     reboot_timeout = 600
     reboot_settling_time = 60
 
     def __init__(self, host, port = 20701, retryLimit = 5,
             deviceRoot = None, logLevel = mozlog.ERROR, **kwargs):
-        DeviceManager.__init__(self, logLevel)
+        DeviceManager.__init__(self, logLevel = logLevel,
+                               deviceRoot = deviceRoot)
         self.host = host
         self.port = port
         self.retryLimit = retryLimit
         self._sock = None
         self._everConnected = False
-        self.deviceRoot = deviceRoot
-
-        # Initialize device root
-        self.getDeviceRoot()
 
         # Get version
         verstring = self._runCmds([{ 'cmd': 'ver' }])
         ver_re = re.match('(\S+) Version (\S+)', verstring)
         self.agentProductName = ver_re.group(1)
         self.agentVersion = ver_re.group(2)
 
     def _cmdNeedsResponse(self, cmd):
@@ -290,16 +287,24 @@ class DeviceManagerSUT(DeviceManager):
         if shouldCloseSocket:
             try:
                 self._sock.close()
                 self._sock = None
             except:
                 self._sock = None
                 raise DMError("Automation Error: Error closing socket")
 
+    def _setupDeviceRoot(self, deviceRoot):
+        if not deviceRoot:
+            deviceRoot = "%s/tests" % self._runCmds(
+                [{ 'cmd': 'testroot' }]).strip()
+        self.mkDir(deviceRoot)
+
+        return deviceRoot
+
     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)
 
         # execcwd/execcwdsu currently unsupported in Negatus; see bug 824127.
         if cwd and self.agentProductName == 'SUTAgentNegatus':
             raise DMError("Negatus does not support execcwd/execcwdsu")
@@ -505,24 +510,21 @@ class DeviceManagerSUT(DeviceManager):
 
         DEPRECATED: Use shell() or launchApplication() for new code
         """
         if not cmd:
             self._logger.warn("launchProcess called without command to run")
             return None
 
         if cmd[0] == 'am' and hasattr(self, '_getExtraAmStartArgs'):
-            cmd = cmd[:2] + self._getExtraAmStartArgs() + cmd[2:] 
+            cmd = cmd[:2] + self._getExtraAmStartArgs() + cmd[2:]
 
         cmdline = subprocess.list2cmdline(cmd)
-        if (outputFile == "process.txt" or outputFile == None):
-            outputFile = self.getDeviceRoot();
-            if outputFile is None:
-                return None
-            outputFile += "/process.txt"
+        if outputFile == "process.txt" or outputFile is None:
+            outputFile += "%s/process.txt" % self.deviceRoot
             cmdline += " > " + outputFile
 
         # Prepend our env to the command
         cmdline = '%s %s' % (self._formatEnvString(env), cmdline)
 
         # fireProcess may trigger an exception, but we won't handle it
         if cmd[0] == "am":
             # Robocop tests spawn "am instrument". sutAgent's exec ensures that
@@ -707,41 +709,22 @@ class DeviceManagerSUT(DeviceManager):
 
         return False
 
     def _getRemoteHash(self, filename):
         data = self._runCmds([{ 'cmd': 'hash ' + filename }]).strip()
         self._logger.debug("remote hash returned: '%s'" % data)
         return data
 
-    def getDeviceRoot(self):
-        if not self.deviceRoot:
-            data = self._runCmds([{ 'cmd': 'testroot' }])
-            self.deviceRoot = data.strip() + '/tests'
-
-        if not self.dirExists(self.deviceRoot):
-            self.mkDir(self.deviceRoot)
-
-        return self.deviceRoot
-
-    def getAppRoot(self, packageName):
-        data = self._runCmds([{ 'cmd': 'getapproot ' + packageName }])
-
-        return data.strip()
-
     def unpackFile(self, filePath, destDir=None):
         """
         Unzips a bundle to a location on the device
 
         If destDir is not specified, the bundle is extracted in the same directory
         """
-        devroot = self.getDeviceRoot()
-        if (devroot == None):
-            return None
-
         # if no destDir is passed in just set it to filePath's folder
         if not destDir:
             destDir = posixpath.dirname(filePath)
 
         if destDir[-1] != '/':
             destDir += '/'
 
         self._runCmds([{ 'cmd': 'unzp %s %s' % (filePath, destDir)}])
--- a/testing/mozbase/mozdevice/mozdevice/dmcli.py
+++ b/testing/mozbase/mozdevice/mozdevice/dmcli.py
@@ -239,17 +239,17 @@ class DMCli(object):
             dest = posixpath.basename(src)
         if self.dm.dirExists(src):
             self.dm.getDirectory(src, dest)
         else:
             self.dm.getFile(src, dest)
 
     def install(self, args):
         basename = os.path.basename(args.file)
-        app_path_on_device = posixpath.join(self.dm.getDeviceRoot(),
+        app_path_on_device = posixpath.join(self.dm.deviceRoot,
                                             basename)
         self.dm.pushFile(args.file, app_path_on_device)
         self.dm.installApp(app_path_on_device)
 
     def uninstall(self, args):
         self.dm.uninstallApp(args.packagename)
 
     def launchapp(self, args):
--- a/testing/mozbase/mozdevice/mozdevice/droid.py
+++ b/testing/mozbase/mozdevice/mozdevice/droid.py
@@ -171,16 +171,23 @@ class DroidADB(DeviceManagerADB, DroidMi
             # Extract package name: string of non-whitespace ending in forward slash
             m = re.search('(\S+)/$', line)
             if m:
                 package = m.group(1)
         if not package:
             raise DMError("unable to find focused app")
         return package
 
+    def getAppRoot(self, packageName):
+        """
+        Returns the root directory for the specified android application
+        """
+        # relying on convention
+        return '/data/data/%s' % packageName
+
 class DroidSUT(DeviceManagerSUT, DroidMixin):
 
     def _getExtraAmStartArgs(self):
         # in versions of android in jellybean and beyond, the agent may run as
         # a different process than the one that started the app. In this case,
         # we need to get back the original user serial number and then pass
         # that to the 'am start' command line
         if not hasattr(self, '_userSerial'):
@@ -200,16 +207,19 @@ class DroidSUT(DeviceManagerSUT, DroidMi
         if self._userSerial is not None:
             return [ "--user", self._userSerial ]
 
         return []
 
     def getTopActivity(self):
         return self._runCmds([{ 'cmd': "activity" }]).strip()
 
+    def getAppRoot(self, packageName):
+        return self._runCmds([{ 'cmd': 'getapproot %s' % packageName }]).strip()
+
 def DroidConnectByHWID(hwid, timeout=30, **kwargs):
     """Try to connect to the given device by waiting for it to show up using mDNS with the given timeout."""
     zc = Zeroconf(moznetwork.get_ip())
 
     evt = threading.Event()
     listener = ZeroconfListener(hwid, evt)
     sb = ServiceBrowser(zc, "_sutagent._tcp.local.", listener)
     foundIP = None
--- a/testing/mozbase/mozdevice/sut_tests/test_exec.py
+++ b/testing/mozbase/mozdevice/sut_tests/test_exec.py
@@ -7,17 +7,17 @@ from StringIO import StringIO
 
 from dmunit import DeviceManagerTestCase
 
 class ExecTestCase(DeviceManagerTestCase):
 
     def runTest(self):
         """Simple exec test, does not use env vars."""
         out = StringIO()
-        filename = posixpath.join(self.dm.getDeviceRoot(), 'test_exec_file')
+        filename = posixpath.join(self.dm.deviceRoot, 'test_exec_file')
         # Make sure the file was not already there
         self.dm.removeFile(filename)
         self.dm.shell(['dd', 'if=/dev/zero', 'of=%s' % filename, 'bs=1024',
                        'count=1'], out)
         # Check that the file has been created
         self.assertTrue(self.dm.fileExists(filename))
         # Clean up
         self.dm.removeFile(filename)
--- a/testing/mozbase/mozdevice/sut_tests/test_exec_env.py
+++ b/testing/mozbase/mozdevice/sut_tests/test_exec_env.py
@@ -9,17 +9,17 @@ from StringIO import StringIO
 from dmunit import DeviceManagerTestCase
 
 class ExecEnvTestCase(DeviceManagerTestCase):
 
     def runTest(self):
         """Exec test with env vars."""
         # Push the file
         localfile = os.path.join('test-files', 'test_script.sh')
-        remotefile = posixpath.join(self.dm.getDeviceRoot(), 'test_script.sh')
+        remotefile = posixpath.join(self.dm.deviceRoot, 'test_script.sh')
         self.dm.pushFile(localfile, remotefile)
 
         # Run the cmd
         out = StringIO()
         self.dm.shell(['sh', remotefile], out, env={'THE_ANSWER': 42})
 
         # Rewind the output file
         out.seek(0)
--- a/testing/mozbase/mozdevice/sut_tests/test_fileExists.py
+++ b/testing/mozbase/mozdevice/sut_tests/test_fileExists.py
@@ -13,25 +13,25 @@ class FileExistsTestCase(DeviceManagerTe
 
     def testOnRoot(self):
         self.assertTrue(self.dm.fileExists('/'))
 
     def testOnNonexistent(self):
         self.assertFalse(self.dm.fileExists('/doesNotExist'))
 
     def testOnRegularFile(self):
-        remote_path = posixpath.join(self.dm.getDeviceRoot(), 'testFile')
+        remote_path = posixpath.join(self.dm.deviceRoot, 'testFile')
         self.assertFalse(self.dm.fileExists(remote_path))
         with tempfile.NamedTemporaryFile() as f:
             self.dm.pushFile(f.name, remote_path)
         self.assertTrue(self.dm.fileExists(remote_path))
         self.dm.removeFile(remote_path)
 
     def testOnDirectory(self):
-        remote_path = posixpath.join(self.dm.getDeviceRoot(), 'testDir')
+        remote_path = posixpath.join(self.dm.deviceRoot, 'testDir')
         remote_path_file = posixpath.join(remote_path, 'testFile')
         self.assertFalse(self.dm.fileExists(remote_path))
         with tempfile.NamedTemporaryFile() as f:
             self.dm.pushFile(f.name, remote_path_file)
         self.assertTrue(self.dm.fileExists(remote_path))
         self.dm.removeFile(remote_path_file)
         self.dm.removeDir(remote_path)
 
--- a/testing/mozbase/mozdevice/sut_tests/test_getdir.py
+++ b/testing/mozbase/mozdevice/sut_tests/test_getdir.py
@@ -24,17 +24,17 @@ class GetDirectoryTestCase(DeviceManager
 
     def tearDown(self):
         shutil.rmtree(self.localsrcdir)
         shutil.rmtree(self.localdestdir)
 
     def runTest(self):
         """This tests the getDirectory() function.
         """
-        testroot = posixpath.join(self.dm.getDeviceRoot(), 'infratest')
+        testroot = posixpath.join(self.dm.deviceRoot, 'infratest')
         self.dm.removeDir(testroot)
         self.dm.mkDir(testroot)
         self.dm.pushDir(
             os.path.join(self.localsrcdir, 'push1'),
             posixpath.join(testroot, 'push1'))
         # pushDir doesn't copy over empty directories, but we want to make sure
         # that they are retrieved correctly.
         self.dm.mkDir(posixpath.join(testroot, 'push1', 'emptysub'))
--- a/testing/mozbase/mozdevice/sut_tests/test_pull.py
+++ b/testing/mozbase/mozdevice/sut_tests/test_pull.py
@@ -14,17 +14,17 @@ class PullTestCase(DeviceManagerTestCase
     def runTest(self):
         """Tests the "pull" command with a binary file.
         """
         orig = hashlib.md5()
         new = hashlib.md5()
         local_test_file = os.path.join('test-files', 'mybinary.zip')
         orig.update(file(local_test_file, 'r').read())
 
-        testroot = self.dm.getDeviceRoot()
+        testroot = self.dm.deviceRoot
         remote_test_file = posixpath.join(testroot, 'mybinary.zip')
         self.dm.removeFile(remote_test_file)
         self.dm.pushFile(local_test_file, remote_test_file)
         new.update(self.dm.pullFile(remote_test_file))
         # Use hexdigest() instead of digest() since values are printed
         # if assert fails
         self.assertEqual(orig.hexdigest(), new.hexdigest())
 
--- a/testing/mozbase/mozdevice/sut_tests/test_push1.py
+++ b/testing/mozbase/mozdevice/sut_tests/test_push1.py
@@ -7,17 +7,17 @@ import posixpath
 
 from dmunit import DeviceManagerTestCase
 
 class Push1TestCase(DeviceManagerTestCase):
 
     def runTest(self):
         """This tests copying a directory structure to the device.
         """
-        dvroot = self.dm.getDeviceRoot()
+        dvroot = self.dm.deviceRoot
         dvpath = posixpath.join(dvroot, 'infratest')
         self.dm.removeDir(dvpath)
         self.dm.mkDir(dvpath)
 
         p1 = os.path.join('test-files', 'push1')
         # Set up local stuff
         try:
             os.rmdir(p1)
--- a/testing/mozbase/mozdevice/sut_tests/test_push2.py
+++ b/testing/mozbase/mozdevice/sut_tests/test_push2.py
@@ -7,17 +7,17 @@ import posixpath
 
 from dmunit import DeviceManagerTestCase
 
 class Push2TestCase(DeviceManagerTestCase):
 
     def runTest(self):
         """This tests copying a directory structure with files to the device.
         """
-        testroot = posixpath.join(self.dm.getDeviceRoot(), 'infratest')
+        testroot = posixpath.join(self.dm.deviceRoot, 'infratest')
         self.dm.removeDir(testroot)
         self.dm.mkDir(testroot)
         path = posixpath.join(testroot, 'push2')
         self.dm.pushDir(os.path.join('test-files', 'push2'), path)
 
         # Let's walk the tree and make sure everything is there
         # though it's kind of cheesy, we'll use the validate file to compare
         # hashes - we use the client side hashing when testing the cat command
--- a/testing/mozbase/mozdevice/sut_tests/test_pushbinary.py
+++ b/testing/mozbase/mozdevice/sut_tests/test_pushbinary.py
@@ -7,12 +7,12 @@ import posixpath
 
 from dmunit import DeviceManagerTestCase
 
 class PushBinaryTestCase(DeviceManagerTestCase):
 
     def runTest(self):
         """This tests copying a binary file.
         """
-        testroot = self.dm.getDeviceRoot()
+        testroot = self.dm.deviceRoot
         self.dm.removeFile(posixpath.join(testroot, 'mybinary.zip'))
         self.dm.pushFile(os.path.join('test-files', 'mybinary.zip'),
                          posixpath.join(testroot, 'mybinary.zip'))
--- a/testing/mozbase/mozdevice/sut_tests/test_pushsmalltext.py
+++ b/testing/mozbase/mozdevice/sut_tests/test_pushsmalltext.py
@@ -7,12 +7,12 @@ import posixpath
 
 from dmunit import DeviceManagerTestCase
 
 class PushSmallTextTestCase(DeviceManagerTestCase):
 
     def runTest(self):
         """This tests copying a small text file.
         """
-        testroot = self.dm.getDeviceRoot()
+        testroot = self.dm.deviceRoot
         self.dm.removeFile(posixpath.join(testroot, 'smalltext.txt'))
         self.dm.pushFile(os.path.join('test-files', 'smalltext.txt'),
                          posixpath.join(testroot, 'smalltext.txt'))
--- a/testing/mozbase/mozdevice/tests/sut.py
+++ b/testing/mozbase/mozdevice/tests/sut.py
@@ -13,19 +13,17 @@ class MockAgent(object):
 
     MAX_WAIT_TIME_SECONDS = 10
     SOCKET_TIMEOUT_SECONDS = 5
 
     def __init__(self, tester, start_commands = None, commands = []):
         if start_commands:
             self.commands = start_commands
         else:
-            self.commands = [("testroot", "/mnt/sdcard"),
-                             ("isdir /mnt/sdcard/tests", "TRUE"),
-                             ("ver", "SUTAgentAndroid Version 1.14")]
+            self.commands = [("ver", "SUTAgentAndroid Version 1.14")]
         self.commands = self.commands + commands
 
         self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         self._sock.bind(("127.0.0.1", 0))
         self._sock.listen(1)
 
         self.tester = tester
 
--- a/testing/mozbase/mozdevice/tests/sut_basic.py
+++ b/testing/mozbase/mozdevice/tests/sut_basic.py
@@ -10,23 +10,21 @@ class BasicTest(unittest.TestCase):
         a = MockAgent(self)
 
         mozdevice.DroidSUT("127.0.0.1", port=a.port, logLevel=mozlog.DEBUG)
         # all testing done in device's constructor
         a.wait()
 
     def test_init_err(self):
         """Tests error handling during initialization."""
-        cmds = [("testroot", "/mnt/sdcard"),
-                ("isdir /mnt/sdcard/tests", "/mnt/sdcard/tests: No such file or directory\n"),
-                ("isdir /mnt/sdcard/tests", "/mnt/sdcard/tests: No such file or directory\n"),
-                ("mkdr /mnt/sdcard/tests", "/mnt/sdcard/tests successfully created"),
-                ("ver", "SUTAgentAndroid Version 1.14")]
-        a = MockAgent(self, start_commands = cmds)
-        mozdevice.DroidSUT("127.0.0.1", port=a.port, logLevel=mozlog.DEBUG)
+        a = MockAgent(self, start_commands=[("ver", "##AGENT-WARNING## No version")])
+        self.assertRaises(mozdevice.DMError,
+                          lambda: mozdevice.DroidSUT("127.0.0.1",
+                                                     port=a.port,
+                                                     logLevel=mozlog.DEBUG))
         a.wait()
 
     def test_timeout_normal(self):
         """Tests DeviceManager timeout, normal case."""
         a = MockAgent(self, commands = [("isdir /mnt/sdcard/tests", "TRUE"),
                                         ("cd /mnt/sdcard/tests", ""),
                                         ("ls", "test.txt"),
                                         ("rm /mnt/sdcard/tests/test.txt",
--- a/testing/mozbase/mozdevice/tests/sut_unpackfile.py
+++ b/testing/mozbase/mozdevice/tests/sut_unpackfile.py
@@ -5,18 +5,17 @@ import mozlog
 import unittest
 from sut import MockAgent
 
 
 class TestUnpack(unittest.TestCase):
 
     def test_unpackFile(self):
 
-        commands = [("isdir /mnt/sdcard/tests", "TRUE"),
-                    ("unzp /data/test/sample.zip /data/test/",
+        commands = [("unzp /data/test/sample.zip /data/test/",
                      "Checksum:          653400271\n"
                      "1 of 1 successfully extracted\n")]
         m = MockAgent(self, commands=commands)
         d = mozdevice.DroidSUT("127.0.0.1", port=m.port, logLevel=mozlog.DEBUG)
         # No error being thrown imples all is well
         self.assertEqual(None, d.unpackFile("/data/test/sample.zip",
                                             "/data/test/"))
 
--- a/testing/remotecppunittests.py
+++ b/testing/remotecppunittests.py
@@ -23,17 +23,17 @@ except ImportError:
 
 log = mozlog.getLogger('remotecppunittests')
 
 class RemoteCPPUnitTests(cppunittests.CPPUnitTests):
     def __init__(self, devmgr, options, progs):
         cppunittests.CPPUnitTests.__init__(self)
         self.options = options
         self.device = devmgr
-        self.remote_test_root = self.device.getDeviceRoot() + "/cppunittests"
+        self.remote_test_root = self.device.deviceRoot + "/cppunittests"
         self.remote_bin_dir = posixpath.join(self.remote_test_root, "b")
         self.remote_tmp_dir = posixpath.join(self.remote_test_root, "tmp")
         self.remote_home_dir = posixpath.join(self.remote_test_root, "h")
         if options.setup:
             self.setup_bin(progs)
 
     def setup_bin(self, progs):
         if not self.device.dirExists(self.remote_test_root):
--- a/testing/xpcshell/remotexpcshelltests.py
+++ b/testing/xpcshell/remotexpcshelltests.py
@@ -5,19 +5,19 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import posixpath
 import sys, os
 import subprocess
 import runxpcshelltests as xpcshell
 import tempfile
 from automationutils import replaceBackSlashes
-from mozdevice import devicemanagerADB, devicemanagerSUT, devicemanager
 from zipfile import ZipFile
 import shutil
+import mozdevice
 import mozfile
 import mozinfo
 
 here = os.path.dirname(os.path.abspath(__file__))
 
 def remoteJoin(path1, path2):
     return posixpath.join(path1, path2)
 
@@ -128,17 +128,17 @@ class RemoteXPCShellTestThread(xpcshell.
 
     def launchProcess(self, cmd, stdout, stderr, env, cwd, timeout=None):
         self.timedout = False
         cmd.insert(1, self.remoteHere)
         outputFile = "xpcshelloutput"
         with open(outputFile, 'w+') as f:
             try:
                 self.shellReturnCode = self.device.shell(cmd, f, timeout=timeout+10)
-            except devicemanager.DMError as e:
+            except mozdevice.DMError as e:
                 if self.timedout:
                     # If the test timed out, there is a good chance the SUTagent also
                     # timed out and failed to return a return code, generating a
                     # DMError. Ignore the DMError to simplify the error report.
                     self.shellReturnCode = None
                     pass
                 else:
                     raise e
@@ -219,17 +219,17 @@ class XPCShellRemote(xpcshell.XPCShellTe
         androidVersion = devmgr.shellCheckOutput(['getprop', 'ro.build.version.sdk'])
         mozinfo.info['android_version'] = androidVersion
 
         self.localLib = options.localLib
         self.localBin = options.localBin
         self.options = options
         self.device = devmgr
         self.pathMapping = []
-        self.remoteTestRoot = self.device.getTestRoot("xpcshell")
+        self.remoteTestRoot = "%s/xpcshell" % self.device.deviceRoot
         # remoteBinDir contains xpcshell and its wrapper script, both of which must
         # be executable. Since +x permissions cannot usually be set on /mnt/sdcard,
         # and the test root may be on /mnt/sdcard, remoteBinDir is set to be on
         # /data/local, always.
         self.remoteBinDir = "/data/local/xpcb"
         # Terse directory names are used here ("c" for the components directory)
         # to minimize the length of the command line used to execute
         # xpcshell on the remote device. adb has a limit to the number
@@ -338,17 +338,17 @@ class XPCShellRemote(xpcshell.XPCShellTe
                 pass
         return None
 
     def setupUtilities(self):
         if (not self.device.dirExists(self.remoteBinDir)):
             # device.mkDir may fail here where shellCheckOutput may succeed -- see bug 817235
             try:
                 self.device.shellCheckOutput(["mkdir", self.remoteBinDir]);
-            except devicemanager.DMError:
+            except mozdevice.DMError:
                 # Might get a permission error; try again as root, if available
                 self.device.shellCheckOutput(["mkdir", self.remoteBinDir], root=True);
                 self.device.shellCheckOutput(["chmod", "777", self.remoteBinDir], root=True);
 
         remotePrefDir = remoteJoin(self.remoteBinDir, "defaults/pref")
         if (self.device.dirExists(self.remoteTmpDir)):
             self.device.removeDir(self.remoteTmpDir)
         self.device.mkDir(self.remoteTmpDir)
@@ -588,26 +588,26 @@ def main():
 
     options = parser.verifyRemoteOptions(options)
 
     if len(args) < 1 and options.manifest is None:
         print >>sys.stderr, """Usage: %s <test dirs>
              or: %s --manifest=test.manifest """ % (sys.argv[0], sys.argv[0])
         sys.exit(1)
 
-    if (options.dm_trans == "adb"):
-        if (options.deviceIP):
-            dm = devicemanagerADB.DeviceManagerADB(options.deviceIP, options.devicePort, packageName=None, deviceRoot=options.remoteTestRoot)
+    if options.dm_trans == "adb":
+        if options.deviceIP:
+            dm = mozdevice.DroidADB(options.deviceIP, options.devicePort, packageName=None, deviceRoot=options.remoteTestRoot)
         else:
-            dm = devicemanagerADB.DeviceManagerADB(packageName=None, deviceRoot=options.remoteTestRoot)
+            dm = mozdevice.DroidADB(packageName=None, deviceRoot=options.remoteTestRoot)
     else:
-        dm = devicemanagerSUT.DeviceManagerSUT(options.deviceIP, options.devicePort, deviceRoot=options.remoteTestRoot)
-        if (options.deviceIP == None):
+        if not options.deviceIP:
             print "Error: you must provide a device IP to connect to via the --device option"
             sys.exit(1)
+        dm = mozdevice.DroidSUT(options.deviceIP, options.devicePort, deviceRoot=options.remoteTestRoot)
 
     if options.interactive and not options.testPath:
         print >>sys.stderr, "Error: You must specify a test filename in interactive mode!"
         sys.exit(1)
 
     xpcsh = XPCShellRemote(dm, options, args)
 
     # we don't run concurrent tests on mobile
--- a/testing/xpcshell/runtestsb2g.py
+++ b/testing/xpcshell/runtestsb2g.py
@@ -188,17 +188,17 @@ def run_remote_xpcshell(parser, options,
         kwargs = {'adbPath': options.adb_path}
         if options.deviceIP:
             kwargs['host'] = options.deviceIP
             kwargs['port'] = options.devicePort
         kwargs['deviceRoot'] = options.remoteTestRoot
         dm = devicemanagerADB.DeviceManagerADB(**kwargs)
 
     if not options.remoteTestRoot:
-        options.remoteTestRoot = dm.getDeviceRoot()
+        options.remoteTestRoot = dm.deviceRoot
     xpcsh = B2GXPCShellRemote(dm, options, args)
 
     # we don't run concurrent tests on mobile
     options.sequential = True
 
     try:
         if not xpcsh.runTests(xpcshell='xpcshell', testdirs=args[0:],
                                  testClass=B2GXPCShellTestThread,