blob: 17ed8769a23996fff6536d894e5bf5f7851652cf [file] [log] [blame]
#!/usr/bin/python2.4
#
# Copyright 2009 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.
"""A tool to run a chrome sync integration test, used by the buildbot slaves.
When this is run, the current directory (cwd) should be the outer build
directory (e.g., chrome-release/build/).
For a list of command-line options, call this script with '--help'.
"""
__author__ = '[email protected]'
import logging
import optparse
import os
import re
import subprocess
import sys
import tempfile
import time
import urllib2
USAGE = '%s [options] [test args]' % os.path.basename(sys.argv[0])
HTTP_SERVER_URL = None
HTTP_SERVER_PORT = None
class PathNotFound(Exception): pass
def ListSyncTests(test_exe_path):
"""Returns list of the enabled live sync tests.
Args:
test_exe_path: Absolute path to test exe file.
Returns:
List of the tests that should be run.
"""
command = [test_exe_path]
command.append('--gtest_list_tests')
proc = subprocess.Popen(
command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1)
proc.wait()
sys.stdout.flush()
test_list = []
for line in proc.stdout.readlines():
if not line.strip():
continue
elif line.count("."): # A new test collection
test_collection = line.split(".")[0].strip();
else: # An individual test to run
test_name = line.strip()
test_list.append("%s.%s" % (test_collection, test_name))
logging.info('List of tests to run: %s' % (test_list))
return test_list
def GetUrl(command, port):
"""Prepares the URL with appropriate command to send it to http server.
Args:
command: Command for HTTP server
port: Port number as a parameter to the command
Returns:
Formulated URL with the command and parameter.
"""
command_url = (
'%s:%s/%s?port=%s'
% (HTTP_SERVER_URL, HTTP_SERVER_PORT, command, port))
return command_url
def StartSyncServer(port=None):
"""Starts a chrome sync server.
Args:
port: Port number on which sync server to start
(Default value none, in this case it uses random port).
Returns:
If success then port number on which sync server started, else None.
"""
sync_port = None
if port:
start_sync_server_url = GetUrl('startsyncserver', port)
else:
start_sync_server_url = (
'%s:%s/%s' % (HTTP_SERVER_URL, HTTP_SERVER_PORT, 'startsyncserver'))
req = urllib2.Request(start_sync_server_url)
try:
response = urllib2.urlopen(req)
except urllib2.HTTPError, e:
logging.error(
'Could not start sync server, something went wrong.'
'Request URL: %s , Error Code: %s'% (start_sync_server_url, e.code))
return sync_port
except urllib2.URLError, e:
logging.error(
'Failed to reach HTTP server.Request URL: %s , Error: %s'
% (start_sync_server_url, e.reason))
return sync_port
else:
# Let's read response and parse the sync server port number.
output = response.readlines()
# Regex to ensure that sync server started and extract the port number.
regex = re.compile(
".*not.*running.*on.*port\s*:\s*(\d+).*started.*new.*sync.*server",
re.IGNORECASE|re.MULTILINE|re.DOTALL)
r = regex.search(output[0])
if r:
sync_port = r.groups()[0]
if sync_port:
logging.info(
'Started Sync Server Successfully on Port:%s. Request URL: %s , '
'Response: %s' % (sync_port, start_sync_server_url, output))
response.fp._sock.recv = None
response.close()
return sync_port
def CheckIfSyncServerRunning(port):
"""Check the healthz status of a chrome sync server.
Args:
port: Port number on which sync server is running
Returns:
True: If sync server running.
False: Otherwise.
"""
sync_server_healthz_url = ('%s:%s/healthz' % (HTTP_SERVER_URL, port))
req = urllib2.Request(sync_server_healthz_url)
try:
response = urllib2.urlopen(req)
except urllib2.HTTPError, e:
logging.error(
'It seems like Sync Server is not running, healthz check failed.'
'Request URL: %s , Error Code: %s'% (sync_server_healthz_url, e.code))
return False
except urllib2.URLError, e:
logging.error(
'Failed to reach Sync server, healthz check failed.'
'Request URL: %s , Error: %s'% (sync_server_healthz_url, e.reason))
return False
else:
logging.info(
'Sync Server healthz check Passed.Request URL: %s , Response: %s'
% (sync_server_healthz_url, response.readlines()))
response.fp._sock.recv = None
response.close()
return True
def StopSyncServer(port):
"""Stops a chrome sync server.
Args:
port: Port number on which sync server to Stop
Returns:
Success/Failure as a bool value.
"""
stop_sync_server_url = GetUrl('stopsyncserver', port)
req = urllib2.Request(stop_sync_server_url)
logging.info("Stopping: %s" % stop_sync_server_url)
try:
response = urllib2.urlopen(req)
except urllib2.HTTPError, e:
logging.error(
'Could not stop sync server, something went wrong.'
'Request URL: %s , Error Code: %s'% (stop_sync_server_url, e.code))
return False
except urllib2.URLError, e:
logging.error(
'Failed to reach HTTP server.Request URL: %s , Error: %s'
% (stop_sync_server_url, e.reason))
return False
else:
logging.info(
'Stopped Sync Server Successfully.Request URL: %s , Response: %s'
% (stop_sync_server_url, response.readlines()))
response.fp._sock.recv = None
response.close()
return True
def ShowGtestLikeFailure(test_exe_path, test_name):
"""Show a gtest-like error when the test can't be run for some reason.
The scripts responsible for detecting test failures watch for this pattern.
Args:
test_exe_path: Absolute path to the test exe file.
test_name: The name of the test that wasn't run.
"""
print '[ RUN ] %s.%s' % (os.path.basename(test_exe_path), test_name)
print '[ FAILED ] %s.%s' % (os.path.basename(test_exe_path), test_name)
def RunCommand(command):
"""Runs the command list, printing its output and returning its exit status.
Prints the given command (which should be a list of one or more strings),
then runs it and prints its stdout and stderr together to stdout,
line-buffered, converting line endings to CRLF (see note below). Waits for
the command to terminate and returns its status.
Args:
command: Command to run.
Returns:
Process exit code.
"""
print '\n' + subprocess.list2cmdline(command) + '\n',
proc = subprocess.Popen(command, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, bufsize=1)
last_flush_time = time.time()
while proc.poll() == None:
# Note that Windows Python converts \n to \r\n automatically whenever it
# encounters it written to a text file (including stdout). The only way
# around it is to write to a binary file, which isn't feasible for stdout.
# So we're stuck with \r\n here even though we explicitly write \n. (We
# could write \r instead, which doesn't get converted to \r\n, but that's
# probably more troublesome for people trying to read the files.)
line = proc.stdout.readline()
if line:
# The comma at the end tells python to not add \n, which is \r\n on
# Windows.
print line.rstrip() + '\n',
# Python on windows writes the buffer only when it reaches 4k. This is
# not fast enough. Flush each 10 seconds instead.
if time.time() - last_flush_time >= 10:
sys.stdout.flush()
last_flush_time = time.time()
sys.stdout.flush()
# Write the remaining buffer.
for line in proc.stdout.readlines():
print line.rstrip() + '\n',
sys.stdout.flush()
return proc.returncode
def RunOneTest(test_exe_path, test_name, username, password):
"""Run one live sync test after setting up a fresh server environment.
Args:
test_exe_path: Absolute path to test exe file.
test_name: the name of the one test to run.
username: test account username.
password: test account password.
Returns:
Zero for suceess.
Non-zero for failure.
"""
def LogTestNotRun(message):
ShowGtestLikeFailure(test_exe_path, test_name)
logging.info('\n\n%s did not run because %s' % (test_name, message))
try:
logging.info('\n\n*************************************')
logging.info('%s Start' % (test_name))
sync_port = StartSyncServer()
if not sync_port:
LogTestNotRun('starting the sync server failed.')
return 1
if not CheckIfSyncServerRunning(sync_port):
LogTestNotRun('sync server running check failed.')
return 1
logging.info('Verified that sync server is running on port: %s', sync_port)
user_dir = GenericSetup(test_name)
logging.info('Created user data dir %s' % (user_dir))
command = [
test_exe_path,
'--gtest_filter='+ test_name,
'--user-data-dir=' + user_dir,
'--sync-user-for-test=' + username,
'--sync-password-for-test=' + password,
'--sync-url=' + HTTP_SERVER_URL + ':' + sync_port]
logging.info(
'%s will run with command: %s'
% (test_name, subprocess.list2cmdline(command)))
result = RunCommand(command)
StopSyncServer(sync_port)
return result
finally:
logging.info('%s End' % (test_name))
def GenericSetup(test_name):
"""Generic setup for running one test.
Args:
test_name: The name of a test that is about to be run.
Returns:
user_dir: Absolute path to user data dir created for the test.
"""
user_dir = tempfile.mkdtemp(prefix=test_name + '.')
return user_dir
def main_win(options, args):
"""Main Function for win32 platform which drives the test here.
Using the target build configuration, run the executable given in the
first non-option argument, passing any following arguments to that
executable.
Args:
options: Option parameters.
args: Test arguments.
Returns:
Result: Zero for success/ Non-zero for failure.
"""
final_result = 0
test_exe = 'sync_integration_tests.exe'
build_dir = os.path.abspath(options.build_dir)
test_exe_path = os.path.join(build_dir, options.target, test_exe)
if not os.path.exists(test_exe_path):
raise PathNotFound('Unable to find %s' % test_exe_path)
test_list = ListSyncTests(test_exe_path)
for test_name in test_list:
result = RunOneTest(
test_exe_path, test_name, options.sync_test_username,
options.sync_test_password)
# If any single test fails then final result should be failure
if result != 0:
final_result = result
return final_result
if '__main__' == __name__:
# Initialize logging.
log_level = logging.INFO
logging.basicConfig(
level=log_level, format='%(asctime)s %(filename)s:%(lineno)-3d'
' %(levelname)s %(message)s', datefmt='%y%m%d %H:%M:%S')
option_parser = optparse.OptionParser(usage=USAGE)
# Since the trailing program to run may have has command-line args of its
# own, we need to stop parsing when we reach the first positional argument.
option_parser.disable_interspersed_args()
option_parser.add_option(
'', '--target', default='Release', help='Build target (Debug or Release)'
' Default value Release.')
option_parser.add_option(
'', '--build-dir', help='Path to main build directory'
'(the parent of the Release or Debug directory).')
option_parser.add_option(
'', '--http-server-url', help='Path to http server that can be used to'
' start/stop sync server e.g. http://http_server_url_without_port')
option_parser.add_option(
'', '--http-server-port', help='Port on which http server is running'
' e.g. 1010')
option_parser.add_option(
'', '--sync-test-username', help='Test username e.g. [email protected]')
option_parser.add_option(
'', '--sync-test-password', help='Password for the test account')
options, args = option_parser.parse_args()
if not options.sync_test_password:
# Check along side this script under src/ first, and then check in
# the user profile dir.
password_file_path = os.path.join(
os.path.dirname(__file__),'sync_password')
if (not os.path.exists(password_file_path)):
password_file_path = os.path.join(
os.environ['USERPROFILE'], 'sync_password')
if os.path.exists(password_file_path):
fs = open(password_file_path, 'r')
lines = fs.readlines()
if len(lines)==1:
options.sync_test_password = lines[0].strip()
else:
sys.stderr.write('sync_password file is not in required format.\n')
sys.exit(1)
else:
sys.stderr.write(
'Missing required parameter- sync_test_password, please fix it.\n')
sys.exit(1)
if (not options.build_dir or not options.http_server_url or
not options.http_server_port or not options.sync_test_username):
sys.stderr.write('Missing required parameter, please fix it.\n')
option_parser.print_help()
sys.exit(1)
if sys.platform == 'win32':
HTTP_SERVER_URL = options.http_server_url
HTTP_SERVER_PORT = options.http_server_port
sys.exit(main_win(options, args))
else:
sys.stderr.write('Unknown sys.platform value %s\n' % repr(sys.platform))
sys.exit(1)