Bug 418772. PGO scripts and input. r=ted.mielczarek.
authorsayrer@gmail.com
Thu, 21 Feb 2008 13:08:39 -0800
changeset 12041 b8d2c2b276715734f606d429364bf6dfc418e574
parent 12040 242c2e4364500c221b81a85f7cb797e87d4471db
child 12042 09651f3991f3b52759311b0da62f0e68d1604061
push idunknown
push userunknown
push dateunknown
reviewersted.mielczarek
bugs418772
milestone1.9b4pre
Bug 418772. PGO scripts and input. r=ted.mielczarek.
build/Makefile.in
build/pgo/Makefile.in
build/pgo/automation.py.in
build/pgo/index.html
build/pgo/profileserver.py.in
build/pgo/quit.js
testing/mochitest/Makefile.in
testing/mochitest/runtests.py.in
--- a/build/Makefile.in
+++ b/build/Makefile.in
@@ -49,16 +49,18 @@ PACKAGE_FILE = build.pkg
 ifeq (,$(filter WINCE WINNT OS2,$(OS_ARCH)))
 DIRS		= unix
 endif
 
 ifeq (WINNT,$(OS_ARCH))
 DIRS = win32
 endif
 
+DIRS += pgo
+
 include $(topsrcdir)/config/rules.mk
 
 ifdef ENABLE_TESTS
 # Install bloaturls.txt file for tinderbox bloat test.
 libs:: bloaturls.txt
 	$(INSTALL) $< $(DIST)/bin
 
 # Install bloatcycle.html into dist/bin/res, for auto-cycling
