1import os 2 3import moznetwork 4from mozrunner import FennecEmulatorRunner 5 6from .base import (get_free_port, 7 cmd_arg, 8 browser_command) 9from ..executors.executormarionette import (MarionetteTestharnessExecutor, # noqa: F401 10 MarionetteRefTestExecutor, # noqa: F401 11 MarionetteCrashtestExecutor) # noqa: F401 12from .base import (Browser, 13 ExecutorBrowser) 14from .firefox import (get_timeout_multiplier, # noqa: F401 15 run_info_extras as fx_run_info_extras, 16 update_properties, # noqa: F401 17 executor_kwargs, # noqa: F401 18 ProfileCreator as FirefoxProfileCreator) 19 20 21__wptrunner__ = {"product": "firefox_android", 22 "check_args": "check_args", 23 "browser": "FirefoxAndroidBrowser", 24 "executor": {"testharness": "MarionetteTestharnessExecutor", 25 "reftest": "MarionetteRefTestExecutor", 26 "crashtest": "MarionetteCrashtestExecutor"}, 27 "browser_kwargs": "browser_kwargs", 28 "executor_kwargs": "executor_kwargs", 29 "env_extras": "env_extras", 30 "env_options": "env_options", 31 "run_info_extras": "run_info_extras", 32 "update_properties": "update_properties", 33 "timeout_multiplier": "get_timeout_multiplier"} 34 35 36def check_args(**kwargs): 37 pass 38 39 40def browser_kwargs(logger, test_type, run_info_data, config, **kwargs): 41 return {"adb_binary": kwargs["adb_binary"], 42 "package_name": kwargs["package_name"], 43 "device_serial": kwargs["device_serial"], 44 "prefs_root": kwargs["prefs_root"], 45 "extra_prefs": kwargs["extra_prefs"], 46 "test_type": test_type, 47 "debug_info": kwargs["debug_info"], 48 "symbols_path": kwargs["symbols_path"], 49 "stackwalk_binary": kwargs["stackwalk_binary"], 50 "certutil_binary": kwargs["certutil_binary"], 51 "ca_certificate_path": config.ssl_config["ca_cert_path"], 52 "enable_webrender": kwargs["enable_webrender"], 53 "stackfix_dir": kwargs["stackfix_dir"], 54 "binary_args": kwargs["binary_args"], 55 "timeout_multiplier": get_timeout_multiplier(test_type, 56 run_info_data, 57 **kwargs), 58 "e10s": run_info_data["e10s"], 59 # desktop only 60 "leak_check": False, 61 "stylo_threads": kwargs["stylo_threads"], 62 "chaos_mode_flags": kwargs["chaos_mode_flags"], 63 "config": config, 64 "install_fonts": kwargs["install_fonts"], 65 "tests_root": config.doc_root, 66 "specialpowers_path": kwargs["specialpowers_path"]} 67 68 69def env_extras(**kwargs): 70 return [] 71 72 73def run_info_extras(**kwargs): 74 rv = fx_run_info_extras(**kwargs) 75 package = kwargs["package_name"] 76 rv.update({"e10s": True if package is not None and "geckoview" in package else False, 77 "headless": False}) 78 return rv 79 80 81def env_options(): 82 # The server host is set to public localhost IP so that resources can be accessed 83 # from Android emulator 84 return {"server_host": moznetwork.get_ip(), 85 "bind_address": False, 86 "supports_debugger": True} 87 88 89class ProfileCreator(FirefoxProfileCreator): 90 def __init__(self, logger, prefs_root, config, test_type, extra_prefs, 91 enable_fission, browser_channel, certutil_binary, ca_certificate_path): 92 super(ProfileCreator, self).__init__(logger, prefs_root, config, test_type, extra_prefs, 93 True, enable_fission, browser_channel, None, 94 certutil_binary, ca_certificate_path) 95 96 def _set_required_prefs(self, profile): 97 profile.set_preferences({ 98 "network.dns.localDomains": ",".join(self.config.domains_set), 99 "dom.disable_open_during_load": False, 100 "places.history.enabled": False, 101 "dom.send_after_paint_to_content": True, 102 "network.preload": True, 103 "browser.tabs.remote.autostart": True, 104 }) 105 106 if self.test_type == "reftest": 107 self.logger.info("Setting android reftest preferences") 108 profile.set_preferences({ 109 "browser.viewport.desktopWidth": 800, 110 # Disable high DPI 111 "layout.css.devPixelsPerPx": "1.0", 112 # Ensure that the full browser element 113 # appears in the screenshot 114 "apz.allow_zooming": False, 115 "android.widget_paints_background": False, 116 # Ensure that scrollbars are always painted 117 "layout.testing.overlay-scrollbars.always-visible": True, 118 }) 119 120 121class FirefoxAndroidBrowser(Browser): 122 init_timeout = 300 123 shutdown_timeout = 60 124 125 def __init__(self, logger, prefs_root, test_type, package_name="org.mozilla.geckoview.test", 126 device_serial="emulator-5444", extra_prefs=None, debug_info=None, 127 symbols_path=None, stackwalk_binary=None, certutil_binary=None, 128 ca_certificate_path=None, e10s=False, enable_webrender=False, stackfix_dir=None, 129 binary_args=None, timeout_multiplier=None, leak_check=False, asan=False, 130 stylo_threads=1, chaos_mode_flags=None, config=None, browser_channel="nightly", 131 install_fonts=False, tests_root=None, specialpowers_path=None, adb_binary=None, 132 **kwargs): 133 134 super(FirefoxAndroidBrowser, self).__init__(logger) 135 self.prefs_root = prefs_root 136 self.test_type = test_type 137 self.package_name = package_name 138 self.device_serial = device_serial 139 self.debug_info = debug_info 140 self.symbols_path = symbols_path 141 self.stackwalk_binary = stackwalk_binary 142 self.certutil_binary = certutil_binary 143 self.ca_certificate_path = ca_certificate_path 144 self.e10s = True 145 self.enable_webrender = enable_webrender 146 self.stackfix_dir = stackfix_dir 147 self.binary_args = binary_args 148 self.timeout_multiplier = timeout_multiplier 149 self.leak_check = leak_check 150 self.asan = asan 151 self.stylo_threads = stylo_threads 152 self.chaos_mode_flags = chaos_mode_flags 153 self.config = config 154 self.browser_channel = browser_channel 155 self.install_fonts = install_fonts 156 self.tests_root = tests_root 157 self.specialpowers_path = specialpowers_path 158 self.adb_binary = adb_binary 159 160 self.profile_creator = ProfileCreator(logger, 161 prefs_root, 162 config, 163 test_type, 164 extra_prefs, 165 False, 166 browser_channel, 167 certutil_binary, 168 ca_certificate_path) 169 170 self.marionette_port = None 171 self.profile = None 172 self.runner = None 173 self._settings = {} 174 175 def settings(self, test): 176 self._settings = {"check_leaks": self.leak_check and not test.leaks, 177 "lsan_allowed": test.lsan_allowed, 178 "lsan_max_stack_depth": test.lsan_max_stack_depth, 179 "mozleak_allowed": self.leak_check and test.mozleak_allowed, 180 "mozleak_thresholds": self.leak_check and test.mozleak_threshold, 181 "special_powers": self.specialpowers_path and test.url_base == "/_mozilla/"} 182 return self._settings 183 184 def start(self, **kwargs): 185 if self.marionette_port is None: 186 self.marionette_port = get_free_port() 187 188 addons = [self.specialpowers_path] if self._settings.get("special_powers") else None 189 self.profile = self.profile_creator.create(addons=addons) 190 self.profile.set_preferences({"marionette.port": self.marionette_port}) 191 192 if self.install_fonts: 193 self.logger.debug("Copying Ahem font to profile") 194 font_dir = os.path.join(self.profile.profile, "fonts") 195 if not os.path.exists(font_dir): 196 os.makedirs(font_dir) 197 with open(os.path.join(self.tests_root, "fonts", "Ahem.ttf"), "rb") as src: 198 with open(os.path.join(font_dir, "Ahem.ttf"), "wb") as dest: 199 dest.write(src.read()) 200 201 self.leak_report_file = None 202 203 debug_args, cmd = browser_command(self.package_name, 204 self.binary_args if self.binary_args else [] + 205 [cmd_arg("marionette"), "about:blank"], 206 self.debug_info) 207 208 env = {} 209 env["MOZ_CRASHREPORTER"] = "1" 210 env["MOZ_CRASHREPORTER_SHUTDOWN"] = "1" 211 env["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1" 212 env["STYLO_THREADS"] = str(self.stylo_threads) 213 if self.chaos_mode_flags is not None: 214 env["MOZ_CHAOSMODE"] = str(self.chaos_mode_flags) 215 if self.enable_webrender: 216 env["MOZ_WEBRENDER"] = "1" 217 else: 218 env["MOZ_WEBRENDER"] = "0" 219 220 self.runner = FennecEmulatorRunner(app=self.package_name, 221 profile=self.profile, 222 cmdargs=cmd[1:], 223 env=env, 224 symbols_path=self.symbols_path, 225 serial=self.device_serial, 226 # TODO - choose appropriate log dir 227 logdir=os.getcwd(), 228 adb_path=self.adb_binary, 229 explicit_cleanup=True) 230 231 self.logger.debug("Starting %s" % self.package_name) 232 # connect to a running emulator 233 self.runner.device.connect() 234 235 self.runner.stop() 236 self.runner.start(debug_args=debug_args, 237 interactive=self.debug_info and self.debug_info.interactive) 238 239 self.runner.device.device.forward( 240 local="tcp:{}".format(self.marionette_port), 241 remote="tcp:{}".format(self.marionette_port)) 242 243 for ports in self.config.ports.values(): 244 for port in ports: 245 self.runner.device.device.reverse( 246 local="tcp:{}".format(port), 247 remote="tcp:{}".format(port)) 248 249 self.logger.debug("%s Started" % self.package_name) 250 251 def stop(self, force=False): 252 if self.runner is not None: 253 if self.runner.device.connected: 254 try: 255 self.runner.device.device.remove_forwards() 256 self.runner.device.device.remove_reverses() 257 except Exception as e: 258 self.logger.warning("Failed to remove forwarded or reversed ports: %s" % e) 259 # We assume that stopping the runner prompts the 260 # browser to shut down. 261 self.runner.cleanup() 262 self.logger.debug("stopped") 263 264 def pid(self): 265 if self.runner.process_handler is None: 266 return None 267 268 try: 269 return self.runner.process_handler.pid 270 except AttributeError: 271 return None 272 273 def is_alive(self): 274 if self.runner: 275 return self.runner.is_running() 276 return False 277 278 def cleanup(self, force=False): 279 self.stop(force) 280 281 def executor_browser(self): 282 return ExecutorBrowser, {"marionette_port": self.marionette_port, 283 # We never want marionette to install extensions because 284 # that doesn't work on Android; instead they are in the profile 285 "extensions": []} 286 287 def check_crash(self, process, test): 288 if not os.environ.get("MINIDUMP_STACKWALK", "") and self.stackwalk_binary: 289 os.environ["MINIDUMP_STACKWALK"] = self.stackwalk_binary 290 return bool(self.runner.check_for_crashes(test_name=test)) 291