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
9 from argparse
import Namespace
17 import runxpcshelltests
as xpcshell
21 from zipfile
import ZipFile
24 from mozdevice
import ADBDevice
, ADBDeviceFactory
, ADBTimeoutError
27 from mozlog
import commandline
29 from xpcshellcommandline
import parser_remote
31 here
= os
.path
.dirname(os
.path
.abspath(__file__
))
34 class RemoteProcessMonitor(object):
37 def __init__(self
, package
, device
, log
, remoteLogFile
):
38 self
.package
= package
41 self
.remoteLogFile
= remoteLogFile
42 self
.selectedProcess
= -1
45 def pickUnusedProcess(cls
):
46 for i
in range(len(cls
.processStatus
)):
47 if not cls
.processStatus
[i
]:
48 cls
.processStatus
[i
] = True
50 # No more free processes :(
54 def freeProcess(cls
, processId
):
55 cls
.processStatus
[processId
] = False
58 self
.device
.pkill(self
.process_name
, sig
=9, attempts
=1)
60 def launch_service(self
, extra_args
, env
, selectedProcess
):
61 if not self
.device
.process_exist(self
.package
):
62 # Make sure the main app is running, this should help making the
63 # tests get foreground priority scheduling.
64 self
.device
.launch_activity(
66 intent
="org.mozilla.geckoview.test.XPCSHELL_TEST_MAIN",
67 activity_name
="TestRunnerActivity",
70 # Newer Androids require that background services originate from
71 # active apps, so wait here until the test runner is the top
74 top
= self
.device
.get_top_activity(timeout
=60)
75 while top
!= self
.package
and retries
> 0:
76 self
.log
.info("Checking that %s is the top activity." % self
.package
)
77 top
= self
.device
.get_top_activity(timeout
=60)
81 self
.process_name
= self
.package
+ (":xpcshell%d" % selectedProcess
)
82 self
.device
.launch_service(
84 activity_name
=("XpcshellTestRunnerService$i%d" % selectedProcess
),
87 grant_runtime_permissions
=False,
88 extra_args
=extra_args
,
89 out_file
=self
.remoteLogFile
,
93 def wait(self
, timeout
, interval
=0.1):
97 # wait for log creation on startup
99 while retries
< 20 / interval
and not self
.device
.is_file(self
.remoteLogFile
):
102 if not self
.device
.is_file(self
.remoteLogFile
):
104 "Failed wait for remote log: %s missing?" % self
.remoteLogFile
107 while self
.device
.process_exist(self
.process_name
):
111 if timeout
and timer
> timeout
:
113 self
.log
.info("Timing out...")
121 Determine the pid of the remote process (or the first process with
124 procs
= self
.device
.get_process_list()
125 # limit the comparison to the first 75 characters due to a
126 # limitation in processname length in android.
127 pids
= [proc
[0] for proc
in procs
if proc
[1] == self
.process_name
[:75]]
128 if pids
is None or len(pids
) < 1:
133 class RemoteXPCShellTestThread(xpcshell
.XPCShellTestThread
):
134 def __init__(self
, *args
, **kwargs
):
135 xpcshell
.XPCShellTestThread
.__init
__(self
, *args
, **kwargs
)
137 self
.shellReturnCode
= None
138 # embed the mobile params from the harness into the TestThread
139 mobileArgs
= kwargs
.get("mobileArgs")
140 for key
in mobileArgs
:
141 setattr(self
, key
, mobileArgs
[key
])
142 self
.remoteLogFile
= posixpath
.join(
143 mobileArgs
["remoteLogFolder"], "xpcshell-%s.log" % str(uuid
.uuid4())
146 def initDir(self
, path
, mask
="777", timeout
=None):
147 """Initialize a directory by removing it if it exists, creating it
148 and changing the permissions."""
149 self
.device
.rm(path
, recursive
=True, force
=True, timeout
=timeout
)
150 self
.device
.mkdir(path
, parents
=True, timeout
=timeout
)
152 def updateTestPrefsFile(self
):
153 # The base method will either be no-op (and return the existing
154 # remote path), or return a path to a new local file.
155 testPrefsFile
= xpcshell
.XPCShellTestThread
.updateTestPrefsFile(self
)
156 if testPrefsFile
== self
.rootPrefsFile
:
157 # The pref file is the shared one, which has been already pushed on the
158 # device, and so there is nothing more to do here.
159 return self
.rootPrefsFile
161 # Push the per-test prefs file in the remote temp dir.
162 remoteTestPrefsFile
= posixpath
.join(self
.remoteTmpDir
, "user.js")
163 self
.device
.push(testPrefsFile
, remoteTestPrefsFile
)
164 self
.device
.chmod(remoteTestPrefsFile
)
165 os
.remove(testPrefsFile
)
166 return remoteTestPrefsFile
168 def buildCmdTestFile(self
, name
):
169 remoteDir
= self
.remoteForLocal(os
.path
.dirname(name
))
170 if remoteDir
== self
.remoteHere
:
171 remoteName
= os
.path
.basename(name
)
173 remoteName
= posixpath
.join(remoteDir
, os
.path
.basename(name
))
176 'const _TEST_CWD = "%s";' % self
.remoteHere
,
178 'const _TEST_FILE = ["%s"];' % remoteName
.replace("\\", "/"),
181 def remoteForLocal(self
, local
):
182 for mapping
in self
.pathMapping
:
183 if os
.path
.abspath(mapping
.local
) == os
.path
.abspath(local
):
184 return mapping
.remote
187 def setupTempDir(self
):
188 self
.remoteTmpDir
= posixpath
.join(self
.remoteTmpDir
, str(uuid
.uuid4()))
189 # make sure the temp dir exists
190 self
.initDir(self
.remoteTmpDir
)
191 # env var is set in buildEnvironment
192 self
.env
["XPCSHELL_TEST_TEMP_DIR"] = self
.remoteTmpDir
193 return self
.remoteTmpDir
195 def setupPluginsDir(self
):
196 if not os
.path
.isdir(self
.pluginsPath
):
199 # making sure tmp dir is set up
202 pluginsDir
= posixpath
.join(self
.remoteTmpDir
, "plugins")
203 self
.device
.push(self
.pluginsPath
, pluginsDir
)
204 self
.device
.chmod(pluginsDir
)
206 self
.log
.info("plugins dir is %s" % pluginsDir
)
209 def setupProfileDir(self
):
210 profileId
= str(uuid
.uuid4())
211 self
.profileDir
= posixpath
.join(self
.profileDir
, profileId
)
212 self
.initDir(self
.profileDir
)
213 if self
.interactive
or self
.singleFile
:
214 self
.log
.info("profile dir is %s" % self
.profileDir
)
215 self
.env
["XPCSHELL_TEST_PROFILE_DIR"] = self
.profileDir
216 self
.env
["TMPDIR"] = self
.profileDir
217 self
.remoteMinidumpDir
= posixpath
.join(self
.remoteMinidumpRootDir
, profileId
)
218 self
.initDir(self
.remoteMinidumpDir
)
219 self
.env
["XPCSHELL_MINIDUMP_DIR"] = self
.remoteMinidumpDir
220 return self
.profileDir
222 def clean_temp_dirs(self
, name
):
223 self
.log
.info("Cleaning up profile for %s folder: %s" % (name
, self
.profileDir
))
224 self
.device
.rm(self
.profileDir
, force
=True, recursive
=True)
225 self
.device
.rm(self
.remoteTmpDir
, force
=True, recursive
=True)
226 self
.device
.rm(self
.remoteMinidumpDir
, force
=True, recursive
=True)
228 def setupMozinfoJS(self
):
229 local
= tempfile
.mktemp()
230 mozinfo
.output_to_file(local
)
231 mozInfoJSPath
= posixpath
.join(self
.profileDir
, "mozinfo.json")
232 self
.device
.push(local
, mozInfoJSPath
)
233 self
.device
.chmod(mozInfoJSPath
)
237 def logCommand(self
, name
, completeCmd
, testdir
):
238 self
.log
.info("%s | full command: %r" % (name
, completeCmd
))
239 self
.log
.info("%s | current directory: %r" % (name
, self
.remoteHere
))
240 self
.log
.info("%s | environment: %s" % (name
, self
.env
))
242 def getHeadFiles(self
, test
):
243 """Override parent method to find files on remote device.
245 Obtains lists of head- files. Returns a list of head files.
248 def sanitize_list(s
, kind
):
249 for f
in s
.strip().split(" "):
254 path
= posixpath
.join(self
.remoteHere
, f
)
256 # skip check for file existence: the convenience of discovering
257 # a missing file does not justify the time cost of the round trip
261 self
.remoteHere
= self
.remoteForLocal(test
["here"])
263 headlist
= test
.get("head", "")
264 return list(sanitize_list(headlist
, "head"))
266 def buildXpcsCmd(self
):
267 # change base class' paths to remote paths and use base class to build command
268 self
.xpcshell
= posixpath
.join(self
.remoteBinDir
, "xpcw")
269 self
.headJSPath
= posixpath
.join(self
.remoteScriptsDir
, "head.js")
270 self
.httpdJSPath
= posixpath
.join(self
.remoteComponentsDir
, "httpd.js")
271 self
.testingModulesDir
= self
.remoteModulesDir
272 self
.testharnessdir
= self
.remoteScriptsDir
273 xpcsCmd
= xpcshell
.XPCShellTestThread
.buildXpcsCmd(self
)
274 # remove "-g <dir> -a <dir>" and replace with remote alternatives
276 if self
.options
["localAPK"]:
277 xpcsCmd
.insert(1, "--greomni")
278 xpcsCmd
.insert(2, self
.remoteAPK
)
279 xpcsCmd
.insert(1, "-g")
280 xpcsCmd
.insert(2, self
.remoteBinDir
)
282 if self
.remoteDebugger
:
283 # for example, "/data/local/gdbserver" "localhost:12345"
284 xpcsCmd
= [self
.remoteDebugger
, self
.remoteDebuggerArgs
] + xpcsCmd
287 def killTimeout(self
, proc
):
290 def launchProcess(self
, cmd
, stdout
, stderr
, env
, cwd
, timeout
=None):
291 rpm
= RemoteProcessMonitor(
292 "org.mozilla.geckoview.test",
298 startTime
= datetime
.datetime
.now()
301 pid
= rpm
.launch_service(cmd
[1:], self
.env
, self
.selectedProcess
)
302 except Exception as e
:
304 "remotexpcshelltests.py | Failed to start process: %s" % str(e
)
306 self
.shellReturnCode
= 1
309 self
.log
.info("remotexpcshelltests.py | Launched Test App PID=%s" % str(pid
))
311 if rpm
.wait(timeout
):
312 self
.shellReturnCode
= 0
314 self
.shellReturnCode
= 1
316 "remotexpcshelltests.py | Application ran for: %s"
317 % str(datetime
.datetime
.now() - startTime
)
321 return self
.device
.get_file(self
.remoteLogFile
)
322 except mozdevice
.ADBTimeoutError
:
324 except Exception as e
:
326 "remotexpcshelltests.py | Could not read log file: %s" % str(e
)
328 self
.shellReturnCode
= 1
331 def checkForCrashes(self
, dump_directory
, symbols_path
, test_name
=None):
332 with mozfile
.TemporaryDirectory() as dumpDir
:
333 self
.device
.pull(self
.remoteMinidumpDir
, dumpDir
)
334 crashed
= mozcrash
.log_crashes(
335 self
.log
, dumpDir
, symbols_path
, test
=test_name
339 def communicate(self
, proc
):
342 def poll(self
, proc
):
343 if not self
.device
.process_exist("xpcshell"):
344 return self
.getReturnCode(proc
)
345 # Process is still running
348 def kill(self
, proc
):
349 return self
.device
.pkill("xpcshell")
351 def getReturnCode(self
, proc
):
352 if self
.shellReturnCode
is not None:
353 return self
.shellReturnCode
357 def removeDir(self
, dirname
):
359 self
.device
.rm(dirname
, recursive
=True)
360 except ADBTimeoutError
:
362 except Exception as e
:
363 self
.log
.warning(str(e
))
365 def createLogFile(self
, test
, stdout
):
366 filename
= test
.replace("\\", "/").split("/")[-1] + ".log"
367 with
open(filename
, "wb") as f
:
371 # A specialization of XPCShellTests that runs tests on an Android device.
372 class XPCShellRemote(xpcshell
.XPCShellTests
, object):
373 def __init__(self
, options
, log
):
374 xpcshell
.XPCShellTests
.__init
__(self
, log
)
376 options
["threadCount"] = min(options
["threadCount"] or 4, 4)
378 self
.options
= options
380 if options
["log_tbpl_level"] == "debug" or options
["log_mach_level"] == "debug":
382 self
.device
= ADBDeviceFactory(
383 adb
=options
["adbPath"] or "adb",
384 device
=options
["deviceSerial"],
385 test_root
=options
["remoteTestRoot"],
388 self
.remoteTestRoot
= posixpath
.join(self
.device
.test_root
, "xpc")
389 self
.remoteLogFolder
= posixpath
.join(self
.remoteTestRoot
, "logs")
390 # Add Android version (SDK level) to mozinfo so that manifest entries
391 # can be conditional on android_version.
392 mozinfo
.info
["android_version"] = str(self
.device
.version
)
393 mozinfo
.info
["is_emulator"] = self
.device
._device
_serial
.startswith("emulator-")
395 self
.localBin
= options
["localBin"]
396 self
.pathMapping
= []
397 # remoteBinDir contains xpcshell and its wrapper script, both of which must
398 # be executable. Since +x permissions cannot usually be set on /mnt/sdcard,
399 # and the test root may be on /mnt/sdcard, remoteBinDir is set to be on
400 # /data/local, always.
401 self
.remoteBinDir
= posixpath
.join(self
.device
.test_root
, "xpcb")
402 # Terse directory names are used here ("c" for the components directory)
403 # to minimize the length of the command line used to execute
404 # xpcshell on the remote device. adb has a limit to the number
405 # of characters used in a shell command, and the xpcshell command
406 # line can be quite complex.
407 self
.remoteTmpDir
= posixpath
.join(self
.remoteTestRoot
, "tmp")
408 self
.remoteScriptsDir
= self
.remoteTestRoot
409 self
.remoteComponentsDir
= posixpath
.join(self
.remoteTestRoot
, "c")
410 self
.remoteModulesDir
= posixpath
.join(self
.remoteTestRoot
, "m")
411 self
.remoteMinidumpRootDir
= posixpath
.join(self
.remoteTestRoot
, "minidumps")
412 self
.profileDir
= posixpath
.join(self
.remoteTestRoot
, "p")
413 self
.remoteDebugger
= options
["debugger"]
414 self
.remoteDebuggerArgs
= options
["debuggerArgs"]
415 self
.testingModulesDir
= options
["testingModulesDir"]
417 self
.initDir(self
.remoteTmpDir
)
418 self
.initDir(self
.profileDir
)
420 # Make sure we get a fresh start
421 self
.device
.stop_application("org.mozilla.geckoview.test")
423 for i
in range(options
["threadCount"]):
424 RemoteProcessMonitor
.processStatus
+= [False]
428 if options
["objdir"]:
429 self
.xpcDir
= os
.path
.join(options
["objdir"], "_tests/xpcshell")
430 elif os
.path
.isdir(os
.path
.join(here
, "tests")):
431 self
.xpcDir
= os
.path
.join(here
, "tests")
433 print("Couldn't find local xpcshell test directory", file=sys
.stderr
)
436 self
.remoteAPK
= None
437 if options
["localAPK"]:
438 self
.localAPKContents
= ZipFile(options
["localAPK"])
439 self
.remoteAPK
= posixpath
.join(
440 self
.remoteBinDir
, os
.path
.basename(options
["localAPK"])
443 self
.localAPKContents
= None
446 self
.setupUtilities()
448 self
.initDir(self
.remoteMinidumpRootDir
)
449 self
.initDir(self
.remoteLogFolder
)
451 # data that needs to be passed to the RemoteXPCShellTestThread
453 "device": self
.device
,
454 "remoteBinDir": self
.remoteBinDir
,
455 "remoteScriptsDir": self
.remoteScriptsDir
,
456 "remoteComponentsDir": self
.remoteComponentsDir
,
457 "remoteModulesDir": self
.remoteModulesDir
,
458 "options": self
.options
,
459 "remoteDebugger": self
.remoteDebugger
,
460 "remoteDebuggerArgs": self
.remoteDebuggerArgs
,
461 "pathMapping": self
.pathMapping
,
462 "profileDir": self
.profileDir
,
463 "remoteLogFolder": self
.remoteLogFolder
,
464 "remoteTmpDir": self
.remoteTmpDir
,
465 "remoteMinidumpRootDir": self
.remoteMinidumpRootDir
,
468 self
.mobileArgs
["remoteAPK"] = self
.remoteAPK
470 def initDir(self
, path
, mask
="777", timeout
=None):
471 """Initialize a directory by removing it if it exists, creating it
472 and changing the permissions."""
473 self
.device
.rm(path
, recursive
=True, force
=True, timeout
=timeout
)
474 self
.device
.mkdir(path
, parents
=True, timeout
=timeout
)
476 def setLD_LIBRARY_PATH(self
):
477 self
.env
["LD_LIBRARY_PATH"] = self
.remoteBinDir
479 def pushWrapper(self
):
480 # Rather than executing xpcshell directly, this wrapper script is
481 # used. By setting environment variables and the cwd in the script,
482 # the length of the per-test command line is shortened. This is
483 # often important when using ADB, as there is a limit to the length
484 # of the ADB command line.
485 localWrapper
= tempfile
.mktemp()
486 with
open(localWrapper
, "w") as f
:
487 f
.write("#!/system/bin/sh\n")
488 for envkey
, envval
in six
.iteritems(self
.env
):
489 f
.write("export %s=%s\n" % (envkey
, envval
))
493 "echo xpcw: cd $1\n",
495 'echo xpcw: xpcshell "$@"\n',
496 '%s/xpcshell "$@"\n' % self
.remoteBinDir
,
499 remoteWrapper
= posixpath
.join(self
.remoteBinDir
, "xpcw")
500 self
.device
.push(localWrapper
, remoteWrapper
)
501 self
.device
.chmod(remoteWrapper
)
502 os
.remove(localWrapper
)
504 def start_test(self
, test
):
505 test
.selectedProcess
= RemoteProcessMonitor
.pickUnusedProcess()
506 if test
.selectedProcess
== -1:
508 "TEST-UNEXPECTED-FAIL | remotexpcshelltests.py | "
509 "no more free processes"
513 def test_ended(self
, test
):
514 RemoteProcessMonitor
.freeProcess(test
.selectedProcess
)
516 def buildPrefsFile(self
, extraPrefs
):
517 prefs
= super(XPCShellRemote
, self
).buildPrefsFile(extraPrefs
)
518 remotePrefsFile
= posixpath
.join(self
.remoteTestRoot
, "user.js")
519 self
.device
.push(self
.prefsFile
, remotePrefsFile
)
520 self
.device
.chmod(remotePrefsFile
)
521 # os.remove(self.prefsFile) is not called despite having pushed the
522 # file to the device, because the local file is relied upon by the
523 # updateTestPrefsFile method
524 self
.prefsFile
= remotePrefsFile
527 def buildEnvironment(self
):
528 self
.buildCoreEnvironment()
529 self
.setLD_LIBRARY_PATH()
530 self
.env
["MOZ_LINKER_CACHE"] = self
.remoteBinDir
531 self
.env
["GRE_HOME"] = self
.remoteBinDir
532 self
.env
["XPCSHELL_TEST_PROFILE_DIR"] = self
.profileDir
533 self
.env
["HOME"] = self
.profileDir
534 self
.env
["XPCSHELL_TEST_TEMP_DIR"] = self
.remoteTmpDir
535 self
.env
["MOZ_ANDROID_DATA_DIR"] = self
.remoteBinDir
536 self
.env
["MOZ_IN_AUTOMATION"] = "1"
538 # Guard against intermittent failures to retrieve abi property;
539 # without an abi, xpcshell cannot find greprefs.js and crashes.
543 while not abi
and retries
< 3:
544 abi
= self
.device
.get_prop("ro.product.cpu.abi")
547 raise Exception("failed to get ro.product.cpu.abi from device")
548 self
.log
.info("ro.product.cpu.abi %s" % abi
)
549 if self
.localAPKContents
:
552 while not abilistprop
and retries
< 3:
553 abilistprop
= self
.device
.get_prop("ro.product.cpu.abilist")
555 self
.log
.info("ro.product.cpu.abilist %s" % abilistprop
)
558 n
for n
in self
.localAPKContents
.namelist() if n
.startswith("lib/")
560 self
.log
.debug("apk names: %s" % names
)
561 if abilistprop
and len(abilistprop
) > 0:
562 abilist
.extend(abilistprop
.split(","))
563 for abicand
in abilist
:
565 len([n
for n
in names
if n
.startswith("lib/%s" % abicand
)]) > 0
571 self
.log
.info("failed to get matching abi from apk.")
574 "device cpu abi not found in apk. Using abi from apk."
576 abi
= names
[0].split("/")[1]
577 self
.log
.info("Using abi %s." % abi
)
578 self
.env
["MOZ_ANDROID_CPU_ABI"] = abi
579 self
.log
.info("Using env %r" % (self
.env
,))
581 def setupUtilities(self
):
582 self
.initDir(self
.remoteTmpDir
)
583 self
.initDir(self
.remoteBinDir
)
584 remotePrefDir
= posixpath
.join(self
.remoteBinDir
, "defaults", "pref")
585 self
.initDir(posixpath
.join(remotePrefDir
, "extra"))
586 self
.initDir(self
.remoteComponentsDir
)
588 local
= os
.path
.join(os
.path
.dirname(os
.path
.abspath(__file__
)), "head.js")
589 remoteFile
= posixpath
.join(self
.remoteScriptsDir
, "head.js")
590 self
.device
.push(local
, remoteFile
)
591 self
.device
.chmod(remoteFile
)
593 # Additional binaries are required for some tests. This list should be
594 # similar to TEST_HARNESS_BINS in testing/mochitest/Makefile.in.
599 "BadCertAndPinningServer",
600 "DelegatedCredentialsServer",
601 "EncryptedClientHelloServer",
602 "OCSPStaplingServer",
603 "GenerateOCSPResponse",
604 "SanctionsTestServer",
606 for fname
in binaries
:
607 local
= os
.path
.join(self
.localBin
, fname
)
608 if os
.path
.isfile(local
):
609 print("Pushing %s.." % fname
, file=sys
.stderr
)
610 remoteFile
= posixpath
.join(self
.remoteBinDir
, fname
)
611 self
.device
.push(local
, remoteFile
)
612 self
.device
.chmod(remoteFile
)
615 "*** Expected binary %s not found in %s!" % (fname
, self
.localBin
),
619 local
= os
.path
.join(self
.localBin
, "components/httpd.js")
620 remoteFile
= posixpath
.join(self
.remoteComponentsDir
, "httpd.js")
621 self
.device
.push(local
, remoteFile
)
622 self
.device
.chmod(remoteFile
)
624 if self
.options
["localAPK"]:
625 remoteFile
= posixpath
.join(
626 self
.remoteBinDir
, os
.path
.basename(self
.options
["localAPK"])
628 self
.device
.push(self
.options
["localAPK"], remoteFile
)
629 self
.device
.chmod(remoteFile
)
633 localB2G
= os
.path
.join(self
.options
["objdir"], "dist", "b2g")
634 if os
.path
.exists(localB2G
):
635 self
.device
.push(localB2G
, self
.remoteBinDir
)
636 self
.device
.chmod(self
.remoteBinDir
)
638 raise Exception("unable to install gre: no APK and not b2g")
641 pushed_libs_count
= 0
643 dir = tempfile
.mkdtemp()
644 for info
in self
.localAPKContents
.infolist():
645 if info
.filename
.endswith(".so"):
646 print("Pushing %s.." % info
.filename
, file=sys
.stderr
)
647 remoteFile
= posixpath
.join(
648 self
.remoteBinDir
, os
.path
.basename(info
.filename
)
650 self
.localAPKContents
.extract(info
, dir)
651 localFile
= os
.path
.join(dir, info
.filename
)
652 self
.device
.push(localFile
, remoteFile
)
653 pushed_libs_count
+= 1
654 self
.device
.chmod(remoteFile
)
657 return pushed_libs_count
659 def setupModules(self
):
660 if self
.testingModulesDir
:
661 self
.device
.push(self
.testingModulesDir
, self
.remoteModulesDir
)
662 self
.device
.chmod(self
.remoteModulesDir
)
664 def setupTestDir(self
):
665 print("pushing %s" % self
.xpcDir
)
666 # The tests directory can be quite large: 5000 files and growing!
667 # Sometimes - like on a low-end aws instance running an emulator - the push
668 # may exceed the default 5 minute timeout, so we increase it here to 10 minutes.
669 self
.device
.rm(self
.remoteScriptsDir
, recursive
=True, force
=True, timeout
=None)
670 self
.device
.push(self
.xpcDir
, self
.remoteScriptsDir
, timeout
=600)
671 self
.device
.chmod(self
.remoteScriptsDir
, recursive
=True)
673 def setupSocketConnections(self
):
674 # make node host ports visible to device
675 if "MOZHTTP2_PORT" in self
.env
:
676 port
= "tcp:{}".format(self
.env
["MOZHTTP2_PORT"])
677 self
.device
.create_socket_connection(
678 ADBDevice
.SOCKET_DIRECTION_REVERSE
, port
, port
680 self
.log
.info("reversed MOZHTTP2_PORT connection for port " + port
)
681 if "MOZNODE_EXEC_PORT" in self
.env
:
682 port
= "tcp:{}".format(self
.env
["MOZNODE_EXEC_PORT"])
683 self
.device
.create_socket_connection(
684 ADBDevice
.SOCKET_DIRECTION_REVERSE
, port
, port
686 self
.log
.info("reversed MOZNODE_EXEC_PORT connection for port " + port
)
688 def buildTestList(self
, test_tags
=None, test_paths
=None, verify
=False):
689 xpcshell
.XPCShellTests
.buildTestList(
690 self
, test_tags
=test_tags
, test_paths
=test_paths
, verify
=verify
692 uniqueTestPaths
= set([])
693 for test
in self
.alltests
:
694 uniqueTestPaths
.add(test
["here"])
695 for testdir
in uniqueTestPaths
:
696 abbrevTestDir
= os
.path
.relpath(testdir
, self
.xpcDir
)
697 remoteScriptDir
= posixpath
.join(self
.remoteScriptsDir
, abbrevTestDir
)
698 self
.pathMapping
.append(PathMapping(testdir
, remoteScriptDir
))
699 # This is not related to building the test list, but since this is called late
700 # in the test suite run, this is a convenient place to finalize preparations;
701 # in particular, these operations cannot be executed much earlier because
702 # self.env may not be finalized.
703 self
.setupSocketConnections()
704 if self
.options
["setup"]:
708 def verifyRemoteOptions(parser
, options
):
709 if isinstance(options
, Namespace
):
710 options
= vars(options
)
712 if options
["localBin"] is None:
713 if options
["objdir"]:
714 options
["localBin"] = os
.path
.join(options
["objdir"], "dist", "bin")
715 if not os
.path
.isdir(options
["localBin"]):
716 parser
.error("Couldn't find local binary dir, specify --local-bin-dir")
717 elif os
.path
.isfile(os
.path
.join(here
, "..", "bin", "xpcshell")):
718 # assume tests are being run from a tests archive
719 options
["localBin"] = os
.path
.abspath(os
.path
.join(here
, "..", "bin"))
721 parser
.error("Couldn't find local binary dir, specify --local-bin-dir")
726 def __init__(self
, localDir
, remoteDir
):
727 self
.local
= localDir
728 self
.remote
= remoteDir
732 if sys
.version_info
< (2, 7):
734 "Error: You must use python version 2.7 or newer but less than 3.0",
739 parser
= parser_remote()
740 options
= parser
.parse_args()
742 options
= verifyRemoteOptions(parser
, options
)
743 log
= commandline
.setup_logging("Remote XPCShell", options
, {"tbpl": sys
.stdout
})
745 if options
["interactive"] and not options
["testPath"]:
747 "Error: You must specify a test filename in interactive mode!",
752 if options
["xpcshell"] is None:
753 options
["xpcshell"] = "xpcshell"
755 # The threadCount depends on the emulator rather than the host machine and
756 # empirically 10 seems to yield the best performance.
757 options
["threadCount"] = min(options
["threadCount"], 10)
759 xpcsh
= XPCShellRemote(options
, log
)
761 if not xpcsh
.runTests(
762 options
, testClass
=RemoteXPCShellTestThread
, mobileArgs
=xpcsh
.mobileArgs
767 if __name__
== "__main__":