new file mode 100644
--- /dev/null
+++ b/build/pgo/Makefile.in
@@ -0,0 +1,101 @@
+#
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is Mozilla Communicator client code, released
+# March 31, 1998.
+#
+# The Initial Developer of the Original Code is
+# Mozilla Corporation.
+# Portions created by the Initial Developer are Copyright (C) 2008
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+relativesrcdir = build/pgo
+
+include $(DEPTH)/config/autoconf.mk
+include $(topsrcdir)/config/rules.mk
+
+# Stuff to make a build with a profile
+_PROFILE_DIR = $(DEPTH)/_profile/pgo
+
+_PGO_FILES = 	\
+		automation.py \
+		profileserver.py \
+		index.html \
+		quit.js \
+		$(NULL)
+
+
+ifeq ($(USE_SHORT_LIBNAME), 1)
+PROGRAM = $(MOZ_APP_NAME)$(BIN_SUFFIX)
+else
+PROGRAM = $(MOZ_APP_NAME)-bin$(BIN_SUFFIX)
+endif
+
+ifeq ($(OS_ARCH),Darwin)
+ifdef MOZ_DEBUG
+browser_path = \"$(DIST)/$(MOZ_APP_DISPLAYNAME)Debug.app/Contents/MacOS/$(PROGRAM)\"
+else
+browser_path = \"$(DIST)/$(MOZ_APP_DISPLAYNAME).app/Contents/MacOS/$(PROGRAM)\"
+endif
+else
+browser_path = \"$(DIST)/bin/$(PROGRAM)\"
+endif
+
+AUTOMATION_PPARGS = 	\
+			-DBROWSER_PATH=$(browser_path) \
+			-DXPC_BIN_PATH=\"$(DIST)/bin\" \
+			$(NULL)
+
+ifeq ($(OS_ARCH),Darwin)
+AUTOMATION_PPARGS += -DIS_MAC=1
+else
+AUTOMATION_PPARGS += -DIS_MAC=0
+endif
+
+ifeq ($(host_os), cygwin)
+AUTOMATION_PPARGS += -DIS_CYGWIN=1
+endif
+
+automation.py: automation.py.in
+	$(PYTHON) $(topsrcdir)/config/Preprocessor.py \
+	$(AUTOMATION_PPARGS) $(DEFINES) $(ACDEFINES) $^ > $@
+
+profileserver.py: profileserver.py.in
+	$(PYTHON) $(topsrcdir)/config/Preprocessor.py $^ > $@
+	chmod +x $@
+
+GARBAGE += automation.py profileserver.py
+
+libs:: $(_PGO_FILES)
+	$(INSTALL) $^ $(_PROFILE_DIR)
new file mode 100644
--- /dev/null
+++ b/build/pgo/automation.py.in
@@ -0,0 +1,275 @@
+#
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2008
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Robert Sayre <sayrer@gmail.com>
+#   Jeff Walden <jwalden+bmo@mit.edu>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+from datetime import datetime
+import itertools
+import shutil
+import os
+import sys
+
+"""
+Runs the browser from a script, and provides useful utilities
+for setting up the browser environment.
+"""
+
+# Since some tests require cross-domain support in Mochitest, across ports,
+# domains, subdomains, etc. we use a proxy autoconfig hack to map a bunch of
+# servers onto localhost:8888.  We have to grant them the same privileges as
+# localhost:8888 here, since the browser only knows them as the URLs they're
+# pretending to be.  We also have two servers which are set up but don't have
+# privileges, for testing privilege functionality.
+#
+# These lists must be kept in sync with the following list:
+#
+# http://developer.mozilla.org/en/docs/Mochitest#How_do_I_test_issues_which_only_show_up_when_tests_are_run_across_domains.3F
+#
+servers = [
+           "localhost:8888", # MUST be first -- see PAC pref-setting code
+           "example.org:80",
+           "test1.example.org:80",
+           "test2.example.org:80",
+           "sub1.test1.example.org:80",
+           "sub1.test2.example.org:80",
+           "sub2.test1.example.org:80",
+           "sub2.test2.example.org:80",
+           "example.org:8000",
+           "test1.example.org:8000",
+           "test2.example.org:8000",
+           "sub1.test1.example.org:8000",
+           "sub1.test2.example.org:8000",
+           "sub2.test1.example.org:8000",
+           "sub2.test2.example.org:8000",
+           "example.com:80",
+           "test1.example.com:80",
+           "test2.example.com:80",
+           "sub1.test1.example.com:80",
+           "sub1.test2.example.com:80",
+           "sub2.test1.example.com:80",
+           "sub2.test2.example.com:80",
+           "sectest1.example.org:80",
+           "sub.sectest2.example.org:80",
+           "sub1.xn--lt-uia.example.org:8000", # U+00E4 U+006C U+0074
+           "sub2.xn--lt-uia.example.org:80",   # U+00E4 U+006C U+0074
+           "xn--exmple-cua.test:80",
+           "sub1.xn--exmple-cua.test:80",
+           "xn--hxajbheg2az3al.xn--jxalpdlp:80", # Greek IDN for example.test
+           "sub1.xn--hxajbheg2az3al.xn--jxalpdlp:80",
+          ]
+
+unprivilegedServers = [
+                       "sectest2.example.org:80",
+                       "sub.sectest1.example.org:80",
+                      ]
+
+
+# These are generated in mozilla/build/Makefile.in
+#expand DIST_BIN = "./" + __XPC_BIN_PATH__
+#expand IS_WIN32 = len("__WIN32__") != 0
+#expand IS_MAC = __IS_MAC__ != 0
+#ifdef IS_CYGWIN
+#expand IS_CYGWIN = __IS_CYGWIN__ == 1
+#else
+IS_CYGWIN = False
+#endif
+
+UNIXISH = not IS_WIN32 and not IS_MAC
+
+#expand DEFAULT_APP = "./" + __BROWSER_PATH__
+
+#################
+# SUBPROCESSING #
+#################
+
+class Process:
+  """
+  Represents a subprocess of this process.  We don't just directly use the
+  subprocess module here because we want compatibility with Python 2.3 on
+  non-Windows platforms.  :-(
+  """
+
+  def __init__(self, command, args, env):
+    """
+    Executes the given command, which must be an absolute path, with the given
+    arguments in the given environment.
+    """
+    command = os.path.abspath(command)
+    if IS_WIN32:
+      import subprocess
+      cmd = [command]
+      cmd.extend(args)
+      self._process = subprocess.Popen(cmd, env = env)
+    else:
+      import popen2
+      cmd = []
+      for (k, v) in env.iteritems():
+        cmd.append(k + "='" + v + "' ")
+      cmd.append("'" + command + "'")
+      cmd.extend(map(lambda x: "'" + x + "'", args))
+      cmd = " ".join(cmd)
+      self._process = popen2.Popen4(cmd)
+    self.pid = self._process.pid
+
+  def wait(self):
+    "Waits for this process to finish, then returns the process's status."
+    if IS_WIN32:
+      return self._process.wait()
+    # popen2 is a bit harder to work with because we have to manually redirect
+    # output to stdout
+    p = self._process
+    stdout = sys.stdout
+    out = p.fromchild
+    while p.poll() == -1:
+      line = out.readline().rstrip()
+      if len(line) > 0:
+        print >> stdout, line
+    # read in the last lines that happened between the last -1 poll and the
+    # process finishing
+    for line in out:
+      line = line.rstrip()
+      if len(line) > 0:
+        print >> stdout, line
+    return p.poll()
+
+
+#######################
+# PROFILE SETUP       #
+#######################
+
+def initializeProfile(profileDir):
+  "Sets up the standard testing profile."
+
+  # Start with a clean slate.
+  shutil.rmtree(profileDir, True)
+  os.mkdir(profileDir)
+
+  prefs = []
+
+  part = """\
+user_pref("browser.dom.window.dump.enabled", true);
+user_pref("dom.disable_open_during_load", false);
+user_pref("dom.max_script_run_time", 0); // no slow script dialogs
+user_pref("signed.applets.codebase_principal_support", true);
+user_pref("security.warn_submit_insecure", false);
+user_pref("browser.shell.checkDefaultBrowser", false);
+user_pref("browser.warnOnQuit", false);
+"""
+  prefs.append(part)
+
+  # Grant God-power to all the servers on which tests can run.
+  for (i, server) in itertools.izip(itertools.count(1), servers):
+    part = """
+user_pref("capability.principal.codebase.p%(i)d.granted",
+          "UniversalXPConnect UniversalBrowserRead UniversalBrowserWrite \
+           UniversalPreferencesRead UniversalPreferencesWrite \
+           UniversalFileRead");
+user_pref("capability.principal.codebase.p%(i)d.id", "http://%(server)s");
+user_pref("capability.principal.codebase.p%(i)d.subjectName", "");
+"""  % {"i": i, "server": server}
+    prefs.append(part)
+
+  # Now add the two servers that do NOT have God-power so we can properly test
+  # the granting and receiving of God-power.  Strip off the first server because
+  # we proxy all the others to it.
+  allServers = servers[1:] + unprivilegedServers
+
+
+  # Now actually create the preference to make the proxying happen.
+  quotedServers = ", ".join(map(lambda x: "'" + x + "'", allServers))
+
+  pacURL = """data:text/plain,
+function FindProxyForURL(url, host)
+{
+  var servers = [%(quotedServers)s];
+  var regex = new RegExp('http://(.*?(:\\\\\\\\d+)?)/');
+  var matches = regex.exec(url);
+  if (!matches)
+    return 'DIRECT';
+  var hostport = matches[1], port = matches[2];
+  if (!port)
+    hostport += ':80';
+  if (servers.indexOf(hostport) >= 0)
+    return 'PROXY localhost:8888';
+  return 'DIRECT';
+}""" % {"quotedServers": quotedServers}
+  pacURL = "".join(pacURL.splitlines())
+
+  part = """
+user_pref("network.proxy.type", 2);
+user_pref("network.proxy.autoconfig_url", "%(pacURL)s");
+""" % {"pacURL": pacURL}
+  prefs.append(part)
+
+  # write the preferences
+  prefsFile = open(profileDir + "/" + "user.js", "a")
+  prefsFile.write("".join(prefs))
+  prefsFile.close()
+
+
+#######################
+# RUN THE APP         #
+#######################
+
+def runApp(testURL, env, app, profileDir):
+  "Run the app, returning the time at which it was started."
+  # mark the start
+  start = datetime.now()
+
+  # now run with the profile we created
+  cmd = app
+  if IS_MAC and not cmd.endswith("-bin"):
+    cmd += "-bin"
+  cmd = os.path.abspath(cmd)
+
+  args = []
+  if IS_MAC:
+    args.append("-foreground")
+
+  if IS_CYGWIN:
+    profileDirectory = commands.getoutput("cygpath -w \"" + profileDir + "/\"")
+  else:
+    profileDirectory = profileDir + "/"
+
+  args.extend(("-no-remote", "-profile", profileDirectory, testURL))
+  proc = Process(cmd, args, env = env)
+  print "Application pid: " + str(proc.pid)
+  status = proc.wait()
+  if status != 0:
+    print "FAIL Exited with code " + str(status) + " during test run"
+
+  return start
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/build/pgo/index.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<title>PGO</title>
+<script src="quit.js"></script>
+</head>
+<body>
+
+Just going to quit after a timeout for now...
+
+<script>
+setTimeout(goQuitApplication, 2000);
+</script>
+</body>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/build/pgo/profileserver.py.in
@@ -0,0 +1,77 @@
+#literal #!/usr/bin/python
+#
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 1998
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Robert Sayre <sayrer@gmail.com>
+#   Jeff Walden <jwalden+bmo@mit.edu>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+import SimpleHTTPServer
+import SocketServer
+import socket
+import threading
+import os
+import sys
+import shutil
+from datetime import datetime
+import automation
+
+PORT = 8888
+SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0])))
+PROFILE_DIRECTORY = os.path.abspath(os.path.join(SCRIPT_DIR, "./pgoprofile"))
+os.chdir(SCRIPT_DIR)
+
+class EasyServer(SocketServer.TCPServer):
+  allow_reuse_address = True
+
+if __name__ == '__main__':
+  httpd = EasyServer(("", PORT), SimpleHTTPServer.SimpleHTTPRequestHandler)
+  t = threading.Thread(target=httpd.serve_forever)
+  t.setDaemon(True) # don't hang on exit
+  t.start()
+
+  automation.initializeProfile(PROFILE_DIRECTORY)
+  browserEnv = dict(os.environ)
+ 
+  # These variables are necessary for correct application startup; change
+  # via the commandline at your own risk.
+  browserEnv["NO_EM_RESTART"] = "1"
+  browserEnv["XPCOM_DEBUG_BREAK"] = "warn"
+  if automation.UNIXISH:
+    browserEnv["LD_LIBRARY_PATH"] = os.path.join(SCRIPT_DIR, automation.DIST_BIN)
+    browserEnv["MOZILLA_FIVE_HOME"] = os.path.join(SCRIPT_DIR, automation.DIST_BIN)
+
+  automation.runApp("http://localhost:%d/index.html" % PORT, browserEnv,
+                    os.path.join(SCRIPT_DIR, automation.DEFAULT_APP), PROFILE_DIRECTORY)
new file mode 100644
--- /dev/null
+++ b/build/pgo/quit.js
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla Automated Testing Code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2005
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Bob Clary <bob@bclary.com>
+ *   Jeff Walden <jwalden+code@mit.edu>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+  From mozilla/toolkit/content
+  These files did not have a license
+*/
+
+function quitHook()
+{
+  var xhr = new XMLHttpRequest();
+  xhr.open("GET", "http://" + location.host + "/server/shutdown", true);
+  xhr.onreadystatechange = function (event)
+    {
+      if (xhr.readyState == 4)
+        goQuitApplication();
+    };
+  xhr.send(null);
+}
+
+function canQuitApplication()
+{
+  var os = Components.classes["@mozilla.org/observer-service;1"]
+    .getService(Components.interfaces.nsIObserverService);
+  if (!os) 
+  {
+    return true;
+  }
+  
+  try 
+  {
+    var cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"]
+      .createInstance(Components.interfaces.nsISupportsPRBool);
+    os.notifyObservers(cancelQuit, "quit-application-requested", null);
+    
+    // Something aborted the quit process. 
+    if (cancelQuit.data)
+    {
+      return false;
+    }
+  }
+  catch (ex) 
+  {
+  }
+  return true;
+}
+
+function goQuitApplication()
+{
+  const privs = 'UniversalXPConnect';
+
+  try
+  {
+    netscape.security.PrivilegeManager.enablePrivilege(privs);
+  }
+  catch(ex)
+  {
+    throw('goQuitApplication: privilege failure ' + ex);
+  }
+
+  if (!canQuitApplication())
+  {
+    return false;
+  }
+  
+  const kAppStartup = '@mozilla.org/toolkit/app-startup;1';
+  const kAppShell   = '@mozilla.org/appshell/appShellService;1';
+  var   appService;
+  var   forceQuit;
+
+  if (kAppStartup in Components.classes)
+  {
+    appService = Components.classes[kAppStartup].
+      getService(Components.interfaces.nsIAppStartup);
+    forceQuit  = Components.interfaces.nsIAppStartup.eForceQuit;
+
+  }
+  else if (kAppShell in Components.classes)
+  {
+    appService = Components.classes[kAppShell].
+      getService(Components.interfaces.nsIAppShellService);
+    forceQuit = Components.interfaces.nsIAppShellService.eForceQuit;
+  }
+  else
+  {
+    throw 'goQuitApplication: no AppStartup/appShell';
+  }
+
+  try
+  {
+    appService.quit(forceQuit);
+  }
+  catch(ex)
+  {
+    throw('goQuitApplication: ' + ex);
+  }
+
+  return true;
+}
+
--- a/testing/mochitest/Makefile.in
+++ b/testing/mochitest/Makefile.in
@@ -50,16 +50,17 @@ DIRS =	MochiKit \
 	$(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
 # files that get copied into $objdir/_tests/
 _SERV_FILES = 	\
 		runtests.pl \
 		runtests.py \
+		automation.py \
 		gen_template.pl \
 		server.js \
 		harness-a11y.xul \
 		harness-overlay.xul \
 		harness.xul \
 		browser-test-overlay.xul \
 		browser-test.js \
 		browser-harness.xul \
@@ -100,19 +101,23 @@ else
 TEST_DRIVER_PPARGS += -DIS_MAC=0
 endif
 
 ifeq ($(host_os), cygwin)
 TEST_DRIVER_PPARGS += -DIS_CYGWIN=1
 endif
 
 runtests.pl: runtests.pl.in
-	$(PYTHON) $(MOZILLA_DIR)/config/Preprocessor.py \
+	$(PYTHON) $(topsrcdir)/config/Preprocessor.py \
 	$(TEST_DRIVER_PPARGS) $(DEFINES) $(ACDEFINES) $^ > $@
 
 runtests.py: runtests.py.in
-	$(PYTHON) $(MOZILLA_DIR)/config/Preprocessor.py \
+	$(PYTHON) $(topsrcdir)/config/Preprocessor.py \
 	$(TEST_DRIVER_PPARGS) $(DEFINES) $(ACDEFINES) $^ > $@
 
-GARBAGE += runtests.pl runtests.py
+automation.py: $(topsrcdir)/build/pgo/automation.py.in
+	$(PYTHON) $(topsrcdir)/config/Preprocessor.py \
+	$(TEST_DRIVER_PPARGS) $(DEFINES) $(ACDEFINES) $^ > $@
+
+GARBAGE += runtests.pl runtests.py automation.py
 
 libs:: $(_SERV_FILES)
 	$(INSTALL) $^ $(_DEST_DIR)
--- a/testing/mochitest/runtests.py.in
+++ b/testing/mochitest/runtests.py.in
@@ -37,111 +37,44 @@
 #
 # ***** END LICENSE BLOCK *****
 
 """
 Runs the Mochitest test harness.
 """
 
 from datetime import datetime
-import itertools
 import optparse
 import os
 import os.path
-import shutil
 import signal
 import sys
 import time
 from urllib import quote_plus as encodeURIComponent
 import urllib2
 import commands
-
+import automation
 
 # Path to the test script on the server
 TEST_SERVER_HOST = "localhost:8888"
 TEST_PATH = "/tests/"
 CHROME_PATH = "/redirect.html";
 A11Y_PATH = "/redirect-a11y.html"
 TESTS_URL = "http://" + TEST_SERVER_HOST + TEST_PATH
 CHROMETESTS_URL = "http://" + TEST_SERVER_HOST + CHROME_PATH
 A11YTESTS_URL = "http://" + TEST_SERVER_HOST + A11Y_PATH
 SERVER_SHUTDOWN_URL = "http://" + TEST_SERVER_HOST + "/server/shutdown"
 
 # Max time in seconds to wait for server startup before tests will fail -- if
 # this seems big, it's mostly for debug machines where cold startup
 # (particularly after a build) takes forever.
 SERVER_STARTUP_TIMEOUT = 45
 
-# Since some tests require cross-domain support in Mochitest, across ports,
-# domains, subdomains, etc. we use a proxy autoconfig hack to map a bunch of
-# servers onto localhost:8888.  We have to grant them the same privileges as
-# localhost:8888 here, since the browser only knows them as the URLs they're
-# pretending to be.  We also have two servers which are set up but don't have
-# privileges, for testing privilege functionality.
-#
-# These lists must be kept in sync with the following list:
-#
-# http://developer.mozilla.org/en/docs/Mochitest#How_do_I_test_issues_which_only_show_up_when_tests_are_run_across_domains.3F
-#
-servers = [
-           "localhost:8888", # MUST be first -- see PAC pref-setting code
-           "example.org:80",
-           "test1.example.org:80",
-           "test2.example.org:80",
-           "sub1.test1.example.org:80",
-           "sub1.test2.example.org:80",
-           "sub2.test1.example.org:80",
-           "sub2.test2.example.org:80",
-           "example.org:8000",
-           "test1.example.org:8000",
-           "test2.example.org:8000",
-           "sub1.test1.example.org:8000",
-           "sub1.test2.example.org:8000",
-           "sub2.test1.example.org:8000",
-           "sub2.test2.example.org:8000",
-           "example.com:80",
-           "test1.example.com:80",
-           "test2.example.com:80",
-           "sub1.test1.example.com:80",
-           "sub1.test2.example.com:80",
-           "sub2.test1.example.com:80",
-           "sub2.test2.example.com:80",
-           "sectest1.example.org:80",
-           "sub.sectest2.example.org:80",
-           "sub1.xn--lt-uia.example.org:8000", # U+00E4 U+006C U+0074
-           "sub2.xn--lt-uia.example.org:80",   # U+00E4 U+006C U+0074
-           "xn--exmple-cua.test:80",
-           "sub1.xn--exmple-cua.test:80",
-           "xn--hxajbheg2az3al.xn--jxalpdlp:80", # Greek IDN for example.test
-           "sub1.xn--hxajbheg2az3al.xn--jxalpdlp:80",
-          ]
-
-unprivilegedServers = [
-                       "sectest2.example.org:80",
-                       "sub.sectest1.example.org:80",
-                      ]
-
 PROFILE_DIRECTORY = os.path.abspath("./mochitesttestingprofile")
 
- # These are generated in mozilla/testing/mochitest/Makefile.in
-#expand DIST_BIN = "./" + __XPC_BIN_PATH__
-#expand IS_WIN32 = len("__WIN32__") != 0
-#expand IS_MAC = __IS_MAC__ != 0
-#ifdef IS_CYGWIN
-#expand IS_CYGWIN = __IS_CYGWIN__ == 1
-#else
-IS_CYGWIN = False
-#endif
-
-UNIXISH = not IS_WIN32 and not IS_MAC
-
-
-#expand DEFAULT_APP = "./" + __BROWSER_PATH__
-
-
 #######################
 # COMMANDLINE OPTIONS #
 #######################
 
 class MochitestOptions(optparse.OptionParser):
   """Parses Mochitest commandline options."""
   def __init__(self, **kwargs):
     optparse.OptionParser.__init__(self, **kwargs)
@@ -150,17 +83,17 @@ class MochitestOptions(optparse.OptionPa
     self.add_option("--close-when-done",
                     action = "store_true", dest = "closeWhenDone",
                     help = "close the application when tests are done running")
     defaults["closeWhenDone"] = False
 
     self.add_option("--appname",
                     action = "store", type = "string", dest = "app",
                     help = "absolute path to application, overriding default")
-    defaults["app"] = DEFAULT_APP
+    defaults["app"] = automation.DEFAULT_APP
 
     self.add_option("--log-file",
                     action = "store", type = "string", dest = "logFile",
                     help = "file to which logging occurs")
     defaults["logFile"] = ""
 
     self.add_option("--autorun",
                     action = "store_true", dest = "autorun",
@@ -237,26 +170,27 @@ class MochitestServer:
 
   def __init__(self, options):
     self._closeWhenDone = options.closeWhenDone
 
   def start(self):
     "Run the Mochitest server, returning the process ID of the server."
     
     env = dict(os.environ)
-    if UNIXISH:
-      env["LD_LIBRARY_PATH"] = DIST_BIN
-      env["MOZILLA_FIVE_HOME"] = DIST_BIN
+    if automation.UNIXISH:
+      env["LD_LIBRARY_PATH"] = automation.DIST_BIN
+      env["MOZILLA_FIVE_HOME"] = automation.DIST_BIN
       env["XPCOM_DEBUG_BREAK"] = "warn"
 
     args = ["-v", "170",
             "-f", "./" + "httpd.js",
             "-f", "./" + "server.js"]
 
-    self._process = Process(DIST_BIN + "/" + "xpcshell", args, env = env)
+    xpcshell = automation.DIST_BIN + "/" + "xpcshell";
+    self._process = automation.Process(xpcshell, args, env = env)
     pid = self._process.pid
     if pid < 0:
       print "Error starting server."
       sys.exit(2)
     print "Server pid: " + str(pid)
     
 
   def ensureReady(self, timeout):
@@ -277,78 +211,22 @@ class MochitestServer:
   def stop(self):
     pid = self._process.pid
     try:
       c = urllib2.urlopen(SERVER_SHUTDOWN_URL)
       c.read()
       c.close()
       os.waitpid(pid, 0)
     except:
-      if IS_WIN32:
+      if automation.IS_WIN32:
         pass # XXX do something here!
       else:
         os.kill(pid, signal.SIGKILL)
 
 
-
-#################
-# SUBPROCESSING #
-#################
-
-class Process:
-  """
-  Represents a subprocess of this process.  We don't just directly use the
-  subprocess module here because we want compatibility with Python 2.3 on
-  non-Windows platforms.  :-(
-  """
-
-  def __init__(self, command, args, env):
-    """
-    Executes the given command, which must be an absolute path, with the given
-    arguments in the given environment.
-    """
-    command = os.path.abspath(command)
-    if IS_WIN32:
-      import subprocess
-      cmd = [command]
-      cmd.extend(args)
-      self._process = subprocess.Popen(cmd, env = env)
-    else:
-      import popen2
-      cmd = []
-      for (k, v) in env.iteritems():
-        cmd.append(k + "='" + v + "' ")
-      cmd.append("'" + command + "'")
-      cmd.extend(map(lambda x: "'" + x + "'", args))
-      cmd = " ".join(cmd)
-      self._process = popen2.Popen4(cmd)
-    self.pid = self._process.pid
-
-  def wait(self):
-    "Waits for this process to finish, then returns the process's status."
-    if IS_WIN32:
-      return self._process.wait()
-    # popen2 is a bit harder to work with because we have to manually redirect
-    # output to stdout
-    p = self._process
-    stdout = sys.stdout
-    out = p.fromchild
-    while p.poll() == -1:
-      line = out.readline().rstrip()
-      if len(line) > 0:
-        print >> stdout, line
-    # read in the last lines that happened between the last -1 poll and the
-    # process finishing
-    for line in out:
-      line = line.rstrip()
-      if len(line) > 0:
-        print >> stdout, line
-    return p.poll()
-
-
 #################
 # MAIN FUNCTION #
 #################
 
 def main():
   parser = MochitestOptions()
   options, args = parser.parse_args()
   
@@ -361,28 +239,29 @@ Are you executing $objdir/_tests/testing
 
   # browser environment
   browserEnv = dict(os.environ)
 
   # These variables are necessary for correct application startup; change
   # via the commandline at your own risk.
   browserEnv["NO_EM_RESTART"] = "1"
   browserEnv["XPCOM_DEBUG_BREAK"] = "warn"
-  if UNIXISH:
-    browserEnv["LD_LIBRARY_PATH"] = DIST_BIN 
-    browserEnv["MOZILLA_FIVE_HOME"] = DIST_BIN 
+  if automation.UNIXISH:
+    browserEnv["LD_LIBRARY_PATH"] = automation.DIST_BIN 
+    browserEnv["MOZILLA_FIVE_HOME"] = automation.DIST_BIN 
 
   for v in options.environment:
     ix = v.find("=")
     if ix <= 0:
       print "Error: syntax error in --setenv=" + v
       sys.exit(1)
     browserEnv[v[:ix]] = v[ix + 1:]
 
-  manifest = initializeProfile(options)
+  automation.initializeProfile(PROFILE_DIRECTORY)
+  manifest = addChromeToProfile(options)
   server = MochitestServer(options)
   server.start()
 
   # If we're lucky, the server has fully started by now, and all paths are
   # ready, etc.  However, xpcshell cold start times suck, at least for debug
   # builds.  We'll try to connect to the server for awhile, and if we fail,
   # we'll try to kill the server and exit with an error.
   server.ensureReady(SERVER_STARTUP_TIMEOUT)
@@ -420,17 +299,17 @@ Are you executing $objdir/_tests/testing
     if options.fileLevel:
       urlOpts.append("fileLevel=" + encodeURIComponent(options.fileLevel))
     if options.consoleLevel:
       urlOpts.append("consoleLevel=" + encodeURIComponent(options.consoleLevel))
     if len(urlOpts) > 0:
       testURL += "?" + "&".join(urlOpts)
 
 
-  start = runTests(testURL, browserEnv, options)
+  start = automation.runApp(testURL, browserEnv, options.app, PROFILE_DIRECTORY)
 
   server.stop()
 
   # print test run times
   finish = datetime.now()
   print " started: " + str(start)
   print "finished: " + str(finish)
 
@@ -463,88 +342,18 @@ def makeTestConfig(options):
          "logPath": logFile,
          "testPath": testPath}
 
   config = open(PROFILE_DIRECTORY + "/" + "testConfig.js", "w")
   config.write(content)
   config.close() 
 
 
-def initializeProfile(options):
-  "Sets up the standard Mochitest profile."
-
-  # Start with a clean slate.
-  shutil.rmtree(PROFILE_DIRECTORY, True)
-  os.mkdir(PROFILE_DIRECTORY)
-
-
-  prefs = []
-
-  part = """\
-user_pref("browser.dom.window.dump.enabled", true);
-user_pref("dom.disable_open_during_load", false);
-user_pref("dom.max_script_run_time", 0); // no slow script dialogs
-user_pref("signed.applets.codebase_principal_support", true);
-user_pref("security.warn_submit_insecure", false);
-user_pref("browser.shell.checkDefaultBrowser", false);
-user_pref("browser.warnOnQuit", false);
-"""
-  prefs.append(part)
-
-  # Grant God-power to all the servers on which tests can run.
-  for (i, server) in itertools.izip(itertools.count(1), servers):
-    part = """
-user_pref("capability.principal.codebase.p%(i)d.granted",
-          "UniversalXPConnect UniversalBrowserRead UniversalBrowserWrite \
-           UniversalPreferencesRead UniversalPreferencesWrite \
-           UniversalFileRead");
-user_pref("capability.principal.codebase.p%(i)d.id", "http://%(server)s");
-user_pref("capability.principal.codebase.p%(i)d.subjectName", "");
-"""  % {"i": i, "server": server}
-    prefs.append(part)
-
-  # Now add the two servers that do NOT have God-power so we can properly test
-  # the granting and receiving of God-power.  Strip off the first server because
-  # we proxy all the others to it.
-  allServers = servers[1:] + unprivilegedServers
-               
-
-  # Now actually create the preference to make the proxying happen.
-  quotedServers = ", ".join(map(lambda x: "'" + x + "'", allServers))
-
-  pacURL = """data:text/plain,
-function FindProxyForURL(url, host)
-{
-  var servers = [%(quotedServers)s];
-  var regex = new RegExp('http://(.*?(:\\\\\\\\d+)?)/');
-  var matches = regex.exec(url);
-  if (!matches)
-    return 'DIRECT';
-  var hostport = matches[1], port = matches[2];
-  if (!port)
-    hostport += ':80';
-  if (servers.indexOf(hostport) >= 0)
-    return 'PROXY localhost:8888';
-  return 'DIRECT';
-}""" % {"quotedServers": quotedServers}
-  pacURL = "".join(pacURL.splitlines())
-
-  part = """
-user_pref("network.proxy.type", 2);
-user_pref("network.proxy.autoconfig_url", "%(pacURL)s");
-""" % {"pacURL": pacURL}
-  prefs.append(part)
-
-  # write the preferences
-  prefsFile = open(PROFILE_DIRECTORY + "/" + "user.js", "a")
-  prefsFile.write("".join(prefs))
-  prefsFile.close()
-
-
-  # Now onto chrome config changes
+def addChromeToProfile(options):
+  "Adds MochiKit chrome tests to the profile."
 
   chromedir = PROFILE_DIRECTORY + "/" + "chrome"
   os.mkdir(chromedir)
 
   chrome = []
 
   part = """
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); /* set default namespace to XUL */
@@ -563,69 +372,30 @@ toolbar#nav-bar {
   # write userChrome.css
   chromeFile = open(PROFILE_DIRECTORY + "/" + "userChrome.css", "a")
   chromeFile.write("".join(chrome))
   chromeFile.close()
 
 
   # register our chrome dir
   chrometestDir = os.path.abspath(".") + "/"
-  if IS_WIN32:
+  if automation.IS_WIN32:
     chrometestDir = "file:///" + chrometestDir.replace("\\", "/")
 
 
   (path, leaf) = os.path.split(options.app)
   manifest = path + "/" + "chrome/mochikit.manifest"
   manifestFile = open(manifest, "w")
   manifestFile.write("content mochikit " + chrometestDir + "\n")
   if options.browserChrome:
     overlayLine = "overlay chrome://browser/content/browser.xul " \
                   "chrome://mochikit/content/browser-test-overlay.xul\n"
     manifestFile.write(overlayLine)
   manifestFile.close()
 
   return manifest
 
-
-
- ##################
- # TEST EXECUTION #
- ##################
-
-def runTests(testURL, env, options):
-  "Run the tests, returning the time at which the tests were started."
-
-  # mark the start
-  start = datetime.now()
-
-  # now run with the profile we created
-  cmd = options.app
-  if IS_MAC and not cmd.endswith("-bin"):
-    cmd += "-bin"
-  cmd = os.path.abspath(cmd)
-
-  args = []
-  if IS_MAC:
-    args.append("-foreground")
-
-  if IS_CYGWIN:
-    profileDirectory = commands.getoutput("cygpath -w \"" + PROFILE_DIRECTORY + "/\"")
-  else:
-    profileDirectory = PROFILE_DIRECTORY + "/"
-
-  args.extend(("-no-remote", "-profile", profileDirectory, testURL))
-
-  proc = Process(cmd, args, env = env)
-  print "Application pid: " + str(proc.pid)
-  status = proc.wait()
-  if status != 0:
-    print "FAIL Exited with code " + str(status) + " during test run"
-
-  return start
-
-
-
 #########
 # DO IT #
 #########
 
 if __name__ == "__main__":
   main()