[Third time landing] Python implementation of sync server, for testing.
Implement the server side of chromium sync inside of testserver.py. The implementation supports at most one account (and ignores authentication credentials), but is otherwise reasonably full featured.
Make the sync_integration_tests run by default against the test server. An externally-provided --sync-url will give the old behavior.
Protocol buffers stuff: The test sync server requires Python generated code for .proto files. I've put generated code, as well as the python protocol buffers runtime library, in the output directory + "/python" (e.g, on windows, src/chrome/Debug/python/google/protobuf).
Flakiness fix: In the InProcessBrowserTest framework, improve the mechanism for tests that want to manually set up a user data directory. The new way ensures that the user data directory is always wiped; tests can't accidentally forget to do this anymore.
Flakiness fix: Make testserver try to /kill any old instance that might be hogging the port. Very useful if a test failure leaves a server running. Tested this against all combos of protocols, and it seems to work.
Flakiness fix: Port sync_integration_tests to the out-of-process test runner.
Flakiness fix: For IN_PROC_BROWSER_TESTS, don't run the test body if the setup triggered a fatal (ASSERT_) failure.
BUG=20905,40980
Committed: http://src.chromium.org/viewvc/chrome?view=rev&revision=44708
Committed: http://src.chromium.org/viewvc/chrome?view=rev&revision=45916
Review URL: http://codereview.chromium.org/1622012
git-svn-id: http://src.chromium.org/svn/trunk/src/net/tools/testserver@46040 4ff67af0-8c30-449e-8e8b-ad334ec8d88c
diff --git a/testserver.py b/testserver.py
index 94ad3da..8e3df5e 100644
--- a/testserver.py
+++ b/testserver.py
@@ -1,5 +1,5 @@
#!/usr/bin/python2.4
-# Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+# Copyright (c) 2006-2010 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
@@ -22,9 +22,13 @@
import SocketServer
import sys
import time
+import urllib2
+
+import pyftpdlib.ftpserver
import tlslite
import tlslite.api
-import pyftpdlib.ftpserver
+
+import chromiumsync
try:
import hashlib
@@ -125,12 +129,14 @@
self.ContentTypeHandler,
self.ServerRedirectHandler,
self.ClientRedirectHandler,
+ self.ChromiumSyncTimeHandler,
self.MultipartHandler,
self.DefaultResponseHandler]
self._post_handlers = [
self.WriteFile,
self.EchoTitleHandler,
self.EchoAllHandler,
+ self.ChromiumSyncCommandHandler,
self.EchoHandler] + self._get_handlers
self._put_handlers = [
self.WriteFile,
@@ -149,6 +155,8 @@
BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request,
client_address,
socket_server)
+ # Class variable; shared across requests.
+ _sync_handler = chromiumsync.TestServer()
def _ShouldHandleRequest(self, handler_name):
"""Determines if the path can be handled by the handler.
@@ -996,6 +1004,39 @@
return True
+ def ChromiumSyncTimeHandler(self):
+ """Handle Chromium sync .../time requests.
+
+ The syncer sometimes checks server reachability by examining /time.
+ """
+ test_name = "/chromiumsync/time"
+ if not self._ShouldHandleRequest(test_name):
+ return False
+
+ self.send_response(200)
+ self.send_header('Content-type', 'text/html')
+ self.end_headers()
+ return True
+
+ def ChromiumSyncCommandHandler(self):
+ """Handle a chromiumsync command arriving via http.
+
+ This covers all sync protocol commands: authentication, getupdates, and
+ commit.
+ """
+ test_name = "/chromiumsync/command"
+ if not self._ShouldHandleRequest(test_name):
+ return False
+
+ length = int(self.headers.getheader('content-length'))
+ raw_request = self.rfile.read(length)
+
+ http_response, raw_reply = self._sync_handler.HandleCommand(raw_request)
+ self.send_response(http_response)
+ self.end_headers()
+ self.wfile.write(raw_reply)
+ return True
+
def MultipartHandler(self):
"""Send a multipart response (10 text/html pages)."""
test_name = "/multipart"
@@ -1125,13 +1166,24 @@
# Create the default path to our data dir, relative to the exe dir.
my_data_dir = os.path.dirname(sys.argv[0])
my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
- "test", "data")
+ "test", "data")
#TODO(ibrar): Must use Find* funtion defined in google\tools
#i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
return my_data_dir
+def TryKillingOldServer(port):
+ # Note that an HTTP /kill request to the FTP server has the effect of
+ # killing it.
+ for protocol in ["http", "https"]:
+ try:
+ urllib2.urlopen("%s://localhost:%d/kill" % (protocol, port)).read()
+ print "Killed old server instance on port %d (via %s)" % (port, protocol)
+ except urllib2.URLError:
+ # Common case, indicates no server running.
+ pass
+
def main(options, args):
# redirect output to a log file so it doesn't spam the unit test output
logfile = open('testserver.log', 'w')
@@ -1139,6 +1191,9 @@
port = options.port
+ # Try to free up the port if there's an orphaned old instance.
+ TryKillingOldServer(port)
+
if options.server_type == SERVER_HTTP:
if options.cert:
# let's make sure the cert file exists.