Bug 1578501 [wpt PR 18803] - WebKit export of https://bugs.webkit.org/show_bug.cgi...
[gecko.git] / testing / remotecppunittests.py
blob037f7748ff31f9a19782acadcf8bcd3d4b916fe4
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 ADBDevice, 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 = ADBDevice(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, root=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 %s..".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. /mnt/sdcard/tests or /data/local/tests).")
214 # /data/local/tests is used because it is usually not possible to set +x permissions
215 # on binaries on /mnt/sdcard
216 defaults["remote_test_root"] = "/data/local/tests"
218 self.add_option("--addEnv", action="append",
219 type="string", dest="add_env",
220 help="additional remote environment variable definitions "
221 "(eg. --addEnv \"somevar=something\")")
222 defaults["add_env"] = None
224 self.set_defaults(**defaults)
227 def run_test_harness(options, args):
228 options.xre_path = os.path.abspath(options.xre_path)
229 cppunittests.update_mozinfo()
230 progs = cppunittests.extract_unittests_from_args(args,
231 mozinfo.info,
232 options.manifest_path)
233 tester = RemoteCPPUnitTests(options, [item[0] for item in progs])
234 result = tester.run_tests(progs, options.xre_path, options.symbols_path,
235 enable_webrender=options.enable_webrender)
236 return result
239 def main():
240 parser = RemoteCPPUnittestOptions()
241 mozlog.commandline.add_logging_group(parser)
242 options, args = parser.parse_args()
243 if not args:
244 print("""Usage: %s <test binary> [<test binary>...]""" % sys.argv[0], file=sys.stderr)
245 sys.exit(1)
246 if options.local_lib is not None and not os.path.isdir(options.local_lib):
247 print("""Error: --localLib directory %s not found""" % options.local_lib, file=sys.stderr)
248 sys.exit(1)
249 if options.local_apk is not None and not os.path.isfile(options.local_apk):
250 print("""Error: --apk file %s not found""" % options.local_apk, file=sys.stderr)
251 sys.exit(1)
252 if not options.xre_path:
253 print("""Error: --xre-path is required""", file=sys.stderr)
254 sys.exit(1)
256 log = mozlog.commandline.setup_logging("remotecppunittests", options,
257 {"tbpl": sys.stdout})
258 try:
259 result = run_test_harness(options, args)
260 except Exception as e:
261 log.error(str(e))
262 traceback.print_exc()
263 result = False
264 sys.exit(0 if result else 1)
267 if __name__ == '__main__':
268 main()