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/.
10 from zipfile
import ZipFile
11 import runcppunittests
as cppunittests
17 from mozdevice
import ADBAndroid
, ADBProcessError
, ADBTimeoutError
20 from mozbuild
.base
import MozbuildObject
21 build_obj
= MozbuildObject
.from_environment()
26 class RemoteCPPUnitTests(cppunittests
.CPPUnitTests
):
28 def __init__(self
, options
, progs
):
29 cppunittests
.CPPUnitTests
.__init
__(self
)
30 self
.options
= options
31 self
.device
= ADBAndroid(adb
=options
.adb_path
or 'adb',
32 device
=options
.device_serial
,
33 test_root
=options
.remote_test_root
)
34 self
.remote_test_root
= posixpath
.join(self
.device
.test_root
, "cppunittests")
35 self
.remote_bin_dir
= posixpath
.join(self
.remote_test_root
, "b")
36 self
.remote_tmp_dir
= posixpath
.join(self
.remote_test_root
, "tmp")
37 self
.remote_home_dir
= posixpath
.join(self
.remote_test_root
, "h")
41 def setup_bin(self
, progs
):
42 self
.device
.rm(self
.remote_test_root
, force
=True, recursive
=True)
43 self
.device
.mkdir(self
.remote_home_dir
, parents
=True)
44 self
.device
.mkdir(self
.remote_tmp_dir
)
46 self
.push_progs(progs
)
47 self
.device
.chmod(self
.remote_bin_dir
, recursive
=True, root
=True)
50 if self
.options
.local_apk
:
51 with mozfile
.TemporaryDirectory() as tmpdir
:
52 apk_contents
= ZipFile(self
.options
.local_apk
)
54 for info
in apk_contents
.infolist():
55 if info
.filename
.endswith(".so"):
56 print >> sys
.stderr
, "Pushing %s.." % info
.filename
57 remote_file
= posixpath
.join(
58 self
.remote_bin_dir
, os
.path
.basename(info
.filename
))
59 apk_contents
.extract(info
, tmpdir
)
60 local_file
= os
.path
.join(tmpdir
, info
.filename
)
61 with
open(local_file
) as f
:
62 # Decompress xz-compressed file.
63 if f
.read(5)[1:] == '7zXZ':
65 'xz', '-df', '--suffix', '.so', local_file
]
66 subprocess
.check_output(cmd
)
67 # xz strips the ".so" file suffix.
68 os
.rename(local_file
[:-3], local_file
)
69 self
.device
.push(local_file
, remote_file
)
71 elif self
.options
.local_lib
:
72 for file in os
.listdir(self
.options
.local_lib
):
73 if file.endswith(".so"):
74 print >> sys
.stderr
, "Pushing %s.." % file
75 remote_file
= posixpath
.join(self
.remote_bin_dir
, file)
76 local_file
= os
.path
.join(self
.options
.local_lib
, file)
77 self
.device
.push(local_file
, remote_file
)
78 # Additional libraries may be found in a sub-directory such as
80 for subdir
in ["assets", "lib"]:
81 local_arm_lib
= os
.path
.join(self
.options
.local_lib
, subdir
)
82 if os
.path
.isdir(local_arm_lib
):
83 for root
, dirs
, files
in os
.walk(local_arm_lib
):
85 if (file.endswith(".so")):
86 print >> sys
.stderr
, "Pushing %s.." % file
87 remote_file
= posixpath
.join(
88 self
.remote_bin_dir
, file)
89 local_file
= os
.path
.join(root
, file)
90 self
.device
.push(local_file
, remote_file
)
92 def push_progs(self
, progs
):
93 for local_file
in progs
:
94 remote_file
= posixpath
.join(
95 self
.remote_bin_dir
, os
.path
.basename(local_file
))
96 self
.device
.push(local_file
, remote_file
)
98 def build_environment(self
):
99 env
= self
.build_core_environment()
100 env
['LD_LIBRARY_PATH'] = self
.remote_bin_dir
101 env
["TMPDIR"] = self
.remote_tmp_dir
102 env
["HOME"] = self
.remote_home_dir
103 env
["MOZ_XRE_DIR"] = self
.remote_bin_dir
104 if self
.options
.add_env
:
105 for envdef
in self
.options
.add_env
:
106 envdef_parts
= envdef
.split("=", 1)
107 if len(envdef_parts
) == 2:
108 env
[envdef_parts
[0]] = envdef_parts
[1]
109 elif len(envdef_parts
) == 1:
110 env
[envdef_parts
[0]] = ""
113 "invalid --addEnv option skipped: %s" % envdef
)
117 def run_one_test(self
, prog
, env
, symbols_path
=None, interactive
=False,
120 Run a single C++ unit test program remotely.
123 * prog: The path to the test program to run.
124 * env: The environment to use for running the program.
125 * symbols_path: A path to a directory containing Breakpad-formatted
126 symbol files for producing stack traces on crash.
127 * timeout_factor: An optional test-specific timeout multiplier.
129 Return True if the program exits with a zero status, False otherwise.
131 basename
= os
.path
.basename(prog
)
132 remote_bin
= posixpath
.join(self
.remote_bin_dir
, basename
)
133 self
.log
.test_start(basename
)
134 test_timeout
= cppunittests
.CPPUnitTests
.TEST_PROC_TIMEOUT
* \
138 output
= self
.device
.shell_output(remote_bin
, env
=env
,
139 cwd
=self
.remote_home_dir
,
140 timeout
=test_timeout
)
142 except ADBTimeoutError
:
144 except ADBProcessError
as e
:
145 output
= e
.adb_process
.stdout
146 returncode
= e
.adb_process
.exitcode
148 self
.log
.process_output(basename
, "\n%s" % output
,
149 command
=[remote_bin
])
150 with mozfile
.TemporaryDirectory() as tempdir
:
151 self
.device
.pull(self
.remote_home_dir
, tempdir
)
152 if mozcrash
.check_for_crashes(tempdir
, symbols_path
,
154 self
.log
.test_end(basename
, status
='CRASH', expected
='PASS')
156 result
= returncode
== 0
158 self
.log
.test_end(basename
, status
='FAIL', expected
='PASS',
159 message
=("test failed with return code %s" %
162 self
.log
.test_end(basename
, status
='PASS', expected
='PASS')
166 class RemoteCPPUnittestOptions(cppunittests
.CPPUnittestOptions
):
169 cppunittests
.CPPUnittestOptions
.__init
__(self
)
172 self
.add_option("--deviceSerial", action
="store",
173 type="string", dest
="device_serial",
174 help="serial ID of device")
175 defaults
["device_serial"] = None
177 self
.add_option("--adbPath", action
="store",
178 type="string", dest
="adb_path",
180 defaults
["adb_path"] = None
182 self
.add_option("--noSetup", action
="store_false",
184 help="do not copy any files to device (to be used only if "
185 "device is already setup)")
186 defaults
["setup"] = True
188 self
.add_option("--localLib", action
="store",
189 type="string", dest
="local_lib",
190 help="location of libraries to push -- preferably stripped")
191 defaults
["local_lib"] = None
193 self
.add_option("--apk", action
="store",
194 type="string", dest
="local_apk",
195 help="local path to Fennec APK")
196 defaults
["local_apk"] = None
198 self
.add_option("--localBinDir", action
="store",
199 type="string", dest
="local_bin",
200 help="local path to bin directory")
202 "local_bin"] = build_obj
.bindir
if build_obj
is not None else None
204 self
.add_option("--remoteTestRoot", action
="store",
205 type="string", dest
="remote_test_root",
206 help="remote directory to use as test root (eg. /data/local/tests)")
207 # /data/local/tests is used because it is usually not possible to set +x permissions
208 # on binaries on /mnt/sdcard
209 defaults
["remote_test_root"] = "/data/local/tests"
211 self
.add_option("--addEnv", action
="append",
212 type="string", dest
="add_env",
213 help="additional remote environment variable definitions "
214 "(eg. --addEnv \"somevar=something\")")
215 defaults
["add_env"] = None
217 self
.set_defaults(**defaults
)
220 def run_test_harness(options
, args
):
221 options
.xre_path
= os
.path
.abspath(options
.xre_path
)
222 cppunittests
.update_mozinfo()
223 progs
= cppunittests
.extract_unittests_from_args(args
,
225 options
.manifest_path
)
226 tester
= RemoteCPPUnitTests(options
, [item
[0] for item
in progs
])
227 result
= tester
.run_tests(progs
, options
.xre_path
, options
.symbols_path
)
232 parser
= RemoteCPPUnittestOptions()
233 mozlog
.commandline
.add_logging_group(parser
)
234 options
, args
= parser
.parse_args()
236 print >>sys
.stderr
, """Usage: %s <test binary> [<test binary>...]""" % sys
.argv
[0]
238 if options
.local_lib
is not None and not os
.path
.isdir(options
.local_lib
):
239 print >>sys
.stderr
, """Error: --localLib directory %s not found""" % options
.local_lib
241 if options
.local_apk
is not None and not os
.path
.isfile(options
.local_apk
):
242 print >>sys
.stderr
, """Error: --apk file %s not found""" % options
.local_apk
244 if not options
.xre_path
:
245 print >>sys
.stderr
, """Error: --xre-path is required"""
248 log
= mozlog
.commandline
.setup_logging("remotecppunittests", options
,
249 {"tbpl": sys
.stdout
})
251 result
= run_test_harness(options
, args
)
252 except Exception as e
:
255 sys
.exit(0 if result
else 1)
258 if __name__
== '__main__':