示例#1
0
class RemoteCPPUnitTests(cppunittests.CPPUnitTests):
    def __init__(self, options, progs):
        cppunittests.CPPUnitTests.__init__(self)
        self.options = options
        self.device = ADBDeviceFactory(
            adb=options.adb_path or "adb",
            device=options.device_serial,
            test_root=options.remote_test_root,
        )
        self.remote_test_root = posixpath.join(self.device.test_root,
                                               "cppunittests")
        self.remote_bin_dir = posixpath.join(self.remote_test_root, "b")
        self.remote_tmp_dir = posixpath.join(self.remote_test_root, "tmp")
        self.remote_home_dir = posixpath.join(self.remote_test_root, "h")
        if options.setup:
            self.setup_bin(progs)

    def setup_bin(self, progs):
        self.device.rm(self.remote_test_root, force=True, recursive=True)
        self.device.mkdir(self.remote_home_dir, parents=True)
        self.device.mkdir(self.remote_tmp_dir)
        self.device.mkdir(self.remote_bin_dir)
        self.push_libs()
        self.push_progs(progs)
        self.device.chmod(self.remote_bin_dir, recursive=True)

    def push_libs(self):
        if self.options.local_apk:
            with mozfile.TemporaryDirectory() as tmpdir:
                apk_contents = ZipFile(self.options.local_apk)

                for info in apk_contents.infolist():
                    if info.filename.endswith(".so"):
                        print("Pushing %s.." % info.filename, file=sys.stderr)
                        remote_file = posixpath.join(
                            self.remote_bin_dir,
                            os.path.basename(info.filename))
                        apk_contents.extract(info, tmpdir)
                        local_file = os.path.join(tmpdir, info.filename)
                        with open(local_file) as f:
                            # Decompress xz-compressed file.
                            if f.read(5)[1:] == "7zXZ":
                                cmd = [
                                    "xz", "-df", "--suffix", ".so", local_file
                                ]
                                subprocess.check_output(cmd)
                                # xz strips the ".so" file suffix.
                                os.rename(local_file[:-3], local_file)
                        self.device.push(local_file, remote_file)

        elif self.options.local_lib:
            for path in os.listdir(self.options.local_lib):
                if path.endswith(".so"):
                    print("Pushing {}..".format(path), file=sys.stderr)
                    remote_file = posixpath.join(self.remote_bin_dir, path)
                    local_file = os.path.join(self.options.local_lib, path)
                    self.device.push(local_file, remote_file)
            # Additional libraries may be found in a sub-directory such as
            # "lib/armeabi-v7a"
            for subdir in ["assets", "lib"]:
                local_arm_lib = os.path.join(self.options.local_lib, subdir)
                if os.path.isdir(local_arm_lib):
                    for root, dirs, paths in os.walk(local_arm_lib):
                        for path in paths:
                            if path.endswith(".so"):
                                print("Pushing {}..".format(path),
                                      file=sys.stderr)
                                remote_file = posixpath.join(
                                    self.remote_bin_dir, path)
                                local_file = os.path.join(root, path)
                                self.device.push(local_file, remote_file)

    def push_progs(self, progs):
        for local_file in progs:
            remote_file = posixpath.join(self.remote_bin_dir,
                                         os.path.basename(local_file))
            self.device.push(local_file, remote_file)

    def build_environment(self, enable_webrender=False):
        env = self.build_core_environment({}, enable_webrender)
        env["LD_LIBRARY_PATH"] = self.remote_bin_dir
        env["TMPDIR"] = self.remote_tmp_dir
        env["HOME"] = self.remote_home_dir
        env["MOZ_XRE_DIR"] = self.remote_bin_dir
        if self.options.add_env:
            for envdef in self.options.add_env:
                envdef_parts = envdef.split("=", 1)
                if len(envdef_parts) == 2:
                    env[envdef_parts[0]] = envdef_parts[1]
                elif len(envdef_parts) == 1:
                    env[envdef_parts[0]] = ""
                else:
                    self.log.warning("invalid --addEnv option skipped: %s" %
                                     envdef)

        return env

    def run_one_test(self,
                     prog,
                     env,
                     symbols_path=None,
                     interactive=False,
                     timeout_factor=1):
        """
        Run a single C++ unit test program remotely.

        Arguments:
        * prog: The path to the test program to run.
        * env: The environment to use for running the program.
        * symbols_path: A path to a directory containing Breakpad-formatted
                        symbol files for producing stack traces on crash.
        * timeout_factor: An optional test-specific timeout multiplier.

        Return True if the program exits with a zero status, False otherwise.
        """
        basename = os.path.basename(prog)
        remote_bin = posixpath.join(self.remote_bin_dir, basename)
        self.log.test_start(basename)
        test_timeout = cppunittests.CPPUnitTests.TEST_PROC_TIMEOUT * timeout_factor

        try:
            output = self.device.shell_output(remote_bin,
                                              env=env,
                                              cwd=self.remote_home_dir,
                                              timeout=test_timeout)
            returncode = 0
        except ADBTimeoutError:
            raise
        except ADBProcessError as e:
            output = e.adb_process.stdout
            returncode = e.adb_process.exitcode

        self.log.process_output(basename,
                                "\n%s" % output,
                                command=[remote_bin])
        with mozfile.TemporaryDirectory() as tempdir:
            self.device.pull(self.remote_home_dir, tempdir)
            if mozcrash.check_for_crashes(tempdir,
                                          symbols_path,
                                          test_name=basename):
                self.log.test_end(basename, status="CRASH", expected="PASS")
                return False
        result = returncode == 0
        if not result:
            self.log.test_end(
                basename,
                status="FAIL",
                expected="PASS",
                message=("test failed with return code %s" % returncode),
            )
        else:
            self.log.test_end(basename, status="PASS", expected="PASS")
        return result
