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
16 from mozdevice
import devicemanager
, devicemanagerADB
, devicemanagerSUT
17 from mozlog
import structured
20 from mozbuild
.base
import MozbuildObject
21 build_obj
= MozbuildObject
.from_environment()
25 class RemoteCPPUnitTests(cppunittests
.CPPUnitTests
):
26 def __init__(self
, devmgr
, options
, progs
):
27 cppunittests
.CPPUnitTests
.__init
__(self
)
28 self
.options
= options
30 self
.remote_test_root
= self
.device
.deviceRoot
+ "/cppunittests"
31 self
.remote_bin_dir
= posixpath
.join(self
.remote_test_root
, "b")
32 self
.remote_tmp_dir
= posixpath
.join(self
.remote_test_root
, "tmp")
33 self
.remote_home_dir
= posixpath
.join(self
.remote_test_root
, "h")
37 def setup_bin(self
, progs
):
38 if not self
.device
.dirExists(self
.remote_test_root
):
39 self
.device
.mkDir(self
.remote_test_root
)
40 if self
.device
.dirExists(self
.remote_tmp_dir
):
41 self
.device
.removeDir(self
.remote_tmp_dir
)
42 self
.device
.mkDir(self
.remote_tmp_dir
)
43 if self
.device
.dirExists(self
.remote_bin_dir
):
44 self
.device
.removeDir(self
.remote_bin_dir
)
45 self
.device
.mkDir(self
.remote_bin_dir
)
46 if self
.device
.dirExists(self
.remote_home_dir
):
47 self
.device
.removeDir(self
.remote_home_dir
)
48 self
.device
.mkDir(self
.remote_home_dir
)
50 self
.push_progs(progs
)
51 self
.device
.chmodDir(self
.remote_bin_dir
)
54 if self
.options
.local_apk
:
55 with mozfile
.TemporaryDirectory() as tmpdir
:
56 apk_contents
= ZipFile(self
.options
.local_apk
)
57 szip
= os
.path
.join(self
.options
.local_bin
, '..', 'host', 'bin', 'szip')
58 if not os
.path
.exists(szip
):
59 # Tinderbox builds must run szip from the test package
60 szip
= os
.path
.join(self
.options
.local_bin
, 'host', 'szip')
61 if not os
.path
.exists(szip
):
62 # If the test package doesn't contain szip, it means files
63 # are not szipped in the test package.
66 for info
in apk_contents
.infolist():
67 if info
.filename
.endswith(".so"):
68 print >> sys
.stderr
, "Pushing %s.." % info
.filename
69 remote_file
= posixpath
.join(self
.remote_bin_dir
, os
.path
.basename(info
.filename
))
70 apk_contents
.extract(info
, tmpdir
)
71 file = os
.path
.join(tmpdir
, info
.filename
)
73 out
= subprocess
.check_output([szip
, '-d', file], stderr
=subprocess
.STDOUT
)
74 self
.device
.pushFile(os
.path
.join(tmpdir
, info
.filename
), remote_file
)
77 elif self
.options
.local_lib
:
78 for file in os
.listdir(self
.options
.local_lib
):
79 if file.endswith(".so"):
80 print >> sys
.stderr
, "Pushing %s.." % file
81 remote_file
= posixpath
.join(self
.remote_bin_dir
, file)
82 self
.device
.pushFile(os
.path
.join(self
.options
.local_lib
, file), remote_file
)
83 # Additional libraries may be found in a sub-directory such as "lib/armeabi-v7a"
84 local_arm_lib
= os
.path
.join(self
.options
.local_lib
, "lib")
85 if os
.path
.isdir(local_arm_lib
):
86 for root
, dirs
, files
in os
.walk(local_arm_lib
):
88 if (file.endswith(".so")):
89 remote_file
= posixpath
.join(self
.remote_bin_dir
, file)
90 self
.device
.pushFile(os
.path
.join(root
, file), remote_file
)
92 def push_progs(self
, progs
):
93 for local_file
in progs
:
94 remote_file
= posixpath
.join(self
.remote_bin_dir
, os
.path
.basename(local_file
))
95 self
.device
.pushFile(local_file
, remote_file
)
97 def build_environment(self
):
98 env
= self
.build_core_environment()
99 env
['LD_LIBRARY_PATH'] = self
.remote_bin_dir
100 env
["TMPDIR"]=self
.remote_tmp_dir
101 env
["HOME"]=self
.remote_home_dir
102 env
["MOZILLA_FIVE_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]] = ""
112 self
.log
.warning("invalid --addEnv option skipped: %s" % envdef
)
116 def run_one_test(self
, prog
, env
, symbols_path
=None, interactive
=False):
118 Run a single C++ unit test program remotely.
121 * prog: The path to the test program to run.
122 * env: The environment to use for running the program.
123 * symbols_path: A path to a directory containing Breakpad-formatted
124 symbol files for producing stack traces on crash.
126 Return True if the program exits with a zero status, False otherwise.
128 basename
= os
.path
.basename(prog
)
129 remote_bin
= posixpath
.join(self
.remote_bin_dir
, basename
)
130 self
.log
.test_start(basename
)
131 buf
= StringIO
.StringIO()
132 returncode
= self
.device
.shell([remote_bin
], buf
, env
=env
, cwd
=self
.remote_home_dir
,
133 timeout
=cppunittests
.CPPUnitTests
.TEST_PROC_TIMEOUT
)
134 self
.log
.process_output(basename
, "\n%s" % buf
.getvalue(),
135 command
=[remote_bin
])
136 with mozfile
.TemporaryDirectory() as tempdir
:
137 self
.device
.getDirectory(self
.remote_home_dir
, tempdir
)
138 if mozcrash
.check_for_crashes(tempdir
, symbols_path
,
140 self
.log
.test_end(basename
, status
='CRASH', expected
='PASS')
142 result
= returncode
== 0
144 self
.log
.test_end(basename
, status
='FAIL', expected
='PASS',
145 message
=("test failed with return code %d" %
148 self
.log
.test_end(basename
, status
='PASS', expected
='PASS')
151 class RemoteCPPUnittestOptions(cppunittests
.CPPUnittestOptions
):
153 cppunittests
.CPPUnittestOptions
.__init
__(self
)
156 self
.add_option("--deviceIP", action
="store",
157 type = "string", dest
= "device_ip",
158 help = "ip address of remote device to test")
159 defaults
["device_ip"] = None
161 self
.add_option("--devicePort", action
="store",
162 type = "string", dest
= "device_port",
163 help = "port of remote device to test")
164 defaults
["device_port"] = 20701
166 self
.add_option("--dm_trans", action
="store",
167 type = "string", dest
= "dm_trans",
168 help = "the transport to use to communicate with device: [adb|sut]; default=sut")
169 defaults
["dm_trans"] = "sut"
171 self
.add_option("--noSetup", action
="store_false",
173 help = "do not copy any files to device (to be used only if device is already setup)")
174 defaults
["setup"] = True
176 self
.add_option("--localLib", action
="store",
177 type = "string", dest
= "local_lib",
178 help = "location of libraries to push -- preferably stripped")
179 defaults
["local_lib"] = None
181 self
.add_option("--apk", action
="store",
182 type = "string", dest
= "local_apk",
183 help = "local path to Fennec APK")
184 defaults
["local_apk"] = None
186 self
.add_option("--localBinDir", action
="store",
187 type = "string", dest
= "local_bin",
188 help = "local path to bin directory")
189 defaults
["local_bin"] = build_obj
.bindir
if build_obj
is not None else None
191 self
.add_option("--remoteTestRoot", action
= "store",
192 type = "string", dest
= "remote_test_root",
193 help = "remote directory to use as test root (eg. /data/local/tests)")
194 self
.add_option("--with-b2g-emulator", action
= "store",
195 type = "string", dest
= "with_b2g_emulator",
196 help = "Start B2G Emulator (specify path to b2g home)")
197 # /data/local/tests is used because it is usually not possible to set +x permissions
198 # on binaries on /mnt/sdcard
199 defaults
["remote_test_root"] = "/data/local/tests"
201 self
.add_option("--addEnv", action
= "append",
202 type = "string", dest
= "add_env",
203 help = "additional remote environment variable definitions (eg. --addEnv \"somevar=something\")")
204 defaults
["add_env"] = None
206 self
.set_defaults(**defaults
)
209 parser
= RemoteCPPUnittestOptions()
210 structured
.commandline
.add_logging_group(parser
)
211 options
, args
= parser
.parse_args()
213 print >>sys
.stderr
, """Usage: %s <test binary> [<test binary>...]""" % sys
.argv
[0]
215 if options
.local_lib
is not None and not os
.path
.isdir(options
.local_lib
):
216 print >>sys
.stderr
, """Error: --localLib directory %s not found""" % options
.local_lib
218 if options
.local_apk
is not None and not os
.path
.isfile(options
.local_apk
):
219 print >>sys
.stderr
, """Error: --apk file %s not found""" % options
.local_apk
221 if not options
.xre_path
:
222 print >>sys
.stderr
, """Error: --xre-path is required"""
224 if options
.with_b2g_emulator
:
225 from mozrunner
import B2GEmulatorRunner
226 runner
= B2GEmulatorRunner(b2g_home
=options
.with_b2g_emulator
)
228 if options
.dm_trans
== "adb":
229 if options
.with_b2g_emulator
:
230 # because we just started the emulator, we need more than the
231 # default number of retries here.
236 if options
.device_ip
:
237 dm
= devicemanagerADB
.DeviceManagerADB(options
.device_ip
, options
.device_port
, packageName
=None, deviceRoot
=options
.remote_test_root
, retryLimit
=retryLimit
)
239 dm
= devicemanagerADB
.DeviceManagerADB(packageName
=None, deviceRoot
=options
.remote_test_root
, retryLimit
=retryLimit
)
241 if options
.with_b2g_emulator
:
246 dm
= devicemanagerSUT
.DeviceManagerSUT(options
.device_ip
, options
.device_port
, deviceRoot
=options
.remote_test_root
)
247 if not options
.device_ip
:
248 print "Error: you must provide a device IP to connect to via the --deviceIP option"
251 log
= structured
.commandline
.setup_logging("remotecppunittests",
253 {"tbpl": sys
.stdout
})
256 options
.xre_path
= os
.path
.abspath(options
.xre_path
)
257 if options
.with_b2g_emulator
:
258 environ
= {'os': 'b2g'}
260 environ
= {'os': 'android'}
261 progs
= cppunittests
.extract_unittests_from_args(args
, environ
)
262 tester
= RemoteCPPUnitTests(dm
, options
, progs
)
264 result
= tester
.run_tests(progs
, options
.xre_path
, options
.symbols_path
)
268 if options
.with_b2g_emulator
:
271 sys
.exit(0 if result
else 1)
273 if __name__
== '__main__':