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()