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