blob: 60870ad5145da4b61de7d46c87d9b26e6578a42d [file] [log] [blame]
# Copyright (c) 2012 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.
import inspect
import logging
import httplib
import os
import json
import re
import socket
import subprocess
import sys
import time
from tracing.build import dev_server
def GetUnreservedAvailableLocalPort():
"""Returns an available port on the system.
WARNING: This method does not reserve the port it returns, so it may be used
by something else before you get to use it. This can lead to flake.
"""
tmp = socket.socket()
tmp.bind(('', 0))
port = tmp.getsockname()[1]
tmp.close()
return port
def WaitFor(condition, timeout):
"""Waits for up to |timeout| secs for the function |condition| to return True.
Polling frequency is (elapsed_time / 10), with a min of .1s and max of 5s.
Returns:
Result of |condition| function (if present).
"""
min_poll_interval = 0.1
max_poll_interval = 5
output_interval = 300
def GetConditionString():
if condition.__name__ == '<lambda>':
try:
return inspect.getsource(condition).strip()
except IOError:
pass
return condition.__name__
start_time = time.time()
last_output_time = start_time
while True:
res = condition()
if res:
return res
now = time.time()
elapsed_time = now - start_time
last_output_elapsed_time = now - last_output_time
if elapsed_time > timeout:
raise Exception('Timed out while waiting %ds for %s.' %
(timeout, GetConditionString()))
if last_output_elapsed_time > output_interval:
logging.info('Continuing to wait %ds for %s. Elapsed: %ds.',
timeout, GetConditionString(), elapsed_time)
last_output_time = time.time()
poll_interval = min(max(elapsed_time / 10., min_poll_interval),
max_poll_interval)
time.sleep(poll_interval)
class TemporaryDevServer(object):
def __init__(self):
self._port = None
self._server = None
cmd = [sys.executable, '-m', __name__]
env = os.environ.copy()
env['PYTHONPATH'] = os.path.abspath(
os.path.join(os.path.dirname(__file__), '..'))
self._server = subprocess.Popen(
cmd, cwd=os.getcwd(), env=env,
stdout=subprocess.PIPE, stderr=sys.stderr)
port_re = re.compile(
'TemporaryDevServer started on port (?P<port>\d+)')
while self._server.poll() is None:
line = self._server.stdout.readline()
m = port_re.match(line)
if m:
self._port = int(m.group('port'))
break
if not self._port:
raise Exception('Couldn\'t start')
def IsServerUp():
return not socket.socket().connect_ex(('localhost', self._port))
WaitFor(IsServerUp, 10)
@property
def port(self):
return self._port
def __del__(self):
self.Close()
def Close(self):
if self._server:
# TODO(tonyg): Should this block until it goes away?
if self._server.poll() is None:
self._server.kill()
self._server = None
@property
def url(self):
return 'http://localhost:%i/' % self._port
def Get(self, path, expected_response_code=200):
conn = httplib.HTTPConnection('localhost', self.port, True)
conn.connect()
conn.request('GET', path)
resp = conn.getresponse()
resp_str = resp.read(resp.getheader('Content-Length'))
if resp.status != expected_response_code:
try:
resp_data = json.loads(resp_str)
except ValueError:
resp_data = {}
if 'details' in resp_data:
sys.stderr.write(resp_data['details'])
sys.stderr.write('\n')
raise Exception('Expected status %i, got %i' %
(expected_response_code, resp.status))
else:
raise Exception('Expected status %i, got %i: %s' %
(expected_response_code, resp.status, resp_str))
return resp_str
def CallOnServer(self, method_name, *args):
arg_string = ','.join(repr(arg) for arg in args)
data = 'self.%s(%s)' % (method_name, arg_string)
conn = httplib.HTTPConnection('localhost', self.port, True)
conn.connect()
conn.request('POST', '/test/customize', data)
resp = conn.getresponse()
resp_str = resp.read(resp.getheader('Content-Length'))
if resp.status != 200:
resp_data = json.loads(resp_str)
sys.stderr.write(resp_data['details'])
sys.stderr.write('\n')
assert False
return resp_str
def _do_POST_customize(request):
if 'Content-Length' in request.headers:
cl = int(request.headers['Content-Length'])
text = request.rfile.read(cl).encode('utf8')
eval(text, {}, {'self': request.server})
data = 'ok'
request.send_response(200)
request.send_header('Content-Type', 'text/plain')
request.send_header('Content-Length', len(data))
request.end_headers()
request.wfile.write(data)
def SubprocessMain(_):
port = GetUnreservedAvailableLocalPort()
server = dev_server.DevServer(port=port, quiet=True)
server.AddPathHandler('/test/customize', _do_POST_customize,
supports_get=False,
supports_post=True)
sys.stdout.write('TemporaryDevServer started on port %i\n' % port)
sys.stdout.flush() # This is key! It kicks the port detector above.
server.serve_forever()
if __name__ == '__main__':
sys.exit(SubprocessMain(sys.argv[1:]))