testing/remotecppunittests.py

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/testing/remotecppunittests.py	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,240 @@
     1.4 +#!/usr/bin/env python
     1.5 +#
     1.6 +# This Source Code Form is subject to the terms of the Mozilla Public
     1.7 +# License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 +# file, You can obtain one at http://mozilla.org/MPL/2.0/.
     1.9 +
    1.10 +import os, sys
    1.11 +import subprocess
    1.12 +import tempfile
    1.13 +from zipfile import ZipFile
    1.14 +import runcppunittests as cppunittests
    1.15 +import mozcrash, mozlog
    1.16 +import mozfile
    1.17 +import StringIO
    1.18 +import posixpath
    1.19 +from mozdevice import devicemanager, devicemanagerADB, devicemanagerSUT
    1.20 +
    1.21 +try:
    1.22 +    from mozbuild.base import MozbuildObject
    1.23 +    build_obj = MozbuildObject.from_environment()
    1.24 +except ImportError:
    1.25 +    build_obj = None
    1.26 +
    1.27 +log = mozlog.getLogger('remotecppunittests')
    1.28 +
    1.29 +class RemoteCPPUnitTests(cppunittests.CPPUnitTests):
    1.30 +    def __init__(self, devmgr, options, progs):
    1.31 +        cppunittests.CPPUnitTests.__init__(self)
    1.32 +        self.options = options
    1.33 +        self.device = devmgr
    1.34 +        self.remote_test_root = self.device.getDeviceRoot() + "/cppunittests"
    1.35 +        self.remote_bin_dir = posixpath.join(self.remote_test_root, "b")
    1.36 +        self.remote_tmp_dir = posixpath.join(self.remote_test_root, "tmp")
    1.37 +        self.remote_home_dir = posixpath.join(self.remote_test_root, "h")
    1.38 +        if options.setup:
    1.39 +            self.setup_bin(progs)
    1.40 +
    1.41 +    def setup_bin(self, progs):
    1.42 +        if not self.device.dirExists(self.remote_test_root):
    1.43 +            self.device.mkDir(self.remote_test_root)
    1.44 +        if self.device.dirExists(self.remote_tmp_dir):
    1.45 +            self.device.removeDir(self.remote_tmp_dir)
    1.46 +        self.device.mkDir(self.remote_tmp_dir)
    1.47 +        if self.device.dirExists(self.remote_bin_dir):
    1.48 +            self.device.removeDir(self.remote_bin_dir)
    1.49 +        self.device.mkDir(self.remote_bin_dir)
    1.50 +        if self.device.dirExists(self.remote_home_dir):
    1.51 +            self.device.removeDir(self.remote_home_dir)
    1.52 +        self.device.mkDir(self.remote_home_dir)
    1.53 +        self.push_libs()
    1.54 +        self.push_progs(progs)
    1.55 +        self.device.chmodDir(self.remote_bin_dir)
    1.56 +
    1.57 +    def push_libs(self):
    1.58 +        if self.options.local_apk:
    1.59 +            with mozfile.TemporaryDirectory() as tmpdir:
    1.60 +                apk_contents = ZipFile(self.options.local_apk)
    1.61 +                szip = os.path.join(self.options.local_bin, '..', 'host', 'bin', 'szip')
    1.62 +                if not os.path.exists(szip):
    1.63 +                    # Tinderbox builds must run szip from the test package
    1.64 +                    szip = os.path.join(self.options.local_bin, 'host', 'szip')
    1.65 +                if not os.path.exists(szip):
    1.66 +                    # If the test package doesn't contain szip, it means files
    1.67 +                    # are not szipped in the test package.
    1.68 +                    szip = None
    1.69 +
    1.70 +                for info in apk_contents.infolist():
    1.71 +                    if info.filename.endswith(".so"):
    1.72 +                        print >> sys.stderr, "Pushing %s.." % info.filename
    1.73 +                        remote_file = posixpath.join(self.remote_bin_dir, os.path.basename(info.filename))
    1.74 +                        apk_contents.extract(info, tmpdir)
    1.75 +                        file = os.path.join(tmpdir, info.filename)
    1.76 +                        if szip:
    1.77 +                            out = subprocess.check_output([szip, '-d', file], stderr=subprocess.STDOUT)
    1.78 +                        self.device.pushFile(os.path.join(tmpdir, info.filename), remote_file)
    1.79 +            return
    1.80 +
    1.81 +        for file in os.listdir(self.options.local_lib):
    1.82 +            if file.endswith(".so"):
    1.83 +                print >> sys.stderr, "Pushing %s.." % file
    1.84 +                remote_file = posixpath.join(self.remote_bin_dir, file)
    1.85 +                self.device.pushFile(os.path.join(self.options.local_lib, file), remote_file)
    1.86 +        # Additional libraries may be found in a sub-directory such as "lib/armeabi-v7a"
    1.87 +        local_arm_lib = os.path.join(self.options.local_lib, "lib")
    1.88 +        if os.path.isdir(local_arm_lib):
    1.89 +            for root, dirs, files in os.walk(local_arm_lib):
    1.90 +                for file in files:
    1.91 +                    if (file.endswith(".so")):
    1.92 +                        remote_file = posixpath.join(self.remote_bin_dir, file)
    1.93 +                        self.device.pushFile(os.path.join(root, file), remote_file)
    1.94 +
    1.95 +    def push_progs(self, progs):
    1.96 +        for local_file in progs:
    1.97 +            remote_file = posixpath.join(self.remote_bin_dir, os.path.basename(local_file))
    1.98 +            self.device.pushFile(local_file, remote_file)
    1.99 +
   1.100 +    def build_environment(self):
   1.101 +        env = self.build_core_environment()
   1.102 +        env['LD_LIBRARY_PATH'] = self.remote_bin_dir
   1.103 +        env["TMPDIR"]=self.remote_tmp_dir
   1.104 +        env["HOME"]=self.remote_home_dir
   1.105 +        env["MOZILLA_FIVE_HOME"] = self.remote_home_dir
   1.106 +        env["MOZ_XRE_DIR"] = self.remote_bin_dir
   1.107 +        if self.options.add_env:
   1.108 +            for envdef in self.options.add_env:
   1.109 +                envdef_parts = envdef.split("=", 1)
   1.110 +                if len(envdef_parts) == 2:
   1.111 +                    env[envdef_parts[0]] = envdef_parts[1]
   1.112 +                elif len(envdef_parts) == 1:
   1.113 +                    env[envdef_parts[0]] = ""
   1.114 +                else:
   1.115 +                    print >> sys.stderr, "warning: invalid --addEnv option skipped: "+envdef
   1.116 +
   1.117 +        return env
   1.118 +
   1.119 +    def run_one_test(self, prog, env, symbols_path=None):
   1.120 +        """
   1.121 +        Run a single C++ unit test program remotely.
   1.122 +
   1.123 +        Arguments:
   1.124 +        * prog: The path to the test program to run.
   1.125 +        * env: The environment to use for running the program.
   1.126 +        * symbols_path: A path to a directory containing Breakpad-formatted
   1.127 +                        symbol files for producing stack traces on crash.
   1.128 +
   1.129 +        Return True if the program exits with a zero status, False otherwise.
   1.130 +        """
   1.131 +        basename = os.path.basename(prog)
   1.132 +        remote_bin = posixpath.join(self.remote_bin_dir, basename)
   1.133 +        log.info("Running test %s", basename)
   1.134 +        buf = StringIO.StringIO()
   1.135 +        returncode = self.device.shell([remote_bin], buf, env=env, cwd=self.remote_home_dir,
   1.136 +                                       timeout=cppunittests.CPPUnitTests.TEST_PROC_TIMEOUT)
   1.137 +        print >> sys.stdout, buf.getvalue()
   1.138 +        with mozfile.TemporaryDirectory() as tempdir:
   1.139 +            self.device.getDirectory(self.remote_home_dir, tempdir)
   1.140 +            if mozcrash.check_for_crashes(tempdir, symbols_path,
   1.141 +                                          test_name=basename):
   1.142 +                log.testFail("%s | test crashed", basename)
   1.143 +                return False
   1.144 +        result = returncode == 0
   1.145 +        if not result:
   1.146 +            log.testFail("%s | test failed with return code %s",
   1.147 +                         basename, returncode)
   1.148 +        return result
   1.149 +
   1.150 +class RemoteCPPUnittestOptions(cppunittests.CPPUnittestOptions):
   1.151 +    def __init__(self):
   1.152 +        cppunittests.CPPUnittestOptions.__init__(self)
   1.153 +        defaults = {}
   1.154 +
   1.155 +        self.add_option("--deviceIP", action="store",
   1.156 +                        type = "string", dest = "device_ip",
   1.157 +                        help = "ip address of remote device to test")
   1.158 +        defaults["device_ip"] = None
   1.159 +
   1.160 +        self.add_option("--devicePort", action="store",
   1.161 +                        type = "string", dest = "device_port",
   1.162 +                        help = "port of remote device to test")
   1.163 +        defaults["device_port"] = 20701
   1.164 +
   1.165 +        self.add_option("--dm_trans", action="store",
   1.166 +                        type = "string", dest = "dm_trans",
   1.167 +                        help = "the transport to use to communicate with device: [adb|sut]; default=sut")
   1.168 +        defaults["dm_trans"] = "sut"
   1.169 +
   1.170 +        self.add_option("--noSetup", action="store_false",
   1.171 +                        dest = "setup",
   1.172 +                        help = "do not copy any files to device (to be used only if device is already setup)")
   1.173 +        defaults["setup"] = True
   1.174 +
   1.175 +        self.add_option("--localLib", action="store",
   1.176 +                        type = "string", dest = "local_lib",
   1.177 +                        help = "location of libraries to push -- preferably stripped")
   1.178 +        defaults["local_lib"] = None
   1.179 +
   1.180 +        self.add_option("--apk", action="store",
   1.181 +                        type = "string", dest = "local_apk",
   1.182 +                        help = "local path to Fennec APK")
   1.183 +        defaults["local_apk"] = None
   1.184 +
   1.185 +        self.add_option("--localBinDir", action="store",
   1.186 +                        type = "string", dest = "local_bin",
   1.187 +                        help = "local path to bin directory")
   1.188 +        defaults["local_bin"] = build_obj.bindir if build_obj is not None else None
   1.189 +
   1.190 +        self.add_option("--remoteTestRoot", action = "store",
   1.191 +                    type = "string", dest = "remote_test_root",
   1.192 +                    help = "remote directory to use as test root (eg. /data/local/tests)")
   1.193 +        # /data/local/tests is used because it is usually not possible to set +x permissions
   1.194 +        # on binaries on /mnt/sdcard
   1.195 +        defaults["remote_test_root"] = "/data/local/tests"
   1.196 +
   1.197 +        self.add_option("--addEnv", action = "append",
   1.198 +                    type = "string", dest = "add_env",
   1.199 +                    help = "additional remote environment variable definitions (eg. --addEnv \"somevar=something\")")
   1.200 +        defaults["add_env"] = None
   1.201 +
   1.202 +        self.set_defaults(**defaults)
   1.203 +
   1.204 +def main():
   1.205 +    parser = RemoteCPPUnittestOptions()
   1.206 +    options, args = parser.parse_args()
   1.207 +    if not args:
   1.208 +        print >>sys.stderr, """Usage: %s <test binary> [<test binary>...]""" % sys.argv[0]
   1.209 +        sys.exit(1)
   1.210 +    if options.local_lib is None and options.local_apk is None:
   1.211 +        print >>sys.stderr, """Error: --localLib or --apk is required"""
   1.212 +        sys.exit(1)
   1.213 +    if options.local_lib is not None and not os.path.isdir(options.local_lib):
   1.214 +        print >>sys.stderr, """Error: --localLib directory %s not found""" % options.local_lib
   1.215 +        sys.exit(1)
   1.216 +    if options.local_apk is not None and not os.path.isfile(options.local_apk):
   1.217 +        print >>sys.stderr, """Error: --apk file %s not found""" % options.local_apk
   1.218 +        sys.exit(1)
   1.219 +    if not options.xre_path:
   1.220 +        print >>sys.stderr, """Error: --xre-path is required"""
   1.221 +        sys.exit(1)
   1.222 +    if options.dm_trans == "adb":
   1.223 +        if options.device_ip:
   1.224 +            dm = devicemanagerADB.DeviceManagerADB(options.device_ip, options.device_port, packageName=None, deviceRoot=options.remote_test_root)
   1.225 +        else:
   1.226 +            dm = devicemanagerADB.DeviceManagerADB(packageName=None, deviceRoot=options.remote_test_root)
   1.227 +    else:
   1.228 +        dm = devicemanagerSUT.DeviceManagerSUT(options.device_ip, options.device_port, deviceRoot=options.remote_test_root)
   1.229 +        if not options.device_ip:
   1.230 +            print "Error: you must provide a device IP to connect to via the --deviceIP option"
   1.231 +            sys.exit(1)
   1.232 +    options.xre_path = os.path.abspath(options.xre_path)
   1.233 +    progs = cppunittests.extract_unittests_from_args(args, options.manifest_file)
   1.234 +    tester = RemoteCPPUnitTests(dm, options, progs)
   1.235 +    try:
   1.236 +        result = tester.run_tests(progs, options.xre_path, options.symbols_path)
   1.237 +    except Exception, e:
   1.238 +        log.error(str(e))
   1.239 +        result = False
   1.240 +    sys.exit(0 if result else 1)
   1.241 +
   1.242 +if __name__ == '__main__':
   1.243 +    main()

mercurial