class RemoteCPPUnitTests(cppunittests.CPPUnitTests): def __init__(self, options, progs): cppunittests.CPPUnitTests.__init__(self) self.options = options self.device = ADBDeviceFactory( adb=options.adb_path or "adb", device=options.device_serial, test_root=options.remote_test_root, ) self.remote_test_root = posixpath.join(self.device.test_root, "cppunittests") self.remote_bin_dir = posixpath.join(self.remote_test_root, "b") self.remote_tmp_dir = posixpath.join(self.remote_test_root, "tmp") self.remote_home_dir = posixpath.join(self.remote_test_root, "h") if options.setup: self.setup_bin(progs) def setup_bin(self, progs): self.device.rm(self.remote_test_root, force=True, recursive=True) self.device.mkdir(self.remote_home_dir, parents=True) self.device.mkdir(self.remote_tmp_dir) self.device.mkdir(self.remote_bin_dir) self.push_libs() self.push_progs(progs) self.device.chmod(self.remote_bin_dir, recursive=True) def push_libs(self): if self.options.local_apk: with mozfile.TemporaryDirectory() as tmpdir: apk_contents = ZipFile(self.options.local_apk) for info in apk_contents.infolist(): if info.filename.endswith(".so"): print("Pushing %s.." % info.filename, file=sys.stderr) remote_file = posixpath.join( self.remote_bin_dir, os.path.basename(info.filename)) apk_contents.extract(info, tmpdir) local_file = os.path.join(tmpdir, info.filename) with open(local_file) as f: # Decompress xz-compressed file. if f.read(5)[1:] == "7zXZ": cmd = [ "xz", "-df", "--suffix", ".so", local_file ] subprocess.check_output(cmd) # xz strips the ".so" file suffix. os.rename(local_file[:-3], local_file) self.device.push(local_file, remote_file) elif self.options.local_lib: for path in os.listdir(self.options.local_lib): if path.endswith(".so"): print("Pushing {}..".format(path), file=sys.stderr) remote_file = posixpath.join(self.remote_bin_dir, path) local_file = os.path.join(self.options.local_lib, path) self.device.push(local_file, remote_file) # Additional libraries may be found in a sub-directory such as # "lib/armeabi-v7a" for subdir in ["assets", "lib"]: local_arm_lib = os.path.join(self.options.local_lib, subdir) if os.path.isdir(local_arm_lib): for root, dirs, paths in os.walk(local_arm_lib): for path in paths: if path.endswith(".so"): print("Pushing {}..".format(path), file=sys.stderr) remote_file = posixpath.join( self.remote_bin_dir, path) local_file = os.path.join(root, path) self.device.push(local_file, remote_file) def push_progs(self, progs): for local_file in progs: remote_file = posixpath.join(self.remote_bin_dir, os.path.basename(local_file)) self.device.push(local_file, remote_file) def build_environment(self, enable_webrender=False): env = self.build_core_environment({}, enable_webrender) env["LD_LIBRARY_PATH"] = self.remote_bin_dir env["TMPDIR"] = self.remote_tmp_dir env["HOME"] = self.remote_home_dir env["MOZ_XRE_DIR"] = self.remote_bin_dir if self.options.add_env: for envdef in self.options.add_env: envdef_parts = envdef.split("=", 1) if len(envdef_parts) == 2: env[envdef_parts[0]] = envdef_parts[1] elif len(envdef_parts) == 1: env[envdef_parts[0]] = "" else: self.log.warning("invalid --addEnv option skipped: %s" % envdef) return env def run_one_test(self, prog, env, symbols_path=None, interactive=False, timeout_factor=1): """ Run a single C++ unit test program remotely. Arguments: * prog: The path to the test program to run. * env: The environment to use for running the program. * symbols_path: A path to a directory containing Breakpad-formatted symbol files for producing stack traces on crash. * timeout_factor: An optional test-specific timeout multiplier. Return True if the program exits with a zero status, False otherwise. """ basename = os.path.basename(prog) remote_bin = posixpath.join(self.remote_bin_dir, basename) self.log.test_start(basename) test_timeout = cppunittests.CPPUnitTests.TEST_PROC_TIMEOUT * timeout_factor try: output = self.device.shell_output(remote_bin, env=env, cwd=self.remote_home_dir, timeout=test_timeout) returncode = 0 except ADBTimeoutError: raise except ADBProcessError as e: output = e.adb_process.stdout returncode = e.adb_process.exitcode self.log.process_output(basename, "\n%s" % output, command=[remote_bin]) with mozfile.TemporaryDirectory() as tempdir: self.device.pull(self.remote_home_dir, tempdir) if mozcrash.check_for_crashes(tempdir, symbols_path, test_name=basename): self.log.test_end(basename, status="CRASH", expected="PASS") return False result = returncode == 0 if not result: self.log.test_end( basename, status="FAIL", expected="PASS", message=("test failed with return code %s" % returncode), ) else: self.log.test_end(basename, status="PASS", expected="PASS") return result
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 XPCShellRemote(xpcshell.XPCShellTests, object): def __init__(self, options, log): xpcshell.XPCShellTests.__init__(self, log) self.options = options verbose = False if options["log_tbpl_level"] == "debug" or options[ "log_mach_level"] == "debug": verbose = True self.device = ADBDeviceFactory( adb=options["adbPath"] or "adb", device=options["deviceSerial"], test_root=options["remoteTestRoot"], verbose=verbose, ) self.remoteTestRoot = posixpath.join(self.device.test_root, "xpc") # Add Android version (SDK level) to mozinfo so that manifest entries # can be conditional on android_version. mozinfo.info["android_version"] = str(self.device.version) mozinfo.info["is_emulator"] = self.device._device_serial.startswith( "emulator-") self.localBin = options["localBin"] self.pathMapping = [] # remoteBinDir contains xpcshell and its wrapper script, both of which must # be executable. Since +x permissions cannot usually be set on /mnt/sdcard, # and the test root may be on /mnt/sdcard, remoteBinDir is set to be on # /data/local, always. self.remoteBinDir = posixpath.join(self.device.test_root, "xpcb") # Terse directory names are used here ("c" for the components directory) # to minimize the length of the command line used to execute # xpcshell on the remote device. adb has a limit to the number # of characters used in a shell command, and the xpcshell command # line can be quite complex. self.remoteTmpDir = posixpath.join(self.remoteTestRoot, "tmp") self.remoteScriptsDir = self.remoteTestRoot self.remoteComponentsDir = posixpath.join(self.remoteTestRoot, "c") self.remoteModulesDir = posixpath.join(self.remoteTestRoot, "m") self.remoteMinidumpDir = posixpath.join(self.remoteTestRoot, "minidumps") self.profileDir = posixpath.join(self.remoteTestRoot, "p") self.remoteDebugger = options["debugger"] self.remoteDebuggerArgs = options["debuggerArgs"] self.testingModulesDir = options["testingModulesDir"] self.env = {} if options["objdir"]: self.xpcDir = os.path.join(options["objdir"], "_tests/xpcshell") elif os.path.isdir(os.path.join(here, "tests")): self.xpcDir = os.path.join(here, "tests") else: print("Couldn't find local xpcshell test directory", file=sys.stderr) sys.exit(1) self.remoteAPK = None if options["localAPK"]: self.localAPKContents = ZipFile(options["localAPK"]) self.remoteAPK = posixpath.join( self.remoteBinDir, os.path.basename(options["localAPK"])) else: self.localAPKContents = None if options["setup"]: self.setupTestDir() self.setupUtilities() self.setupModules() self.initDir(self.remoteMinidumpDir) # data that needs to be passed to the RemoteXPCShellTestThread self.mobileArgs = { "device": self.device, "remoteBinDir": self.remoteBinDir, "remoteScriptsDir": self.remoteScriptsDir, "remoteComponentsDir": self.remoteComponentsDir, "remoteModulesDir": self.remoteModulesDir, "options": self.options, "remoteDebugger": self.remoteDebugger, "remoteDebuggerArgs": self.remoteDebuggerArgs, "pathMapping": self.pathMapping, "profileDir": self.profileDir, "remoteTmpDir": self.remoteTmpDir, "remoteMinidumpDir": self.remoteMinidumpDir, } if self.remoteAPK: self.mobileArgs["remoteAPK"] = self.remoteAPK def initDir(self, path, mask="777", timeout=None): """Initialize a directory by removing it if it exists, creating it and changing the permissions.""" self.device.rm(path, recursive=True, force=True, timeout=timeout) self.device.mkdir(path, parents=True, timeout=timeout) def setLD_LIBRARY_PATH(self): self.env["LD_LIBRARY_PATH"] = self.remoteBinDir def pushWrapper(self): # Rather than executing xpcshell directly, this wrapper script is # used. By setting environment variables and the cwd in the script, # the length of the per-test command line is shortened. This is # often important when using ADB, as there is a limit to the length # of the ADB command line. localWrapper = tempfile.mktemp() with open(localWrapper, "w") as f: f.write("#!/system/bin/sh\n") for envkey, envval in six.iteritems(self.env): f.write("export %s=%s\n" % (envkey, envval)) f.writelines([ "cd $1\n", "echo xpcw: cd $1\n", "shift\n", 'echo xpcw: xpcshell "$@"\n', '%s/xpcshell "$@"\n' % self.remoteBinDir, ]) remoteWrapper = posixpath.join(self.remoteBinDir, "xpcw") self.device.push(localWrapper, remoteWrapper) self.device.chmod(remoteWrapper) os.remove(localWrapper) def buildPrefsFile(self, extraPrefs): prefs = super(XPCShellRemote, self).buildPrefsFile(extraPrefs) remotePrefsFile = posixpath.join(self.remoteTestRoot, "user.js") self.device.push(self.prefsFile, remotePrefsFile) self.device.chmod(remotePrefsFile) os.remove(self.prefsFile) self.prefsFile = remotePrefsFile return prefs def buildEnvironment(self): self.buildCoreEnvironment() self.setLD_LIBRARY_PATH() self.env["MOZ_LINKER_CACHE"] = self.remoteBinDir self.env["GRE_HOME"] = self.remoteBinDir self.env["XPCSHELL_TEST_PROFILE_DIR"] = self.profileDir self.env["TMPDIR"] = self.remoteTmpDir self.env["HOME"] = self.profileDir self.env["XPCSHELL_TEST_TEMP_DIR"] = self.remoteTmpDir self.env["XPCSHELL_MINIDUMP_DIR"] = self.remoteMinidumpDir self.env["MOZ_ANDROID_DATA_DIR"] = self.remoteBinDir self.env["MOZ_FORCE_DISABLE_E10S"] = "1" # Guard against intermittent failures to retrieve abi property; # without an abi, xpcshell cannot find greprefs.js and crashes. abilistprop = None abi = None retries = 0 while not abi and retries < 3: abi = self.device.get_prop("ro.product.cpu.abi") retries += 1 if not abi: raise Exception("failed to get ro.product.cpu.abi from device") self.log.info("ro.product.cpu.abi %s" % abi) if self.localAPKContents: abilist = [abi] retries = 0 while not abilistprop and retries < 3: abilistprop = self.device.get_prop("ro.product.cpu.abilist") retries += 1 self.log.info("ro.product.cpu.abilist %s" % abilistprop) abi_found = False names = [ n for n in self.localAPKContents.namelist() if n.startswith("lib/") ] self.log.debug("apk names: %s" % names) if abilistprop and len(abilistprop) > 0: abilist.extend(abilistprop.split(",")) for abicand in abilist: abi_found = (len( [n for n in names if n.startswith("lib/%s" % abicand)]) > 0) if abi_found: abi = abicand break if not abi_found: self.log.info("failed to get matching abi from apk.") if len(names) > 0: self.log.info( "device cpu abi not found in apk. Using abi from apk.") abi = names[0].split("/")[1] self.log.info("Using abi %s." % abi) self.env["MOZ_ANDROID_CPU_ABI"] = abi self.log.info("Using env %r" % (self.env, )) def setupUtilities(self): self.initDir(self.remoteTmpDir) self.initDir(self.remoteBinDir) remotePrefDir = posixpath.join(self.remoteBinDir, "defaults", "pref") self.initDir(posixpath.join(remotePrefDir, "extra")) self.initDir(self.remoteComponentsDir) local = os.path.join(os.path.dirname(os.path.abspath(__file__)), "head.js") remoteFile = posixpath.join(self.remoteScriptsDir, "head.js") self.device.push(local, remoteFile) self.device.chmod(remoteFile) # The xpcshell binary is required for all tests. Additional binaries # are required for some tests. This list should be similar to # TEST_HARNESS_BINS in testing/mochitest/Makefile.in. binaries = [ "xpcshell", "ssltunnel", "certutil", "pk12util", "BadCertAndPinningServer", "DelegatedCredentialsServer", "EncryptedClientHelloServer", "OCSPStaplingServer", "GenerateOCSPResponse", "SanctionsTestServer", ] for fname in binaries: local = os.path.join(self.localBin, fname) if os.path.isfile(local): print("Pushing %s.." % fname, file=sys.stderr) remoteFile = posixpath.join(self.remoteBinDir, fname) self.device.push(local, remoteFile) self.device.chmod(remoteFile) else: print( "*** Expected binary %s not found in %s!" % (fname, self.localBin), file=sys.stderr, ) local = os.path.join(self.localBin, "components/httpd.js") remoteFile = posixpath.join(self.remoteComponentsDir, "httpd.js") self.device.push(local, remoteFile) self.device.chmod(remoteFile) if self.options["localAPK"]: remoteFile = posixpath.join( self.remoteBinDir, os.path.basename(self.options["localAPK"])) self.device.push(self.options["localAPK"], remoteFile) self.device.chmod(remoteFile) self.pushLibs() else: localB2G = os.path.join(self.options["objdir"], "dist", "b2g") if os.path.exists(localB2G): self.device.push(localB2G, self.remoteBinDir) self.device.chmod(self.remoteBinDir) else: raise Exception("unable to install gre: no APK and not b2g") def pushLibs(self): pushed_libs_count = 0 try: dir = tempfile.mkdtemp() for info in self.localAPKContents.infolist(): if info.filename.endswith(".so"): print("Pushing %s.." % info.filename, file=sys.stderr) remoteFile = posixpath.join( self.remoteBinDir, os.path.basename(info.filename)) self.localAPKContents.extract(info, dir) localFile = os.path.join(dir, info.filename) self.device.push(localFile, remoteFile) pushed_libs_count += 1 self.device.chmod(remoteFile) finally: shutil.rmtree(dir) return pushed_libs_count def setupModules(self): if self.testingModulesDir: self.device.push(self.testingModulesDir, self.remoteModulesDir) self.device.chmod(self.remoteModulesDir) def setupTestDir(self): print("pushing %s" % self.xpcDir) # The tests directory can be quite large: 5000 files and growing! # Sometimes - like on a low-end aws instance running an emulator - the push # may exceed the default 5 minute timeout, so we increase it here to 10 minutes. self.device.rm(self.remoteScriptsDir, recursive=True, force=True, timeout=None) self.device.push(self.xpcDir, self.remoteScriptsDir, timeout=600) self.device.chmod(self.remoteScriptsDir, recursive=True) def setupSocketConnections(self): # make node host ports visible to device if "MOZHTTP2_PORT" in self.env: port = "tcp:{}".format(self.env["MOZHTTP2_PORT"]) self.device.create_socket_connection( ADBDevice.SOCKET_DIRECTION_REVERSE, port, port) self.log.info("reversed MOZHTTP2_PORT connection for port " + port) if "MOZNODE_EXEC_PORT" in self.env: port = "tcp:{}".format(self.env["MOZNODE_EXEC_PORT"]) self.device.create_socket_connection( ADBDevice.SOCKET_DIRECTION_REVERSE, port, port) self.log.info("reversed MOZNODE_EXEC_PORT connection for port " + port) def buildTestList(self, test_tags=None, test_paths=None, verify=False): xpcshell.XPCShellTests.buildTestList(self, test_tags=test_tags, test_paths=test_paths, verify=verify) uniqueTestPaths = set([]) for test in self.alltests: uniqueTestPaths.add(test["here"]) for testdir in uniqueTestPaths: abbrevTestDir = os.path.relpath(testdir, self.xpcDir) remoteScriptDir = posixpath.join(self.remoteScriptsDir, abbrevTestDir) self.pathMapping.append(PathMapping(testdir, remoteScriptDir)) # This is not related to building the test list, but since this is called late # in the test suite run, this is a convenient place to finalize preparations; # in particular, these operations cannot be executed much earlier because # self.env may not be finalized. self.setupSocketConnections() if self.options["setup"]: self.pushWrapper()
class MochiRemote(MochitestDesktop): localProfile = None logMessages = [] def __init__(self, options): MochitestDesktop.__init__(self, options.flavor, vars(options)) verbose = False if (options.log_mach_verbose or options.log_tbpl_level == "debug" or options.log_mach_level == "debug" or options.log_raw_level == "debug"): verbose = True if hasattr(options, "log"): delattr(options, "log") self.certdbNew = True self.chromePushed = False expected = options.app.split("/")[-1] self.device = ADBDeviceFactory( adb=options.adbPath or "adb", device=options.deviceSerial, test_root=options.remoteTestRoot, verbose=verbose, run_as_package=expected, ) if options.remoteTestRoot is None: options.remoteTestRoot = self.device.test_root options.dumpOutputDirectory = options.remoteTestRoot self.remoteLogFile = posixpath.join(options.remoteTestRoot, "logs", "mochitest.log") logParent = posixpath.dirname(self.remoteLogFile) self.device.rm(logParent, force=True, recursive=True) self.device.mkdir(logParent, parents=True) self.remoteProfile = posixpath.join(options.remoteTestRoot, "profile") self.device.rm(self.remoteProfile, force=True, recursive=True) self.message_logger = MessageLogger(logger=None) self.message_logger.logger = self.log # Check that Firefox is installed expected = options.app.split("/")[-1] if not self.device.is_app_installed(expected): raise Exception("%s is not installed on this device" % expected) self.device.clear_logcat() self.remoteModulesDir = posixpath.join(options.remoteTestRoot, "modules/") self.remoteCache = posixpath.join(options.remoteTestRoot, "cache/") self.device.rm(self.remoteCache, force=True, recursive=True) # move necko cache to a location that can be cleaned up options.extraPrefs += [ "browser.cache.disk.parent_directory=%s" % self.remoteCache ] self.remoteMozLog = posixpath.join(options.remoteTestRoot, "mozlog") self.device.rm(self.remoteMozLog, force=True, recursive=True) self.device.mkdir(self.remoteMozLog, parents=True) self.remoteChromeTestDir = posixpath.join(options.remoteTestRoot, "chrome") self.device.rm(self.remoteChromeTestDir, force=True, recursive=True) self.device.mkdir(self.remoteChromeTestDir, parents=True) self.appName = options.remoteappname self.device.stop_application(self.appName) if self.device.process_exist(self.appName): self.log.warning("unable to kill %s before running tests!" % self.appName) # Add Android version (SDK level) to mozinfo so that manifest entries # can be conditional on android_version. self.log.info( "Android sdk version '%s'; will use this to filter manifests" % str(self.device.version)) mozinfo.info["android_version"] = str(self.device.version) mozinfo.info["is_fennec"] = not ("geckoview" in options.app) mozinfo.info["is_emulator"] = self.device._device_serial.startswith( "emulator-") def cleanup(self, options, final=False): if final: self.device.rm(self.remoteChromeTestDir, force=True, recursive=True) self.chromePushed = False uploadDir = os.environ.get("MOZ_UPLOAD_DIR", None) if uploadDir and self.device.is_dir(self.remoteMozLog): self.device.pull(self.remoteMozLog, uploadDir) self.device.rm(self.remoteLogFile, force=True) self.device.rm(self.remoteProfile, force=True, recursive=True) self.device.rm(self.remoteCache, force=True, recursive=True) MochitestDesktop.cleanup(self, options, final) self.localProfile = None def dumpScreen(self, utilityPath): if self.haveDumpedScreen: self.log.info( "Not taking screenshot here: see the one that was previously logged" ) return self.haveDumpedScreen = True if self.device._device_serial.startswith("emulator-"): dump_screen(utilityPath, self.log) else: dump_device_screen(self.device, self.log) def findPath(self, paths, filename=None): for path in paths: p = path if filename: p = os.path.join(p, filename) if os.path.exists(self.getFullPath(p)): return path return None # This seems kludgy, but this class uses paths from the remote host in the # options, except when calling up to the base class, which doesn't # understand the distinction. This switches out the remote values for local # ones that the base class understands. This is necessary for the web # server, SSL tunnel and profile building functions. def switchToLocalPaths(self, options): """ Set local paths in the options, return a function that will restore remote values """ remoteXrePath = options.xrePath remoteProfilePath = options.profilePath remoteUtilityPath = options.utilityPath paths = [ options.xrePath, ] if build_obj: paths.append(os.path.join(build_obj.topobjdir, "dist", "bin")) options.xrePath = self.findPath(paths) if options.xrePath is None: self.log.error( "unable to find xulrunner path for %s, please specify with --xre-path" % os.name) sys.exit(1) xpcshell = "xpcshell" if os.name == "nt": xpcshell += ".exe" if options.utilityPath: paths = [options.utilityPath, options.xrePath] else: paths = [options.xrePath] options.utilityPath = self.findPath(paths, xpcshell) if options.utilityPath is None: self.log.error( "unable to find utility path for %s, please specify with --utility-path" % os.name) sys.exit(1) xpcshell_path = os.path.join(options.utilityPath, xpcshell) if RemoteProcessMonitor.elf_arm(xpcshell_path): self.log.error("xpcshell at %s is an ARM binary; please use " "the --utility-path argument to specify the path " "to a desktop version." % xpcshell_path) sys.exit(1) if self.localProfile: options.profilePath = self.localProfile else: options.profilePath = None def fixup(): options.xrePath = remoteXrePath options.utilityPath = remoteUtilityPath options.profilePath = remoteProfilePath return fixup def startServers(self, options, debuggerInfo, public=None): """ Create the servers on the host and start them up """ restoreRemotePaths = self.switchToLocalPaths(options) MochitestDesktop.startServers(self, options, debuggerInfo, public=True) restoreRemotePaths() def buildProfile(self, options): restoreRemotePaths = self.switchToLocalPaths(options) if options.testingModulesDir: try: self.device.push(options.testingModulesDir, self.remoteModulesDir) self.device.chmod(self.remoteModulesDir, recursive=True) except Exception: self.log.error( "Automation Error: Unable to copy test modules to device.") raise savedTestingModulesDir = options.testingModulesDir options.testingModulesDir = self.remoteModulesDir else: savedTestingModulesDir = None manifest = MochitestDesktop.buildProfile(self, options) if savedTestingModulesDir: options.testingModulesDir = savedTestingModulesDir self.localProfile = options.profilePath restoreRemotePaths() options.profilePath = self.remoteProfile return manifest def buildURLOptions(self, options, env): saveLogFile = options.logFile options.logFile = self.remoteLogFile options.profilePath = self.localProfile env["MOZ_HIDE_RESULTS_TABLE"] = "1" retVal = MochitestDesktop.buildURLOptions(self, options, env) # we really need testConfig.js (for browser chrome) try: self.device.push(options.profilePath, self.remoteProfile) self.device.chmod(self.remoteProfile, recursive=True) except Exception: self.log.error( "Automation Error: Unable to copy profile to device.") raise options.profilePath = self.remoteProfile options.logFile = saveLogFile return retVal def getChromeTestDir(self, options): local = super(MochiRemote, self).getChromeTestDir(options) remote = self.remoteChromeTestDir if options.flavor == "chrome" and not self.chromePushed: self.log.info("pushing %s to %s on device..." % (local, remote)) local = os.path.join(local, "chrome") self.device.push(local, remote) self.chromePushed = True return remote def getLogFilePath(self, logFile): return logFile def printDeviceInfo(self, printLogcat=False): try: if printLogcat: logcat = self.device.get_logcat() for l in logcat: ul = l.decode("utf-8", errors="replace") sl = ul.encode("iso8859-1", errors="replace") self.log.info(sl) self.log.info("Device info:") devinfo = self.device.get_info() for category in devinfo: if type(devinfo[category]) is list: self.log.info(" %s:" % category) for item in devinfo[category]: self.log.info(" %s" % item) else: self.log.info(" %s: %s" % (category, devinfo[category])) self.log.info("Test root: %s" % self.device.test_root) except ADBTimeoutError: raise except Exception as e: self.log.warning("Error getting device information: %s" % str(e)) def getGMPPluginPath(self, options): # TODO: bug 1149374 return None def environment(self, env=None, crashreporter=True, **kwargs): # Since running remote, do not mimic the local env: do not copy os.environ if env is None: env = {} if crashreporter: env["MOZ_CRASHREPORTER_NO_REPORT"] = "1" env["MOZ_CRASHREPORTER"] = "1" env["MOZ_CRASHREPORTER_SHUTDOWN"] = "1" else: env["MOZ_CRASHREPORTER_DISABLE"] = "1" # Crash on non-local network connections by default. # MOZ_DISABLE_NONLOCAL_CONNECTIONS can be set to "0" to temporarily # enable non-local connections for the purposes of local testing. # Don't override the user's choice here. See bug 1049688. env.setdefault("MOZ_DISABLE_NONLOCAL_CONNECTIONS", "1") # Send an env var noting that we are in automation. Passing any # value except the empty string will declare the value to exist. # # This may be used to disabled network connections during testing, e.g. # Switchboard & telemetry uploads. env.setdefault("MOZ_IN_AUTOMATION", "1") # Set WebRTC logging in case it is not set yet. env.setdefault("R_LOG_LEVEL", "6") env.setdefault("R_LOG_DESTINATION", "stderr") env.setdefault("R_LOG_VERBOSE", "1") return env def buildBrowserEnv(self, options, debugger=False): browserEnv = MochitestDesktop.buildBrowserEnv(self, options, debugger=debugger) # remove desktop environment not used on device if "XPCOM_MEM_BLOAT_LOG" in browserEnv: del browserEnv["XPCOM_MEM_BLOAT_LOG"] if self.mozLogs: browserEnv["MOZ_LOG_FILE"] = os.path.join( self.remoteMozLog, "moz-pid=%PID-uid={}.log".format(str(uuid.uuid4()))) if options.dmd: browserEnv["DMD"] = "1" # Contents of remoteMozLog will be pulled from device and copied to the # host MOZ_UPLOAD_DIR, to be made available as test artifacts. Make # MOZ_UPLOAD_DIR available to the browser environment so that tests # can use it as though they were running on the host. browserEnv["MOZ_UPLOAD_DIR"] = self.remoteMozLog return browserEnv def runApp( self, testUrl, env, app, profile, extraArgs, utilityPath, debuggerInfo=None, valgrindPath=None, valgrindArgs=None, valgrindSuppFiles=None, symbolsPath=None, timeout=-1, detectShutdownLeaks=False, screenshotOnFail=False, bisectChunk=None, marionette_args=None, e10s=True, runFailures=False, crashAsPass=False, ): """ Run the app, log the duration it took to execute, return the status code. Kill the app if it outputs nothing for |timeout| seconds. """ if timeout == -1: timeout = self.DEFAULT_TIMEOUT rpm = RemoteProcessMonitor( self.appName, self.device, self.log, self.message_logger, self.remoteLogFile, self.remoteProfile, ) startTime = datetime.datetime.now() status = 0 profileDirectory = self.remoteProfile + "/" args = [] args.extend(extraArgs) args.extend(("-no-remote", "-profile", profileDirectory)) pid = rpm.launch( app, debuggerInfo, testUrl, args, env=self.environment(env=env, crashreporter=not debuggerInfo), e10s=e10s, ) # TODO: not using runFailures or crashAsPass, if we choose to use them # we need to adjust status and check_for_crashes self.log.info("runtestsremote.py | Application pid: %d" % pid) if not rpm.wait(timeout): status = 1 self.log.info("runtestsremote.py | Application ran for: %s" % str(datetime.datetime.now() - startTime)) crashed = self.check_for_crashes(symbolsPath, rpm.last_test_seen) if crashed: status = 1 self.countpass += rpm.counts["pass"] self.countfail += rpm.counts["fail"] self.counttodo += rpm.counts["todo"] return status, rpm.last_test_seen def check_for_crashes(self, symbols_path, last_test_seen): """ Pull any minidumps from remote profile and log any associated crashes. """ try: dump_dir = tempfile.mkdtemp() remote_crash_dir = posixpath.join(self.remoteProfile, "minidumps") if not self.device.is_dir(remote_crash_dir): return False self.device.pull(remote_crash_dir, dump_dir) crashed = mozcrash.log_crashes(self.log, dump_dir, symbols_path, test=last_test_seen) finally: try: shutil.rmtree(dump_dir) except Exception as e: self.log.warning("unable to remove directory %s: %s" % (dump_dir, str(e))) return crashed
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)
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)