def run(self, test, phase='phase1', ignore_unused_engines=True): args = [ '-tps={}'.format(os.path.abspath(test)), '-tpsphase={}'.format(phase), '-marionette'] if ignore_unused_engines: args.append('--ignore-unused-engines') process_args = {'processOutputLine': [self._log]} self.logger.info('Running: {} {}'.format(self.firefox, ' '.join(args))) self.logger.info('Using profile at: {}'.format(self.profile.profile)) runner = FirefoxRunner( binary=self.firefox, cmdargs=args, profile=self.profile, process_args=process_args) runner.start(timeout=TIMEOUT) runner.wait(timeout=TIMEOUT) self.firefox_log.close() with open(self.tps_log) as f: for line in f.readlines(): if 'CROSSWEAVE ERROR: ' in line: raise TPSError(line.partition('CROSSWEAVE ERROR: ')[-1]) with open(self.tps_log) as f: assert 'test phase {}: PASS'.format(phase) in f.read()
def runprofile(binary, fileobj): try: runner = FirefoxRunner(binary=binary, profile=fileobj.profile) runner.start() runner.wait() except KeyboardInterrupt: pass
def runprofile(binary, fileobj): try: runner = FirefoxRunner(binary=binary, profile=fileobj.profile) runner.start() runner.wait() except KeyboardInterrupt: pass
def run(self, test, phase='phase1', ignore_unused_engines=True): self.profile.set_preferences({ 'testing.tps.testFile': os.path.abspath(test), 'testing.tps.testPhase': phase, 'testing.tps.ignoreUnusedEngines': ignore_unused_engines, }) args = ['-marionette'] process_args = {'processOutputLine': [self._log]} self.logger.info('Running: {} {}'.format(self.firefox, ' '.join(args))) self.logger.info('Using profile at: {}'.format(self.profile.profile)) runner = FirefoxRunner(binary=self.firefox, cmdargs=args, profile=self.profile, process_args=process_args) runner.start(timeout=TIMEOUT) runner.wait(timeout=TIMEOUT) self.firefox_log.close() with open(self.tps_log) as f: for line in f.readlines(): if 'CROSSWEAVE ERROR: ' in line: raise TPSError(line.partition('CROSSWEAVE ERROR: ')[-1]) with open(self.tps_log) as f: assert 'test phase {}: PASS'.format(phase) in f.read()
def run(self, test, phase='phase1', ignore_unused_engines=True): args = [ '-tps={}'.format(os.path.abspath(test)), '-tpsphase={}'.format(phase), '-marionette' ] if ignore_unused_engines: args.append('--ignore-unused-engines') process_args = {'processOutputLine': [self._log]} self.logger.info('Running: {} {}'.format(self.firefox, ' '.join(args))) self.logger.info('Using profile at: {}'.format(self.profile.profile)) runner = FirefoxRunner(binary=self.firefox, cmdargs=args, profile=self.profile, process_args=process_args) runner.start(timeout=TIMEOUT) runner.wait(timeout=TIMEOUT) self.firefox_log.close() with open(self.tps_log) as f: for line in f.readlines(): if 'CROSSWEAVE ERROR: ' in line: raise TPSError(line.partition('CROSSWEAVE ERROR: ')[-1]) with open(self.tps_log) as f: assert 'test phase {}: PASS'.format(phase) in f.read()
class FFRunner(): # Calls FirefoxRunner with the right parameters def __init__(self, name="firefox", installDir=os.path.join(os.path.expanduser("~"),"remotebisectorapp")): self.name = name platform=get_platform() if platform['name'] == "Windows": if platform['bits'] == '64': print "No builds available for 64 bit Windows" sys.exit() self.buildRegex = ".*win32.zip" self.processName = self.name + ".exe" self.binary = os.path.join(installDir, self.name, self.name + ".exe") elif platform['name'] == "Linux": self.processName = self.name + "-bin" self.binary = os.path.join(installDir, self.name, self.name) if platform['bits'] == '64': self.buildRegex = ".*linux-x86_64.tar.bz2" else: self.buildRegex = ".*linux-i686.tar.bz2" elif platform['name'] == "Mac": self.buildRegex = ".*mac.*\.dmg" self.processName = self.name + "-bin" self.binary = os.path.join(installDir, "Mozilla.app/Contents/MacOS", self.name + "-bin") def run(self): self.runner = FirefoxRunner(binary=self.binary) self.runner.start()
def run(self, test, phase='phase1', ignore_unused_engines=True): self.profile.set_preferences({ 'testing.tps.testFile': os.path.abspath(test), 'testing.tps.testPhase': phase, 'testing.tps.ignoreUnusedEngines': ignore_unused_engines, }) args = ['-marionette'] process_args = {'processOutputLine': [self._log]} self.logger.info('Running: {} {}'.format(self.firefox, ' '.join(args))) self.logger.info('Using profile at: {}'.format(self.profile.profile)) runner = FirefoxRunner( binary=self.firefox, cmdargs=args, profile=self.profile, process_args=process_args) runner.start(timeout=TIMEOUT) runner.wait(timeout=TIMEOUT) self.firefox_log.close() with open(self.tps_log) as f: for line in f.readlines(): if 'CROSSWEAVE ERROR: ' in line: raise TPSError(line.partition('CROSSWEAVE ERROR: ')[-1]) with open(self.tps_log) as f: assert 'test phase {}: PASS'.format(phase) in f.read()
def launch_control_center(): profile_path = os.path.join(get_core_args().workdir, 'cc_profile') fx_path = PathManager.get_local_firefox_path() if fx_path is None: logger.error( 'Can\'t find local Firefox installation, aborting Iris run.') return False, None args = ['http://127.0.0.1:%s' % get_core_args().port] process_args = {'stream': None} profile = MozProfile(profile=profile_path, preferences=Settings.default_fx_prefs) fx_runner = FirefoxRunner(binary=fx_path, profile=profile, cmdargs=args, process_args=process_args) fx_runner.start() server = LocalWebServer(get_core_args().workdir, get_core_args().port) server.stop() time.sleep(Settings.DEFAULT_UI_DELAY) if OSHelper.is_mac(): type(text='q', modifier=KeyModifier.CMD) elif OSHelper.is_windows(): type(text='w', modifier=[KeyModifier.CTRL, KeyModifier.SHIFT]) else: type(text='q', modifier=KeyModifier.CTRL) try: fx_runner.stop() except Exception as e: logger.debug('Error stopping fx_runner') logger.debug(e) return server.result
def start(self): """Start an instance of Firefox, returning a BrowserInstance handle""" profile = self.base_profile.clone(self.base_profile.profile) marionette_port = get_free_port() profile.set_preferences({"marionette.port": marionette_port}) env = test_environment(xrePath=os.path.dirname(self.binary), debugger=self.debug_info is not None, useLSan=True, log=self.logger) env["STYLO_THREADS"] = str(self.stylo_threads) if self.chaos_mode_flags is not None: env["MOZ_CHAOSMODE"] = str(self.chaos_mode_flags) if self.headless: env["MOZ_HEADLESS"] = "1" if self.enable_webrender: env["MOZ_WEBRENDER"] = "1" env["MOZ_ACCELERATED"] = "1" # Set MOZ_X_SYNC and GDK_SYNCHRONIZE for investigation; bug 1625250. env["MOZ_X_SYNC"] = "1" env["GDK_SYNCHRONIZE"] = "1" else: env["MOZ_WEBRENDER"] = "0" args = self.binary_args[:] if self.binary_args else [] args += [cmd_arg("marionette"), "about:blank"] debug_args, cmd = browser_command(self.binary, args, self.debug_info) if self.leak_check: leak_report_file = os.path.join( profile.profile, "runtests_leaks_%s.log" % os.getpid()) if os.path.exists(leak_report_file): os.remove(leak_report_file) env["XPCOM_MEM_BLOAT_LOG"] = leak_report_file else: leak_report_file = None output_handler = OutputHandler(self.logger, self.stackfix_dir, self.symbols_path, self.asan) runner = FirefoxRunner( profile=profile, binary=cmd[0], cmdargs=cmd[1:], env=cast_env(env), process_class=ProcessHandler, process_args={"processOutputLine": [output_handler]}) instance = BrowserInstance(self.logger, runner, marionette_port, output_handler, leak_report_file) self.logger.debug("Starting Firefox") runner.start(debug_args=debug_args, interactive=self.debug_info and self.debug_info.interactive) self.logger.debug("Firefox Started") return instance
def launch_control_center(): profile_path = os.path.join(PathManager.get_working_dir(), "cc_profile") fx_path = PathManager.get_local_firefox_path() if fx_path is None: logger.error("Can't find local Firefox installation, aborting Iris run.") return False, None args = ["http://127.0.0.1:%s" % get_core_args().port] process_args = {"stream": None} profile = MozProfile(profile=profile_path, preferences=get_fx_prefs()) if OSHelper.is_windows(): process = subprocess.Popen( [ fx_path, "-no-remote", "-new-tab", args, "--wait-for-browser", "-foreground", "-profile", profile.profile, ], shell=False, ) else: fx_runner = FirefoxRunner( binary=fx_path, profile=profile, cmdargs=args, process_args=process_args ) fx_runner.start() logger.debug("Launching web server for directory %s" % PathManager.get_working_dir()) server = LocalWebServer(PathManager.get_working_dir(), get_core_args().port) server.stop() time.sleep(Settings.DEFAULT_UI_DELAY) if OSHelper.is_mac(): type(text="q", modifier=KeyModifier.CMD) elif OSHelper.is_windows(): type(text="w", modifier=[KeyModifier.CTRL, KeyModifier.SHIFT]) else: type(text="q", modifier=KeyModifier.CTRL) if OSHelper.is_windows(): if process.pid is not None: try: logger.debug("Closing Firefox process ID: %s" % process.pid) process = psutil.Process(process.pid) for proc in process.children(recursive=True): proc.kill() process.kill() except psutil.NoSuchProcess: pass else: try: fx_runner.stop() except Exception as e: logger.debug("Error stopping fx_runner") logger.debug(e) return server.result
def launch_control_center(): profile_path = os.path.join(get_core_args().workdir, 'cc_profile') fx_path = PathManager.get_local_firefox_path() if fx_path is None: logger.error( 'Can\'t find local Firefox installation, aborting Iris run.') return False, None args = ['http://127.0.0.1:%s' % get_core_args().port] process_args = {'stream': None} profile = MozProfile(profile=profile_path, preferences=Settings.default_fx_prefs) if OSHelper.is_windows(): process = subprocess.Popen([ fx_path, '-no-remote', '-new-tab', args, '--wait-for-browser', '-foreground', '-profile', profile.profile ], shell=False) else: fx_runner = FirefoxRunner(binary=fx_path, profile=profile, cmdargs=args, process_args=process_args) fx_runner.start() server = LocalWebServer(get_core_args().workdir, get_core_args().port) server.stop() time.sleep(Settings.DEFAULT_UI_DELAY) if OSHelper.is_mac(): type(text='q', modifier=KeyModifier.CMD) elif OSHelper.is_windows(): type(text='w', modifier=[KeyModifier.CTRL, KeyModifier.SHIFT]) else: type(text='q', modifier=KeyModifier.CTRL) if OSHelper.is_windows(): if process.pid is not None: try: logger.debug('Closing Firefox process ID: %s' % process.pid) process = psutil.Process(process.pid) for proc in process.children(recursive=True): proc.kill() process.kill() except psutil.NoSuchProcess: pass else: try: fx_runner.stop() except Exception as e: logger.debug('Error stopping fx_runner') logger.debug(e) return server.result
def run(self, profile=None, timeout=PROCESS_TIMEOUT, env=None, args=None): """Runs the given FirefoxRunner with the given Profile, waits for completion, then returns the process exit code """ if profile is None: profile = Profile() self.profile = profile if self.binary is None and self.url: self.binary = self.download_build() runner = FirefoxRunner(profile=self.profile, binary=self.binary, env=env, cmdargs=args) runner.start(timeout=timeout) return runner.wait()
def run(self, profile=None, timeout=PROCESS_TIMEOUT, env=None, args=None): """Runs the given FirefoxRunner with the given Profile, waits for completion, then returns the process exit code """ if profile is None: profile = Profile() self.profile = profile if self.binary is None and self.url: self.binary = self.download_build() runner = FirefoxRunner(profile=self.profile, binary=self.binary, env=env, cmdargs=args) runner.start(timeout=timeout) return runner.wait()
def start(self): """Start an instance of Firefox, returning a BrowserInstance handle""" profile = self.base_profile.clone(self.base_profile.profile) marionette_port = get_free_port() profile.set_preferences({"marionette.port": marionette_port}) env = get_environ(self.logger, self.binary, self.debug_info, self.stylo_threads, self.headless, self.enable_webrender, self.chaos_mode_flags) args = self.binary_args[:] if self.binary_args else [] args += [cmd_arg("marionette"), "about:blank"] debug_args, cmd = browser_command(self.binary, args, self.debug_info) if self.leak_check: leak_report_file = os.path.join( profile.profile, "runtests_leaks_%s.log" % os.getpid()) if os.path.exists(leak_report_file): os.remove(leak_report_file) env["XPCOM_MEM_BLOAT_LOG"] = leak_report_file else: leak_report_file = None output_handler = OutputHandler(self.logger, self.stackfix_dir, self.symbols_path, self.asan) runner = FirefoxRunner( profile=profile, binary=cmd[0], cmdargs=cmd[1:], env=env, process_class=ProcessHandler, process_args={"processOutputLine": [output_handler]}) instance = BrowserInstance(self.logger, runner, marionette_port, output_handler, leak_report_file) self.logger.debug("Starting Firefox") runner.start(debug_args=debug_args, interactive=self.debug_info and self.debug_info.interactive) self.logger.debug("Firefox Started") return instance
def start(self): """Start an instance of Firefox, returning a BrowserInstance handle""" profile = self.base_profile.clone(self.base_profile.profile) marionette_port = get_free_port() profile.set_preferences({"marionette.port": marionette_port}) env = get_environ(self.logger, self.binary, self.debug_info, self.stylo_threads, self.headless, self.chaos_mode_flags) args = self.binary_args[:] if self.binary_args else [] args += [cmd_arg("marionette"), "about:blank"] debug_args, cmd = browser_command(self.binary, args, self.debug_info) leak_report_file = setup_leak_report(self.leak_check, profile, env) output_handler = FirefoxOutputHandler( self.logger, cmd, stackfix_dir=self.stackfix_dir, symbols_path=self.symbols_path, asan=self.asan, leak_report_file=leak_report_file) runner = FirefoxRunner( profile=profile, binary=cmd[0], cmdargs=cmd[1:], env=env, process_class=ProcessHandler, process_args={"processOutputLine": [output_handler]}) instance = BrowserInstance(self.logger, runner, marionette_port, output_handler, leak_report_file) self.logger.debug("Starting Firefox") runner.start(debug_args=debug_args, interactive=self.debug_info and self.debug_info.interactive) output_handler.after_process_start(runner.process_handler.pid) self.logger.debug("Firefox Started") return instance
def run(self): #Run the built binary if it exists. ONLY WORKS IF BUILD WAS CALLED! if sys.platform == "darwin": runner = FirefoxRunner(binary=os.path.join(self.shellCacheDir,"mozbuild-trunk","obj-ff-dbg","dist","NightlyDebug.app","Contents","MacOS")+"/firefox-bin") runner.start() runner.wait() elif sys.platform == "linux2": runner = FirefoxRunner(binary=os.path.join(self.shellCacheDir,"mozbuild-trunk","obj-ff-dbg","dist","bin") + "/firefox") runner.start() runner.wait() elif sys.platform == "win32" or sys.platform == "cygwin": runner = FirefoxRunner(binary=os.path.join(self.shellCacheDir,"mozbuild-trunk","obj-ff-dbg","dist","bin") + "/firefox.exe") runner.start() runner.wait() else: print "Your platform is not currently supported." quit()
class FirefoxBrowser(Browser): used_ports = set() init_timeout = 60 shutdown_timeout = 60 def __init__(self, logger, binary, prefs_root, test_type, extra_prefs=None, debug_info=None, symbols_path=None, stackwalk_binary=None, certutil_binary=None, ca_certificate_path=None, e10s=False, stackfix_dir=None, binary_args=None, timeout_multiplier=None, leak_check=False, stylo_threads=1, chaos_mode_flags=None, config=None): Browser.__init__(self, logger) self.binary = binary self.prefs_root = prefs_root self.test_type = test_type self.extra_prefs = extra_prefs self.marionette_port = None self.runner = None self.debug_info = debug_info self.profile = None self.symbols_path = symbols_path self.stackwalk_binary = stackwalk_binary self.ca_certificate_path = ca_certificate_path self.certutil_binary = certutil_binary self.e10s = e10s self.binary_args = binary_args self.config = config if stackfix_dir: self.stack_fixer = get_stack_fixer_function(stackfix_dir, self.symbols_path) else: self.stack_fixer = None if timeout_multiplier: self.init_timeout = self.init_timeout * timeout_multiplier self.leak_report_file = None self.leak_check = leak_check self.stylo_threads = stylo_threads self.chaos_mode_flags = chaos_mode_flags def settings(self, test): return {"check_leaks": self.leak_check and not test.leaks} def start(self, **kwargs): if self.marionette_port is None: self.marionette_port = get_free_port(2828, exclude=self.used_ports) self.used_ports.add(self.marionette_port) env = os.environ.copy() env["MOZ_CRASHREPORTER"] = "1" env["MOZ_CRASHREPORTER_SHUTDOWN"] = "1" env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1" env["STYLO_THREADS"] = str(self.stylo_threads) if self.chaos_mode_flags is not None: env["MOZ_CHAOSMODE"] = str(self.chaos_mode_flags) preferences = self.load_prefs() self.profile = FirefoxProfile(preferences=preferences) self.profile.set_preferences({"marionette.port": self.marionette_port, "dom.disable_open_during_load": False, "network.dns.localDomains": ",".join(self.config.domains_set), "network.proxy.type": 0, "places.history.enabled": False, "dom.send_after_paint_to_content": True, "network.preload": True}) if self.e10s: self.profile.set_preferences({"browser.tabs.remote.autostart": True}) if self.test_type == "reftest": self.profile.set_preferences({"layout.interruptible-reflow.enabled": False}) if self.leak_check and kwargs.get("check_leaks", True): self.leak_report_file = os.path.join(self.profile.profile, "runtests_leaks.log") if os.path.exists(self.leak_report_file): os.remove(self.leak_report_file) env["XPCOM_MEM_BLOAT_LOG"] = self.leak_report_file else: self.leak_report_file = None # Bug 1262954: winxp + e10s, disable hwaccel if (self.e10s and platform.system() in ("Windows", "Microsoft") and '5.1' in platform.version()): self.profile.set_preferences({"layers.acceleration.disabled": True}) if self.ca_certificate_path is not None: self.setup_ssl() debug_args, cmd = browser_command(self.binary, self.binary_args if self.binary_args else [] + [cmd_arg("marionette"), "about:blank"], self.debug_info) self.runner = FirefoxRunner(profile=self.profile, binary=cmd[0], cmdargs=cmd[1:], env=env, process_class=ProcessHandler, process_args={"processOutputLine": [self.on_output]}) self.logger.debug("Starting Firefox") self.runner.start(debug_args=debug_args, interactive=self.debug_info and self.debug_info.interactive) self.logger.debug("Firefox Started") def load_prefs(self): prefs = Preferences() pref_paths = [] prefs_general = os.path.join(self.prefs_root, 'prefs_general.js') if os.path.isfile(prefs_general): # Old preference file used in Firefox 60 and earlier (remove when no longer supported) pref_paths.append(prefs_general) profiles = os.path.join(self.prefs_root, 'profiles.json') if os.path.isfile(profiles): with open(profiles, 'r') as fh: for name in json.load(fh)['web-platform-tests']: pref_paths.append(os.path.join(self.prefs_root, name, 'user.js')) for path in pref_paths: if os.path.exists(path): prefs.add(Preferences.read_prefs(path)) else: self.logger.warning("Failed to find base prefs file in %s" % path) # Add any custom preferences prefs.add(self.extra_prefs, cast=True) return prefs() def stop(self, force=False): if self.runner is not None and self.runner.is_running(): try: # For Firefox we assume that stopping the runner prompts the # browser to shut down. This allows the leak log to be written for clean, stop_f in [(True, lambda: self.runner.wait(self.shutdown_timeout)), (False, lambda: self.runner.stop(signal.SIGTERM)), (False, lambda: self.runner.stop(signal.SIGKILL))]: if not force or not clean: retcode = stop_f() if retcode is not None: self.logger.info("Browser exited with return code %s" % retcode) break except OSError: # This can happen on Windows if the process is already dead pass self.logger.debug("stopped") def process_leaks(self): self.logger.debug("PROCESS LEAKS %s" % self.leak_report_file) if self.leak_report_file is None: return mozleak.process_leak_log( self.leak_report_file, leak_thresholds={ "default": 0, "tab": 10000, # See dependencies of bug 1051230. # GMP rarely gets a log, but when it does, it leaks a little. "geckomediaplugin": 20000, }, ignore_missing_leaks=["geckomediaplugin"], log=self.logger, stack_fixer=self.stack_fixer ) def pid(self): if self.runner.process_handler is None: return None try: return self.runner.process_handler.pid except AttributeError: return None def on_output(self, line): """Write a line of output from the firefox process to the log""" if "GLib-GObject-CRITICAL" in line: return if line: data = line.decode("utf8", "replace") if self.stack_fixer: data = self.stack_fixer(data) self.logger.process_output(self.pid(), data, command=" ".join(self.runner.command)) def is_alive(self): if self.runner: return self.runner.is_running() return False def cleanup(self): self.stop() self.process_leaks() def executor_browser(self): assert self.marionette_port is not None return ExecutorBrowser, {"marionette_port": self.marionette_port} def check_for_crashes(self): dump_dir = os.path.join(self.profile.profile, "minidumps") return bool(mozcrash.check_for_crashes(dump_dir, symbols_path=self.symbols_path, stackwalk_binary=self.stackwalk_binary, quiet=True)) def log_crash(self, process, test): dump_dir = os.path.join(self.profile.profile, "minidumps") mozcrash.log_crashes(self.logger, dump_dir, symbols_path=self.symbols_path, stackwalk_binary=self.stackwalk_binary, process=process, test=test) def setup_ssl(self): """Create a certificate database to use in the test profile. This is configured to trust the CA Certificate that has signed the web-platform.test server certificate.""" if self.certutil_binary is None: self.logger.info("--certutil-binary not supplied; Firefox will not check certificates") return self.logger.info("Setting up ssl") # Make sure the certutil libraries from the source tree are loaded when using a # local copy of certutil # TODO: Maybe only set this if certutil won't launch? env = os.environ.copy() certutil_dir = os.path.dirname(self.binary) if mozinfo.isMac: env_var = "DYLD_LIBRARY_PATH" elif mozinfo.isUnix: env_var = "LD_LIBRARY_PATH" else: env_var = "PATH" env[env_var] = (os.path.pathsep.join([certutil_dir, env[env_var]]) if env_var in env else certutil_dir).encode( sys.getfilesystemencoding() or 'utf-8', 'replace') def certutil(*args): cmd = [self.certutil_binary] + list(args) self.logger.process_output("certutil", subprocess.check_output(cmd, env=env, stderr=subprocess.STDOUT), " ".join(cmd)) pw_path = os.path.join(self.profile.profile, ".crtdbpw") with open(pw_path, "w") as f: # Use empty password for certificate db f.write("\n") cert_db_path = self.profile.profile # Create a new certificate db certutil("-N", "-d", cert_db_path, "-f", pw_path) # Add the CA certificate to the database and mark as trusted to issue server certs certutil("-A", "-d", cert_db_path, "-f", pw_path, "-t", "CT,,", "-n", "web-platform-tests", "-i", self.ca_certificate_path) # List all certs in the database certutil("-L", "-d", cert_db_path)
def valgrind_test(self, suppressions): from mozfile import TemporaryDirectory from mozhttpd import MozHttpd from mozprofile import FirefoxProfile, Preferences from mozprofile.permissions import ServerLocations from mozrunner import FirefoxRunner from mozrunner.utils import findInPath from six import string_types from valgrind.output_handler import OutputHandler build_dir = os.path.join(self.topsrcdir, "build") # XXX: currently we just use the PGO inputs for Valgrind runs. This may # change in the future. httpd = MozHttpd(docroot=os.path.join(build_dir, "pgo")) httpd.start(block=False) with TemporaryDirectory() as profilePath: # TODO: refactor this into mozprofile profile_data_dir = os.path.join(self.topsrcdir, "testing", "profiles") with open(os.path.join(profile_data_dir, "profiles.json"), "r") as fh: base_profiles = json.load(fh)["valgrind"] prefpaths = [ os.path.join(profile_data_dir, profile, "user.js") for profile in base_profiles ] prefs = {} for path in prefpaths: prefs.update(Preferences.read_prefs(path)) interpolation = { "server": "%s:%d" % httpd.httpd.server_address, } for k, v in prefs.items(): if isinstance(v, string_types): v = v.format(**interpolation) prefs[k] = Preferences.cast(v) quitter = os.path.join(self.topsrcdir, "tools", "quitter", "*****@*****.**") locations = ServerLocations() locations.add_host(host="127.0.0.1", port=httpd.httpd.server_port, options="primary") profile = FirefoxProfile( profile=profilePath, preferences=prefs, addons=[quitter], locations=locations, ) firefox_args = [httpd.get_url()] env = os.environ.copy() env["G_SLICE"] = "always-malloc" env["MOZ_CC_RUN_DURING_SHUTDOWN"] = "1" env["MOZ_CRASHREPORTER_NO_REPORT"] = "1" env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1" env["XPCOM_DEBUG_BREAK"] = "warn" outputHandler = OutputHandler(self.log) kp_kwargs = { "processOutputLine": [outputHandler], "universal_newlines": True, } valgrind = "valgrind" if not os.path.exists(valgrind): valgrind = findInPath(valgrind) valgrind_args = [ valgrind, "--sym-offsets=yes", "--smc-check=all-non-file", "--vex-iropt-register-updates=allregs-at-mem-access", "--gen-suppressions=all", "--num-callers=36", "--leak-check=full", "--show-possibly-lost=no", "--track-origins=yes", "--trace-children=yes", "-v", # Enable verbosity to get the list of used suppressions # Avoid excessive delays in the presence of spinlocks. # See bug 1309851. "--fair-sched=yes", # Keep debuginfo after library unmap. See bug 1382280. "--keep-debuginfo=yes", # Reduce noise level on rustc and/or LLVM compiled code. # See bug 1365915 "--expensive-definedness-checks=yes", # Compensate for the compiler inlining `new` but not `delete` # or vice versa. "--show-mismatched-frees=no", ] for s in suppressions: valgrind_args.append("--suppressions=" + s) supps_dir = os.path.join(build_dir, "valgrind") supps_file1 = os.path.join(supps_dir, "cross-architecture.sup") valgrind_args.append("--suppressions=" + supps_file1) if mozinfo.os == "linux": machtype = { "x86_64": "x86_64-pc-linux-gnu", "x86": "i386-pc-linux-gnu", }.get(mozinfo.processor) if machtype: supps_file2 = os.path.join(supps_dir, machtype + ".sup") if os.path.isfile(supps_file2): valgrind_args.append("--suppressions=" + supps_file2) exitcode = None timeout = 1800 binary_not_found_exception = None try: runner = FirefoxRunner( profile=profile, binary=self.get_binary_path(), cmdargs=firefox_args, env=env, process_args=kp_kwargs, ) runner.start(debug_args=valgrind_args) exitcode = runner.wait(timeout=timeout) except BinaryNotFoundException as e: binary_not_found_exception = e finally: errs = outputHandler.error_count supps = outputHandler.suppression_count if errs != supps: status = 1 # turns the TBPL job orange self.log( logging.ERROR, "valgrind-fail-parsing", { "errs": errs, "supps": supps }, "TEST-UNEXPECTED-FAIL | valgrind-test | error parsing: {errs} errors " "seen, but {supps} generated suppressions seen", ) elif errs == 0: status = 0 self.log( logging.INFO, "valgrind-pass", {}, "TEST-PASS | valgrind-test | valgrind found no errors", ) else: status = 1 # turns the TBPL job orange # We've already printed details of the errors. if binary_not_found_exception: status = 2 # turns the TBPL job red self.log( logging.ERROR, "valgrind-fail-errors", {"error": str(binary_not_found_exception)}, "TEST-UNEXPECTED-FAIL | valgrind-test | {error}", ) self.log( logging.INFO, "valgrind-fail-errors", {"help": binary_not_found_exception.help()}, "{help}", ) elif exitcode is None: status = 2 # turns the TBPL job red self.log( logging.ERROR, "valgrind-fail-timeout", {"timeout": timeout}, "TEST-UNEXPECTED-FAIL | valgrind-test | Valgrind timed out " "(reached {timeout} second limit)", ) elif exitcode != 0: status = 2 # turns the TBPL job red self.log( logging.ERROR, "valgrind-fail-errors", {"exitcode": exitcode}, "TEST-UNEXPECTED-FAIL | valgrind-test | non-zero exit code " "from Valgrind: {exitcode}", ) httpd.stop() return status
class TPSFirefoxRunner(object): PROCESS_TIMEOUT = 240 def __init__(self, binary): if binary is not None and ('http://' in binary or 'ftp://' in binary): self.url = binary self.binary = None else: self.url = None self.binary = binary self.runner = None self.installdir = None def __del__(self): if self.installdir: shutil.rmtree(self.installdir, True) def download_build(self, installdir='downloadedbuild', appname='firefox', macAppName='Minefield.app'): self.installdir = os.path.abspath(installdir) buildName = os.path.basename(self.url) pathToBuild = os.path.join(os.path.dirname(os.path.abspath(__file__)), buildName) # delete the build if it already exists if os.access(pathToBuild, os.F_OK): os.remove(pathToBuild) # download the build print "downloading build" download_url(self.url, pathToBuild) # install the build print "installing %s" % pathToBuild shutil.rmtree(self.installdir, True) MozInstaller(src=pathToBuild, dest=self.installdir, dest_app=macAppName) # remove the downloaded archive os.remove(pathToBuild) # calculate path to binary platform = get_platform() if platform['name'] == 'Mac': binary = '%s/%s/Contents/MacOS/%s-bin' % (installdir, macAppName, appname) else: binary = '%s/%s/%s%s' % (installdir, appname, appname, '.exe' if platform['name'] == 'Windows' else '') return binary def run(self, profile=None, timeout=PROCESS_TIMEOUT, env=None, args=None): """Runs the given FirefoxRunner with the given Profile, waits for completion, then returns the process exit code """ if profile is None: profile = Profile() self.profile = profile if self.binary is None and self.url: self.binary = self.download_build() if self.runner is None: self.runner = FirefoxRunner(self.profile, binary=self.binary) self.runner.profile = self.profile if env is not None: self.runner.env.update(env) if args is not None: self.runner.cmdargs = copy.copy(args) self.runner.start() status = self.runner.process_handler.waitForFinish(timeout=timeout) return status
class FirefoxBrowser(Browser): used_ports = set() init_timeout = 60 def __init__(self, logger, binary, prefs_root, debug_info=None, symbols_path=None, stackwalk_binary=None, certutil_binary=None, ca_certificate_path=None, e10s=False): Browser.__init__(self, logger) self.binary = binary self.prefs_root = prefs_root self.marionette_port = None self.runner = None self.debug_info = debug_info self.profile = None self.symbols_path = symbols_path self.stackwalk_binary = stackwalk_binary self.ca_certificate_path = ca_certificate_path self.certutil_binary = certutil_binary self.e10s = e10s def start(self): self.marionette_port = get_free_port(2828, exclude=self.used_ports) self.used_ports.add(self.marionette_port) env = os.environ.copy() env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1" locations = ServerLocations(filename=os.path.join(here, "server-locations.txt")) preferences = self.load_prefs() self.profile = FirefoxProfile(locations=locations, preferences=preferences) self.profile.set_preferences({"marionette.defaultPrefs.enabled": True, "marionette.defaultPrefs.port": self.marionette_port, "dom.disable_open_during_load": False, "network.dns.localDomains": ",".join(hostnames), "network.proxy.type": 0, "places.history.enabled": False}) if self.e10s: self.profile.set_preferences({"browser.tabs.remote.autostart": True}) # Bug 1262954: winxp + e10s, disable hwaccel if (self.e10s and platform.system() in ("Windows", "Microsoft") and '5.1' in platform.version()): self.profile.set_preferences({"layers.acceleration.disabled": True}) if self.ca_certificate_path is not None: self.setup_ssl() debug_args, cmd = browser_command(self.binary, [cmd_arg("marionette"), "about:blank"], self.debug_info) self.runner = FirefoxRunner(profile=self.profile, binary=cmd[0], cmdargs=cmd[1:], env=env, process_class=ProcessHandler, process_args={"processOutputLine": [self.on_output]}) self.logger.debug("Starting Firefox") self.runner.start(debug_args=debug_args, interactive=self.debug_info and self.debug_info.interactive) self.logger.debug("Firefox Started") def load_prefs(self): prefs_path = os.path.join(self.prefs_root, "prefs_general.js") if os.path.exists(prefs_path): preferences = Preferences.read_prefs(prefs_path) else: self.logger.warning("Failed to find base prefs file in %s" % prefs_path) preferences = [] return preferences def stop(self): self.logger.debug("Stopping browser") if self.runner is not None: try: self.runner.stop() except OSError: # This can happen on Windows if the process is already dead pass def pid(self): if self.runner.process_handler is None: return None try: return self.runner.process_handler.pid except AttributeError: return None def on_output(self, line): """Write a line of output from the firefox process to the log""" self.logger.process_output(self.pid(), line.decode("utf8", "replace"), command=" ".join(self.runner.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.marionette_port is not None return ExecutorBrowser, {"marionette_port": self.marionette_port} def log_crash(self, process, test): dump_dir = os.path.join(self.profile.profile, "minidumps") mozcrash.log_crashes(self.logger, dump_dir, symbols_path=self.symbols_path, stackwalk_binary=self.stackwalk_binary, process=process, test=test) def setup_ssl(self): """Create a certificate database to use in the test profile. This is configured to trust the CA Certificate that has signed the web-platform.test server certificate.""" self.logger.info("Setting up ssl") # Make sure the certutil libraries from the source tree are loaded when using a # local copy of certutil # TODO: Maybe only set this if certutil won't launch? env = os.environ.copy() certutil_dir = os.path.dirname(self.binary) if mozinfo.isMac: env_var = "DYLD_LIBRARY_PATH" elif mozinfo.isUnix: env_var = "LD_LIBRARY_PATH" else: env_var = "PATH" env[env_var] = (os.path.pathsep.join([certutil_dir, env[env_var]]) if env_var in env else certutil_dir).encode( sys.getfilesystemencoding() or 'utf-8', 'replace') def certutil(*args): cmd = [self.certutil_binary] + list(args) self.logger.process_output("certutil", subprocess.check_output(cmd, env=env, stderr=subprocess.STDOUT), " ".join(cmd)) pw_path = os.path.join(self.profile.profile, ".crtdbpw") with open(pw_path, "w") as f: # Use empty password for certificate db f.write("\n") cert_db_path = self.profile.profile # Create a new certificate db certutil("-N", "-d", cert_db_path, "-f", pw_path) # Add the CA certificate to the database and mark as trusted to issue server certs certutil("-A", "-d", cert_db_path, "-f", pw_path, "-t", "CT,,", "-n", "web-platform-tests", "-i", self.ca_certificate_path) # List all certs in the database certutil("-L", "-d", cert_db_path)
class FirefoxBrowser(Browser): used_ports = set() init_timeout = 70 shutdown_timeout = 70 def __init__(self, logger, binary, prefs_root, test_type, extra_prefs=None, debug_info=None, symbols_path=None, stackwalk_binary=None, certutil_binary=None, ca_certificate_path=None, e10s=False, lsan_dir=None, stackfix_dir=None, binary_args=None, timeout_multiplier=None, leak_check=False, asan=False, stylo_threads=1, chaos_mode_flags=None, config=None, browser_channel="nightly", headless=None, **kwargs): Browser.__init__(self, logger) self.binary = binary self.prefs_root = prefs_root self.test_type = test_type self.extra_prefs = extra_prefs self.marionette_port = None self.runner = None self.debug_info = debug_info self.profile = None self.symbols_path = symbols_path self.stackwalk_binary = stackwalk_binary self.ca_certificate_path = ca_certificate_path self.certutil_binary = certutil_binary self.e10s = e10s self.binary_args = binary_args self.config = config if stackfix_dir: self.stack_fixer = get_stack_fixer_function( stackfix_dir, self.symbols_path) else: self.stack_fixer = None if timeout_multiplier: self.init_timeout = self.init_timeout * timeout_multiplier self.asan = asan self.lsan_dir = lsan_dir self.lsan_allowed = None self.lsan_max_stack_depth = None self.mozleak_allowed = None self.mozleak_thresholds = None self.leak_check = leak_check self.leak_report_file = None self.lsan_handler = None self.stylo_threads = stylo_threads self.chaos_mode_flags = chaos_mode_flags self.browser_channel = browser_channel self.headless = headless def settings(self, test): return { "check_leaks": self.leak_check and not test.leaks, "lsan_allowed": test.lsan_allowed, "lsan_max_stack_depth": test.lsan_max_stack_depth, "mozleak_allowed": self.leak_check and test.mozleak_allowed, "mozleak_thresholds": self.leak_check and test.mozleak_threshold } def start(self, group_metadata=None, **kwargs): if group_metadata is None: group_metadata = {} self.group_metadata = group_metadata self.lsan_allowed = kwargs.get("lsan_allowed") self.lsan_max_stack_depth = kwargs.get("lsan_max_stack_depth") self.mozleak_allowed = kwargs.get("mozleak_allowed") self.mozleak_thresholds = kwargs.get("mozleak_thresholds") if self.marionette_port is None: self.marionette_port = get_free_port(2828, exclude=self.used_ports) self.used_ports.add(self.marionette_port) if self.asan: self.lsan_handler = mozleak.LSANLeaks( self.logger, scope=group_metadata.get("scope", "/"), allowed=self.lsan_allowed, maxNumRecordedFrames=self.lsan_max_stack_depth) env = test_environment(xrePath=os.path.dirname(self.binary), debugger=self.debug_info is not None, log=self.logger, lsanPath=self.lsan_dir) env["STYLO_THREADS"] = str(self.stylo_threads) if self.chaos_mode_flags is not None: env["MOZ_CHAOSMODE"] = str(self.chaos_mode_flags) if self.headless: env["MOZ_HEADLESS"] = "1" preferences = self.load_prefs() self.profile = FirefoxProfile(preferences=preferences) self.profile.set_preferences({ "marionette.port": self.marionette_port, "network.dns.localDomains": ",".join(self.config.domains_set), "dom.file.createInChild": True, # TODO: Remove preferences once Firefox 64 is stable (Bug 905404) "network.proxy.type": 0, "places.history.enabled": False, "network.preload": True, }) if self.e10s: self.profile.set_preferences( {"browser.tabs.remote.autostart": True}) if self.test_type == "reftest": self.profile.set_preferences( {"layout.interruptible-reflow.enabled": False}) if self.leak_check: self.leak_report_file = os.path.join( self.profile.profile, "runtests_leaks_%s.log" % os.getpid()) if os.path.exists(self.leak_report_file): os.remove(self.leak_report_file) env["XPCOM_MEM_BLOAT_LOG"] = self.leak_report_file else: self.leak_report_file = None # Bug 1262954: winxp + e10s, disable hwaccel if (self.e10s and platform.system() in ("Windows", "Microsoft") and '5.1' in platform.version()): self.profile.set_preferences( {"layers.acceleration.disabled": True}) if self.ca_certificate_path is not None: self.setup_ssl() args = self.binary_args[:] if self.binary_args else [] args += [cmd_arg("marionette"), "about:blank"] debug_args, cmd = browser_command(self.binary, args, self.debug_info) self.runner = FirefoxRunner( profile=self.profile, binary=cmd[0], cmdargs=cmd[1:], env=env, process_class=ProcessHandler, process_args={"processOutputLine": [self.on_output]}) self.logger.debug("Starting Firefox") self.runner.start(debug_args=debug_args, interactive=self.debug_info and self.debug_info.interactive) self.logger.debug("Firefox Started") def load_prefs(self): prefs = Preferences() pref_paths = [] profiles = os.path.join(self.prefs_root, 'profiles.json') if os.path.isfile(profiles): with open(profiles, 'r') as fh: for name in json.load(fh)['web-platform-tests']: if self.browser_channel in (None, 'nightly'): pref_paths.append( os.path.join(self.prefs_root, name, 'user.js')) elif name != 'unittest-features': pref_paths.append( os.path.join(self.prefs_root, name, 'user.js')) else: # Old preference files used before the creation of profiles.json (remove when no longer supported) legacy_pref_paths = ( os.path.join( self.prefs_root, 'prefs_general.js'), # Used in Firefox 60 and below os.path.join(self.prefs_root, 'common', 'user.js'), # Used in Firefox 61 ) for path in legacy_pref_paths: if os.path.isfile(path): pref_paths.append(path) for path in pref_paths: if os.path.exists(path): prefs.add(Preferences.read_prefs(path)) else: self.logger.warning("Failed to find base prefs file in %s" % path) # Add any custom preferences prefs.add(self.extra_prefs, cast=True) return prefs() def stop(self, force=False): if self.runner is not None and self.runner.is_running(): try: # For Firefox we assume that stopping the runner prompts the # browser to shut down. This allows the leak log to be written for clean, stop_f in [ (True, lambda: self.runner.wait(self.shutdown_timeout)), (False, lambda: self.runner.stop(signal.SIGTERM)), (False, lambda: self.runner.stop(signal.SIGKILL)) ]: if not force or not clean: retcode = stop_f() if retcode is not None: self.logger.info( "Browser exited with return code %s" % retcode) break except OSError: # This can happen on Windows if the process is already dead pass self.process_leaks() self.logger.debug("stopped") def process_leaks(self): self.logger.info("PROCESS LEAKS %s" % self.leak_report_file) if self.lsan_handler: self.lsan_handler.process() if self.leak_report_file is not None: mozleak.process_leak_log(self.leak_report_file, leak_thresholds=self.mozleak_thresholds, ignore_missing_leaks=["gmplugin"], log=self.logger, stack_fixer=self.stack_fixer, scope=self.group_metadata.get("scope"), allowed=self.mozleak_allowed) def pid(self): if self.runner.process_handler is None: return None try: return self.runner.process_handler.pid except AttributeError: return None def on_output(self, line): """Write a line of output from the firefox process to the log""" if "GLib-GObject-CRITICAL" in line: return if line: data = line.decode("utf8", "replace") if self.stack_fixer: data = self.stack_fixer(data) if self.lsan_handler: data = self.lsan_handler.log(data) if data is not None: self.logger.process_output(self.pid(), data, command=" ".join( self.runner.command)) def is_alive(self): if self.runner: return self.runner.is_running() return False def cleanup(self, force=False): self.stop(force) def executor_browser(self): assert self.marionette_port is not None return ExecutorBrowser, {"marionette_port": self.marionette_port} def check_crash(self, process, test): dump_dir = os.path.join(self.profile.profile, "minidumps") return bool( mozcrash.log_crashes(self.logger, dump_dir, symbols_path=self.symbols_path, stackwalk_binary=self.stackwalk_binary, process=process, test=test)) def setup_ssl(self): """Create a certificate database to use in the test profile. This is configured to trust the CA Certificate that has signed the web-platform.test server certificate.""" if self.certutil_binary is None: self.logger.info( "--certutil-binary not supplied; Firefox will not check certificates" ) return self.logger.info("Setting up ssl") # Make sure the certutil libraries from the source tree are loaded when using a # local copy of certutil # TODO: Maybe only set this if certutil won't launch? env = os.environ.copy() certutil_dir = os.path.dirname(self.binary or self.certutil_binary) if mozinfo.isMac: env_var = "DYLD_LIBRARY_PATH" elif mozinfo.isUnix: env_var = "LD_LIBRARY_PATH" else: env_var = "PATH" env[env_var] = (os.path.pathsep.join([certutil_dir, env[env_var]]) if env_var in env else certutil_dir).encode( sys.getfilesystemencoding() or 'utf-8', 'replace') def certutil(*args): cmd = [self.certutil_binary] + list(args) self.logger.process_output( "certutil", subprocess.check_output(cmd, env=env, stderr=subprocess.STDOUT), " ".join(cmd)) pw_path = os.path.join(self.profile.profile, ".crtdbpw") with open(pw_path, "w") as f: # Use empty password for certificate db f.write("\n") cert_db_path = self.profile.profile # Create a new certificate db certutil("-N", "-d", cert_db_path, "-f", pw_path) # Add the CA certificate to the database and mark as trusted to issue server certs certutil("-A", "-d", cert_db_path, "-f", pw_path, "-t", "CT,,", "-n", "web-platform-tests", "-i", self.ca_certificate_path) # List all certs in the database certutil("-L", "-d", cert_db_path)
def valgrind_test(self, suppressions): from mozfile import TemporaryDirectory from mozhttpd import MozHttpd from mozprofile import FirefoxProfile, Preferences from mozprofile.permissions import ServerLocations from mozrunner import FirefoxRunner from mozrunner.utils import findInPath from six import string_types from valgrind.output_handler import OutputHandler build_dir = os.path.join(self.topsrcdir, 'build') # XXX: currently we just use the PGO inputs for Valgrind runs. This may # change in the future. httpd = MozHttpd(docroot=os.path.join(build_dir, 'pgo')) httpd.start(block=False) with TemporaryDirectory() as profilePath: # TODO: refactor this into mozprofile profile_data_dir = os.path.join(self.topsrcdir, 'testing', 'profiles') with open(os.path.join(profile_data_dir, 'profiles.json'), 'r') as fh: base_profiles = json.load(fh)['valgrind'] prefpaths = [ os.path.join(profile_data_dir, profile, 'user.js') for profile in base_profiles ] prefs = {} for path in prefpaths: prefs.update(Preferences.read_prefs(path)) interpolation = { 'server': '%s:%d' % httpd.httpd.server_address, } for k, v in prefs.items(): if isinstance(v, string_types): v = v.format(**interpolation) prefs[k] = Preferences.cast(v) quitter = os.path.join(self.topsrcdir, 'tools', 'quitter', '*****@*****.**') locations = ServerLocations() locations.add_host(host='127.0.0.1', port=httpd.httpd.server_port, options='primary') profile = FirefoxProfile(profile=profilePath, preferences=prefs, addons=[quitter], locations=locations) firefox_args = [httpd.get_url()] env = os.environ.copy() env['G_SLICE'] = 'always-malloc' env['MOZ_CC_RUN_DURING_SHUTDOWN'] = '1' env['MOZ_CRASHREPORTER_NO_REPORT'] = '1' env['MOZ_DISABLE_NONLOCAL_CONNECTIONS'] = '1' env['XPCOM_DEBUG_BREAK'] = 'warn' env.update(self.extra_environment_variables) outputHandler = OutputHandler(self.log) kp_kwargs = {'processOutputLine': [outputHandler]} valgrind = 'valgrind' if not os.path.exists(valgrind): valgrind = findInPath(valgrind) valgrind_args = [ valgrind, '--sym-offsets=yes', '--smc-check=all-non-file', '--vex-iropt-register-updates=allregs-at-mem-access', '--gen-suppressions=all', '--num-callers=36', '--leak-check=full', '--show-possibly-lost=no', '--track-origins=yes', '--trace-children=yes', '-v', # Enable verbosity to get the list of used suppressions # Avoid excessive delays in the presence of spinlocks. # See bug 1309851. '--fair-sched=yes', # Keep debuginfo after library unmap. See bug 1382280. '--keep-debuginfo=yes', # Reduce noise level on rustc and/or LLVM compiled code. # See bug 1365915 '--expensive-definedness-checks=yes', ] for s in suppressions: valgrind_args.append('--suppressions=' + s) supps_dir = os.path.join(build_dir, 'valgrind') supps_file1 = os.path.join(supps_dir, 'cross-architecture.sup') valgrind_args.append('--suppressions=' + supps_file1) if mozinfo.os == 'linux': machtype = { 'x86_64': 'x86_64-pc-linux-gnu', 'x86': 'i386-pc-linux-gnu', }.get(mozinfo.processor) if machtype: supps_file2 = os.path.join(supps_dir, machtype + '.sup') if os.path.isfile(supps_file2): valgrind_args.append('--suppressions=' + supps_file2) exitcode = None timeout = 1800 try: runner = FirefoxRunner(profile=profile, binary=self.get_binary_path(), cmdargs=firefox_args, env=env, process_args=kp_kwargs) runner.start(debug_args=valgrind_args) exitcode = runner.wait(timeout=timeout) finally: errs = outputHandler.error_count supps = outputHandler.suppression_count if errs != supps: status = 1 # turns the TBPL job orange self.log( logging.ERROR, 'valgrind-fail-parsing', { 'errs': errs, 'supps': supps }, 'TEST-UNEXPECTED-FAIL | valgrind-test | error parsing: {errs} errors ' 'seen, but {supps} generated suppressions seen') elif errs == 0: status = 0 self.log( logging.INFO, 'valgrind-pass', {}, 'TEST-PASS | valgrind-test | valgrind found no errors') else: status = 1 # turns the TBPL job orange # We've already printed details of the errors. if exitcode is None: status = 2 # turns the TBPL job red self.log( logging.ERROR, 'valgrind-fail-timeout', {'timeout': timeout}, 'TEST-UNEXPECTED-FAIL | valgrind-test | Valgrind timed out ' '(reached {timeout} second limit)') elif exitcode != 0: status = 2 # turns the TBPL job red self.log( logging.ERROR, 'valgrind-fail-errors', {'exitcode': exitcode}, 'TEST-UNEXPECTED-FAIL | valgrind-test | non-zero exit code ' 'from Valgrind: {exitcode}') httpd.stop() return status
def valgrind_test(self, suppressions): import json import re import sys import tempfile from mozbuild.base import MozbuildObject from mozfile import TemporaryDirectory from mozhttpd import MozHttpd from mozprofile import FirefoxProfile, Preferences from mozprofile.permissions import ServerLocations from mozrunner import FirefoxRunner from mozrunner.utils import findInPath build_dir = os.path.join(self.topsrcdir, "build") # XXX: currently we just use the PGO inputs for Valgrind runs. This may # change in the future. httpd = MozHttpd(docroot=os.path.join(build_dir, "pgo")) httpd.start(block=False) with TemporaryDirectory() as profilePath: # TODO: refactor this into mozprofile prefpath = os.path.join(self.topsrcdir, "testing", "profiles", "prefs_general.js") prefs = {} prefs.update(Preferences.read_prefs(prefpath)) interpolation = {"server": "%s:%d" % httpd.httpd.server_address, "OOP": "false"} prefs = json.loads(json.dumps(prefs) % interpolation) for pref in prefs: prefs[pref] = Preferences.cast(prefs[pref]) quitter = os.path.join(self.distdir, "xpi-stage", "quitter") locations = ServerLocations() locations.add_host(host="127.0.0.1", port=httpd.httpd.server_port, options="primary") profile = FirefoxProfile(profile=profilePath, preferences=prefs, addons=[quitter], locations=locations) firefox_args = [httpd.get_url()] env = os.environ.copy() env["G_SLICE"] = "always-malloc" env["XPCOM_CC_RUN_DURING_SHUTDOWN"] = "1" env["MOZ_CRASHREPORTER_NO_REPORT"] = "1" env["XPCOM_DEBUG_BREAK"] = "warn" class OutputHandler(object): def __init__(self): self.found_errors = False def __call__(self, line): print(line) m = re.match(r".*ERROR SUMMARY: [1-9]\d* errors from \d+ contexts", line) if m: self.found_errors = True outputHandler = OutputHandler() kp_kwargs = {"processOutputLine": [outputHandler]} valgrind = "valgrind" if not os.path.exists(valgrind): valgrind = findInPath(valgrind) valgrind_args = [ valgrind, "--smc-check=all-non-file", "--vex-iropt-register-updates=allregs-at-mem-access", "--gen-suppressions=all", "--num-callers=20", "--leak-check=full", "--show-possibly-lost=no", "--track-origins=yes", ] for s in suppressions: valgrind_args.append("--suppressions=" + s) supps_dir = os.path.join(build_dir, "valgrind") supps_file1 = os.path.join(supps_dir, "cross-architecture.sup") valgrind_args.append("--suppressions=" + supps_file1) # MACHTYPE is an odd bash-only environment variable that doesn't # show up in os.environ, so we have to get it another way. machtype = subprocess.check_output(["bash", "-c", "echo $MACHTYPE"]).rstrip() supps_file2 = os.path.join(supps_dir, machtype + ".sup") if os.path.isfile(supps_file2): valgrind_args.append("--suppressions=" + supps_file2) exitcode = None try: runner = FirefoxRunner( profile=profile, binary=self.get_binary_path(), cmdargs=firefox_args, env=env, kp_kwargs=kp_kwargs ) runner.start(debug_args=valgrind_args) exitcode = runner.wait() finally: if not outputHandler.found_errors: status = 0 print("TEST-PASS | valgrind-test | valgrind found no errors") else: status = 1 # turns the TBPL job orange print("TEST-UNEXPECTED-FAIL | valgrind-test | valgrind found errors") if exitcode != 0: status = 2 # turns the TBPL job red print("TEST-UNEXPECTED-FAIL | valgrind-test | non-zero exit code from Valgrind") httpd.stop() return status
def run(): parser = argparse.ArgumentParser(description='Run crawler') parser.add_argument('-b', '--binary', type=str, help='path to the Firefox binary') parser.add_argument('-a', '--abpdir', type=str, help='path to the Adblock Plus repository') parser.add_argument( '-f', '--filters', metavar='url', type=str, nargs='+', default=[ "https://easylist-downloads.adblockplus.org/easylist.txt", "https://easylist-downloads.adblockplus.org/exceptionrules.txt" ], help= 'filter lists to install in Adblock Plus. The arguments can also have the format path=url, the data will be read from the specified path then.' ) parser.add_argument('-t', '--timeout', type=int, default=300, help='Load timeout (seconds)') parser.add_argument('-x', '--maxtabs', type=int, default=15, help='Maximal number of tabs to open in parallel') parser.add_argument('list', type=str, help='URL list to process') parser.add_argument('outdir', type=str, help='directory to write data into') parameters = parser.parse_args() import buildtools.packagerGecko as packager cleanup = [] try: base_dir = os.path.dirname(__file__) handle, crawlerxpi = tempfile.mkstemp(suffix='.xpi') os.close(handle) cleanup.append(crawlerxpi) packager.createBuild(base_dir, outFile=crawlerxpi, releaseBuild=True) abpxpi = 'https://addons.mozilla.org/firefox/downloads/latest/1865/addon-1865-latest.xpi' if parameters.abpdir: handle, abpxpi = tempfile.mkstemp(suffix='.xpi') os.close(handle) cleanup.append(abpxpi) packager.createBuild(parameters.abpdir, outFile=abpxpi, releaseBuild=True) profile = FirefoxProfile(addons=[ crawlerxpi, abpxpi, ], preferences={ 'browser.uitour.enabled': False, 'prompts.tab_modal.enabled': False, }) abpsettings = os.path.join(profile.profile, 'adblockplus') os.makedirs(abpsettings) with open(os.path.join(abpsettings, 'patterns.ini'), 'w') as handle: print >> handle, '# Adblock Plus preferences' print >> handle, 'version=4' for url in parameters.filters: if '=' in url: path, url = url.split('=', 1) with open(path, 'r') as source: data = source.read() else: data = urllib.urlopen(url).read() print >> handle, '[Subscription]' print >> handle, 'url=%s' % url print >> handle, '[Subscription filters]' print >> handle, '\n'.join(data.splitlines()[1:]) finally: for path in cleanup: os.unlink(path) server = None try: port = random.randrange(2000, 60000) print "Communicating with client on port %i" % port app = CrawlerApp(parameters) server = make_server('localhost', port, app) app.server = server threading.Thread(target=lambda: server.serve_forever()).start() runner = FirefoxRunner( profile=profile, binary=parameters.binary, cmdargs=['--crawler-port', str(port)], env=dict(os.environ, MOZ_CRASHREPORTER_DISABLE='1'), ) while app.urls: runner.start() runner.wait() finally: if server: server.shutdown() profile.cleanup()
def valgrind_test(self, suppressions): import json import sys import tempfile from mozbuild.base import MozbuildObject from mozfile import TemporaryDirectory from mozhttpd import MozHttpd from mozprofile import FirefoxProfile, Preferences from mozprofile.permissions import ServerLocations from mozrunner import FirefoxRunner from mozrunner.utils import findInPath from valgrind.output_handler import OutputHandler build_dir = os.path.join(self.topsrcdir, 'build') # XXX: currently we just use the PGO inputs for Valgrind runs. This may # change in the future. httpd = MozHttpd(docroot=os.path.join(build_dir, 'pgo')) httpd.start(block=False) with TemporaryDirectory() as profilePath: #TODO: refactor this into mozprofile prefpath = os.path.join(self.topsrcdir, 'testing', 'profiles', 'prefs_general.js') prefs = {} prefs.update(Preferences.read_prefs(prefpath)) interpolation = { 'server': '%s:%d' % httpd.httpd.server_address, 'OOP': 'false'} prefs = json.loads(json.dumps(prefs) % interpolation) for pref in prefs: prefs[pref] = Preferences.cast(prefs[pref]) quitter = os.path.join(self.topsrcdir, 'tools', 'quitter', '*****@*****.**') locations = ServerLocations() locations.add_host(host='127.0.0.1', port=httpd.httpd.server_port, options='primary') profile = FirefoxProfile(profile=profilePath, preferences=prefs, addons=[quitter], locations=locations) firefox_args = [httpd.get_url()] env = os.environ.copy() env['G_SLICE'] = 'always-malloc' env['MOZ_CC_RUN_DURING_SHUTDOWN'] = '1' env['MOZ_CRASHREPORTER_NO_REPORT'] = '1' env['XPCOM_DEBUG_BREAK'] = 'warn' env.update(self.extra_environment_variables) outputHandler = OutputHandler(self.log) kp_kwargs = {'processOutputLine': [outputHandler]} valgrind = 'valgrind' if not os.path.exists(valgrind): valgrind = findInPath(valgrind) valgrind_args = [ valgrind, '--smc-check=all-non-file', '--vex-iropt-register-updates=allregs-at-mem-access', '--gen-suppressions=all', '--num-callers=36', '--leak-check=full', '--show-possibly-lost=no', '--track-origins=yes', '--trace-children=yes', '-v', # Enable verbosity to get the list of used suppressions ] for s in suppressions: valgrind_args.append('--suppressions=' + s) supps_dir = os.path.join(build_dir, 'valgrind') supps_file1 = os.path.join(supps_dir, 'cross-architecture.sup') valgrind_args.append('--suppressions=' + supps_file1) # MACHTYPE is an odd bash-only environment variable that doesn't # show up in os.environ, so we have to get it another way. machtype = subprocess.check_output(['bash', '-c', 'echo $MACHTYPE']).rstrip() supps_file2 = os.path.join(supps_dir, machtype + '.sup') if os.path.isfile(supps_file2): valgrind_args.append('--suppressions=' + supps_file2) exitcode = None timeout = 1800 try: runner = FirefoxRunner(profile=profile, binary=self.get_binary_path(), cmdargs=firefox_args, env=env, process_args=kp_kwargs) runner.start(debug_args=valgrind_args) exitcode = runner.wait(timeout=timeout) finally: errs = outputHandler.error_count supps = outputHandler.suppression_count if errs != supps: status = 1 # turns the TBPL job orange self.log(logging.ERROR, 'valgrind-fail-parsing', {'errs': errs, 'supps': supps}, 'TEST-UNEXPECTED-FAIL | valgrind-test | error parsing: {errs} errors seen, but {supps} generated suppressions seen') elif errs == 0: status = 0 self.log(logging.INFO, 'valgrind-pass', {}, 'TEST-PASS | valgrind-test | valgrind found no errors') else: status = 1 # turns the TBPL job orange # We've already printed details of the errors. if exitcode == None: status = 2 # turns the TBPL job red self.log(logging.ERROR, 'valgrind-fail-timeout', {'timeout': timeout}, 'TEST-UNEXPECTED-FAIL | valgrind-test | Valgrind timed out (reached {timeout} second limit)') elif exitcode != 0: status = 2 # turns the TBPL job red self.log(logging.ERROR, 'valgrind-fail-errors', {}, 'TEST-UNEXPECTED-FAIL | valgrind-test | non-zero exit code from Valgrind') httpd.stop() return status
class MuletReftest(RefTest): build_type = "mulet" marionette = None def __init__(self, marionette_args): RefTest.__init__(self) self.last_test = os.path.basename(__file__) self.marionette_args = marionette_args self.profile = None self.runner = None self.test_script = os.path.join(here, 'b2g_start_script.js') self.timeout = None def run_marionette_script(self): self.marionette = Marionette(**self.marionette_args) assert(self.marionette.wait_for_port()) self.marionette.start_session() if self.build_type == "mulet": self._wait_for_homescreen(timeout=300) self._unlockScreen() self.marionette.set_context(self.marionette.CONTEXT_CHROME) if os.path.isfile(self.test_script): f = open(self.test_script, 'r') self.test_script = f.read() f.close() self.marionette.execute_script(self.test_script) def run_tests(self, tests, options): manifests = self.resolver.resolveManifests(options, tests) self.profile = self.create_profile(options, manifests, profile_to_clone=options.profile) env = self.buildBrowserEnv(options, self.profile.profile) self._populate_logger(options) outputHandler = OutputHandler(self.log, options.utilityPath, symbolsPath=options.symbolsPath) kp_kwargs = { 'processOutputLine': [outputHandler], 'onTimeout': [self._on_timeout], 'kill_on_timeout': False } if not options.debugger: if not options.timeout: if mozinfo.info['debug']: options.timeout = 420 else: options.timeout = 300 self.timeout = options.timeout + 30.0 self.log.info("%s | Running tests: start." % os.path.basename(__file__)) cmd, args = self.build_command_line(options.app, ignore_window_size=options.ignoreWindowSize, browser_arg=options.browser_arg) self.runner = FirefoxRunner(profile=self.profile, binary=cmd, cmdargs=args, env=env, process_class=ProcessHandler, process_args=kp_kwargs, symbols_path=options.symbolsPath) status = 0 try: self.runner.start(outputTimeout=self.timeout) self.log.info("%s | Application pid: %d" % ( os.path.basename(__file__), self.runner.process_handler.pid)) # kick starts the reftest harness self.run_marionette_script() status = self.runner.wait() finally: self.runner.check_for_crashes(test_name=self.last_test) self.runner.cleanup() if status > 0: self.log.testFail("%s | application terminated with exit code %s" % ( self.last_test, status)) elif status < 0: self.log.info("%s | application killed with signal %s" % ( self.last_test, -status)) self.log.info("%s | Running tests: end." % os.path.basename(__file__)) return status def create_profile(self, options, manifests, profile_to_clone=None): profile = RefTest.createReftestProfile(self, options, manifests, profile_to_clone=profile_to_clone) prefs = {} # Turn off the locale picker screen prefs["browser.firstrun.show.localepicker"] = False if not self.build_type == "mulet": # FIXME: With Mulet we can't set this values since Gaia won't launch prefs["b2g.system_startup_url"] = \ "app://test-container.gaiamobile.org/index.html" prefs["b2g.system_manifest_url"] = \ "app://test-container.gaiamobile.org/manifest.webapp" # Make sure we disable system updates prefs["app.update.enabled"] = False prefs["app.update.url"] = "" # Disable webapp updates prefs["webapps.update.enabled"] = False # Disable tiles also prefs["browser.newtabpage.directory.source"] = "" prefs["browser.newtabpage.directory.ping"] = "" prefs["dom.ipc.tabs.disabled"] = False prefs["dom.mozBrowserFramesEnabled"] = True prefs["font.size.inflation.emPerLine"] = 0 prefs["font.size.inflation.minTwips"] = 0 prefs["network.dns.localDomains"] = "app://test-container.gaiamobile.org" prefs["reftest.browser.iframe.enabled"] = False prefs["reftest.remote"] = False # Set the extra prefs. profile.set_preferences(prefs) return profile def build_command_line(self, app, ignore_window_size=False, browser_arg=None): cmd = os.path.abspath(app) args = ['-marionette'] if browser_arg: args += [browser_arg] if not ignore_window_size: args.extend(['--screen', '800x1000']) if self.build_type == "mulet": args += ['-chrome', 'chrome://b2g/content/shell.html'] return cmd, args def _on_timeout(self): msg = "%s | application timed out after %s seconds with no output" self.log.testFail(msg % (self.last_test, self.timeout)) self.log.error("Force-terminating active process(es)."); # kill process to get a stack self.runner.stop(sig=signal.SIGABRT) def _unlockScreen(self): self.marionette.set_context(self.marionette.CONTEXT_CONTENT) self.marionette.import_script(os.path.abspath( os.path.join(__file__, os.path.pardir, "gaia_lock_screen.js"))) self.marionette.switch_to_frame() self.marionette.execute_async_script('GaiaLockScreen.unlock()') def _wait_for_homescreen(self, timeout): self.log.info("Waiting for home screen to load") Wait(self.marionette, timeout).until(expected.element_present( By.CSS_SELECTOR, '#homescreen[loading-state=false]'))
def run(): parser = argparse.ArgumentParser(description="Run crawler") parser.add_argument("-b", "--binary", type=str, help="path to the Firefox binary") parser.add_argument("-a", "--abpdir", type=str, help="path to the Adblock Plus repository") parser.add_argument( "-f", "--filters", metavar="url", type=str, nargs="+", default=[ "https://easylist-downloads.adblockplus.org/easylist.txt", "https://easylist-downloads.adblockplus.org/exceptionrules.txt", ], help="filter lists to install in Adblock Plus. The arguments can also have the format path=url, the data will be read from the specified path then.", ) parser.add_argument("-t", "--timeout", type=int, default=300, help="Load timeout (seconds)") parser.add_argument("-x", "--maxtabs", type=int, default=15, help="Maximal number of tabs to open in parallel") parser.add_argument("list", type=str, help="URL list to process") parser.add_argument("outdir", type=str, help="directory to write data into") parameters = parser.parse_args() import buildtools.packagerGecko as packager cleanup = [] try: base_dir = os.path.dirname(os.path.abspath(__file__)) handle, crawlerxpi = tempfile.mkstemp(suffix=".xpi") os.close(handle) cleanup.append(crawlerxpi) packager.createBuild(base_dir, outFile=crawlerxpi, releaseBuild=True) abpxpi = "https://addons.mozilla.org/firefox/downloads/latest/1865/addon-1865-latest.xpi" if parameters.abpdir: handle, abpxpi = tempfile.mkstemp(suffix=".xpi") os.close(handle) cleanup.append(abpxpi) packager.createBuild(parameters.abpdir, outFile=abpxpi, releaseBuild=True) profile = FirefoxProfile( addons=[crawlerxpi, abpxpi], preferences={ "browser.startup.homepage": "about:blank", "browser.tabs.warnOnCloseOtherTabs": False, "browser.uitour.enabled": False, "prompts.tab_modal.enabled": False, "startup.homepage_welcome_url": "about:blank", "startup.homepage_welcome_url.additional": "about:blank", "xpinstall.signatures.required": False, }, ) abpsettings = os.path.join(profile.profile, "adblockplus") os.makedirs(abpsettings) with open(os.path.join(abpsettings, "patterns.ini"), "w") as handle: print >> handle, "# Adblock Plus preferences" print >> handle, "version=4" for url in parameters.filters: if "=" in url: path, url = url.split("=", 1) with open(path, "r") as source: data = source.read() else: data = urllib.urlopen(url).read() print >> handle, "[Subscription]" print >> handle, "url=%s" % url print >> handle, "[Subscription filters]" print >> handle, "\n".join(data.splitlines()[1:]) finally: for path in cleanup: os.unlink(path) server = None try: port = random.randrange(2000, 60000) print "Communicating with client on port %i" % port app = CrawlerApp(parameters) server = make_server("localhost", port, app) app.server = server threading.Thread(target=lambda: server.serve_forever()).start() runner = FirefoxRunner( profile=profile, binary=parameters.binary, cmdargs=["--crawler-port", str(port)], env=dict(os.environ, MOZ_CRASHREPORTER_DISABLE="1"), ) while app.urls: runner.start() runner.wait() finally: if server: server.shutdown() profile.cleanup()
def valgrind_test(self, suppressions): import json import sys import tempfile from mozbuild.base import MozbuildObject from mozfile import TemporaryDirectory from mozhttpd import MozHttpd from mozprofile import FirefoxProfile, Preferences from mozprofile.permissions import ServerLocations from mozrunner import FirefoxRunner from mozrunner.utils import findInPath from valgrind.output_handler import OutputHandler build_dir = os.path.join(self.topsrcdir, "build") # XXX: currently we just use the PGO inputs for Valgrind runs. This may # change in the future. httpd = MozHttpd(docroot=os.path.join(build_dir, "pgo")) httpd.start(block=False) with TemporaryDirectory() as profilePath: # TODO: refactor this into mozprofile prefpath = os.path.join(self.topsrcdir, "testing", "profiles", "prefs_general.js") prefs = {} prefs.update(Preferences.read_prefs(prefpath)) interpolation = {"server": "%s:%d" % httpd.httpd.server_address, "OOP": "false"} prefs = json.loads(json.dumps(prefs) % interpolation) for pref in prefs: prefs[pref] = Preferences.cast(prefs[pref]) quitter = os.path.join(self.topsrcdir, "tools", "quitter", "*****@*****.**") locations = ServerLocations() locations.add_host(host="127.0.0.1", port=httpd.httpd.server_port, options="primary") profile = FirefoxProfile(profile=profilePath, preferences=prefs, addons=[quitter], locations=locations) firefox_args = [httpd.get_url()] env = os.environ.copy() env["G_SLICE"] = "always-malloc" env["MOZ_CC_RUN_DURING_SHUTDOWN"] = "1" env["MOZ_CRASHREPORTER_NO_REPORT"] = "1" env["XPCOM_DEBUG_BREAK"] = "warn" env.update(self.extra_environment_variables) outputHandler = OutputHandler(self.log) kp_kwargs = {"processOutputLine": [outputHandler]} valgrind = "valgrind" if not os.path.exists(valgrind): valgrind = findInPath(valgrind) valgrind_args = [ valgrind, "--smc-check=all-non-file", "--vex-iropt-register-updates=allregs-at-mem-access", "--gen-suppressions=all", "--num-callers=36", "--leak-check=full", "--show-possibly-lost=no", "--track-origins=yes", "--trace-children=yes", "-v", # Enable verbosity to get the list of used suppressions ] for s in suppressions: valgrind_args.append("--suppressions=" + s) supps_dir = os.path.join(build_dir, "valgrind") supps_file1 = os.path.join(supps_dir, "cross-architecture.sup") valgrind_args.append("--suppressions=" + supps_file1) # MACHTYPE is an odd bash-only environment variable that doesn't # show up in os.environ, so we have to get it another way. machtype = subprocess.check_output(["bash", "-c", "echo $MACHTYPE"]).rstrip() supps_file2 = os.path.join(supps_dir, machtype + ".sup") if os.path.isfile(supps_file2): valgrind_args.append("--suppressions=" + supps_file2) exitcode = None timeout = 1800 try: runner = FirefoxRunner( profile=profile, binary=self.get_binary_path(), cmdargs=firefox_args, env=env, process_args=kp_kwargs, ) runner.start(debug_args=valgrind_args) exitcode = runner.wait(timeout=timeout) finally: errs = outputHandler.error_count supps = outputHandler.suppression_count if errs != supps: status = 1 # turns the TBPL job orange self.log( logging.ERROR, "valgrind-fail-parsing", {"errs": errs, "supps": supps}, "TEST-UNEXPECTED-FAIL | valgrind-test | error parsing: {errs} errors seen, but {supps} generated suppressions seen", ) elif errs == 0: status = 0 self.log(logging.INFO, "valgrind-pass", {}, "TEST-PASS | valgrind-test | valgrind found no errors") else: status = 1 # turns the TBPL job orange # We've already printed details of the errors. if exitcode == None: status = 2 # turns the TBPL job red self.log( logging.ERROR, "valgrind-fail-timeout", {"timeout": timeout}, "TEST-UNEXPECTED-FAIL | valgrind-test | Valgrind timed out (reached {timeout} second limit)", ) elif exitcode != 0: status = 2 # turns the TBPL job red self.log( logging.ERROR, "valgrind-fail-errors", {}, "TEST-UNEXPECTED-FAIL | valgrind-test | non-zero exit code from Valgrind", ) httpd.stop() return status
self.log.debug("Creating Firefox profile and installing extensions") prefs = {"extensions.update.enabled" : "false"} mozProfile = FirefoxProfile(profile=self.profile, addons=["firebug.xpi", "fbtest.xpi"], preferences=prefs) self.profile = mozProfile.profile self.log.debug("Creating Firefox runner") mozRunner = FirefoxRunner(profile=mozProfile, binary=self.binary, cmdargs=["-no-remote", "-runFBTests", self.testlist], env=mozEnv) self.binary = mozRunner.binary if not self.appVersion: self.appdir, self.appVersion = self.get_app_info() # Disable the compatibility check on startup self.disable_compatibility_check() self.log.debug("Running '" + self.binary + " -no-remote -runFBTests " + self.testlist + "'") mozRunner.start() except Exception: self.log.error("Could not start Firefox") self.log.error(traceback.format_exc()) self.cleanup() raise # Find the log file timeout, logfile = 0, 0 # Wait up to 60 seconds for the log file to be initialized while not logfile and timeout < 60: try: for name in os.listdir(os.path.join(self.profile, "firebug", "fbtest", "logs")): logfile = open(os.path.join(self.profile, "firebug", "fbtest", "logs", name), 'r') except Exception: timeout += 1
class TPSFirefoxRunner(object): PROCESS_TIMEOUT = 240 def __init__(self, binary): if binary is not None and ('http://' in binary or 'ftp://' in binary): self.url = binary self.binary = None else: self.url = None self.binary = binary self.runner = None self.installdir = None def __del__(self): if self.installdir: shutil.rmtree(self.installdir, True) def download_url(self, url, dest=None): h = httplib2.Http() resp, content = h.request(url, "GET") if dest == None: dest = os.path.basename(url) local = open(dest, 'wb') local.write(content) local.close() return dest def download_build(self, installdir='downloadedbuild', appname='firefox'): self.installdir = os.path.abspath(installdir) buildName = os.path.basename(self.url) pathToBuild = os.path.join(os.path.dirname(os.path.abspath(__file__)), buildName) # delete the build if it already exists if os.access(pathToBuild, os.F_OK): os.remove(pathToBuild) # download the build print "downloading build" self.download_url(self.url, pathToBuild) # install the build print "installing %s" % pathToBuild shutil.rmtree(self.installdir, True) binary = mozinstall.install(src=pathToBuild, dest=self.installdir) # remove the downloaded archive os.remove(pathToBuild) return binary def run(self, profile=None, timeout=PROCESS_TIMEOUT, env=None, args=None): """Runs the given FirefoxRunner with the given Profile, waits for completion, then returns the process exit code """ if profile is None: profile = Profile() self.profile = profile if self.binary is None and self.url: self.binary = self.download_build() if self.runner is None: self.runner = FirefoxRunner(self.profile, binary=self.binary) self.runner.profile = self.profile if env is not None: self.runner.env.update(env) if args is not None: self.runner.cmdargs = copy.copy(args) self.runner.start() status = self.runner.process_handler.waitForFinish(timeout=timeout) return status
class FirefoxBrowser(Browser): used_ports = set() init_timeout = 60 shutdown_timeout = 60 def __init__(self, logger, binary, prefs_root, test_type, extra_prefs=None, debug_info=None, symbols_path=None, stackwalk_binary=None, certutil_binary=None, ca_certificate_path=None, e10s=False, stackfix_dir=None, binary_args=None, timeout_multiplier=None, leak_check=False, stylo_threads=1, chaos_mode_flags=None): Browser.__init__(self, logger) self.binary = binary self.prefs_root = prefs_root self.test_type = test_type self.extra_prefs = extra_prefs self.marionette_port = None self.runner = None self.debug_info = debug_info self.profile = None self.symbols_path = symbols_path self.stackwalk_binary = stackwalk_binary self.ca_certificate_path = ca_certificate_path self.certutil_binary = certutil_binary self.e10s = e10s self.binary_args = binary_args if stackfix_dir: self.stack_fixer = get_stack_fixer_function( stackfix_dir, self.symbols_path) else: self.stack_fixer = None if timeout_multiplier: self.init_timeout = self.init_timeout * timeout_multiplier self.leak_report_file = None self.leak_check = leak_check self.stylo_threads = stylo_threads self.chaos_mode_flags = chaos_mode_flags def settings(self, test): return {"check_leaks": self.leak_check and not test.leaks} def start(self, **kwargs): if self.marionette_port is None: self.marionette_port = get_free_port(2828, exclude=self.used_ports) self.used_ports.add(self.marionette_port) env = os.environ.copy() env["MOZ_CRASHREPORTER"] = "1" env["MOZ_CRASHREPORTER_SHUTDOWN"] = "1" env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1" env["STYLO_THREADS"] = str(self.stylo_threads) if self.chaos_mode_flags is not None: env["MOZ_CHAOSMODE"] = str(self.chaos_mode_flags) locations = ServerLocations( filename=os.path.join(here, "server-locations.txt")) preferences = self.load_prefs() self.profile = FirefoxProfile(locations=locations, preferences=preferences) self.profile.set_preferences({ "marionette.port": self.marionette_port, "dom.disable_open_during_load": False, "network.dns.localDomains": ",".join(hostnames), "network.proxy.type": 0, "places.history.enabled": False, "dom.send_after_paint_to_content": True, "network.preload": True }) if self.e10s: self.profile.set_preferences( {"browser.tabs.remote.autostart": True}) if self.test_type == "reftest": self.profile.set_preferences( {"layout.interruptible-reflow.enabled": False}) if self.leak_check and kwargs.get("check_leaks", True): self.leak_report_file = os.path.join(self.profile.profile, "runtests_leaks.log") if os.path.exists(self.leak_report_file): os.remove(self.leak_report_file) env["XPCOM_MEM_BLOAT_LOG"] = self.leak_report_file else: self.leak_report_file = None # Bug 1262954: winxp + e10s, disable hwaccel if (self.e10s and platform.system() in ("Windows", "Microsoft") and '5.1' in platform.version()): self.profile.set_preferences( {"layers.acceleration.disabled": True}) if self.ca_certificate_path is not None: self.setup_ssl() debug_args, cmd = browser_command( self.binary, self.binary_args if self.binary_args else [] + [cmd_arg("marionette"), "about:blank"], self.debug_info) self.runner = FirefoxRunner( profile=self.profile, binary=cmd[0], cmdargs=cmd[1:], env=env, process_class=ProcessHandler, process_args={"processOutputLine": [self.on_output]}) self.logger.debug("Starting Firefox") self.runner.start(debug_args=debug_args, interactive=self.debug_info and self.debug_info.interactive) self.logger.debug("Firefox Started") def load_prefs(self): prefs = Preferences() prefs_path = os.path.join(self.prefs_root, "prefs_general.js") if os.path.exists(prefs_path): prefs.add(Preferences.read_prefs(prefs_path)) else: self.logger.warning("Failed to find base prefs file in %s" % prefs_path) # Add any custom preferences prefs.add(self.extra_prefs, cast=True) return prefs() def stop(self, force=False): if self.runner is not None and self.runner.is_running(): try: # For Firefox we assume that stopping the runner prompts the # browser to shut down. This allows the leak log to be written for clean, stop_f in [ (True, lambda: self.runner.wait(self.shutdown_timeout)), (False, lambda: self.runner.stop(signal.SIGTERM)), (False, lambda: self.runner.stop(signal.SIGKILL)) ]: if not force or not clean: retcode = stop_f() if retcode is not None: self.logger.info( "Browser exited with return code %s" % retcode) break except OSError: # This can happen on Windows if the process is already dead pass self.logger.debug("stopped") def process_leaks(self): self.logger.debug("PROCESS LEAKS %s" % self.leak_report_file) if self.leak_report_file is None: return mozleak.process_leak_log( self.leak_report_file, leak_thresholds={ "default": 0, "tab": 10000, # See dependencies of bug 1051230. # GMP rarely gets a log, but when it does, it leaks a little. "geckomediaplugin": 20000, }, ignore_missing_leaks=["geckomediaplugin"], log=self.logger, stack_fixer=self.stack_fixer) def pid(self): if self.runner.process_handler is None: return None try: return self.runner.process_handler.pid except AttributeError: return None def on_output(self, line): """Write a line of output from the firefox process to the log""" data = line.decode("utf8", "replace") if self.stack_fixer: data = self.stack_fixer(data) self.logger.process_output(self.pid(), data, command=" ".join(self.runner.command)) def is_alive(self): if self.runner: return self.runner.is_running() return False def cleanup(self): self.stop() self.process_leaks() def executor_browser(self): assert self.marionette_port is not None return ExecutorBrowser, {"marionette_port": self.marionette_port} def check_for_crashes(self): dump_dir = os.path.join(self.profile.profile, "minidumps") return bool( mozcrash.check_for_crashes(dump_dir, symbols_path=self.symbols_path, stackwalk_binary=self.stackwalk_binary, quiet=True)) def log_crash(self, process, test): dump_dir = os.path.join(self.profile.profile, "minidumps") mozcrash.log_crashes(self.logger, dump_dir, symbols_path=self.symbols_path, stackwalk_binary=self.stackwalk_binary, process=process, test=test) def setup_ssl(self): """Create a certificate database to use in the test profile. This is configured to trust the CA Certificate that has signed the web-platform.test server certificate.""" if self.certutil_binary is None: self.logger.info( "--certutil-binary not supplied; Firefox will not check certificates" ) return self.logger.info("Setting up ssl") # Make sure the certutil libraries from the source tree are loaded when using a # local copy of certutil # TODO: Maybe only set this if certutil won't launch? env = os.environ.copy() certutil_dir = os.path.dirname(self.binary) if mozinfo.isMac: env_var = "DYLD_LIBRARY_PATH" elif mozinfo.isUnix: env_var = "LD_LIBRARY_PATH" else: env_var = "PATH" env[env_var] = (os.path.pathsep.join([certutil_dir, env[env_var]]) if env_var in env else certutil_dir).encode( sys.getfilesystemencoding() or 'utf-8', 'replace') def certutil(*args): cmd = [self.certutil_binary] + list(args) self.logger.process_output( "certutil", subprocess.check_output(cmd, env=env, stderr=subprocess.STDOUT), " ".join(cmd)) pw_path = os.path.join(self.profile.profile, ".crtdbpw") with open(pw_path, "w") as f: # Use empty password for certificate db f.write("\n") cert_db_path = self.profile.profile # Create a new certificate db certutil("-N", "-d", cert_db_path, "-f", pw_path) # Add the CA certificate to the database and mark as trusted to issue server certs certutil("-A", "-d", cert_db_path, "-f", pw_path, "-t", "CT,,", "-n", "web-platform-tests", "-i", self.ca_certificate_path) # List all certs in the database certutil("-L", "-d", cert_db_path)
class TPSFirefoxRunner(object): PROCESS_TIMEOUT = 240 def __init__(self, binary): if binary is not None and ('http://' in binary or 'ftp://' in binary): self.url = binary self.binary = None else: self.url = None self.binary = binary self.runner = None self.installdir = None def __del__(self): if self.installdir: mozfile.remove(self.installdir, True) def download_url(self, url, dest=None): h = httplib2.Http() resp, content = h.request(url, 'GET') if dest == None: dest = os.path.basename(url) local = open(dest, 'wb') local.write(content) local.close() return dest def download_build(self, installdir='downloadedbuild', appname='firefox'): self.installdir = os.path.abspath(installdir) buildName = os.path.basename(self.url) pathToBuild = os.path.join(os.path.dirname(os.path.abspath(__file__)), buildName) # delete the build if it already exists if os.access(pathToBuild, os.F_OK): os.remove(pathToBuild) # download the build print 'downloading build' self.download_url(self.url, pathToBuild) # install the build print 'installing %s' % pathToBuild mozfile.remove(self.installdir, True) binary = mozinstall.install(src=pathToBuild, dest=self.installdir) # remove the downloaded archive os.remove(pathToBuild) return binary def run(self, profile=None, timeout=PROCESS_TIMEOUT, env=None, args=None): """Runs the given FirefoxRunner with the given Profile, waits for completion, then returns the process exit code """ if profile is None: profile = Profile() self.profile = profile if self.binary is None and self.url: self.binary = self.download_build() if self.runner is None: self.runner = FirefoxRunner(self.profile, binary=self.binary) self.runner.profile = self.profile if env is not None: self.runner.env.update(env) if args is not None: self.runner.cmdargs = copy.copy(args) self.runner.start() returncode = self.runner.wait(timeout) return returncode
preferences=prefs, addons=[os.path.join(build.distdir, 'xpi-stage', 'quitter')], locations=locations) env = os.environ.copy() env["MOZ_CRASHREPORTER_NO_REPORT"] = "1" env["XPCOM_DEBUG_BREAK"] = "warn" # For VC12, make sure we can find the right bitness of pgort120.dll if "VS120COMNTOOLS" in env and not substs["HAVE_64BIT_OS"]: vc12dir = os.path.abspath(os.path.join(env["VS120COMNTOOLS"], "../../VC/bin")) if os.path.exists(vc12dir): env["PATH"] = vc12dir + ";" + env["PATH"] jarlog = os.getenv("JARLOG_FILE") if jarlog: env["MOZ_JAR_LOG_FILE"] = os.path.abspath(jarlog) print "jarlog: %s" % env["MOZ_JAR_LOG_FILE"] cmdargs = ["http://localhost:%d/index.html" % PORT] runner = FirefoxRunner(profile=profile, binary=build.get_binary_path(where="staged-package"), cmdargs=cmdargs, env=env) runner.start(debug_args=debug_args, interactive=interactive) runner.wait() httpd.stop() finally: shutil.rmtree(profilePath)
class TPSFirefoxRunner(object): PROCESS_TIMEOUT = 240 def __init__(self, binary): if binary is not None and ('http://' in binary or 'ftp://' in binary): self.url = binary self.binary = None else: self.url = None self.binary = binary self.runner = None self.installdir = None def __del__(self): if self.installdir: shutil.rmtree(self.installdir, True) @property def names(self): if sys.platform == 'darwin': return ['firefox', 'minefield'] if (sys.platform == 'linux2') or (sys.platform in ('sunos5', 'solaris')): return ['firefox', 'mozilla-firefox', 'minefield'] if os.name == 'nt' or sys.platform == 'cygwin': return ['firefox'] def download_build(self, installdir='downloadedbuild', appname='firefox', macAppName='Minefield.app'): self.installdir = os.path.abspath(installdir) buildName = os.path.basename(self.url) pathToBuild = os.path.join(os.path.dirname(os.path.abspath(__file__)), buildName) # delete the build if it already exists if os.access(pathToBuild, os.F_OK): os.remove(pathToBuild) # download the build print "downloading build" download_url(self.url, pathToBuild) # install the build print "installing %s" % pathToBuild shutil.rmtree(self.installdir, True) MozInstaller(src=pathToBuild, dest=self.installdir, dest_app=macAppName) # remove the downloaded archive os.remove(pathToBuild) # calculate path to binary platform = get_platform() if platform['name'] == 'Mac': binary = '%s/%s/Contents/MacOS/%s-bin' % (installdir, macAppName, appname) else: binary = '%s/%s/%s%s' % (installdir, appname, appname, '.exe' if platform['name'] == 'Windows' else '') return binary def get_respository_info(self): """Read repository information from application.ini and platform.ini.""" import ConfigParser config = ConfigParser.RawConfigParser() dirname = os.path.dirname(self.runner.binary) repository = { } for entry in [['application', 'App'], ['platform', 'Build']]: (file, section) = entry config.read(os.path.join(dirname, '%s.ini' % file)) for entry in [['SourceRepository', 'repository'], ['SourceStamp', 'changeset']]: (key, id) = entry try: repository['%s_%s' % (file, id)] = config.get(section, key); except: repository['%s_%s' % (file, id)] = None return repository def run(self, profile=None, timeout=PROCESS_TIMEOUT, env=None, args=None): """Runs the given FirefoxRunner with the given Profile, waits for completion, then returns the process exit code """ if profile is None: profile = Profile() self.profile = profile if self.binary is None and self.url: self.binary = self.download_build() if self.runner is None: self.runner = FirefoxRunner(self.profile, binary=self.binary) self.runner.profile = self.profile if env is not None: self.runner.env.update(env) if args is not None: self.runner.cmdargs = copy.copy(args) self.runner.start() status = self.runner.process_handler.waitForFinish(timeout=timeout) return status
class TestRunnerManager(threading.Thread): """Thread that owns a single TestRunner process and any processes required by the TestRunner (e.g. the Firefox binary)""" init_lock = threading.Lock() def __init__(self, server_url, firefox_binary, run_info, tests_queue, stop_flag, runner_cls=TestharnessTestRunner, marionette_port=None, process_cls=FirefoxProcess): self.http_server_url = server_url self.firefox_binary = firefox_binary self.tests_queue = tests_queue self.run_info = run_info self.stop_flag = stop_flag self.command_pipe = None self.firefox_runner = None self.test_runner_proc = None self.runner_cls = runner_cls self.marionette_port = marionette_port self.process_cls = process_cls threading.Thread.__init__(self) #This is started in the actual new thread self.logger = None #This may not really be what we want self.daemon = True self.setup_fail_count = 0 self.max_setup_fails = 5 self.init_timer = None def run(self): self.logger = structuredlog.getOutputLogger("WPT") self.init() while True: commands = {"test_ended":self.test_ended, "setup_succeeded": self.setup_succeeded, "setup_failed": self.setup_failed} has_data = self.command_pipe.poll(1) if has_data: command, data = self.command_pipe.recv() if commands[command](*data) is Stop: break else: if self.stop_flag.is_set(): self.stop_runner(graceful=True) break elif not self.test_runner_proc.is_alive(): #This happens when we run out of tests; #We ask the runner to stop, it shuts itself #down and then we end up here #An alternate implementation strategy would be to have the #runner signal that it is done just before it terminates self.firefox_runner.stop() break def init(self): #It seems that this lock is helpful to prevent some race that otherwise #sometimes stops the spawned processes initalising correctly, and #leaves this thread hung with self.init_lock: def init_failed(): self.logger.error("Init failed") self.setup_failed() #TODO: make this timeout configurable self.init_timer = threading.Timer(30, self.setup_failed) self.init_timer.start() self.command_pipe, remote_end = Pipe() self.start_firefox() self.start_test_runner(remote_end) def start_firefox(self): env = os.environ.copy() env['MOZ_CRASHREPORTER_NO_REPORT'] = '1' profile = Profile() profile.set_preferences({"marionette.defaultPrefs.enabled": True, "marionette.defaultPrefs.port": self.marionette_port, "dom.disable_open_during_load": False, "dom.max_script_run_time": 0}) self.firefox_runner = FirefoxRunner(profile, self.firefox_binary, cmdargs=["--marionette"], env=env, kp_kwargs = {"processOutputLine":[self.on_output]}, process_class=self.process_cls) self.logger.debug("Starting Firefox") self.firefox_runner.start() self.logger.debug("Firefox Started") def start_test_runner(self, remote_connection): self.test_runner_proc = Process(target=start_runner, args=(self.runner_cls, self.http_server_url, self.marionette_port, remote_connection)) self.logger.debug("Starting test runner") self.test_runner_proc.start() self.logger.debug("Test runner started") def send_message(self, command, *args): self.command_pipe.send((command, args)) def stop_runner(self, graceful=True): self.logger.debug("Stopping runner") if graceful: self.test_runner_proc.join(10) if self.test_runner_proc.is_alive(): graceful = False self.firefox_runner.stop() if not graceful: self.test_runner_proc.terminate() self.logger.flush() self.command_pipe.close() def start_next_test(self): try: test = self.tests_queue.get(False) except Empty: logger.debug("No more tests") self.send_message("stop") else: self.logger.test_start(test.id) self.send_message("run_test", test) def test_ended(self, test, results): #It would be nice to move this down into the runner file_result, test_results = results for result in test_results: if test.disabled(self.run_info, result.name): continue expected = test.expected_status(self.run_info, result.name) self.logger.test_status(test.id, result.name, result.status, message=result.message, unexpected=expected != result.status) expected = test.expected_status(self.run_info) status = file_result.status if file_result.status != "EXTERNAL-TIMEOUT" else "TIMEOUT" self.logger.test_end(test.id, status, message=file_result.message, unexpected=expected != status) #Restarting after a timeout is quite wasteful, but it seems otherwise we can get #results from the timed-out test back when we are waiting for the results of a #later test if file_result.status in ("CRASH", "EXTERNAL-TIMEOUT"): self.restart_runner() else: self.start_next_test() def setup_succeeded(self): self.init_timer.cancel() self.setup_fail_count = 0 self.start_next_test() def setup_failed(self): self.init_timer.cancel() self.send_message("stop") self.setup_fail_count += 1 if self.setup_fail_count < self.max_setup_fails: self.restart_runner() else: return Stop def restart_runner(self): self.stop_runner(graceful=False) self.init() def on_output(self, line): self.logger.process_output(line, self.firefox_runner.process_handler.pid, command=" ".join(self.firefox_runner.command))
def valgrind_test(self, suppressions): import json import sys import tempfile from mozbuild.base import MozbuildObject from mozfile import TemporaryDirectory from mozhttpd import MozHttpd from mozprofile import FirefoxProfile, Preferences from mozprofile.permissions import ServerLocations from mozrunner import FirefoxRunner from mozrunner.utils import findInPath from valgrind.output_handler import OutputHandler build_dir = os.path.join(self.topsrcdir, 'build') # XXX: currently we just use the PGO inputs for Valgrind runs. This may # change in the future. httpd = MozHttpd(docroot=os.path.join(build_dir, 'pgo')) httpd.start(block=False) with TemporaryDirectory() as profilePath: #TODO: refactor this into mozprofile prefpath = os.path.join(self.topsrcdir, 'testing', 'profiles', 'prefs_general.js') prefs = {} prefs.update(Preferences.read_prefs(prefpath)) interpolation = { 'server': '%s:%d' % httpd.httpd.server_address, 'OOP': 'false'} prefs = json.loads(json.dumps(prefs) % interpolation) for pref in prefs: prefs[pref] = Preferences.cast(prefs[pref]) quitter = os.path.join(self.distdir, 'xpi-stage', 'quitter') locations = ServerLocations() locations.add_host(host='127.0.0.1', port=httpd.httpd.server_port, options='primary') profile = FirefoxProfile(profile=profilePath, preferences=prefs, addons=[quitter], locations=locations) firefox_args = [httpd.get_url()] env = os.environ.copy() env['G_SLICE'] = 'always-malloc' env['XPCOM_CC_RUN_DURING_SHUTDOWN'] = '1' env['MOZ_CRASHREPORTER_NO_REPORT'] = '1' env['XPCOM_DEBUG_BREAK'] = 'warn' outputHandler = OutputHandler() kp_kwargs = {'processOutputLine': [outputHandler]} valgrind = 'valgrind' if not os.path.exists(valgrind): valgrind = findInPath(valgrind) valgrind_args = [ valgrind, '--smc-check=all-non-file', '--vex-iropt-register-updates=allregs-at-mem-access', '--gen-suppressions=all', '--num-callers=20', '--leak-check=full', '--show-possibly-lost=no', '--track-origins=yes' ] for s in suppressions: valgrind_args.append('--suppressions=' + s) supps_dir = os.path.join(build_dir, 'valgrind') supps_file1 = os.path.join(supps_dir, 'cross-architecture.sup') valgrind_args.append('--suppressions=' + supps_file1) # MACHTYPE is an odd bash-only environment variable that doesn't # show up in os.environ, so we have to get it another way. machtype = subprocess.check_output(['bash', '-c', 'echo $MACHTYPE']).rstrip() supps_file2 = os.path.join(supps_dir, machtype + '.sup') if os.path.isfile(supps_file2): valgrind_args.append('--suppressions=' + supps_file2) exitcode = None try: runner = FirefoxRunner(profile=profile, binary=self.get_binary_path(), cmdargs=firefox_args, env=env, kp_kwargs=kp_kwargs) runner.start(debug_args=valgrind_args) exitcode = runner.wait() finally: errs = outputHandler.error_count supps = outputHandler.suppression_count if errs != supps: status = 1 # turns the TBPL job orange print('TEST-UNEXPECTED-FAILURE | valgrind-test | error parsing:', errs, "errors seen, but", supps, "generated suppressions seen") elif errs == 0: status = 0 print('TEST-PASS | valgrind-test | valgrind found no errors') else: status = 1 # turns the TBPL job orange # We've already printed details of the errors. if exitcode != 0: status = 2 # turns the TBPL job red print('TEST-UNEXPECTED-FAIL | valgrind-test | non-zero exit code from Valgrind') httpd.stop() return status
def valgrind_test(self, suppressions): import json import re import sys import tempfile from mozbuild.base import MozbuildObject from mozfile import TemporaryDirectory from mozhttpd import MozHttpd from mozprofile import FirefoxProfile, Preferences from mozprofile.permissions import ServerLocations from mozrunner import FirefoxRunner from mozrunner.utils import findInPath build_dir = os.path.join(self.topsrcdir, 'build') # XXX: currently we just use the PGO inputs for Valgrind runs. This may # change in the future. httpd = MozHttpd(docroot=os.path.join(build_dir, 'pgo')) httpd.start(block=False) with TemporaryDirectory() as profilePath: #TODO: refactor this into mozprofile prefpath = os.path.join(self.topsrcdir, 'testing', 'profiles', 'prefs_general.js') prefs = {} prefs.update(Preferences.read_prefs(prefpath)) interpolation = { 'server': '%s:%d' % httpd.httpd.server_address, 'OOP': 'false' } prefs = json.loads(json.dumps(prefs) % interpolation) for pref in prefs: prefs[pref] = Preferences.cast(prefs[pref]) quitter = os.path.join(self.distdir, 'xpi-stage', 'quitter') locations = ServerLocations() locations.add_host(host='127.0.0.1', port=httpd.httpd.server_port, options='primary') profile = FirefoxProfile(profile=profilePath, preferences=prefs, addons=[quitter], locations=locations) firefox_args = [httpd.get_url()] env = os.environ.copy() env['G_SLICE'] = 'always-malloc' env['XPCOM_CC_RUN_DURING_SHUTDOWN'] = '1' env['MOZ_CRASHREPORTER_NO_REPORT'] = '1' env['XPCOM_DEBUG_BREAK'] = 'warn' class OutputHandler(object): def __init__(self): self.found_errors = False def __call__(self, line): print(line) m = re.match( r'.*ERROR SUMMARY: [1-9]\d* errors from \d+ contexts', line) if m: self.found_errors = True outputHandler = OutputHandler() kp_kwargs = {'processOutputLine': [outputHandler]} valgrind = 'valgrind' if not os.path.exists(valgrind): valgrind = findInPath(valgrind) valgrind_args = [ valgrind, '--smc-check=all-non-file', '--vex-iropt-register-updates=allregs-at-mem-access', '--gen-suppressions=all', '--num-callers=20', '--leak-check=full', '--show-possibly-lost=no', '--track-origins=yes' ] for s in suppressions: valgrind_args.append('--suppressions=' + s) supps_dir = os.path.join(build_dir, 'valgrind') supps_file1 = os.path.join(supps_dir, 'cross-architecture.sup') valgrind_args.append('--suppressions=' + supps_file1) # MACHTYPE is an odd bash-only environment variable that doesn't # show up in os.environ, so we have to get it another way. machtype = subprocess.check_output( ['bash', '-c', 'echo $MACHTYPE']).rstrip() supps_file2 = os.path.join(supps_dir, machtype + '.sup') if os.path.isfile(supps_file2): valgrind_args.append('--suppressions=' + supps_file2) exitcode = None try: runner = FirefoxRunner(profile=profile, binary=self.get_binary_path(), cmdargs=firefox_args, env=env, kp_kwargs=kp_kwargs) runner.start(debug_args=valgrind_args) exitcode = runner.wait() finally: if not outputHandler.found_errors: status = 0 print( 'TEST-PASS | valgrind-test | valgrind found no errors') else: status = 1 # turns the TBPL job orange print( 'TEST-UNEXPECTED-FAIL | valgrind-test | valgrind found errors' ) if exitcode != 0: status = 2 # turns the TBPL job red print( 'TEST-UNEXPECTED-FAIL | valgrind-test | non-zero exit code from Valgrind' ) httpd.stop() return status
class B2GDesktopReftest(RefTest): marionette = None def __init__(self, marionette_args): RefTest.__init__(self) self.last_test = os.path.basename(__file__) self.marionette_args = marionette_args self.profile = None self.runner = None self.test_script = os.path.join(here, 'b2g_start_script.js') self.timeout = None def run_marionette_script(self): self.marionette = Marionette(**self.marionette_args) assert (self.marionette.wait_for_port()) self.marionette.start_session() self.marionette.set_context(self.marionette.CONTEXT_CHROME) if os.path.isfile(self.test_script): f = open(self.test_script, 'r') self.test_script = f.read() f.close() self.marionette.execute_script(self.test_script) def run_tests(self, test_path, options): reftestlist = self.getManifestPath(test_path) if not reftestlist.startswith('file://'): reftestlist = 'file://%s' % reftestlist self.profile = self.create_profile(options, reftestlist, profile_to_clone=options.profile) env = self.buildBrowserEnv(options, self.profile.profile) kp_kwargs = { 'processOutputLine': [self._on_output], 'onTimeout': [self._on_timeout], 'kill_on_timeout': False } if not options.debugger: if not options.timeout: if mozinfo.info['debug']: options.timeout = 420 else: options.timeout = 300 self.timeout = options.timeout + 30.0 log.info("%s | Running tests: start.", os.path.basename(__file__)) cmd, args = self.build_command_line( options.app, ignore_window_size=options.ignoreWindowSize, browser_arg=options.browser_arg) self.runner = FirefoxRunner(profile=self.profile, binary=cmd, cmdargs=args, env=env, process_class=ProcessHandler, process_args=kp_kwargs, symbols_path=options.symbolsPath) status = 0 try: self.runner.start(outputTimeout=self.timeout) log.info("%s | Application pid: %d", os.path.basename(__file__), self.runner.process_handler.pid) # kick starts the reftest harness self.run_marionette_script() status = self.runner.wait() finally: self.runner.check_for_crashes(test_name=self.last_test) self.runner.cleanup() if status > 0: log.testFail("%s | application terminated with exit code %s", self.last_test, status) elif status < 0: log.info("%s | application killed with signal %s", self.last_test, -status) log.info("%s | Running tests: end.", os.path.basename(__file__)) return status def create_profile(self, options, reftestlist, profile_to_clone=None): profile = RefTest.createReftestProfile( self, options, reftestlist, profile_to_clone=profile_to_clone) prefs = {} # Turn off the locale picker screen prefs["browser.firstrun.show.localepicker"] = False prefs[ "b2g.system_startup_url"] = "app://test-container.gaiamobile.org/index.html" prefs[ "b2g.system_manifest_url"] = "app://test-container.gaiamobile.org/manifest.webapp" prefs["browser.tabs.remote"] = False prefs["dom.ipc.tabs.disabled"] = False prefs["dom.mozBrowserFramesEnabled"] = True prefs["font.size.inflation.emPerLine"] = 0 prefs["font.size.inflation.minTwips"] = 0 prefs[ "network.dns.localDomains"] = "app://test-container.gaiamobile.org" prefs["reftest.browser.iframe.enabled"] = False prefs["reftest.remote"] = False prefs["reftest.uri"] = "%s" % reftestlist # Set a future policy version to avoid the telemetry prompt. prefs["toolkit.telemetry.prompted"] = 999 prefs["toolkit.telemetry.notifiedOptOut"] = 999 # Set the extra prefs. profile.set_preferences(prefs) return profile def build_command_line(self, app, ignore_window_size=False, browser_arg=None): cmd = os.path.abspath(app) args = ['-marionette'] if browser_arg: args += [browser_arg] if not ignore_window_size: args.extend(['--screen', '800x1000']) return cmd, args def _on_output(self, line): print(line) # TODO use structured logging if "TEST-START" in line and "|" in line: self.last_test = line.split("|")[1].strip() def _on_timeout(self): msg = "%s | application timed out after %s seconds with no output" log.testFail(msg % (self.last_test, self.timeout)) # kill process to get a stack self.runner.stop(sig=signal.SIGABRT)
class B2GDesktopReftest(RefTest): def __init__(self, marionette): RefTest.__init__(self) self.last_test = os.path.basename(__file__) self.marionette = marionette self.profile = None self.runner = None self.test_script = os.path.join(here, 'b2g_start_script.js') self.timeout = None def run_marionette_script(self): assert(self.marionette.wait_for_port()) self.marionette.start_session() self.marionette.set_context(self.marionette.CONTEXT_CHROME) if os.path.isfile(self.test_script): f = open(self.test_script, 'r') self.test_script = f.read() f.close() self.marionette.execute_script(self.test_script) def run_tests(self, test_path, options): reftestlist = self.getManifestPath(test_path) if not reftestlist.startswith('file://'): reftestlist = 'file://%s' % reftestlist self.profile = self.create_profile(options, reftestlist, profile_to_clone=options.profile) env = self.buildBrowserEnv(options, self.profile.profile) kp_kwargs = { 'processOutputLine': [self._on_output], 'onTimeout': [self._on_timeout], 'kill_on_timeout': False } if not options.debugger: if not options.timeout: if mozinfo.info['debug']: options.timeout = 420 else: options.timeout = 300 self.timeout = options.timeout + 30.0 log.info("%s | Running tests: start.", os.path.basename(__file__)) cmd, args = self.build_command_line(options.app, ignore_window_size=options.ignoreWindowSize) self.runner = FirefoxRunner(profile=self.profile, binary=cmd, cmdargs=args, env=env, process_class=ProcessHandler, symbols_path=options.symbolsPath, kp_kwargs=kp_kwargs) status = 0 try: self.runner.start(outputTimeout=self.timeout) log.info("%s | Application pid: %d", os.path.basename(__file__), self.runner.process_handler.pid) # kick starts the reftest harness self.run_marionette_script() status = self.runner.wait() finally: self.runner.check_for_crashes(test_name=self.last_test) self.runner.cleanup() if status > 0: log.testFail("%s | application terminated with exit code %s", self.last_test, status) elif status < 0: log.info("%s | application killed with signal %s", self.last_test, -status) log.info("%s | Running tests: end.", os.path.basename(__file__)) return status def create_profile(self, options, reftestlist, profile_to_clone=None): profile = RefTest.createReftestProfile(self, options, reftestlist, profile_to_clone=profile_to_clone) prefs = {} # Turn off the locale picker screen prefs["browser.firstrun.show.localepicker"] = False prefs["browser.homescreenURL"] = "app://test-container.gaiamobile.org/index.html" prefs["browser.manifestURL"] = "app://test-container.gaiamobile.org/manifest.webapp" prefs["browser.tabs.remote"] = False prefs["dom.ipc.tabs.disabled"] = False prefs["dom.mozBrowserFramesEnabled"] = True prefs["font.size.inflation.emPerLine"] = 0 prefs["font.size.inflation.minTwips"] = 0 prefs["network.dns.localDomains"] = "app://test-container.gaiamobile.org" prefs["reftest.browser.iframe.enabled"] = False prefs["reftest.remote"] = False prefs["reftest.uri"] = "%s" % reftestlist # Set a future policy version to avoid the telemetry prompt. prefs["toolkit.telemetry.prompted"] = 999 prefs["toolkit.telemetry.notifiedOptOut"] = 999 # Set the extra prefs. profile.set_preferences(prefs) return profile def build_command_line(self, app, ignore_window_size=False): cmd = os.path.abspath(app) args = ['-marionette'] if not ignore_window_size: args.extend(['--screen', '800x1000']) return cmd, args def _on_output(self, line): print(line) # TODO use structured logging if "TEST-START" in line and "|" in line: self.last_test = line.split("|")[1].strip() def _on_timeout(self): msg = "%s | application timed out after %s seconds with no output" log.testFail(msg % (self.last_test, self.timeout)) # kill process to get a stack self.runner.stop(sig=signal.SIGABRT)
def run(): parser = argparse.ArgumentParser(description='Run crawler') parser.add_argument( '-b', '--binary', type=str, help='path to the Firefox binary' ) parser.add_argument( '-a', '--abpdir', type=str, help='path to the Adblock Plus repository' ) parser.add_argument( '-f', '--filters', metavar='url', type=str, nargs='+', default=["https://easylist-downloads.adblockplus.org/easylist.txt", "https://easylist-downloads.adblockplus.org/exceptionrules.txt"], help='filter lists to install in Adblock Plus. The arguments can also have the format path=url, the data will be read from the specified path then.' ) parser.add_argument( '-t', '--timeout', type=int, default=300, help='Load timeout (seconds)' ) parser.add_argument( '-x', '--maxtabs', type=int, default=15, help='Maximal number of tabs to open in parallel' ) parser.add_argument( 'list', type=str, help='URL list to process' ) parser.add_argument( 'outdir', type=str, help='directory to write data into' ) parameters = parser.parse_args() import buildtools.packagerGecko as packager cleanup = [] try: base_dir = os.path.dirname(__file__) handle, crawlerxpi = tempfile.mkstemp(suffix='.xpi') os.close(handle) cleanup.append(crawlerxpi) packager.createBuild(base_dir, outFile=crawlerxpi, releaseBuild=True) abpxpi = 'https://addons.mozilla.org/firefox/downloads/latest/1865/addon-1865-latest.xpi' if parameters.abpdir: handle, abpxpi = tempfile.mkstemp(suffix='.xpi') os.close(handle) cleanup.append(abpxpi) packager.createBuild(parameters.abpdir, outFile=abpxpi, releaseBuild=True) profile = FirefoxProfile( addons=[ crawlerxpi, abpxpi, ], preferences={ 'browser.uitour.enabled': False, 'prompts.tab_modal.enabled': False, } ) abpsettings = os.path.join(profile.profile, 'adblockplus') os.makedirs(abpsettings) with open(os.path.join(abpsettings, 'patterns.ini'), 'w') as handle: print >>handle, '# Adblock Plus preferences' print >>handle, 'version=4' for url in parameters.filters: if '=' in url: path, url = url.split('=', 1) with open(path, 'r') as source: data = source.read() else: data = urllib.urlopen(url).read() print >>handle, '[Subscription]' print >>handle, 'url=%s' % url print >>handle, '[Subscription filters]' print >>handle, '\n'.join(data.splitlines()[1:]) finally: for path in cleanup: os.unlink(path) server = None try: port = random.randrange(2000, 60000) print "Communicating with client on port %i" % port app = CrawlerApp(parameters) server = make_server('localhost', port, app) app.server = server threading.Thread(target=lambda: server.serve_forever()).start() runner = FirefoxRunner( profile=profile, binary=parameters.binary, cmdargs=['--crawler-port', str(port)], env=dict(os.environ, MOZ_CRASHREPORTER_DISABLE='1'), ) while app.urls: runner.start() runner.wait() finally: if server: server.shutdown() profile.cleanup()
class FirefoxBrowser(Browser): used_ports = set() def __init__(self, logger, binary, prefs_root, debug_args=None, interactive=None, symbols_path=None, stackwalk_binary=None): Browser.__init__(self, logger) self.binary = binary self.prefs_root = prefs_root self.marionette_port = None self.used_ports.add(self.marionette_port) self.runner = None self.debug_args = debug_args self.interactive = interactive self.profile = None self.symbols_path = symbols_path self.stackwalk_binary = stackwalk_binary def start(self): self.marionette_port = get_free_port(2828, exclude=self.used_ports) env = os.environ.copy() env["MOZ_CRASHREPORTER"] = "1" env["MOZ_CRASHREPORTER_SHUTDOWN"] = "1" env["MOZ_CRASHREPORTER_NO_REPORT"] = "1" env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1" locations = ServerLocations(filename=os.path.join(here, "server-locations.txt")) preferences = self.load_prefs() ports = {"http": "8000", "https": "8443", "ws": "8888"} self.profile = FirefoxProfile(locations=locations, proxy=ports, preferences=preferences) self.profile.set_preferences({"marionette.defaultPrefs.enabled": True, "marionette.defaultPrefs.port": self.marionette_port, "dom.disable_open_during_load": False}) self.runner = FirefoxRunner(profile=self.profile, binary=self.binary, cmdargs=[cmd_arg("marionette"), "about:blank"], env=env, process_class=ProcessHandler, process_args={"processOutputLine": [self.on_output]}) self.logger.debug("Starting Firefox") self.runner.start(debug_args=self.debug_args, interactive=self.interactive) self.logger.debug("Firefox Started") def load_prefs(self): prefs_path = os.path.join(self.prefs_root, "prefs_general.js") if os.path.exists(prefs_path): preferences = Preferences.read_prefs(prefs_path) else: self.logger.warning("Failed to find base prefs file in %s" % prefs_path) preferences = [] return preferences def stop(self): self.logger.debug("Stopping browser") if self.runner is not None: try: self.runner.stop() except OSError: # This can happen on Windows if the process is already dead pass def pid(self): if self.runner.process_handler is None: return None try: return self.runner.process_handler.pid except AttributeError: return None def on_output(self, line): """Write a line of output from the firefox process to the log""" self.logger.process_output(self.pid(), line.decode("utf8", "replace"), command=" ".join(self.runner.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.marionette_port is not None return ExecutorBrowser, {"marionette_port": self.marionette_port} def log_crash(self, process, test): dump_dir = os.path.join(self.profile.profile, "minidumps") mozcrash.log_crashes(self.logger, dump_dir, symbols_path=self.symbols_path, stackwalk_binary=self.stackwalk_binary, process=process, test=test)
class FirefoxBrowser(Browser): used_ports = set() def __init__(self, logger, binary, prefs_root, debug_args=None, interactive=None, symbols_path=None, stackwalk_binary=None, certutil_binary=None, ca_certificate_path=None): Browser.__init__(self, logger) self.binary = binary self.prefs_root = prefs_root self.marionette_port = None self.used_ports.add(self.marionette_port) self.runner = None self.debug_args = debug_args self.interactive = interactive self.profile = None self.symbols_path = symbols_path self.stackwalk_binary = stackwalk_binary self.ca_certificate_path = ca_certificate_path self.certutil_binary = certutil_binary def start(self): self.marionette_port = get_free_port(2828, exclude=self.used_ports) env = os.environ.copy() env["MOZ_CRASHREPORTER"] = "1" env["MOZ_CRASHREPORTER_SHUTDOWN"] = "1" env["MOZ_CRASHREPORTER_NO_REPORT"] = "1" env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1" locations = ServerLocations(filename=os.path.join(here, "server-locations.txt")) preferences = self.load_prefs() ports = {"http": "8000", "https": "8443", "ws": "8888"} self.profile = FirefoxProfile(locations=locations, proxy=ports, preferences=preferences) self.profile.set_preferences({"marionette.defaultPrefs.enabled": True, "marionette.defaultPrefs.port": self.marionette_port, "dom.disable_open_during_load": False}) if self.ca_certificate_path is not None: self.setup_ssl() self.runner = FirefoxRunner(profile=self.profile, binary=self.binary, cmdargs=[cmd_arg("marionette"), "about:blank"], env=env, process_class=ProcessHandler, process_args={"processOutputLine": [self.on_output]}) self.logger.debug("Starting Firefox") self.runner.start(debug_args=self.debug_args, interactive=self.interactive) self.logger.debug("Firefox Started") def load_prefs(self): prefs_path = os.path.join(self.prefs_root, "prefs_general.js") if os.path.exists(prefs_path): preferences = Preferences.read_prefs(prefs_path) else: self.logger.warning("Failed to find base prefs file in %s" % prefs_path) preferences = [] return preferences def stop(self): self.logger.debug("Stopping browser") if self.runner is not None: try: self.runner.stop() except OSError: # This can happen on Windows if the process is already dead pass def pid(self): if self.runner.process_handler is None: return None try: return self.runner.process_handler.pid except AttributeError: return None def on_output(self, line): """Write a line of output from the firefox process to the log""" self.logger.process_output(self.pid(), line.decode("utf8", "replace"), command=" ".join(self.runner.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.marionette_port is not None return ExecutorBrowser, {"marionette_port": self.marionette_port} def log_crash(self, process, test): dump_dir = os.path.join(self.profile.profile, "minidumps") mozcrash.log_crashes(self.logger, dump_dir, symbols_path=self.symbols_path, stackwalk_binary=self.stackwalk_binary, process=process, test=test) def setup_ssl(self): """Create a certificate database to use in the test profile. This is configured to trust the CA Certificate that has signed the web-platform.test server certificate.""" self.logger.info("Setting up ssl") # Make sure the certutil libraries from the source tree are loaded when using a # local copy of certutil # TODO: Maybe only set this if certutil won't launch? env = os.environ.copy() certutil_dir = os.path.dirname(self.binary) env["LD_LIBRARY_PATH"] = certutil_dir env["PATH"] = os.path.pathsep.join([certutil_dir, env["PATH"]]) def certutil(*args): cmd = [self.certutil_binary] + list(args) self.logger.process_output("certutil", subprocess.check_output(cmd, env=env, stderr=subprocess.STDOUT), " ".join(cmd)) pw_path = os.path.join(self.profile.profile, ".crtdbpw") with open(pw_path, "w") as f: # Use empty password for certificate db f.write("\n") cert_db_path = self.profile.profile # Create a new certificate db certutil("-N", "-d", cert_db_path, "-f", pw_path) # Add the CA certificate to the database and mark as trusted to issue server certs certutil("-A", "-d", cert_db_path, "-f", pw_path, "-t", "CT,,", "-n", "web-platform-tests", "-i", self.ca_certificate_path) # List all certs in the database certutil("-L", "-d", cert_db_path)
self.log.debug("Creating Firefox runner") mozRunner = FirefoxRunner( profile=mozProfile, binary=self.binary, cmdargs=["-no-remote", "-runFBTests", self.testlist], env=mozEnv) self.binary = mozRunner.binary if not self.appVersion: self.appdir, self.appVersion = self.get_app_info() # Disable the compatibility check on startup self.disable_compatibility_check() self.log.debug("Running '" + self.binary + " -no-remote -runFBTests " + self.testlist + "'") mozRunner.start() except Exception: self.log.error("Could not start Firefox") self.log.error(traceback.format_exc()) self.cleanup() raise # Find the log file timeout, logfile = 0, 0 # Wait up to 60 seconds for the log file to be initialized while not logfile and timeout < 60: try: for name in os.listdir( os.path.join(self.profile, "firebug", "fbtest", "logs")): logfile = open(
class FirefoxBrowser(Browser): init_timeout = 70 shutdown_timeout = 70 def __init__(self, logger, binary, prefs_root, test_type, extra_prefs=None, debug_info=None, symbols_path=None, stackwalk_binary=None, certutil_binary=None, ca_certificate_path=None, e10s=False, lsan_dir=None, stackfix_dir=None, binary_args=None, timeout_multiplier=None, leak_check=False, asan=False, stylo_threads=1, chaos_mode_flags=None, config=None, browser_channel="nightly", headless=None, **kwargs): Browser.__init__(self, logger) self.binary = binary self.prefs_root = prefs_root self.test_type = test_type self.extra_prefs = extra_prefs self.marionette_port = None self.runner = None self.debug_info = debug_info self.profile = None self.symbols_path = symbols_path self.stackwalk_binary = stackwalk_binary self.ca_certificate_path = ca_certificate_path self.certutil_binary = certutil_binary self.e10s = e10s self.binary_args = binary_args self.config = config if stackfix_dir: self.stack_fixer = get_stack_fixer_function(stackfix_dir, self.symbols_path) else: self.stack_fixer = None if timeout_multiplier: self.init_timeout = self.init_timeout * timeout_multiplier self.asan = asan self.lsan_dir = lsan_dir self.lsan_allowed = None self.lsan_max_stack_depth = None self.mozleak_allowed = None self.mozleak_thresholds = None self.leak_check = leak_check self.leak_report_file = None self.lsan_handler = None self.stylo_threads = stylo_threads self.chaos_mode_flags = chaos_mode_flags self.browser_channel = browser_channel self.headless = headless def settings(self, test): return {"check_leaks": self.leak_check and not test.leaks, "lsan_allowed": test.lsan_allowed, "lsan_max_stack_depth": test.lsan_max_stack_depth, "mozleak_allowed": self.leak_check and test.mozleak_allowed, "mozleak_thresholds": self.leak_check and test.mozleak_threshold} def start(self, group_metadata=None, **kwargs): if group_metadata is None: group_metadata = {} self.group_metadata = group_metadata self.lsan_allowed = kwargs.get("lsan_allowed") self.lsan_max_stack_depth = kwargs.get("lsan_max_stack_depth") self.mozleak_allowed = kwargs.get("mozleak_allowed") self.mozleak_thresholds = kwargs.get("mozleak_thresholds") if self.marionette_port is None: self.marionette_port = get_free_port() if self.asan: self.lsan_handler = mozleak.LSANLeaks(self.logger, scope=group_metadata.get("scope", "/"), allowed=self.lsan_allowed, maxNumRecordedFrames=self.lsan_max_stack_depth) env = test_environment(xrePath=os.path.dirname(self.binary), debugger=self.debug_info is not None, log=self.logger, lsanPath=self.lsan_dir) env["STYLO_THREADS"] = str(self.stylo_threads) if self.chaos_mode_flags is not None: env["MOZ_CHAOSMODE"] = str(self.chaos_mode_flags) if self.headless: env["MOZ_HEADLESS"] = "1" preferences = self.load_prefs() self.profile = FirefoxProfile(preferences=preferences) self.profile.set_preferences({ "marionette.port": self.marionette_port, "network.dns.localDomains": ",".join(self.config.domains_set), "dom.file.createInChild": True, # TODO: Remove preferences once Firefox 64 is stable (Bug 905404) "network.proxy.type": 0, "places.history.enabled": False, "network.preload": True, }) if self.e10s: self.profile.set_preferences({"browser.tabs.remote.autostart": True}) if self.test_type == "reftest": self.profile.set_preferences({"layout.interruptible-reflow.enabled": False}) if self.leak_check: self.leak_report_file = os.path.join(self.profile.profile, "runtests_leaks_%s.log" % os.getpid()) if os.path.exists(self.leak_report_file): os.remove(self.leak_report_file) env["XPCOM_MEM_BLOAT_LOG"] = self.leak_report_file else: self.leak_report_file = None # Bug 1262954: winxp + e10s, disable hwaccel if (self.e10s and platform.system() in ("Windows", "Microsoft") and '5.1' in platform.version()): self.profile.set_preferences({"layers.acceleration.disabled": True}) if self.ca_certificate_path is not None: self.setup_ssl() args = self.binary_args[:] if self.binary_args else [] args += [cmd_arg("marionette"), "about:blank"] debug_args, cmd = browser_command(self.binary, args, self.debug_info) self.runner = FirefoxRunner(profile=self.profile, binary=cmd[0], cmdargs=cmd[1:], env=env, process_class=ProcessHandler, process_args={"processOutputLine": [self.on_output]}) self.logger.debug("Starting Firefox") self.runner.start(debug_args=debug_args, interactive=self.debug_info and self.debug_info.interactive) self.logger.debug("Firefox Started") def load_prefs(self): prefs = Preferences() pref_paths = [] profiles = os.path.join(self.prefs_root, 'profiles.json') if os.path.isfile(profiles): with open(profiles, 'r') as fh: for name in json.load(fh)['web-platform-tests']: if self.browser_channel in (None, 'nightly'): pref_paths.append(os.path.join(self.prefs_root, name, 'user.js')) elif name != 'unittest-features': pref_paths.append(os.path.join(self.prefs_root, name, 'user.js')) else: # Old preference files used before the creation of profiles.json (remove when no longer supported) legacy_pref_paths = ( os.path.join(self.prefs_root, 'prefs_general.js'), # Used in Firefox 60 and below os.path.join(self.prefs_root, 'common', 'user.js'), # Used in Firefox 61 ) for path in legacy_pref_paths: if os.path.isfile(path): pref_paths.append(path) for path in pref_paths: if os.path.exists(path): prefs.add(Preferences.read_prefs(path)) else: self.logger.warning("Failed to find base prefs file in %s" % path) # Add any custom preferences prefs.add(self.extra_prefs, cast=True) return prefs() def stop(self, force=False): if self.runner is not None and self.runner.is_running(): try: # For Firefox we assume that stopping the runner prompts the # browser to shut down. This allows the leak log to be written for clean, stop_f in [(True, lambda: self.runner.wait(self.shutdown_timeout)), (False, lambda: self.runner.stop(signal.SIGTERM)), (False, lambda: self.runner.stop(signal.SIGKILL))]: if not force or not clean: retcode = stop_f() if retcode is not None: self.logger.info("Browser exited with return code %s" % retcode) break except OSError: # This can happen on Windows if the process is already dead pass self.process_leaks() self.logger.debug("stopped") def process_leaks(self): self.logger.info("PROCESS LEAKS %s" % self.leak_report_file) if self.lsan_handler: self.lsan_handler.process() if self.leak_report_file is not None: mozleak.process_leak_log( self.leak_report_file, leak_thresholds=self.mozleak_thresholds, ignore_missing_leaks=["gmplugin"], log=self.logger, stack_fixer=self.stack_fixer, scope=self.group_metadata.get("scope"), allowed=self.mozleak_allowed ) def pid(self): if self.runner.process_handler is None: return None try: return self.runner.process_handler.pid except AttributeError: return None def on_output(self, line): """Write a line of output from the firefox process to the log""" if "GLib-GObject-CRITICAL" in line: return if line: data = line.decode("utf8", "replace") if self.stack_fixer: data = self.stack_fixer(data) if self.lsan_handler: data = self.lsan_handler.log(data) if data is not None: self.logger.process_output(self.pid(), data, command=" ".join(self.runner.command)) def is_alive(self): if self.runner: return self.runner.is_running() return False def cleanup(self, force=False): self.stop(force) def executor_browser(self): assert self.marionette_port is not None return ExecutorBrowser, {"marionette_port": self.marionette_port} def check_crash(self, process, test): dump_dir = os.path.join(self.profile.profile, "minidumps") return bool(mozcrash.log_crashes(self.logger, dump_dir, symbols_path=self.symbols_path, stackwalk_binary=self.stackwalk_binary, process=process, test=test)) def setup_ssl(self): """Create a certificate database to use in the test profile. This is configured to trust the CA Certificate that has signed the web-platform.test server certificate.""" if self.certutil_binary is None: self.logger.info("--certutil-binary not supplied; Firefox will not check certificates") return self.logger.info("Setting up ssl") # Make sure the certutil libraries from the source tree are loaded when using a # local copy of certutil # TODO: Maybe only set this if certutil won't launch? env = os.environ.copy() certutil_dir = os.path.dirname(self.binary or self.certutil_binary) if mozinfo.isMac: env_var = "DYLD_LIBRARY_PATH" elif mozinfo.isUnix: env_var = "LD_LIBRARY_PATH" else: env_var = "PATH" env[env_var] = (os.path.pathsep.join([certutil_dir, env[env_var]]) if env_var in env else certutil_dir).encode( sys.getfilesystemencoding() or 'utf-8', 'replace') def certutil(*args): cmd = [self.certutil_binary] + list(args) self.logger.process_output("certutil", subprocess.check_output(cmd, env=env, stderr=subprocess.STDOUT), " ".join(cmd)) pw_path = os.path.join(self.profile.profile, ".crtdbpw") with open(pw_path, "w") as f: # Use empty password for certificate db f.write("\n") cert_db_path = self.profile.profile # Create a new certificate db certutil("-N", "-d", cert_db_path, "-f", pw_path) # Add the CA certificate to the database and mark as trusted to issue server certs certutil("-A", "-d", cert_db_path, "-f", pw_path, "-t", "CT,,", "-n", "web-platform-tests", "-i", self.ca_certificate_path) # List all certs in the database certutil("-L", "-d", cert_db_path)
class FirefoxBrowser(Browser): used_ports = set() def __init__(self, logger, binary, prefs_root, debug_args=None, interactive=None, symbols_path=None, stackwalk_binary=None): Browser.__init__(self, logger) self.binary = binary self.prefs_root = prefs_root self.marionette_port = None self.used_ports.add(self.marionette_port) self.runner = None self.debug_args = debug_args self.interactive = interactive self.profile = None self.symbols_path = symbols_path self.stackwalk_binary = stackwalk_binary def start(self): self.marionette_port = get_free_port(2828, exclude=self.used_ports) env = os.environ.copy() env["MOZ_CRASHREPORTER"] = "1" env["MOZ_CRASHREPORTER_SHUTDOWN"] = "1" env["MOZ_CRASHREPORTER_NO_REPORT"] = "1" env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1" locations = ServerLocations( filename=os.path.join(here, "server-locations.txt")) preferences = self.load_prefs() self.profile = FirefoxProfile(locations=locations, proxy=True, preferences=preferences) self.profile.set_preferences({ "marionette.defaultPrefs.enabled": True, "marionette.defaultPrefs.port": self.marionette_port, "dom.disable_open_during_load": False }) self.runner = FirefoxRunner( profile=self.profile, binary=self.binary, cmdargs=[cmd_arg("marionette"), "about:blank"], env=env, process_class=ProcessHandler, process_args={"processOutputLine": [self.on_output]}) self.logger.debug("Starting Firefox") self.runner.start(debug_args=self.debug_args, interactive=self.interactive) self.logger.debug("Firefox Started") def load_prefs(self): prefs_path = os.path.join(self.prefs_root, "prefs_general.js") if os.path.exists(prefs_path): preferences = Preferences.read_prefs(prefs_path) else: self.logger.warning("Failed to find base prefs file in %s" % prefs_path) preferences = [] return preferences def stop(self): self.logger.debug("Stopping browser") if self.runner is not None: try: self.runner.stop() except OSError: # This can happen on Windows if the process is already dead pass def pid(self): if self.runner.process_handler is None: return None try: return self.runner.process_handler.pid except AttributeError: return None def on_output(self, line): """Write a line of output from the firefox process to the log""" self.logger.process_output(self.pid(), line.decode("utf8", "replace"), command=" ".join(self.runner.command)) def is_alive(self): return self.runner.is_running() def cleanup(self): self.stop() def executor_browser(self): assert self.marionette_port is not None return ExecutorBrowser, {"marionette_port": self.marionette_port} def log_crash(self, logger, process, test): dump_dir = os.path.join(self.profile.profile, "minidumps") mozcrash.log_crashes(logger, dump_dir, symbols_path=self.symbols_path, stackwalk_binary=self.stackwalk_binary, process=process, test=test)
class MuletReftest(RefTest): build_type = "mulet" marionette = None def __init__(self, marionette_args): RefTest.__init__(self) self.last_test = os.path.basename(__file__) self.marionette_args = marionette_args self.profile = None self.runner = None self.test_script = os.path.join(here, 'b2g_start_script.js') self.timeout = None def run_marionette_script(self): self.marionette = Marionette(**self.marionette_args) assert (self.marionette.wait_for_port()) self.marionette.start_session() if self.build_type == "mulet": self._wait_for_homescreen(timeout=300) self._unlockScreen() self.marionette.set_context(self.marionette.CONTEXT_CHROME) if os.path.isfile(self.test_script): f = open(self.test_script, 'r') self.test_script = f.read() f.close() self.marionette.execute_script(self.test_script) def run_tests(self, tests, options): manifests = self.resolver.resolveManifests(options, tests) self.profile = self.create_profile(options, manifests, profile_to_clone=options.profile) env = self.buildBrowserEnv(options, self.profile.profile) kp_kwargs = { 'processOutputLine': [self._on_output], 'onTimeout': [self._on_timeout], 'kill_on_timeout': False } if not options.debugger: if not options.timeout: if mozinfo.info['debug']: options.timeout = 420 else: options.timeout = 300 self.timeout = options.timeout + 30.0 log.info("%s | Running tests: start.", os.path.basename(__file__)) cmd, args = self.build_command_line( options.app, ignore_window_size=options.ignoreWindowSize, browser_arg=options.browser_arg) self.runner = FirefoxRunner(profile=self.profile, binary=cmd, cmdargs=args, env=env, process_class=ProcessHandler, process_args=kp_kwargs, symbols_path=options.symbolsPath) status = 0 try: self.runner.start(outputTimeout=self.timeout) log.info("%s | Application pid: %d", os.path.basename(__file__), self.runner.process_handler.pid) # kick starts the reftest harness self.run_marionette_script() status = self.runner.wait() finally: self.runner.check_for_crashes(test_name=self.last_test) self.runner.cleanup() if status > 0: log.testFail("%s | application terminated with exit code %s", self.last_test, status) elif status < 0: log.info("%s | application killed with signal %s", self.last_test, -status) log.info("%s | Running tests: end.", os.path.basename(__file__)) return status def create_profile(self, options, manifests, profile_to_clone=None): profile = RefTest.createReftestProfile( self, options, manifests, profile_to_clone=profile_to_clone) prefs = {} # Turn off the locale picker screen prefs["browser.firstrun.show.localepicker"] = False if not self.build_type == "mulet": # FIXME: With Mulet we can't set this values since Gaia won't launch prefs["b2g.system_startup_url"] = \ "app://test-container.gaiamobile.org/index.html" prefs["b2g.system_manifest_url"] = \ "app://test-container.gaiamobile.org/manifest.webapp" # Make sure we disable system updates prefs["app.update.enabled"] = False prefs["app.update.url"] = "" prefs["app.update.url.override"] = "" # Disable webapp updates prefs["webapps.update.enabled"] = False # Disable tiles also prefs["browser.newtabpage.directory.source"] = "" prefs["browser.newtabpage.directory.ping"] = "" prefs["dom.ipc.tabs.disabled"] = False prefs["dom.mozBrowserFramesEnabled"] = True prefs["font.size.inflation.emPerLine"] = 0 prefs["font.size.inflation.minTwips"] = 0 prefs[ "network.dns.localDomains"] = "app://test-container.gaiamobile.org" prefs["reftest.browser.iframe.enabled"] = False prefs["reftest.remote"] = False # Set a future policy version to avoid the telemetry prompt. prefs["toolkit.telemetry.prompted"] = 999 prefs["toolkit.telemetry.notifiedOptOut"] = 999 # Set the extra prefs. profile.set_preferences(prefs) return profile def build_command_line(self, app, ignore_window_size=False, browser_arg=None): cmd = os.path.abspath(app) args = ['-marionette'] if browser_arg: args += [browser_arg] if not ignore_window_size: args.extend(['--screen', '800x1000']) if self.build_type == "mulet": args += ['-chrome', 'chrome://b2g/content/shell.html'] return cmd, args def _on_output(self, line): sys.stdout.write("%s\n" % line) sys.stdout.flush() # TODO use structured logging if "TEST-START" in line and "|" in line: self.last_test = line.split("|")[1].strip() def _on_timeout(self): msg = "%s | application timed out after %s seconds with no output" log.testFail(msg % (self.last_test, self.timeout)) # kill process to get a stack self.runner.stop(sig=signal.SIGABRT) def _unlockScreen(self): self.marionette.set_context(self.marionette.CONTEXT_CONTENT) self.marionette.import_script( os.path.abspath( os.path.join(__file__, os.path.pardir, "gaia_lock_screen.js"))) self.marionette.switch_to_frame() self.marionette.execute_async_script('GaiaLockScreen.unlock()') def _wait_for_homescreen(self, timeout): log.info("Waiting for home screen to load") Wait(self.marionette, timeout).until( expected.element_present(By.CSS_SELECTOR, '#homescreen[loading-state=false]'))
class B2GDesktopReftest(RefTest): build_type = "desktop" marionette = None def __init__(self, marionette_args): RefTest.__init__(self) self.last_test = os.path.basename(__file__) self.marionette_args = marionette_args self.profile = None self.runner = None self.test_script = os.path.join(here, 'b2g_start_script.js') self.timeout = None def run_marionette_script(self): self.marionette = Marionette(**self.marionette_args) assert(self.marionette.wait_for_port()) self.marionette.start_session() if self.build_type == "mulet": self._wait_for_homescreen(timeout=300) self._unlockScreen() self.marionette.set_context(self.marionette.CONTEXT_CHROME) if os.path.isfile(self.test_script): f = open(self.test_script, 'r') self.test_script = f.read() f.close() self.marionette.execute_script(self.test_script) def run_tests(self, tests, options): manifests = self.resolver.resolveManifests(options, tests) self.profile = self.create_profile(options, manifests, profile_to_clone=options.profile) env = self.buildBrowserEnv(options, self.profile.profile) kp_kwargs = { 'processOutputLine': [self._on_output], 'onTimeout': [self._on_timeout], 'kill_on_timeout': False } if not options.debugger: if not options.timeout: if mozinfo.info['debug']: options.timeout = 420 else: options.timeout = 300 self.timeout = options.timeout + 30.0 log.info("%s | Running tests: start.", os.path.basename(__file__)) cmd, args = self.build_command_line(options.app, ignore_window_size=options.ignoreWindowSize, browser_arg=options.browser_arg) self.runner = FirefoxRunner(profile=self.profile, binary=cmd, cmdargs=args, env=env, process_class=ProcessHandler, process_args=kp_kwargs, symbols_path=options.symbolsPath) status = 0 try: self.runner.start(outputTimeout=self.timeout) log.info("%s | Application pid: %d", os.path.basename(__file__), self.runner.process_handler.pid) # kick starts the reftest harness self.run_marionette_script() status = self.runner.wait() finally: self.runner.check_for_crashes(test_name=self.last_test) self.runner.cleanup() if status > 0: log.testFail("%s | application terminated with exit code %s", self.last_test, status) elif status < 0: log.info("%s | application killed with signal %s", self.last_test, -status) log.info("%s | Running tests: end.", os.path.basename(__file__)) return status def create_profile(self, options, manifests, profile_to_clone=None): profile = RefTest.createReftestProfile(self, options, manifests, profile_to_clone=profile_to_clone) prefs = {} # Turn off the locale picker screen prefs["browser.firstrun.show.localepicker"] = False if not self.build_type == "mulet": # FIXME: With Mulet we can't set this values since Gaia won't launch prefs["b2g.system_startup_url"] = \ "app://test-container.gaiamobile.org/index.html" prefs["b2g.system_manifest_url"] = \ "app://test-container.gaiamobile.org/manifest.webapp" # Make sure we disable system updates prefs["app.update.enabled"] = False prefs["app.update.url"] = "" prefs["app.update.url.override"] = "" # Disable webapp updates prefs["webapps.update.enabled"] = False # Disable tiles also prefs["browser.newtabpage.directory.source"] = "" prefs["browser.newtabpage.directory.ping"] = "" prefs["dom.ipc.tabs.disabled"] = False prefs["dom.mozBrowserFramesEnabled"] = True prefs["font.size.inflation.emPerLine"] = 0 prefs["font.size.inflation.minTwips"] = 0 prefs["network.dns.localDomains"] = "app://test-container.gaiamobile.org" prefs["reftest.browser.iframe.enabled"] = False prefs["reftest.remote"] = False # Set a future policy version to avoid the telemetry prompt. prefs["toolkit.telemetry.prompted"] = 999 prefs["toolkit.telemetry.notifiedOptOut"] = 999 # Disable periodic updates of service workers prefs["dom.serviceWorkers.periodic-updates.enabled"] = False # Set the extra prefs. profile.set_preferences(prefs) return profile def build_command_line(self, app, ignore_window_size=False, browser_arg=None): cmd = os.path.abspath(app) args = ['-marionette'] if browser_arg: args += [browser_arg] if not ignore_window_size: args.extend(['--screen', '800x1000']) if self.build_type == "mulet": args += ['-chrome', 'chrome://b2g/content/shell.html'] return cmd, args def _on_output(self, line): sys.stdout.write("%s\n" % line) sys.stdout.flush() # TODO use structured logging if "TEST-START" in line and "|" in line: self.last_test = line.split("|")[1].strip() def _on_timeout(self): msg = "%s | application timed out after %s seconds with no output" log.testFail(msg % (self.last_test, self.timeout)) # kill process to get a stack self.runner.stop(sig=signal.SIGABRT)
def valgrind_test(self, suppressions): from mozfile import TemporaryDirectory from mozhttpd import MozHttpd from mozprofile import FirefoxProfile, Preferences from mozprofile.permissions import ServerLocations from mozrunner import FirefoxRunner from mozrunner.utils import findInPath from six import string_types from valgrind.output_handler import OutputHandler build_dir = os.path.join(self.topsrcdir, 'build') # XXX: currently we just use the PGO inputs for Valgrind runs. This may # change in the future. httpd = MozHttpd(docroot=os.path.join(build_dir, 'pgo')) httpd.start(block=False) with TemporaryDirectory() as profilePath: # TODO: refactor this into mozprofile profile_data_dir = os.path.join( self.topsrcdir, 'testing', 'profiles') with open(os.path.join(profile_data_dir, 'profiles.json'), 'r') as fh: base_profiles = json.load(fh)['valgrind'] prefpaths = [os.path.join(profile_data_dir, profile, 'user.js') for profile in base_profiles] prefs = {} for path in prefpaths: prefs.update(Preferences.read_prefs(path)) interpolation = { 'server': '%s:%d' % httpd.httpd.server_address, } for k, v in prefs.items(): if isinstance(v, string_types): v = v.format(**interpolation) prefs[k] = Preferences.cast(v) quitter = os.path.join( self.topsrcdir, 'tools', 'quitter', '*****@*****.**') locations = ServerLocations() locations.add_host(host='127.0.0.1', port=httpd.httpd.server_port, options='primary') profile = FirefoxProfile(profile=profilePath, preferences=prefs, addons=[quitter], locations=locations) firefox_args = [httpd.get_url()] env = os.environ.copy() env['G_SLICE'] = 'always-malloc' env['MOZ_CC_RUN_DURING_SHUTDOWN'] = '1' env['MOZ_CRASHREPORTER_NO_REPORT'] = '1' env['XPCOM_DEBUG_BREAK'] = 'warn' env.update(self.extra_environment_variables) outputHandler = OutputHandler(self.log) kp_kwargs = {'processOutputLine': [outputHandler]} valgrind = 'valgrind' if not os.path.exists(valgrind): valgrind = findInPath(valgrind) valgrind_args = [ valgrind, '--smc-check=all-non-file', '--vex-iropt-register-updates=allregs-at-mem-access', '--gen-suppressions=all', '--num-callers=36', '--leak-check=full', '--show-possibly-lost=no', '--track-origins=yes', '--trace-children=yes', '-v', # Enable verbosity to get the list of used suppressions # Avoid excessive delays in the presence of spinlocks. # See bug 1309851. '--fair-sched=yes', # Keep debuginfo after library unmap. See bug 1382280. '--keep-debuginfo=yes', # Reduce noise level on rustc and/or LLVM compiled code. # See bug 1365915 '--expensive-definedness-checks=yes', ] for s in suppressions: valgrind_args.append('--suppressions=' + s) supps_dir = os.path.join(build_dir, 'valgrind') supps_file1 = os.path.join(supps_dir, 'cross-architecture.sup') valgrind_args.append('--suppressions=' + supps_file1) if mozinfo.os == 'linux': machtype = { 'x86_64': 'x86_64-pc-linux-gnu', 'x86': 'i386-pc-linux-gnu', }.get(mozinfo.processor) if machtype: supps_file2 = os.path.join(supps_dir, machtype + '.sup') if os.path.isfile(supps_file2): valgrind_args.append('--suppressions=' + supps_file2) exitcode = None timeout = 1800 try: runner = FirefoxRunner(profile=profile, binary=self.get_binary_path(), cmdargs=firefox_args, env=env, process_args=kp_kwargs) runner.start(debug_args=valgrind_args) exitcode = runner.wait(timeout=timeout) finally: errs = outputHandler.error_count supps = outputHandler.suppression_count if errs != supps: status = 1 # turns the TBPL job orange self.log(logging.ERROR, 'valgrind-fail-parsing', {'errs': errs, 'supps': supps}, 'TEST-UNEXPECTED-FAIL | valgrind-test | error parsing: {errs} errors ' 'seen, but {supps} generated suppressions seen') elif errs == 0: status = 0 self.log(logging.INFO, 'valgrind-pass', {}, 'TEST-PASS | valgrind-test | valgrind found no errors') else: status = 1 # turns the TBPL job orange # We've already printed details of the errors. if exitcode is None: status = 2 # turns the TBPL job red self.log(logging.ERROR, 'valgrind-fail-timeout', {'timeout': timeout}, 'TEST-UNEXPECTED-FAIL | valgrind-test | Valgrind timed out ' '(reached {timeout} second limit)') elif exitcode != 0: status = 2 # turns the TBPL job red self.log(logging.ERROR, 'valgrind-fail-errors', {}, 'TEST-UNEXPECTED-FAIL | valgrind-test | non-zero exit code' 'from Valgrind') httpd.stop() return status
class TPSFirefoxRunner(object): PROCESS_TIMEOUT = 240 def __init__(self, binary): if binary is not None and ('http://' in binary or 'ftp://' in binary): self.url = binary self.binary = None else: self.url = None self.binary = binary self.runner = None self.installdir = None def __del__(self): if self.installdir: shutil.rmtree(self.installdir, True) def download_build(self, installdir='downloadedbuild', appname='firefox', macAppName='Minefield.app'): self.installdir = os.path.abspath(installdir) buildName = os.path.basename(self.url) pathToBuild = os.path.join(os.path.dirname(os.path.abspath(__file__)), buildName) # delete the build if it already exists if os.access(pathToBuild, os.F_OK): os.remove(pathToBuild) # download the build print "downloading build" download_url(self.url, pathToBuild) # install the build print "installing %s" % pathToBuild shutil.rmtree(self.installdir, True) MozInstaller(src=pathToBuild, dest=self.installdir, dest_app=macAppName) # remove the downloaded archive os.remove(pathToBuild) # calculate path to binary platform = get_platform() if platform['name'] == 'Mac': binary = '%s/%s/Contents/MacOS/%s-bin' % (installdir, macAppName, appname) else: binary = '%s/%s/%s%s' % (installdir, appname, appname, '.exe' if platform['name'] == 'Windows' else '') return binary def run(self, profile=None, timeout=PROCESS_TIMEOUT, env=None, args=None): """Runs the given FirefoxRunner with the given Profile, waits for completion, then returns the process exit code """ if profile is None: profile = Profile() self.profile = profile if self.binary is None and self.url: self.binary = self.download_build() if self.runner is None: self.runner = FirefoxRunner(self.profile, binary=self.binary) self.runner.profile = self.profile if env is not None: self.runner.env.update(env) if args is not None: self.runner.cmdargs = copy.copy(args) self.runner.start() status = self.runner.process_handler.waitForFinish(timeout=timeout) return status
if not substs.get('HAVE_64BIT_BUILD'): for e in ('VS140COMNTOOLS', 'VS120COMNTOOLS'): if e not in env: continue vcdir = os.path.abspath(os.path.join(env[e], '../../VC/bin')) if os.path.exists(vcdir): env['PATH'] = '%s;%s' % (vcdir, env['PATH']) break # Run Firefox a first time to initialize its profile runner = FirefoxRunner(profile=profile, binary=binary, cmdargs=['data:text/html,<script>Quitter.quit()</script>'], env=env) runner.start() ret = runner.wait() if ret: print("Firefox exited with code %d during profile initialization" % ret) httpd.stop() sys.exit(ret) jarlog = os.getenv("JARLOG_FILE") if jarlog: env["MOZ_JAR_LOG_FILE"] = os.path.abspath(jarlog) print("jarlog: %s" % env["MOZ_JAR_LOG_FILE"]) cmdargs = ["http://localhost:%d/index.html" % PORT] runner = FirefoxRunner(profile=profile, binary=binary,
def bisectRecurse(self, testcondition=None, args_for_condition=[]): #Recursively build, run, and prompt verdict = "" current_revision = captureStdout(self.hgPrefix+["id","-i"]) if self.remote: print "on current revision "+current_revision print "This would ask for a remote changeset, but it's not implemented yet." #TODO: #Remote bisection! #Step 1. Check if revision is in the archive #Step 2. If revision is not in the archive, set remote=False and continue (it will build and bisect that revision) #if not check_archived: # set remote false and continue #else: #Step 3. If the revision is in the archive, download it and its corresponding tests #STEP3 #1. Extract tests into some directory #2. Extract Nightly.app into "tests" #MozInstaller(src=, dest="", dest_app="Nightly.app") #3. run the following: #test_command = ['python', 'mochitest/runtests.py', '--appname=./Nightly.app/Contents/MacOS/firefox-bin', '--utility-path=bin', '--extra-profile-file=bin/plugins', '--certificate-path=certs', '--autorun', '--close-when-done', '--console-level=INFO', '--test-path=test_name'] #output = captureStdout(test_command, ignoreStderr=True) #set verdict based on output #python mochitest/runtests.py --appname=./Nightly.app/Contents/MacOS/firefox-bin --utility-path=bin --extra-profile-file=bin/plugins --certificate-path=certs --autorun --close-when-done --console-level=INFO --test-path=test_name #example test name: Harness_sanity/test_sanityException.html #Step 4. Run and run test to get verdict #Step 5. Set verdict elif self.tryPusher: try: caller = BuildCaller(host=self.tryhost, port=int(self.tryport), data=current_revision) print "Getting revision "+current_revision+"..." except: print "Failed to connect to trypusher. Make sure your settings are correct and that the trypusher server was started." exit() response = caller.getChangeset() print "Waiting on Mozilla Pulse for revision " + response + "..." url = caller.getURLResponse(response) print "the base is " +url_base(url) #Download it here #1. Download from url, extract to same place as tests #2. Run test or start browser. binary_path = os.path.join(self.binaryDir,url_base(url)) downloaded_binary = download_url(url, dest=str(binary_path)) MozInstaller(src=str(binary_path), dest=str(self.testDir), dest_app="Nightly.app") #now nightly is installed in if sys.platform == "darwin": binary_path = os.path.join(self.testDir,"Nightly.app") runner = FirefoxRunner(binary=os.path.join(binary_path,"Contents","MacOS")+"/firefox-bin") elif sys.platform == "linux2": binary_path = os.path.join(self.testDir,"firefox") runner = FirefoxRunner(binary=binary_path) elif sys.platform == "win32" or sys.platform == "cygwin": binary_path = os.path.join(self.testDir,"firefox.exe") runner = FirefoxRunner(binary=binary_path) else: print "Your platform is not currently supported." quit() dest = runner.start() if not dest: print "Failed to start the downloaded binary" verdict == "skip" runner.wait() if verdict == "skip": pass elif testcondition!=None: #Support condition scripts where arg0 is the directory with the binary and tests args_to_pass = [self.testDir] + args_for_condition if hasattr(testcondition, "init"): testcondition.init(args_to_pass) #TODO: refactor to use directories with revision numbers #8.2.11 - revision number can now be found in current_revision variable tmpdir = tempfile.mkdtemp() verdict = testcondition.interesting(args_to_pass,tmpdir) #Allow user to return true/false or bad/good if verdict != "bad" and verdict != "good": verdict = "bad" if verdict else "good" else: try: self.build() except Exception: print "This build failed!" verdict = "skip" if verdict == "skip": pass elif testcondition==None: #Not using a test, interactive bisect begin! self.run() else: #Using Jesse's idea: import any testing script and run it as the truth condition args_to_pass = [self.objdir] + args_for_condition if hasattr(testcondition, "init"): testcondition.init(args_to_pass) #TODO: refactor to use directories with revision numbers #8.2.11 - revision number can now be found in current_revision variable tmpdir = tempfile.mkdtemp() verdict = testcondition.interesting(args_to_pass,tmpdir) #Allow user to return true/false or bad/good if verdict != "bad" and verdict != "good": verdict = "bad" if verdict else "good" while verdict not in ["good", "bad", "skip"]: verdict = raw_input("Was this commit good or bad? (type 'good', 'bad', or 'skip'): ") if verdict == 'g': verdict = "good" if verdict == 'b': verdict = "bad" if verdict == 's': verdict = "skip" # do hg bisect --good, --bad, or --skip verdictCommand = self.hgPrefix+["bisect","--"+verdict] print " ".join(verdictCommand) retval = captureStdout(verdictCommand) string_to_parse = str(retval) print string_to_parse self.check_done(string_to_parse) if retval.startswith("Testing changeset"): print "\n" self.bisectRecurse(testcondition=testcondition, args_for_condition=args_for_condition)
def valgrind_test(self, suppressions): import json import sys import tempfile from mozbuild.base import MozbuildObject from mozfile import TemporaryDirectory from mozhttpd import MozHttpd from mozprofile import FirefoxProfile, Preferences from mozprofile.permissions import ServerLocations from mozrunner import FirefoxRunner from mozrunner.utils import findInPath from valgrind.output_handler import OutputHandler build_dir = os.path.join(self.topsrcdir, 'build') # XXX: currently we just use the PGO inputs for Valgrind runs. This may # change in the future. httpd = MozHttpd(docroot=os.path.join(build_dir, 'pgo')) httpd.start(block=False) with TemporaryDirectory() as profilePath: #TODO: refactor this into mozprofile prefpath = os.path.join(self.topsrcdir, 'testing', 'profiles', 'prefs_general.js') prefs = {} prefs.update(Preferences.read_prefs(prefpath)) interpolation = { 'server': '%s:%d' % httpd.httpd.server_address, 'OOP': 'false'} prefs = json.loads(json.dumps(prefs) % interpolation) for pref in prefs: prefs[pref] = Preferences.cast(prefs[pref]) quitter = os.path.join(self.distdir, 'xpi-stage', 'quitter') locations = ServerLocations() locations.add_host(host='127.0.0.1', port=httpd.httpd.server_port, options='primary') profile = FirefoxProfile(profile=profilePath, preferences=prefs, addons=[quitter], locations=locations) firefox_args = [httpd.get_url()] env = os.environ.copy() env['G_SLICE'] = 'always-malloc' env['MOZ_CC_RUN_DURING_SHUTDOWN'] = '1' env['MOZ_CRASHREPORTER_NO_REPORT'] = '1' env['XPCOM_DEBUG_BREAK'] = 'warn' env.update(self.extra_environment_variables) outputHandler = OutputHandler() kp_kwargs = {'processOutputLine': [outputHandler]} valgrind = 'valgrind' if not os.path.exists(valgrind): valgrind = findInPath(valgrind) valgrind_args = [ valgrind, '--smc-check=all-non-file', '--vex-iropt-register-updates=allregs-at-mem-access', '--gen-suppressions=all', '--num-callers=36', '--leak-check=full', '--show-possibly-lost=no', '--track-origins=yes', '--trace-children=yes', # The gstreamer plugin scanner can run as part of executing # firefox, but is an external program. In some weird cases, # valgrind finds errors while executing __libc_freeres when # it runs, but those are not relevant, as it's related to # executing third party code. So don't trace # gst-plugin-scanner. '--trace-children-skip=*/gst-plugin-scanner', '-v', # Enable verbosity to get the list of used suppressions ] for s in suppressions: valgrind_args.append('--suppressions=' + s) supps_dir = os.path.join(build_dir, 'valgrind') supps_file1 = os.path.join(supps_dir, 'cross-architecture.sup') valgrind_args.append('--suppressions=' + supps_file1) # MACHTYPE is an odd bash-only environment variable that doesn't # show up in os.environ, so we have to get it another way. machtype = subprocess.check_output(['bash', '-c', 'echo $MACHTYPE']).rstrip() supps_file2 = os.path.join(supps_dir, machtype + '.sup') if os.path.isfile(supps_file2): valgrind_args.append('--suppressions=' + supps_file2) exitcode = None timeout = 1100 try: runner = FirefoxRunner(profile=profile, binary=self.get_binary_path(), cmdargs=firefox_args, env=env, process_args=kp_kwargs) runner.start(debug_args=valgrind_args) # This timeout is slightly less than the no-output timeout on # TBPL, so we'll timeout here first and give an informative # message. exitcode = runner.wait(timeout=timeout) finally: errs = outputHandler.error_count supps = outputHandler.suppression_count if errs != supps: status = 1 # turns the TBPL job orange print('TEST-UNEXPECTED-FAIL | valgrind-test | error parsing: {} errors seen, but {} generated suppressions seen'.format(errs, supps)) elif errs == 0: status = 0 print('TEST-PASS | valgrind-test | valgrind found no errors') else: status = 1 # turns the TBPL job orange # We've already printed details of the errors. if exitcode == None: status = 2 # turns the TBPL job red print('TEST-UNEXPECTED-FAIL | valgrind-test | Valgrind timed out (reached {} second limit)'.format(timeout)) elif exitcode != 0: status = 2 # turns the TBPL job red print('TEST-UNEXPECTED-FAIL | valgrind-test | non-zero exit code from Valgrind') httpd.stop() return status
class FirefoxThread(Thread): def __init__(self, binary, marionette_port = 2828): Thread.__init__(self) self.binary = binary self.marionette_port = marionette_port self.logger = FirefoxThreadLogger(None) self._firefoxRunningEvent = Event() def run(self): ''' Starts Firefox thread with Marionette turned on. ''' self.profile = FirefoxProfile() self.profile.set_preferences({"marionette.defaultPrefs.enabled" : True, "marionette.defaultPrefs.port": 2828, "browser.startup.page": 0, "browser.startup.homepage": "about:blank", }) self.runner = FirefoxRunner(profile = self.profile, binary = self.binary, kp_kwargs = {'processOutputLine' : [self.logger]}) self.runner.start() self._firefoxRunningEvent.set() self.runner.wait() def stop(self): ''' Stops Firefox/Nightly. To be called by external thread. ''' self.runner.stop() def getPID(self): ''' This is called by external threads, and blocks until PID is available in FirefoxRunner, which is shortly after start() has been called. ''' self._firefoxRunningEvent.wait() return self.runner.process_handler.proc.pid def waitForMarionettePortOpenReady(self, timeout): ''' This method can be run by an external thread. Returns True when the port is open, or False on timeout. It's active waiting with 1 sec heartbeat, if you know better solution please mail me. Originally taken from: https://github.com/mozilla/marionette_client/blob/master/marionette/emulator.py#L246 ''' starttime = datetime.datetime.now() while datetime.datetime.now() - starttime < datetime.timedelta(seconds=timeout): try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(('127.0.0.1', self.marionette_port)) data = sock.recv(16) sock.close() if '"from"' in data: return True except: #import traceback #print traceback.format_exc() pass time.sleep(1) print '' return False