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
13 from zipfile
import ZipFile
14 import runcppunittests
as cppunittests
19 from mozdevice
import ADBDevice
, ADBProcessError
, ADBTimeoutError
22 from mozbuild
.base
import MozbuildObject
23 build_obj
= MozbuildObject
.from_environment()
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")
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
)
49 self
.push_progs(progs
)
50 self
.device
.chmod(self
.remote_bin_dir
, recursive
=True, root
=True)
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':
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
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
):
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]] = ""
116 "invalid --addEnv option skipped: %s" % envdef
)
120 def run_one_test(self
, prog
, env
, symbols_path
=None, interactive
=False,
123 Run a single C++ unit test program remotely.
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
* \
141 output
= self
.device
.shell_output(remote_bin
, env
=env
,
142 cwd
=self
.remote_home_dir
,
143 timeout
=test_timeout
)
145 except ADBTimeoutError
:
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
,
157 self
.log
.test_end(basename
, status
='CRASH', expected
='PASS')
159 result
= returncode
== 0
161 self
.log
.test_end(basename
, status
='FAIL', expected
='PASS',
162 message
=("test failed with return code %s" %
165 self
.log
.test_end(basename
, status
='PASS', expected
='PASS')
169 class RemoteCPPUnittestOptions(cppunittests
.CPPUnittestOptions
):
172 cppunittests
.CPPUnittestOptions
.__init
__(self
)
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",
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.")
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
,
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
)
240 parser
= RemoteCPPUnittestOptions()
241 mozlog
.commandline
.add_logging_group(parser
)
242 options
, args
= parser
.parse_args()
244 print("""Usage: %s <test binary> [<test binary>...]""" % sys
.argv
[0], file=sys
.stderr
)
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
)
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
)
252 if not options
.xre_path
:
253 print("""Error: --xre-path is required""", file=sys
.stderr
)
256 log
= mozlog
.commandline
.setup_logging("remotecppunittests", options
,
257 {"tbpl": sys
.stdout
})
259 result
= run_test_harness(options
, args
)
260 except Exception as e
:
262 traceback
.print_exc()
264 sys
.exit(0 if result
else 1)
267 if __name__
== '__main__':