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