OLD | NEW |
| (Empty) |
1 # Copyright (c) 2009 The Chromium Authors. All rights reserved. | |
2 # Use of this source code is governed by a BSD-style license that can be | |
3 # found in the LICENSE file. | |
4 | |
5 """A class to start/stop the apache http server used by layout tests.""" | |
6 | |
7 import logging | |
8 import optparse | |
9 import os | |
10 import re | |
11 import subprocess | |
12 import sys | |
13 | |
14 import http_server_base | |
15 import path_utils | |
16 import platform_utils | |
17 | |
18 | |
19 class LayoutTestApacheHttpd(http_server_base.HttpServerBase): | |
20 | |
21 def __init__(self, output_dir): | |
22 """Args: | |
23 output_dir: the absolute path to the layout test result directory | |
24 """ | |
25 self._output_dir = output_dir | |
26 self._httpd_proc = None | |
27 path_utils.MaybeMakeDirectory(output_dir) | |
28 | |
29 self.mappings = [{'port': 8000}, | |
30 {'port': 8080}, | |
31 {'port': 8081}, | |
32 {'port': 8443, 'sslcert': True}] | |
33 | |
34 # The upstream .conf file assumed the existence of /tmp/WebKit for | |
35 # placing apache files like the lock file there. | |
36 self._runtime_path = os.path.join("/tmp", "WebKit") | |
37 path_utils.MaybeMakeDirectory(self._runtime_path) | |
38 | |
39 # The PID returned when Apache is started goes away (due to dropping | |
40 # privileges?). The proper controlling PID is written to a file in the | |
41 # apache runtime directory. | |
42 self._pid_file = os.path.join(self._runtime_path, 'httpd.pid') | |
43 | |
44 test_dir = path_utils.PathFromBase('third_party', 'WebKit', | |
45 'LayoutTests') | |
46 js_test_resources_dir = self._CygwinSafeJoin(test_dir, "fast", "js", | |
47 "resources") | |
48 mime_types_path = self._CygwinSafeJoin(test_dir, "http", "conf", | |
49 "mime.types") | |
50 cert_file = self._CygwinSafeJoin(test_dir, "http", "conf", | |
51 "webkit-httpd.pem") | |
52 access_log = self._CygwinSafeJoin(output_dir, "access_log.txt") | |
53 error_log = self._CygwinSafeJoin(output_dir, "error_log.txt") | |
54 document_root = self._CygwinSafeJoin(test_dir, "http", "tests") | |
55 | |
56 executable = platform_utils.ApacheExecutablePath() | |
57 if self._IsCygwin(): | |
58 executable = self._GetCygwinPath(executable) | |
59 | |
60 cmd = [executable, | |
61 '-f', self._GetApacheConfigFilePath(test_dir, output_dir), | |
62 '-C', "\'DocumentRoot %s\'" % document_root, | |
63 '-c', "\'Alias /js-test-resources %s\'" % js_test_resources_dir, | |
64 '-C', "\'Listen %s\'" % "127.0.0.1:8000", | |
65 '-C', "\'Listen %s\'" % "127.0.0.1:8081", | |
66 '-c', "\'TypesConfig \"%s\"\'" % mime_types_path, | |
67 '-c', "\'CustomLog \"%s\" common\'" % access_log, | |
68 '-c', "\'ErrorLog \"%s\"\'" % error_log, | |
69 '-C', "\'User \"%s\"\'" % os.environ.get("USERNAME", | |
70 os.environ.get("USER", ""))] | |
71 | |
72 if self._IsCygwin(): | |
73 cygbin = path_utils.PathFromBase('third_party', 'cygwin', 'bin') | |
74 # Not entirely sure why, but from cygwin we need to run the | |
75 # httpd command through bash. | |
76 self._start_cmd = [ | |
77 os.path.join(cygbin, 'bash.exe'), | |
78 '-c', | |
79 'PATH=%s %s' % (self._GetCygwinPath(cygbin), " ".join(cmd)), | |
80 ] | |
81 else: | |
82 # TODO(ojan): When we get cygwin using Apache 2, use set the | |
83 # cert file for cygwin as well. | |
84 cmd.extend(['-c', "\'SSLCertificateFile %s\'" % cert_file]) | |
85 # Join the string here so that Cygwin/Windows and Mac/Linux | |
86 # can use the same code. Otherwise, we could remove the single | |
87 # quotes above and keep cmd as a sequence. | |
88 self._start_cmd = " ".join(cmd) | |
89 | |
90 def _IsCygwin(self): | |
91 return sys.platform in ("win32", "cygwin") | |
92 | |
93 def _CygwinSafeJoin(self, *parts): | |
94 """Returns a platform appropriate path.""" | |
95 path = os.path.join(*parts) | |
96 if self._IsCygwin(): | |
97 return self._GetCygwinPath(path) | |
98 return path | |
99 | |
100 def _GetCygwinPath(self, path): | |
101 """Convert a Windows path to a cygwin path. | |
102 | |
103 The cygpath utility insists on converting paths that it thinks are | |
104 Cygwin root paths to what it thinks the correct roots are. So paths | |
105 such as "C:\b\slave\webkit-release\build\third_party\cygwin\bin" | |
106 are converted to plain "/usr/bin". To avoid this, we | |
107 do the conversion manually. | |
108 | |
109 The path is expected to be an absolute path, on any drive. | |
110 """ | |
111 drive_regexp = re.compile(r'([a-z]):[/\\]', re.IGNORECASE) | |
112 | |
113 def LowerDrive(matchobj): | |
114 return '/cygdrive/%s/' % matchobj.group(1).lower() | |
115 path = drive_regexp.sub(LowerDrive, path) | |
116 return path.replace('\\', '/') | |
117 | |
118 def _GetApacheConfigFilePath(self, test_dir, output_dir): | |
119 """Returns the path to the apache config file to use. | |
120 Args: | |
121 test_dir: absolute path to the LayoutTests directory. | |
122 output_dir: absolute path to the layout test results directory. | |
123 """ | |
124 httpd_config = platform_utils.ApacheConfigFilePath() | |
125 httpd_config_copy = os.path.join(output_dir, "httpd.conf") | |
126 httpd_conf = open(httpd_config).read() | |
127 if self._IsCygwin(): | |
128 # This is a gross hack, but it lets us use the upstream .conf file | |
129 # and our checked in cygwin. This tells the server the root | |
130 # directory to look in for .so modules. It will use this path | |
131 # plus the relative paths to the .so files listed in the .conf | |
132 # file. We have apache/cygwin checked into our tree so | |
133 # people don't have to install it into their cygwin. | |
134 cygusr = path_utils.PathFromBase('third_party', 'cygwin', 'usr') | |
135 httpd_conf = httpd_conf.replace('ServerRoot "/usr"', | |
136 'ServerRoot "%s"' % self._GetCygwinPath(cygusr)) | |
137 | |
138 # TODO(ojan): Instead of writing an extra file, checkin a conf file | |
139 # upstream. Or, even better, upstream/delete all our chrome http | |
140 # tests so we don't need this special-cased DocumentRoot and then | |
141 # just use the upstream | |
142 # conf file. | |
143 chrome_document_root = path_utils.PathFromBase('webkit', 'data', | |
144 'layout_tests') | |
145 if self._IsCygwin(): | |
146 chrome_document_root = self._GetCygwinPath(chrome_document_root) | |
147 httpd_conf = (httpd_conf + | |
148 self._GetVirtualHostConfig(chrome_document_root, 8081)) | |
149 | |
150 f = open(httpd_config_copy, 'wb') | |
151 f.write(httpd_conf) | |
152 f.close() | |
153 | |
154 if self._IsCygwin(): | |
155 return self._GetCygwinPath(httpd_config_copy) | |
156 return httpd_config_copy | |
157 | |
158 def _GetVirtualHostConfig(self, document_root, port, ssl=False): | |
159 """Returns a <VirtualHost> directive block for an httpd.conf file. | |
160 It will listen to 127.0.0.1 on each of the given port. | |
161 """ | |
162 return '\n'.join(('<VirtualHost 127.0.0.1:%s>' % port, | |
163 'DocumentRoot %s' % document_root, | |
164 ssl and 'SSLEngine On' or '', | |
165 '</VirtualHost>', '')) | |
166 | |
167 def _StartHttpdProcess(self): | |
168 """Starts the httpd process and returns whether there were errors.""" | |
169 # Use shell=True because we join the arguments into a string for | |
170 # the sake of Window/Cygwin and it needs quoting that breaks | |
171 # shell=False. | |
172 self._httpd_proc = subprocess.Popen(self._start_cmd, | |
173 stderr=subprocess.PIPE, | |
174 shell=True) | |
175 err = self._httpd_proc.stderr.read() | |
176 if len(err): | |
177 logging.debug(err) | |
178 return False | |
179 return True | |
180 | |
181 def Start(self): | |
182 """Starts the apache http server.""" | |
183 # Stop any currently running servers. | |
184 self.Stop() | |
185 | |
186 logging.debug("Starting apache http server") | |
187 server_started = self.WaitForAction(self._StartHttpdProcess) | |
188 if server_started: | |
189 logging.debug("Apache started. Testing ports") | |
190 server_started = self.WaitForAction(self.IsServerRunningOnAllPorts) | |
191 | |
192 if server_started: | |
193 logging.debug("Server successfully started") | |
194 else: | |
195 raise Exception('Failed to start http server') | |
196 | |
197 def Stop(self): | |
198 """Stops the apache http server.""" | |
199 logging.debug("Shutting down any running http servers") | |
200 httpd_pid = None | |
201 if os.path.exists(self._pid_file): | |
202 httpd_pid = int(open(self._pid_file).readline()) | |
203 path_utils.ShutDownHTTPServer(httpd_pid) | |
OLD | NEW |