示例#2
0
class MochiRemote(MochitestDesktop):
    localProfile = None
    logMessages = []

    def __init__(self, options):
        MochitestDesktop.__init__(self, options.flavor, vars(options))

        verbose = False
        if options.log_mach_verbose or options.log_tbpl_level == 'debug' or \
           options.log_mach_level == 'debug' or options.log_raw_level == 'debug':
            verbose = True
        if hasattr(options, 'log'):
            delattr(options, 'log')

        self.certdbNew = True
        self.chromePushed = False

        expected = options.app.split('/')[-1]
        self.device = ADBDeviceFactory(adb=options.adbPath or 'adb',
                                       device=options.deviceSerial,
                                       test_root=options.remoteTestRoot,
                                       verbose=verbose,
                                       run_as_package=expected)

        if options.remoteTestRoot is None:
            options.remoteTestRoot = self.device.test_root
        options.dumpOutputDirectory = options.remoteTestRoot
        self.remoteLogFile = posixpath.join(options.remoteTestRoot, "logs",
                                            "mochitest.log")
        logParent = posixpath.dirname(self.remoteLogFile)
        self.device.rm(logParent, force=True, recursive=True)
        self.device.mkdir(logParent, parents=True)

        self.remoteProfile = posixpath.join(options.remoteTestRoot, "profile")
        self.device.rm(self.remoteProfile, force=True, recursive=True)

        self.counts = dict()
        self.message_logger = MessageLogger(logger=None)
        self.message_logger.logger = self.log
        process_args = {
            'messageLogger': self.message_logger,
            'counts': self.counts
        }
        self.automation = RemoteAutomation(self.device,
                                           options.remoteappname,
                                           self.remoteProfile,
                                           self.remoteLogFile,
                                           processArgs=process_args)
        self.environment = self.automation.environment

        # Check that Firefox is installed
        expected = options.app.split('/')[-1]
        if not self.device.is_app_installed(expected):
            raise Exception("%s is not installed on this device" % expected)

        self.device.clear_logcat()

        self.remoteModulesDir = posixpath.join(options.remoteTestRoot,
                                               "modules/")

        self.remoteCache = posixpath.join(options.remoteTestRoot, "cache/")
        self.device.rm(self.remoteCache, force=True, recursive=True)

        # move necko cache to a location that can be cleaned up
        options.extraPrefs += [
            "browser.cache.disk.parent_directory=%s" % self.remoteCache
        ]

        self.remoteMozLog = posixpath.join(options.remoteTestRoot, "mozlog")
        self.device.rm(self.remoteMozLog, force=True, recursive=True)
        self.device.mkdir(self.remoteMozLog, parents=True)

        self.remoteChromeTestDir = posixpath.join(options.remoteTestRoot,
                                                  "chrome")
        self.device.rm(self.remoteChromeTestDir, force=True, recursive=True)
        self.device.mkdir(self.remoteChromeTestDir, parents=True)

        procName = options.app.split('/')[-1]
        self.device.stop_application(procName)
        if self.device.process_exist(procName):
            self.log.warning("unable to kill %s before running tests!" %
                             procName)

        # Add Android version (SDK level) to mozinfo so that manifest entries
        # can be conditional on android_version.
        self.log.info(
            "Android sdk version '%s'; will use this to filter manifests" %
            str(self.device.version))
        mozinfo.info['android_version'] = str(self.device.version)
        mozinfo.info['is_fennec'] = not ('geckoview' in options.app)
        mozinfo.info['is_emulator'] = self.device._device_serial.startswith(
            'emulator-')

    def cleanup(self, options, final=False):
        if final:
            self.device.rm(self.remoteChromeTestDir,
                           force=True,
                           recursive=True)
            self.chromePushed = False
            uploadDir = os.environ.get('MOZ_UPLOAD_DIR', None)
            if uploadDir and self.device.is_dir(self.remoteMozLog):
                self.device.pull(self.remoteMozLog, uploadDir)
        self.device.rm(self.remoteLogFile, force=True)
        self.device.rm(self.remoteProfile, force=True, recursive=True)
        self.device.rm(self.remoteCache, force=True, recursive=True)
        MochitestDesktop.cleanup(self, options, final)
        self.localProfile = None

    def dumpScreen(self, utilityPath):
        if self.haveDumpedScreen:
            self.log.info(
                "Not taking screenshot here: see the one that was previously logged"
            )
            return
        self.haveDumpedScreen = True
        if self.device._device_serial.startswith('emulator-'):
            dump_screen(utilityPath, self.log)
        else:
            dump_device_screen(self.device, self.log)

    def findPath(self, paths, filename=None):
        for path in paths:
            p = path
            if filename:
                p = os.path.join(p, filename)
            if os.path.exists(self.getFullPath(p)):
                return path
        return None

    # This seems kludgy, but this class uses paths from the remote host in the
    # options, except when calling up to the base class, which doesn't
    # understand the distinction.  This switches out the remote values for local
    # ones that the base class understands.  This is necessary for the web
    # server, SSL tunnel and profile building functions.
    def switchToLocalPaths(self, options):
        """ Set local paths in the options, return a function that will restore remote values """
        remoteXrePath = options.xrePath
        remoteProfilePath = options.profilePath
        remoteUtilityPath = options.utilityPath

        paths = [
            options.xrePath,
        ]
        if build_obj:
            paths.append(os.path.join(build_obj.topobjdir, "dist", "bin"))
        options.xrePath = self.findPath(paths)
        if options.xrePath is None:
            self.log.error(
                "unable to find xulrunner path for %s, please specify with --xre-path"
                % os.name)
            sys.exit(1)

        xpcshell = "xpcshell"
        if (os.name == "nt"):
            xpcshell += ".exe"

        if options.utilityPath:
            paths = [options.utilityPath, options.xrePath]
        else:
            paths = [options.xrePath]
        options.utilityPath = self.findPath(paths, xpcshell)

        if options.utilityPath is None:
            self.log.error(
                "unable to find utility path for %s, please specify with --utility-path"
                % os.name)
            sys.exit(1)

        xpcshell_path = os.path.join(options.utilityPath, xpcshell)
        if RemoteAutomation.elf_arm(xpcshell_path):
            self.log.error('xpcshell at %s is an ARM binary; please use '
                           'the --utility-path argument to specify the path '
                           'to a desktop version.' % xpcshell_path)
            sys.exit(1)

        if self.localProfile:
            options.profilePath = self.localProfile
        else:
            options.profilePath = None

        def fixup():
            options.xrePath = remoteXrePath
            options.utilityPath = remoteUtilityPath
            options.profilePath = remoteProfilePath

        return fixup

    def startServers(self, options, debuggerInfo, public=None):
        """ Create the servers on the host and start them up """
        restoreRemotePaths = self.switchToLocalPaths(options)
        MochitestDesktop.startServers(self, options, debuggerInfo, public=True)
        restoreRemotePaths()

    def buildProfile(self, options):
        restoreRemotePaths = self.switchToLocalPaths(options)
        if options.testingModulesDir:
            try:
                self.device.push(options.testingModulesDir,
                                 self.remoteModulesDir)
                self.device.chmod(self.remoteModulesDir, recursive=True)
            except Exception:
                self.log.error(
                    "Automation Error: Unable to copy test modules to device.")
                raise
            savedTestingModulesDir = options.testingModulesDir
            options.testingModulesDir = self.remoteModulesDir
        else:
            savedTestingModulesDir = None
        manifest = MochitestDesktop.buildProfile(self, options)
        if savedTestingModulesDir:
            options.testingModulesDir = savedTestingModulesDir
        self.localProfile = options.profilePath

        restoreRemotePaths()
        options.profilePath = self.remoteProfile
        return manifest

    def buildURLOptions(self, options, env):
        saveLogFile = options.logFile
        options.logFile = self.remoteLogFile
        options.profilePath = self.localProfile
        env["MOZ_HIDE_RESULTS_TABLE"] = "1"
        retVal = MochitestDesktop.buildURLOptions(self, options, env)

        # we really need testConfig.js (for browser chrome)
        try:
            self.device.push(options.profilePath, self.remoteProfile)
            self.device.chmod(self.remoteProfile, recursive=True)
        except Exception:
            self.log.error(
                "Automation Error: Unable to copy profile to device.")
            raise

        options.profilePath = self.remoteProfile
        options.logFile = saveLogFile
        return retVal

    def getChromeTestDir(self, options):
        local = super(MochiRemote, self).getChromeTestDir(options)
        remote = self.remoteChromeTestDir
        if options.flavor == 'chrome' and not self.chromePushed:
            self.log.info("pushing %s to %s on device..." % (local, remote))
            local = os.path.join(local, "chrome")
            self.device.push(local, remote)
            self.chromePushed = True
        return remote

    def getLogFilePath(self, logFile):
        return logFile

    def printDeviceInfo(self, printLogcat=False):
        try:
            if printLogcat:
                logcat = self.device.get_logcat(
                    filter_out_regexps=fennecLogcatFilters)
                for l in logcat:
                    ul = l.decode('utf-8', errors='replace')
                    sl = ul.encode('iso8859-1', errors='replace')
                    self.log.info(sl)
            self.log.info("Device info:")
            devinfo = self.device.get_info()
            for category in devinfo:
                if type(devinfo[category]) is list:
                    self.log.info("  %s:" % category)
                    for item in devinfo[category]:
                        self.log.info("     %s" % item)
                else:
                    self.log.info("  %s: %s" % (category, devinfo[category]))
            self.log.info("Test root: %s" % self.device.test_root)
        except ADBTimeoutError:
            raise
        except Exception as e:
            self.log.warning("Error getting device information: %s" % str(e))

    def getGMPPluginPath(self, options):
        # TODO: bug 1149374
        return None

    def buildBrowserEnv(self, options, debugger=False):
        browserEnv = MochitestDesktop.buildBrowserEnv(self,
                                                      options,
                                                      debugger=debugger)
        # remove desktop environment not used on device
        if "XPCOM_MEM_BLOAT_LOG" in browserEnv:
            del browserEnv["XPCOM_MEM_BLOAT_LOG"]
        if self.mozLogs:
            browserEnv["MOZ_LOG_FILE"] = os.path.join(
                self.remoteMozLog,
                "moz-pid=%PID-uid={}.log".format(str(uuid.uuid4())))
        if options.dmd:
            browserEnv['DMD'] = '1'
        # Contents of remoteMozLog will be pulled from device and copied to the
        # host MOZ_UPLOAD_DIR, to be made available as test artifacts. Make
        # MOZ_UPLOAD_DIR available to the browser environment so that tests
        # can use it as though they were running on the host.
        browserEnv["MOZ_UPLOAD_DIR"] = self.remoteMozLog
        return browserEnv

    def runApp(self, *args, **kwargs):
        """front-end automation's `runApp` functionality until FennecRunner is written"""

        # remoteautomation `runApp` takes the profile path,
        # whereas runtest.py's `runApp` takes a mozprofile object.
        if 'profileDir' not in kwargs and 'profile' in kwargs:
            kwargs['profileDir'] = kwargs.pop('profile').profile

        # remove args not supported by automation
        kwargs.pop('marionette_args', None)

        ret, _ = self.automation.runApp(*args, **kwargs)
        self.countpass += self.counts['pass']
        self.countfail += self.counts['fail']
        self.counttodo += self.counts['todo']

        return ret, None
