You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
189 lines
5.8 KiB
Python
189 lines
5.8 KiB
Python
#!/usr/bin/env python
|
|
# 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.
|
|
|
|
"""Setups a local Rietveld instance to test against a live server for
|
|
integration tests.
|
|
|
|
It makes sure Google AppEngine SDK is found, download Rietveld and Django code
|
|
if necessary and starts the server on a free inbound TCP port.
|
|
"""
|
|
|
|
import logging
|
|
import optparse
|
|
import os
|
|
import shutil
|
|
import socket
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
|
|
try:
|
|
import subprocess2
|
|
except ImportError:
|
|
sys.path.append(
|
|
os.path.join(os.path.dirname(os.path.abspath(__file__)), '..'))
|
|
import subprocess2
|
|
|
|
|
|
class Failure(Exception):
|
|
pass
|
|
|
|
|
|
def test_port(port):
|
|
s = socket.socket()
|
|
try:
|
|
return s.connect_ex(('127.0.0.1', port)) == 0
|
|
finally:
|
|
s.close()
|
|
|
|
|
|
def find_free_port(start_port):
|
|
"""Search for a free port starting at specified port."""
|
|
for port in xrange(start_port, (2<<16)):
|
|
if not test_port(port):
|
|
return port
|
|
raise Failure('Having issues finding an available port')
|
|
|
|
|
|
class LocalRietveld(object):
|
|
"""Downloads everything needed to run a local instance of Rietveld."""
|
|
|
|
def __init__(self, base_dir=None):
|
|
# Paths
|
|
self.base_dir = base_dir
|
|
if not self.base_dir:
|
|
self.base_dir = os.path.dirname(os.path.abspath(__file__))
|
|
# TODO(maruel): This should be in /tmp but that would mean having to fetch
|
|
# everytime. This test is already annoyingly slow.
|
|
self.rietveld = os.path.join(self.base_dir, '_rietveld')
|
|
self.rietveld_app = os.path.join(
|
|
self.rietveld, 'appengine', 'chromium_rietveld')
|
|
self.test_server = None
|
|
self.port = None
|
|
self.tempdir = None
|
|
self.dev_app = None
|
|
|
|
def install_prerequisites(self):
|
|
# First, install the Google AppEngine SDK.
|
|
cmd = [os.path.join(self.base_dir, 'get_appengine.py'),
|
|
'--dest=%s' % self.base_dir]
|
|
try:
|
|
subprocess2.check_call(cmd)
|
|
except (OSError, subprocess2.CalledProcessError), e:
|
|
raise Failure('Failed to run %s\n%s' % (cmd, e))
|
|
sdk_path = os.path.join(self.base_dir, 'google_appengine')
|
|
self.dev_app = os.path.join(sdk_path, 'dev_appserver.py')
|
|
|
|
if os.path.isdir(os.path.join(self.rietveld, '.hg')):
|
|
# Left over from mercurial. Delete it.
|
|
print('Deleting deprecated mercurial rietveld files...')
|
|
shutil.rmtree(self.rietveld)
|
|
|
|
# Second, checkout rietveld if not available.
|
|
if not os.path.isdir(self.rietveld):
|
|
print('Checking out rietveld...')
|
|
try:
|
|
subprocess2.check_call(['git', 'init', self.rietveld])
|
|
subprocess2.check_call(
|
|
['git', 'remote', 'add', '-f', 'origin',
|
|
'https://chromium.googlesource.com/infra/infra.git'],
|
|
cwd=self.rietveld)
|
|
subprocess2.check_call(
|
|
['git', 'config', 'core.sparseCheckout', 'true'],
|
|
cwd=self.rietveld)
|
|
with file(os.path.join(self.rietveld, '.git/info/sparse-checkout'),
|
|
'w') as sparse_file:
|
|
sparse_file.write('appengine/chromium_rietveld')
|
|
subprocess2.check_call(
|
|
['git', 'pull', 'origin', 'master'],
|
|
cwd=self.rietveld)
|
|
except (OSError, subprocess2.CalledProcessError), e:
|
|
raise Failure('Failed to clone rietveld. \n%s' % e)
|
|
else:
|
|
print('Syncing rietveld...')
|
|
try:
|
|
subprocess2.check_call(
|
|
['git', 'pull', 'origin', 'master'],
|
|
cwd=self.rietveld)
|
|
except (OSError, subprocess2.CalledProcessError), e:
|
|
raise Failure('Failed to sync rietveld\n%s' % e)
|
|
|
|
def start_server(self, verbose=False):
|
|
self.install_prerequisites()
|
|
assert not self.tempdir
|
|
self.tempdir = tempfile.mkdtemp(prefix='rietveld_test')
|
|
self.port = find_free_port(10000)
|
|
admin_port = find_free_port(self.port + 1)
|
|
if verbose:
|
|
stdout = stderr = None
|
|
else:
|
|
stdout = subprocess2.PIPE
|
|
stderr = subprocess2.STDOUT
|
|
cmd = [
|
|
sys.executable,
|
|
self.dev_app,
|
|
'./app.yaml', # Explicitly specify file to avoid bringing up backends.
|
|
'--port', str(self.port),
|
|
'--admin_port', str(admin_port),
|
|
'--storage', self.tempdir,
|
|
'--clear_search_indexes',
|
|
'--skip_sdk_update_check',
|
|
]
|
|
|
|
# CHEAP TRICK
|
|
# By default you only want to bind on loopback but I'm testing over a
|
|
# headless computer so it's useful to be able to access the test instance
|
|
# remotely.
|
|
if os.environ.get('GAE_LISTEN_ALL', '') == 'true':
|
|
cmd.extend(('-a', '0.0.0.0'))
|
|
logging.info(' '.join(cmd))
|
|
self.test_server = subprocess2.Popen(
|
|
cmd, stdout=stdout, stderr=stderr, cwd=self.rietveld_app)
|
|
# Loop until port 127.0.0.1:port opens or the process dies.
|
|
while not test_port(self.port):
|
|
self.test_server.poll()
|
|
if self.test_server.returncode is not None:
|
|
if not verbose:
|
|
out = self.test_server.communicate()[0]
|
|
shutil.rmtree(self.tempdir)
|
|
self.tempdir = None
|
|
raise Failure(
|
|
'Test rietveld instance failed early on port %s\n%s' %
|
|
(self.port, out))
|
|
time.sleep(0.01)
|
|
|
|
def stop_server(self):
|
|
if self.test_server:
|
|
try:
|
|
self.test_server.kill()
|
|
except OSError:
|
|
pass
|
|
self.test_server.wait()
|
|
self.test_server = None
|
|
self.port = None
|
|
if self.tempdir:
|
|
shutil.rmtree(self.tempdir)
|
|
self.tempdir = None
|
|
|
|
|
|
def main():
|
|
parser = optparse.OptionParser()
|
|
parser.add_option('-v', '--verbose', action='store_true')
|
|
options, args = parser.parse_args()
|
|
if args:
|
|
parser.error('Unknown arguments: %s' % ' '.join(args))
|
|
instance = LocalRietveld()
|
|
try:
|
|
instance.start_server(verbose=options.verbose)
|
|
print 'Local rietveld instance started on port %d' % instance.port
|
|
while True:
|
|
time.sleep(0.1)
|
|
finally:
|
|
instance.stop_server()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|