michael@0: #!/usr/bin/env python michael@0: # michael@0: # This Source Code Form is subject to the terms of the Mozilla Public michael@0: # License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: # file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: michael@0: import os, sys michael@0: import subprocess michael@0: import tempfile michael@0: from zipfile import ZipFile michael@0: import runcppunittests as cppunittests michael@0: import mozcrash, mozlog michael@0: import mozfile michael@0: import StringIO michael@0: import posixpath michael@0: from mozdevice import devicemanager, devicemanagerADB, devicemanagerSUT michael@0: michael@0: try: michael@0: from mozbuild.base import MozbuildObject michael@0: build_obj = MozbuildObject.from_environment() michael@0: except ImportError: michael@0: build_obj = None michael@0: michael@0: log = mozlog.getLogger('remotecppunittests') michael@0: michael@0: class RemoteCPPUnitTests(cppunittests.CPPUnitTests): michael@0: def __init__(self, devmgr, options, progs): michael@0: cppunittests.CPPUnitTests.__init__(self) michael@0: self.options = options michael@0: self.device = devmgr michael@0: self.remote_test_root = self.device.getDeviceRoot() + "/cppunittests" michael@0: self.remote_bin_dir = posixpath.join(self.remote_test_root, "b") michael@0: self.remote_tmp_dir = posixpath.join(self.remote_test_root, "tmp") michael@0: self.remote_home_dir = posixpath.join(self.remote_test_root, "h") michael@0: if options.setup: michael@0: self.setup_bin(progs) michael@0: michael@0: def setup_bin(self, progs): michael@0: if not self.device.dirExists(self.remote_test_root): michael@0: self.device.mkDir(self.remote_test_root) michael@0: if self.device.dirExists(self.remote_tmp_dir): michael@0: self.device.removeDir(self.remote_tmp_dir) michael@0: self.device.mkDir(self.remote_tmp_dir) michael@0: if self.device.dirExists(self.remote_bin_dir): michael@0: self.device.removeDir(self.remote_bin_dir) michael@0: self.device.mkDir(self.remote_bin_dir) michael@0: if self.device.dirExists(self.remote_home_dir): michael@0: self.device.removeDir(self.remote_home_dir) michael@0: self.device.mkDir(self.remote_home_dir) michael@0: self.push_libs() michael@0: self.push_progs(progs) michael@0: self.device.chmodDir(self.remote_bin_dir) michael@0: michael@0: def push_libs(self): michael@0: if self.options.local_apk: michael@0: with mozfile.TemporaryDirectory() as tmpdir: michael@0: apk_contents = ZipFile(self.options.local_apk) michael@0: szip = os.path.join(self.options.local_bin, '..', 'host', 'bin', 'szip') michael@0: if not os.path.exists(szip): michael@0: # Tinderbox builds must run szip from the test package michael@0: szip = os.path.join(self.options.local_bin, 'host', 'szip') michael@0: if not os.path.exists(szip): michael@0: # If the test package doesn't contain szip, it means files michael@0: # are not szipped in the test package. michael@0: szip = None michael@0: michael@0: for info in apk_contents.infolist(): michael@0: if info.filename.endswith(".so"): michael@0: print >> sys.stderr, "Pushing %s.." % info.filename michael@0: remote_file = posixpath.join(self.remote_bin_dir, os.path.basename(info.filename)) michael@0: apk_contents.extract(info, tmpdir) michael@0: file = os.path.join(tmpdir, info.filename) michael@0: if szip: michael@0: out = subprocess.check_output([szip, '-d', file], stderr=subprocess.STDOUT) michael@0: self.device.pushFile(os.path.join(tmpdir, info.filename), remote_file) michael@0: return michael@0: michael@0: for file in os.listdir(self.options.local_lib): michael@0: if file.endswith(".so"): michael@0: print >> sys.stderr, "Pushing %s.." % file michael@0: remote_file = posixpath.join(self.remote_bin_dir, file) michael@0: self.device.pushFile(os.path.join(self.options.local_lib, file), remote_file) michael@0: # Additional libraries may be found in a sub-directory such as "lib/armeabi-v7a" michael@0: local_arm_lib = os.path.join(self.options.local_lib, "lib") michael@0: if os.path.isdir(local_arm_lib): michael@0: for root, dirs, files in os.walk(local_arm_lib): michael@0: for file in files: michael@0: if (file.endswith(".so")): michael@0: remote_file = posixpath.join(self.remote_bin_dir, file) michael@0: self.device.pushFile(os.path.join(root, file), remote_file) michael@0: michael@0: def push_progs(self, progs): michael@0: for local_file in progs: michael@0: remote_file = posixpath.join(self.remote_bin_dir, os.path.basename(local_file)) michael@0: self.device.pushFile(local_file, remote_file) michael@0: michael@0: def build_environment(self): michael@0: env = self.build_core_environment() michael@0: env['LD_LIBRARY_PATH'] = self.remote_bin_dir michael@0: env["TMPDIR"]=self.remote_tmp_dir michael@0: env["HOME"]=self.remote_home_dir michael@0: env["MOZILLA_FIVE_HOME"] = self.remote_home_dir michael@0: env["MOZ_XRE_DIR"] = self.remote_bin_dir michael@0: if self.options.add_env: michael@0: for envdef in self.options.add_env: michael@0: envdef_parts = envdef.split("=", 1) michael@0: if len(envdef_parts) == 2: michael@0: env[envdef_parts[0]] = envdef_parts[1] michael@0: elif len(envdef_parts) == 1: michael@0: env[envdef_parts[0]] = "" michael@0: else: michael@0: print >> sys.stderr, "warning: invalid --addEnv option skipped: "+envdef michael@0: michael@0: return env michael@0: michael@0: def run_one_test(self, prog, env, symbols_path=None): michael@0: """ michael@0: Run a single C++ unit test program remotely. michael@0: michael@0: Arguments: michael@0: * prog: The path to the test program to run. michael@0: * env: The environment to use for running the program. michael@0: * symbols_path: A path to a directory containing Breakpad-formatted michael@0: symbol files for producing stack traces on crash. michael@0: michael@0: Return True if the program exits with a zero status, False otherwise. michael@0: """ michael@0: basename = os.path.basename(prog) michael@0: remote_bin = posixpath.join(self.remote_bin_dir, basename) michael@0: log.info("Running test %s", basename) michael@0: buf = StringIO.StringIO() michael@0: returncode = self.device.shell([remote_bin], buf, env=env, cwd=self.remote_home_dir, michael@0: timeout=cppunittests.CPPUnitTests.TEST_PROC_TIMEOUT) michael@0: print >> sys.stdout, buf.getvalue() michael@0: with mozfile.TemporaryDirectory() as tempdir: michael@0: self.device.getDirectory(self.remote_home_dir, tempdir) michael@0: if mozcrash.check_for_crashes(tempdir, symbols_path, michael@0: test_name=basename): michael@0: log.testFail("%s | test crashed", basename) michael@0: return False michael@0: result = returncode == 0 michael@0: if not result: michael@0: log.testFail("%s | test failed with return code %s", michael@0: basename, returncode) michael@0: return result michael@0: michael@0: class RemoteCPPUnittestOptions(cppunittests.CPPUnittestOptions): michael@0: def __init__(self): michael@0: cppunittests.CPPUnittestOptions.__init__(self) michael@0: defaults = {} michael@0: michael@0: self.add_option("--deviceIP", action="store", michael@0: type = "string", dest = "device_ip", michael@0: help = "ip address of remote device to test") michael@0: defaults["device_ip"] = None michael@0: michael@0: self.add_option("--devicePort", action="store", michael@0: type = "string", dest = "device_port", michael@0: help = "port of remote device to test") michael@0: defaults["device_port"] = 20701 michael@0: michael@0: self.add_option("--dm_trans", action="store", michael@0: type = "string", dest = "dm_trans", michael@0: help = "the transport to use to communicate with device: [adb|sut]; default=sut") michael@0: defaults["dm_trans"] = "sut" michael@0: michael@0: self.add_option("--noSetup", action="store_false", michael@0: dest = "setup", michael@0: help = "do not copy any files to device (to be used only if device is already setup)") michael@0: defaults["setup"] = True michael@0: michael@0: self.add_option("--localLib", action="store", michael@0: type = "string", dest = "local_lib", michael@0: help = "location of libraries to push -- preferably stripped") michael@0: defaults["local_lib"] = None michael@0: michael@0: self.add_option("--apk", action="store", michael@0: type = "string", dest = "local_apk", michael@0: help = "local path to Fennec APK") michael@0: defaults["local_apk"] = None michael@0: michael@0: self.add_option("--localBinDir", action="store", michael@0: type = "string", dest = "local_bin", michael@0: help = "local path to bin directory") michael@0: defaults["local_bin"] = build_obj.bindir if build_obj is not None else None michael@0: michael@0: self.add_option("--remoteTestRoot", action = "store", michael@0: type = "string", dest = "remote_test_root", michael@0: help = "remote directory to use as test root (eg. /data/local/tests)") michael@0: # /data/local/tests is used because it is usually not possible to set +x permissions michael@0: # on binaries on /mnt/sdcard michael@0: defaults["remote_test_root"] = "/data/local/tests" michael@0: michael@0: self.add_option("--addEnv", action = "append", michael@0: type = "string", dest = "add_env", michael@0: help = "additional remote environment variable definitions (eg. --addEnv \"somevar=something\")") michael@0: defaults["add_env"] = None michael@0: michael@0: self.set_defaults(**defaults) michael@0: michael@0: def main(): michael@0: parser = RemoteCPPUnittestOptions() michael@0: options, args = parser.parse_args() michael@0: if not args: michael@0: print >>sys.stderr, """Usage: %s [...]""" % sys.argv[0] michael@0: sys.exit(1) michael@0: if options.local_lib is None and options.local_apk is None: michael@0: print >>sys.stderr, """Error: --localLib or --apk is required""" michael@0: sys.exit(1) michael@0: if options.local_lib is not None and not os.path.isdir(options.local_lib): michael@0: print >>sys.stderr, """Error: --localLib directory %s not found""" % options.local_lib michael@0: sys.exit(1) michael@0: if options.local_apk is not None and not os.path.isfile(options.local_apk): michael@0: print >>sys.stderr, """Error: --apk file %s not found""" % options.local_apk michael@0: sys.exit(1) michael@0: if not options.xre_path: michael@0: print >>sys.stderr, """Error: --xre-path is required""" michael@0: sys.exit(1) michael@0: if options.dm_trans == "adb": michael@0: if options.device_ip: michael@0: dm = devicemanagerADB.DeviceManagerADB(options.device_ip, options.device_port, packageName=None, deviceRoot=options.remote_test_root) michael@0: else: michael@0: dm = devicemanagerADB.DeviceManagerADB(packageName=None, deviceRoot=options.remote_test_root) michael@0: else: michael@0: dm = devicemanagerSUT.DeviceManagerSUT(options.device_ip, options.device_port, deviceRoot=options.remote_test_root) michael@0: if not options.device_ip: michael@0: print "Error: you must provide a device IP to connect to via the --deviceIP option" michael@0: sys.exit(1) michael@0: options.xre_path = os.path.abspath(options.xre_path) michael@0: progs = cppunittests.extract_unittests_from_args(args, options.manifest_file) michael@0: tester = RemoteCPPUnitTests(dm, options, progs) michael@0: try: michael@0: result = tester.run_tests(progs, options.xre_path, options.symbols_path) michael@0: except Exception, e: michael@0: log.error(str(e)) michael@0: result = False michael@0: sys.exit(0 if result else 1) michael@0: michael@0: if __name__ == '__main__': michael@0: main()