| #!/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.test_server = None |
| self.port = None |
| self.tempdir = None |
| |
| # Find the GAE SDK |
| previous_dir = '' |
| self.sdk_path = '' |
| base_dir = self.base_dir |
| while base_dir != previous_dir: |
| previous_dir = base_dir |
| self.sdk_path = os.path.join(base_dir, 'google_appengine') |
| if not os.path.isfile(os.path.join(self.sdk_path, 'VERSION')): |
| base_dir = os.path.dirname(base_dir) |
| self.dev_app = os.path.join(self.sdk_path, 'dev_appserver.py') |
| |
| def install_prerequisites(self): |
| # First, verify the Google AppEngine SDK is available. |
| if not os.path.isfile(self.dev_app): |
| raise Failure( |
| 'Install google_appengine sdk in %s or higher up' % self.base_dir) |
| |
| if os.path.isdir(os.path.join(self.rietveld, '.svn')): |
| # Left over from subversion. Delete it. |
| shutil.rmtree(self.rietveld) |
| |
| # Second, checkout rietveld if not available. |
| rev = '9349cab9a3bb' |
| if not os.path.isdir(self.rietveld): |
| print('Checking out rietveld...') |
| try: |
| subprocess2.check_call( |
| [ 'hg', 'clone', '-q', '-u', rev, '-r', rev, |
| 'https://code.google.com/p/rietveld/', self.rietveld]) |
| except (OSError, subprocess2.CalledProcessError), e: |
| raise Failure( |
| 'Failed to checkout rietveld. Do you have mercurial installed?\n' |
| '%s' % e) |
| else: |
| print('Syncing rietveld...') |
| try: |
| subprocess2.check_call( |
| ['hg', 'co', '-q', '-C', rev], 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(8080) |
| 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, |
| '.', |
| '--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) |
| # 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() |