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/.
15 from argparse
import Namespace
16 from zipfile
import ZipFile
22 import runxpcshelltests
as xpcshell
24 from mozdevice
import ADBDevice
, ADBDeviceFactory
, ADBTimeoutError
25 from mozlog
import commandline
26 from xpcshellcommandline
import parser_remote
28 here
= os
.path
.dirname(os
.path
.abspath(__file__
))
31 class RemoteProcessMonitor(object):
34 def __init__(self
, package
, device
, log
, remoteLogFile
):
35 self
.package
= package
38 self
.remoteLogFile
= remoteLogFile
39 self
.selectedProcess
= -1
42 def pickUnusedProcess(cls
):
43 for i
in range(len(cls
.processStatus
)):
44 if not cls
.processStatus
[i
]:
45 cls
.processStatus
[i
] = True
47 # No more free processes :(
51 def freeProcess(cls
, processId
):
52 cls
.processStatus
[processId
] = False
55 self
.device
.pkill(self
.process_name
, sig
=9, attempts
=1)
57 def launch_service(self
, extra_args
, env
, selectedProcess
, test_name
=None):
58 if not self
.device
.process_exist(self
.package
):
59 # Make sure the main app is running, this should help making the
60 # tests get foreground priority scheduling.
61 self
.device
.launch_activity(
63 intent
="org.mozilla.geckoview.test_runner.XPCSHELL_TEST_MAIN",
64 activity_name
="TestRunnerActivity",
67 # Newer Androids require that background services originate from
68 # active apps, so wait here until the test runner is the top
71 top
= self
.device
.get_top_activity(timeout
=60)
72 while top
!= self
.package
and retries
> 0:
74 "%s | Checking that %s is the top activity."
75 % (test_name
, self
.package
)
77 top
= self
.device
.get_top_activity(timeout
=60)
81 self
.process_name
= self
.package
+ (":xpcshell%d" % selectedProcess
)
84 while retries
> 0 and self
.device
.process_exist(self
.process_name
):
86 "%s | %s | Killing left-over process %s"
87 % (test_name
, self
.pid
, self
.process_name
)
93 if self
.device
.process_exist(self
.process_name
):
95 "%s | %s | Could not kill left-over process" % (test_name
, self
.pid
)
98 self
.device
.launch_service(
100 activity_name
=("XpcshellTestRunnerService$i%d" % selectedProcess
),
103 grant_runtime_permissions
=False,
104 extra_args
=extra_args
,
105 out_file
=self
.remoteLogFile
,
109 def wait(self
, timeout
, interval
=0.1, test_name
=None):
113 # wait for log creation on startup
115 while retries
< 20 / interval
and not self
.device
.is_file(self
.remoteLogFile
):
118 if not self
.device
.is_file(self
.remoteLogFile
):
120 "%s | Failed wait for remote log: %s missing?"
121 % (test_name
, self
.remoteLogFile
)
124 while self
.device
.process_exist(self
.process_name
):
128 if timeout
and timer
> timeout
:
131 "remotexpcshelltests.py | %s | %s | Timing out"
132 % (test_name
, str(self
.pid
))
141 Determine the pid of the remote process (or the first process with
144 procs
= self
.device
.get_process_list()
145 # limit the comparison to the first 75 characters due to a
146 # limitation in processname length in android.
147 pids
= [proc
[0] for proc
in procs
if proc
[1] == self
.process_name
[:75]]
148 if pids
is None or len(pids
) < 1:
153 class RemoteXPCShellTestThread(xpcshell
.XPCShellTestThread
):
154 def __init__(self
, *args
, **kwargs
):
155 xpcshell
.XPCShellTestThread
.__init
__(self
, *args
, **kwargs
)
157 self
.shellReturnCode
= None
158 # embed the mobile params from the harness into the TestThread
159 mobileArgs
= kwargs
.get("mobileArgs")
160 for key
in mobileArgs
:
161 setattr(self
, key
, mobileArgs
[key
])
162 self
.remoteLogFile
= posixpath
.join(
163 mobileArgs
["remoteLogFolder"], "xpcshell-%s.log" % str(uuid
.uuid4())
166 def initDir(self
, path
, mask
="777", timeout
=None):
167 """Initialize a directory by removing it if it exists, creating it
168 and changing the permissions."""
169 self
.device
.rm(path
, recursive
=True, force
=True, timeout
=timeout
)
170 self
.device
.mkdir(path
, parents
=True, timeout
=timeout
)
172 def updateTestPrefsFile(self
):
173 # The base method will either be no-op (and return the existing
174 # remote path), or return a path to a new local file.
175 testPrefsFile
= xpcshell
.XPCShellTestThread
.updateTestPrefsFile(self
)
176 if testPrefsFile
== self
.rootPrefsFile
:
177 # The pref file is the shared one, which has been already pushed on the
178 # device, and so there is nothing more to do here.
179 return self
.rootPrefsFile
181 # Push the per-test prefs file in the remote temp dir.
182 remoteTestPrefsFile
= posixpath
.join(self
.remoteTmpDir
, "user.js")
183 self
.device
.push(testPrefsFile
, remoteTestPrefsFile
)
184 self
.device
.chmod(remoteTestPrefsFile
)
185 os
.remove(testPrefsFile
)
186 return remoteTestPrefsFile
188 def buildCmdTestFile(self
, name
):
189 remoteDir
= self
.remoteForLocal(os
.path
.dirname(name
))
190 if remoteDir
== self
.remoteHere
:
191 remoteName
= os
.path
.basename(name
)
193 remoteName
= posixpath
.join(remoteDir
, os
.path
.basename(name
))
196 'const _TEST_CWD = "%s";' % self
.remoteHere
,
198 'const _TEST_FILE = ["%s"];' % remoteName
.replace("\\", "/"),
201 def remoteForLocal(self
, local
):
202 for mapping
in self
.pathMapping
:
203 if os
.path
.abspath(mapping
.local
) == os
.path
.abspath(local
):
204 return mapping
.remote
207 def setupTempDir(self
):
208 self
.remoteTmpDir
= posixpath
.join(self
.remoteTmpDir
, str(uuid
.uuid4()))
209 # make sure the temp dir exists
210 self
.initDir(self
.remoteTmpDir
)
211 # env var is set in buildEnvironment
212 self
.env
["XPCSHELL_TEST_TEMP_DIR"] = self
.remoteTmpDir
213 return self
.remoteTmpDir
215 def setupProfileDir(self
):
216 profileId
= str(uuid
.uuid4())
217 self
.profileDir
= posixpath
.join(self
.profileDir
, profileId
)
218 self
.initDir(self
.profileDir
)
219 if self
.interactive
or self
.singleFile
:
220 self
.log
.info("profile dir is %s" % self
.profileDir
)
221 self
.env
["XPCSHELL_TEST_PROFILE_DIR"] = self
.profileDir
222 self
.env
["TMPDIR"] = self
.profileDir
223 self
.remoteMinidumpDir
= posixpath
.join(self
.remoteMinidumpRootDir
, profileId
)
224 self
.initDir(self
.remoteMinidumpDir
)
225 self
.env
["XPCSHELL_MINIDUMP_DIR"] = self
.remoteMinidumpDir
226 return self
.profileDir
228 def clean_temp_dirs(self
, name
):
229 self
.log
.info("Cleaning up profile for %s folder: %s" % (name
, self
.profileDir
))
230 self
.device
.rm(self
.profileDir
, force
=True, recursive
=True)
231 self
.device
.rm(self
.remoteTmpDir
, force
=True, recursive
=True)
232 self
.device
.rm(self
.remoteMinidumpDir
, force
=True, recursive
=True)
234 def setupMozinfoJS(self
):
235 local
= tempfile
.mktemp()
236 mozinfo
.output_to_file(local
)
237 mozInfoJSPath
= posixpath
.join(self
.profileDir
, "mozinfo.json")
238 self
.device
.push(local
, mozInfoJSPath
)
239 self
.device
.chmod(mozInfoJSPath
)
243 def logCommand(self
, name
, completeCmd
, testdir
):
244 self
.log
.info("%s | full command: %r" % (name
, completeCmd
))
245 self
.log
.info("%s | current directory: %r" % (name
, self
.remoteHere
))
246 self
.log
.info("%s | environment: %s" % (name
, self
.env
))
248 def getHeadFiles(self
, test
):
249 """Override parent method to find files on remote device.
251 Obtains lists of head- files. Returns a list of head files.
254 def sanitize_list(s
, kind
):
255 for f
in s
.strip().split(" "):
260 path
= posixpath
.join(self
.remoteHere
, f
)
262 # skip check for file existence: the convenience of discovering
263 # a missing file does not justify the time cost of the round trip
267 self
.remoteHere
= self
.remoteForLocal(test
["here"])
269 headlist
= test
.get("head", "")
270 return list(sanitize_list(headlist
, "head"))
272 def buildXpcsCmd(self
):
273 # change base class' paths to remote paths and use base class to build command
274 self
.xpcshell
= posixpath
.join(self
.remoteBinDir
, "xpcw")
275 self
.headJSPath
= posixpath
.join(self
.remoteScriptsDir
, "head.js")
276 self
.testingModulesDir
= self
.remoteModulesDir
277 self
.testharnessdir
= self
.remoteScriptsDir
278 xpcsCmd
= xpcshell
.XPCShellTestThread
.buildXpcsCmd(self
)
279 # remove "-g <dir> -a <dir>" and replace with remote alternatives
281 if self
.options
["localAPK"]:
282 xpcsCmd
.insert(1, "--greomni")
283 xpcsCmd
.insert(2, self
.remoteAPK
)
284 xpcsCmd
.insert(1, "-g")
285 xpcsCmd
.insert(2, self
.remoteBinDir
)
287 if self
.remoteDebugger
:
288 # for example, "/data/local/gdbserver" "localhost:12345"
289 xpcsCmd
= [self
.remoteDebugger
, self
.remoteDebuggerArgs
] + xpcsCmd
292 def killTimeout(self
, proc
):
296 self
, cmd
, stdout
, stderr
, env
, cwd
, timeout
=None, test_name
=None
298 rpm
= RemoteProcessMonitor(
299 "org.mozilla.geckoview.test_runner",
305 startTime
= datetime
.datetime
.now()
308 pid
= rpm
.launch_service(
309 cmd
[1:], self
.env
, self
.selectedProcess
, test_name
=test_name
311 except Exception as e
:
313 "remotexpcshelltests.py | Failed to start process: %s" % str(e
)
315 self
.shellReturnCode
= 1
319 "remotexpcshelltests.py | %s | %s | Launched Test App"
320 % (test_name
, str(pid
))
323 if rpm
.wait(timeout
, test_name
=test_name
):
324 self
.shellReturnCode
= 0
326 self
.shellReturnCode
= 1
328 "remotexpcshelltests.py | %s | %s | Application ran for: %s"
329 % (test_name
, str(pid
), str(datetime
.datetime
.now() - startTime
))
333 return self
.device
.get_file(self
.remoteLogFile
)
334 except mozdevice
.ADBTimeoutError
:
336 except Exception as e
:
338 "remotexpcshelltests.py | %s | %s | Could not read log file: %s"
339 % (test_name
, str(pid
), str(e
))
341 self
.shellReturnCode
= 1
344 def checkForCrashes(self
, dump_directory
, symbols_path
, test_name
=None):
345 with mozfile
.TemporaryDirectory() as dumpDir
:
346 self
.device
.pull(self
.remoteMinidumpDir
, dumpDir
)
347 crashed
= mozcrash
.log_crashes(
348 self
.log
, dumpDir
, symbols_path
, test
=test_name
352 def communicate(self
, proc
):
355 def poll(self
, proc
):
356 if not self
.device
.process_exist("xpcshell"):
357 return self
.getReturnCode(proc
)
358 # Process is still running
361 def kill(self
, proc
):
362 return self
.device
.pkill("xpcshell")
364 def getReturnCode(self
, proc
):
365 if self
.shellReturnCode
is not None:
366 return self
.shellReturnCode
370 def removeDir(self
, dirname
):
372 self
.device
.rm(dirname
, recursive
=True)
373 except ADBTimeoutError
:
375 except Exception as e
:
376 self
.log
.warning(str(e
))
378 def createLogFile(self
, test
, stdout
):
379 filename
= test
.replace("\\", "/").split("/")[-1] + ".log"
380 with
open(filename
, "wb") as f
:
384 # A specialization of XPCShellTests that runs tests on an Android device.
385 class XPCShellRemote(xpcshell
.XPCShellTests
, object):
386 def __init__(self
, options
, log
):
387 xpcshell
.XPCShellTests
.__init
__(self
, log
)
389 options
["threadCount"] = min(options
["threadCount"] or 4, 4)
391 self
.options
= options
393 if options
["log_tbpl_level"] == "debug" or options
["log_mach_level"] == "debug":
395 self
.device
= ADBDeviceFactory(
396 adb
=options
["adbPath"] or "adb",
397 device
=options
["deviceSerial"],
398 test_root
=options
["remoteTestRoot"],
401 self
.remoteTestRoot
= posixpath
.join(self
.device
.test_root
, "xpc")
402 self
.remoteLogFolder
= posixpath
.join(self
.remoteTestRoot
, "logs")
403 # Add Android version (SDK level) to mozinfo so that manifest entries
404 # can be conditional on android_version.
405 mozinfo
.info
["android_version"] = str(self
.device
.version
)
406 mozinfo
.info
["is_emulator"] = self
.device
._device
_serial
.startswith("emulator-")
408 self
.localBin
= options
["localBin"]
409 self
.pathMapping
= []
410 # remoteBinDir contains xpcshell and its wrapper script, both of which must
411 # be executable. Since +x permissions cannot usually be set on /mnt/sdcard,
412 # and the test root may be on /mnt/sdcard, remoteBinDir is set to be on
413 # /data/local, always.
414 self
.remoteBinDir
= posixpath
.join(self
.device
.test_root
, "xpcb")
415 # Terse directory names are used here ("c" for the components directory)
416 # to minimize the length of the command line used to execute
417 # xpcshell on the remote device. adb has a limit to the number
418 # of characters used in a shell command, and the xpcshell command
419 # line can be quite complex.
420 self
.remoteTmpDir
= posixpath
.join(self
.remoteTestRoot
, "tmp")
421 self
.remoteScriptsDir
= self
.remoteTestRoot
422 self
.remoteComponentsDir
= posixpath
.join(self
.remoteTestRoot
, "c")
423 self
.remoteModulesDir
= posixpath
.join(self
.remoteTestRoot
, "m")
424 self
.remoteMinidumpRootDir
= posixpath
.join(self
.remoteTestRoot
, "minidumps")
425 self
.profileDir
= posixpath
.join(self
.remoteTestRoot
, "p")
426 self
.remoteDebugger
= options
["debugger"]
427 self
.remoteDebuggerArgs
= options
["debuggerArgs"]
428 self
.testingModulesDir
= options
["testingModulesDir"]
430 self
.initDir(self
.remoteTmpDir
)
431 self
.initDir(self
.profileDir
)
433 # Make sure we get a fresh start
434 self
.device
.stop_application("org.mozilla.geckoview.test_runner")
436 for i
in range(options
["threadCount"]):
437 RemoteProcessMonitor
.processStatus
+= [False]
441 if options
["objdir"]:
442 self
.xpcDir
= os
.path
.join(options
["objdir"], "_tests/xpcshell")
443 elif os
.path
.isdir(os
.path
.join(here
, "tests")):
444 self
.xpcDir
= os
.path
.join(here
, "tests")
446 print("Couldn't find local xpcshell test directory", file=sys
.stderr
)
449 self
.remoteAPK
= None
450 if options
["localAPK"]:
451 self
.localAPKContents
= ZipFile(options
["localAPK"])
452 self
.remoteAPK
= posixpath
.join(
453 self
.remoteBinDir
, os
.path
.basename(options
["localAPK"])
456 self
.localAPKContents
= None
459 self
.setupUtilities()
461 self
.initDir(self
.remoteMinidumpRootDir
)
462 self
.initDir(self
.remoteLogFolder
)
464 eprefs
= options
.get("extraPrefs") or []
465 if options
.get("disableFission"):
466 eprefs
.append("fission.autostart=false")
468 # should be by default, just in case
469 eprefs
.append("fission.autostart=true")
470 options
["extraPrefs"] = eprefs
472 # data that needs to be passed to the RemoteXPCShellTestThread
474 "device": self
.device
,
475 "remoteBinDir": self
.remoteBinDir
,
476 "remoteScriptsDir": self
.remoteScriptsDir
,
477 "remoteComponentsDir": self
.remoteComponentsDir
,
478 "remoteModulesDir": self
.remoteModulesDir
,
479 "options": self
.options
,
480 "remoteDebugger": self
.remoteDebugger
,
481 "remoteDebuggerArgs": self
.remoteDebuggerArgs
,
482 "pathMapping": self
.pathMapping
,
483 "profileDir": self
.profileDir
,
484 "remoteLogFolder": self
.remoteLogFolder
,
485 "remoteTmpDir": self
.remoteTmpDir
,
486 "remoteMinidumpRootDir": self
.remoteMinidumpRootDir
,
489 self
.mobileArgs
["remoteAPK"] = self
.remoteAPK
491 def initDir(self
, path
, mask
="777", timeout
=None):
492 """Initialize a directory by removing it if it exists, creating it
493 and changing the permissions."""
494 self
.device
.rm(path
, recursive
=True, force
=True, timeout
=timeout
)
495 self
.device
.mkdir(path
, parents
=True, timeout
=timeout
)
497 def setLD_LIBRARY_PATH(self
):
498 self
.env
["LD_LIBRARY_PATH"] = self
.remoteBinDir
500 def pushWrapper(self
):
501 # Rather than executing xpcshell directly, this wrapper script is
502 # used. By setting environment variables and the cwd in the script,
503 # the length of the per-test command line is shortened. This is
504 # often important when using ADB, as there is a limit to the length
505 # of the ADB command line.
506 localWrapper
= tempfile
.mktemp()
507 with
open(localWrapper
, "w") as f
:
508 f
.write("#!/system/bin/sh\n")
509 for envkey
, envval
in six
.iteritems(self
.env
):
510 f
.write("export %s=%s\n" % (envkey
, envval
))
514 "echo xpcw: cd $1\n",
516 'echo xpcw: xpcshell "$@"\n',
517 '%s/xpcshell "$@"\n' % self
.remoteBinDir
,
520 remoteWrapper
= posixpath
.join(self
.remoteBinDir
, "xpcw")
521 self
.device
.push(localWrapper
, remoteWrapper
)
522 self
.device
.chmod(remoteWrapper
)
523 os
.remove(localWrapper
)
525 def start_test(self
, test
):
526 test
.selectedProcess
= RemoteProcessMonitor
.pickUnusedProcess()
527 if test
.selectedProcess
== -1:
529 "TEST-UNEXPECTED-FAIL | remotexpcshelltests.py | "
530 "no more free processes"
534 def test_ended(self
, test
):
535 RemoteProcessMonitor
.freeProcess(test
.selectedProcess
)
537 def buildPrefsFile(self
, extraPrefs
):
538 prefs
= super(XPCShellRemote
, self
).buildPrefsFile(extraPrefs
)
539 remotePrefsFile
= posixpath
.join(self
.remoteTestRoot
, "user.js")
540 self
.device
.push(self
.prefsFile
, remotePrefsFile
)
541 self
.device
.chmod(remotePrefsFile
)
542 # os.remove(self.prefsFile) is not called despite having pushed the
543 # file to the device, because the local file is relied upon by the
544 # updateTestPrefsFile method
545 self
.prefsFile
= remotePrefsFile
548 def buildEnvironment(self
):
549 self
.buildCoreEnvironment()
550 self
.setLD_LIBRARY_PATH()
551 self
.env
["MOZ_LINKER_CACHE"] = self
.remoteBinDir
552 self
.env
["GRE_HOME"] = self
.remoteBinDir
553 self
.env
["XPCSHELL_TEST_PROFILE_DIR"] = self
.profileDir
554 self
.env
["HOME"] = self
.profileDir
555 self
.env
["XPCSHELL_TEST_TEMP_DIR"] = self
.remoteTmpDir
556 self
.env
["MOZ_ANDROID_DATA_DIR"] = self
.remoteBinDir
557 self
.env
["MOZ_IN_AUTOMATION"] = "1"
559 # Guard against intermittent failures to retrieve abi property;
560 # without an abi, xpcshell cannot find greprefs.js and crashes.
564 while not abi
and retries
< 3:
565 abi
= self
.device
.get_prop("ro.product.cpu.abi")
568 raise Exception("failed to get ro.product.cpu.abi from device")
569 self
.log
.info("ro.product.cpu.abi %s" % abi
)
570 if self
.localAPKContents
:
573 while not abilistprop
and retries
< 3:
574 abilistprop
= self
.device
.get_prop("ro.product.cpu.abilist")
576 self
.log
.info("ro.product.cpu.abilist %s" % abilistprop
)
579 n
for n
in self
.localAPKContents
.namelist() if n
.startswith("lib/")
581 self
.log
.debug("apk names: %s" % names
)
582 if abilistprop
and len(abilistprop
) > 0:
583 abilist
.extend(abilistprop
.split(","))
584 for abicand
in abilist
:
586 len([n
for n
in names
if n
.startswith("lib/%s" % abicand
)]) > 0
592 self
.log
.info("failed to get matching abi from apk.")
595 "device cpu abi not found in apk. Using abi from apk."
597 abi
= names
[0].split("/")[1]
598 self
.log
.info("Using abi %s." % abi
)
599 self
.env
["MOZ_ANDROID_CPU_ABI"] = abi
600 self
.log
.info("Using env %r" % (self
.env
,))
602 def setupUtilities(self
):
603 self
.initDir(self
.remoteTmpDir
)
604 self
.initDir(self
.remoteBinDir
)
605 remotePrefDir
= posixpath
.join(self
.remoteBinDir
, "defaults", "pref")
606 self
.initDir(posixpath
.join(remotePrefDir
, "extra"))
607 self
.initDir(self
.remoteComponentsDir
)
609 local
= os
.path
.join(os
.path
.dirname(os
.path
.abspath(__file__
)), "head.js")
610 remoteFile
= posixpath
.join(self
.remoteScriptsDir
, "head.js")
611 self
.device
.push(local
, remoteFile
)
612 self
.device
.chmod(remoteFile
)
614 # Additional binaries are required for some tests. This list should be
615 # similar to TEST_HARNESS_BINS in testing/mochitest/Makefile.in.
620 "BadCertAndPinningServer",
621 "DelegatedCredentialsServer",
622 "EncryptedClientHelloServer",
624 "OCSPStaplingServer",
625 "GenerateOCSPResponse",
626 "SanctionsTestServer",
628 for fname
in binaries
:
629 local
= os
.path
.join(self
.localBin
, fname
)
630 if os
.path
.isfile(local
):
631 print("Pushing %s.." % fname
, file=sys
.stderr
)
632 remoteFile
= posixpath
.join(self
.remoteBinDir
, fname
)
633 self
.device
.push(local
, remoteFile
)
634 self
.device
.chmod(remoteFile
)
637 "*** Expected binary %s not found in %s!" % (fname
, self
.localBin
),
641 local
= os
.path
.join(self
.localBin
, "components/httpd.sys.mjs")
642 remoteFile
= posixpath
.join(self
.remoteComponentsDir
, "httpd.sys.mjs")
643 self
.device
.push(local
, remoteFile
)
644 self
.device
.chmod(remoteFile
)
646 if self
.options
["localAPK"]:
647 remoteFile
= posixpath
.join(
648 self
.remoteBinDir
, os
.path
.basename(self
.options
["localAPK"])
650 self
.device
.push(self
.options
["localAPK"], remoteFile
)
651 self
.device
.chmod(remoteFile
)
655 localB2G
= os
.path
.join(self
.options
["objdir"], "dist", "b2g")
656 if os
.path
.exists(localB2G
):
657 self
.device
.push(localB2G
, self
.remoteBinDir
)
658 self
.device
.chmod(self
.remoteBinDir
)
660 raise Exception("unable to install gre: no APK and not b2g")
663 pushed_libs_count
= 0
665 dir = tempfile
.mkdtemp()
666 for info
in self
.localAPKContents
.infolist():
667 if info
.filename
.endswith(".so"):
668 print("Pushing %s.." % info
.filename
, file=sys
.stderr
)
669 remoteFile
= posixpath
.join(
670 self
.remoteBinDir
, os
.path
.basename(info
.filename
)
672 self
.localAPKContents
.extract(info
, dir)
673 localFile
= os
.path
.join(dir, info
.filename
)
674 self
.device
.push(localFile
, remoteFile
)
675 pushed_libs_count
+= 1
676 self
.device
.chmod(remoteFile
)
679 return pushed_libs_count
681 def setupModules(self
):
682 if self
.testingModulesDir
:
683 self
.device
.push(self
.testingModulesDir
, self
.remoteModulesDir
)
684 self
.device
.chmod(self
.remoteModulesDir
)
686 def setupTestDir(self
):
687 print("pushing %s" % self
.xpcDir
)
688 # The tests directory can be quite large: 5000 files and growing!
689 # Sometimes - like on a low-end aws instance running an emulator - the push
690 # may exceed the default 5 minute timeout, so we increase it here to 10 minutes.
691 self
.device
.rm(self
.remoteScriptsDir
, recursive
=True, force
=True, timeout
=None)
692 self
.device
.push(self
.xpcDir
, self
.remoteScriptsDir
, timeout
=600)
693 self
.device
.chmod(self
.remoteScriptsDir
, recursive
=True)
695 def setupSocketConnections(self
):
696 # make node host ports visible to device
697 if "MOZHTTP2_PORT" in self
.env
:
698 port
= "tcp:{}".format(self
.env
["MOZHTTP2_PORT"])
699 self
.device
.create_socket_connection(
700 ADBDevice
.SOCKET_DIRECTION_REVERSE
, port
, port
702 self
.log
.info("reversed MOZHTTP2_PORT connection for port " + port
)
703 if "MOZNODE_EXEC_PORT" in self
.env
:
704 port
= "tcp:{}".format(self
.env
["MOZNODE_EXEC_PORT"])
705 self
.device
.create_socket_connection(
706 ADBDevice
.SOCKET_DIRECTION_REVERSE
, port
, port
708 self
.log
.info("reversed MOZNODE_EXEC_PORT connection for port " + port
)
710 def buildTestList(self
, test_tags
=None, test_paths
=None, verify
=False):
711 xpcshell
.XPCShellTests
.buildTestList(
712 self
, test_tags
=test_tags
, test_paths
=test_paths
, verify
=verify
714 uniqueTestPaths
= set([])
715 for test
in self
.alltests
:
716 uniqueTestPaths
.add(test
["here"])
717 for testdir
in uniqueTestPaths
:
718 abbrevTestDir
= os
.path
.relpath(testdir
, self
.xpcDir
)
719 remoteScriptDir
= posixpath
.join(self
.remoteScriptsDir
, abbrevTestDir
)
720 self
.pathMapping
.append(PathMapping(testdir
, remoteScriptDir
))
721 # This is not related to building the test list, but since this is called late
722 # in the test suite run, this is a convenient place to finalize preparations;
723 # in particular, these operations cannot be executed much earlier because
724 # self.env may not be finalized.
725 self
.setupSocketConnections()
726 if self
.options
["setup"]:
730 def verifyRemoteOptions(parser
, options
):
731 if isinstance(options
, Namespace
):
732 options
= vars(options
)
734 if options
["localBin"] is None:
735 if options
["objdir"]:
736 options
["localBin"] = os
.path
.join(options
["objdir"], "dist", "bin")
737 if not os
.path
.isdir(options
["localBin"]):
738 parser
.error("Couldn't find local binary dir, specify --local-bin-dir")
739 elif os
.path
.isfile(os
.path
.join(here
, "..", "bin", "xpcshell")):
740 # assume tests are being run from a tests archive
741 options
["localBin"] = os
.path
.abspath(os
.path
.join(here
, "..", "bin"))
743 parser
.error("Couldn't find local binary dir, specify --local-bin-dir")
748 def __init__(self
, localDir
, remoteDir
):
749 self
.local
= localDir
750 self
.remote
= remoteDir
754 if sys
.version_info
< (2, 7):
756 "Error: You must use python version 2.7 or newer but less than 3.0",
761 parser
= parser_remote()
762 options
= parser
.parse_args()
764 options
= verifyRemoteOptions(parser
, options
)
765 log
= commandline
.setup_logging("Remote XPCShell", options
, {"tbpl": sys
.stdout
})
767 if options
["interactive"] and not options
["testPath"]:
769 "Error: You must specify a test filename in interactive mode!",
774 if options
["xpcshell"] is None:
775 options
["xpcshell"] = "xpcshell"
777 # The threadCount depends on the emulator rather than the host machine and
778 # empirically 10 seems to yield the best performance.
779 options
["threadCount"] = min(options
["threadCount"], 10)
781 xpcsh
= XPCShellRemote(options
, log
)
783 if not xpcsh
.runTests(
784 options
, testClass
=RemoteXPCShellTestThread
, mobileArgs
=xpcsh
.mobileArgs
789 if __name__
== "__main__":