Bug 1440714 - Convert Android cppunit test harness to adb.py; r=bc
authorGeoff Brown <gbrown@mozilla.com>
Fri, 16 Mar 2018 09:37:48 -0600
changeset 408605 0d0760485016d2fc3449a5366d364fe9e589f818
parent 408604 d8e2178f6adcc4e5a4ecaba7e171e5461a866fa5
child 408606 143c179449ccd09ec8bfd66db99333d7fd561911
push id33649
push userbtara@mozilla.com
push dateSat, 17 Mar 2018 10:29:43 +0000
treeherdermozilla-central@97160a734959 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbc
bugs1440714
milestone61.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 1440714 - Convert Android cppunit test harness to adb.py; r=bc
testing/mozbase/mozdevice/mozdevice/__init__.py
testing/mozbase/mozdevice/mozdevice/adb.py
testing/remotecppunittests.py
--- a/testing/mozbase/mozdevice/mozdevice/__init__.py
+++ b/testing/mozbase/mozdevice/mozdevice/__init__.py
@@ -1,17 +1,17 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # 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 __future__ import absolute_import
 
-from .adb import ADBError, ADBRootError, ADBTimeoutError
+from .adb import ADBError, ADBProcessError, ADBRootError, ADBTimeoutError
 from .adb import ADBProcess, ADBCommand, ADBHost, ADBDevice
 from .adb_android import ADBAndroid
 from .adb_b2g import ADBB2G
 from .devicemanager import DeviceManager, DMError
 from .devicemanagerADB import DeviceManagerADB
 from .droid import DroidADB
 
-__all__ = ['ADBError', 'ADBRootError', 'ADBTimeoutError', 'ADBProcess', 'ADBCommand', 'ADBHost',
-           'ADBDevice', 'ADBAndroid', 'ADBB2G', 'DeviceManager', 'DMError',
-           'DeviceManagerADB', 'DroidADB']
+__all__ = ['ADBError', 'ADBProcessError', 'ADBRootError', 'ADBTimeoutError',
+           'ADBProcess', 'ADBCommand', 'ADBHost', 'ADBDevice', 'ADBAndroid', 'ADBB2G',
+           'DeviceManager', 'DMError', 'DeviceManagerADB', 'DroidADB']
--- a/testing/mozbase/mozdevice/mozdevice/adb.py
+++ b/testing/mozbase/mozdevice/mozdevice/adb.py
@@ -57,16 +57,25 @@ class ADBError(Exception):
     """ADBError is raised in situations where a command executed on a
     device either exited with a non-zero exitcode or when an
     unexpected error condition has occurred. Generally, ADBErrors can
     be handled and the device can continue to be used.
     """
     pass
 
 
+class ADBProcessError(ADBError):
+    """ADBProcessError is raised when an associated ADBProcess is
+    available and relevant.
+    """
+    def __init__(self, adb_process):
+        ADBError.__init__(self, str(adb_process))
+        self.adb_process = adb_process
+
+
 class ADBListDevicesError(ADBError):
     """ADBListDevicesError is raised when errors are found listing the
     devices, typically not any permissions.
 
     The devices information is stocked with the *devices* member.
     """
 
     def __init__(self, msg, devices):
@@ -275,17 +284,17 @@ class ADBCommand(object):
             # since ADBDevice will redefine command and call its
             # own version otherwise.
             adb_process = ADBCommand.command(self, cmds,
                                              device_serial=device_serial,
                                              timeout=timeout)
             if adb_process.timedout:
                 raise ADBTimeoutError("%s" % adb_process)
             elif adb_process.exitcode:
