Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
michael@0 | 1 | #!/usr/bin/env python |
michael@0 | 2 | # |
michael@0 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
michael@0 | 6 | |
michael@0 | 7 | import os, sys |
michael@0 | 8 | import subprocess |
michael@0 | 9 | import tempfile |
michael@0 | 10 | from zipfile import ZipFile |
michael@0 | 11 | import runcppunittests as cppunittests |
michael@0 | 12 | import mozcrash, mozlog |
michael@0 | 13 | import mozfile |
michael@0 | 14 | import StringIO |
michael@0 | 15 | import posixpath |
michael@0 | 16 | from mozdevice import devicemanager, devicemanagerADB, devicemanagerSUT |
michael@0 | 17 | |
michael@0 | 18 | try: |
michael@0 | 19 | from mozbuild.base import MozbuildObject |
michael@0 | 20 | build_obj = MozbuildObject.from_environment() |
michael@0 | 21 | except ImportError: |
michael@0 | 22 | build_obj = None |
michael@0 | 23 | |
michael@0 | 24 | log = mozlog.getLogger('remotecppunittests') |
michael@0 | 25 | |
michael@0 | 26 | class RemoteCPPUnitTests(cppunittests.CPPUnitTests): |
michael@0 | 27 | def __init__(self, devmgr, options, progs): |
michael@0 | 28 | cppunittests.CPPUnitTests.__init__(self) |
michael@0 | 29 | self.options = options |
michael@0 | 30 | self.device = devmgr |
michael@0 | 31 | self.remote_test_root = self.device.getDeviceRoot() + "/cppunittests" |
michael@0 | 32 | self.remote_bin_dir = posixpath.join(self.remote_test_root, "b") |
michael@0 | 33 | self.remote_tmp_dir = posixpath.join(self.remote_test_root, "tmp") |
michael@0 | 34 | self.remote_home_dir = posixpath.join(self.remote_test_root, "h") |
michael@0 | 35 | if options.setup: |
michael@0 | 36 | self.setup_bin(progs) |
michael@0 | 37 | |
michael@0 | 38 | def setup_bin(self, progs): |
michael@0 | 39 | if not self.device.dirExists(self.remote_test_root): |
michael@0 | 40 | self.device.mkDir(self.remote_test_root) |
michael@0 | 41 | if self.device.dirExists(self.remote_tmp_dir): |
michael@0 | 42 | self.device.removeDir(self.remote_tmp_dir) |
michael@0 | 43 | self.device.mkDir(self.remote_tmp_dir) |
michael@0 | 44 | if self.device.dirExists(self.remote_bin_dir): |
michael@0 | 45 | self.device.removeDir(self.remote_bin_dir) |
michael@0 | 46 | self.device.mkDir(self.remote_bin_dir) |
michael@0 | 47 | if self.device.dirExists(self.remote_home_dir): |
michael@0 | 48 | self.device.removeDir(self.remote_home_dir) |
michael@0 | 49 | self.device.mkDir(self.remote_home_dir) |
michael@0 | 50 | self.push_libs() |
michael@0 | 51 | self.push_progs(progs) |
michael@0 | 52 | self.device.chmodDir(self.remote_bin_dir) |
michael@0 | 53 | |
michael@0 | 54 | def push_libs(self): |
michael@0 | 55 | if self.options.local_apk: |
michael@0 | 56 | with mozfile.TemporaryDirectory() as tmpdir: |
michael@0 | 57 | apk_contents = ZipFile(self.options.local_apk) |
michael@0 | 58 | szip = os.path.join(self.options.local_bin, '..', 'host', 'bin', 'szip') |
michael@0 | 59 | if not os.path.exists(szip): |
michael@0 | 60 | # Tinderbox builds must run szip from the test package |
michael@0 | 61 | szip = os.path.join(self.options.local_bin, 'host', 'szip') |
michael@0 | 62 | if not os.path.exists(szip): |
michael@0 | 63 | # If the test package doesn't contain szip, it means files |
michael@0 | 64 | # are not szipped in the test package. |
michael@0 | 65 | szip = None |
michael@0 | 66 | |
michael@0 | 67 | for info in apk_contents.infolist(): |
michael@0 | 68 | if info.filename.endswith(".so"): |
michael@0 | 69 | print >> sys.stderr, "Pushing %s.." % info.filename |
michael@0 | 70 | remote_file = posixpath.join(self.remote_bin_dir, os.path.basename(info.filename)) |
michael@0 | 71 | apk_contents.extract(info, tmpdir) |
michael@0 | 72 | file = os.path.join(tmpdir, info.filename) |
michael@0 | 73 | if szip: |
michael@0 | 74 | out = subprocess.check_output([szip, '-d', file], stderr=subprocess.STDOUT) |
michael@0 | 75 | self.device.pushFile(os.path.join(tmpdir, info.filename), remote_file) |
michael@0 | 76 | return |
michael@0 | 77 | |
michael@0 | 78 | for file in os.listdir(self.options.local_lib): |
michael@0 | 79 | if file.endswith(".so"): |
michael@0 | 80 | print >> sys.stderr, "Pushing %s.." % file |
michael@0 | 81 | remote_file = posixpath.join(self.remote_bin_dir, file) |
michael@0 | 82 | self.device.pushFile(os.path.join(self.options.local_lib, file), remote_file) |
michael@0 | 83 | # Additional libraries may be found in a sub-directory such as "lib/armeabi-v7a" |
michael@0 | 84 | local_arm_lib = os.path.join(self.options.local_lib, "lib") |
michael@0 | 85 | if os.path.isdir(local_arm_lib): |
michael@0 | 86 | for root, dirs, files in os.walk(local_arm_lib): |
michael@0 | 87 | for file in files: |
michael@0 | 88 | if (file.endswith(".so")): |
michael@0 | 89 | remote_file = posixpath.join(self.remote_bin_dir, file) |
michael@0 | 90 | self.device.pushFile(os.path.join(root, file), remote_file) |
michael@0 | 91 | |
michael@0 | 92 | def push_progs(self, progs): |
michael@0 | 93 | for local_file in progs: |
michael@0 | 94 | remote_file = posixpath.join(self.remote_bin_dir, os.path.basename(local_file)) |
michael@0 | 95 | self.device.pushFile(local_file, remote_file) |
michael@0 | 96 | |
michael@0 | 97 | def build_environment(self): |
michael@0 | 98 | env = self.build_core_environment() |
michael@0 | 99 | env['LD_LIBRARY_PATH'] = self.remote_bin_dir |
michael@0 | 100 | env["TMPDIR"]=self.remote_tmp_dir |
michael@0 | 101 | env["HOME"]=self.remote_home_dir |
michael@0 | 102 | env["MOZILLA_FIVE_HOME"] = self.remote_home_dir |
michael@0 | 103 | env["MOZ_XRE_DIR"] = self.remote_bin_dir |
michael@0 | 104 | if self.options.add_env: |
michael@0 | 105 | for envdef in self.options.add_env: |
michael@0 | 106 | envdef_parts = envdef.split("=", 1) |
michael@0 | 107 | if len(envdef_parts) == 2: |
michael@0 | 108 | env[envdef_parts[0]] = envdef_parts[1] |
michael@0 | 109 | elif len(envdef_parts) == 1: |
michael@0 | 110 | env[envdef_parts[0]] = "" |
michael@0 | 111 | else: |
michael@0 | 112 | print >> sys.stderr, "warning: invalid --addEnv option skipped: "+envdef |
michael@0 | 113 | |
michael@0 | 114 | return env |
michael@0 | 115 | |
michael@0 | 116 | def run_one_test(self, prog, env, symbols_path=None): |
michael@0 | 117 | """ |
michael@0 | 118 | Run a single C++ unit test program remotely. |
michael@0 | 119 | |
michael@0 | 120 | Arguments: |
michael@0 | 121 | * prog: The path to the test program to run. |
michael@0 | 122 | * env: The environment to use for running the program. |
michael@0 | 123 | * symbols_path: A path to a directory containing Breakpad-formatted |
michael@0 | 124 | symbol files for producing stack traces on crash. |
michael@0 | 125 | |
michael@0 | 126 | Return True if the program exits with a zero status, False otherwise. |
michael@0 | 127 | """ |
michael@0 | 128 | basename = os.path.basename(prog) |
michael@0 | 129 | remote_bin = posixpath.join(self.remote_bin_dir, basename) |
michael@0 | 130 | log.info("Running test %s", basename) |
michael@0 | 131 | buf = StringIO.StringIO() |
michael@0 | 132 | returncode = self.device.shell([remote_bin], buf, env=env, cwd=self.remote_home_dir, |
michael@0 | 133 | timeout=cppunittests.CPPUnitTests.TEST_PROC_TIMEOUT) |
michael@0 | 134 | print >> sys.stdout, buf.getvalue() |
michael@0 | 135 | with mozfile.TemporaryDirectory() as tempdir: |
michael@0 | 136 | self.device.getDirectory(self.remote_home_dir, tempdir) |
michael@0 | 137 | if mozcrash.check_for_crashes(tempdir, symbols_path, |
michael@0 | 138 | test_name=basename): |
michael@0 | 139 | log.testFail("%s | test crashed", basename) |
michael@0 | 140 | return False |
michael@0 | 141 | result = returncode == 0 |
michael@0 | 142 | if not result: |
michael@0 | 143 | log.testFail("%s | test failed with return code %s", |
michael@0 | 144 | basename, returncode) |
michael@0 | 145 | return result |
michael@0 | 146 | |
michael@0 | 147 | class RemoteCPPUnittestOptions(cppunittests.CPPUnittestOptions): |
michael@0 | 148 | def __init__(self): |
michael@0 | 149 | cppunittests.CPPUnittestOptions.__init__(self) |
michael@0 | 150 | defaults = {} |
michael@0 | 151 | |
michael@0 | 152 | self.add_option("--deviceIP", action="store", |
michael@0 | 153 | type = "string", dest = "device_ip", |
michael@0 | 154 | help = "ip address of remote device to test") |
michael@0 | 155 | defaults["device_ip"] = None |
michael@0 | 156 | |
michael@0 | 157 | self.add_option("--devicePort", action="store", |
michael@0 | 158 | type = "string", dest = "device_port", |
michael@0 | 159 | help = "port of remote device to test") |
michael@0 | 160 | defaults["device_port"] = 20701 |
michael@0 | 161 | |
michael@0 | 162 | self.add_option("--dm_trans", action="store", |
michael@0 | 163 | type = "string", dest = "dm_trans", |
michael@0 | 164 | help = "the transport to use to communicate with device: [adb|sut]; default=sut") |
michael@0 | 165 | defaults["dm_trans"] = "sut" |
michael@0 | 166 | |
michael@0 | 167 | self.add_option("--noSetup", action="store_false", |
michael@0 | 168 | dest = "setup", |
michael@0 | 169 | help = "do not copy any files to device (to be used only if device is already setup)") |
michael@0 | 170 | defaults["setup"] = True |
michael@0 | 171 | |
michael@0 | 172 | self.add_option("--localLib", action="store", |
michael@0 | 173 | type = "string", dest = "local_lib", |
michael@0 | 174 | help = "location of libraries to push -- preferably stripped") |
michael@0 | 175 | defaults["local_lib"] = None |
michael@0 | 176 | |
michael@0 | 177 | self.add_option("--apk", action="store", |
michael@0 | 178 | type = "string", dest = "local_apk", |
michael@0 | 179 | help = "local path to Fennec APK") |
michael@0 | 180 | defaults["local_apk"] = None |
michael@0 | 181 | |
michael@0 | 182 | self.add_option("--localBinDir", action="store", |
michael@0 | 183 | type = "string", dest = "local_bin", |
michael@0 | 184 | help = "local path to bin directory") |
michael@0 | 185 | defaults["local_bin"] = build_obj.bindir if build_obj is not None else None |
michael@0 | 186 | |
michael@0 | 187 | self.add_option("--remoteTestRoot", action = "store", |
michael@0 | 188 | type = "string", dest = "remote_test_root", |
michael@0 | 189 | help = "remote directory to use as test root (eg. /data/local/tests)") |
michael@0 | 190 | # /data/local/tests is used because it is usually not possible to set +x permissions |
michael@0 | 191 | # on binaries on /mnt/sdcard |
michael@0 | 192 | defaults["remote_test_root"] = "/data/local/tests" |
michael@0 | 193 | |
michael@0 | 194 | self.add_option("--addEnv", action = "append", |
michael@0 | 195 | type = "string", dest = "add_env", |
michael@0 | 196 | help = "additional remote environment variable definitions (eg. --addEnv \"somevar=something\")") |
michael@0 | 197 | defaults["add_env"] = None |
michael@0 | 198 | |
michael@0 | 199 | self.set_defaults(**defaults) |
michael@0 | 200 | |
michael@0 | 201 | def main(): |
michael@0 | 202 | parser = RemoteCPPUnittestOptions() |
michael@0 | 203 | options, args = parser.parse_args() |
michael@0 | 204 | if not args: |
michael@0 | 205 | print >>sys.stderr, """Usage: %s <test binary> [<test binary>...]""" % sys.argv[0] |
michael@0 | 206 | sys.exit(1) |
michael@0 | 207 | if options.local_lib is None and options.local_apk is None: |
michael@0 | 208 | print >>sys.stderr, """Error: --localLib or --apk is required""" |
michael@0 | 209 | sys.exit(1) |
michael@0 | 210 | if options.local_lib is not None and not os.path.isdir(options.local_lib): |
michael@0 | 211 | print >>sys.stderr, """Error: --localLib directory %s not found""" % options.local_lib |
michael@0 | 212 | sys.exit(1) |
michael@0 | 213 | if options.local_apk is not None and not os.path.isfile(options.local_apk): |
michael@0 | 214 | print >>sys.stderr, """Error: --apk file %s not found""" % options.local_apk |
michael@0 | 215 | sys.exit(1) |
michael@0 | 216 | if not options.xre_path: |
michael@0 | 217 | print >>sys.stderr, """Error: --xre-path is required""" |
michael@0 | 218 | sys.exit(1) |
michael@0 | 219 | if options.dm_trans == "adb": |
michael@0 | 220 | if options.device_ip: |
michael@0 | 221 | dm = devicemanagerADB.DeviceManagerADB(options.device_ip, options.device_port, packageName=None, deviceRoot=options.remote_test_root) |
michael@0 | 222 | else: |
michael@0 | 223 | dm = devicemanagerADB.DeviceManagerADB(packageName=None, deviceRoot=options.remote_test_root) |
michael@0 | 224 | else: |
michael@0 | 225 | dm = devicemanagerSUT.DeviceManagerSUT(options.device_ip, options.device_port, deviceRoot=options.remote_test_root) |
michael@0 | 226 | if not options.device_ip: |
michael@0 | 227 | print "Error: you must provide a device IP to connect to via the --deviceIP option" |
michael@0 | 228 | sys.exit(1) |
michael@0 | 229 | options.xre_path = os.path.abspath(options.xre_path) |
michael@0 | 230 | progs = cppunittests.extract_unittests_from_args(args, options.manifest_file) |
michael@0 | 231 | tester = RemoteCPPUnitTests(dm, options, progs) |
michael@0 | 232 | try: |
michael@0 | 233 | result = tester.run_tests(progs, options.xre_path, options.symbols_path) |
michael@0 | 234 | except Exception, e: |
michael@0 | 235 | log.error(str(e)) |
michael@0 | 236 | result = False |
michael@0 | 237 | sys.exit(0 if result else 1) |
michael@0 | 238 | |
michael@0 | 239 | if __name__ == '__main__': |
michael@0 | 240 | main() |