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)
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)
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 __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
class JUnitTestRunner(MochitestDesktop): """ A test harness to run geckoview junit tests on a remote device. """ def __init__(self, log, options): self.log = log self.verbose = False if (options.log_tbpl_level == "debug" or options.log_mach_level == "debug" or options.verbose): self.verbose = True self.device = ADBDeviceFactory( adb=options.adbPath or "adb", device=options.deviceSerial, test_root=options.remoteTestRoot, verbose=self.verbose, run_as_package=options.app, ) self.options = options self.log.debug("options=%s" % vars(options)) update_mozinfo() self.remote_profile = posixpath.join(self.device.test_root, "junit-profile") self.remote_filter_list = posixpath.join(self.device.test_root, "junit-filters.list") if self.options.coverage and not self.options.coverage_output_dir: raise UserError( "--coverage-output-dir is required when using --enable-coverage" ) if self.options.coverage: self.remote_coverage_output_file = posixpath.join( self.device.test_root, "junit-coverage.ec") self.coverage_output_file = os.path.join( self.options.coverage_output_dir, "junit-coverage.ec") self.server_init() self.cleanup() self.device.clear_logcat() self.build_profile() self.startServers(self.options, debuggerInfo=None, public=True) self.log.debug("Servers started") def collectLogcatForCurrentTest(self): # These are unique start and end markers logged by GeckoSessionTestRule.java START_MARKER = "1f0befec-3ff2-40ff-89cf-b127eb38b1ec" END_MARKER = "c5ee677f-bc83-49bd-9e28-2d35f3d0f059" logcat = self.device.get_logcat() test_logcat = "" started = False for l in logcat: if START_MARKER in l and self.test_name in l: started = True if started: test_logcat += l + "\n" if started and END_MARKER in l: return test_logcat def needsWebsocketProcessBridge(self, options): """ Overrides MochitestDesktop.needsWebsocketProcessBridge and always returns False as the junit tests do not use the websocket process bridge. This is needed to satisfy MochitestDesktop.startServers. """ return False def server_init(self): """ Additional initialization required to satisfy MochitestDesktop.startServers """ self._locations = None self.server = None self.wsserver = None self.websocketProcessBridge = None self.SERVER_STARTUP_TIMEOUT = 180 if mozinfo.info.get("debug") else 90 if self.options.remoteWebServer is None: self.options.remoteWebServer = moznetwork.get_ip() self.options.webServer = self.options.remoteWebServer self.options.webSocketPort = "9988" self.options.httpdPath = None self.options.keep_open = False self.options.pidFile = "" self.options.subsuite = None self.options.xrePath = None if build_obj and "MOZ_HOST_BIN" in os.environ: self.options.xrePath = os.environ["MOZ_HOST_BIN"] if not self.options.utilityPath: self.options.utilityPath = self.options.xrePath if not self.options.xrePath: self.options.xrePath = self.options.utilityPath if build_obj: self.options.certPath = os.path.join(build_obj.topsrcdir, "build", "pgo", "certs") def build_profile(self): """ Create a local profile with test prefs and proxy definitions and push it to the remote device. """ self.profile = Profile(locations=self.locations, proxy=self.proxy(self.options)) self.options.profilePath = self.profile.profile # Set preferences self.merge_base_profiles(self.options, "geckoview-junit") prefs = parse_preferences(self.options.extra_prefs) self.profile.set_preferences(prefs) if self.fillCertificateDB(self.options): self.log.error("Certificate integration failed") self.device.push(self.profile.profile, self.remote_profile) self.log.debug("profile %s -> %s" % (str(self.profile.profile), str(self.remote_profile))) def cleanup(self): try: self.stopServers() self.log.debug("Servers stopped") self.device.stop_application(self.options.app) self.device.rm(self.remote_profile, force=True, recursive=True) if hasattr(self, "profile"): del self.profile self.device.rm(self.remote_filter_list, force=True) except Exception: traceback.print_exc() self.log.info("Caught and ignored an exception during cleanup") def build_command_line(self, test_filters_file, test_filters): """ Construct and return the 'am instrument' command line. """ cmd = "am instrument -w -r" # profile location cmd = cmd + " -e args '-profile %s'" % self.remote_profile # chunks (shards) shards = self.options.totalChunks shard = self.options.thisChunk if shards is not None and shard is not None: shard -= 1 # shard index is 0 based cmd = cmd + " -e numShards %d -e shardIndex %d" % (shards, shard) # test filters: limit run to specific test(s) # filter can be class-name or 'class-name#method-name' (single test) # Multiple filters must be specified as a line-separated text file # and then pushed to the device. filter_list_name = None if test_filters_file: # We specified a pre-existing file, so use that filter_list_name = test_filters_file elif test_filters: if len(test_filters) > 1: # Generate the list file from test_filters with tempfile.NamedTemporaryFile(delete=False, mode="w") as filter_list: for f in test_filters: print(f, file=filter_list) filter_list_name = filter_list.name else: # A single filter may be directly appended to the command line cmd = cmd + " -e class %s" % test_filters[0] if filter_list_name: self.device.push(filter_list_name, self.remote_filter_list) if test_filters: # We only remove the filter list if we generated it as a # temporary file. os.remove(filter_list_name) cmd = cmd + " -e testFile %s" % self.remote_filter_list # enable code coverage reports if self.options.coverage: cmd = cmd + " -e coverage true" cmd = cmd + " -e coverageFile %s" % self.remote_coverage_output_file # environment env = {} env["MOZ_CRASHREPORTER"] = "1" env["MOZ_CRASHREPORTER_SHUTDOWN"] = "1" env["XPCOM_DEBUG_BREAK"] = "stack" env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1" env["MOZ_IN_AUTOMATION"] = "1" env["R_LOG_VERBOSE"] = "1" env["R_LOG_LEVEL"] = "6" env["R_LOG_DESTINATION"] = "stderr" if self.options.enable_webrender: env["MOZ_WEBRENDER"] = "1" else: env["MOZ_WEBRENDER"] = "0" if self.options.enable_fission: env["MOZ_FORCE_ENABLE_FISSION"] = "1" # Add additional env variables for [key, value] in [p.split("=", 1) for p in self.options.add_env]: env[key] = value for (env_count, (env_key, env_val)) in enumerate(six.iteritems(env)): cmd = cmd + " -e env%d %s=%s" % (env_count, env_key, env_val) # runner cmd = cmd + " %s/%s" % (self.options.app, self.options.runner) return cmd @property def locations(self): if self._locations is not None: return self._locations locations_file = os.path.join(here, "server-locations.txt") self._locations = ServerLocations(locations_file) return self._locations def need_more_runs(self): if self.options.run_until_failure and (self.fail_count == 0): return True if self.runs <= self.options.repeat: return True return False def run_tests(self, test_filters_file=None, test_filters=None): """ Run the tests. """ if not self.device.is_app_installed(self.options.app): raise UserError("%s is not installed" % self.options.app) if self.device.process_exist(self.options.app): raise UserError("%s already running before starting tests" % self.options.app) # test_filters_file and test_filters must be mutually-exclusive if test_filters_file and test_filters: raise UserError( "Test filters may not be specified when test-filters-file is provided" ) self.test_started = False self.pass_count = 0 self.fail_count = 0 self.todo_count = 0 self.runs = 0 def callback(line): # Output callback: Parse the raw junit log messages, translating into # treeherder-friendly test start/pass/fail messages. line = six.ensure_str(line) self.log.process_output(self.options.app, str(line)) # Expect per-test info like: "INSTRUMENTATION_STATUS: class=something" match = re.match(r"INSTRUMENTATION_STATUS:\s*class=(.*)", line) if match: self.class_name = match.group(1) # Expect per-test info like: "INSTRUMENTATION_STATUS: test=something" match = re.match(r"INSTRUMENTATION_STATUS:\s*test=(.*)", line) if match: self.test_name = match.group(1) match = re.match(r"INSTRUMENTATION_STATUS:\s*stack=(.*)", line) if match: self.exception_message = match.group(1) if ("org.mozilla.geckoview.test.rule.TestHarnessException" in self.exception_message): # This is actually a problem in the test harness itself raise JavaTestHarnessException(self.exception_message) # Expect per-test info like: "INSTRUMENTATION_STATUS_CODE: 0|1|..." match = re.match(r"INSTRUMENTATION_STATUS_CODE:\s*([+-]?\d+)", line) if match: status = match.group(1) full_name = "%s.%s" % (self.class_name, self.test_name) if full_name == self.current_full_name: if status == "0": message = "" status = "PASS" expected = "PASS" self.pass_count += 1 if self.verbose: self.log.info("Printing logcat for test:") print(self.collectLogcatForCurrentTest()) elif status == "-3": # ignored (skipped) message = "" status = "SKIP" expected = "SKIP" self.todo_count += 1 elif status == "-4": # known fail message = "" status = "FAIL" expected = "FAIL" self.todo_count += 1 else: if self.exception_message: message = self.exception_message else: message = "status %s" % status status = "FAIL" expected = "PASS" self.fail_count += 1 self.log.info("Printing logcat for test:") print(self.collectLogcatForCurrentTest()) self.log.test_end(full_name, status, expected, message) self.test_started = False else: if self.test_started: # next test started without reporting previous status self.fail_count += 1 status = "FAIL" expected = "PASS" self.log.test_end( self.current_full_name, status, expected, "missing test completion status", ) self.log.test_start(full_name) self.test_started = True self.current_full_name = full_name # Ideally all test names should be reported to suite_start, but these test # names are not known in advance. self.log.suite_start(["geckoview-junit"]) try: self.device.grant_runtime_permissions(self.options.app) cmd = self.build_command_line(test_filters_file=test_filters_file, test_filters=test_filters) while self.need_more_runs(): self.class_name = "" self.exception_message = "" self.test_name = "" self.current_full_name = "" self.runs += 1 self.log.info("launching %s" % cmd) p = self.device.shell(cmd, timeout=self.options.max_time, stdout_callback=callback) if p.timedout: self.log.error("TEST-UNEXPECTED-TIMEOUT | runjunit.py | " "Timed out after %d seconds" % self.options.max_time) self.log.info("Passed: %d" % self.pass_count) self.log.info("Failed: %d" % self.fail_count) self.log.info("Todo: %d" % self.todo_count) finally: self.log.suite_end() if self.check_for_crashes(): self.fail_count = 1 if self.options.coverage: try: self.device.pull(self.remote_coverage_output_file, self.coverage_output_file) except ADBError: # Avoid a task retry in case the code coverage file is not found. self.log.error( "No code coverage file (%s) found on remote device" % self.remote_coverage_output_file) return -1 return 1 if self.fail_count else 0 def check_for_crashes(self): symbols_path = self.options.symbolsPath try: dump_dir = tempfile.mkdtemp() remote_dir = posixpath.join(self.remote_profile, "minidumps") if not self.device.is_dir(remote_dir): return False self.device.pull(remote_dir, dump_dir) crashed = mozcrash.log_crashes(self.log, dump_dir, symbols_path, test=self.current_full_name) finally: try: shutil.rmtree(dump_dir) except Exception: self.log.warning("unable to remove directory: %s" % dump_dir) return crashed
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-")
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", "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 run_tests(self): """ Generate the PGO profile data """ from mozhttpd import MozHttpd from mozprofile import Preferences from mozdevice import ADBDeviceFactory, ADBTimeoutError from six import string_types from marionette_driver.marionette import Marionette app = self.query_package_name() IP = '10.0.2.2' PORT = 8888 PATH_MAPPINGS = { '/js-input/webkit/PerformanceTests': 'third_party/webkit/PerformanceTests', } dirs = self.query_abs_dirs() topsrcdir = dirs['abs_src_dir'] adb = self.query_exe('adb') path_mappings = { k: os.path.join(topsrcdir, v) for k, v in PATH_MAPPINGS.items() } httpd = MozHttpd(port=PORT, docroot=os.path.join(topsrcdir, "build", "pgo"), path_mappings=path_mappings) httpd.start(block=False) profile_data_dir = os.path.join(topsrcdir, 'testing', 'profiles') with open(os.path.join(profile_data_dir, 'profiles.json'), 'r') as fh: base_profiles = json.load(fh)['profileserver'] prefpaths = [ os.path.join(profile_data_dir, profile, 'user.js') for profile in base_profiles ] prefs = {} for path in prefpaths: prefs.update(Preferences.read_prefs(path)) interpolation = { "server": "%s:%d" % httpd.httpd.server_address, "OOP": "false" } for k, v in prefs.items(): if isinstance(v, string_types): v = v.format(**interpolation) prefs[k] = Preferences.cast(v) outputdir = self.config.get('output_directory', '/sdcard/pgo_profile') jarlog = posixpath.join(outputdir, 'en-US.log') profdata = posixpath.join(outputdir, 'default_%p_random_%m.profraw') env = {} env["XPCOM_DEBUG_BREAK"] = "warn" env["MOZ_IN_AUTOMATION"] = "1" env["MOZ_JAR_LOG_FILE"] = jarlog env["LLVM_PROFILE_FILE"] = profdata if self.query_minidump_stackwalk(): os.environ['MINIDUMP_STACKWALK'] = self.minidump_stackwalk_path os.environ['MINIDUMP_SAVE_PATH'] = self.query_abs_dirs( )['abs_blob_upload_dir'] if not self.symbols_path: self.symbols_path = os.environ.get("MOZ_FETCHES_DIR") # Force test_root to be on the sdcard for android pgo # builds which fail for Android 4.3 when profiles are located # in /data/local/tmp/test_root with # E AndroidRuntime: FATAL EXCEPTION: Gecko # E AndroidRuntime: java.lang.IllegalArgumentException: \ # Profile directory must be writable if specified: /data/local/tmp/test_root/profile # This occurs when .can-write-sentinel is written to # the profile in # mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoProfile.java. # This is not a problem on later versions of Android. This # over-ride of test_root should be removed when Android 4.3 is no # longer supported. sdcard_test_root = '/sdcard/test_root' adbdevice = ADBDeviceFactory(adb=adb, device='emulator-5554', test_root=sdcard_test_root) if adbdevice.test_root != sdcard_test_root: # If the test_root was previously set and shared # the initializer will not have updated the shared # value. Force it to match the sdcard_test_root. adbdevice.test_root = sdcard_test_root adbdevice.mkdir(outputdir, parents=True) try: # Run Fennec a first time to initialize its profile driver = Marionette( app='fennec', package_name=app, adb_path=adb, bin="geckoview-androidTest.apk", prefs=prefs, connect_to_running_emulator=True, startup_timeout=1000, env=env, symbols_path=self.symbols_path, ) driver.start_session() # Now generate the profile and wait for it to complete for page in PAGES: driver.navigate("http://%s:%d/%s" % (IP, PORT, page)) timeout = 2 if 'Speedometer/index.html' in page: # The Speedometer test actually runs many tests internally in # javascript, so it needs extra time to run through them. The # emulator doesn't get very far through the whole suite, but # this extra time at least lets some of them process. timeout = 360 time.sleep(timeout) driver.set_context("chrome") driver.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"] .createInstance(Components.interfaces.nsISupportsPRBool); Services.obs.notifyObservers(cancelQuit, "quit-application-requested", null); return cancelQuit.data; """) driver.execute_script(""" Components.utils.import("resource://gre/modules/Services.jsm"); Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit) """) # There is a delay between execute_script() returning and the profile data # actually getting written out, so poll the device until we get a profile. for i in range(50): if not adbdevice.process_exist(app): break time.sleep(2) else: raise Exception("Android App (%s) never quit" % app) # Pull all the profraw files and en-US.log adbdevice.pull(outputdir, '/builds/worker/workspace/') except ADBTimeoutError: self.fatal('INFRA-ERROR: Failed with an ADBTimeoutError', EXIT_STATUS_DICT[TBPL_RETRY]) profraw_files = glob.glob('/builds/worker/workspace/*.profraw') if not profraw_files: self.fatal( 'Could not find any profraw files in /builds/worker/workspace') merge_cmd = [ os.path.join(os.environ['MOZ_FETCHES_DIR'], 'clang/bin/llvm-profdata'), 'merge', '-o', '/builds/worker/workspace/merged.profdata', ] + profraw_files rc = subprocess.call(merge_cmd) if rc != 0: self.fatal( 'INFRA-ERROR: Failed to merge profile data. Corrupt profile?', EXIT_STATUS_DICT[TBPL_RETRY]) # tarfile doesn't support xz in this version of Python tar_cmd = [ 'tar', '-acvf', '/builds/worker/artifacts/profdata.tar.xz', '-C', '/builds/worker/workspace', 'merged.profdata', 'en-US.log', ] subprocess.check_call(tar_cmd) httpd.stop()
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 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
def run_raptor(self, **kwargs): # Defers this import so that a transitive dependency doesn't # stop |mach bootstrap| from running from raptor.power import enable_charging, disable_charging build_obj = self is_android = (Conditions.is_android(build_obj) or kwargs["app"] in ANDROID_BROWSERS) if is_android: from mozrunner.devices.android_device import ( verify_android_device, InstallIntent, ) from mozdevice import ADBDeviceFactory install = (InstallIntent.NO if kwargs.pop("noinstall", False) else InstallIntent.YES) verbose = False if (kwargs.get("log_mach_verbose") or kwargs.get("log_tbpl_level") == "debug" or kwargs.get("log_mach_level") == "debug" or kwargs.get("log_raw_level") == "debug"): verbose = True if not verify_android_device( build_obj, install=install, app=kwargs["binary"], verbose=verbose, xre=True, ): # Equivalent to 'run_local' = True. return 1 # Remove mach global arguments from sys.argv to prevent them # from being consumed by raptor. Treat any item in sys.argv # occuring before "raptor" as a mach global argument. argv = [] in_mach = True for arg in sys.argv: if not in_mach: argv.append(arg) if arg.startswith("raptor"): in_mach = False raptor = self._spawn(RaptorRunner) device = None try: if kwargs["power_test"] and is_android: device = ADBDeviceFactory(verbose=True) disable_charging(device) return raptor.run_test(argv, kwargs) except BinaryNotFoundException as e: self.log(logging.ERROR, "raptor", {"error": str(e)}, "ERROR: {error}") self.log(logging.INFO, "raptor", {"help": e.help()}, "{help}") return 1 except Exception as e: print(repr(e)) return 1 finally: if kwargs["power_test"] and device: enable_charging(device)
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
class WebExtensionAndroid(PerftestAndroid, WebExtension): def __init__(self, app, binary, activity=None, intent=None, **kwargs): super(WebExtensionAndroid, self).__init__(app, binary, profile_class="firefox", **kwargs) self.config.update({"activity": activity, "intent": intent}) self.os_baseline_data = None self.power_test_time = None self.screen_off_timeout = 0 self.screen_brightness = 127 self.app_launched = False def setup_adb_device(self): if self.device is None: self.device = ADBDeviceFactory(verbose=True) if not self.config.get("disable_perf_tuning", False): tune_performance(self.device, log=LOG) self.device.run_as_package = self.config["binary"] self.remote_test_root = os.path.join(self.device.test_root, "raptor") self.remote_profile = os.path.join(self.remote_test_root, "profile") if self.config["power_test"]: disable_charging(self.device) LOG.info("creating remote root folder for raptor: %s" % self.remote_test_root) self.device.rm(self.remote_test_root, force=True, recursive=True) self.device.mkdir(self.remote_test_root, parents=True) self.clear_app_data() self.set_debug_app_flag() def process_exists(self): return self.device is not None and self.device.process_exist( self.config["binary"]) def write_android_app_config(self): # geckoview supports having a local on-device config file; use this file # to tell the app to use the specified browser profile, as well as other opts # on-device: /data/local/tmp/com.yourcompany.yourapp-geckoview-config.yaml # https://mozilla.github.io/geckoview/tutorials/automation.html#configuration-file-format LOG.info("creating android app config.yml") yml_config_data = dict( args=[ "--profile", self.remote_profile, "--allow-downgrade", ], env=dict( LOG_VERBOSE=1, R_LOG_LEVEL=6, MOZ_WEBRENDER=int(self.config["enable_webrender"]), ), ) yml_name = "%s-geckoview-config.yaml" % self.config["binary"] yml_on_host = os.path.join(tempfile.mkdtemp(), yml_name) write_yml_file(yml_on_host, yml_config_data) yml_on_device = os.path.join("/data", "local", "tmp", yml_name) try: LOG.info("copying %s to device: %s" % (yml_on_host, yml_on_device)) self.device.rm(yml_on_device, force=True, recursive=True) self.device.push(yml_on_host, yml_on_device) except Exception: LOG.critical("failed to push %s to device!" % yml_on_device) raise def log_android_device_temperature(self): # retrieve and log the android device temperature try: # use sort since cat gives I/O Error on Pixel 2 - 10. thermal_zone0 = self.device.shell_output( "sort /sys/class/thermal/thermal_zone0/temp") try: thermal_zone0 = "%.3f" % (float(thermal_zone0) / 1000) except ValueError: thermal_zone0 = "Unknown" except ADBProcessError: thermal_zone0 = "Unknown" try: zone_type = self.device.shell_output( "cat /sys/class/thermal/thermal_zone0/type") except ADBProcessError: zone_type = "Unknown" LOG.info("(thermal_zone0) device temperature: %s zone type: %s" % (thermal_zone0, zone_type)) def launch_firefox_android_app(self, test_name): LOG.info("starting %s" % self.config["app"]) try: # make sure the android app is not already running self.device.stop_application(self.config["binary"]) # command line 'extra' args not used with geckoview apps; instead we use # an on-device config.yml file (see write_android_app_config) self.device.launch_application( self.config["binary"], self.config["activity"], self.config["intent"], extras=None, url="about:blank", fail_if_running=False, ) # Check if app has started and it's running if not self.process_exists: raise Exception( "Error launching %s. App did not start properly!" % self.config["binary"]) self.app_launched = True except Exception as e: LOG.error("Exception launching %s" % self.config["binary"]) LOG.error("Exception: %s %s" % (type(e).__name__, str(e))) if self.config["power_test"]: finish_android_power_test(self, test_name) raise # give our control server the device and app info self.control_server.device = self.device self.control_server.app_name = self.config["binary"] def copy_cert_db(self, source_dir, target_dir): # copy browser cert db (that was previously created via certutil) from source to target cert_db_files = ["pkcs11.txt", "key4.db", "cert9.db"] for next_file in cert_db_files: _source = os.path.join(source_dir, next_file) _dest = os.path.join(target_dir, next_file) if os.path.exists(_source): LOG.info("copying %s to %s" % (_source, _dest)) shutil.copyfile(_source, _dest) else: LOG.critical("unable to find ssl cert db file: %s" % _source) def run_tests(self, tests, test_names): self.setup_adb_device() return super(WebExtensionAndroid, self).run_tests(tests, test_names) def run_test_setup(self, test): super(WebExtensionAndroid, self).run_test_setup(test) self.set_reverse_ports() def run_test_teardown(self, test): LOG.info("removing reverse socket connections") self.device.remove_socket_connections("reverse") super(WebExtensionAndroid, self).run_test_teardown(test) def run_test(self, test, timeout): # tests will be run warm (i.e. NO browser restart between page-cycles) # unless otheriwse specified in the test INI by using 'cold = true' try: if self.config["power_test"]: # gather OS baseline data init_android_power_test(self) LOG.info("Running OS baseline, pausing for 1 minute...") time.sleep(60) LOG.info("Finishing baseline...") finish_android_power_test(self, "os-baseline", os_baseline=True) # initialize for the test init_android_power_test(self) if self.config.get("cold") or test.get("cold"): self.__run_test_cold(test, timeout) else: self.__run_test_warm(test, timeout) except SignalHandlerException: self.device.stop_application(self.config["binary"]) if self.config["power_test"]: enable_charging(self.device) finally: if self.config["power_test"]: finish_android_power_test(self, test["name"]) def __run_test_cold(self, test, timeout): """ Run the Raptor test but restart the entire browser app between page-cycles. Note: For page-load tests, playback will only be started once - at the beginning of all browser cycles, and then stopped after all cycles are finished. The proxy is set via prefs in the browser profile so those will need to be set again in each new profile/cycle. Note that instead of using the certutil tool each time to create a db and import the mitmproxy SSL cert (it's done in mozbase/mozproxy) we will simply copy the existing cert db from the first cycle's browser profile into the new clean profile; this way we don't have to re-create the cert db on each browser cycle. Since we're running in cold-mode, before this point (in manifest.py) the 'expected-browser-cycles' value was already set to the initial 'page-cycles' value; and the 'page-cycles' value was set to 1 as we want to perform one page-cycle per browser restart. The 'browser-cycle' value is the current overall browser start iteration. The control server will receive the current 'browser-cycle' and the 'expected-browser-cycles' in each results set received; and will pass that on as part of the results so that the results processing will know results for multiple browser cycles are being received. The default will be to run in warm mode; unless 'cold = true' is set in the test INI. """ LOG.info( "test %s is running in cold mode; browser WILL be restarted between " "page cycles" % test["name"]) for test["browser_cycle"] in range(1, test["expected_browser_cycles"] + 1): LOG.info("begin browser cycle %d of %d for test %s" % (test["browser_cycle"], test["expected_browser_cycles"], test["name"])) self.run_test_setup(test) self.clear_app_data() self.set_debug_app_flag() if test["browser_cycle"] == 1: if test.get("playback") is not None: # an ssl cert db has now been created in the profile; copy it out so we # can use the same cert db in future test cycles / browser restarts local_cert_db_dir = tempfile.mkdtemp() LOG.info( "backing up browser ssl cert db that was created via certutil" ) self.copy_cert_db(self.config["local_profile_dir"], local_cert_db_dir) if not self.is_localhost: self.delete_proxy_settings_from_profile() else: # double-check to ensure app has been shutdown self.device.stop_application(self.config["binary"]) # initial browser profile was already created before run_test was called; # now additional browser cycles we want to create a new one each time self.build_browser_profile() if test.get("playback") is not None: # get cert db from previous cycle profile and copy into new clean profile # this saves us from having to start playback again / recreate cert db etc. LOG.info( "copying existing ssl cert db into new browser profile" ) self.copy_cert_db(local_cert_db_dir, self.config["local_profile_dir"]) self.run_test_setup(test) if test.get("playback") is not None: self.turn_on_android_app_proxy() self.copy_profile_to_device() self.log_android_device_temperature() # write android app config.yml self.write_android_app_config() # now start the browser/app under test self.launch_firefox_android_app(test["name"]) # set our control server flag to indicate we are running the browser/app self.control_server._finished = False if self.config["cpu_test"]: # start measuring CPU usage self.cpu_profiler = start_android_cpu_profiler(self) self.wait_for_test_finish(test, timeout, self.process_exists) # in debug mode, and running locally, leave the browser running if self.debug_mode and self.config["run_local"]: LOG.info( "* debug-mode enabled - please shutdown the browser manually..." ) self.runner.wait(timeout=None) # break test execution if a exception is present if len(self.results_handler.page_timeout_list) > 0: break def __run_test_warm(self, test, timeout): LOG.info( "test %s is running in warm mode; browser will NOT be restarted between " "page cycles" % test["name"]) self.run_test_setup(test) if not self.is_localhost: self.delete_proxy_settings_from_profile() if test.get("playback") is not None: self.turn_on_android_app_proxy() self.clear_app_data() self.set_debug_app_flag() self.copy_profile_to_device() self.log_android_device_temperature() # write android app config.yml self.write_android_app_config() # now start the browser/app under test self.launch_firefox_android_app(test["name"]) # set our control server flag to indicate we are running the browser/app self.control_server._finished = False if self.config["cpu_test"]: # start measuring CPU usage self.cpu_profiler = start_android_cpu_profiler(self) self.wait_for_test_finish(test, timeout, self.process_exists) # in debug mode, and running locally, leave the browser running if self.debug_mode and self.config["run_local"]: LOG.info( "* debug-mode enabled - please shutdown the browser manually..." ) def check_for_crashes(self): super(WebExtensionAndroid, self).check_for_crashes() if not self.app_launched: LOG.info( "skipping check_for_crashes: application has not been launched" ) return self.app_launched = False try: dump_dir = tempfile.mkdtemp() remote_dir = posixpath.join(self.remote_profile, "minidumps") if not self.device.is_dir(remote_dir): return self.device.pull(remote_dir, dump_dir) self.crashes += mozcrash.log_crashes(LOG, dump_dir, self.config["symbols_path"]) finally: try: shutil.rmtree(dump_dir) except Exception: LOG.warning("unable to remove directory: %s" % dump_dir) def clean_up(self): LOG.info("removing test folder for raptor: %s" % self.remote_test_root) self.device.rm(self.remote_test_root, force=True, recursive=True) if self.config["power_test"]: enable_charging(self.device) super(WebExtensionAndroid, self).clean_up()
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)
class BrowsertimeAndroid(PerftestAndroid, Browsertime): """Android setup and configuration for browsertime When running raptor-browsertime tests on android, we create the profile (and set the proxy prefs in the profile that is using playback) but we don't need to copy it onto the device because geckodriver takes care of that. We tell browsertime to use our profile (we pass it in with the firefox.profileTemplate arg); browsertime creates a copy of that and passes that into geckodriver. Geckodriver then takes the profile and copies it onto the mobile device's test root for us; and then it even writes the geckoview app config.yaml file onto the device, which points the app to the profile on the device's test root. Therefore, raptor doesn't have to copy the profile onto the scard (and create the config.yaml) file ourselves. Also note when using playback, the nss certificate db is created as usual when mitmproxy is started (and saved in the profile) so it is already included in the profile that browsertime/geckodriver copies onto the device. XXX: bc: This doesn't work with scoped storage in Android 10 since the shell owns the profile directory that is pushed to the device and the profile can no longer be on the sdcard. But when geckodriver's android.rs defines the profile to be located on internal storage, it will be owned by shell but if we are attempting to eliminate root, then when we run shell commands as the app, they will fail due to the app being unable to write to the shell owned profile directory. """ def __init__(self, app, binary, activity=None, intent=None, **kwargs): super(BrowsertimeAndroid, self).__init__(app, binary, profile_class="firefox", **kwargs) self.config.update({"activity": activity, "intent": intent}) self.remote_test_root = None self.remote_profile = None @property def browsertime_args(self): args_list = ["--viewPort", "1366x695"] if self.config["app"] == "chrome-m": args_list.extend([ "--browser", "chrome", "--android", ]) else: activity = self.config["activity"] if self.config["app"] == "fenix": LOG.info("Changing initial activity to " "`mozilla.telemetry.glean.debug.GleanDebugActivity`") activity = "mozilla.telemetry.glean.debug.GleanDebugActivity" args_list.extend([ "--browser", "firefox", "--android", # Work around a `selenium-webdriver` issue where Browsertime # fails to find a Firefox binary even though we're going to # actually do things on an Android device. "--firefox.binaryPath", self.browsertime_node, "--firefox.android.package", self.config["binary"], "--firefox.android.activity", activity, ]) # Setup power testing if self.config["power_test"]: args_list.extend(["--androidPower", "true"]) # If running on Fenix we must add the intent as we use a special non-default one there if self.config["app"] == "fenix" and self.config.get( "intent") is not None: args_list.extend(["--firefox.android.intentArgument=-a"]) args_list.extend( ["--firefox.android.intentArgument", self.config["intent"]]) # Change glean ping names in all cases on Fenix args_list.extend([ "--firefox.android.intentArgument=--es", "--firefox.android.intentArgument=startNext", "--firefox.android.intentArgument=" + self.config["activity"], "--firefox.android.intentArgument=--esa", "--firefox.android.intentArgument=sourceTags", "--firefox.android.intentArgument=automation", ]) args_list.extend(["--firefox.android.intentArgument=-d"]) args_list.extend( ["--firefox.android.intentArgument", str("about:blank")]) return args_list def setup_chrome_args(self, test): chrome_args = [ "--use-mock-keychain", "--no-default-browser-check", "--no-first-run", ] if test.get("playback", False): pb_args = [ "--proxy-server=%s:%d" % (self.playback.host, self.playback.port), "--proxy-bypass-list=localhost;127.0.0.1", "--ignore-certificate-errors", ] if not self.is_localhost: pb_args[0] = pb_args[0].replace("127.0.0.1", self.config["host"]) chrome_args.extend(pb_args) if self.debug_mode: chrome_args.extend(["--auto-open-devtools-for-tabs"]) args_list = [] for arg in chrome_args: args_list.extend(["--chrome.args=" + str(arg.replace("'", '"'))]) return args_list def build_browser_profile(self): super(BrowsertimeAndroid, self).build_browser_profile() # Merge in the Android profile. path = os.path.join(self.profile_data_dir, "raptor-android") LOG.info("Merging profile: {}".format(path)) self.profile.merge(path) self.profile.set_preferences( {"browser.tabs.remote.autostart": self.config["e10s"]}) # There's no great way to have "after" advice in Python, so we do this # in super and then again here since the profile merging re-introduces # the "#MozRunner" delimiters. self.remove_mozprofile_delimiters_from_profile() def setup_adb_device(self): if self.device is None: self.device = ADBDeviceFactory(verbose=True) if not self.config.get("disable_perf_tuning", False): tune_performance(self.device, log=LOG) self.clear_app_data() self.set_debug_app_flag() self.device.run_as_package = self.config["binary"] self.remote_test_root = os.path.join(self.device.test_root, "raptor") self.remote_profile = os.path.join(self.remote_test_root, "profile") def run_test_setup(self, test): super(BrowsertimeAndroid, self).run_test_setup(test) self.set_reverse_ports() if self.playback: self.turn_on_android_app_proxy() self.remove_mozprofile_delimiters_from_profile() def run_tests(self, tests, test_names): self.setup_adb_device() if self.config["app"] == "chrome-m": # Make sure that chrome is enabled on the device self.device.shell_output("pm enable com.android.chrome") try: if self.config["power_test"]: disable_charging(self.device) return super(BrowsertimeAndroid, self).run_tests(tests, test_names) finally: if self.config["power_test"]: enable_charging(self.device) def run_test_teardown(self, test): LOG.info("removing reverse socket connections") self.device.remove_socket_connections("reverse") super(BrowsertimeAndroid, self).run_test_teardown(test)
def prepare(self, profile, logfile): self._set_adb_logger(logfile) try: # See android_emulator_pgo.py run_tests for more # details on why test_root must be /sdcard/test_root # for android pgo due to Android 4.3. self.device = ADBDeviceFactory(verbose=self.verbose, logger_name="adb", test_root="/sdcard/test_root") except Exception: logger.error("Cannot initialize device") raise device = self.device self.profile = profile # checking that the app is installed if not device.is_app_installed(self.app_name): raise Exception("%s is not installed" % self.app_name) # debug flag logger.info("Setting %s as the debug app on the phone" % self.app_name) device.shell( "am set-debug-app --persistent %s" % self.app_name, stdout_callback=logger.info, ) # creating the profile on the device logger.info("Creating the profile on the device") remote_test_root = posixpath.join(device.test_root, "condprof") remote_profile = posixpath.join(remote_test_root, "profile") logger.info("The profile on the phone will be at %s" % remote_profile) device.rm(remote_test_root, force=True, recursive=True) device.mkdir(remote_test_root) device.rm(remote_profile, force=True, recursive=True) logger.info("Pushing %s on the phone" % self.profile) device.push(profile, remote_profile) device.chmod(remote_profile, recursive=True) self.profile = profile self.remote_profile = remote_profile # creating the yml file yml_data = { "args": ["-marionette", "-profile", self.remote_profile], "prefs": DEFAULT_PREFS, "env": { "LOG_VERBOSE": 1, "R_LOG_LEVEL": 6, "MOZ_LOG": "" }, } yml_name = "%s-geckoview-config.yaml" % self.app_name yml_on_host = posixpath.join(tempfile.mkdtemp(), yml_name) write_yml_file(yml_on_host, yml_data) tmp_on_device = posixpath.join("/data", "local", "tmp") if not device.exists(tmp_on_device): raise IOError("%s does not exists on the device" % tmp_on_device) yml_on_device = posixpath.join(tmp_on_device, yml_name) try: device.rm(yml_on_device, force=True, recursive=True) device.push(yml_on_host, yml_on_device) device.chmod(yml_on_device, recursive=True) except Exception: logger.info( "could not create the yaml file on device. Permission issue?") raise # command line 'extra' args not used with geckoview apps; instead we use # an on-device config.yml file intent = "android.intent.action.VIEW" device.stop_application(self.app_name) if self.fennec: # XXX does the Fennec app picks up the YML file ? extra_args = [ "-profile", self.remote_profile, "--es", "env0", "LOG_VERBOSE=1", "--es", "env1", "R_LOG_LEVEL=6", "--es", "env2", "MOZ_WEBRENDER=0", ] device.launch_fennec( self.app_name, extra_args=extra_args, url="about:blank", fail_if_running=False, ) else: device.launch_application(self.app_name, self.activity, intent, extras=None, url="about:blank") if not device.process_exist(self.app_name): raise Exception("Could not start %s" % self.app_name) logger.info("Creating socket forwarding on port %d" % self.marionette_port) device.forward( local="tcp:%d" % self.marionette_port, remote="tcp:%d" % self.marionette_port, ) # we don't have a clean way for now to check that GV or Fenix # is ready to handle our tests. So here we just wait 30s logger.info("Sleeping for 30s") time.sleep(30)
class AndroidDevice: def __init__(self, app_name, marionette_port=2828, verbose=False): self.app_name = app_name self.fennec = "firefox" in app_name # XXX make that an option if "fenix" in app_name: self.activity = "org.mozilla.fenix.IntentReceiverActivity" elif self.fennec: self.activity = None else: self.activity = "org.mozilla.geckoview_example.GeckoViewActivity" self.verbose = verbose self.device = None self.marionette_port = marionette_port self.profile = None self.remote_profile = None self.log_file = None self._adb_fh = None def _set_adb_logger(self, log_file): self.log_file = log_file if self.log_file is None: return logger.info("Setting ADB log file to %s" % self.log_file) adb_logger = logging.getLogger("adb") adb_logger.setLevel(logging.DEBUG) self._adb_fh = logging.FileHandler(self.log_file) self._adb_fh.setLevel(logging.DEBUG) adb_logger.addHandler(self._adb_fh) def _unset_adb_logger(self): if self._adb_fh is None: return logging.getLogger("adb").removeHandler(self._adb_fh) self._adb_fh = None def clear_logcat(self, timeout=None, buffers=[]): if not self.device: return self.device.clear_logcat(timeout, buffers) def get_logcat(self): if not self.device: return None # we don't want to have ADBCommand dump the command # in the debug stream so we reduce its verbosity here # temporarely old_verbose = self.device._verbose self.device._verbose = False try: return self.device.get_logcat() finally: self.device._verbose = old_verbose def prepare(self, profile, logfile): self._set_adb_logger(logfile) try: # See android_emulator_pgo.py run_tests for more # details on why test_root must be /sdcard/test_root # for android pgo due to Android 4.3. self.device = ADBDeviceFactory(verbose=self.verbose, logger_name="adb", test_root="/sdcard/test_root") except Exception: logger.error("Cannot initialize device") raise device = self.device self.profile = profile # checking that the app is installed if not device.is_app_installed(self.app_name): raise Exception("%s is not installed" % self.app_name) # debug flag logger.info("Setting %s as the debug app on the phone" % self.app_name) device.shell( "am set-debug-app --persistent %s" % self.app_name, stdout_callback=logger.info, ) # creating the profile on the device logger.info("Creating the profile on the device") remote_test_root = posixpath.join(device.test_root, "condprof") remote_profile = posixpath.join(remote_test_root, "profile") logger.info("The profile on the phone will be at %s" % remote_profile) device.rm(remote_test_root, force=True, recursive=True) device.mkdir(remote_test_root) device.rm(remote_profile, force=True, recursive=True) logger.info("Pushing %s on the phone" % self.profile) device.push(profile, remote_profile) device.chmod(remote_profile, recursive=True) self.profile = profile self.remote_profile = remote_profile # creating the yml file yml_data = { "args": ["-marionette", "-profile", self.remote_profile], "prefs": DEFAULT_PREFS, "env": { "LOG_VERBOSE": 1, "R_LOG_LEVEL": 6, "MOZ_LOG": "" }, } yml_name = "%s-geckoview-config.yaml" % self.app_name yml_on_host = posixpath.join(tempfile.mkdtemp(), yml_name) write_yml_file(yml_on_host, yml_data) tmp_on_device = posixpath.join("/data", "local", "tmp") if not device.exists(tmp_on_device): raise IOError("%s does not exists on the device" % tmp_on_device) yml_on_device = posixpath.join(tmp_on_device, yml_name) try: device.rm(yml_on_device, force=True, recursive=True) device.push(yml_on_host, yml_on_device) device.chmod(yml_on_device, recursive=True) except Exception: logger.info( "could not create the yaml file on device. Permission issue?") raise # command line 'extra' args not used with geckoview apps; instead we use # an on-device config.yml file intent = "android.intent.action.VIEW" device.stop_application(self.app_name) if self.fennec: # XXX does the Fennec app picks up the YML file ? extra_args = [ "-profile", self.remote_profile, "--es", "env0", "LOG_VERBOSE=1", "--es", "env1", "R_LOG_LEVEL=6", "--es", "env2", "MOZ_WEBRENDER=0", ] device.launch_fennec( self.app_name, extra_args=extra_args, url="about:blank", fail_if_running=False, ) else: device.launch_application(self.app_name, self.activity, intent, extras=None, url="about:blank") if not device.process_exist(self.app_name): raise Exception("Could not start %s" % self.app_name) logger.info("Creating socket forwarding on port %d" % self.marionette_port) device.forward( local="tcp:%d" % self.marionette_port, remote="tcp:%d" % self.marionette_port, ) # we don't have a clean way for now to check that GV or Fenix # is ready to handle our tests. So here we just wait 30s logger.info("Sleeping for 30s") time.sleep(30) def stop_browser(self): logger.info("Stopping %s" % self.app_name) try: self.device.stop_application(self.app_name) except ADBError: logger.info("Could not stop the application using force-stop") time.sleep(5) if self.device.process_exist(self.app_name): logger.info("%s still running, trying SIGKILL" % self.app_name) num_tries = 0 while self.device.process_exist(self.app_name) and num_tries < 5: try: self.device.pkill(self.app_name) except ADBError: pass num_tries += 1 time.sleep(1) logger.info("%s stopped" % self.app_name) def collect_profile(self): logger.info("Collecting profile from %s" % self.remote_profile) self.device.pull(self.remote_profile, self.profile) def close(self): self._unset_adb_logger() if self.device is None: return try: self.device.remove_forwards("tcp:%d" % self.marionette_port) except ADBError: logger.warning("Could not remove forward port")