-                raise ADBError("%s" % adb_process)
+                raise ADBProcessError(adb_process)
             output = adb_process.stdout_file.read().rstrip()
             if self._verbose:
                 self._logger.debug('command_output: %s, '
                                    'timeout: %s, '
                                    'timedout: %s, '
                                    'exitcode: %s, output: %s' %
                                    (' '.join(adb_process.args),
                                     timeout,
@@ -1133,17 +1142,17 @@ class ADBDevice(ADBCommand):
         """
         adb_process = None
         try:
             adb_process = self.shell(cmd, env=env, cwd=cwd,
                                      timeout=timeout, root=root)
             if adb_process.timedout:
                 raise ADBTimeoutError("%s" % adb_process)
             elif adb_process.exitcode:
-                raise ADBError("%s" % adb_process)
+                raise ADBProcessError(adb_process)
             output = adb_process.stdout_file.read().rstrip()
             if self._verbose:
                 self._logger.debug('shell_output: %s, '
                                    'timeout: %s, '
                                    'root: %s, '
                                    'timedout: %s, '
                                    'exitcode: %s, '
                                    'output: %s' %
@@ -1911,17 +1920,17 @@ class ADBDevice(ADBCommand):
                  * ADBError
         """
         adb_process = None
         try:
             adb_process = self.shell("ps", timeout=timeout)
             if adb_process.timedout:
                 raise ADBTimeoutError("%s" % adb_process)
             elif adb_process.exitcode:
-                raise ADBError("%s" % adb_process)
+                raise ADBProcessError(adb_process)
             # first line is the headers
             header = adb_process.stdout_file.readline()
             pid_i = -1
             user_i = -1
             els = header.split()
             for i in range(len(els)):
                 item = els[i].lower()
                 if item == 'user':
--- a/testing/remotecppunittests.py
+++ b/testing/remotecppunittests.py
@@ -8,55 +8,48 @@ import os
 import sys
 import subprocess
 from zipfile import ZipFile
 import runcppunittests as cppunittests
 import mozcrash
 import mozfile
 import mozinfo
 import mozlog
-import StringIO
 import posixpath
-from mozdevice import devicemanagerADB
+from mozdevice import ADBAndroid, ADBProcessError
 
 try:
     from mozbuild.base import MozbuildObject
     build_obj = MozbuildObject.from_environment()
 except ImportError:
     build_obj = None
 
 
 class RemoteCPPUnitTests(cppunittests.CPPUnitTests):
 
-    def __init__(self, devmgr, options, progs):
+    def __init__(self, options, progs):
         cppunittests.CPPUnitTests.__init__(self)
         self.options = options
-        self.device = devmgr
-        self.remote_test_root = self.device.deviceRoot + "/cppunittests"
+        self.device = ADBAndroid(adb=options.adb_path,
+                                 device=options.device_serial,
+                                 test_root=options.remote_test_root)
+        self.remote_test_root = posixpath.join(self.device.test_root, "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):
-            self.device.mkDir(self.remote_test_root)
-        if self.device.dirExists(self.remote_tmp_dir):
-            self.device.removeDir(self.remote_tmp_dir)
-        self.device.mkDir(self.remote_tmp_dir)
-        if self.device.dirExists(self.remote_bin_dir):
-            self.device.removeDir(self.remote_bin_dir)
-        self.device.mkDir(self.remote_bin_dir)
-        if self.device.dirExists(self.remote_home_dir):
-            self.device.removeDir(self.remote_home_dir)
-        self.device.mkDir(self.remote_home_dir)
+        self.device.rm(self.remote_test_root, force=True, recursive=True)
+        self.device.mkdir(self.remote_home_dir, parents=True)
+        self.device.mkdir(self.remote_tmp_dir)
         self.push_libs()
         self.push_progs(progs)
-        self.device.chmodDir(self.remote_bin_dir)
+        self.device.chmod(self.remote_bin_dir, recursive=True)
 
     def push_libs(self):
         if self.options.local_apk:
             with mozfile.TemporaryDirectory() as tmpdir:
                 apk_contents = ZipFile(self.options.local_apk)
 
                 for info in apk_contents.infolist():
                     if info.filename.endswith(".so"):
@@ -68,44 +61,44 @@ class RemoteCPPUnitTests(cppunittests.CP
                         with open(local_file) as f:
                             # Decompress xz-compressed file.
                             if f.read(5)[1:] == '7zXZ':
                                 cmd = [
                                     'xz', '-df', '--suffix', '.so', local_file]
                                 subprocess.check_output(cmd)
                                 # xz strips the ".so" file suffix.
                                 os.rename(local_file[:-3], local_file)
-                        self.device.pushFile(local_file, remote_file)
+                        self.device.push(local_file, remote_file)
 
         elif self.options.local_lib:
             for file in os.listdir(self.options.local_lib):
                 if file.endswith(".so"):
                     print >> sys.stderr, "Pushing %s.." % file
                     remote_file = posixpath.join(self.remote_bin_dir, file)
                     local_file = os.path.join(self.options.local_lib, file)
-                    self.device.pushFile(local_file, remote_file)
+                    self.device.push(local_file, remote_file)
             # Additional libraries may be found in a sub-directory such as
             # "lib/armeabi-v7a"
             for subdir in ["assets", "lib"]:
                 local_arm_lib = os.path.join(self.options.local_lib, subdir)
                 if os.path.isdir(local_arm_lib):
                     for root, dirs, files in os.walk(local_arm_lib):
                         for file in files:
                             if (file.endswith(".so")):
                                 print >> sys.stderr, "Pushing %s.." % file
                                 remote_file = posixpath.join(
                                     self.remote_bin_dir, file)
                                 local_file = os.path.join(root, file)
-                                self.device.pushFile(local_file, remote_file)
+                                self.device.push(local_file, remote_file)
 
     def push_progs(self, progs):
         for local_file in progs:
             remote_file = posixpath.join(
                 self.remote_bin_dir, os.path.basename(local_file))
-            self.device.pushFile(local_file, remote_file)
+            self.device.push(local_file, remote_file)
 
     def build_environment(self):
         env = self.build_core_environment()
         env['LD_LIBRARY_PATH'] = self.remote_bin_dir
         env["TMPDIR"] = self.remote_tmp_dir
         env["HOME"] = self.remote_home_dir
         env["MOZ_XRE_DIR"] = self.remote_bin_dir
         if self.options.add_env:
@@ -133,26 +126,32 @@ class RemoteCPPUnitTests(cppunittests.CP
                         symbol files for producing stack traces on crash.
         * timeout_factor: An optional test-specific timeout multiplier.
 
         Return True if the program exits with a zero status, False otherwise.
         """
         basename = os.path.basename(prog)
         remote_bin = posixpath.join(self.remote_bin_dir, basename)
         self.log.test_start(basename)
-        buf = StringIO.StringIO()
         test_timeout = cppunittests.CPPUnitTests.TEST_PROC_TIMEOUT * \
             timeout_factor
-        returncode = self.device.shell(
-            [remote_bin], buf, env=env, cwd=self.remote_home_dir,
-            timeout=test_timeout)
-        self.log.process_output(basename, "\n%s" % buf.getvalue(),
+
+        try:
+            output = self.device.shell_output(remote_bin, env=env,
+                                              cwd=self.remote_home_dir,
+                                              timeout=test_timeout)
+            returncode = 0
+        except ADBProcessError as e:
+            output = e.adb_process.stdout
+            returncode = e.adb_process.exitcode
+
+        self.log.process_output(basename, "\n%s" % output,
                                 command=[remote_bin])
         with mozfile.TemporaryDirectory() as tempdir:
-            self.device.getDirectory(self.remote_home_dir, tempdir)
+            self.device.pull(self.remote_home_dir, tempdir)
             if mozcrash.check_for_crashes(tempdir, symbols_path,
                                           test_name=basename):
                 self.log.test_end(basename, status='CRASH', expected='PASS')
                 return False
         result = returncode == 0
         if not result:
             self.log.test_end(basename, status='FAIL', expected='PASS',
                               message=("test failed with return code %s" %
@@ -163,30 +162,25 @@ class RemoteCPPUnitTests(cppunittests.CP
 
 
 class RemoteCPPUnittestOptions(cppunittests.CPPUnittestOptions):
 
     def __init__(self):
         cppunittests.CPPUnittestOptions.__init__(self)
         defaults = {}
 
-        self.add_option("--deviceIP", action="store",
-                        type="string", dest="device_ip",
-                        help="ip address of remote device to test")
-        defaults["device_ip"] = None
-
-        self.add_option("--devicePort", action="store",
-                        type="string", dest="device_port",
-                        help="port of remote device to test")
-        defaults["device_port"] = 20701
+        self.add_option("--deviceSerial", action="store",
+                        type="string", dest="device_serial",
+                        help="serial ID of device")
+        defaults["device_serial"] = None
 
         self.add_option("--adbPath", action="store",
                         type="string", dest="adb_path",
                         help="Path to adb")
-        defaults["adb_path"] = None
+        defaults["adb_path"] = "adb"
 
         self.add_option("--noSetup", action="store_false",
                         dest="setup",
                         help="do not copy any files to device (to be used only if "
                         "device is already setup)")
         defaults["setup"] = True
 
         self.add_option("--localLib", action="store",
@@ -207,71 +201,33 @@ class RemoteCPPUnittestOptions(cppunitte
 
         self.add_option("--remoteTestRoot", action="store",
                         type="string", dest="remote_test_root",
                         help="remote directory to use as test root (eg. /data/local/tests)")
         # /data/local/tests is used because it is usually not possible to set +x permissions
         # on binaries on /mnt/sdcard
         defaults["remote_test_root"] = "/data/local/tests"
 
-        self.add_option("--with-b2g-emulator", action="store",
-                        type="string", dest="with_b2g_emulator",
-                        help="Start B2G Emulator (specify path to b2g home)")
-        self.add_option("--emulator", default="arm", choices=["x86", "arm"],
-                        help="Architecture of emulator to use: x86 or arm")
         self.add_option("--addEnv", action="append",
                         type="string", dest="add_env",
                         help="additional remote environment variable definitions "
                         "(eg. --addEnv \"somevar=something\")")
         defaults["add_env"] = None
 
         self.set_defaults(**defaults)
 
 
 def run_test_harness(options, args):
-    if options.with_b2g_emulator:
-        from mozrunner import B2GEmulatorRunner
-        runner = B2GEmulatorRunner(
-            arch=options.emulator, b2g_home=options.with_b2g_emulator)
-        runner.start()
-        # because we just started the emulator, we need more than the
-        # default number of retries here.
-        retryLimit = 50
-    else:
-        retryLimit = 5
-    try:
-        dm_args = {'deviceRoot': options.remote_test_root}
-        dm_args['retryLimit'] = retryLimit
-        if options.device_ip:
-            dm_args['host'] = options.device_ip
-            dm_args['port'] = options.device_port
-        if options.adb_path:
-            dm_args['adbPath'] = options.adb_path
-        if options.log_tbpl_level == 'debug' or options.log_mach_level == 'debug':
-            dm_args['logLevel'] = logging.DEBUG # noqa python 2 / 3
-        dm = devicemanagerADB.DeviceManagerADB(**dm_args)
-    except BaseException:
-        if options.with_b2g_emulator:
-            runner.cleanup()
-            runner.wait()
-        raise
-
     options.xre_path = os.path.abspath(options.xre_path)
     cppunittests.update_mozinfo()
     progs = cppunittests.extract_unittests_from_args(args,
                                                      mozinfo.info,
                                                      options.manifest_path)
-    tester = RemoteCPPUnitTests(dm, options, [item[0] for item in progs])
-    try:
-        result = tester.run_tests(
-            progs, options.xre_path, options.symbols_path)
-    finally:
-        if options.with_b2g_emulator:
-            runner.cleanup()
-            runner.wait()
+    tester = RemoteCPPUnitTests(options, [item[0] for item in progs])
+    result = tester.run_tests(progs, options.xre_path, options.symbols_path)
     return result
 
 
 def main():
     parser = RemoteCPPUnittestOptions()
     mozlog.commandline.add_logging_group(parser)
     options, args = parser.parse_args()
     if not args: