Bug 1669129 - [devtools] Enable devtools.overflow.debugging.enabled. r=jdescottes
[gecko.git] / testing / remotecppunittests.py
blob4924d532a195f9bdbe1208bec3680bfabaf76dec
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 from __future__ import absolute_import, print_function
8 import os
9 import posixpath
10 import sys
11 import subprocess
12 import traceback
13 from zipfile import ZipFile
14 import runcppunittests as cppunittests
15 import mozcrash
16 import mozfile
17 import mozinfo
18 import mozlog
19 from mozdevice import ADBDeviceFactory, ADBProcessError, ADBTimeoutError
21 try:
22 from mozbuild.base import MozbuildObject
23 build_obj = MozbuildObject.from_environment()
24 except ImportError:
25 build_obj = None
28 class RemoteCPPUnitTests(cppunittests.CPPUnitTests):
30 def __init__(self, options, progs):
31 cppunittests.CPPUnitTests.__init__(self)
32 self.options = options
33 self.device = ADBDeviceFactory(adb=options.adb_path or 'adb',
34 device=options.device_serial,
35 test_root=options.remote_test_root)
36 self.remote_test_root = posixpath.join(self.device.test_root, "cppunittests")
37 self.remote_bin_dir = posixpath.join(self.remote_test_root, "b")
38 self.remote_tmp_dir = posixpath.join(self.remote_test_root, "tmp")
39 self.remote_home_dir = posixpath.join(self.remote_test_root, "h")
40 if options.setup:
41 self.setup_bin(progs)
43 def setup_bin(self, progs):
44 self.device.rm(self.remote_test_root, force=True, recursive=True)
45 self.device.mkdir(self.remote_home_dir, parents=True)
46 self.device.mkdir(self.remote_tmp_dir)
47 self.device.mkdir(self.remote_bin_dir)
48 self.push_libs()
49 self.push_progs(progs)
50 self.device.chmod(self.remote_bin_dir, recursive=True)
52 def push_libs(self):
53 if self.options.local_apk:
54 with mozfile.TemporaryDirectory() as tmpdir:
55 apk_contents = ZipFile(self.options.local_apk)
57 for info in apk_contents.infolist():
58 if info.filename.endswith(".so"):
59 print("Pushing %s.." % info.filename, file=sys.stderr)
60 remote_file = posixpath.join(
61 self.remote_bin_dir, os.path.basename(info.filename))
62 apk_contents.extract(info, tmpdir)
63 local_file = os.path.join(tmpdir, info.filename)
64 with open(local_file) as f:
65 # Decompress xz-compressed file.
66 if f.read(5)[1:] == '7zXZ':
67 cmd = [
68 'xz', '-df', '--suffix', '.so', local_file]
69 subprocess.check_output(cmd)
70 # xz strips the ".so" file suffix.
71 os.rename(local_file[:-3], local_file)
72 self.device.push(local_file, remote_file)
74 elif self.options.local_lib:
75 for path in os.listdir(self.options.local_lib):
76 if path.endswith(".so"):
77 print("Pushing {}..".format(path), file=sys.stderr)
78 remote_file = posixpath.join(self.remote_bin_dir, path)
79 local_file = os.path.join(self.options.local_lib, path)
80 self.device.push(local_file, remote_file)
81 # Additional libraries may be found in a sub-directory such as
82 # "lib/armeabi-v7a"
83 for subdir in ["assets", "lib"]:
84 local_arm_lib = os.path.join(self.options.local_lib, subdir)
85 if os.path.isdir(local_arm_lib):
86 for root, dirs, paths in os.walk(local_arm_lib):
87 for path in paths:
88 if path.endswith(".so"):
89 print("Pushing {}..".format(path), file=sys.stderr)
90 remote_file = posixpath.join(
91 self.remote_bin_dir, path)
92 local_file = os.path.join(root, path)
93 self.device.push(local_file, remote_file)
95 def push_progs(self, progs):
96 for local_file in progs:
97 remote_file = posixpath.join(
98 self.remote_bin_dir, os.path.basename(local_file))
99 self.device.push(local_file, remote_file)
101 def build_environment(self, enable_webrender=False):
102 env = self.build_core_environment({}, enable_webrender)
103 env['LD_LIBRARY_PATH'] = self.remote_bin_dir
104 env["TMPDIR"] = self.remote_tmp_dir
105 env["HOME"] = self.remote_home_dir
106 env["MOZ_XRE_DIR"] = self.remote_bin_dir
107 if self.options.add_env:
108 for envdef in self.options.add_env:
109 envdef_parts = envdef.split("=", 1)
110 if len(envdef_parts) == 2:
111 env[envdef_parts[0]] = envdef_parts[1]
112 elif len(envdef_parts) == 1:
113 env[envdef_parts[0]] = ""
114 else:
115 self.log.warning(
116 "invalid --addEnv option skipped: %s" % envdef)
118 return env
120 def run_one_test(self, prog, env, symbols_path=None, interactive=False,
121 timeout_factor=1):
123 Run a single C++ unit test program remotely.
125 Arguments:
126 * prog: The path to the test program to run.
127 * env: The environment to use for running the program.
128 * symbols_path: A path to a directory containing Breakpad-formatted
129 symbol files for producing stack traces on crash.
130 * timeout_factor: An optional test-specific timeout multiplier.
132 Return True if the program exits with a zero status, False otherwise.
134 basename = os.path.basename(prog)
135 remote_bin = posixpath.join(self.remote_bin_dir, basename)
136 self.log.test_start(basename)
137 test_timeout = cppunittests.CPPUnitTests.TEST_PROC_TIMEOUT * \
138 timeout_factor
140 try:
141 output = self.device.shell_output(remote_bin, env=env,
142 cwd=self.remote_home_dir,
143 timeout=test_timeout)
144 returncode = 0
145 except ADBTimeoutError:
146 raise
147 except ADBProcessError as e:
148 output = e.adb_process.stdout
149 returncode = e.adb_process.exitcode
151 self.log.process_output(basename, "\n%s" % output,
152 command=[remote_bin])
153 with mozfile.TemporaryDirectory() as tempdir:
154 self.device.pull(self.remote_home_dir, tempdir)
155 if mozcrash.check_for_crashes(tempdir, symbols_path,
156 test_name=basename):
157 self.log.test_end(basename, status='CRASH', expected='PASS')
158 return False
159 result = returncode == 0
160 if not result:
161 self.log.test_end(basename, status='FAIL', expected='PASS',
162 message=("test failed with return code %s" %
163 returncode))
164 else:
165 self.log.test_end(basename, status='PASS', expected='PASS')
166 return result
169 class RemoteCPPUnittestOptions(cppunittests.CPPUnittestOptions):
171 def __init__(self):
172 cppunittests.CPPUnittestOptions.__init__(self)
173 defaults = {}
175 self.add_option("--deviceSerial", action="store",
176 type="string", dest="device_serial",
177 help="adb serial number of remote device. This is required "
178 "when more than one device is connected to the host. "
179 "Use 'adb devices' to see connected devices.")
180 defaults["device_serial"] = None
182 self.add_option("--adbPath", action="store",
183 type="string", dest="adb_path",
184 help="Path to adb binary.")
185 defaults["adb_path"] = None
187 self.add_option("--noSetup", action="store_false",
188 dest="setup",
189 help="Do not copy any files to device (to be used only if "
190 "device is already setup).")
191 defaults["setup"] = True
193 self.add_option("--localLib", action="store",
194 type="string", dest="local_lib",
195 help="Location of libraries to push -- preferably stripped.")
196 defaults["local_lib"] = None
198 self.add_option("--apk", action="store",
199 type="string", dest="local_apk",
200 help="Local path to Firefox for Android APK.")
201 defaults["local_apk"] = None
203 self.add_option("--localBinDir", action="store",
204 type="string", dest="local_bin",
205 help="Local path to bin directory.")
206 defaults[
207 "local_bin"] = build_obj.bindir if build_obj is not None else None
209 self.add_option("--remoteTestRoot", action="store",
210 type="string", dest="remote_test_root",
211 help="Remote directory to use as test root "
212 "(eg. /data/local/tmp/test_root).")
214 # /data/local/tmp/test_root is used because it is usually not
215 # possible to set +x permissions on binaries on /mnt/sdcard
216 # and since scope storage on Android 10 causes permission
217 # errors on the sdcard.
218 defaults["remote_test_root"] = "/data/local/tmp/test_root"
220 self.add_option("--addEnv", action="append",
221 type="string", dest="add_env",
222 help="additional remote environment variable definitions "
223 "(eg. --addEnv \"somevar=something\")")
224 defaults["add_env"] = None
226 self.set_defaults(**defaults)
229 def run_test_harness(options, args):
230 options.xre_path = os.path.abspath(options.xre_path)
231 cppunittests.update_mozinfo()
232 progs = cppunittests.extract_unittests_from_args(args,
233 mozinfo.info,
234 options.manifest_path)
235 tester = RemoteCPPUnitTests(options, [item[0] for item in progs])
236 result = tester.run_tests(progs, options.xre_path, options.symbols_path,
237 enable_webrender=options.enable_webrender)
238 return result
241 def main():
242 parser = RemoteCPPUnittestOptions()
243 mozlog.commandline.add_logging_group(parser)
244 options, args = parser.parse_args()
245 if not args:
246 print("""Usage: %s <test binary> [<test binary>...]""" % sys.argv[0], file=sys.stderr)
247 sys.exit(1)
248 if options.local_lib is not None and not os.path.isdir(options.local_lib):
249 print("""Error: --localLib directory %s not found""" % options.local_lib, file=sys.stderr)
250 sys.exit(1)
251 if options.local_apk is not None and not os.path.isfile(options.local_apk):
252 print("""Error: --apk file %s not found""" % options.local_apk, file=sys.stderr)
253 sys.exit(1)
254 if not options.xre_path:
255 print("""Error: --xre-path is required""", file=sys.stderr)
256 sys.exit(1)
258 log = mozlog.commandline.setup_logging("remotecppunittests", options,
259 {"tbpl": sys.stdout})
260 try:
261 result = run_test_harness(options, args)
262 except Exception as e:
263 log.error(str(e))
264 traceback.print_exc()
265 result = False
266 sys.exit(0 if result else 1)
269 if __name__ == '__main__':
270 main()