示例#3
0
class XPCShellRemote(xpcshell.XPCShellTests, object):
    def __init__(self, options, log):
        xpcshell.XPCShellTests.__init__(self, log)

        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")
        # 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.remoteMinidumpDir = 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.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.remoteMinidumpDir)

        # 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,
            "remoteTmpDir": self.remoteTmpDir,
            "remoteMinidumpDir": self.remoteMinidumpDir,
        }
        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 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)
        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["TMPDIR"] = self.remoteTmpDir
        self.env["HOME"] = self.profileDir
        self.env["XPCSHELL_TEST_TEMP_DIR"] = self.remoteTmpDir
        self.env["XPCSHELL_MINIDUMP_DIR"] = self.remoteMinidumpDir
        self.env["MOZ_ANDROID_DATA_DIR"] = self.remoteBinDir
        self.env["MOZ_FORCE_DISABLE_E10S"] = "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)

        # The xpcshell binary is required for all tests. Additional binaries
        # are required for some tests. This list should be similar to
        # TEST_HARNESS_BINS in testing/mochitest/Makefile.in.
        binaries = [
            "xpcshell",
            "ssltunnel",
            "certutil",
            "pk12util",
            "BadCertAndPinningServer",
            "DelegatedCredentialsServer",
            "EncryptedClientHelloServer",
            "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()
示例#4
0
class MochiRemote(MochitestDesktop):
    localProfile = None
    logMessages = []

    def __init__(self, options):
        MochitestDesktop.__init__(self, options.flavor, vars(options))

        verbose = False
        if (options.log_mach_verbose or options.log_tbpl_level == "debug"
                or options.log_mach_level == "debug"
                or options.log_raw_level == "debug"):
            verbose = True
        if hasattr(options, "log"):
            delattr(options, "log")

        self.certdbNew = True
        self.chromePushed = False

        expected = options.app.split("/")[-1]
        self.device = ADBDeviceFactory(
            adb=options.adbPath or "adb",
            device=options.deviceSerial,
            test_root=options.remoteTestRoot,
            verbose=verbose,
            run_as_package=expected,
        )

        if options.remoteTestRoot is None:
            options.remoteTestRoot = self.device.test_root
        options.dumpOutputDirectory = options.remoteTestRoot
        self.remoteLogFile = posixpath.join(options.remoteTestRoot, "logs",
                                            "mochitest.log")
        logParent = posixpath.dirname(self.remoteLogFile)
        self.device.rm(logParent, force=True, recursive=True)
        self.device.mkdir(logParent, parents=True)

        self.remoteProfile = posixpath.join(options.remoteTestRoot, "profile")
        self.device.rm(self.remoteProfile, force=True, recursive=True)

        self.message_logger = MessageLogger(logger=None)
        self.message_logger.logger = self.log

        # Check that Firefox is installed
        expected = options.app.split("/")[-1]
        if not self.device.is_app_installed(expected):
            raise Exception("%s is not installed on this device" % expected)

        self.device.clear_logcat()

        self.remoteModulesDir = posixpath.join(options.remoteTestRoot,
                                               "modules/")

        self.remoteCache = posixpath.join(options.remoteTestRoot, "cache/")
        self.device.rm(self.remoteCache, force=True, recursive=True)

        # move necko cache to a location that can be cleaned up
        options.extraPrefs += [
            "browser.cache.disk.parent_directory=%s" % self.remoteCache
        ]

        self.remoteMozLog = posixpath.join(options.remoteTestRoot, "mozlog")
        self.device.rm(self.remoteMozLog, force=True, recursive=True)
        self.device.mkdir(self.remoteMozLog, parents=True)

        self.remoteChromeTestDir = posixpath.join(options.remoteTestRoot,
                                                  "chrome")
        self.device.rm(self.remoteChromeTestDir, force=True, recursive=True)
        self.device.mkdir(self.remoteChromeTestDir, parents=True)

        self.appName = options.remoteappname
        self.device.stop_application(self.appName)
        if self.device.process_exist(self.appName):
            self.log.warning("unable to kill %s before running tests!" %
                             self.appName)

        # Add Android version (SDK level) to mozinfo so that manifest entries
        # can be conditional on android_version.
        self.log.info(
            "Android sdk version '%s'; will use this to filter manifests" %
            str(self.device.version))
        mozinfo.info["android_version"] = str(self.device.version)
        mozinfo.info["is_fennec"] = not ("geckoview" in options.app)
        mozinfo.info["is_emulator"] = self.device._device_serial.startswith(
            "emulator-")

    def cleanup(self, options, final=False):
        if final:
            self.device.rm(self.remoteChromeTestDir,
                           force=True,
                           recursive=True)
            self.chromePushed = False
            uploadDir = os.environ.get("MOZ_UPLOAD_DIR", None)
            if uploadDir and self.device.is_dir(self.remoteMozLog):
                self.device.pull(self.remoteMozLog, uploadDir)
        self.device.rm(self.remoteLogFile, force=True)
        self.device.rm(self.remoteProfile, force=True, recursive=True)
        self.device.rm(self.remoteCache, force=True, recursive=True)
        MochitestDesktop.cleanup(self, options, final)
        self.localProfile = None

    def dumpScreen(self, utilityPath):
        if self.haveDumpedScreen:
            self.log.info(
                "Not taking screenshot here: see the one that was previously logged"
            )
            return
        self.haveDumpedScreen = True
        if self.device._device_serial.startswith("emulator-"):
            dump_screen(utilityPath, self.log)
        else:
            dump_device_screen(self.device, self.log)

    def findPath(self, paths, filename=None):
        for path in paths:
            p = path
            if filename:
                p = os.path.join(p, filename)
            if os.path.exists(self.getFullPath(p)):
                return path
        return None

    # This seems kludgy, but this class uses paths from the remote host in the
    # options, except when calling up to the base class, which doesn't
    # understand the distinction.  This switches out the remote values for local
    # ones that the base class understands.  This is necessary for the web
    # server, SSL tunnel and profile building functions.
    def switchToLocalPaths(self, options):
        """ Set local paths in the options, return a function that will restore remote values """
        remoteXrePath = options.xrePath
        remoteProfilePath = options.profilePath
        remoteUtilityPath = options.utilityPath

        paths = [
            options.xrePath,
        ]
        if build_obj:
            paths.append(os.path.join(build_obj.topobjdir, "dist", "bin"))
        options.xrePath = self.findPath(paths)
        if options.xrePath is None:
            self.log.error(
                "unable to find xulrunner path for %s, please specify with --xre-path"
                % os.name)
            sys.exit(1)

        xpcshell = "xpcshell"
        if os.name == "nt":
            xpcshell += ".exe"

        if options.utilityPath:
            paths = [options.utilityPath, options.xrePath]
        else:
            paths = [options.xrePath]
        options.utilityPath = self.findPath(paths, xpcshell)

        if options.utilityPath is None:
            self.log.error(
                "unable to find utility path for %s, please specify with --utility-path"
                % os.name)
            sys.exit(1)

        xpcshell_path = os.path.join(options.utilityPath, xpcshell)
        if RemoteProcessMonitor.elf_arm(xpcshell_path):
            self.log.error("xpcshell at %s is an ARM binary; please use "
                           "the --utility-path argument to specify the path "
                           "to a desktop version." % xpcshell_path)
            sys.exit(1)

        if self.localProfile:
            options.profilePath = self.localProfile
        else:
            options.profilePath = None

        def fixup():
            options.xrePath = remoteXrePath
            options.utilityPath = remoteUtilityPath
            options.profilePath = remoteProfilePath

        return fixup

    def startServers(self, options, debuggerInfo, public=None):
        """ Create the servers on the host and start them up """
        restoreRemotePaths = self.switchToLocalPaths(options)
        MochitestDesktop.startServers(self, options, debuggerInfo, public=True)
        restoreRemotePaths()

    def buildProfile(self, options):
        restoreRemotePaths = self.switchToLocalPaths(options)
        if options.testingModulesDir:
            try:
                self.device.push(options.testingModulesDir,
                                 self.remoteModulesDir)
                self.device.chmod(self.remoteModulesDir, recursive=True)
            except Exception:
                self.log.error(
                    "Automation Error: Unable to copy test modules to device.")
                raise
            savedTestingModulesDir = options.testingModulesDir
            options.testingModulesDir = self.remoteModulesDir
        else:
            savedTestingModulesDir = None
        manifest = MochitestDesktop.buildProfile(self, options)
        if savedTestingModulesDir:
            options.testingModulesDir = savedTestingModulesDir
        self.localProfile = options.profilePath

        restoreRemotePaths()
        options.profilePath = self.remoteProfile
        return manifest

    def buildURLOptions(self, options, env):
        saveLogFile = options.logFile
        options.logFile = self.remoteLogFile
        options.profilePath = self.localProfile
        env["MOZ_HIDE_RESULTS_TABLE"] = "1"
        retVal = MochitestDesktop.buildURLOptions(self, options, env)

        # we really need testConfig.js (for browser chrome)
        try:
            self.device.push(options.profilePath, self.remoteProfile)
            self.device.chmod(self.remoteProfile, recursive=True)
        except Exception:
            self.log.error(
                "Automation Error: Unable to copy profile to device.")
            raise

        options.profilePath = self.remoteProfile
        options.logFile = saveLogFile
        return retVal

    def getChromeTestDir(self, options):
        local = super(MochiRemote, self).getChromeTestDir(options)
        remote = self.remoteChromeTestDir
        if options.flavor == "chrome" and not self.chromePushed:
            self.log.info("pushing %s to %s on device..." % (local, remote))
            local = os.path.join(local, "chrome")
            self.device.push(local, remote)
            self.chromePushed = True
        return remote

    def getLogFilePath(self, logFile):
        return logFile

    def printDeviceInfo(self, printLogcat=False):
        try:
            if printLogcat:
                logcat = self.device.get_logcat()
                for l in logcat:
                    ul = l.decode("utf-8", errors="replace")
                    sl = ul.encode("iso8859-1", errors="replace")
                    self.log.info(sl)
            self.log.info("Device info:")
            devinfo = self.device.get_info()
            for category in devinfo:
                if type(devinfo[category]) is list:
                    self.log.info("  %s:" % category)
                    for item in devinfo[category]:
                        self.log.info("     %s" % item)
                else:
                    self.log.info("  %s: %s" % (category, devinfo[category]))
            self.log.info("Test root: %s" % self.device.test_root)
        except ADBTimeoutError:
            raise
        except Exception as e:
            self.log.warning("Error getting device information: %s" % str(e))

    def getGMPPluginPath(self, options):
        # TODO: bug 1149374
        return None

    def environment(self, env=None, crashreporter=True, **kwargs):
        # Since running remote, do not mimic the local env: do not copy os.environ
        if env is None:
            env = {}

        if crashreporter:
            env["MOZ_CRASHREPORTER_NO_REPORT"] = "1"
            env["MOZ_CRASHREPORTER"] = "1"
            env["MOZ_CRASHREPORTER_SHUTDOWN"] = "1"
        else:
            env["MOZ_CRASHREPORTER_DISABLE"] = "1"

        # Crash on non-local network connections by default.
        # MOZ_DISABLE_NONLOCAL_CONNECTIONS can be set to "0" to temporarily
        # enable non-local connections for the purposes of local testing.
        # Don't override the user's choice here.  See bug 1049688.
        env.setdefault("MOZ_DISABLE_NONLOCAL_CONNECTIONS", "1")

        # Send an env var noting that we are in automation. Passing any
        # value except the empty string will declare the value to exist.
        #
        # This may be used to disabled network connections during testing, e.g.
        # Switchboard & telemetry uploads.
        env.setdefault("MOZ_IN_AUTOMATION", "1")

        # Set WebRTC logging in case it is not set yet.
        env.setdefault("R_LOG_LEVEL", "6")
        env.setdefault("R_LOG_DESTINATION", "stderr")
        env.setdefault("R_LOG_VERBOSE", "1")

        return env

    def buildBrowserEnv(self, options, debugger=False):
        browserEnv = MochitestDesktop.buildBrowserEnv(self,
                                                      options,
                                                      debugger=debugger)
        # remove desktop environment not used on device
        if "XPCOM_MEM_BLOAT_LOG" in browserEnv:
            del browserEnv["XPCOM_MEM_BLOAT_LOG"]
        if self.mozLogs:
            browserEnv["MOZ_LOG_FILE"] = os.path.join(
                self.remoteMozLog,
                "moz-pid=%PID-uid={}.log".format(str(uuid.uuid4())))
        if options.dmd:
            browserEnv["DMD"] = "1"
        # Contents of remoteMozLog will be pulled from device and copied to the
        # host MOZ_UPLOAD_DIR, to be made available as test artifacts. Make
        # MOZ_UPLOAD_DIR available to the browser environment so that tests
        # can use it as though they were running on the host.
        browserEnv["MOZ_UPLOAD_DIR"] = self.remoteMozLog
        return browserEnv

    def runApp(
        self,
        testUrl,
        env,
        app,
        profile,
        extraArgs,
        utilityPath,
        debuggerInfo=None,
        valgrindPath=None,
        valgrindArgs=None,
        valgrindSuppFiles=None,
        symbolsPath=None,
        timeout=-1,
        detectShutdownLeaks=False,
        screenshotOnFail=False,
        bisectChunk=None,
        marionette_args=None,
        e10s=True,
        runFailures=False,
        crashAsPass=False,
    ):
        """
        Run the app, log the duration it took to execute, return the status code.
        Kill the app if it outputs nothing for |timeout| seconds.
        """

        if timeout == -1:
            timeout = self.DEFAULT_TIMEOUT

        rpm = RemoteProcessMonitor(
            self.appName,
            self.device,
            self.log,
            self.message_logger,
            self.remoteLogFile,
            self.remoteProfile,
        )
        startTime = datetime.datetime.now()
        status = 0
        profileDirectory = self.remoteProfile + "/"
        args = []
        args.extend(extraArgs)
        args.extend(("-no-remote", "-profile", profileDirectory))

        pid = rpm.launch(
            app,
            debuggerInfo,
            testUrl,
            args,
            env=self.environment(env=env, crashreporter=not debuggerInfo),
            e10s=e10s,
        )

        # TODO: not using runFailures or crashAsPass, if we choose to use them
        # we need to adjust status and check_for_crashes
        self.log.info("runtestsremote.py | Application pid: %d" % pid)
        if not rpm.wait(timeout):
            status = 1
        self.log.info("runtestsremote.py | Application ran for: %s" %
                      str(datetime.datetime.now() - startTime))
        crashed = self.check_for_crashes(symbolsPath, rpm.last_test_seen)
        if crashed:
            status = 1

        self.countpass += rpm.counts["pass"]
        self.countfail += rpm.counts["fail"]
        self.counttodo += rpm.counts["todo"]

        return status, rpm.last_test_seen

    def check_for_crashes(self, symbols_path, last_test_seen):
        """
        Pull any minidumps from remote profile and log any associated crashes.
        """
        try:
            dump_dir = tempfile.mkdtemp()
            remote_crash_dir = posixpath.join(self.remoteProfile, "minidumps")
            if not self.device.is_dir(remote_crash_dir):
                return False
            self.device.pull(remote_crash_dir, dump_dir)
            crashed = mozcrash.log_crashes(self.log,
                                           dump_dir,
                                           symbols_path,
                                           test=last_test_seen)
        finally:
            try:
                shutil.rmtree(dump_dir)
            except Exception as e:
                self.log.warning("unable to remove directory %s: %s" %
                                 (dump_dir, str(e)))
        return crashed
示例#5
0
class RemoteReftest(RefTest):
    use_marionette = False
    resolver_cls = RemoteReftestResolver

    def __init__(self, options, scriptDir):
        RefTest.__init__(self, options.suite)
        self.run_by_manifest = False
        self.scriptDir = scriptDir
        self.localLogName = options.localLogName

        verbose = False
        if (options.log_mach_verbose or options.log_tbpl_level == "debug"
                or options.log_mach_level == "debug"
                or options.log_raw_level == "debug"):
            verbose = True
            print("set verbose!")
        expected = options.app.split("/")[-1]
        self.device = ADBDeviceFactory(
            adb=options.adb_path or "adb",
            device=options.deviceSerial,
            test_root=options.remoteTestRoot,
            verbose=verbose,
            run_as_package=expected,
        )
        if options.remoteTestRoot is None:
            options.remoteTestRoot = posixpath.join(self.device.test_root,
                                                    "reftest")
        options.remoteProfile = posixpath.join(options.remoteTestRoot,
                                               "profile")
        options.remoteLogFile = posixpath.join(options.remoteTestRoot,
                                               "reftest.log")
        options.logFile = options.remoteLogFile
        self.remoteProfile = options.remoteProfile
        self.remoteTestRoot = options.remoteTestRoot

        if not options.ignoreWindowSize:
            parts = self.device.get_info("screen")["screen"][0].split()
            width = int(parts[0].split(":")[1])
            height = int(parts[1].split(":")[1])
            if width < 1366 or height < 1050:
                self.error("ERROR: Invalid screen resolution %sx%s, "
                           "please adjust to 1366x1050 or higher" %
                           (width, height))

        self._populate_logger(options)
        self.outputHandler = OutputHandler(self.log, options.utilityPath,
                                           options.symbolsPath)
        # RemoteAutomation.py's 'messageLogger' is also used by mochitest. Mimic a mochitest
        # MessageLogger object to re-use this code path.
        self.outputHandler.write = self.outputHandler.__call__
        args = {"messageLogger": self.outputHandler}
        self.automation = RemoteAutomation(
            self.device,
            appName=options.app,
            remoteProfile=self.remoteProfile,
            remoteLog=options.remoteLogFile,
            processArgs=args,
        )

        self.environment = self.automation.environment
        self.SERVER_STARTUP_TIMEOUT = 90

        self.remoteCache = os.path.join(options.remoteTestRoot, "cache/")

        # Check that Firefox is installed
        expected = options.app.split("/")[-1]
        if not self.device.is_app_installed(expected):
            raise Exception("%s is not installed on this device" % expected)
        self.device.run_as_package = expected
        self.device.clear_logcat()

        self.device.rm(self.remoteCache, force=True, recursive=True)

        procName = options.app.split("/")[-1]
        self.device.stop_application(procName)
        if self.device.process_exist(procName):
            self.log.error("unable to kill %s before starting tests!" %
                           procName)

    def findPath(self, paths, filename=None):
        for path in paths:
            p = path
            if filename:
                p = os.path.join(p, filename)
            if os.path.exists(self.getFullPath(p)):
                return path
        return None

    def startWebServer(self, options):
        """ Create the webserver on the host and start it up """
        remoteXrePath = options.xrePath
        remoteUtilityPath = options.utilityPath

        paths = [options.xrePath]
        if build_obj:
            paths.append(os.path.join(build_obj.topobjdir, "dist", "bin"))
        options.xrePath = self.findPath(paths)
        if options.xrePath is None:
            print("ERROR: unable to find xulrunner path for %s, "
                  "please specify with --xre-path" % (os.name))
            return 1
        paths.append("bin")
        paths.append(os.path.join("..", "bin"))

        xpcshell = "xpcshell"
        if os.name == "nt":
            xpcshell += ".exe"

        if options.utilityPath:
            paths.insert(0, options.utilityPath)
        options.utilityPath = self.findPath(paths, xpcshell)
        if options.utilityPath is None:
            print("ERROR: unable to find utility path for %s, "
                  "please specify with --utility-path" % (os.name))
            return 1

        options.serverProfilePath = tempfile.mkdtemp()
        self.server = ReftestServer(options, self.scriptDir, self.log)
        retVal = self.server.start()
        if retVal:
            return retVal
        retVal = self.server.ensureReady(self.SERVER_STARTUP_TIMEOUT)
        if retVal:
            return retVal

        options.xrePath = remoteXrePath
        options.utilityPath = remoteUtilityPath
        return 0

    def stopWebServer(self, options):
        self.server.stop()

    def killNamedProc(self, pname, orphans=True):
        """ Kill processes matching the given command name """
        try:
            import psutil
        except ImportError as e:
            self.log.warning("Unable to import psutil: %s" % str(e))
            self.log.warning(
                "Unable to verify that %s is not already running." % pname)
            return

        self.log.info("Checking for %s processes..." % pname)

        for proc in psutil.process_iter():
            try:
                if proc.name() == pname:
                    procd = proc.as_dict(
                        attrs=["pid", "ppid", "name", "username"])
                    if proc.ppid() == 1 or not orphans:
                        self.log.info("killing %s" % procd)
                        try:
                            os.kill(proc.pid,
                                    getattr(signal, "SIGKILL", signal.SIGTERM))
                        except Exception as e:
                            self.log.info("Failed to kill process %d: %s" %
                                          (proc.pid, str(e)))
                    else:
                        self.log.info("NOT killing %s (not an orphan?)" %
                                      procd)
            except Exception:
                # may not be able to access process info for all processes
                continue

    def createReftestProfile(self, options, **kwargs):
        profile = RefTest.createReftestProfile(self,
                                               options,
                                               server=options.remoteWebServer,
                                               port=options.httpPort,
                                               **kwargs)
        profileDir = profile.profile
        prefs = {}
        prefs["app.update.url.android"] = ""
        prefs["reftest.remote"] = True
        prefs[
            "datareporting.policy.dataSubmissionPolicyBypassAcceptance"] = True
        # move necko cache to a location that can be cleaned up
        prefs["browser.cache.disk.parent_directory"] = self.remoteCache

        prefs["layout.css.devPixelsPerPx"] = "1.0"
        # Because Fennec is a little wacky (see bug 1156817) we need to load the
        # reftest pages at 1.0 zoom, rather than zooming to fit the CSS viewport.
        prefs["apz.allow_zooming"] = False

        # Set the extra prefs.
        profile.set_preferences(prefs)

        try:
            self.device.push(profileDir, options.remoteProfile)
            # make sure the parent directories of the profile which
            # may have been created by the push, also have their
            # permissions set to allow access.
            self.device.chmod(options.remoteTestRoot, recursive=True)
        except Exception:
            print("Automation Error: Failed to copy profiledir to device")
            raise

        return profile

    def printDeviceInfo(self, printLogcat=False):
        try:
            if printLogcat:
                logcat = self.device.get_logcat(
                    filter_out_regexps=fennecLogcatFilters)
                for l in logcat:
                    ul = l.decode("utf-8", errors="replace")
                    sl = ul.encode("iso8859-1", errors="replace")
                    print("%s\n" % sl)
            print("Device info:")
            devinfo = self.device.get_info()
            for category in devinfo:
                if type(devinfo[category]) is list:
                    print("  %s:" % category)
                    for item in devinfo[category]:
                        print("     %s" % item)
                else:
                    print("  %s: %s" % (category, devinfo[category]))
            print("Test root: %s" % self.device.test_root)
        except ADBTimeoutError:
            raise
        except Exception as e:
            print("WARNING: Error getting device information: %s" % str(e))

    def environment(self, **kwargs):
        return self.automation.environment(**kwargs)

    def buildBrowserEnv(self, options, profileDir):
        browserEnv = RefTest.buildBrowserEnv(self, options, profileDir)
        # remove desktop environment not used on device
        if "XPCOM_MEM_BLOAT_LOG" in browserEnv:
            del browserEnv["XPCOM_MEM_BLOAT_LOG"]
        return browserEnv

    def runApp(self,
               options,
               cmdargs=None,
               timeout=None,
               debuggerInfo=None,
               symbolsPath=None,
               valgrindPath=None,
               valgrindArgs=None,
               valgrindSuppFiles=None,
               **profileArgs):
        if cmdargs is None:
            cmdargs = []

        if self.use_marionette:
            cmdargs.append("-marionette")

        binary = options.app
        profile = self.createReftestProfile(options, **profileArgs)

        # browser environment
        env = self.buildBrowserEnv(options, profile.profile)

        self.log.info("Running with e10s: {}".format(options.e10s))
        self.log.info("Running with fission: {}".format(options.fission))
        status, self.lastTestSeen = self.automation.runApp(
            None,
            env,
            binary,
            profile.profile,
            cmdargs,
            utilityPath=options.utilityPath,
            xrePath=options.xrePath,
            debuggerInfo=debuggerInfo,
            symbolsPath=symbolsPath,
            timeout=timeout,
            e10s=options.e10s,
        )

        self.cleanup(profile.profile)
        return status

    def cleanup(self, profileDir):
        self.device.rm(self.remoteTestRoot, force=True, recursive=True)
        self.device.rm(self.remoteProfile, force=True, recursive=True)
        self.device.rm(self.remoteCache, force=True, recursive=True)
        RefTest.cleanup(self, profileDir)
示例#6
0
class RemoteReftest(RefTest):
    use_marionette = False
    resolver_cls = RemoteReftestResolver

    def __init__(self, options, scriptDir):
        RefTest.__init__(self, options.suite)
        self.run_by_manifest = False
        self.scriptDir = scriptDir
        self.localLogName = options.localLogName

        verbose = False
        if (options.log_mach_verbose or options.log_tbpl_level == "debug"
                or options.log_mach_level == "debug"
                or options.log_raw_level == "debug"):
            verbose = True
            print("set verbose!")
        expected = options.app.split("/")[-1]
        self.device = ADBDeviceFactory(
            adb=options.adb_path or "adb",
            device=options.deviceSerial,
            test_root=options.remoteTestRoot,
            verbose=verbose,
            run_as_package=expected,
        )
        if options.remoteTestRoot is None:
            options.remoteTestRoot = posixpath.join(self.device.test_root,
                                                    "reftest")
        options.remoteProfile = posixpath.join(options.remoteTestRoot,
                                               "profile")
        options.remoteLogFile = posixpath.join(options.remoteTestRoot,
                                               "reftest.log")
        options.logFile = options.remoteLogFile
        self.remoteProfile = options.remoteProfile
        self.remoteTestRoot = options.remoteTestRoot

        if not options.ignoreWindowSize:
            parts = self.device.get_info("screen")["screen"][0].split()
            width = int(parts[0].split(":")[1])
            height = int(parts[1].split(":")[1])
            if width < 1366 or height < 1050:
                self.error("ERROR: Invalid screen resolution %sx%s, "
                           "please adjust to 1366x1050 or higher" %
                           (width, height))

        self._populate_logger(options)
        self.outputHandler = OutputHandler(self.log, options.utilityPath,
                                           options.symbolsPath)

        self.SERVER_STARTUP_TIMEOUT = 90

        self.remoteCache = os.path.join(options.remoteTestRoot, "cache/")

        # Check that Firefox is installed
        expected = options.app.split("/")[-1]
        if not self.device.is_app_installed(expected):
            raise Exception("%s is not installed on this device" % expected)
        self.device.run_as_package = expected
        self.device.clear_logcat()

        self.device.rm(self.remoteCache, force=True, recursive=True)

        procName = options.app.split("/")[-1]
        self.device.stop_application(procName)
        if self.device.process_exist(procName):
            self.log.error("unable to kill %s before starting tests!" %
                           procName)

    def findPath(self, paths, filename=None):
        for path in paths:
            p = path
            if filename:
                p = os.path.join(p, filename)
            if os.path.exists(self.getFullPath(p)):
                return path
        return None

    def startWebServer(self, options):
        """ Create the webserver on the host and start it up """
        remoteXrePath = options.xrePath
        remoteUtilityPath = options.utilityPath

        paths = [options.xrePath]
        if build_obj:
            paths.append(os.path.join(build_obj.topobjdir, "dist", "bin"))
        options.xrePath = self.findPath(paths)
        if options.xrePath is None:
            print("ERROR: unable to find xulrunner path for %s, "
                  "please specify with --xre-path" % (os.name))
            return 1
        paths.append("bin")
        paths.append(os.path.join("..", "bin"))

        xpcshell = "xpcshell"
        if os.name == "nt":
            xpcshell += ".exe"

        if options.utilityPath:
            paths.insert(0, options.utilityPath)
        options.utilityPath = self.findPath(paths, xpcshell)
        if options.utilityPath is None:
            print("ERROR: unable to find utility path for %s, "
                  "please specify with --utility-path" % (os.name))
            return 1

        options.serverProfilePath = tempfile.mkdtemp()
        self.server = ReftestServer(options, self.scriptDir, self.log)
        retVal = self.server.start()
        if retVal:
            return retVal
        retVal = self.server.ensureReady(self.SERVER_STARTUP_TIMEOUT)
        if retVal:
            return retVal

        options.xrePath = remoteXrePath
        options.utilityPath = remoteUtilityPath
        return 0

    def stopWebServer(self, options):
        self.server.stop()

    def killNamedProc(self, pname, orphans=True):
        """ Kill processes matching the given command name """
        try:
            import psutil
        except ImportError as e:
            self.log.warning("Unable to import psutil: %s" % str(e))
            self.log.warning(
                "Unable to verify that %s is not already running." % pname)
            return

        self.log.info("Checking for %s processes..." % pname)

        for proc in psutil.process_iter():
            try:
                if proc.name() == pname:
                    procd = proc.as_dict(
                        attrs=["pid", "ppid", "name", "username"])
                    if proc.ppid() == 1 or not orphans:
                        self.log.info("killing %s" % procd)
                        try:
                            os.kill(proc.pid,
                                    getattr(signal, "SIGKILL", signal.SIGTERM))
                        except Exception as e:
                            self.log.info("Failed to kill process %d: %s" %
                                          (proc.pid, str(e)))
                    else:
                        self.log.info("NOT killing %s (not an orphan?)" %
                                      procd)
            except Exception:
                # may not be able to access process info for all processes
                continue

    def createReftestProfile(self, options, **kwargs):
        profile = RefTest.createReftestProfile(self,
                                               options,
                                               server=options.remoteWebServer,
                                               port=options.httpPort,
                                               **kwargs)
        profileDir = profile.profile
        prefs = {}
        prefs["app.update.url.android"] = ""
        prefs["reftest.remote"] = True
        prefs[
            "datareporting.policy.dataSubmissionPolicyBypassAcceptance"] = True
        # move necko cache to a location that can be cleaned up
        prefs["browser.cache.disk.parent_directory"] = self.remoteCache

        prefs["layout.css.devPixelsPerPx"] = "1.0"
        # Because Fennec is a little wacky (see bug 1156817) we need to load the
        # reftest pages at 1.0 zoom, rather than zooming to fit the CSS viewport.
        prefs["apz.allow_zooming"] = False

        # Set the extra prefs.
        profile.set_preferences(prefs)

        try:
            self.device.push(profileDir, options.remoteProfile)
            # make sure the parent directories of the profile which
            # may have been created by the push, also have their
            # permissions set to allow access.
            self.device.chmod(options.remoteTestRoot, recursive=True)
        except Exception:
            print("Automation Error: Failed to copy profiledir to device")
            raise

        return profile

    def environment(self, env=None, crashreporter=True, **kwargs):
        # Since running remote, do not mimic the local env: do not copy os.environ
        if env is None:
            env = {}

        if crashreporter:
            env["MOZ_CRASHREPORTER_NO_REPORT"] = "1"
            env["MOZ_CRASHREPORTER"] = "1"
            env["MOZ_CRASHREPORTER_SHUTDOWN"] = "1"
        else:
            env["MOZ_CRASHREPORTER_DISABLE"] = "1"

        # Crash on non-local network connections by default.
        # MOZ_DISABLE_NONLOCAL_CONNECTIONS can be set to "0" to temporarily
        # enable non-local connections for the purposes of local testing.
        # Don't override the user's choice here.  See bug 1049688.
        env.setdefault("MOZ_DISABLE_NONLOCAL_CONNECTIONS", "1")

        # Send an env var noting that we are in automation. Passing any
        # value except the empty string will declare the value to exist.
        #
        # This may be used to disabled network connections during testing, e.g.
        # Switchboard & telemetry uploads.
        env.setdefault("MOZ_IN_AUTOMATION", "1")

        # Set WebRTC logging in case it is not set yet.
        env.setdefault("R_LOG_LEVEL", "6")
        env.setdefault("R_LOG_DESTINATION", "stderr")
        env.setdefault("R_LOG_VERBOSE", "1")

        return env

    def buildBrowserEnv(self, options, profileDir):
        browserEnv = RefTest.buildBrowserEnv(self, options, profileDir)
        # remove desktop environment not used on device
        if "XPCOM_MEM_BLOAT_LOG" in browserEnv:
            del browserEnv["XPCOM_MEM_BLOAT_LOG"]
        return browserEnv

    def runApp(self,
               options,
               cmdargs=None,
               timeout=None,
               debuggerInfo=None,
               symbolsPath=None,
               valgrindPath=None,
               valgrindArgs=None,
               valgrindSuppFiles=None,
               **profileArgs):
        if cmdargs is None:
            cmdargs = []

        if self.use_marionette:
            cmdargs.append("-marionette")

        binary = options.app
        profile = self.createReftestProfile(options, **profileArgs)

        # browser environment
        env = self.buildBrowserEnv(options, profile.profile)

        self.log.info("Running with e10s: {}".format(options.e10s))
        self.log.info("Running with fission: {}".format(options.fission))

        rpm = RemoteProcessMonitor(
            binary,
            self.device,
            self.log,
            self.outputHandler,
            options.remoteLogFile,
            self.remoteProfile,
        )
        startTime = datetime.datetime.now()
        status = 0
        profileDirectory = self.remoteProfile + "/"
        cmdargs.extend(("-no-remote", "-profile", profileDirectory))

        pid = rpm.launch(
            binary,
            debuggerInfo,
            None,
            cmdargs,
            env=env,
            e10s=options.e10s,
        )
        self.log.info("remotereftest.py | Application pid: %d" % pid)
        if not rpm.wait(timeout):
            status = 1
        self.log.info("remotereftest.py | Application ran for: %s" %
                      str(datetime.datetime.now() - startTime))
        crashed = self.check_for_crashes(symbolsPath, rpm.last_test_seen)
        if crashed:
            status = 1

        self.cleanup(profile.profile)
        return status

    def check_for_crashes(self, symbols_path, last_test_seen):
        """
        Pull any minidumps from remote profile and log any associated crashes.
        """
        try:
            dump_dir = tempfile.mkdtemp()
            remote_crash_dir = posixpath.join(self.remoteProfile, "minidumps")
            if not self.device.is_dir(remote_crash_dir):
                return False
            self.device.pull(remote_crash_dir, dump_dir)
            crashed = mozcrash.log_crashes(self.log,
                                           dump_dir,
                                           symbols_path,
                                           test=last_test_seen)
        finally:
            try:
                shutil.rmtree(dump_dir)
            except Exception as e:
                self.log.warning("unable to remove directory %s: %s" %
                                 (dump_dir, str(e)))
        return crashed

    def cleanup(self, profileDir):
        self.device.rm(self.remoteTestRoot, force=True, recursive=True)
        self.device.rm(self.remoteProfile, force=True, recursive=True)
        self.device.rm(self.remoteCache, force=True, recursive=True)
        RefTest.cleanup(self, profileDir)