1import base64 2import hashlib 3import httplib 4import json 5import os 6import subprocess 7import tempfile 8import threading 9import traceback 10import urlparse 11import uuid 12from collections import defaultdict 13 14from mozprocess import ProcessHandler 15 16from .base import (ExecutorException, 17 Protocol, 18 RefTestImplementation, 19 testharness_result_converter, 20 reftest_result_converter, 21 WdspecExecutor, WebDriverProtocol) 22from .process import ProcessTestExecutor 23from ..browsers.base import browser_command 24from ..wpttest import WdspecResult, WdspecSubtestResult 25from ..webdriver_server import ServoDriverServer 26from .executormarionette import WdspecRun 27 28pytestrunner = None 29webdriver = None 30 31extra_timeout = 5 # seconds 32 33hosts_text = """127.0.0.1 web-platform.test 34127.0.0.1 www.web-platform.test 35127.0.0.1 www1.web-platform.test 36127.0.0.1 www2.web-platform.test 37127.0.0.1 xn--n8j6ds53lwwkrqhv28a.web-platform.test 38127.0.0.1 xn--lve-6lad.web-platform.test 39""" 40 41def make_hosts_file(): 42 hosts_fd, hosts_path = tempfile.mkstemp() 43 with os.fdopen(hosts_fd, "w") as f: 44 f.write(hosts_text) 45 return hosts_path 46 47 48class ServoTestharnessExecutor(ProcessTestExecutor): 49 convert_result = testharness_result_converter 50 51 def __init__(self, browser, server_config, timeout_multiplier=1, debug_info=None, 52 pause_after_test=False, **kwargs): 53 ProcessTestExecutor.__init__(self, browser, server_config, 54 timeout_multiplier=timeout_multiplier, 55 debug_info=debug_info) 56 self.pause_after_test = pause_after_test 57 self.result_data = None 58 self.result_flag = None 59 self.protocol = Protocol(self, browser) 60 self.hosts_path = make_hosts_file() 61 62 def teardown(self): 63 try: 64 os.unlink(self.hosts_path) 65 except OSError: 66 pass 67 ProcessTestExecutor.teardown(self) 68 69 def do_test(self, test): 70 self.result_data = None 71 self.result_flag = threading.Event() 72 73 args = [ 74 "--hard-fail", "-u", "Servo/wptrunner", 75 "-Z", "replace-surrogates", "-z", self.test_url(test), 76 ] 77 for stylesheet in self.browser.user_stylesheets: 78 args += ["--user-stylesheet", stylesheet] 79 for pref, value in test.environment.get('prefs', {}).iteritems(): 80 args += ["--pref", "%s=%s" % (pref, value)] 81 if self.browser.ca_certificate_path: 82 args += ["--certificate-path", self.browser.ca_certificate_path] 83 args += self.browser.binary_args 84 debug_args, command = browser_command(self.binary, args, self.debug_info) 85 86 self.command = command 87 88 if self.pause_after_test: 89 self.command.remove("-z") 90 91 self.command = debug_args + self.command 92 93 env = os.environ.copy() 94 env["HOST_FILE"] = self.hosts_path 95 env["RUST_BACKTRACE"] = "1" 96 97 98 if not self.interactive: 99 self.proc = ProcessHandler(self.command, 100 processOutputLine=[self.on_output], 101 onFinish=self.on_finish, 102 env=env, 103 storeOutput=False) 104 self.proc.run() 105 else: 106 self.proc = subprocess.Popen(self.command, env=env) 107 108 try: 109 timeout = test.timeout * self.timeout_multiplier 110 111 # Now wait to get the output we expect, or until we reach the timeout 112 if not self.interactive and not self.pause_after_test: 113 wait_timeout = timeout + 5 114 self.result_flag.wait(wait_timeout) 115 else: 116 wait_timeout = None 117 self.proc.wait() 118 119 proc_is_running = True 120 121 if self.result_flag.is_set(): 122 if self.result_data is not None: 123 result = self.convert_result(test, self.result_data) 124 else: 125 self.proc.wait() 126 result = (test.result_cls("CRASH", None), []) 127 proc_is_running = False 128 else: 129 result = (test.result_cls("TIMEOUT", None), []) 130 131 132 if proc_is_running: 133 if self.pause_after_test: 134 self.logger.info("Pausing until the browser exits") 135 self.proc.wait() 136 else: 137 self.proc.kill() 138 except KeyboardInterrupt: 139 self.proc.kill() 140 raise 141 142 return result 143 144 def on_output(self, line): 145 prefix = "ALERT: RESULT: " 146 line = line.decode("utf8", "replace") 147 if line.startswith(prefix): 148 self.result_data = json.loads(line[len(prefix):]) 149 self.result_flag.set() 150 else: 151 if self.interactive: 152 print line 153 else: 154 self.logger.process_output(self.proc.pid, 155 line, 156 " ".join(self.command)) 157 158 def on_finish(self): 159 self.result_flag.set() 160 161 162class TempFilename(object): 163 def __init__(self, directory): 164 self.directory = directory 165 self.path = None 166 167 def __enter__(self): 168 self.path = os.path.join(self.directory, str(uuid.uuid4())) 169 return self.path 170 171 def __exit__(self, *args, **kwargs): 172 try: 173 os.unlink(self.path) 174 except OSError: 175 pass 176 177 178class ServoRefTestExecutor(ProcessTestExecutor): 179 convert_result = reftest_result_converter 180 181 def __init__(self, browser, server_config, binary=None, timeout_multiplier=1, 182 screenshot_cache=None, debug_info=None, pause_after_test=False, 183 **kwargs): 184 ProcessTestExecutor.__init__(self, 185 browser, 186 server_config, 187 timeout_multiplier=timeout_multiplier, 188 debug_info=debug_info) 189 190 self.protocol = Protocol(self, browser) 191 self.screenshot_cache = screenshot_cache 192 self.implementation = RefTestImplementation(self) 193 self.tempdir = tempfile.mkdtemp() 194 self.hosts_path = make_hosts_file() 195 196 def teardown(self): 197 try: 198 os.unlink(self.hosts_path) 199 except OSError: 200 pass 201 os.rmdir(self.tempdir) 202 ProcessTestExecutor.teardown(self) 203 204 def screenshot(self, test, viewport_size, dpi): 205 full_url = self.test_url(test) 206 207 with TempFilename(self.tempdir) as output_path: 208 debug_args, command = browser_command( 209 self.binary, 210 [ 211 "--hard-fail", "--exit", 212 "-u", "Servo/wptrunner", 213 "-Z", "disable-text-aa,load-webfonts-synchronously,replace-surrogates", 214 "--output=%s" % output_path, full_url 215 ] + self.browser.binary_args, 216 self.debug_info) 217 218 for stylesheet in self.browser.user_stylesheets: 219 command += ["--user-stylesheet", stylesheet] 220 221 for pref, value in test.environment.get('prefs', {}).iteritems(): 222 command += ["--pref", "%s=%s" % (pref, value)] 223 224 command += ["--resolution", viewport_size or "800x600"] 225 226 if self.browser.ca_certificate_path: 227 command += ["--certificate-path", self.browser.ca_certificate_path] 228 229 if dpi: 230 command += ["--device-pixel-ratio", dpi] 231 232 # Run ref tests in headless mode 233 command += ["-z"] 234 235 self.command = debug_args + command 236 237 env = os.environ.copy() 238 env["HOST_FILE"] = self.hosts_path 239 env["RUST_BACKTRACE"] = "1" 240 241 if not self.interactive: 242 self.proc = ProcessHandler(self.command, 243 processOutputLine=[self.on_output], 244 env=env) 245 246 247 try: 248 self.proc.run() 249 timeout = test.timeout * self.timeout_multiplier + 5 250 rv = self.proc.wait(timeout=timeout) 251 except KeyboardInterrupt: 252 self.proc.kill() 253 raise 254 else: 255 self.proc = subprocess.Popen(self.command, 256 env=env) 257 try: 258 rv = self.proc.wait() 259 except KeyboardInterrupt: 260 self.proc.kill() 261 raise 262 263 if rv is None: 264 self.proc.kill() 265 return False, ("EXTERNAL-TIMEOUT", None) 266 267 if rv != 0 or not os.path.exists(output_path): 268 return False, ("CRASH", None) 269 270 with open(output_path) as f: 271 # Might need to strip variable headers or something here 272 data = f.read() 273 return True, base64.b64encode(data) 274 275 def do_test(self, test): 276 result = self.implementation.run_test(test) 277 278 return self.convert_result(test, result) 279 280 def on_output(self, line): 281 line = line.decode("utf8", "replace") 282 if self.interactive: 283 print line 284 else: 285 self.logger.process_output(self.proc.pid, 286 line, 287 " ".join(self.command)) 288 289 290class ServoDriverProtocol(WebDriverProtocol): 291 server_cls = ServoDriverServer 292 293class ServoWdspecExecutor(WdspecExecutor): 294 protocol_cls = ServoDriverProtocol 295