diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /testing/xpcshell/remotexpcshelltests.py | |
parent | Initial commit. (diff) | |
download | thunderbird-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 'testing/xpcshell/remotexpcshelltests.py')
-rw-r--r-- | testing/xpcshell/remotexpcshelltests.py | 791 |
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() |