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 ADBDeviceFactory
, ADBProcessError
, ADBTimeoutError
22 from mozbuild
.base
import MozbuildObject
24 build_obj
= MozbuildObject
.from_environment()
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")
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
)
51 self
.push_progs(progs
)
52 self
.device
.chmod(self
.remote_bin_dir
, recursive
=True)
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
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
):
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]] = ""
117 self
.log
.warning("invalid --addEnv option skipped: %s" % envdef
)
122 self
, prog
, env
, symbols_path
=None, interactive
=False, timeout_factor
=1
125 Run a single C++ unit test program remotely.
128 * prog: The path to the test program to run.
129 * env: The environment to use for running the program.
130 * symbols_path: A path to a directory containing Breakpad-formatted
131 symbol files for producing stack traces on crash.
132 * timeout_factor: An optional test-specific timeout multiplier.
134 Return True if the program exits with a zero status, False otherwise.
136 basename
= os
.path
.basename(prog
)
137 remote_bin
= posixpath
.join(self
.remote_bin_dir
, basename
)
138 self
.log
.test_start(basename
)
139 test_timeout
= cppunittests
.CPPUnitTests
.TEST_PROC_TIMEOUT
* timeout_factor
142 output
= self
.device
.shell_output(
143 remote_bin
, env
=env
, cwd
=self
.remote_home_dir
, timeout
=test_timeout
146 except ADBTimeoutError
:
148 except ADBProcessError
as e
:
149 output
= e
.adb_process
.stdout
150 returncode
= e
.adb_process
.exitcode
152 self
.log
.process_output(basename
, "\n%s" % output
, 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
, test_name
=basename
):
156 self
.log
.test_end(basename
, status
="CRASH", expected
="PASS")
158 result
= returncode
== 0
164 message
=("test failed with return code %s" % returncode
),
167 self
.log
.test_end(basename
, status
="PASS", expected
="PASS")
171 class RemoteCPPUnittestOptions(cppunittests
.CPPUnittestOptions
):
173 cppunittests
.CPPUnittestOptions
.__init
__(self
)
180 dest
="device_serial",
181 help="adb serial number of remote device. This is required "
182 "when more than one device is connected to the host. "
183 "Use 'adb devices' to see connected devices.",
185 defaults
["device_serial"] = None
192 help="Path to adb binary.",
194 defaults
["adb_path"] = None
198 action
="store_false",
200 help="Do not copy any files to device (to be used only if "
201 "device is already setup).",
203 defaults
["setup"] = True
210 help="Location of libraries to push -- preferably stripped.",
212 defaults
["local_lib"] = None
219 help="Local path to Firefox for Android APK.",
221 defaults
["local_apk"] = None
228 help="Local path to bin directory.",
230 defaults
["local_bin"] = build_obj
.bindir
if build_obj
is not None else None
236 dest
="remote_test_root",
237 help="Remote directory to use as test root "
238 "(eg. /data/local/tmp/test_root).",
241 # /data/local/tmp/test_root is used because it is usually not
242 # possible to set +x permissions on binaries on /mnt/sdcard
243 # and since scope storage on Android 10 causes permission
244 # errors on the sdcard.
245 defaults
["remote_test_root"] = "/data/local/tmp/test_root"
252 help="additional remote environment variable definitions "
253 '(eg. --addEnv "somevar=something")',
255 defaults
["add_env"] = None
257 self
.set_defaults(**defaults
)
260 def run_test_harness(options
, args
):
261 options
.xre_path
= os
.path
.abspath(options
.xre_path
)
262 cppunittests
.update_mozinfo()
263 progs
= cppunittests
.extract_unittests_from_args(
264 args
, mozinfo
.info
, options
.manifest_path
266 tester
= RemoteCPPUnitTests(options
, [item
[0] for item
in progs
])
267 result
= tester
.run_tests(
270 options
.symbols_path
,
276 parser
= RemoteCPPUnittestOptions()
277 mozlog
.commandline
.add_logging_group(parser
)
278 options
, args
= parser
.parse_args()
281 """Usage: %s <test binary> [<test binary>...]""" % sys
.argv
[0],
285 if options
.local_lib
is not None and not os
.path
.isdir(options
.local_lib
):
287 """Error: --localLib directory %s not found""" % options
.local_lib
,
291 if options
.local_apk
is not None and not os
.path
.isfile(options
.local_apk
):
292 print("""Error: --apk file %s not found""" % options
.local_apk
, file=sys
.stderr
)
294 if not options
.xre_path
:
295 print("""Error: --xre-path is required""", file=sys
.stderr
)
298 log
= mozlog
.commandline
.setup_logging(
299 "remotecppunittests", options
, {"tbpl": sys
.stdout
}
302 result
= run_test_harness(options
, args
)
303 except Exception as e
:
305 traceback
.print_exc()
307 sys
.exit(0 if result
else 1)
310 if __name__
== "__main__":