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