testing/remotecppunittests.py

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

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

mercurial