2 # ***** BEGIN LICENSE BLOCK *****
3 # Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 # The contents of this file are subject to the Mozilla Public License Version
6 # 1.1 (the "License"); you may not use this file except in compliance with
7 # the License. You may obtain a copy of the License at
8 # http://www.mozilla.org/MPL/
10 # Software distributed under the License is distributed on an "AS IS" basis,
11 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 # for the specific language governing rights and limitations under the
15 # The Original Code is mozilla.org code.
17 # The Initial Developer of the Original Code is
19 # Portions created by the Initial Developer are Copyright (C) 2008
20 # the Initial Developer. All Rights Reserved.
23 # Robert Sayre <sayrer@gmail.com>
24 # Jeff Walden <jwalden+bmo@mit.edu>
26 # Alternatively, the contents of this file may be used under the terms of
27 # either the GNU General Public License Version 2 or later (the "GPL"), or
28 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 # in which case the provisions of the GPL or the LGPL are applicable instead
30 # of those above. If you wish to allow use of your version of this file only
31 # under the terms of either the GPL or the LGPL, and not to allow others to
32 # use your version of this file under the terms of the MPL, indicate your
33 # decision by deleting the provisions above and replace them with the notice
34 # and other provisions required by the GPL or the LGPL. If you do not delete
35 # the provisions above, a recipient may use your version of this file under
36 # the terms of any one of the MPL, the GPL or the LGPL.
38 # ***** END LICENSE BLOCK *****
41 from datetime
import datetime
53 Runs the browser from a script, and provides useful utilities
54 for setting up the browser environment.
57 SCRIPT_DIR
= os
.path
.abspath(os
.path
.realpath(os
.path
.dirname(sys
.argv
[0])))
72 # These are generated in mozilla/build/Makefile.in
73 #expand DIST_BIN = __XPC_BIN_PATH__
74 #expand IS_WIN32 = len("__WIN32__") != 0
75 #expand IS_MAC = __IS_MAC__ != 0
77 #expand IS_CYGWIN = __IS_CYGWIN__ == 1
81 #expand IS_CAMINO = __IS_CAMINO__ != 0
82 #expand BIN_SUFFIX = __BIN_SUFFIX__
84 UNIXISH
= not IS_WIN32
and not IS_MAC
86 #expand DEFAULT_APP = "./" + __BROWSER_PATH__
87 #expand CERTS_SRC_DIR = __CERTS_SRC_DIR__
88 #expand IS_TEST_BUILD = __IS_TEST_BUILD__
89 #expand IS_DEBUG_BUILD = __IS_DEBUG_BUILD__
95 # We use the logging system here primarily because it'll handle multiple
96 # threads, which is needed to process the output of the server and application
97 # processes simultaneously.
98 log
= logging
.getLogger()
99 handler
= logging
.StreamHandler(sys
.stdout
)
100 log
.setLevel(logging
.INFO
)
101 log
.addHandler(handler
)
108 class Process(subprocess
.Popen
):
110 Represents our view of a subprocess.
111 It adds a kill() method which allows it to be stopped explicitly.
117 pid
= "%i" % self
.pid
118 if platform
.release() == "2000":
119 # Windows 2000 needs 'kill.exe' from the 'Windows 2000 Resource Kit tools'. (See bug 475455.)
121 subprocess
.Popen(["kill", "-f", pid
]).wait()
123 log
.info("TEST-UNEXPECTED-FAIL | Missing 'kill' utility to kill process with pid=%s. Kill it manually!", pid
)
125 # Windows XP and later.
126 subprocess
.Popen(["taskkill", "/F", "/PID", pid
]).wait()
128 os
.kill(self
.pid
, signal
.SIGKILL
)
135 class SyntaxError(Exception):
136 "Signifies a syntax error on a particular line in server-locations.txt."
138 def __init__(self
, lineno
, msg
= None):
143 s
= "Syntax error on line " + str(self
.lineno
)
145 s
+= ": %s." % self
.msg
152 "Represents a location line in server-locations.txt."
154 def __init__(self
, scheme
, host
, port
, options
):
158 self
.options
= options
161 def readLocations(locationsPath
= "server-locations.txt"):
163 Reads the locations at which the Mochitest HTTP server is available from
164 server-locations.txt.
167 locationFile
= codecs
.open(locationsPath
, "r", "UTF-8")
169 # Perhaps more detail than necessary, but it's the easiest way to make sure
170 # we get exactly the format we want. See server-locations.txt for the exact
171 # format guaranteed here.
172 lineRe
= re
.compile(r
"^(?P<scheme>[a-z][-a-z0-9+.]*)"
175 r
"\d+\.\d+\.\d+\.\d+"
177 r
"(?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\.)*"
178 r
"[a-z](?:[-a-z0-9]*[a-z0-9])?"
184 r
"(?P<options>\S+(?:,\S+)*)"
189 for line
in locationFile
:
191 if line
.startswith("#") or line
== "\n":
194 match
= lineRe
.match(line
)
196 raise SyntaxError(lineno
)
198 options
= match
.group("options")
200 options
= options
.split(",")
201 if "primary" in options
:
203 raise SyntaxError(lineno
, "multiple primary locations")
208 locations
.append(Location(match
.group("scheme"), match
.group("host"),
209 match
.group("port"), options
))
212 raise SyntaxError(lineno
+ 1, "missing primary location")
217 def initializeProfile(profileDir
):
218 "Sets up the standard testing profile."
220 # Start with a clean slate.
221 shutil
.rmtree(profileDir
, True)
227 user_pref("browser.dom.window.dump.enabled", true);
228 user_pref("dom.allow_scripts_to_close_windows", true);
229 user_pref("dom.disable_open_during_load", false);
230 user_pref("dom.max_script_run_time", 0); // no slow script dialogs
231 user_pref("signed.applets.codebase_principal_support", true);
232 user_pref("security.warn_submit_insecure", false);
233 user_pref("browser.shell.checkDefaultBrowser", false);
234 user_pref("shell.checkDefaultClient", false);
235 user_pref("browser.warnOnQuit", false);
236 user_pref("accessibility.typeaheadfind.autostart", false);
237 user_pref("javascript.options.showInConsole", true);
238 user_pref("layout.debug.enable_data_xbl", true);
239 user_pref("browser.EULA.override", true);
240 user_pref("javascript.options.jit.content", true);
241 user_pref("gfx.color_management.force_srgb", true);
242 user_pref("network.manage-offline-status", false);
243 user_pref("security.default_personal_cert", "Select Automatically"); // Need to client auth test be w/o any dialogs
245 user_pref("camino.warn_when_closing", false); // Camino-only, harmless to others
250 # Increase the max script run time 10-fold for debug builds
253 user_pref("dom.max_script_run_time", 100);
254 user_pref("dom.max_chrome_script_run_time", 200);
257 locations
= readLocations()
259 # Grant God-power to all the privileged servers on which tests run.
260 privileged
= filter(lambda loc
: "privileged" in loc
.options
, locations
)
261 for (i
, l
) in itertools
.izip(itertools
.count(1), privileged
):
263 user_pref("capability.principal.codebase.p%(i)d.granted",
264 "UniversalXPConnect UniversalBrowserRead UniversalBrowserWrite \
265 UniversalPreferencesRead UniversalPreferencesWrite \
267 user_pref("capability.principal.codebase.p%(i)d.id", "%(origin)s");
268 user_pref("capability.principal.codebase.p%(i)d.subjectName", "");
270 "origin": (l
.scheme
+ "://" + l
.host
+ ":" + l
.port
) }
273 # We need to proxy every server but the primary one.
274 origins
= ["'%s://%s:%s'" % (l
.scheme
, l
.host
, l
.port
)
275 for l
in filter(lambda l
: "primary" not in l
.options
, locations
)]
276 origins
= ", ".join(origins
)
278 pacURL
= """data:text/plain,
279 function FindProxyForURL(url, host)
281 var origins = [%(origins)s];
282 var regex = new RegExp('^([a-z][-a-z0-9+.]*)' +
286 '(?::(\\\\\\\\d+))?/');
287 var matches = regex.exec(url);
290 var isHttp = matches[1] == 'http';
291 var isHttps = matches[1] == 'https';
294 if (isHttp) matches[3] = '80';
295 if (isHttps) matches[3] = '443';
298 var origin = matches[1] + '://' + matches[2] + ':' + matches[3];
299 if (origins.indexOf(origin) < 0)
302 return 'PROXY 127.0.0.1:8888';
304 return 'PROXY 127.0.0.1:4443';
306 }""" % { "origins": origins
}
307 pacURL
= "".join(pacURL
.splitlines())
310 user_pref("network.proxy.type", 2);
311 user_pref("network.proxy.autoconfig_url", "%(pacURL)s");
313 user_pref("camino.use_system_proxy_settings", false); // Camino-only, harmless to others
314 """ % {"pacURL": pacURL
}
317 # write the preferences
318 prefsFile
= open(profileDir
+ "/" + "user.js", "a")
319 prefsFile
.write("".join(prefs
))
322 def fillCertificateDB(profileDir
, certPath
, utilityPath
, xrePath
):
323 pwfilePath
= os
.path
.join(profileDir
, ".crtdbpw")
325 pwfile
= open(pwfilePath
, "w")
329 # Create head of the ssltunnel configuration file
330 sslTunnelConfigPath
= os
.path
.join(profileDir
, "ssltunnel.cfg")
331 sslTunnelConfig
= open(sslTunnelConfigPath
, "w")
333 sslTunnelConfig
.write("httpproxy:1\n")
334 sslTunnelConfig
.write("certdbdir:%s\n" % certPath
)
335 sslTunnelConfig
.write("forward:127.0.0.1:8888\n")
336 sslTunnelConfig
.write("listen:*:4443:pgo server certificate\n")
338 # Configure automatic certificate and bind custom certificates, client authentication
339 locations
= readLocations()
341 for loc
in locations
:
342 if loc
.scheme
== "https" and "nocert" not in loc
.options
:
343 customCertRE
= re
.compile("^cert=(?P<nickname>[0-9a-zA-Z_ ]+)")
344 clientAuthRE
= re
.compile("^clientauth=(?P<clientauth>[a-z]+)")
345 for option
in loc
.options
:
346 match
= customCertRE
.match(option
)
348 customcert
= match
.group("nickname");
349 sslTunnelConfig
.write("listen:%s:%s:4443:%s\n" %
350 (loc
.host
, loc
.port
, customcert
))
352 match
= clientAuthRE
.match(option
)
354 clientauth
= match
.group("clientauth");
355 sslTunnelConfig
.write("clientauth:%s:%s:4443:%s\n" %
356 (loc
.host
, loc
.port
, clientauth
))
358 sslTunnelConfig
.close()
360 # Pre-create the certification database for the profile
361 env
= environment(xrePath
= xrePath
)
362 certutil
= os
.path
.join(utilityPath
, "certutil" + BIN_SUFFIX
)
363 pk12util
= os
.path
.join(utilityPath
, "pk12util" + BIN_SUFFIX
)
365 status
= Process([certutil
, "-N", "-d", profileDir
, "-f", pwfilePath
], env
= env
).wait()
369 # Walk the cert directory and add custom CAs and client certs
370 files
= os
.listdir(certPath
)
372 root
, ext
= os
.path
.splitext(item
)
375 if root
.endswith("-object"):
377 Process([certutil
, "-A", "-i", os
.path
.join(certPath
, item
),
378 "-d", profileDir
, "-f", pwfilePath
, "-n", root
, "-t", trustBits
],
381 Process([pk12util
, "-i", os
.path
.join(certPath
, item
), "-w",
382 pwfilePath
, "-d", profileDir
],
385 os
.unlink(pwfilePath
)
388 def environment(env
= None, xrePath
= DIST_BIN
):
390 env
= dict(os
.environ
)
392 ldLibraryPath
= os
.path
.abspath(os
.path
.join(SCRIPT_DIR
, xrePath
))
393 if UNIXISH
or IS_MAC
:
394 envVar
= "LD_LIBRARY_PATH"
396 envVar
= "DYLD_LIBRARY_PATH"
398 ldLibraryPath
= ldLibraryPath
+ ":" + env
[envVar
]
399 env
[envVar
] = ldLibraryPath
401 env
["PATH"] = env
["PATH"] + ";" + ldLibraryPath
409 def runApp(testURL
, env
, app
, profileDir
, extraArgs
, runSSLTunnel
= False, utilityPath
= DIST_BIN
, xrePath
= DIST_BIN
, certPath
= CERTS_SRC_DIR
):
410 "Run the app, returning a tuple containing the status code and the time at which it was started."
411 if IS_TEST_BUILD
and runSSLTunnel
:
412 # create certificate database for the profile
413 certificateStatus
= fillCertificateDB(profileDir
, certPath
, utilityPath
, xrePath
)
414 if certificateStatus
!= 0:
415 log
.info("TEST-UNEXPECTED FAIL | Certificate integration failed")
416 return certificateStatus
418 # start ssltunnel to provide https:// URLs capability
419 ssltunnel
= os
.path
.join(utilityPath
, "ssltunnel" + BIN_SUFFIX
)
420 ssltunnelProcess
= Process([ssltunnel
, os
.path
.join(profileDir
, "ssltunnel.cfg")], env
= environment(xrePath
= xrePath
))
421 log
.info("SSL tunnel pid: %d", ssltunnelProcess
.pid
)
423 "Run the app, returning the time at which it was started."
425 start
= datetime
.now()
427 # now run with the profile we created
429 if IS_MAC
and not IS_CAMINO
and not cmd
.endswith("-bin"):
431 cmd
= os
.path
.abspath(cmd
)
435 args
.append("-foreground")
438 profileDirectory
= commands
.getoutput("cygpath -w \"" + profileDir
+ "/\"")
440 profileDirectory
= profileDir
+ "/"
442 args
.extend(("-no-remote", "-profile", profileDirectory
))
443 if testURL
is not None:
445 args
.extend(("-url", testURL
))
447 args
.append((testURL
))
448 args
.extend(extraArgs
)
449 proc
= Process([cmd
] + args
, env
= environment(env
), stdout
= subprocess
.PIPE
, stderr
= subprocess
.STDOUT
)
450 log
.info("Application pid: %d", proc
.pid
)
451 line
= proc
.stdout
.readline()
453 log
.info(line
.rstrip())
454 line
= proc
.stdout
.readline()
457 log
.info("TEST-UNEXPECTED-FAIL | Exited with code %d during test run", status
)
459 if IS_TEST_BUILD
and runSSLTunnel
:
460 ssltunnelProcess
.kill()
462 return (status
, start
)