def _runCmd(self, args, timeout=None, retryLimit=None): """ Runs a command using adb If timeout is specified, the process is killed after <timeout> seconds. returns: instance of ProcessHandler """ retryLimit = retryLimit or self.retryLimit finalArgs = [self._adbPath] if self._serverHost is not None: finalArgs.extend(['-H', self._serverHost]) if self._serverPort is not None: finalArgs.extend(['-P', str(self._serverPort)]) if self._deviceSerial: finalArgs.extend(['-s', self._deviceSerial]) finalArgs.extend(args) self._logger.debug("_runCmd - command: %s" % ' '.join(finalArgs)) if not timeout: timeout = self.default_timeout def _timeout(): self._logger.error("Timeout exceeded for _runCmd call '%s'" % ' '.join(finalArgs)) retries = 0 while retries < retryLimit: proc = ProcessHandler(finalArgs, storeOutput=True, processOutputLine=self._log, onTimeout=_timeout) proc.run(timeout=timeout) proc.returncode = proc.wait() if proc.returncode == None: proc.kill() retries += 1 else: return proc
def _run_profile(self): command_args = utils.GenerateBrowserCommandLine( self.browser_config["browser_path"], self.browser_config["extra_args"], self.profile_dir, self.browser_config["init_url"]) def browser_log(line): logging.debug('BROWSER_OUTPUT: %s', line) browser = ProcessHandler(command_args, env=self.env, processOutputLine=browser_log) browser.run() try: browser.wait() except KeyboardInterrupt: browser.kill() raise results_raw = '\n'.join(browser.output) if not self.PROFILE_REGEX.search(results_raw): logging.info("Could not find %s in browser output", self.PROFILE_REGEX.pattern) logging.info("Raw results:%s", results_raw) raise TalosError("browser failed to close after being initialized")
def _runCmd(self, args, retryLimit=None): """ Runs a command using adb returns: instance of ProcessHandler """ retryLimit = retryLimit or self.retryLimit finalArgs = [self._adbPath] if self._serverHost is not None: finalArgs.extend(['-H', self._serverHost]) if self._serverPort is not None: finalArgs.extend(['-P', str(self._serverPort)]) if self._deviceSerial: finalArgs.extend(['-s', self._deviceSerial]) finalArgs.extend(args) self._logger.debug("_runCmd - command: %s" % ' '.join(finalArgs)) retries = 0 while retries < retryLimit: proc = ProcessHandler(finalArgs, storeOutput=True, processOutputLine=self._log) proc.run() proc.returncode = proc.wait() if proc.returncode == None: proc.kill() retries += 1 else: return proc
def _checkCmd(self, args, timeout=None, retryLimit=None): """ Runs a command using adb and waits for the command to finish. If timeout is specified, the process is killed after <timeout> seconds. returns: returncode from process """ retryLimit = retryLimit or self.retryLimit finalArgs = [self._adbPath] if self._deviceSerial: finalArgs.extend(['-s', self._deviceSerial]) finalArgs.extend(args) self._logger.debug("_checkCmd - command: %s" % ' '.join(finalArgs)) if not timeout: # We are asserting that all commands will complete in this # time unless otherwise specified timeout = self.default_timeout timeout = int(timeout) retries = 0 while retries < retryLimit: proc = ProcessHandler(finalArgs, processOutputLine=self._log) proc.run(timeout=timeout) ret_code = proc.wait() if ret_code == None: proc.kill() retries += 1 else: return ret_code raise DMError("Timeout exceeded for _checkCmd call after %d retries." % retries)
def _run_profile(self): command_args = utils.GenerateBrowserCommandLine( self.browser_config["browser_path"], self.browser_config["extra_args"], self.profile_dir, self.browser_config["init_url"] ) def browser_log(line): LOG.process_output(browser.pid, line) browser = ProcessHandler(command_args, env=self.env, processOutputLine=browser_log) browser.run() LOG.process_start(browser.pid, ' '.join(command_args)) try: exit_code = browser.wait() except KeyboardInterrupt: browser.kill() raise LOG.process_exit(browser.pid, exit_code) results_raw = '\n'.join(browser.output) if not self.PROFILE_REGEX.search(results_raw): LOG.info("Could not find %s in browser output" % self.PROFILE_REGEX.pattern) LOG.info("Raw results:%s" % results_raw) raise TalosError("browser failed to close after being initialized")
def tooltool_download(manifest, run_local, raptor_dir): """Download a file from tooltool using the provided tooltool manifest""" def outputHandler(line): LOG.info(line) if run_local: command = [ sys.executable, TOOLTOOL_PATH, 'fetch', '-o', '-m', manifest ] else: # we want to use the tooltool cache in production if os.environ.get('TOOLTOOLCACHE', None) is not None: _cache = os.environ['TOOLTOOLCACHE'] else: _cache = "/builds/tooltool_cache" command = [ sys.executable, TOOLTOOL_PATH, 'fetch', '-o', '-m', manifest, '-c', _cache ] proc = ProcessHandler(command, processOutputLine=outputHandler, storeOutput=False, cwd=raptor_dir) proc.run() try: proc.wait() except Exception: if proc.poll() is None: proc.kill(signal.SIGTERM)
def _runCmd(self, args, timeout=None, retryLimit=None): """ Runs a command using adb If timeout is specified, the process is killed after <timeout> seconds. returns: instance of ProcessHandler """ retryLimit = retryLimit or self.retryLimit finalArgs = [self._adbPath] if self._serverHost is not None: finalArgs.extend(['-H', self._serverHost]) if self._serverPort is not None: finalArgs.extend(['-P', str(self._serverPort)]) if self._deviceSerial: finalArgs.extend(['-s', self._deviceSerial]) finalArgs.extend(args) self._logger.debug("_runCmd - command: %s" % ' '.join(finalArgs)) if not timeout: timeout = self.default_timeout def _timeout(): self._logger.error("Timeout exceeded for _runCmd call '%s'" % ' '.join(finalArgs)) retries = 0 while retries < retryLimit: proc = ProcessHandler(finalArgs, storeOutput=True, processOutputLine=self._log, onTimeout=_timeout) proc.run(timeout=timeout) proc.returncode = proc.wait() if proc.returncode is None: proc.kill() retries += 1 else: return proc
def _run_profile(self): command_args = utils.GenerateBrowserCommandLine( self.browser_config["browser_path"], self.browser_config["extra_args"], self.profile_dir, self.browser_config["init_url"] ) def browser_log(line): logging.debug('BROWSER_OUTPUT: %s', line) browser = ProcessHandler(command_args, env=self.env, processOutputLine=browser_log) browser.run() try: browser.wait() except KeyboardInterrupt: browser.kill() raise results_raw = '\n'.join(browser.output) if not self.PROFILE_REGEX.search(results_raw): logging.info("Could not find %s in browser output", self.PROFILE_REGEX.pattern) logging.info("Raw results:%s", results_raw) raise TalosError("browser failed to close after being initialized")
def lint(files, config, **kwargs): tests_dir = os.path.join(kwargs['root'], 'testing', 'web-platform', 'tests') def process_line(line): try: data = json.loads(line) except ValueError: return data["level"] = "error" data["path"] = os.path.relpath(os.path.join(tests_dir, data["path"]), kwargs['root']) data.setdefault("lineno", 0) results.append(result.from_config(config, **data)) if files == [tests_dir]: print >> sys.stderr, ("No specific files specified, running the full wpt lint" " (this is slow)") files = ["--all"] cmd = [os.path.join(tests_dir, 'wpt'), 'lint', '--json'] + files if platform.system() == 'Windows': cmd.insert(0, sys.executable) proc = ProcessHandler(cmd, env=os.environ, processOutputLine=process_line) proc.run() try: proc.wait() if proc.returncode != 0: results.append( result.from_config(config, message="Lint process exited with return code %s" % proc.returncode)) except KeyboardInterrupt: proc.kill() return results
def run_process(self, cmd, cwd=None, dump=False): def _processOutput(line): if self.verbose or dump: print(line) if self.verbose: self.build_obj.log(logging.INFO, "autophone", {}, "Running '%s' in '%s'" % (cmd, cwd)) proc = ProcessHandler(cmd, cwd=cwd, processOutputLine=_processOutput, processStderrLine=_processOutput) proc.run() proc_complete = False try: proc.wait() if proc.proc.returncode == 0: proc_complete = True except: if proc.poll() is None: proc.kill(signal.SIGTERM) if not proc_complete: if not self.verbose: print(proc.output) return proc_complete
def lint(files, config, **kwargs): tests_dir = os.path.join(kwargs['root'], 'testing', 'web-platform', 'tests') def process_line(line): try: data = json.loads(line) except ValueError: return data["level"] = "error" data["path"] = os.path.relpath(os.path.join(tests_dir, data["path"]), kwargs['root']) results.append(result.from_config(config, **data)) path = os.path.join(tests_dir, "lint") proc = ProcessHandler([path, "--json"] + files, env=os.environ, processOutputLine=process_line) proc.run() try: proc.wait() except KeyboardInterrupt: proc.kill() return results
def lint(files, config, **kwargs): tests_dir = os.path.join(kwargs['root'], 'testing', 'web-platform', 'tests') def process_line(line): try: data = json.loads(line) except ValueError: return data["level"] = "error" data["path"] = os.path.relpath(os.path.join(tests_dir, data["path"]), kwargs['root']) results.append(result.from_config(config, **data)) if files == [tests_dir]: print >> sys.stderr, ("No specific files specified, running the full wpt lint" " (this is slow)") files = ["--all"] cmd = [os.path.join(tests_dir, 'wpt'), 'lint', '--json'] + files if platform.system() == 'Windows': cmd.insert(0, sys.executable) proc = ProcessHandler(cmd, env=os.environ, processOutputLine=process_line) proc.run() try: proc.wait() if proc.returncode != 0: results.append( result.from_config(config, message="Lint process exited with return code %s" % proc.returncode)) except KeyboardInterrupt: proc.kill() return results
def _run_profile(self): command_args = utils.GenerateBrowserCommandLine( self.browser_config["browser_path"], self.browser_config["extra_args"], self.profile_dir, self.browser_config["init_url"]) def browser_log(line): LOG.process_output(browser.pid, line) browser = ProcessHandler(command_args, env=self.env, processOutputLine=browser_log) browser.run() LOG.process_start(browser.pid, ' '.join(command_args)) try: exit_code = browser.wait() except KeyboardInterrupt: browser.kill() raise LOG.process_exit(browser.pid, exit_code) results_raw = '\n'.join(browser.output) if not self.PROFILE_REGEX.search(results_raw): LOG.info("Could not find %s in browser output" % self.PROFILE_REGEX.pattern) LOG.info("Raw results:%s" % results_raw) raise TalosError("browser failed to close after being initialized")
def tooltool_download(manifest, run_local, raptor_dir): """Download a file from tooltool using the provided tooltool manifest""" def outputHandler(line): LOG.info(line) tooltool_path = None for path in TOOLTOOL_PATHS: if os.path.exists(os.path.dirname(path)): tooltool_path = path break if tooltool_path is None: raise Exception("Could not find tooltool path!") if run_local: command = [sys.executable, tooltool_path, "fetch", "-o", "-m", manifest] else: # Attempt to determine the tooltool cache path: # - TOOLTOOLCACHE is used by Raptor tests # - TOOLTOOL_CACHE is automatically set for taskcluster jobs # - fallback to a hardcoded path _cache = next( x for x in ( os.environ.get("TOOLTOOLCACHE"), os.environ.get("TOOLTOOL_CACHE"), "/builds/tooltool_cache", ) if x is not None ) command = [ sys.executable, tooltool_path, "fetch", "-o", "-m", manifest, "-c", _cache, ] try: proc = ProcessHandler( command, processOutputLine=outputHandler, storeOutput=False, cwd=raptor_dir ) proc.run() if proc.wait() != 0: raise Exception("Command failed") except Exception as e: LOG.critical( "Error while downloading {} from tooltool:{}".format(manifest, str(e)) ) if proc.poll() is None: proc.kill(signal.SIGTERM) raise
class ServoTestharnessExecutor(ProcessTestExecutor): convert_result = testharness_result_converter def __init__(self, *args, **kwargs): ProcessTestExecutor.__init__(self, *args, **kwargs) self.result_data = None self.result_flag = None def run_test(self, test): self.result_data = None self.result_flag = threading.Event() self.command = [self.binary, "--cpu", "--hard-fail", urlparse.urljoin(self.http_server_url, test.url)] if self.debug_args: self.command = list(self.debug_args) + self.command self.proc = ProcessHandler(self.command, processOutputLine=[self.on_output], onFinish=self.on_finish) self.proc.run() timeout = test.timeout * self.timeout_multiplier # Now wait to get the output we expect, or until we reach the timeout self.result_flag.wait(timeout + 5) if self.result_flag.is_set() and self.result_data is not None: self.result_data["test"] = test.url result = self.convert_result(test, self.result_data) self.proc.kill() else: if self.proc.proc.poll() is not None: result = (test.result_cls("CRASH", None), []) else: self.proc.kill() result = (test.result_cls("TIMEOUT", None), []) self.runner.send_message("test_ended", test, result) def on_output(self, line): prefix = "ALERT: RESULT: " line = line.decode("utf8", "replace") if line.startswith(prefix): self.result_data = json.loads(line[len(prefix):]) self.result_flag.set() else: if self.interactive: print line else: self.logger.process_output(self.proc.pid, line, " ".join(self.command)) def on_finish(self): self.result_flag.set()
def run_process(): path = os.path.join(tests_dir, "lint") proc = ProcessHandler([path, "--json"], env=os.environ, processOutputLine=process_line) proc.run() try: proc.wait() except KeyboardInterrupt: proc.kill()
class ServoTestharnessExecutor(ProcessTestExecutor): convert_result = testharness_result_converter def __init__(self, *args, **kwargs): ProcessTestExecutor.__init__(self, *args, **kwargs) self.result_data = None self.result_flag = None def run_test(self, test): self.result_data = None self.result_flag = threading.Event() self.command = [ self.binary, "--cpu", "--hard-fail", "-z", urlparse.urljoin(self.http_server_url, test.url) ] if self.debug_args: self.command = list(self.debug_args) + self.command self.proc = ProcessHandler(self.command, processOutputLine=[self.on_output], onFinish=self.on_finish) self.proc.run() timeout = test.timeout * self.timeout_multiplier # Now wait to get the output we expect, or until we reach the timeout self.result_flag.wait(timeout + 5) if self.result_flag.is_set() and self.result_data is not None: self.result_data["test"] = test.url result = self.convert_result(test, self.result_data) self.proc.kill() else: if self.proc.proc.poll() is not None: result = (test.result_cls("CRASH", None), []) else: self.proc.kill() result = (test.result_cls("TIMEOUT", None), []) self.runner.send_message("test_ended", test, result) def on_output(self, line): prefix = "ALERT: RESULT: " line = line.decode("utf8", "replace") if line.startswith(prefix): self.result_data = json.loads(line[len(prefix):]) self.result_flag.set() else: if self.interactive: print line else: self.logger.process_output(self.proc.pid, line, " ".join(self.command)) def on_finish(self): self.result_flag.set()
class ServoRefTestExecutor(ProcessTestExecutor): convert_result = reftest_result_converter def __init__(self, browser, http_server_url, binary=None, timeout_multiplier=1, screenshot_cache=None, debug_args=None, pause_after_test=False): ProcessTestExecutor.__init__(self, browser, http_server_url, timeout_multiplier=timeout_multiplier, debug_args=debug_args) self.protocol = Protocol(self, browser, http_server_url) self.screenshot_cache = screenshot_cache self.implementation = RefTestImplementation(self) self.tempdir = tempfile.mkdtemp() def teardown(self): os.rmdir(self.tempdir) ProcessTestExecutor.teardown(self) def screenshot(self, url, timeout): full_url = urlparse.urljoin(self.http_server_url, url) with TempFilename(self.tempdir) as output_path: self.command = [self.binary, "--cpu", "--hard-fail", "--exit", "--output=%s" % output_path, full_url] self.proc = ProcessHandler(self.command, processOutputLine=[self.on_output]) self.proc.run() rv = self.proc.wait(timeout=timeout) if rv is None: self.proc.kill() return False, ("EXTERNAL-TIMEOUT", None) if rv < 0: return False, ("CRASH", None) with open(output_path) as f: # Might need to strip variable headers or something here data = f.read() return True, data def do_test(self, test): result = self.implementation.run_test(test) return self.convert_result(test, result) def on_output(self, line): line = line.decode("utf8", "replace") if self.interactive: print line else: self.logger.process_output(self.proc.pid, line, " ".join(self.command))
def _tooltool_fetch(self): def outputHandler(line): self._log_debug(line) command = ["python", "tooltool.py", "fetch", "-m", "releng.manifest"] proc = ProcessHandler(command, processOutputLine=outputHandler, storeOutput=False, cwd=EMULATOR_HOME_DIR) proc.run() try: proc.wait() except: if proc.poll() is None: proc.kill(signal.SIGTERM)
def _tooltool_fetch(): def outputHandler(line): _log_debug(line) command = ['python', 'tooltool.py', 'fetch', '-o', '-m', 'releng.manifest'] proc = ProcessHandler( command, processOutputLine=outputHandler, storeOutput=False, cwd=EMULATOR_HOME_DIR) proc.run() try: proc.wait() except: if proc.poll() is None: proc.kill(signal.SIGTERM)
def _tooltool_fetch(): def outputHandler(line): _log_debug(line) _download_file(TOOLTOOL_URL, "tooltool.py", EMULATOR_HOME_DIR) command = [sys.executable, "tooltool.py", "fetch", "-o", "-m", "releng.manifest"] proc = ProcessHandler(command, processOutputLine=outputHandler, storeOutput=False, cwd=EMULATOR_HOME_DIR) proc.run() try: proc.wait() except: if proc.poll() is None: proc.kill(signal.SIGTERM)
def run_process(cmdargs): # flake8 seems to handle SIGINT poorly. Handle it here instead # so we can kill the process without a cryptic traceback. orig = signal.signal(signal.SIGINT, signal.SIG_IGN) proc = ProcessHandler(cmdargs, env=os.environ, processOutputLine=process_line) proc.run() signal.signal(signal.SIGINT, orig) try: proc.wait() except KeyboardInterrupt: proc.kill()
def _checkCmd(self, args, timeout=None, retryLimit=None): """ Runs a command using adb and waits for the command to finish. If timeout is specified, the process is killed after <timeout> seconds. returns: returncode from process """ retryLimit = retryLimit or self.retryLimit finalArgs = [self._adbPath] if self._serverHost is not None: finalArgs.extend(['-H', self._serverHost]) if self._serverPort is not None: finalArgs.extend(['-P', str(self._serverPort)]) if self._deviceSerial: finalArgs.extend(['-s', self._deviceSerial]) finalArgs.extend(args) self._logger.debug("_checkCmd - command: %s" % ' '.join(finalArgs)) if not timeout: # We are asserting that all commands will complete in this # time unless otherwise specified timeout = self.default_timeout def _timeout(): self._logger.error("Timeout exceeded for _checkCmd call '%s'" % ' '.join(finalArgs)) timeout = int(timeout) retries = 0 while retries < retryLimit: proc = ProcessHandler(finalArgs, processOutputLine=self._log, onTimeout=_timeout) proc.run(timeout=timeout) ret_code = proc.wait() if ret_code is None: self._logger.error("Failed to launch %s (may retry)" % finalArgs) proc.kill() retries += 1 else: if ret_code != 0: self._logger.error("Non-zero return code (%d) from %s" % (ret_code, finalArgs)) self._logger.error("Output: %s" % proc.output) output = ''.join(proc.output) if self._noDevicesOutput in output: raise DMError(self._noDevicesOutput) return ret_code raise DMError("Timeout exceeded for _checkCmd call after %d retries." % retries)
def _tooltool_fetch(): def outputHandler(line): _log_debug(line) _download_file(TOOLTOOL_URL, 'tooltool.py', EMULATOR_HOME_DIR) command = ['python', 'tooltool.py', 'fetch', '-o', '-m', 'releng.manifest'] proc = ProcessHandler( command, processOutputLine=outputHandler, storeOutput=False, cwd=EMULATOR_HOME_DIR) proc.run() try: proc.wait() except: if proc.poll() is None: proc.kill(signal.SIGTERM)
def _tooltool_fetch(self, manifest): def outputHandler(line): LOG.info(line) command = [sys.executable, TOOLTOOL_PATH, 'fetch', '-o', '-m', manifest] proc = ProcessHandler( command, processOutputLine=outputHandler, storeOutput=False, cwd=self.raptor_dir) proc.run() try: proc.wait() except Exception: if proc.poll() is None: proc.kill(signal.SIGTERM)
def _tooltool_fetch(): def outputHandler(line): _log_debug(line) tooltool_full_path = os.path.abspath(TOOLTOOL_PATH) command = [sys.executable, tooltool_full_path, 'fetch', '-o', '-m', 'releng.manifest'] proc = ProcessHandler( command, processOutputLine=outputHandler, storeOutput=False, cwd=EMULATOR_HOME_DIR) proc.run() try: proc.wait() except Exception: if proc.poll() is None: proc.kill(signal.SIGTERM)
def lint(files, config, **kwargs): log = kwargs["log"] tests_dir = os.path.join(kwargs["root"], "testing", "web-platform", "tests") def process_line(line): try: data = json.loads(line) except ValueError: return data["level"] = "error" data["path"] = os.path.relpath(os.path.join(tests_dir, data["path"]), kwargs["root"]) data.setdefault("lineno", 0) results.append(result.from_config(config, **data)) if files == [tests_dir]: print( "No specific files specified, running the full wpt lint" " (this is slow)", file=sys.stderr, ) files = ["--all"] cmd = ["python2", os.path.join(tests_dir, "wpt"), "lint", "--json"] + files log.debug("Command: {}".format(" ".join(cmd))) proc = ProcessHandler(cmd, env=os.environ, processOutputLine=process_line, universal_newlines=True) proc.run() try: proc.wait() if proc.returncode != 0: results.append( result.from_config( config, message="Lint process exited with return code %s" % proc.returncode, )) except KeyboardInterrupt: proc.kill() return results
def tooltool_download(manifest, run_local, raptor_dir): """Download a file from tooltool using the provided tooltool manifest""" def outputHandler(line): LOG.info(line) if run_local: command = [ sys.executable, TOOLTOOL_PATH, "fetch", "-o", "-m", manifest ] else: # we want to use the tooltool cache in production if os.environ.get("TOOLTOOLCACHE") is not None: _cache = os.environ["TOOLTOOLCACHE"] else: # XXX top level dir? really? # that gets run locally on any platform # when you call ./mach python-test _cache = "/builds/tooltool_cache" command = [ sys.executable, TOOLTOOL_PATH, "fetch", "-o", "-m", manifest, "-c", _cache, ] proc = ProcessHandler(command, processOutputLine=outputHandler, storeOutput=False, cwd=raptor_dir) proc.run() try: proc.wait() except Exception: if proc.poll() is None: proc.kill(signal.SIGTERM)
def lint(files, config, **kwargs): tests_dir = os.path.join(kwargs['root'], 'testing', 'web-platform', 'tests') def process_line(line): try: data = json.loads(line) except ValueError: return data["level"] = "error" data["path"] = os.path.relpath(os.path.join(tests_dir, data["path"]), kwargs['root']) results.append(result.from_config(config, **data)) cmd = [os.path.join(tests_dir, 'wpt'), 'lint', '--json'] + files if platform.system() == 'Windows': cmd.insert(0, sys.executable) proc = ProcessHandler(cmd, env=os.environ, processOutputLine=process_line) proc.run() try: proc.wait() except KeyboardInterrupt: proc.kill() return results
class ServoTestharnessExecutor(ProcessTestExecutor): convert_result = testharness_result_converter def __init__(self, logger, browser, server_config, timeout_multiplier=1, debug_info=None, pause_after_test=False, **kwargs): ProcessTestExecutor.__init__(self, logger, browser, server_config, timeout_multiplier=timeout_multiplier, debug_info=debug_info) self.pause_after_test = pause_after_test self.result_data = None self.result_flag = None self.protocol = ConnectionlessProtocol(self, browser) self.hosts_path = write_hosts_file(server_config) def teardown(self): try: os.unlink(self.hosts_path) except OSError: pass ProcessTestExecutor.teardown(self) def do_test(self, test): self.result_data = None self.result_flag = threading.Event() self.command = build_servo_command(test, self.test_url, self.browser, self.binary, self.pause_after_test, self.debug_info) env = os.environ.copy() env["HOST_FILE"] = self.hosts_path env["RUST_BACKTRACE"] = "1" if not self.interactive: self.proc = ProcessHandler(self.command, processOutputLine=[self.on_output], onFinish=self.on_finish, env=env, storeOutput=False) self.proc.run() else: self.proc = subprocess.Popen(self.command, env=env) try: timeout = test.timeout * self.timeout_multiplier # Now wait to get the output we expect, or until we reach the timeout if not self.interactive and not self.pause_after_test: wait_timeout = timeout + 5 self.result_flag.wait(wait_timeout) else: wait_timeout = None self.proc.wait() proc_is_running = True if self.result_flag.is_set(): if self.result_data is not None: result = self.convert_result(test, self.result_data) else: self.proc.wait() result = (test.result_cls("CRASH", None), []) proc_is_running = False else: result = (test.result_cls("TIMEOUT", None), []) if proc_is_running: if self.pause_after_test: self.logger.info("Pausing until the browser exits") self.proc.wait() else: self.proc.kill() except: # noqa self.proc.kill() raise return result def on_output(self, line): prefix = "ALERT: RESULT: " line = line.decode("utf8", "replace") if line.startswith(prefix): self.result_data = json.loads(line[len(prefix):]) self.result_flag.set() else: if self.interactive: print(line) else: self.logger.process_output(self.proc.pid, line, " ".join(self.command)) def on_finish(self): self.result_flag.set()
class ServoWebDriverBrowser(Browser): used_ports = set() def __init__(self, logger, binary, debug_info=None, webdriver_host="127.0.0.1", user_stylesheets=None): Browser.__init__(self, logger) self.binary = binary self.webdriver_host = webdriver_host self.webdriver_port = None self.proc = None self.debug_info = debug_info self.hosts_path = make_hosts_file() self.command = None self.user_stylesheets = user_stylesheets if user_stylesheets else [] def start(self): self.webdriver_port = get_free_port(4444, exclude=self.used_ports) self.used_ports.add(self.webdriver_port) env = os.environ.copy() env["HOST_FILE"] = self.hosts_path env["RUST_BACKTRACE"] = "1" debug_args, command = browser_command( self.binary, [ "--hard-fail", "--webdriver", str(self.webdriver_port), "about:blank", ], self.debug_info ) for stylesheet in self.user_stylesheets: command += ["--user-stylesheet", stylesheet] self.command = command self.command = debug_args + self.command if not self.debug_info or not self.debug_info.interactive: self.proc = ProcessHandler(self.command, processOutputLine=[self.on_output], env=env, storeOutput=False) self.proc.run() else: self.proc = subprocess.Popen(self.command, env=env) self.logger.debug("Servo Started") def stop(self): self.logger.debug("Stopping browser") if self.proc is not None: try: self.proc.kill() except OSError: # This can happen on Windows if the process is already dead pass def pid(self): if self.proc is None: return None try: return self.proc.pid except AttributeError: return None def on_output(self, line): """Write a line of output from the process to the log""" self.logger.process_output(self.pid(), line.decode("utf8", "replace"), command=" ".join(self.command)) def is_alive(self): if self.runner: return self.runner.is_running() return False def cleanup(self): self.stop() def executor_browser(self): assert self.webdriver_port is not None return ExecutorBrowser, {"webdriver_host": self.webdriver_host, "webdriver_port": self.webdriver_port}
class ServoTestharnessExecutor(ProcessTestExecutor): convert_result = testharness_result_converter def __init__(self, browser, server_config, timeout_multiplier=1, debug_info=None, pause_after_test=False): do_delayed_imports() ProcessTestExecutor.__init__(self, browser, server_config, timeout_multiplier=timeout_multiplier, debug_info=debug_info) self.pause_after_test = pause_after_test self.result_data = None self.result_flag = None self.protocol = Protocol(self, browser) self.hosts_path = make_hosts_file() def teardown(self): try: os.unlink(self.hosts_path) except OSError: pass ProcessTestExecutor.teardown(self) def do_test(self, test): self.result_data = None self.result_flag = threading.Event() args = [render_arg(self.browser.render_backend), "--hard-fail", "-u", "Servo/wptrunner", "-Z", "replace-surrogates", "-z", self.test_url(test)] for stylesheet in self.browser.user_stylesheets: args += ["--user-stylesheet", stylesheet] for pref, value in test.environment.get('prefs', {}).iteritems(): args += ["--pref", "%s=%s" % (pref, value)] args += self.browser.binary_args debug_args, command = browser_command(self.binary, args, self.debug_info) self.command = command if self.pause_after_test: self.command.remove("-z") self.command = debug_args + self.command env = os.environ.copy() env["HOST_FILE"] = self.hosts_path env["RUST_BACKTRACE"] = "1" if not self.interactive: self.proc = ProcessHandler(self.command, processOutputLine=[self.on_output], onFinish=self.on_finish, env=env, storeOutput=False) self.proc.run() else: self.proc = subprocess.Popen(self.command, env=env) try: timeout = test.timeout * self.timeout_multiplier # Now wait to get the output we expect, or until we reach the timeout if not self.interactive and not self.pause_after_test: wait_timeout = timeout + 5 self.result_flag.wait(wait_timeout) else: wait_timeout = None self.proc.wait() proc_is_running = True if self.result_flag.is_set(): if self.result_data is not None: result = self.convert_result(test, self.result_data) else: self.proc.wait() result = (test.result_cls("CRASH", None), []) proc_is_running = False else: result = (test.result_cls("TIMEOUT", None), []) if proc_is_running: if self.pause_after_test: self.logger.info("Pausing until the browser exits") self.proc.wait() else: self.proc.kill() except KeyboardInterrupt: self.proc.kill() raise return result def on_output(self, line): prefix = "ALERT: RESULT: " line = line.decode("utf8", "replace") if line.startswith(prefix): self.result_data = json.loads(line[len(prefix):]) self.result_flag.set() else: if self.interactive: print line else: self.logger.process_output(self.proc.pid, line, " ".join(self.command)) def on_finish(self): self.result_flag.set()
class ServoTestharnessExecutor(ProcessTestExecutor): convert_result = testharness_result_converter def __init__(self, browser, server_config, timeout_multiplier=1, debug_info=None, pause_after_test=False, **kwargs): ProcessTestExecutor.__init__(self, browser, server_config, timeout_multiplier=timeout_multiplier, debug_info=debug_info) self.pause_after_test = pause_after_test self.result_data = None self.result_flag = None self.protocol = ConnectionlessProtocol(self, browser) self.hosts_path = write_hosts_file(server_config) def teardown(self): try: os.unlink(self.hosts_path) except OSError: pass ProcessTestExecutor.teardown(self) def do_test(self, test): self.result_data = None self.result_flag = threading.Event() args = [ "--hard-fail", "-u", "Servo/wptrunner", "-Z", "replace-surrogates", "-z", self.test_url(test), ] for stylesheet in self.browser.user_stylesheets: args += ["--user-stylesheet", stylesheet] for pref, value in test.environment.get('prefs', {}).iteritems(): args += ["--pref", "%s=%s" % (pref, value)] if self.browser.ca_certificate_path: args += ["--certificate-path", self.browser.ca_certificate_path] args += self.browser.binary_args debug_args, command = browser_command(self.binary, args, self.debug_info) self.command = command if self.pause_after_test: self.command.remove("-z") self.command = debug_args + self.command env = os.environ.copy() env["HOST_FILE"] = self.hosts_path env["RUST_BACKTRACE"] = "1" if not self.interactive: self.proc = ProcessHandler(self.command, processOutputLine=[self.on_output], onFinish=self.on_finish, env=env, storeOutput=False) self.proc.run() else: self.proc = subprocess.Popen(self.command, env=env) try: timeout = test.timeout * self.timeout_multiplier # Now wait to get the output we expect, or until we reach the timeout if not self.interactive and not self.pause_after_test: wait_timeout = timeout + 5 self.result_flag.wait(wait_timeout) else: wait_timeout = None self.proc.wait() proc_is_running = True if self.result_flag.is_set(): if self.result_data is not None: result = self.convert_result(test, self.result_data) else: self.proc.wait() result = (test.result_cls("CRASH", None), []) proc_is_running = False else: result = (test.result_cls("TIMEOUT", None), []) if proc_is_running: if self.pause_after_test: self.logger.info("Pausing until the browser exits") self.proc.wait() else: self.proc.kill() except: # noqa self.proc.kill() raise return result def on_output(self, line): prefix = "ALERT: RESULT: " line = line.decode("utf8", "replace") if line.startswith(prefix): self.result_data = json.loads(line[len(prefix):]) self.result_flag.set() else: if self.interactive: print(line) else: self.logger.process_output(self.proc.pid, line, " ".join(self.command)) def on_finish(self): self.result_flag.set()
def lint(paths, config, binary=None, fix=None, setup=None, **lintargs): """Run eslint.""" setup_helper.set_project_root(lintargs['root']) module_path = setup_helper.get_project_root() if not setup_helper.check_node_executables_valid(): return 1 if setup: return setup_helper.eslint_setup() setup_helper.eslint_maybe_setup() # Valid binaries are: # - Any provided by the binary argument. # - Any pointed at by the ESLINT environmental variable. # - Those provided by mach eslint --setup. # # eslint --setup installs some mozilla specific plugins and installs # all node modules locally. This is the preferred method of # installation. if not binary: binary = os.environ.get('ESLINT', None) if not binary: binary = os.path.join(module_path, "node_modules", ".bin", "eslint") if not os.path.isfile(binary): binary = None if not binary: print(ESLINT_NOT_FOUND_MESSAGE) return 1 extra_args = lintargs.get('extra_args') or [] cmd_args = [binary, # Enable the HTML plugin. # We can't currently enable this in the global config file # because it has bad interactions with the SublimeText # ESLint plugin (bug 1229874). '--plugin', 'html', # This keeps ext as a single argument. '--ext', '[{}]'.format(','.join(config['extensions'])), '--format', 'json', ] + extra_args + paths # eslint requires that --fix be set before the --ext argument. if fix: cmd_args.insert(1, '--fix') shell = False if os.environ.get('MSYSTEM') in ('MINGW32', 'MINGW64'): # The eslint binary needs to be run from a shell with msys shell = True orig = signal.signal(signal.SIGINT, signal.SIG_IGN) proc = ProcessHandler(cmd_args, env=os.environ, stream=None, shell=shell) proc.run() signal.signal(signal.SIGINT, orig) try: proc.wait() except KeyboardInterrupt: proc.kill() return [] if not proc.output: return [] # no output means success try: jsonresult = json.loads(proc.output[0]) except ValueError: print(ESLINT_ERROR_MESSAGE.format("\n".join(proc.output))) return 1 results = [] for obj in jsonresult: errors = obj['messages'] for err in errors: err.update({ 'hint': err.get('fix'), 'level': 'error' if err['severity'] == 2 else 'warning', 'lineno': err.get('line'), 'path': obj['filePath'], 'rule': err.get('ruleId'), }) results.append(result.from_config(config, **err)) return results
class AndroidEmulator(object): """ Support running the Android emulator with an AVD from Mozilla test automation. Example usage: emulator = AndroidEmulator() if not emulator.is_running() and emulator.is_available(): if not emulator.check_avd(): warn("this may take a while...") emulator.update_avd() emulator.start() emulator.wait_for_start() emulator.wait() """ def __init__(self, avd_type='4.3', verbose=False, substs=None, device_serial=None): global verbose_logging self.emulator_log = None self.emulator_path = 'emulator' verbose_logging = verbose self.substs = substs self.avd_type = self._get_avd_type(avd_type) self.avd_info = AVD_DICT[self.avd_type] adb_path = _find_sdk_exe(substs, 'adb', False) if not adb_path: adb_path = 'adb' self.dm = DeviceManagerADB(autoconnect=False, adbPath=adb_path, retryLimit=1, deviceSerial=device_serial) self.dm.default_timeout = 10 _log_debug("Emulator created with type %s" % self.avd_type) def __del__(self): if self.emulator_log: self.emulator_log.close() def is_running(self): """ Returns True if the Android emulator is running. """ for proc in psutil.process_iter(): name = proc.name() # On some platforms, "emulator" may start an emulator with # process name "emulator64-arm" or similar. if name and name.startswith('emulator'): return True return False def is_available(self): """ Returns True if an emulator executable is found. """ found = False emulator_path = _find_sdk_exe(self.substs, 'emulator', True) if emulator_path: self.emulator_path = emulator_path found = True return found def check_avd(self, force=False): """ Determine if the AVD is already installed locally. (This is usually used to determine if update_avd() is likely to require a download; it is a convenient way of determining whether a 'this may take a while' warning is warranted.) Returns True if the AVD is installed. """ avd = os.path.join(EMULATOR_HOME_DIR, 'avd', self.avd_info.name + '.avd') if force and os.path.exists(avd): shutil.rmtree(avd) if os.path.exists(avd): _log_debug("AVD found at %s" % avd) return True return False def update_avd(self, force=False): """ If required, update the AVD via tooltool. If the AVD directory is not found, or "force" is requested, download the tooltool manifest associated with the AVD and then invoke tooltool.py on the manifest. tooltool.py will download the required archive (unless already present in the local tooltool cache) and install the AVD. """ avd = os.path.join(EMULATOR_HOME_DIR, 'avd', self.avd_info.name + '.avd') if force and os.path.exists(avd): shutil.rmtree(avd) if not os.path.exists(avd): _download_file(TOOLTOOL_URL, 'tooltool.py', EMULATOR_HOME_DIR) url = '%s/%s' % (TRY_URL, self.avd_info.tooltool_manifest) _download_file(url, 'releng.manifest', EMULATOR_HOME_DIR) _tooltool_fetch() self._update_avd_paths() def start(self): """ Launch the emulator. """ def outputHandler(line): self.emulator_log.write("<%s>\n" % line) env = os.environ env['ANDROID_AVD_HOME'] = os.path.join(EMULATOR_HOME_DIR, "avd") command = [ self.emulator_path, "-avd", self.avd_info.name, "-port", "5554" ] if self.avd_info.extra_args: # -enable-kvm option is not valid on OSX if _get_host_platform( ) == 'macosx64' and '-enable-kvm' in self.avd_info.extra_args: self.avd_info.extra_args.remove('-enable-kvm') command += self.avd_info.extra_args log_path = os.path.join(EMULATOR_HOME_DIR, 'emulator.log') self.emulator_log = open(log_path, 'w') _log_debug("Starting the emulator with this command: %s" % ' '.join(command)) _log_debug("Emulator output will be written to '%s'" % log_path) self.proc = ProcessHandler(command, storeOutput=False, processOutputLine=outputHandler, env=env) self.proc.run() _log_debug("Emulator started with pid %d" % int(self.proc.proc.pid)) def wait_for_start(self): """ Verify that the emulator is running, the emulator device is visible to adb, and Android has booted. """ if not self.proc: _log_warning("Emulator not started!") return False if self.proc.proc.poll() is not None: _log_warning("Emulator has already completed!") return False _log_debug("Waiting for device status...") while (('emulator-5554', 'device') not in self.dm.devices()): time.sleep(10) if self.proc.proc.poll() is not None: _log_warning("Emulator has already completed!") return False _log_debug("Device status verified.") _log_debug("Checking that Android has booted...") complete = False while (not complete): output = '' try: output = self.dm.shellCheckOutput( ['getprop', 'sys.boot_completed'], timeout=5) except DMError: # adb not yet responding...keep trying pass if output.strip() == '1': complete = True else: time.sleep(10) if self.proc.proc.poll() is not None: _log_warning("Emulator has already completed!") return False _log_debug("Android boot status verified.") if not self._verify_emulator(): return False if self.avd_info.uses_sut: if not self._verify_sut(): return False return True def wait(self): """ Wait for the emulator to close. If interrupted, close the emulator. """ try: self.proc.wait() except: if self.proc.poll() is None: self.cleanup() return self.proc.poll() def cleanup(self): """ Close the emulator. """ self.proc.kill(signal.SIGTERM) def get_avd_description(self): """ Return the human-friendly description of this AVD. """ return self.avd_info.description def _update_avd_paths(self): avd_path = os.path.join(EMULATOR_HOME_DIR, "avd") ini_file = os.path.join(avd_path, "test-1.ini") ini_file_new = os.path.join(avd_path, self.avd_info.name + ".ini") os.rename(ini_file, ini_file_new) avd_dir = os.path.join(avd_path, "test-1.avd") avd_dir_new = os.path.join(avd_path, self.avd_info.name + ".avd") os.rename(avd_dir, avd_dir_new) self._replace_ini_contents(ini_file_new) def _replace_ini_contents(self, path): with open(path, "r") as f: lines = f.readlines() with open(path, "w") as f: for line in lines: if line.startswith('path='): avd_path = os.path.join(EMULATOR_HOME_DIR, "avd") f.write('path=%s/%s.avd\n' % (avd_path, self.avd_info.name)) elif line.startswith('path.rel='): f.write('path.rel=avd/%s.avd\n' % self.avd_info.name) else: f.write(line) def _telnet_cmd(self, telnet, command): _log_debug(">>> " + command) telnet.write('%s\n' % command) result = telnet.read_until('OK', 10) _log_debug("<<< " + result) return result def _verify_emulator(self): telnet_ok = False tn = None while (not telnet_ok): try: tn = telnetlib.Telnet('localhost', self.avd_info.port, 10) if tn is not None: res = tn.read_until('OK', 10) self._telnet_cmd(tn, 'avd status') if self.avd_info.uses_sut: cmd = 'redir add tcp:%s:%s' % \ (str(self.avd_info.sut_port), str(self.avd_info.sut_port)) self._telnet_cmd(tn, cmd) cmd = 'redir add tcp:%s:%s' % \ (str(self.avd_info.sut_port2), str(self.avd_info.sut_port2)) self._telnet_cmd(tn, cmd) self._telnet_cmd(tn, 'redir list') self._telnet_cmd(tn, 'network status') tn.write('quit\n') tn.read_all() telnet_ok = True else: _log_warning("Unable to connect to port %d" % port) except: _log_warning("Trying again after unexpected exception") finally: if tn is not None: tn.close() if not telnet_ok: time.sleep(10) if self.proc.proc.poll() is not None: _log_warning("Emulator has already completed!") return False return telnet_ok def _verify_sut(self): sut_ok = False while (not sut_ok): try: tn = telnetlib.Telnet('localhost', self.avd_info.sut_port, 10) if tn is not None: _log_debug("Connected to port %d" % self.avd_info.sut_port) res = tn.read_until('$>', 10) if res.find('$>') == -1: _log_debug("Unexpected SUT response: %s" % res) else: _log_debug("SUT response: %s" % res) sut_ok = True tn.write('quit\n') tn.read_all() except: _log_debug("Caught exception while verifying sutagent") finally: if tn is not None: tn.close() if not sut_ok: time.sleep(10) if self.proc.proc.poll() is not None: _log_warning("Emulator has already completed!") return False return sut_ok def _get_avd_type(self, requested): if requested in AVD_DICT.keys(): return requested if self.substs: if not self.substs['TARGET_CPU'].startswith('arm'): return 'x86' if self.substs['MOZ_ANDROID_MIN_SDK_VERSION'] == '9': return '2.3' return '4.3'
class ServoTestharnessExecutor(ProcessTestExecutor): convert_result = testharness_result_converter def __init__(self, browser, server_config, timeout_multiplier=1, debug_info=None, pause_after_test=False): ProcessTestExecutor.__init__(self, browser, server_config, timeout_multiplier=timeout_multiplier, debug_info=debug_info) self.pause_after_test = pause_after_test self.result_data = None self.result_flag = None self.protocol = Protocol(self, browser) self.hosts_path = make_hosts_file() def teardown(self): try: os.unlink(self.hosts_path) except OSError: pass ProcessTestExecutor.teardown(self) def do_test(self, test): self.result_data = None self.result_flag = threading.Event() debug_args, command = browser_command(self.binary, ["--cpu", "--hard-fail", "-z", self.test_url(test)], self.debug_info) self.command = command if self.pause_after_test: self.command.remove("-z") self.command = debug_args + self.command env = os.environ.copy() env["HOST_FILE"] = self.hosts_path if not self.interactive: self.proc = ProcessHandler(self.command, processOutputLine=[self.on_output], onFinish=self.on_finish, env=env, storeOutput=False) self.proc.run() else: self.proc = subprocess.Popen(self.command, env=env) try: timeout = test.timeout * self.timeout_multiplier # Now wait to get the output we expect, or until we reach the timeout if not self.interactive and not self.pause_after_test: wait_timeout = timeout + 5 self.result_flag.wait(wait_timeout) else: wait_timeout = None self.proc.wait() proc_is_running = True if self.result_flag.is_set() and self.result_data is not None: self.result_data["test"] = test.url result = self.convert_result(test, self.result_data) else: if self.proc.poll() is not None: result = (test.result_cls("CRASH", None), []) proc_is_running = False else: result = (test.result_cls("TIMEOUT", None), []) if proc_is_running: if self.pause_after_test: self.logger.info("Pausing until the browser exits") self.proc.wait() else: self.proc.kill() except KeyboardInterrupt: self.proc.kill() raise return result def on_output(self, line): prefix = "ALERT: RESULT: " line = line.decode("utf8", "replace") if line.startswith(prefix): self.result_data = json.loads(line[len(prefix):]) self.result_flag.set() else: if self.interactive: print line else: self.logger.process_output(self.proc.pid, line, " ".join(self.command)) def on_finish(self): self.result_flag.set()
class ServoReftestExecutor(ProcessTestExecutor): convert_result = reftest_result_converter def __init__(self, *args, **kwargs): ProcessTestExecutor.__init__(self, *args, **kwargs) self.ref_hashes = {} self.ref_urls_by_hash = defaultdict(set) self.tempdir = tempfile.mkdtemp() def teardown(self): os.rmdir(self.tempdir) ProcessTestExecutor.teardown(self) def run_test(self, test): test_url, ref_type, ref_url = test.url, test.ref_type, test.ref_url hashes = {"test": None, "ref": self.ref_hashes.get(ref_url)} status = None for url_type, url in [("test", test_url), ("ref", ref_url)]: if hashes[url_type] is None: full_url = urlparse.urljoin(self.http_server_url, url) with TempFilename(self.tempdir) as output_path: self.command = [ self.binary, "--cpu", "--hard-fail", "--exit", "--output=%s" % output_path, full_url, ] timeout = test.timeout * self.timeout_multiplier self.proc = ProcessHandler(self.command, processOutputLine=[self.on_output]) self.proc.run() rv = self.proc.wait(timeout=timeout) if rv is None: status = "EXTERNAL-TIMEOUT" self.proc.kill() break if rv < 0: status = "CRASH" break with open(output_path) as f: # Might need to strip variable headers or something here data = f.read() hashes[url_type] = hashlib.sha1(data).hexdigest() if status is None: self.ref_urls_by_hash[hashes["ref"]].add(ref_url) self.ref_hashes[ref_url] = hashes["ref"] if ref_type == "==": passed = hashes["test"] == hashes["ref"] elif ref_type == "!=": passed = hashes["test"] != hashes["ref"] else: raise ValueError status = "PASS" if passed else "FAIL" result = self.convert_result(test, {"status": status, "message": None}) self.runner.send_message("test_ended", test, result) def on_output(self, line): line = line.decode("utf8", "replace") if self.interactive: print line else: self.logger.process_output(self.proc.pid, line, " ".join(self.command))
class BaseEmulator(Device): port = None proc = None telnet = None def __init__(self, app_ctx, **kwargs): self.arch = ArchContext(kwargs.pop('arch', 'arm'), app_ctx, binary=kwargs.pop('binary', None), avd=kwargs.pop('avd', None)) super(BaseEmulator, self).__init__(app_ctx, **kwargs) self.tmpdir = tempfile.mkdtemp() # These rely on telnet self.battery = EmulatorBattery(self) self.geo = EmulatorGeo(self) self.screen = EmulatorScreen(self) @property def args(self): """ Arguments to pass into the emulator binary. """ return [self.arch.binary] def start(self): """ Starts a new emulator. """ if self.proc: return original_devices = set(self._get_online_devices()) # QEMU relies on atexit() to remove temporary files, which does not # work since mozprocess uses SIGKILL to kill the emulator process. # Use a customized temporary directory so we can clean it up. os.environ['ANDROID_TMP'] = self.tmpdir qemu_log = None qemu_proc_args = {} if self.logdir: # save output from qemu to logfile qemu_log = os.path.join(self.logdir, 'qemu.log') if os.path.isfile(qemu_log): self._rotate_log(qemu_log) qemu_proc_args['logfile'] = qemu_log else: qemu_proc_args['processOutputLine'] = lambda line: None self.proc = ProcessHandler(self.args, **qemu_proc_args) self.proc.run() devices = set(self._get_online_devices()) now = datetime.datetime.now() while (devices - original_devices) == set([]): time.sleep(1) # Sometimes it takes more than 60s to launch emulator, so we # increase timeout value to 180s. Please see bug 1143380. if datetime.datetime.now() - now > datetime.timedelta( seconds=180): raise TimeoutException( 'timed out waiting for emulator to start') devices = set(self._get_online_devices()) devices = devices - original_devices self.serial = devices.pop() self.connect() def _get_online_devices(self): return [d[0] for d in self.dm.devices() if d[1] != 'offline' if d[0].startswith('emulator')] def connect(self): """ Connects to a running device. If no serial was specified in the constructor, defaults to the first entry in `adb devices`. """ if self.connected: return super(BaseEmulator, self).connect() serial = self.serial or self.dm._deviceSerial self.port = int(serial[serial.rindex('-') + 1:]) def cleanup(self): """ Cleans up and kills the emulator, if it was started by mozrunner. """ super(BaseEmulator, self).cleanup() if self.proc: self.proc.kill() self.proc = None self.connected = False # Remove temporary files if os.path.isdir(self.tmpdir): shutil.rmtree(self.tmpdir) def _get_telnet_response(self, command=None): output = [] assert self.telnet if command is not None: self.telnet.write('%s\n' % command) while True: line = self.telnet.read_until('\n') output.append(line.rstrip()) if line.startswith('OK'): return output elif line.startswith('KO:'): raise Exception('bad telnet response: %s' % line) def _run_telnet(self, command): if not self.telnet: self.telnet = Telnet('localhost', self.port) self._get_telnet_response() return self._get_telnet_response(command) def __del__(self): if self.telnet: self.telnet.write('exit\n') self.telnet.read_all()
class AndroidEmulator(object): """ Support running the Android emulator with an AVD from Mozilla test automation. Example usage: emulator = AndroidEmulator() if not emulator.is_running() and emulator.is_available(): if not emulator.check_avd(): warn("this may take a while...") emulator.update_avd() emulator.start() emulator.wait_for_start() emulator.wait() """ def __init__(self, avd_type='4.3', verbose=False, substs=None): global verbose_logging self.emulator_log = None self.emulator_path = 'emulator' verbose_logging = verbose self.substs = substs self.avd_type = self._get_avd_type(avd_type) self.avd_info = AVD_DICT[self.avd_type] adb_path = self._find_sdk_exe('adb', False) if not adb_path: adb_path = 'adb' self.dm = DeviceManagerADB(autoconnect=False, adbPath=adb_path, retryLimit=1) self.dm.default_timeout = 10 _log_debug("Emulator created with type %s" % self.avd_type) def __del__(self): if self.emulator_log: self.emulator_log.close() def is_running(self): """ Returns True if the Android emulator is running. """ for proc in psutil.process_iter(): name = proc.name() # On some platforms, "emulator" may start an emulator with # process name "emulator64-arm" or similar. if name and name.startswith('emulator'): return True return False def is_available(self): """ Returns True if an emulator executable is found. """ found = False emulator_path = self._find_sdk_exe('emulator', True) if emulator_path: self.emulator_path = emulator_path found = True return found def check_avd(self, force=False): """ Determine if the AVD is already installed locally. (This is usually used to determine if update_avd() is likely to require a download; it is a convenient way of determining whether a 'this may take a while' warning is warranted.) Returns True if the AVD is installed. """ avd = os.path.join( EMULATOR_HOME_DIR, 'avd', self.avd_info.name + '.avd') if force and os.path.exists(avd): shutil.rmtree(avd) if os.path.exists(avd): _log_debug("AVD found at %s" % avd) return True return False def update_avd(self, force=False): """ If required, update the AVD via tooltool. If the AVD directory is not found, or "force" is requested, download the tooltool manifest associated with the AVD and then invoke tooltool.py on the manifest. tooltool.py will download the required archive (unless already present in the local tooltool cache) and install the AVD. """ avd = os.path.join( EMULATOR_HOME_DIR, 'avd', self.avd_info.name + '.avd') if force and os.path.exists(avd): shutil.rmtree(avd) if not os.path.exists(avd): _download_file(TOOLTOOL_URL, 'tooltool.py', EMULATOR_HOME_DIR) url = '%s/%s' % (TRY_URL, self.avd_info.tooltool_manifest) _download_file(url, 'releng.manifest', EMULATOR_HOME_DIR) _tooltool_fetch() self._update_avd_paths() def start(self): """ Launch the emulator. """ def outputHandler(line): self.emulator_log.write("<%s>\n" % line) env = os.environ env['ANDROID_AVD_HOME'] = os.path.join(EMULATOR_HOME_DIR, "avd") command = [self.emulator_path, "-avd", self.avd_info.name, "-port", "5554"] if self.avd_info.extra_args: command += self.avd_info.extra_args log_path = os.path.join(EMULATOR_HOME_DIR, 'emulator.log') self.emulator_log = open(log_path, 'w') _log_debug("Starting the emulator with this command: %s" % ' '.join(command)) _log_debug("Emulator output will be written to '%s'" % log_path) self.proc = ProcessHandler( command, storeOutput=False, processOutputLine=outputHandler, env=env) self.proc.run() _log_debug("Emulator started with pid %d" % int(self.proc.proc.pid)) def wait_for_start(self): """ Verify that the emulator is running, the emulator device is visible to adb, and Android has booted. """ if not self.proc: _log_warning("Emulator not started!") return False if self.proc.proc.poll() is not None: _log_warning("Emulator has already completed!") return False _log_debug("Waiting for device status...") while(('emulator-5554', 'device') not in self.dm.devices()): time.sleep(10) if self.proc.proc.poll() is not None: _log_warning("Emulator has already completed!") return False _log_debug("Device status verified.") _log_debug("Checking that Android has booted...") complete = False while(not complete): output = '' try: output = self.dm.shellCheckOutput( ['getprop', 'sys.boot_completed'], timeout=5) except DMError: # adb not yet responding...keep trying pass if output.strip() == '1': complete = True else: time.sleep(10) if self.proc.proc.poll() is not None: _log_warning("Emulator has already completed!") return False _log_debug("Android boot status verified.") if not self._verify_emulator(): return False if self.avd_info.uses_sut: if not self._verify_sut(): return False return True def wait(self): """ Wait for the emulator to close. If interrupted, close the emulator. """ try: self.proc.wait() except: if self.proc.poll() is None: self.cleanup() return self.proc.poll() def cleanup(self): """ Close the emulator. """ self.proc.kill(signal.SIGTERM) def get_avd_description(self): """ Return the human-friendly description of this AVD. """ return self.avd_info.description def _update_avd_paths(self): avd_path = os.path.join(EMULATOR_HOME_DIR, "avd") ini_file = os.path.join(avd_path, "test-1.ini") ini_file_new = os.path.join(avd_path, self.avd_info.name + ".ini") os.rename(ini_file, ini_file_new) avd_dir = os.path.join(avd_path, "test-1.avd") avd_dir_new = os.path.join(avd_path, self.avd_info.name + ".avd") os.rename(avd_dir, avd_dir_new) self._replace_ini_contents(ini_file_new) def _replace_ini_contents(self, path): with open(path, "r") as f: lines = f.readlines() with open(path, "w") as f: for line in lines: if line.startswith('path='): avd_path = os.path.join(EMULATOR_HOME_DIR, "avd") f.write('path=%s/%s.avd\n' % (avd_path, self.avd_info.name)) elif line.startswith('path.rel='): f.write('path.rel=avd/%s.avd\n' % self.avd_info.name) else: f.write(line) def _telnet_cmd(self, telnet, command): _log_debug(">>> " + command) telnet.write('%s\n' % command) result = telnet.read_until('OK', 10) _log_debug("<<< " + result) return result def _verify_emulator(self): telnet_ok = False tn = None while(not telnet_ok): try: tn = telnetlib.Telnet('localhost', self.avd_info.port, 10) if tn is not None: res = tn.read_until('OK', 10) self._telnet_cmd(tn, 'avd status') if self.avd_info.uses_sut: cmd = 'redir add tcp:%s:%s' % \ (str(self.avd_info.sut_port), str(self.avd_info.sut_port)) self._telnet_cmd(tn, cmd) cmd = 'redir add tcp:%s:%s' % \ (str(self.avd_info.sut_port2), str(self.avd_info.sut_port2)) self._telnet_cmd(tn, cmd) self._telnet_cmd(tn, 'redir list') self._telnet_cmd(tn, 'network status') tn.write('quit\n') tn.read_all() telnet_ok = True else: _log_warning("Unable to connect to port %d" % port) except: _log_warning("Trying again after unexpected exception") finally: if tn is not None: tn.close() if not telnet_ok: time.sleep(10) if self.proc.proc.poll() is not None: _log_warning("Emulator has already completed!") return False return telnet_ok def _verify_sut(self): sut_ok = False while(not sut_ok): try: tn = telnetlib.Telnet('localhost', self.avd_info.sut_port, 10) if tn is not None: _log_debug( "Connected to port %d" % self.avd_info.sut_port) res = tn.read_until('$>', 10) if res.find('$>') == -1: _log_debug("Unexpected SUT response: %s" % res) else: _log_debug("SUT response: %s" % res) sut_ok = True tn.write('quit\n') tn.read_all() except: _log_debug("Caught exception while verifying sutagent") finally: if tn is not None: tn.close() if not sut_ok: time.sleep(10) if self.proc.proc.poll() is not None: _log_warning("Emulator has already completed!") return False return sut_ok def _get_avd_type(self, requested): if requested in AVD_DICT.keys(): return requested if self.substs: if not self.substs['TARGET_CPU'].startswith('arm'): return 'x86' if self.substs['MOZ_ANDROID_MIN_SDK_VERSION'] == '9': return '2.3' return '4.3' def _find_sdk_exe(self, exe, tools): if tools: subdir = 'tools' var = 'ANDROID_TOOLS' else: subdir = 'platform-tools' var = 'ANDROID_PLATFORM_TOOLS' found = False # Can exe be found in the Android SDK? try: android_sdk_root = os.environ['ANDROID_SDK_ROOT'] exe_path = os.path.join( android_sdk_root, subdir, exe) if os.path.exists(exe_path): found = True else: _log_debug( "Unable to find executable at %s" % exe_path) except KeyError: _log_debug("ANDROID_SDK_ROOT not set") if not found and self.substs: # Can exe be found in ANDROID_TOOLS/ANDROID_PLATFORM_TOOLS? try: exe_path = os.path.join( self.substs[var], exe) if os.path.exists(exe_path): found = True else: _log_debug( "Unable to find executable at %s" % exe_path) except KeyError: _log_debug("%s not set" % var) if not found: # Can exe be found in the default bootstrap location? mozbuild_path = os.environ.get('MOZBUILD_STATE_PATH', os.path.expanduser(os.path.join('~', '.mozbuild'))) exe_path = os.path.join( mozbuild_path, 'android-sdk-linux', subdir, exe) if os.path.exists(exe_path): found = True else: _log_debug( "Unable to find executable at %s" % exe_path) if not found: # Is exe on PATH? exe_path = find_executable(exe) if exe_path: found = True else: _log_debug("Unable to find executable on PATH") if found: _log_debug("%s found at %s" % (exe, exe_path)) else: exe_path = None return exe_path
class ServoWebDriverBrowser(Browser): used_ports = set() def __init__(self, logger, binary, debug_info=None, webdriver_host="127.0.0.1", user_stylesheets=None): Browser.__init__(self, logger) self.binary = binary self.webdriver_host = webdriver_host self.webdriver_port = None self.proc = None self.debug_info = debug_info self.hosts_path = make_hosts_file() self.command = None self.user_stylesheets = user_stylesheets if user_stylesheets else [] def start(self): self.webdriver_port = get_free_port(4444, exclude=self.used_ports) self.used_ports.add(self.webdriver_port) env = os.environ.copy() env["HOST_FILE"] = self.hosts_path env["RUST_BACKTRACE"] = "1" debug_args, command = browser_command(self.binary, [ "--hard-fail", "--webdriver", str(self.webdriver_port), "about:blank", ], self.debug_info) for stylesheet in self.user_stylesheets: command += ["--user-stylesheet", stylesheet] self.command = command self.command = debug_args + self.command if not self.debug_info or not self.debug_info.interactive: self.proc = ProcessHandler(self.command, processOutputLine=[self.on_output], env=env, storeOutput=False) self.proc.run() else: self.proc = subprocess.Popen(self.command, env=env) self.logger.debug("Servo Started") def stop(self, force=False): self.logger.debug("Stopping browser") if self.proc is not None: try: self.proc.kill() except OSError: # This can happen on Windows if the process is already dead pass def pid(self): if self.proc is None: return None try: return self.proc.pid except AttributeError: return None def on_output(self, line): """Write a line of output from the process to the log""" self.logger.process_output(self.pid(), line.decode("utf8", "replace"), command=" ".join(self.command)) def is_alive(self): if self.runner: return self.runner.is_running() return False def cleanup(self): self.stop() def executor_browser(self): assert self.webdriver_port is not None return ExecutorBrowser, { "webdriver_host": self.webdriver_host, "webdriver_port": self.webdriver_port }
class ServoRefTestExecutor(ProcessTestExecutor): convert_result = reftest_result_converter def __init__(self, browser, server_config, binary=None, timeout_multiplier=1, screenshot_cache=None, debug_info=None, pause_after_test=False): ProcessTestExecutor.__init__(self, browser, server_config, timeout_multiplier=timeout_multiplier, debug_info=debug_info) self.protocol = Protocol(self, browser) self.screenshot_cache = screenshot_cache self.implementation = RefTestImplementation(self) self.tempdir = tempfile.mkdtemp() self.hosts_path = make_hosts_file() def teardown(self): try: os.unlink(self.hosts_path) except OSError: pass os.rmdir(self.tempdir) ProcessTestExecutor.teardown(self) def screenshot(self, test): full_url = self.test_url(test) with TempFilename(self.tempdir) as output_path: self.command = [self.binary, "--cpu", "--hard-fail", "--exit", "-Z", "disable-text-aa", "--output=%s" % output_path, full_url] env = os.environ.copy() env["HOST_FILE"] = self.hosts_path self.proc = ProcessHandler(self.command, processOutputLine=[self.on_output], env=env) try: self.proc.run() timeout = test.timeout * self.timeout_multiplier + 5 rv = self.proc.wait(timeout=timeout) except KeyboardInterrupt: self.proc.kill() raise if rv is None: self.proc.kill() return False, ("EXTERNAL-TIMEOUT", None) if rv != 0 or not os.path.exists(output_path): return False, ("CRASH", None) with open(output_path) as f: # Might need to strip variable headers or something here data = f.read() return True, base64.b64encode(data) def do_test(self, test): result = self.implementation.run_test(test) return self.convert_result(test, result) def on_output(self, line): line = line.decode("utf8", "replace") if self.interactive: print line else: self.logger.process_output(self.proc.pid, line, " ".join(self.command))
class Mitmproxy(Playback): def __init__(self, config): self.config = config self.host = ("127.0.0.1" if "localhost" in self.config["host"] else self.config["host"]) self.port = None self.mitmproxy_proc = None self.mitmdump_path = None self.record_mode = config.get("record", False) self.recording = None self.playback_files = [] self.browser_path = "" if config.get("binary", None): self.browser_path = os.path.normpath(config.get("binary")) self.policies_dir = None self.ignore_mitmdump_exit_failure = config.get( "ignore_mitmdump_exit_failure", False) if self.record_mode: if "recording_file" not in self.config: LOG.error( "recording_file value was not provided. Proxy service wont' start " ) raise Exception("Please provide a playback_files list.") if not isinstance(self.config.get("recording_file"), six.string_types): LOG.error("recording_file argument type is not str!") raise Exception("recording_file argument type invalid!") if not os.path.splitext( self.config.get("recording_file"))[1] == ".zip": LOG.error("Recording file type (%s) should be a zip. " "Please provide a valid file type!" % self.config.get("recording_file")) raise Exception("Recording file type should be a zip") if os.path.exists(self.config.get("recording_file")): LOG.error("Recording file (%s) already exists." "Please provide a valid file path!" % self.config.get("recording_file")) raise Exception("Recording file already exists.") if self.config.get("playback_files", False): LOG.error( "Record mode is True and playback_files where provided!") raise Exception("playback_files specified during record!") if self.config.get("playback_version") is None: LOG.error("mitmproxy was not provided with a 'playback_version' " "Please provide a valid playback version") raise Exception("playback_version not specified!") # mozproxy_dir is where we will download all mitmproxy required files # when running locally it comes from obj_path via mozharness/mach if self.config.get("obj_path") is not None: self.mozproxy_dir = self.config.get("obj_path") else: # in production it is ../tasks/task_N/build/, in production that dir # is not available as an envvar, however MOZ_UPLOAD_DIR is set as # ../tasks/task_N/build/blobber_upload_dir so take that and go up 1 level self.mozproxy_dir = os.path.dirname( os.path.dirname(os.environ["MOZ_UPLOAD_DIR"])) self.mozproxy_dir = os.path.join(self.mozproxy_dir, "testing", "mozproxy") self.upload_dir = os.environ.get("MOZ_UPLOAD_DIR", self.mozproxy_dir) LOG.info( "mozproxy_dir used for mitmproxy downloads and exe files: %s" % self.mozproxy_dir) # setting up the MOZPROXY_DIR env variable so custom scripts know # where to get the data os.environ["MOZPROXY_DIR"] = self.mozproxy_dir LOG.info("Playback tool: %s" % self.config["playback_tool"]) LOG.info("Playback tool version: %s" % self.config["playback_version"]) def download_mitm_bin(self): # Download and setup mitm binaries manifest = os.path.join( here, "manifests", "mitmproxy-rel-bin-%s-{platform}.manifest" % self.config["playback_version"], ) transformed_manifest = transform_platform(manifest, self.config["platform"]) # generate the mitmdump_path self.mitmdump_path = os.path.normpath( os.path.join( self.mozproxy_dir, "mitmdump-%s" % self.config["playback_version"], "mitmdump", )) # Check if mitmproxy bin exists if os.path.exists(self.mitmdump_path): LOG.info("mitmproxy binary already exists. Skipping download") else: # Download and unpack mitmproxy binary download_path = os.path.dirname(self.mitmdump_path) LOG.info("create mitmproxy %s dir" % self.config["playback_version"]) if not os.path.exists(download_path): os.makedirs(download_path) LOG.info("downloading mitmproxy binary") tooltool_download(transformed_manifest, self.config["run_local"], download_path) def download_manifest_file(self, manifest_path): # Manifest File # we use one pageset for all platforms LOG.info("downloading mitmproxy pageset") tooltool_download(manifest_path, self.config["run_local"], self.mozproxy_dir) with open(manifest_path) as manifest_file: manifest = json.load(manifest_file) for file in manifest: zip_path = os.path.join(self.mozproxy_dir, file["filename"]) LOG.info("Adding %s to recording list" % zip_path) self.playback_files.append(RecordingFile(zip_path)) def download_playback_files(self): # Detect type of file from playback_files and download accordingly if "playback_files" not in self.config: LOG.error( "playback_files value was not provided. Proxy service wont' start " ) raise Exception("Please provide a playback_files list.") if not isinstance(self.config["playback_files"], list): LOG.error("playback_files should be a list") raise Exception("playback_files should be a list") for playback_file in self.config["playback_files"]: if playback_file.startswith( "https://") and "mozilla.com" in playback_file: # URL provided dest = os.path.join(self.mozproxy_dir, os.path.basename(playback_file)) download_file_from_url(playback_file, self.mozproxy_dir, extract=False) # Add Downloaded file to playback_files list LOG.info("Adding %s to recording list" % dest) self.playback_files.append(RecordingFile(dest)) continue if not os.path.exists(playback_file): LOG.error( "Zip or manifest file path (%s) does not exist. Please provide a valid path!" % playback_file) raise Exception("Zip or manifest file path does not exist") if os.path.splitext(playback_file)[1] == ".zip": # zip file path provided LOG.info("Adding %s to recording list" % playback_file) self.playback_files.append(RecordingFile(playback_file)) elif os.path.splitext(playback_file)[1] == ".manifest": # manifest file path provided self.download_manifest_file(playback_file) def download(self): """Download and unpack mitmproxy binary and pageset using tooltool""" if not os.path.exists(self.mozproxy_dir): os.makedirs(self.mozproxy_dir) self.download_mitm_bin() if self.record_mode: self.recording = RecordingFile(self.config["recording_file"]) else: self.download_playback_files() def stop(self): LOG.info("Mitmproxy stop!!") self.stop_mitmproxy_playback() if self.record_mode: LOG.info("Record mode ON. Generating zip file ") self.recording.generate_zip_file() def wait(self, timeout=1): """Wait until the mitmproxy process has terminated.""" # We wait using this method to allow Windows to respond to the Ctrl+Break # signal so that we can exit cleanly from the command-line driver. while True: returncode = self.mitmproxy_proc.wait(timeout) if returncode is not None: return returncode def start(self): # go ahead and download and setup mitmproxy self.download() # mitmproxy must be started before setup, so that the CA cert is available self.start_mitmproxy(self.mitmdump_path, self.browser_path) # In case the setup fails, we want to stop the process before raising. try: self.setup() except Exception: try: self.stop() except Exception: LOG.error("MitmProxy failed to STOP.", exc_info=True) LOG.error("Setup of MitmProxy failed.", exc_info=True) raise def start_mitmproxy(self, mitmdump_path, browser_path): """Startup mitmproxy and replay the specified flow file""" if self.mitmproxy_proc is not None: raise Exception("Proxy already started.") self.port = get_available_port() LOG.info("mitmdump path: %s" % mitmdump_path) LOG.info("browser path: %s" % browser_path) # mitmproxy needs some DLL's that are a part of Firefox itself, so add to path env = os.environ.copy() env["PATH"] = os.path.dirname(browser_path) + os.pathsep + env["PATH"] command = [mitmdump_path] if self.config.get("verbose", False): # Generate mitmproxy verbose logs command.extend(["-v"]) # add proxy host and port options command.extend( ["--listen-host", self.host, "--listen-port", str(self.port)]) # record mode if self.record_mode: # generate recording script paths inject_deterministic = os.path.join( mitm_folder, "scripts", "inject-deterministic.py", ) http_protocol_extractor = os.path.join( mitm_folder, "scripts", "http_protocol_extractor.py", ) args = [ "--save-stream-file", normalize_path(self.recording.recording_path), "--set", "websocket=false", "--scripts", inject_deterministic, "--scripts", http_protocol_extractor, ] command.extend(args) self.recording.set_metadata("proxy_version", self.config["playback_version"]) else: # playback mode if len(self.playback_files) > 0: script = os.path.join( mitm_folder, "scripts", "alternate-server-replay.py", ) if self.config["playback_version"] in [ "4.0.4", "5.1.1", "6.0.2" ]: args = [ "--set", "upstream_cert=false", "--set", "upload_dir=" + normalize_path(self.upload_dir), "--set", "websocket=false", "--set", "server_replay_files={}".format(",".join([ normalize_path(playback_file.recording_path) for playback_file in self.playback_files ])), "--scripts", normalize_path(script), ] command.extend(args) else: raise Exception("Mitmproxy version is unknown!") else: raise Exception( "Mitmproxy can't start playback! Playback settings missing." ) # mitmproxy needs some DLL's that are a part of Firefox itself, so add to path env = os.environ.copy() if not os.path.dirname(self.browser_path) in env["PATH"]: env["PATH"] = os.path.dirname( self.browser_path) + os.pathsep + env["PATH"] LOG.info("Starting mitmproxy playback using env path: %s" % env["PATH"]) LOG.info("Starting mitmproxy playback using command: %s" % " ".join(command)) # to turn off mitmproxy log output, use these params for Popen: # Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) self.mitmproxy_proc = ProcessHandler( command, logfile=os.path.join(self.upload_dir, "mitmproxy.log"), env=env, processStderrLine=LOG.error, storeOutput=False, ) self.mitmproxy_proc.run() end_time = time.time() + MITMDUMP_COMMAND_TIMEOUT ready = False while time.time() < end_time: ready = self.check_proxy(host=self.host, port=self.port) if ready: LOG.info( "Mitmproxy playback successfully started on %s:%d as pid %d" % (self.host, self.port, self.mitmproxy_proc.pid)) return time.sleep(0.25) # cannot continue as we won't be able to playback the pages LOG.error("Aborting: Mitmproxy process did not startup") self.stop_mitmproxy_playback() sys.exit(1) # XXX why do we need to do that? a raise is not enough? def stop_mitmproxy_playback(self): """Stop the mitproxy server playback""" if self.mitmproxy_proc is None or self.mitmproxy_proc.poll( ) is not None: return LOG.info("Stopping mitmproxy playback, killing process %d" % self.mitmproxy_proc.pid) # On Windows, mozprocess brutally kills mitmproxy with TerminateJobObject # The process has no chance to gracefully shutdown. # Here, we send the process a break event to give it a chance to wrapup. # See the signal handler in the alternate-server-replay-4.0.4.py script if mozinfo.os == "win": LOG.info("Sending CTRL_BREAK_EVENT to mitmproxy") os.kill(self.mitmproxy_proc.pid, signal.CTRL_BREAK_EVENT) time.sleep(2) exit_code = self.mitmproxy_proc.kill() self.mitmproxy_proc = None if exit_code != 0: if exit_code is None: LOG.error("Failed to kill the mitmproxy playback process") return if mozinfo.os == "win": from mozprocess.winprocess import ERROR_CONTROL_C_EXIT # noqa if exit_code == ERROR_CONTROL_C_EXIT: LOG.info( "Successfully killed the mitmproxy playback process" " with exit code %d" % exit_code) return log_func = LOG.error if self.ignore_mitmdump_exit_failure: log_func = LOG.info log_func("Mitmproxy exited with error code %d" % exit_code) else: LOG.info("Successfully killed the mitmproxy playback process") def check_proxy(self, host, port): """Check that mitmproxy process is working by doing a socket call using the proxy settings :param host: Host of the proxy server :param port: Port of the proxy server :return: True if the proxy service is working """ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.connect((host, port)) s.shutdown(socket.SHUT_RDWR) s.close() return True except socket.error: return False
class Emulator(Device): logcat_proc = None port = None proc = None telnet = None def __init__(self, app_ctx, arch, resolution=None, sdcard=None, userdata=None, no_window=None, binary=None, **kwargs): Device.__init__(self, app_ctx, **kwargs) self.arch = ArchContext(arch, self.app_ctx, binary=binary) self.resolution = resolution or '320x480' self.tmpdir = tempfile.mkdtemp() self.sdcard = None if sdcard: self.sdcard = self.create_sdcard(sdcard) self.userdata = tempfile.NamedTemporaryFile(prefix='userdata-qemu', dir=self.tmpdir) self.initdata = userdata if userdata else os.path.join(self.arch.sysdir, 'userdata.img') self.no_window = no_window self.battery = EmulatorBattery(self) self.geo = EmulatorGeo(self) self.screen = EmulatorScreen(self) @property def args(self): """ Arguments to pass into the emulator binary. """ qemu_args = [self.arch.binary, '-kernel', self.arch.kernel, '-sysdir', self.arch.sysdir, '-data', self.userdata.name, '-initdata', self.initdata, '-wipe-data'] if self.no_window: qemu_args.append('-no-window') if self.sdcard: qemu_args.extend(['-sdcard', self.sdcard]) qemu_args.extend(['-memory', '512', '-partition-size', '512', '-verbose', '-skin', self.resolution, '-gpu', 'on', '-qemu'] + self.arch.extra_args) return qemu_args def start(self): """ Starts a new emulator. """ if self.proc: return original_devices = set(self._get_online_devices()) # QEMU relies on atexit() to remove temporary files, which does not # work since mozprocess uses SIGKILL to kill the emulator process. # Use a customized temporary directory so we can clean it up. os.environ['ANDROID_TMP'] = self.tmpdir qemu_log = None qemu_proc_args = {} if self.logdir: # save output from qemu to logfile qemu_log = os.path.join(self.logdir, 'qemu.log') if os.path.isfile(qemu_log): self._rotate_log(qemu_log) qemu_proc_args['logfile'] = qemu_log else: qemu_proc_args['processOutputLine'] = lambda line: None self.proc = ProcessHandler(self.args, **qemu_proc_args) self.proc.run() devices = set(self._get_online_devices()) now = datetime.datetime.now() while (devices - original_devices) == set([]): time.sleep(1) # Sometimes it takes more than 60s to launch emulator, so we # increase timeout value to 180s. Please see bug 1143380. if datetime.datetime.now() - now > datetime.timedelta(seconds=180): raise TimeoutException('timed out waiting for emulator to start') devices = set(self._get_online_devices()) devices = devices - original_devices self.serial = devices.pop() self.connect() def _get_online_devices(self): return set([d[0] for d in self.dm.devices() if d[1] != 'offline' if d[0].startswith('emulator')]) def connect(self): """ Connects to a running device. If no serial was specified in the constructor, defaults to the first entry in `adb devices`. """ if self.connected: return Device.connect(self) self.port = int(self.serial[self.serial.rindex('-')+1:]) self.geo.set_default_location() self.screen.initialize() # setup DNS fix for networking self.app_ctx.dm.shellCheckOutput(['setprop', 'net.dns1', '10.0.2.3']) def create_sdcard(self, sdcard_size): """ Creates an sdcard partition in the emulator. :param sdcard_size: Size of partition to create, e.g '10MB'. """ mksdcard = self.app_ctx.which('mksdcard') path = tempfile.mktemp(prefix='sdcard', dir=self.tmpdir) sdargs = [mksdcard, '-l', 'mySdCard', sdcard_size, path] sd = subprocess.Popen(sdargs, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) retcode = sd.wait() if retcode: raise Exception('unable to create sdcard: exit code %d: %s' % (retcode, sd.stdout.read())) return path def cleanup(self): """ Cleans up and kills the emulator. """ Device.cleanup(self) if self.proc: self.proc.kill() self.proc = None # Remove temporary files self.userdata.close() shutil.rmtree(self.tmpdir) def _get_telnet_response(self, command=None): output = [] assert(self.telnet) if command is not None: self.telnet.write('%s\n' % command) while True: line = self.telnet.read_until('\n') output.append(line.rstrip()) if line.startswith('OK'): return output elif line.startswith('KO:'): raise Exception('bad telnet response: %s' % line) def _run_telnet(self, command): if not self.telnet: self.telnet = Telnet('localhost', self.port) self._get_telnet_response() return self._get_telnet_response(command) def __del__(self): if self.telnet: self.telnet.write('exit\n') self.telnet.read_all()
class BaseEmulator(Device): port = None proc = None telnet = None def __init__(self, app_ctx, **kwargs): self.arch = ArchContext(kwargs.pop('arch', 'arm'), app_ctx, binary=kwargs.pop('binary', None), avd=kwargs.pop('avd', None)) super(BaseEmulator, self).__init__(app_ctx, **kwargs) self.tmpdir = tempfile.mkdtemp() # These rely on telnet self.battery = EmulatorBattery(self) self.geo = EmulatorGeo(self) self.screen = EmulatorScreen(self) @property def args(self): """ Arguments to pass into the emulator binary. """ return [self.arch.binary] def start(self): """ Starts a new emulator. """ if self.proc: return original_devices = set(self._get_online_devices()) # QEMU relies on atexit() to remove temporary files, which does not # work since mozprocess uses SIGKILL to kill the emulator process. # Use a customized temporary directory so we can clean it up. os.environ['ANDROID_TMP'] = self.tmpdir qemu_log = None qemu_proc_args = {} if self.logdir: # save output from qemu to logfile qemu_log = os.path.join(self.logdir, 'qemu.log') if os.path.isfile(qemu_log): self._rotate_log(qemu_log) qemu_proc_args['logfile'] = qemu_log else: qemu_proc_args['processOutputLine'] = lambda line: None self.proc = ProcessHandler(self.args, **qemu_proc_args) self.proc.run() devices = set(self._get_online_devices()) now = datetime.datetime.now() while (devices - original_devices) == set([]): time.sleep(1) # Sometimes it takes more than 60s to launch emulator, so we # increase timeout value to 180s. Please see bug 1143380. if datetime.datetime.now() - now > datetime.timedelta(seconds=180): raise TimeoutException( 'timed out waiting for emulator to start') devices = set(self._get_online_devices()) devices = devices - original_devices self.serial = devices.pop() self.connect() def _get_online_devices(self): return [ d[0] for d in self.dm.devices() if d[1] != 'offline' if d[0].startswith('emulator') ] def connect(self): """ Connects to a running device. If no serial was specified in the constructor, defaults to the first entry in `adb devices`. """ if self.connected: return super(BaseEmulator, self).connect() serial = self.serial or self.dm._deviceSerial self.port = int(serial[serial.rindex('-') + 1:]) def cleanup(self): """ Cleans up and kills the emulator, if it was started by mozrunner. """ super(BaseEmulator, self).cleanup() if self.proc: self.proc.kill() self.proc = None self.connected = False # Remove temporary files if os.path.isdir(self.tmpdir): shutil.rmtree(self.tmpdir) def _get_telnet_response(self, command=None): output = [] assert self.telnet if command is not None: self.telnet.write('%s\n' % command) while True: line = self.telnet.read_until('\n') output.append(line.rstrip()) if line.startswith('OK'): return output elif line.startswith('KO:'): raise Exception('bad telnet response: %s' % line) def _run_telnet(self, command): if not self.telnet: self.telnet = Telnet('localhost', self.port) self._get_telnet_response() return self._get_telnet_response(command) def __del__(self): if self.telnet: self.telnet.write('exit\n') self.telnet.read_all()
def verify_android_device(build_obj, install=False, xre=False, debugger=False): """ Determine if any Android device is connected via adb. If no device is found, prompt to start an emulator. If a device is found or an emulator started and 'install' is specified, also check whether Firefox is installed on the device; if not, prompt to install Firefox. If 'xre' is specified, also check with MOZ_HOST_BIN is set to a valid xre/host-utils directory; if not, prompt to set one up. If 'debugger' is specified, also check that JimDB is installed; if JimDB is not found, prompt to set up JimDB. Returns True if the emulator was started or another device was already connected. """ device_verified = False emulator = AndroidEmulator('*', substs=build_obj.substs) devices = emulator.dm.devices() if (len(devices) > 0) and ('device' in [d[1] for d in devices]): device_verified = True elif emulator.is_available(): response = raw_input( "No Android devices connected. Start an emulator? (Y/n) ").strip() if response.lower().startswith('y') or response == '': if not emulator.check_avd(): _log_info("Fetching AVD. This may take a while...") emulator.update_avd() _log_info("Starting emulator running %s..." % emulator.get_avd_description()) emulator.start() emulator.wait_for_start() device_verified = True if device_verified and install: # Determine if Firefox is installed on the device; if not, # prompt to install. This feature allows a test command to # launch an emulator, install Firefox, and proceed with testing # in one operation. It is also a basic safeguard against other # cases where testing is requested but Firefox installation has # been forgotten. # If Firefox is installed, there is no way to determine whether # the current build is installed, and certainly no way to # determine if the installed build is the desired build. # Installing every time is problematic because: # - it prevents testing against other builds (downloaded apk) # - installation may take a couple of minutes. installed = emulator.dm.shellCheckOutput( ['pm', 'list', 'packages', 'org.mozilla.']) if not 'fennec' in installed and not 'firefox' in installed: response = raw_input( "It looks like Firefox is not installed on this device.\n" "Install Firefox? (Y/n) ").strip() if response.lower().startswith('y') or response == '': _log_info("Installing Firefox. This may take a while...") build_obj._run_make(directory=".", target='install', ensure_exit_code=False) if device_verified and xre: # Check whether MOZ_HOST_BIN has been set to a valid xre; if not, # prompt to install one. xre_path = os.environ.get('MOZ_HOST_BIN') err = None if not xre_path: err = 'environment variable MOZ_HOST_BIN is not set to a directory containing host xpcshell' elif not os.path.isdir(xre_path): err = '$MOZ_HOST_BIN does not specify a directory' elif not os.path.isfile(os.path.join(xre_path, 'xpcshell')): err = '$MOZ_HOST_BIN/xpcshell does not exist' if err: xre_path = glob.glob(os.path.join(EMULATOR_HOME_DIR, 'host-utils*')) for path in xre_path: if os.path.isdir(path) and os.path.isfile( os.path.join(path, 'xpcshell')): os.environ['MOZ_HOST_BIN'] = path err = None break if err: _log_info("Host utilities not found: %s" % err) response = raw_input( "Download and setup your host utilities? (Y/n) ").strip() if response.lower().startswith('y') or response == '': _log_info( "Installing host utilities. This may take a while...") _download_file(TOOLTOOL_URL, 'tooltool.py', EMULATOR_HOME_DIR) host_platform = _get_host_platform() if host_platform: path = os.path.join(MANIFEST_PATH, host_platform, 'hostutils.manifest') _get_tooltool_manifest(build_obj.substs, path, EMULATOR_HOME_DIR, 'releng.manifest') _tooltool_fetch() xre_path = glob.glob( os.path.join(EMULATOR_HOME_DIR, 'host-utils*')) for path in xre_path: if os.path.isdir(path) and os.path.isfile( os.path.join(path, 'xpcshell')): os.environ['MOZ_HOST_BIN'] = path err = None break if err: _log_warning("Unable to install host utilities.") else: _log_warning( "Unable to install host utilities -- your platform is not supported!" ) if debugger: # Optionally set up JimDB. See https://wiki.mozilla.org/Mobile/Fennec/Android/GDB. build_platform = _get_build_platform(build_obj.substs) jimdb_path = os.path.join(EMULATOR_HOME_DIR, 'jimdb-%s' % build_platform) jimdb_utils_path = os.path.join(jimdb_path, 'utils') gdb_path = os.path.join(jimdb_path, 'bin', 'gdb') err = None if not os.path.isdir(jimdb_path): err = '%s does not exist' % jimdb_path elif not os.path.isfile(gdb_path): err = '%s not found' % gdb_path if err: _log_info("JimDB (%s) not found: %s" % (build_platform, err)) response = raw_input("Download and setup JimDB (%s)? (Y/n) " % build_platform).strip() if response.lower().startswith('y') or response == '': host_platform = _get_host_platform() if host_platform: _log_info( "Installing JimDB (%s/%s). This may take a while..." % (host_platform, build_platform)) path = os.path.join(MANIFEST_PATH, host_platform, 'jimdb-%s.manifest' % build_platform) _get_tooltool_manifest(build_obj.substs, path, EMULATOR_HOME_DIR, 'releng.manifest') _tooltool_fetch() if os.path.isfile(gdb_path): # Get JimDB utilities from git repository proc = ProcessHandler(['git', 'pull'], cwd=jimdb_utils_path) proc.run() git_pull_complete = False try: proc.wait() if proc.proc.returncode == 0: git_pull_complete = True except: if proc.poll() is None: proc.kill(signal.SIGTERM) if not git_pull_complete: _log_warning( "Unable to update JimDB utils from git -- some JimDB features may be unavailable." ) else: _log_warning( "Unable to install JimDB -- unable to fetch from tooltool." ) else: _log_warning( "Unable to install JimDB -- your platform is not supported!" ) if os.path.isfile(gdb_path): # sync gdbinit.local with build settings _update_gdbinit(build_obj.substs, os.path.join(jimdb_utils_path, "gdbinit.local")) # ensure JimDB is in system path, so that mozdebug can find it bin_path = os.path.join(jimdb_path, 'bin') os.environ['PATH'] = "%s:%s" % (bin_path, os.environ['PATH']) return device_verified
class ServoTestharnessExecutor(ProcessTestExecutor): convert_result = testharness_result_converter def __init__(self, browser, http_server_url, timeout_multiplier=1, debug_args=None, pause_after_test=False): ProcessTestExecutor.__init__(self, browser, http_server_url, timeout_multiplier=timeout_multiplier, debug_args=debug_args) self.pause_after_test = pause_after_test self.result_data = None self.result_flag = None self.protocol = Protocol(self, browser, http_server_url) def do_test(self, test): self.result_data = None self.result_flag = threading.Event() self.command = [self.binary, "--cpu", "--hard-fail", "-z", urlparse.urljoin(self.http_server_url, test.url)] if self.pause_after_test: self.command.remove("-z") if self.debug_args: self.command = list(self.debug_args) + self.command self.proc = ProcessHandler(self.command, processOutputLine=[self.on_output], onFinish=self.on_finish) self.proc.run() timeout = test.timeout * self.timeout_multiplier # Now wait to get the output we expect, or until we reach the timeout if self.debug_args is None and not self.pause_after_test: wait_timeout = timeout + 5 else: wait_timeout = None self.result_flag.wait(wait_timeout) proc_is_running = True if self.result_flag.is_set() and self.result_data is not None: self.result_data["test"] = test.url result = self.convert_result(test, self.result_data) else: if self.proc.proc.poll() is not None: result = (test.result_cls("CRASH", None), []) proc_is_running = False else: result = (test.result_cls("TIMEOUT", None), []) if proc_is_running: if self.pause_after_test: self.logger.info("Pausing until the browser exits") self.proc.wait() else: self.proc.kill() return result def on_output(self, line): prefix = "ALERT: RESULT: " line = line.decode("utf8", "replace") if line.startswith(prefix): self.result_data = json.loads(line[len(prefix):]) self.result_flag.set() else: if self.interactive: print line else: self.logger.process_output(self.proc.pid, line, " ".join(self.command)) def on_finish(self): self.result_flag.set()
class Mitmproxy(Playback): def __init__(self, config): self.config = config self.host = ("127.0.0.1" if "localhost" in self.config["host"] else self.config["host"]) self.port = None self.mitmproxy_proc = None self.mitmdump_path = None self.browser_path = "" if config.get("binary", None): self.browser_path = os.path.normpath(config.get("binary")) self.policies_dir = None self.ignore_mitmdump_exit_failure = config.get( "ignore_mitmdump_exit_failure", False) self.recording_paths = None if self.config.get("playback_version") is None: LOG.info("mitmproxy was not provided with a 'playback_version' " "Using default playback version: 4.0.4") self.config["playback_version"] = "4.0.4" if self.config.get("playback_binary_manifest") is None: LOG.info( "mitmproxy was not provided with a 'playback_binary_manifest' " "Using default playback_binary_manifest") self.config["playback_binary_manifest"] = ( "mitmproxy-rel-bin-%s-{platform}.manifest" % self.config["playback_version"]) # mozproxy_dir is where we will download all mitmproxy required files # when running locally it comes from obj_path via mozharness/mach if self.config.get("obj_path") is not None: self.mozproxy_dir = self.config.get("obj_path") else: # in production it is ../tasks/task_N/build/, in production that dir # is not available as an envvar, however MOZ_UPLOAD_DIR is set as # ../tasks/task_N/build/blobber_upload_dir so take that and go up 1 level self.mozproxy_dir = os.path.dirname( os.path.dirname(os.environ["MOZ_UPLOAD_DIR"])) self.mozproxy_dir = os.path.join(self.mozproxy_dir, "testing", "mozproxy") self.upload_dir = os.environ.get("MOZ_UPLOAD_DIR", self.mozproxy_dir) LOG.info( "mozproxy_dir used for mitmproxy downloads and exe files: %s" % self.mozproxy_dir) # setting up the MOZPROXY_DIR env variable so custom scripts know # where to get the data os.environ["MOZPROXY_DIR"] = self.mozproxy_dir LOG.info("Playback tool: %s" % self.config["playback_tool"]) LOG.info("Playback tool version: %s" % self.config["playback_version"]) def start(self): # go ahead and download and setup mitmproxy self.download() # mitmproxy must be started before setup, so that the CA cert is available self.start_mitmproxy_playback(self.mitmdump_path, self.browser_path) # In case the setup fails, we want to stop the process before raising. try: self.setup() except Exception: self.stop() raise def download(self): """Download and unpack mitmproxy binary and pageset using tooltool""" if not os.path.exists(self.mozproxy_dir): os.makedirs(self.mozproxy_dir) _manifest = os.path.join(here, self.config["playback_binary_manifest"]) transformed_manifest = transform_platform(_manifest, self.config["platform"]) # generate the mitmdump_path self.mitmdump_path = os.path.normpath( os.path.join( self.mozproxy_dir, "mitmdump-%s" % self.config["playback_version"], "mitmdump", )) # Check if mitmproxy bin exists if os.path.exists(self.mitmdump_path): LOG.info("mitmproxy binary already exists. Skipping download") else: # Download and unpack mitmproxy binary download_path = os.path.dirname(self.mitmdump_path) LOG.info("create mitmproxy %s dir" % self.config["playback_version"]) if not os.path.exists(download_path): os.makedirs(download_path) LOG.info("downloading mitmproxy binary") tooltool_download(transformed_manifest, self.config["run_local"], download_path) if "playback_pageset_manifest" in self.config: # we use one pageset for all platforms LOG.info("downloading mitmproxy pageset") _manifest = self.config["playback_pageset_manifest"] transformed_manifest = transform_platform(_manifest, self.config["platform"]) tooltool_download(transformed_manifest, self.config["run_local"], self.mozproxy_dir) if "playback_artifacts" in self.config: artifacts = self.config["playback_artifacts"].split(",") for artifact in artifacts: artifact = artifact.strip() if not artifact: continue artifact_name = artifact.split("/")[-1] if artifact_name.endswith(".manifest"): tooltool_download(artifact, self.config["run_local"], self.mozproxy_dir) else: dest = os.path.join(self.mozproxy_dir, artifact_name) download_file_from_url(artifact, dest, extract=True) def stop(self): self.stop_mitmproxy_playback() def start_mitmproxy_playback(self, mitmdump_path, browser_path): """Startup mitmproxy and replay the specified flow file""" if self.mitmproxy_proc is not None: raise Exception("Proxy already started.") self.port = get_available_port() LOG.info("mitmdump path: %s" % mitmdump_path) LOG.info("browser path: %s" % browser_path) # mitmproxy needs some DLL's that are a part of Firefox itself, so add to path env = os.environ.copy() env["PATH"] = os.path.dirname(browser_path) + os.pathsep + env["PATH"] command = [mitmdump_path] # add proxy host and port options command.extend( ["--listen-host", self.host, "--listen-port", str(self.port)]) if "playback_tool_args" in self.config: LOG.info("Staring Proxy using provided command line!") command.extend(self.config["playback_tool_args"]) elif "playback_files" in self.config: script = os.path.join( os.path.dirname(os.path.realpath(__file__)), "scripts", "alternate-server-replay.py", ) self.recording_paths = [ normalize_path(recording_path) for recording_path in self.config["playback_files"] ] if self.config["playback_version"] in ["4.0.4", "5.0.1"]: args = [ "-v", "--set", "upstream_cert=false", "--set", "upload_dir=" + normalize_path(self.upload_dir), "--set", "websocket=false", "--set", "server_replay_files={}".format(",".join( self.recording_paths)), "--scripts", normalize_path(script), ] command.extend(args) else: raise Exception("Mitmproxy version is unknown!") else: raise Exception( "Mitmproxy can't start playback! Playback settings missing.") LOG.info("Starting mitmproxy playback using env path: %s" % env["PATH"]) LOG.info("Starting mitmproxy playback using command: %s" % " ".join(command)) # to turn off mitmproxy log output, use these params for Popen: # Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) self.mitmproxy_proc = ProcessHandler( command, logfile=os.path.join(self.upload_dir, "mitmproxy.log"), env=env, processStderrLine=LOG.error, storeOutput=False, ) self.mitmproxy_proc.run() end_time = time.time() + MITMDUMP_COMMAND_TIMEOUT ready = False while time.time() < end_time: ready = self.check_proxy(host=self.host, port=self.port) if ready: LOG.info( "Mitmproxy playback successfully started on %s:%d as pid %d" % (self.host, self.port, self.mitmproxy_proc.pid)) return time.sleep(0.25) # cannot continue as we won't be able to playback the pages LOG.error("Aborting: Mitmproxy process did not startup") self.stop_mitmproxy_playback() sys.exit() # XXX why do we need to do that? a raise is not enough? def stop_mitmproxy_playback(self): """Stop the mitproxy server playback""" if self.mitmproxy_proc is None or self.mitmproxy_proc.poll( ) is not None: return LOG.info("Stopping mitmproxy playback, killing process %d" % self.mitmproxy_proc.pid) # On Windows, mozprocess brutally kills mitmproxy with TerminateJobObject # The process has no chance to gracefully shutdown. # Here, we send the process a break event to give it a chance to wrapup. # See the signal handler in the alternate-server-replay-4.0.4.py script if mozinfo.os == "win": LOG.info("Sending CTRL_BREAK_EVENT to mitmproxy") os.kill(self.mitmproxy_proc.pid, signal.CTRL_BREAK_EVENT) time.sleep(2) exit_code = self.mitmproxy_proc.kill() self.mitmproxy_proc = None if exit_code != 0: if exit_code is None: LOG.error("Failed to kill the mitmproxy playback process") return if mozinfo.os == "win": from mozprocess.winprocess import ERROR_CONTROL_C_EXIT # noqa if exit_code == ERROR_CONTROL_C_EXIT: LOG.info( "Successfully killed the mitmproxy playback process" " with exit code %d" % exit_code) return log_func = LOG.error if self.ignore_mitmdump_exit_failure: log_func = LOG.info log_func("Mitmproxy exited with error code %d" % exit_code) else: LOG.info("Successfully killed the mitmproxy playback process") def check_proxy(self, host, port): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.connect((host, port)) s.shutdown(socket.SHUT_RDWR) s.close() return True except socket.error: return False def confidence(self): file_name = "mitm_netlocs_%s.json" % os.path.splitext( os.path.basename(self.recording_paths[0]))[0] path = os.path.normpath(os.path.join(self.upload_dir, file_name)) if os.path.exists(path): try: LOG.info("Reading confidence values from: %s" % path) with open(path, "r") as f: data = json.load(f) return { "confidence": data["confidence"], "not-replayed": data["not-replayed"], "replayed": data["replayed"] } except Exception: LOG.info("Can't read netlocs file!", exc_info=True) return None else: LOG.info("Netlocs file is not available! Cant find %s" % path) return None
class AndroidEmulator(object): """ Support running the Android emulator with an AVD from Mozilla test automation. Example usage: emulator = AndroidEmulator() if not emulator.is_running() and emulator.is_available(): if not emulator.check_avd(): warn("this may take a while...") emulator.update_avd() emulator.start() emulator.wait_for_start() emulator.wait() """ def __init__(self, avd_type='4.3', verbose=False, substs=None, device_serial=None): global verbose_logging self.emulator_log = None self.emulator_path = 'emulator' verbose_logging = verbose self.substs = substs self.avd_type = self._get_avd_type(avd_type) self.avd_info = AVD_DICT[self.avd_type] self.gpu = True self.restarted = False adb_path = _find_sdk_exe(substs, 'adb', False) if not adb_path: adb_path = 'adb' self.dm = DeviceManagerADB(autoconnect=False, adbPath=adb_path, retryLimit=1, deviceSerial=device_serial) self.dm.default_timeout = 10 _log_debug("Running on %s" % platform.platform()) _log_debug("Emulator created with type %s" % self.avd_type) def __del__(self): if self.emulator_log: self.emulator_log.close() def is_running(self): """ Returns True if the Android emulator is running. """ for proc in psutil.process_iter(): name = proc.name() # On some platforms, "emulator" may start an emulator with # process name "emulator64-arm" or similar. if name and name.startswith('emulator'): return True return False def is_available(self): """ Returns True if an emulator executable is found. """ found = False emulator_path = _find_sdk_exe(self.substs, 'emulator', True) if emulator_path: self.emulator_path = emulator_path found = True return found def check_avd(self, force=False): """ Determine if the AVD is already installed locally. (This is usually used to determine if update_avd() is likely to require a download; it is a convenient way of determining whether a 'this may take a while' warning is warranted.) Returns True if the AVD is installed. """ avd = os.path.join( EMULATOR_HOME_DIR, 'avd', self.avd_info.name + '.avd') if force and os.path.exists(avd): shutil.rmtree(avd) if os.path.exists(avd): _log_debug("AVD found at %s" % avd) return True return False def update_avd(self, force=False): """ If required, update the AVD via tooltool. If the AVD directory is not found, or "force" is requested, download the tooltool manifest associated with the AVD and then invoke tooltool.py on the manifest. tooltool.py will download the required archive (unless already present in the local tooltool cache) and install the AVD. """ avd = os.path.join( EMULATOR_HOME_DIR, 'avd', self.avd_info.name + '.avd') ini_file = os.path.join( EMULATOR_HOME_DIR, 'avd', self.avd_info.name + '.ini') if force and os.path.exists(avd): shutil.rmtree(avd) if not os.path.exists(avd): if os.path.exists(ini_file): os.remove(ini_file) path = self.avd_info.tooltool_manifest _get_tooltool_manifest(self.substs, path, EMULATOR_HOME_DIR, 'releng.manifest') _tooltool_fetch() self._update_avd_paths() def start(self): """ Launch the emulator. """ if os.path.exists(EMULATOR_AUTH_FILE): os.remove(EMULATOR_AUTH_FILE) _log_debug("deleted %s" % EMULATOR_AUTH_FILE) # create an empty auth file to disable emulator authentication auth_file = open(EMULATOR_AUTH_FILE, 'w') auth_file.close() def outputHandler(line): self.emulator_log.write("<%s>\n" % line) if "Invalid value for -gpu" in line or "Invalid GPU mode" in line: self.gpu = False env = os.environ env['ANDROID_AVD_HOME'] = os.path.join(EMULATOR_HOME_DIR, "avd") command = [self.emulator_path, "-avd", self.avd_info.name, "-port", "5554"] if self.gpu: command += ['-gpu', 'swiftshader'] if self.avd_info.extra_args: # -enable-kvm option is not valid on OSX if _get_host_platform() == 'macosx64' and '-enable-kvm' in self.avd_info.extra_args: self.avd_info.extra_args.remove('-enable-kvm') command += self.avd_info.extra_args log_path = os.path.join(EMULATOR_HOME_DIR, 'emulator.log') self.emulator_log = open(log_path, 'w') _log_debug("Starting the emulator with this command: %s" % ' '.join(command)) _log_debug("Emulator output will be written to '%s'" % log_path) self.proc = ProcessHandler( command, storeOutput=False, processOutputLine=outputHandler, env=env) self.proc.run() _log_debug("Emulator started with pid %d" % int(self.proc.proc.pid)) def wait_for_start(self): """ Verify that the emulator is running, the emulator device is visible to adb, and Android has booted. """ if not self.proc: _log_warning("Emulator not started!") return False if self.check_completed(): return False _log_debug("Waiting for device status...") while(('emulator-5554', 'device') not in self.dm.devices()): time.sleep(10) if self.check_completed(): return False _log_debug("Device status verified.") _log_debug("Checking that Android has booted...") complete = False while(not complete): output = '' try: output = self.dm.shellCheckOutput( ['getprop', 'sys.boot_completed'], timeout=5) except DMError: # adb not yet responding...keep trying pass if output.strip() == '1': complete = True else: time.sleep(10) if self.check_completed(): return False _log_debug("Android boot status verified.") if not self._verify_emulator(): return False if self.avd_info.x86: _log_info("Running the x86 emulator; be sure to install an x86 APK!") else: _log_info("Running the arm emulator; be sure to install an arm APK!") return True def check_completed(self): if self.proc.proc.poll() is not None: if not self.gpu and not self.restarted: _log_warning("Emulator failed to start. Your emulator may be out of date.") _log_warning("Trying to restart the emulator without -gpu argument.") self.restarted = True self.start() return False _log_warning("Emulator has already completed!") log_path = os.path.join(EMULATOR_HOME_DIR, 'emulator.log') _log_warning("See log at %s and/or use --verbose for more information." % log_path) return True return False def wait(self): """ Wait for the emulator to close. If interrupted, close the emulator. """ try: self.proc.wait() except: if self.proc.poll() is None: self.cleanup() return self.proc.poll() def cleanup(self): """ Close the emulator. """ self.proc.kill(signal.SIGTERM) def get_avd_description(self): """ Return the human-friendly description of this AVD. """ return self.avd_info.description def _update_avd_paths(self): avd_path = os.path.join(EMULATOR_HOME_DIR, "avd") ini_file = os.path.join(avd_path, "test-1.ini") ini_file_new = os.path.join(avd_path, self.avd_info.name + ".ini") os.rename(ini_file, ini_file_new) avd_dir = os.path.join(avd_path, "test-1.avd") avd_dir_new = os.path.join(avd_path, self.avd_info.name + ".avd") os.rename(avd_dir, avd_dir_new) self._replace_ini_contents(ini_file_new) def _replace_ini_contents(self, path): with open(path, "r") as f: lines = f.readlines() with open(path, "w") as f: for line in lines: if line.startswith('path='): avd_path = os.path.join(EMULATOR_HOME_DIR, "avd") f.write('path=%s/%s.avd\n' % (avd_path, self.avd_info.name)) elif line.startswith('path.rel='): f.write('path.rel=avd/%s.avd\n' % self.avd_info.name) else: f.write(line) def _telnet_cmd(self, telnet, command): _log_debug(">>> " + command) telnet.write('%s\n' % command) result = telnet.read_until('OK', 10) _log_debug("<<< " + result) return result def _verify_emulator(self): telnet_ok = False tn = None while(not telnet_ok): try: tn = telnetlib.Telnet('localhost', 5554, 10) if tn is not None: tn.read_until('OK', 10) self._telnet_cmd(tn, 'avd status') self._telnet_cmd(tn, 'redir list') self._telnet_cmd(tn, 'network status') tn.write('quit\n') tn.read_all() telnet_ok = True else: _log_warning("Unable to connect to port 5554") except: _log_warning("Trying again after unexpected exception") finally: if tn is not None: tn.close() if not telnet_ok: time.sleep(10) if self.proc.proc.poll() is not None: _log_warning("Emulator has already completed!") return False return telnet_ok def _get_avd_type(self, requested): if requested in AVD_DICT.keys(): return requested if self.substs: if not self.substs['TARGET_CPU'].startswith('arm'): return 'x86' return '4.3'
class ServoRefTestExecutor(ProcessTestExecutor): convert_result = reftest_result_converter def __init__(self, browser, server_config, binary=None, timeout_multiplier=1, screenshot_cache=None, debug_info=None, pause_after_test=False, **kwargs): ProcessTestExecutor.__init__(self, browser, server_config, timeout_multiplier=timeout_multiplier, debug_info=debug_info) self.protocol = ConnectionlessProtocol(self, browser) self.screenshot_cache = screenshot_cache self.implementation = RefTestImplementation(self) self.tempdir = tempfile.mkdtemp() self.hosts_path = write_hosts_file(server_config) def teardown(self): try: os.unlink(self.hosts_path) except OSError: pass os.rmdir(self.tempdir) ProcessTestExecutor.teardown(self) def screenshot(self, test, viewport_size, dpi): full_url = self.test_url(test) with TempFilename(self.tempdir) as output_path: debug_args, command = browser_command(self.binary, [ "--hard-fail", "--exit", "-u", "Servo/wptrunner", "-Z", "disable-text-aa,load-webfonts-synchronously,replace-surrogates", "--output=%s" % output_path, full_url ] + self.browser.binary_args, self.debug_info) for stylesheet in self.browser.user_stylesheets: command += ["--user-stylesheet", stylesheet] for pref, value in test.environment.get('prefs', {}).iteritems(): command += ["--pref", "%s=%s" % (pref, value)] command += ["--resolution", viewport_size or "800x600"] if self.browser.ca_certificate_path: command += [ "--certificate-path", self.browser.ca_certificate_path ] if dpi: command += ["--device-pixel-ratio", dpi] # Run ref tests in headless mode command += ["-z"] self.command = debug_args + command env = os.environ.copy() env["HOST_FILE"] = self.hosts_path env["RUST_BACKTRACE"] = "1" if not self.interactive: self.proc = ProcessHandler(self.command, processOutputLine=[self.on_output], env=env) try: self.proc.run() timeout = test.timeout * self.timeout_multiplier + 5 rv = self.proc.wait(timeout=timeout) except KeyboardInterrupt: self.proc.kill() raise else: self.proc = subprocess.Popen(self.command, env=env) try: rv = self.proc.wait() except KeyboardInterrupt: self.proc.kill() raise if rv is None: self.proc.kill() return False, ("EXTERNAL-TIMEOUT", None) if rv != 0 or not os.path.exists(output_path): return False, ("CRASH", None) with open(output_path) as f: # Might need to strip variable headers or something here data = f.read() return True, base64.b64encode(data) def do_test(self, test): result = self.implementation.run_test(test) return self.convert_result(test, result) def on_output(self, line): line = line.decode("utf8", "replace") if self.interactive: print(line) else: self.logger.process_output(self.proc.pid, line, " ".join(self.command))
def verify_android_device(build_obj, install=False, xre=False, debugger=False, verbose=False): """ Determine if any Android device is connected via adb. If no device is found, prompt to start an emulator. If a device is found or an emulator started and 'install' is specified, also check whether Firefox is installed on the device; if not, prompt to install Firefox. If 'xre' is specified, also check with MOZ_HOST_BIN is set to a valid xre/host-utils directory; if not, prompt to set one up. If 'debugger' is specified, also check that JimDB is installed; if JimDB is not found, prompt to set up JimDB. Returns True if the emulator was started or another device was already connected. """ device_verified = False emulator = AndroidEmulator('*', substs=build_obj.substs, verbose=verbose) devices = emulator.dm.devices() if (len(devices) > 0) and ('device' in [d[1] for d in devices]): device_verified = True elif emulator.is_available(): response = raw_input( "No Android devices connected. Start an emulator? (Y/n) ").strip() if response.lower().startswith('y') or response == '': if not emulator.check_avd(): _log_info("Fetching AVD. This may take a while...") emulator.update_avd() _log_info("Starting emulator running %s..." % emulator.get_avd_description()) emulator.start() emulator.wait_for_start() device_verified = True if device_verified and install: # Determine if Firefox is installed on the device; if not, # prompt to install. This feature allows a test command to # launch an emulator, install Firefox, and proceed with testing # in one operation. It is also a basic safeguard against other # cases where testing is requested but Firefox installation has # been forgotten. # If Firefox is installed, there is no way to determine whether # the current build is installed, and certainly no way to # determine if the installed build is the desired build. # Installing every time is problematic because: # - it prevents testing against other builds (downloaded apk) # - installation may take a couple of minutes. installed = emulator.dm.shellCheckOutput(['pm', 'list', 'packages', 'org.mozilla.']) if 'fennec' not in installed and 'firefox' not in installed: response = raw_input( "It looks like Firefox is not installed on this device.\n" "Install Firefox? (Y/n) ").strip() if response.lower().startswith('y') or response == '': _log_info("Installing Firefox. This may take a while...") build_obj._run_make(directory=".", target='install', ensure_exit_code=False) if device_verified and xre: # Check whether MOZ_HOST_BIN has been set to a valid xre; if not, # prompt to install one. xre_path = os.environ.get('MOZ_HOST_BIN') err = None if not xre_path: err = "environment variable MOZ_HOST_BIN is not set to a directory" \ "containing host xpcshell" elif not os.path.isdir(xre_path): err = '$MOZ_HOST_BIN does not specify a directory' elif not os.path.isfile(os.path.join(xre_path, 'xpcshell')): err = '$MOZ_HOST_BIN/xpcshell does not exist' if err: xre_path = glob.glob(os.path.join(EMULATOR_HOME_DIR, 'host-utils*')) for path in xre_path: if os.path.isdir(path) and os.path.isfile(os.path.join(path, 'xpcshell')): os.environ['MOZ_HOST_BIN'] = path err = None break if err: _log_info("Host utilities not found: %s" % err) response = raw_input( "Download and setup your host utilities? (Y/n) ").strip() if response.lower().startswith('y') or response == '': _log_info("Installing host utilities. This may take a while...") host_platform = _get_host_platform() if host_platform: path = os.path.join(MANIFEST_PATH, host_platform, 'hostutils.manifest') _get_tooltool_manifest(build_obj.substs, path, EMULATOR_HOME_DIR, 'releng.manifest') _tooltool_fetch() xre_path = glob.glob(os.path.join(EMULATOR_HOME_DIR, 'host-utils*')) for path in xre_path: if os.path.isdir(path) and os.path.isfile(os.path.join(path, 'xpcshell')): os.environ['MOZ_HOST_BIN'] = path err = None break if err: _log_warning("Unable to install host utilities.") else: _log_warning( "Unable to install host utilities -- your platform is not supported!") if debugger: # Optionally set up JimDB. See https://wiki.mozilla.org/Mobile/Fennec/Android/GDB. build_platform = _get_device_platform(build_obj.substs) jimdb_path = os.path.join(EMULATOR_HOME_DIR, 'jimdb-%s' % build_platform) jimdb_utils_path = os.path.join(jimdb_path, 'utils') gdb_path = os.path.join(jimdb_path, 'bin', 'gdb') err = None if not os.path.isdir(jimdb_path): err = '%s does not exist' % jimdb_path elif not os.path.isfile(gdb_path): err = '%s not found' % gdb_path if err: _log_info("JimDB (%s) not found: %s" % (build_platform, err)) response = raw_input( "Download and setup JimDB (%s)? (Y/n) " % build_platform).strip() if response.lower().startswith('y') or response == '': host_platform = _get_host_platform() if host_platform: _log_info( "Installing JimDB (%s/%s). This may take a while..." % (host_platform, build_platform)) path = os.path.join(MANIFEST_PATH, host_platform, 'jimdb-%s.manifest' % build_platform) _get_tooltool_manifest(build_obj.substs, path, EMULATOR_HOME_DIR, 'releng.manifest') _tooltool_fetch() if os.path.isfile(gdb_path): # Get JimDB utilities from git repository proc = ProcessHandler(['git', 'pull'], cwd=jimdb_utils_path) proc.run() git_pull_complete = False try: proc.wait() if proc.proc.returncode == 0: git_pull_complete = True except: if proc.poll() is None: proc.kill(signal.SIGTERM) if not git_pull_complete: _log_warning("Unable to update JimDB utils from git -- " "some JimDB features may be unavailable.") else: _log_warning("Unable to install JimDB -- unable to fetch from tooltool.") else: _log_warning("Unable to install JimDB -- your platform is not supported!") if os.path.isfile(gdb_path): # sync gdbinit.local with build settings _update_gdbinit(build_obj.substs, os.path.join(jimdb_utils_path, "gdbinit.local")) # ensure JimDB is in system path, so that mozdebug can find it bin_path = os.path.join(jimdb_path, 'bin') os.environ['PATH'] = "%s:%s" % (bin_path, os.environ['PATH']) return device_verified
class ServoRefTestExecutor(ProcessTestExecutor): convert_result = reftest_result_converter def __init__(self, browser, server_config, binary=None, timeout_multiplier=1, screenshot_cache=None, debug_info=None, pause_after_test=False): do_delayed_imports() ProcessTestExecutor.__init__(self, browser, server_config, timeout_multiplier=timeout_multiplier, debug_info=debug_info) self.protocol = Protocol(self, browser) self.screenshot_cache = screenshot_cache self.implementation = RefTestImplementation(self) self.tempdir = tempfile.mkdtemp() self.hosts_path = make_hosts_file() def teardown(self): try: os.unlink(self.hosts_path) except OSError: pass os.rmdir(self.tempdir) ProcessTestExecutor.teardown(self) def screenshot(self, test, viewport_size, dpi): full_url = self.test_url(test) with TempFilename(self.tempdir) as output_path: debug_args, command = browser_command( self.binary, [render_arg(self.browser.render_backend), "--hard-fail", "--exit", "-u", "Servo/wptrunner", "-Z", "disable-text-aa,load-webfonts-synchronously,replace-surrogates", "--output=%s" % output_path, full_url], self.debug_info) for stylesheet in self.browser.user_stylesheets: command += ["--user-stylesheet", stylesheet] for pref in test.environment.get('prefs', {}): command += ["--pref", pref] command += ["--resolution", viewport_size or "800x600"] if dpi: command += ["--device-pixel-ratio", dpi] self.command = debug_args + command env = os.environ.copy() env["HOST_FILE"] = self.hosts_path env["RUST_BACKTRACE"] = "1" if not self.interactive: self.proc = ProcessHandler(self.command, processOutputLine=[self.on_output], env=env) try: self.proc.run() timeout = test.timeout * self.timeout_multiplier + 5 rv = self.proc.wait(timeout=timeout) except KeyboardInterrupt: self.proc.kill() raise else: self.proc = subprocess.Popen(self.command, env=env) try: rv = self.proc.wait() except KeyboardInterrupt: self.proc.kill() raise if rv is None: self.proc.kill() return False, ("EXTERNAL-TIMEOUT", None) if rv != 0 or not os.path.exists(output_path): return False, ("CRASH", None) with open(output_path) as f: # Might need to strip variable headers or something here data = f.read() return True, base64.b64encode(data) def do_test(self, test): result = self.implementation.run_test(test) return self.convert_result(test, result) def on_output(self, line): line = line.decode("utf8", "replace") if self.interactive: print line else: self.logger.process_output(self.proc.pid, line, " ".join(self.command))
class Emulator(Device): logcat_proc = None port = None proc = None telnet = None def __init__(self, app_ctx, arch, resolution=None, sdcard=None, userdata=None, no_window=None, binary=None, **kwargs): Device.__init__(self, app_ctx, **kwargs) self.arch = ArchContext(arch, self.app_ctx, binary=binary) self.resolution = resolution or '320x480' self.sdcard = None if sdcard: self.sdcard = self.create_sdcard(sdcard) self.userdata = tempfile.NamedTemporaryFile(prefix='qemu-userdata') self.initdata = userdata if userdata else os.path.join(self.arch.sysdir, 'userdata.img') self.no_window = no_window self.battery = EmulatorBattery(self) self.geo = EmulatorGeo(self) self.screen = EmulatorScreen(self) @property def args(self): """ Arguments to pass into the emulator binary. """ qemu_args = [self.arch.binary, '-kernel', self.arch.kernel, '-sysdir', self.arch.sysdir, '-data', self.userdata.name, '-initdata', self.initdata, '-wipe-data'] if self.no_window: qemu_args.append('-no-window') if self.sdcard: qemu_args.extend(['-sdcard', self.sdcard]) qemu_args.extend(['-memory', '512', '-partition-size', '512', '-verbose', '-skin', self.resolution, '-gpu', 'on', '-qemu'] + self.arch.extra_args) return qemu_args def start(self): """ Starts a new emulator. """ if self.proc: return original_devices = set(self._get_online_devices()) qemu_log = None qemu_proc_args = {} if self.logdir: # save output from qemu to logfile qemu_log = os.path.join(self.logdir, 'qemu.log') if os.path.isfile(qemu_log): self._rotate_log(qemu_log) qemu_proc_args['logfile'] = qemu_log else: qemu_proc_args['processOutputLine'] = lambda line: None self.proc = ProcessHandler(self.args, **qemu_proc_args) self.proc.run() devices = set(self._get_online_devices()) now = datetime.datetime.now() while (devices - original_devices) == set([]): time.sleep(1) if datetime.datetime.now() - now > datetime.timedelta(seconds=60): raise TimeoutException('timed out waiting for emulator to start') devices = set(self._get_online_devices()) devices = devices - original_devices self.serial = devices.pop() self.connect() def _get_online_devices(self): return set([d[0] for d in self.dm.devices() if d[1] != 'offline' if d[0].startswith('emulator')]) def connect(self): """ Connects to a running device. If no serial was specified in the constructor, defaults to the first entry in `adb devices`. """ if self.connected: return Device.connect(self) self.port = int(self.serial[self.serial.rindex('-')+1:]) self.geo.set_default_location() self.screen.initialize() # setup DNS fix for networking self.app_ctx.dm.shellCheckOutput(['setprop', 'net.dns1', '10.0.2.3']) def create_sdcard(self, sdcard_size): """ Creates an sdcard partition in the emulator. :param sdcard_size: Size of partition to create, e.g '10MB'. """ mksdcard = self.app_ctx.which('mksdcard') path = tempfile.mktemp(prefix='sdcard') sdargs = [mksdcard, '-l', 'mySdCard', sdcard_size, path] sd = subprocess.Popen(sdargs, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) retcode = sd.wait() if retcode: raise Exception('unable to create sdcard: exit code %d: %s' % (retcode, sd.stdout.read())) return path def cleanup(self): """ Cleans up and kills the emulator. """ Device.cleanup(self) if self.proc: self.proc.kill() self.proc = None # Remove temporary user data image if self.userdata: self.userdata.close() # Remove temporary sdcard if self.sdcard and os.path.isfile(self.sdcard): os.remove(self.sdcard) # TODO this function is B2G specific and shouldn't live here @uses_marionette def wait_for_system_message(self, marionette): marionette.set_script_timeout(45000) # Telephony API's won't be available immediately upon emulator # boot; we have to wait for the syste-message-listener-ready # message before we'll be able to use them successfully. See # bug 792647. print 'waiting for system-message-listener-ready...' try: marionette.execute_async_script(""" waitFor( function() { marionetteScriptFinished(true); }, function() { return isSystemMessageListenerReady(); } ); """) except: # Look for ScriptTimeoutException this way to avoid a # dependency on the marionette python client. exc_name = sys.exc_info()[0].__name__ if exc_name != 'ScriptTimeoutException': raise print 'timed out' # We silently ignore the timeout if it occurs, since # isSystemMessageListenerReady() isn't available on # older emulators. 45s *should* be enough of a delay # to allow telephony API's to work. pass print '...done' # TODO this function is B2G specific and shouldn't live here @uses_marionette def wait_for_homescreen(self, marionette): print 'waiting for homescreen...' marionette.set_context(marionette.CONTEXT_CONTENT) marionette.execute_async_script(""" log('waiting for mozbrowserloadend'); window.addEventListener('mozbrowserloadend', function loaded(aEvent) { log('received mozbrowserloadend for ' + aEvent.target.src); if (aEvent.target.src.indexOf('ftu') != -1 || aEvent.target.src.indexOf('homescreen') != -1 || aEvent.target.src.indexOf('verticalhome') != -1) { window.removeEventListener('mozbrowserloadend', loaded); marionetteScriptFinished(); } });""", script_timeout=300000) print '...done' def _get_telnet_response(self, command=None): output = [] assert(self.telnet) if command is not None: self.telnet.write('%s\n' % command) while True: line = self.telnet.read_until('\n') output.append(line.rstrip()) if line.startswith('OK'): return output elif line.startswith('KO:'): raise Exception('bad telnet response: %s' % line) def _run_telnet(self, command): if not self.telnet: self.telnet = Telnet('localhost', self.port) self._get_telnet_response() return self._get_telnet_response(command) def __del__(self): if self.telnet: self.telnet.write('exit\n') self.telnet.read_all()
class ServoRefTestExecutor(ProcessTestExecutor): convert_result = reftest_result_converter def __init__(self, logger, browser, server_config, binary=None, timeout_multiplier=1, screenshot_cache=None, debug_info=None, pause_after_test=False, **kwargs): ProcessTestExecutor.__init__(self, logger, browser, server_config, timeout_multiplier=timeout_multiplier, debug_info=debug_info) self.protocol = ConnectionlessProtocol(self, browser) self.screenshot_cache = screenshot_cache self.implementation = RefTestImplementation(self) self.tempdir = tempfile.mkdtemp() self.hosts_path = write_hosts_file(server_config) def reset(self): self.implementation.reset() def teardown(self): try: os.unlink(self.hosts_path) except OSError: pass os.rmdir(self.tempdir) ProcessTestExecutor.teardown(self) def screenshot(self, test, viewport_size, dpi, page_ranges): with TempFilename(self.tempdir) as output_path: extra_args = [ "--exit", "--output=%s" % output_path, "--resolution", viewport_size or "800x600" ] debug_opts = "disable-text-aa,load-webfonts-synchronously,replace-surrogates" if dpi: extra_args += ["--device-pixel-ratio", dpi] self.command = build_servo_command(test, self.test_url, self.browser, self.binary, False, self.debug_info, extra_args, debug_opts) env = os.environ.copy() env["HOST_FILE"] = self.hosts_path env["RUST_BACKTRACE"] = "1" if not self.interactive: self.proc = ProcessHandler(self.command, processOutputLine=[self.on_output], env=env) try: self.proc.run() timeout = test.timeout * self.timeout_multiplier + 5 rv = self.proc.wait(timeout=timeout) except KeyboardInterrupt: self.proc.kill() raise else: self.proc = subprocess.Popen(self.command, env=env) try: rv = self.proc.wait() except KeyboardInterrupt: self.proc.kill() raise if rv is None: self.proc.kill() return False, ("EXTERNAL-TIMEOUT", None) if rv != 0 or not os.path.exists(output_path): return False, ("CRASH", None) with open(output_path, "rb") as f: # Might need to strip variable headers or something here data = f.read() return True, [ensure_str(base64.b64encode(data))] def do_test(self, test): result = self.implementation.run_test(test) return self.convert_result(test, result) def on_output(self, line): line = line.decode("utf8", "replace") if self.interactive: print(line) else: self.logger.process_output(self.proc.pid, line, " ".join(self.command))
class AndroidEmulator(object): """ Support running the Android emulator with an AVD from Mozilla test automation. Example usage: emulator = AndroidEmulator() if not emulator.is_running() and emulator.is_available(): if not emulator.check_avd(): warn("this may take a while...") emulator.update_avd() emulator.start() emulator.wait_for_start() emulator.wait() """ def __init__(self, avd_type="4.3", verbose=False, substs=None): self.emulator_log = None self.emulator_path = "emulator" self.verbose = verbose self.substs = substs self.avd_type = self._get_avd_type(avd_type) self.avd_info = AVD_DICT[self.avd_type] adb_path = self._find_sdk_exe("adb", False) if not adb_path: adb_path = "adb" self.dm = DeviceManagerADB(autoconnect=False, adbPath=adb_path, retryLimit=1) self.dm.default_timeout = 10 self._log_debug("Emulator created with type %s" % self.avd_type) def __del__(self): if self.emulator_log: self.emulator_log.close() def is_running(self): """ Returns True if the Android emulator is running. """ for proc in psutil.process_iter(): name = proc.name() # On some platforms, "emulator" may start an emulator with # process name "emulator64-arm" or similar. if name and name.startswith("emulator"): return True return False def is_available(self): """ Returns True if an emulator executable is found. """ found = False emulator_path = self._find_sdk_exe("emulator", True) if emulator_path: self.emulator_path = emulator_path found = True return found def check_avd(self, force=False): """ Determine if the AVD is already installed locally. (This is usually used to determine if update_avd() is likely to require a download; it is a convenient way of determining whether a 'this may take a while' warning is warranted.) Returns True if the AVD is installed. """ avd = os.path.join(EMULATOR_HOME_DIR, "avd", self.avd_info.name + ".avd") if force and os.path.exists(avd): shutil.rmtree(avd) if os.path.exists(avd): self._log_debug("AVD found at %s" % avd) return True return False def update_avd(self, force=False): """ If required, update the AVD via tooltool. If the AVD directory is not found, or "force" is requested, download the tooltool manifest associated with the AVD and then invoke tooltool.py on the manifest. tooltool.py will download the required archive (unless already present in the local tooltool cache) and install the AVD. """ avd = os.path.join(EMULATOR_HOME_DIR, "avd", self.avd_info.name + ".avd") if force and os.path.exists(avd): shutil.rmtree(avd) if not os.path.exists(avd): self._fetch_tooltool() self._fetch_tooltool_manifest() self._tooltool_fetch() self._update_avd_paths() def start(self): """ Launch the emulator. """ def outputHandler(line): self.emulator_log.write("<%s>\n" % line) env = os.environ env["ANDROID_AVD_HOME"] = os.path.join(EMULATOR_HOME_DIR, "avd") command = [self.emulator_path, "-avd", self.avd_info.name, "-port", "5554"] if self.avd_info.extra_args: command += self.avd_info.extra_args log_path = os.path.join(EMULATOR_HOME_DIR, "emulator.log") self.emulator_log = open(log_path, "w") self._log_debug("Starting the emulator with this command: %s" % " ".join(command)) self._log_debug("Emulator output will be written to '%s'" % log_path) self.proc = ProcessHandler(command, storeOutput=False, processOutputLine=outputHandler, env=env) self.proc.run() self._log_debug("Emulator started with pid %d" % int(self.proc.proc.pid)) def wait_for_start(self): """ Verify that the emulator is running, the emulator device is visible to adb, and Android has booted. """ if not self.proc: self._log_warning("Emulator not started!") return False if self.proc.proc.poll() is not None: self._log_warning("Emulator has already completed!") return False self._log_debug("Waiting for device status...") while ("emulator-5554", "device") not in self.dm.devices(): time.sleep(10) if self.proc.proc.poll() is not None: self._log_warning("Emulator has already completed!") return False self._log_debug("Device status verified.") self._log_debug("Checking that Android has booted...") complete = False while not complete: output = "" try: output = self.dm.shellCheckOutput(["getprop", "sys.boot_completed"], timeout=5) except DMError: # adb not yet responding...keep trying pass if output.strip() == "1": complete = True else: time.sleep(10) if self.proc.proc.poll() is not None: self._log_warning("Emulator has already completed!") return False self._log_debug("Android boot status verified.") if not self._verify_emulator(): return False if self.avd_info.uses_sut: if not self._verify_sut(): return False return True def wait(self): """ Wait for the emulator to close. If interrupted, close the emulator. """ try: self.proc.wait() except: if self.proc.poll() is None: self.cleanup() return self.proc.poll() def cleanup(self): """ Close the emulator. """ self.proc.kill(signal.SIGTERM) def get_avd_description(self): """ Return the human-friendly description of this AVD. """ return self.avd_info.description def _log_debug(self, text): if self.verbose: print "DEBUG: %s" % text def _log_warning(self, text): print "WARNING: %s" % text def _fetch_tooltool(self): self._download_file(TOOLTOOL_URL, "tooltool.py", EMULATOR_HOME_DIR) def _fetch_tooltool_manifest(self): url = "https://hg.mozilla.org/%s/raw-file/%s/%s" % ("try", "default", self.avd_info.tooltool_manifest) self._download_file(url, "releng.manifest", EMULATOR_HOME_DIR) def _tooltool_fetch(self): def outputHandler(line): self._log_debug(line) command = ["python", "tooltool.py", "fetch", "-m", "releng.manifest"] proc = ProcessHandler(command, processOutputLine=outputHandler, storeOutput=False, cwd=EMULATOR_HOME_DIR) proc.run() try: proc.wait() except: if proc.poll() is None: proc.kill(signal.SIGTERM) def _update_avd_paths(self): avd_path = os.path.join(EMULATOR_HOME_DIR, "avd") ini_file = os.path.join(avd_path, "test-1.ini") ini_file_new = os.path.join(avd_path, self.avd_info.name + ".ini") os.rename(ini_file, ini_file_new) avd_dir = os.path.join(avd_path, "test-1.avd") avd_dir_new = os.path.join(avd_path, self.avd_info.name + ".avd") os.rename(avd_dir, avd_dir_new) self._replace_ini_contents(ini_file_new) def _download_file(self, url, filename, path): f = urllib2.urlopen(url) if not os.path.isdir(path): try: os.makedirs(path) except Exception, e: self._log_warning(str(e)) return False local_file = open(os.path.join(path, filename), "wb") local_file.write(f.read()) local_file.close() self._log_debug("Downloaded %s to %s/%s" % (url, path, filename)) return True