summaryrefslogtreecommitdiffstats
path: root/testing/xpcshell/remotexpcshelltests.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /testing/xpcshell/remotexpcshelltests.py
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--testing/xpcshell/remotexpcshelltests.py791
1 files changed, 791 insertions, 0 deletions
diff --git a/testing/xpcshell/remotexpcshelltests.py b/testing/xpcshell/remotexpcshelltests.py
new file mode 100644
index 0000000000..6dd40f15f9
--- /dev/null
+++ b/testing/xpcshell/remotexpcshelltests.py
@@ -0,0 +1,791 @@
+#!/usr/bin/env python
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import datetime
+import os
+import posixpath
+import shutil
+import sys
+import tempfile
+import time
+import uuid
+from argparse import Namespace
+from zipfile import ZipFile
+
+import mozcrash
+import mozdevice
+import mozfile
+import mozinfo
+import runxpcshelltests as xpcshell
+import six
+from mozdevice import ADBDevice, ADBDeviceFactory, ADBTimeoutError
+from mozlog import commandline
+from xpcshellcommandline import parser_remote
+
+here = os.path.dirname(os.path.abspath(__file__))
+
+
+class RemoteProcessMonitor(object):
+ processStatus = []
+
+ def __init__(self, package, device, log, remoteLogFile):
+ self.package = package
+ self.device = device
+ self.log = log
+ self.remoteLogFile = remoteLogFile
+ self.selectedProcess = -1
+
+ @classmethod
+ def pickUnusedProcess(cls):
+ for i in range(len(cls.processStatus)):
+ if not cls.processStatus[i]:
+ cls.processStatus[i] = True
+ return i
+ # No more free processes :(
+ return -1
+
+ @classmethod
+ def freeProcess(cls, processId):
+ cls.processStatus[processId] = False
+
+ def kill(self):
+ self.device.pkill(self.process_name, sig=9, attempts=1)
+
+ def launch_service(self, extra_args, env, selectedProcess, test_name=None):
+ if not self.device.process_exist(self.package):
+ # Make sure the main app is running, this should help making the
+ # tests get foreground priority scheduling.
+ self.device.launch_activity(
+ self.package,
+ intent="org.mozilla.geckoview.test_runner.XPCSHELL_TEST_MAIN",
+ activity_name="TestRunnerActivity",
+ e10s=True,
+ )
+ # Newer Androids require that background services originate from
+ # active apps, so wait here until the test runner is the top
+ # activity.
+ retries = 20
+ top = self.device.get_top_activity(timeout=60)
+ while top != self.package and retries > 0:
+ self.log.info(
+ "%s | Checking that %s is the top activity."
+ % (test_name, self.package)
+ )
+ top = self.device.get_top_activity(timeout=60)
+ time.sleep(1)
+ retries -= 1
+
+ self.process_name = self.package + (":xpcshell%d" % selectedProcess)
+
+ retries = 20
+ while retries > 0 and self.device.process_exist(self.process_name):
+ self.log.info(
+ "%s | %s | Killing left-over process %s"
+ % (test_name, self.pid, self.process_name)
+ )
+ self.kill()
+ time.sleep(1)
+ retries -= 1
+
+ if self.device.process_exist(self.process_name):
+ raise Exception(
+ "%s | %s | Could not kill left-over process" % (test_name, self.pid)
+ )
+
+ self.device.launch_service(
+ self.package,
+ activity_name=("XpcshellTestRunnerService$i%d" % selectedProcess),
+ e10s=True,
+ moz_env=env,
+ grant_runtime_permissions=False,
+ extra_args=extra_args,
+ out_file=self.remoteLogFile,
+ )
+ return self.pid
+
+ def wait(self, timeout, interval=0.1, test_name=None):
+ timer = 0
+ status = True
+
+ # wait for log creation on startup
+ retries = 0
+ while retries < 20 / interval and not self.device.is_file(self.remoteLogFile):
+ retries += 1
+ time.sleep(interval)
+ if not self.device.is_file(self.remoteLogFile):
+ self.log.warning(
+ "%s | Failed wait for remote log: %s missing?"
+ % (test_name, self.remoteLogFile)
+ )
+
+ while self.device.process_exist(self.process_name):
+ time.sleep(interval)
+ timer += interval
+ interval *= 1.5
+ if timeout and timer > timeout:
+ status = False
+ self.log.info(
+ "remotexpcshelltests.py | %s | %s | Timing out"
+ % (test_name, str(self.pid))
+ )
+ self.kill()
+ break
+ return status
+
+ @property
+ def pid(self):
+ """
+ Determine the pid of the remote process (or the first process with
+ the same name).
+ """
+ procs = self.device.get_process_list()
+ # limit the comparison to the first 75 characters due to a
+ # limitation in processname length in android.
+ pids = [proc[0] for proc in procs if proc[1] == self.process_name[:75]]
+ if pids is None or len(pids) < 1:
+ return 0
+ return pids[0]
+
+
+class RemoteXPCShellTestThread(xpcshell.XPCShellTestThread):
+ def __init__(self, *args, **kwargs):
+ xpcshell.XPCShellTestThread.__init__(self, *args, **kwargs)
+
+ self.shellReturnCode = None
+ # embed the mobile params from the harness into the TestThread
+ mobileArgs = kwargs.get("mobileArgs")
+ for key in mobileArgs:
+ setattr(self, key, mobileArgs[key])
+ self.remoteLogFile = posixpath.join(
+ mobileArgs["remoteLogFolder"], "xpcshell-%s.log" % str(uuid.uuid4())
+ )
+
+ def initDir(self, path, mask="777", timeout=None):
+ """Initialize a directory by removing it if it exists, creating it
+ and changing the permissions."""
+ self.device.rm(path, recursive=True, force=True, timeout=timeout)
+ self.device.mkdir(path, parents=True, timeout=timeout)
+
+ def updateTestPrefsFile(self):
+ # The base method will either be no-op (and return the existing
+ # remote path), or return a path to a new local file.
+ testPrefsFile = xpcshell.XPCShellTestThread.updateTestPrefsFile(self)
+ if testPrefsFile == self.rootPrefsFile:
+ # The pref file is the shared one, which has been already pushed on the
+ # device, and so there is nothing more to do here.
+ return self.rootPrefsFile
+
+ # Push the per-test prefs file in the remote temp dir.
+ remoteTestPrefsFile = posixpath.join(self.remoteTmpDir, "user.js")
+ self.device.push(testPrefsFile, remoteTestPrefsFile)
+ self.device.chmod(remoteTestPrefsFile)
+ os.remove(testPrefsFile)
+ return remoteTestPrefsFile
+
+ def buildCmdTestFile(self, name):
+ remoteDir = self.remoteForLocal(os.path.dirname(name))
+ if remoteDir == self.remoteHere:
+ remoteName = os.path.basename(name)
+ else:
+ remoteName = posixpath.join(remoteDir, os.path.basename(name))
+ return [
+ "-e",
+ 'const _TEST_CWD = "%s";' % self.remoteHere,
+ "-e",
+ 'const _TEST_FILE = ["%s"];' % remoteName.replace("\\", "/"),
+ ]
+
+ def remoteForLocal(self, local):
+ for mapping in self.pathMapping:
+ if os.path.abspath(mapping.local) == os.path.abspath(local):
+ return mapping.remote
+ return local
+
+ def setupTempDir(self):
+ self.remoteTmpDir = posixpath.join(self.remoteTmpDir, str(uuid.uuid4()))
+ # make sure the temp dir exists
+ self.initDir(self.remoteTmpDir)
+ # env var is set in buildEnvironment
+ self.env["XPCSHELL_TEST_TEMP_DIR"] = self.remoteTmpDir
+ return self.remoteTmpDir
+
+ def setupProfileDir(self):
+ profileId = str(uuid.uuid4())
+ self.profileDir = posixpath.join(self.profileDir, profileId)
+ self.initDir(self.profileDir)
+ if self.interactive or self.singleFile:
+ self.log.info("profile dir is %s" % self.profileDir)
+ self.env["XPCSHELL_TEST_PROFILE_DIR"] = self.profileDir
+ self.env["TMPDIR"] = self.profileDir
+ self.remoteMinidumpDir = posixpath.join(self.remoteMinidumpRootDir, profileId)
+ self.initDir(self.remoteMinidumpDir)
+ self.env["XPCSHELL_MINIDUMP_DIR"] = self.remoteMinidumpDir
+ return self.profileDir
+
+ def clean_temp_dirs(self, name):
+ self.log.info("Cleaning up profile for %s folder: %s" % (name, self.profileDir))
+ self.device.rm(self.profileDir, force=True, recursive=True)
+ self.device.rm(self.remoteTmpDir, force=True, recursive=True)
+ self.device.rm(self.remoteMinidumpDir, force=True, recursive=True)
+
+ def setupMozinfoJS(self):
+ local = tempfile.mktemp()
+ mozinfo.output_to_file(local)
+ mozInfoJSPath = posixpath.join(self.profileDir, "mozinfo.json")
+ self.device.push(local, mozInfoJSPath)
+ self.device.chmod(mozInfoJSPath)
+ os.remove(local)
+ return mozInfoJSPath
+
+ def logCommand(self, name, completeCmd, testdir):
+ self.log.info("%s | full command: %r" % (name, completeCmd))
+ self.log.info("%s | current directory: %r" % (name, self.remoteHere))
+ self.log.info("%s | environment: %s" % (name, self.env))
+
+ def getHeadFiles(self, test):
+ """Override parent method to find files on remote device.
+
+ Obtains lists of head- files. Returns a list of head files.
+ """
+
+ def sanitize_list(s, kind):
+ for f in s.strip().split(" "):
+ f = f.strip()
+ if len(f) < 1:
+ continue
+
+ path = posixpath.join(self.remoteHere, f)
+
+ # skip check for file existence: the convenience of discovering
+ # a missing file does not justify the time cost of the round trip
+ # to the device
+ yield path
+
+ self.remoteHere = self.remoteForLocal(test["here"])
+
+ headlist = test.get("head", "")
+ return list(sanitize_list(headlist, "head"))
+
+ def buildXpcsCmd(self):
+ # change base class' paths to remote paths and use base class to build command
+ self.xpcshell = posixpath.join(self.remoteBinDir, "xpcw")
+ self.headJSPath = posixpath.join(self.remoteScriptsDir, "head.js")
+ self.httpdJSPath = posixpath.join(self.remoteComponentsDir, "httpd.js")
+ self.testingModulesDir = self.remoteModulesDir
+ self.testharnessdir = self.remoteScriptsDir
+ xpcsCmd = xpcshell.XPCShellTestThread.buildXpcsCmd(self)
+ # remove "-g <dir> -a <dir>" and replace with remote alternatives
+ del xpcsCmd[1:5]
+ if self.options["localAPK"]:
+ xpcsCmd.insert(1, "--greomni")
+ xpcsCmd.insert(2, self.remoteAPK)
+ xpcsCmd.insert(1, "-g")
+ xpcsCmd.insert(2, self.remoteBinDir)
+
+ if self.remoteDebugger:
+ # for example, "/data/local/gdbserver" "localhost:12345"
+ xpcsCmd = [self.remoteDebugger, self.remoteDebuggerArgs] + xpcsCmd
+ return xpcsCmd
+
+ def killTimeout(self, proc):
+ self.kill(proc)
+
+ def launchProcess(
+ self, cmd, stdout, stderr, env, cwd, timeout=None, test_name=None
+ ):
+ rpm = RemoteProcessMonitor(
+ "org.mozilla.geckoview.test_runner",
+ self.device,
+ self.log,
+ self.remoteLogFile,
+ )
+
+ startTime = datetime.datetime.now()
+
+ try:
+ pid = rpm.launch_service(
+ cmd[1:], self.env, self.selectedProcess, test_name=test_name
+ )
+ except Exception as e:
+ self.log.info(
+ "remotexpcshelltests.py | Failed to start process: %s" % str(e)
+ )
+ self.shellReturnCode = 1
+ return ""
+
+ self.log.info(
+ "remotexpcshelltests.py | %s | %s | Launched Test App"
+ % (test_name, str(pid))
+ )
+
+ if rpm.wait(timeout, test_name=test_name):
+ self.shellReturnCode = 0
+ else:
+ self.shellReturnCode = 1
+ self.log.info(
+ "remotexpcshelltests.py | %s | %s | Application ran for: %s"
+ % (test_name, str(pid), str(datetime.datetime.now() - startTime))
+ )
+
+ try:
+ return self.device.get_file(self.remoteLogFile)
+ except mozdevice.ADBTimeoutError:
+ raise
+ except Exception as e:
+ self.log.info(
+ "remotexpcshelltests.py | %s | %s | Could not read log file: %s"
+ % (test_name, str(pid), str(e))
+ )
+ self.shellReturnCode = 1
+ return ""
+
+ def checkForCrashes(self, dump_directory, symbols_path, test_name=None):
+ with mozfile.TemporaryDirectory() as dumpDir:
+ self.device.pull(self.remoteMinidumpDir, dumpDir)
+ crashed = mozcrash.log_crashes(
+ self.log, dumpDir, symbols_path, test=test_name
+ )
+ return crashed
+
+ def communicate(self, proc):
+ return proc, ""
+
+ def poll(self, proc):
+ if not self.device.process_exist("xpcshell"):
+ return self.getReturnCode(proc)
+ # Process is still running
+ return None
+
+ def kill(self, proc):
+ return self.device.pkill("xpcshell")
+
+ def getReturnCode(self, proc):
+ if self.shellReturnCode is not None:
+ return self.shellReturnCode
+ else:
+ return -1
+
+ def removeDir(self, dirname):
+ try:
+ self.device.rm(dirname, recursive=True)
+ except ADBTimeoutError:
+ raise
+ except Exception as e:
+ self.log.warning(str(e))
+
+ def createLogFile(self, test, stdout):
+ filename = test.replace("\\", "/").split("/")[-1] + ".log"
+ with open(filename, "wb") as f:
+ f.write(stdout)
+
+
+# A specialization of XPCShellTests that runs tests on an Android device.
+class XPCShellRemote(xpcshell.XPCShellTests, object):
+ def __init__(self, options, log):
+ xpcshell.XPCShellTests.__init__(self, log)
+
+ options["threadCount"] = min(options["threadCount"] or 4, 4)
+
+ self.options = options
+ verbose = False
+ if options["log_tbpl_level"] == "debug" or options["log_mach_level"] == "debug":
+ verbose = True
+ self.device = ADBDeviceFactory(
+ adb=options["adbPath"] or "adb",
+ device=options["deviceSerial"],
+ test_root=options["remoteTestRoot"],
+ verbose=verbose,
+ )
+ self.remoteTestRoot = posixpath.join(self.device.test_root, "xpc")
+ self.remoteLogFolder = posixpath.join(self.remoteTestRoot, "logs")
+ # Add Android version (SDK level) to mozinfo so that manifest entries
+ # can be conditional on android_version.
+ mozinfo.info["android_version"] = str(self.device.version)
+ mozinfo.info["is_emulator"] = self.device._device_serial.startswith("emulator-")
+
+ self.localBin = options["localBin"]
+ self.pathMapping = []
+ # remoteBinDir contains xpcshell and its wrapper script, both of which must
+ # be executable. Since +x permissions cannot usually be set on /mnt/sdcard,
+ # and the test root may be on /mnt/sdcard, remoteBinDir is set to be on
+ # /data/local, always.
+ self.remoteBinDir = posixpath.join(self.device.test_root, "xpcb")
+ # Terse directory names are used here ("c" for the components directory)
+ # to minimize the length of the command line used to execute
+ # xpcshell on the remote device. adb has a limit to the number
+ # of characters used in a shell command, and the xpcshell command
+ # line can be quite complex.
+ self.remoteTmpDir = posixpath.join(self.remoteTestRoot, "tmp")
+ self.remoteScriptsDir = self.remoteTestRoot
+ self.remoteComponentsDir = posixpath.join(self.remoteTestRoot, "c")
+ self.remoteModulesDir = posixpath.join(self.remoteTestRoot, "m")
+ self.remoteMinidumpRootDir = posixpath.join(self.remoteTestRoot, "minidumps")
+ self.profileDir = posixpath.join(self.remoteTestRoot, "p")
+ self.remoteDebugger = options["debugger"]
+ self.remoteDebuggerArgs = options["debuggerArgs"]
+ self.testingModulesDir = options["testingModulesDir"]
+
+ self.initDir(self.remoteTmpDir)
+ self.initDir(self.profileDir)
+
+ # Make sure we get a fresh start
+ self.device.stop_application("org.mozilla.geckoview.test_runner")
+
+ for i in range(options["threadCount"]):
+ RemoteProcessMonitor.processStatus += [False]
+
+ self.env = {}
+
+ if options["objdir"]:
+ self.xpcDir = os.path.join(options["objdir"], "_tests/xpcshell")
+ elif os.path.isdir(os.path.join(here, "tests")):
+ self.xpcDir = os.path.join(here, "tests")
+ else:
+ print("Couldn't find local xpcshell test directory", file=sys.stderr)
+ sys.exit(1)
+
+ self.remoteAPK = None
+ if options["localAPK"]:
+ self.localAPKContents = ZipFile(options["localAPK"])
+ self.remoteAPK = posixpath.join(
+ self.remoteBinDir, os.path.basename(options["localAPK"])
+ )
+ else:
+ self.localAPKContents = None
+ if options["setup"]:
+ self.setupTestDir()
+ self.setupUtilities()
+ self.setupModules()
+ self.initDir(self.remoteMinidumpRootDir)
+ self.initDir(self.remoteLogFolder)
+
+ eprefs = options.get("extraPrefs") or []
+ if options.get("disableFission"):
+ eprefs.append("fission.autostart=false")
+ else:
+ # should be by default, just in case
+ eprefs.append("fission.autostart=true")
+ options["extraPrefs"] = eprefs
+
+ # data that needs to be passed to the RemoteXPCShellTestThread
+ self.mobileArgs = {
+ "device": self.device,
+ "remoteBinDir": self.remoteBinDir,
+ "remoteScriptsDir": self.remoteScriptsDir,
+ "remoteComponentsDir": self.remoteComponentsDir,
+ "remoteModulesDir": self.remoteModulesDir,
+ "options": self.options,
+ "remoteDebugger": self.remoteDebugger,
+ "remoteDebuggerArgs": self.remoteDebuggerArgs,
+ "pathMapping": self.pathMapping,
+ "profileDir": self.profileDir,
+ "remoteLogFolder": self.remoteLogFolder,
+ "remoteTmpDir": self.remoteTmpDir,
+ "remoteMinidumpRootDir": self.remoteMinidumpRootDir,
+ }
+ if self.remoteAPK:
+ self.mobileArgs["remoteAPK"] = self.remoteAPK
+
+ def initDir(self, path, mask="777", timeout=None):
+ """Initialize a directory by removing it if it exists, creating it
+ and changing the permissions."""
+ self.device.rm(path, recursive=True, force=True, timeout=timeout)
+ self.device.mkdir(path, parents=True, timeout=timeout)
+
+ def setLD_LIBRARY_PATH(self):
+ self.env["LD_LIBRARY_PATH"] = self.remoteBinDir
+
+ def pushWrapper(self):
+ # Rather than executing xpcshell directly, this wrapper script is
+ # used. By setting environment variables and the cwd in the script,
+ # the length of the per-test command line is shortened. This is
+ # often important when using ADB, as there is a limit to the length
+ # of the ADB command line.
+ localWrapper = tempfile.mktemp()
+ with open(localWrapper, "w") as f:
+ f.write("#!/system/bin/sh\n")
+ for envkey, envval in six.iteritems(self.env):
+ f.write("export %s=%s\n" % (envkey, envval))
+ f.writelines(
+ [
+ "cd $1\n",
+ "echo xpcw: cd $1\n",
+ "shift\n",
+ 'echo xpcw: xpcshell "$@"\n',
+ '%s/xpcshell "$@"\n' % self.remoteBinDir,
+ ]
+ )
+ remoteWrapper = posixpath.join(self.remoteBinDir, "xpcw")
+ self.device.push(localWrapper, remoteWrapper)
+ self.device.chmod(remoteWrapper)
+ os.remove(localWrapper)
+
+ def start_test(self, test):
+ test.selectedProcess = RemoteProcessMonitor.pickUnusedProcess()
+ if test.selectedProcess == -1:
+ self.log.error(
+ "TEST-UNEXPECTED-FAIL | remotexpcshelltests.py | "
+ "no more free processes"
+ )
+ test.start()
+
+ def test_ended(self, test):
+ RemoteProcessMonitor.freeProcess(test.selectedProcess)
+
+ def buildPrefsFile(self, extraPrefs):
+ prefs = super(XPCShellRemote, self).buildPrefsFile(extraPrefs)
+ remotePrefsFile = posixpath.join(self.remoteTestRoot, "user.js")
+ self.device.push(self.prefsFile, remotePrefsFile)
+ self.device.chmod(remotePrefsFile)
+ # os.remove(self.prefsFile) is not called despite having pushed the
+ # file to the device, because the local file is relied upon by the
+ # updateTestPrefsFile method
+ self.prefsFile = remotePrefsFile
+ return prefs
+
+ def buildEnvironment(self):
+ self.buildCoreEnvironment()
+ self.setLD_LIBRARY_PATH()
+ self.env["MOZ_LINKER_CACHE"] = self.remoteBinDir
+ self.env["GRE_HOME"] = self.remoteBinDir
+ self.env["XPCSHELL_TEST_PROFILE_DIR"] = self.profileDir
+ self.env["HOME"] = self.profileDir
+ self.env["XPCSHELL_TEST_TEMP_DIR"] = self.remoteTmpDir
+ self.env["MOZ_ANDROID_DATA_DIR"] = self.remoteBinDir
+ self.env["MOZ_IN_AUTOMATION"] = "1"
+
+ # Guard against intermittent failures to retrieve abi property;
+ # without an abi, xpcshell cannot find greprefs.js and crashes.
+ abilistprop = None
+ abi = None
+ retries = 0
+ while not abi and retries < 3:
+ abi = self.device.get_prop("ro.product.cpu.abi")
+ retries += 1
+ if not abi:
+ raise Exception("failed to get ro.product.cpu.abi from device")
+ self.log.info("ro.product.cpu.abi %s" % abi)
+ if self.localAPKContents:
+ abilist = [abi]
+ retries = 0
+ while not abilistprop and retries < 3:
+ abilistprop = self.device.get_prop("ro.product.cpu.abilist")
+ retries += 1
+ self.log.info("ro.product.cpu.abilist %s" % abilistprop)
+ abi_found = False
+ names = [
+ n for n in self.localAPKContents.namelist() if n.startswith("lib/")
+ ]
+ self.log.debug("apk names: %s" % names)
+ if abilistprop and len(abilistprop) > 0:
+ abilist.extend(abilistprop.split(","))
+ for abicand in abilist:
+ abi_found = (
+ len([n for n in names if n.startswith("lib/%s" % abicand)]) > 0
+ )
+ if abi_found:
+ abi = abicand
+ break
+ if not abi_found:
+ self.log.info("failed to get matching abi from apk.")
+ if len(names) > 0:
+ self.log.info(
+ "device cpu abi not found in apk. Using abi from apk."
+ )
+ abi = names[0].split("/")[1]
+ self.log.info("Using abi %s." % abi)
+ self.env["MOZ_ANDROID_CPU_ABI"] = abi
+ self.log.info("Using env %r" % (self.env,))
+
+ def setupUtilities(self):
+ self.initDir(self.remoteTmpDir)
+ self.initDir(self.remoteBinDir)
+ remotePrefDir = posixpath.join(self.remoteBinDir, "defaults", "pref")
+ self.initDir(posixpath.join(remotePrefDir, "extra"))
+ self.initDir(self.remoteComponentsDir)
+
+ local = os.path.join(os.path.dirname(os.path.abspath(__file__)), "head.js")
+ remoteFile = posixpath.join(self.remoteScriptsDir, "head.js")
+ self.device.push(local, remoteFile)
+ self.device.chmod(remoteFile)
+
+ # Additional binaries are required for some tests. This list should be
+ # similar to TEST_HARNESS_BINS in testing/mochitest/Makefile.in.
+ binaries = [
+ "ssltunnel",
+ "certutil",
+ "pk12util",
+ "BadCertAndPinningServer",
+ "DelegatedCredentialsServer",
+ "EncryptedClientHelloServer",
+ "FaultyServer",
+ "OCSPStaplingServer",
+ "GenerateOCSPResponse",
+ "SanctionsTestServer",
+ ]
+ for fname in binaries:
+ local = os.path.join(self.localBin, fname)
+ if os.path.isfile(local):
+ print("Pushing %s.." % fname, file=sys.stderr)
+ remoteFile = posixpath.join(self.remoteBinDir, fname)
+ self.device.push(local, remoteFile)
+ self.device.chmod(remoteFile)
+ else:
+ print(
+ "*** Expected binary %s not found in %s!" % (fname, self.localBin),
+ file=sys.stderr,
+ )
+
+ local = os.path.join(self.localBin, "components/httpd.js")
+ remoteFile = posixpath.join(self.remoteComponentsDir, "httpd.js")
+ self.device.push(local, remoteFile)
+ self.device.chmod(remoteFile)
+
+ if self.options["localAPK"]:
+ remoteFile = posixpath.join(
+ self.remoteBinDir, os.path.basename(self.options["localAPK"])
+ )
+ self.device.push(self.options["localAPK"], remoteFile)
+ self.device.chmod(remoteFile)
+
+ self.pushLibs()
+ else:
+ localB2G = os.path.join(self.options["objdir"], "dist", "b2g")
+ if os.path.exists(localB2G):
+ self.device.push(localB2G, self.remoteBinDir)
+ self.device.chmod(self.remoteBinDir)
+ else:
+ raise Exception("unable to install gre: no APK and not b2g")
+
+ def pushLibs(self):
+ pushed_libs_count = 0
+ try:
+ dir = tempfile.mkdtemp()
+ for info in self.localAPKContents.infolist():
+ if info.filename.endswith(".so"):
+ print("Pushing %s.." % info.filename, file=sys.stderr)
+ remoteFile = posixpath.join(
+ self.remoteBinDir, os.path.basename(info.filename)
+ )
+ self.localAPKContents.extract(info, dir)
+ localFile = os.path.join(dir, info.filename)
+ self.device.push(localFile, remoteFile)
+ pushed_libs_count += 1
+ self.device.chmod(remoteFile)
+ finally:
+ shutil.rmtree(dir)
+ return pushed_libs_count
+
+ def setupModules(self):
+ if self.testingModulesDir:
+ self.device.push(self.testingModulesDir, self.remoteModulesDir)
+ self.device.chmod(self.remoteModulesDir)
+
+ def setupTestDir(self):
+ print("pushing %s" % self.xpcDir)
+ # The tests directory can be quite large: 5000 files and growing!
+ # Sometimes - like on a low-end aws instance running an emulator - the push
+ # may exceed the default 5 minute timeout, so we increase it here to 10 minutes.
+ self.device.rm(self.remoteScriptsDir, recursive=True, force=True, timeout=None)
+ self.device.push(self.xpcDir, self.remoteScriptsDir, timeout=600)
+ self.device.chmod(self.remoteScriptsDir, recursive=True)
+
+ def setupSocketConnections(self):
+ # make node host ports visible to device
+ if "MOZHTTP2_PORT" in self.env:
+ port = "tcp:{}".format(self.env["MOZHTTP2_PORT"])
+ self.device.create_socket_connection(
+ ADBDevice.SOCKET_DIRECTION_REVERSE, port, port
+ )
+ self.log.info("reversed MOZHTTP2_PORT connection for port " + port)
+ if "MOZNODE_EXEC_PORT" in self.env:
+ port = "tcp:{}".format(self.env["MOZNODE_EXEC_PORT"])
+ self.device.create_socket_connection(
+ ADBDevice.SOCKET_DIRECTION_REVERSE, port, port
+ )
+ self.log.info("reversed MOZNODE_EXEC_PORT connection for port " + port)
+
+ def buildTestList(self, test_tags=None, test_paths=None, verify=False):
+ xpcshell.XPCShellTests.buildTestList(
+ self, test_tags=test_tags, test_paths=test_paths, verify=verify
+ )
+ uniqueTestPaths = set([])
+ for test in self.alltests:
+ uniqueTestPaths.add(test["here"])
+ for testdir in uniqueTestPaths:
+ abbrevTestDir = os.path.relpath(testdir, self.xpcDir)
+ remoteScriptDir = posixpath.join(self.remoteScriptsDir, abbrevTestDir)
+ self.pathMapping.append(PathMapping(testdir, remoteScriptDir))
+ # This is not related to building the test list, but since this is called late
+ # in the test suite run, this is a convenient place to finalize preparations;
+ # in particular, these operations cannot be executed much earlier because
+ # self.env may not be finalized.
+ self.setupSocketConnections()
+ if self.options["setup"]:
+ self.pushWrapper()
+
+
+def verifyRemoteOptions(parser, options):
+ if isinstance(options, Namespace):
+ options = vars(options)
+
+ if options["localBin"] is None:
+ if options["objdir"]:
+ options["localBin"] = os.path.join(options["objdir"], "dist", "bin")
+ if not os.path.isdir(options["localBin"]):
+ parser.error("Couldn't find local binary dir, specify --local-bin-dir")
+ elif os.path.isfile(os.path.join(here, "..", "bin", "xpcshell")):
+ # assume tests are being run from a tests archive
+ options["localBin"] = os.path.abspath(os.path.join(here, "..", "bin"))
+ else:
+ parser.error("Couldn't find local binary dir, specify --local-bin-dir")
+ return options
+
+
+class PathMapping:
+ def __init__(self, localDir, remoteDir):
+ self.local = localDir
+ self.remote = remoteDir
+
+
+def main():
+ if sys.version_info < (2, 7):
+ print(
+ "Error: You must use python version 2.7 or newer but less than 3.0",
+ file=sys.stderr,
+ )
+ sys.exit(1)
+
+ parser = parser_remote()
+ options = parser.parse_args()
+
+ options = verifyRemoteOptions(parser, options)
+ log = commandline.setup_logging("Remote XPCShell", options, {"tbpl": sys.stdout})
+
+ if options["interactive"] and not options["testPath"]:
+ print(
+ "Error: You must specify a test filename in interactive mode!",
+ file=sys.stderr,
+ )
+ sys.exit(1)
+
+ if options["xpcshell"] is None:
+ options["xpcshell"] = "xpcshell"
+
+ # The threadCount depends on the emulator rather than the host machine and
+ # empirically 10 seems to yield the best performance.
+ options["threadCount"] = min(options["threadCount"], 10)
+
+ xpcsh = XPCShellRemote(options, log)
+
+ if not xpcsh.runTests(
+ options, testClass=RemoteXPCShellTestThread, mobileArgs=xpcsh.mobileArgs
+ ):
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()