]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Santhosh/Vinkesh | Added the testing framework. Moved the smoketest to tests/functional
authorSanthosh <santhom@thoughtworks.com>
Wed, 8 Jun 2011 08:52:51 +0000 (14:22 +0530)
committerSanthosh <santhom@thoughtworks.com>
Wed, 8 Jun 2011 08:52:51 +0000 (14:22 +0530)
12 files changed:
run_tests.py [new file with mode: 0644]
run_tests.sh [new file with mode: 0755]
test_scripts/miniclient.py [deleted file]
test_scripts/tests.py [deleted file]
tests/__init__.py [moved from smoketests/__init__.py with 100% similarity]
tests/functional/__init__.py [moved from test_scripts/__init__.py with 100% similarity]
tests/functional/miniclient.py [moved from smoketests/miniclient.py with 100% similarity]
tests/functional/test_service.py [moved from smoketests/tests.py with 100% similarity]
tests/unit/__init__.py [new file with mode: 0644]
tools/install_venv.py [new file with mode: 0644]
tools/pip-requires [new file with mode: 0644]
tools/with_venv.sh [new file with mode: 0755]

diff --git a/run_tests.py b/run_tests.py
new file mode 100644 (file)
index 0000000..acc8260
--- /dev/null
@@ -0,0 +1,293 @@
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 OpenStack, LLC
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License");
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+
+# Colorizer Code is borrowed from Twisted:
+# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
+#
+#    Permission is hereby granted, free of charge, to any person obtaining
+#    a copy of this software and associated documentation files (the
+#    "Software"), to deal in the Software without restriction, including
+#    without limitation the rights to use, copy, modify, merge, publish,
+#    distribute, sublicense, and/or sell copies of the Software, and to
+#    permit persons to whom the Software is furnished to do so, subject to
+#    the following conditions:
+#
+#    The above copyright notice and this permission notice shall be
+#    included in all copies or substantial portions of the Software.
+#
+#    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+#    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+#    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+#    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+#    LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+#    OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+#    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+"""Unittest runner for quantum
+
+To run all test::
+    python run_tests.py
+
+To run all unit tests::
+    python run_tests.py unit
+
+To run all functional tests::
+    python run_tests.py functional
+
+To run a single unit test::
+    python run_tests.py unit.test_stores:TestSwiftBackend.test_get
+
+To run a single functional test::
+    python run_tests.py functional.test_service:TestController.test_create
+
+To run a single unit test module::
+    python run_tests.py unit.test_stores
+
+To run a single functional test module::
+    python run_tests.py functional.test_stores
+"""
+
+import gettext
+import os
+import unittest
+import sys
+
+from nose import config
+from nose import result
+from nose import core
+
+
+class _AnsiColorizer(object):
+    """
+    A colorizer is an object that loosely wraps around a stream, allowing
+    callers to write text to the stream in a particular color.
+
+    Colorizer classes must implement C{supported()} and C{write(text, color)}.
+    """
+    _colors = dict(black=30, red=31, green=32, yellow=33,
+                   blue=34, magenta=35, cyan=36, white=37)
+
+    def __init__(self, stream):
+        self.stream = stream
+
+    def supported(cls, stream=sys.stdout):
+        """
+        A class method that returns True if the current platform supports
+        coloring terminal output using this method. Returns False otherwise.
+        """
+        if not stream.isatty():
+            return False  # auto color only on TTYs
+        try:
+            import curses
+        except ImportError:
+            return False
+        else:
+            try:
+                try:
+                    return curses.tigetnum("colors") > 2
+                except curses.error:
+                    curses.setupterm()
+                    return curses.tigetnum("colors") > 2
+            except:
+                raise
+                # guess false in case of error
+                return False
+    supported = classmethod(supported)
+
+    def write(self, text, color):
+        """
+        Write the given text to the stream in the given color.
+
+        @param text: Text to be written to the stream.
+
+        @param color: A string label for a color. e.g. 'red', 'white'.
+        """
+        color = self._colors[color]
+        self.stream.write('\x1b[%s;1m%s\x1b[0m' % (color, text))
+
+
+class _Win32Colorizer(object):
+    """
+    See _AnsiColorizer docstring.
+    """
+    def __init__(self, stream):
+        from win32console import GetStdHandle, STD_OUT_HANDLE, \
+             FOREGROUND_RED, FOREGROUND_BLUE, FOREGROUND_GREEN, \
+             FOREGROUND_INTENSITY
+        red, green, blue, bold = (FOREGROUND_RED, FOREGROUND_GREEN,
+                                  FOREGROUND_BLUE, FOREGROUND_INTENSITY)
+        self.stream = stream
+        self.screenBuffer = GetStdHandle(STD_OUT_HANDLE)
+        self._colors = {
+            'normal': red | green | blue,
+            'red': red | bold,
+            'green': green | bold,
+            'blue': blue | bold,
+            'yellow': red | green | bold,
+            'magenta': red | blue | bold,
+            'cyan': green | blue | bold,
+            'white': red | green | blue | bold}
+
+    def supported(cls, stream=sys.stdout):
+        try:
+            import win32console
+            screenBuffer = win32console.GetStdHandle(
+                win32console.STD_OUT_HANDLE)
+        except ImportError:
+            return False
+        import pywintypes
+        try:
+            screenBuffer.SetConsoleTextAttribute(
+                win32console.FOREGROUND_RED |
+                win32console.FOREGROUND_GREEN |
+                win32console.FOREGROUND_BLUE)
+        except pywintypes.error:
+            return False
+        else:
+            return True
+    supported = classmethod(supported)
+
+    def write(self, text, color):
+        color = self._colors[color]
+        self.screenBuffer.SetConsoleTextAttribute(color)
+        self.stream.write(text)
+        self.screenBuffer.SetConsoleTextAttribute(self._colors['normal'])
+
+
+class _NullColorizer(object):
+    """
+    See _AnsiColorizer docstring.
+    """
+    def __init__(self, stream):
+        self.stream = stream
+
+    def supported(cls, stream=sys.stdout):
+        return True
+    supported = classmethod(supported)
+
+    def write(self, text, color):
+        self.stream.write(text)
+
+
+class QuantumTestResult(result.TextTestResult):
+    def __init__(self, *args, **kw):
+        result.TextTestResult.__init__(self, *args, **kw)
+        self._last_case = None
+        self.colorizer = None
+        # NOTE(vish, tfukushima): reset stdout for the terminal check
+        stdout = sys.__stdout__
+        for colorizer in [_Win32Colorizer, _AnsiColorizer, _NullColorizer]:
+            if colorizer.supported():
+                self.colorizer = colorizer(self.stream)
+                break
+        sys.stdout = stdout
+
+    def getDescription(self, test):
+        return str(test)
+
+    # NOTE(vish, tfukushima): copied from unittest with edit to add color
+    def addSuccess(self, test):
+        unittest.TestResult.addSuccess(self, test)
+        if self.showAll:
+            self.colorizer.write("OK", 'green')
+            self.stream.writeln()
+        elif self.dots:
+            self.stream.write('.')
+            self.stream.flush()
+
+    # NOTE(vish, tfukushima): copied from unittest with edit to add color
+    def addFailure(self, test, err):
+        unittest.TestResult.addFailure(self, test, err)
+        if self.showAll:
+            self.colorizer.write("FAIL", 'red')
+            self.stream.writeln()
+        elif self.dots:
+            self.stream.write('F')
+            self.stream.flush()
+
+    # NOTE(vish, tfukushima): copied from unittest with edit to add color
+    def addError(self, test, err):
+        """Overrides normal addError to add support for errorClasses.
+        If the exception is a registered class, the error will be added
+        to the list for that class, not errors.
+        """
+        stream = getattr(self, 'stream', None)
+        ec, ev, tb = err
+        try:
+            exc_info = self._exc_info_to_string(err, test)
+        except TypeError:
+            # This is for compatibility with Python 2.3.
+            exc_info = self._exc_info_to_string(err)
+        for cls, (storage, label, isfail) in self.errorClasses.items():
+            if result.isclass(ec) and issubclass(ec, cls):
+                if isfail:
+                    test.passwd = False
+                storage.append((test, exc_info))
+                # Might get patched into a streamless result
+                if stream is not None:
+                    if self.showAll:
+                        message = [label]
+                        detail = result._exception_details(err[1])
+                        if detail:
+                            message.append(detail)
+                        stream.writeln(": ".join(message))
+                    elif self.dots:
+                        stream.write(label[:1])
+                return
+        self.errors.append((test, exc_info))
+        test.passed = False
+        if stream is not None:
+            if self.showAll:
+                self.colorizer.write("ERROR", 'red')
+                self.stream.writeln()
+            elif self.dots:
+                stream.write('E')
+
+    def startTest(self, test):
+        unittest.TestResult.startTest(self, test)
+        current_case = test.test.__class__.__name__
+
+        if self.showAll:
+            if current_case != self._last_case:
+                self.stream.writeln(current_case)
+                self._last_case = current_case
+
+            self.stream.write(
+                '    %s' % str(test.test._testMethodName).ljust(60))
+            self.stream.flush()
+
+
+class QuantumTestRunner(core.TextTestRunner):
+    def _makeResult(self):
+        return QuantumTestResult(self.stream,
+                              self.descriptions,
+                              self.verbosity,
+                              self.config)
+
+
+if __name__ == '__main__':
+    working_dir = os.path.abspath("tests")
+    c = config.Config(stream=sys.stdout,
+                      env=os.environ,
+                      verbosity=3,
+                      workingDir=working_dir)
+
+    runner = QuantumTestRunner(stream=c.stream,
+                            verbosity=c.verbosity,
+                            config=c)
+    sys.exit(not core.run(config=c, testRunner=runner))
diff --git a/run_tests.sh b/run_tests.sh
new file mode 100755 (executable)
index 0000000..aa72cbe
--- /dev/null
@@ -0,0 +1,83 @@
+#!/bin/bash
+
+function usage {
+  echo "Usage: $0 [OPTION]..."
+  echo "Run Melange's test suite(s)"
+  echo ""
+  echo "  -V, --virtual-env        Always use virtualenv.  Install automatically if not present"
+  echo "  -N, --no-virtual-env     Don't use virtualenv.  Run tests in local environment"
+  echo "  -f, --force              Force a clean re-build of the virtual environment. Useful when dependencies have been added."
+  echo "  -h, --help               Print this usage message"
+  echo ""
+  echo "Note: with no options specified, the script will try to run the tests in a virtual environment,"
+  echo "      If no virtualenv is found, the script will ask if you would like to create one.  If you "
+  echo "      prefer to run tests NOT in a virtual environment, simply pass the -N option."
+  exit
+}
+
+function process_option {
+  case "$1" in
+    -h|--help) usage;;
+    -V|--virtual-env) let always_venv=1; let never_venv=0;;
+    -N|--no-virtual-env) let always_venv=0; let never_venv=1;;
+    -f|--force) let force=1;;
+    *) noseargs="$noseargs $1"
+  esac
+}
+
+venv=.quantum-venv
+with_venv=tools/with_venv.sh
+always_venv=0
+never_venv=0
+force=0
+noseargs=
+wrapper=""
+
+for arg in "$@"; do
+  process_option $arg
+done
+
+function run_tests {
+  # Just run the test suites in current environment
+  ${wrapper} rm -f tests.sqlite
+  ${wrapper} $NOSETESTS 2> run_tests.err.log
+}
+
+NOSETESTS="python run_tests.py $noseargs"
+
+if [ $never_venv -eq 0 ]
+then
+  # Remove the virtual environment if --force used
+  if [ $force -eq 1 ]; then
+    echo "Cleaning virtualenv..."
+    rm -rf ${venv}
+  fi
+  if [ -e ${venv} ]; then
+    wrapper="${with_venv}"
+  else
+    if [ $always_venv -eq 1 ]; then
+      # Automatically install the virtualenv
+      python tools/install_venv.py
+      wrapper="${with_venv}"
+    else
+      echo -e "No virtual environment found...create one? (Y/n) \c"
+      read use_ve
+      if [ "x$use_ve" = "xY" -o "x$use_ve" = "x" -o "x$use_ve" = "xy" ]; then
+        # Install the virtualenv and run the test suite in it
+        python tools/install_venv.py
+                   wrapper=${with_venv}
+      fi
+    fi
+  fi
+fi
+
+# FIXME(sirp): bzr version-info is not currently pep-8. This was fixed with
+# lp701898 [1], however, until that version of bzr becomes standard, I'm just
+# excluding the vcsversion.py file
+#
+# [1] https://bugs.launchpad.net/bzr/+bug/701898
+#
+PEP8_EXCLUDE=vcsversion.py
+PEP8_OPTIONS="--exclude=$PEP8_EXCLUDE --repeat --show-source"
+PEP8_INCLUDE="bin/* quantum tests tools run_tests.py"
+run_tests && pep8 $PEP8_OPTIONS $PEP8_INCLUDE || exit 1
diff --git a/test_scripts/miniclient.py b/test_scripts/miniclient.py
deleted file mode 100644 (file)
index fb1ebc8..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2011 Citrix Systems
-# All Rights Reserved.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-
-import httplib
-import socket
-import urllib
-
-class MiniClient(object):
-
-    """A base client class - derived from Glance.BaseClient"""
-
-    action_prefix = '/v0.1/tenants/{tenant_id}'
-
-    def __init__(self, host, port, use_ssl):
-        """
-        Creates a new client to some service.
-
-        :param host: The host where service resides
-        :param port: The port where service resides
-        :param use_ssl: Should we use HTTPS?
-        """
-        self.host = host
-        self.port = port
-        self.use_ssl = use_ssl
-        self.connection = None
-
-    def get_connection_type(self):
-        """
-        Returns the proper connection type
-        """
-        if self.use_ssl:
-            return httplib.HTTPSConnection
-        else:
-            return httplib.HTTPConnection
-
-    def do_request(self, tenant, method, action, body=None,
-                   headers=None, params=None):
-        """
-        Connects to the server and issues a request.  
-        Returns the result data, or raises an appropriate exception if
-        HTTP status code is not 2xx
-
-        :param method: HTTP method ("GET", "POST", "PUT", etc...)
-        :param body: string of data to send, or None (default)
-        :param headers: mapping of key/value pairs to add as headers
-        :param params: dictionary of key/value pairs to add to append
-                             to action
-
-        """
-        action = MiniClient.action_prefix + action
-        action = action.replace('{tenant_id}',tenant)
-        if type(params) is dict:
-            action += '?' + urllib.urlencode(params)
-
-        try:
-            connection_type = self.get_connection_type()
-            headers = headers or {}
-            
-            # Open connection and send request
-            c = connection_type(self.host, self.port)
-            c.request(method, action, body, headers)
-            res = c.getresponse()
-            status_code = self.get_status_code(res)
-            if status_code in (httplib.OK,
-                               httplib.CREATED,
-                               httplib.ACCEPTED,
-                               httplib.NO_CONTENT):
-                return res
-            else:
-                raise Exception("Server returned error: %s" % res.read())
-
-        except (socket.error, IOError), e:
-            raise Exception("Unable to connect to "
-                            "server. Got error: %s" % e)
-
-    def get_status_code(self, response):
-        """
-        Returns the integer status code from the response, which
-        can be either a Webob.Response (used in testing) or httplib.Response
-        """
-        if hasattr(response, 'status_int'):
-            return response.status_int
-        else:
-            return response.status
\ No newline at end of file
diff --git a/test_scripts/tests.py b/test_scripts/tests.py
deleted file mode 100644 (file)
index 589d9da..0000000
+++ /dev/null
@@ -1,150 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2011 Citrix Systems
-# All Rights Reserved.
-#
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-
-import gettext
-
-gettext.install('quantum', unicode=1)
-
-from miniclient import MiniClient
-from quantum.common.wsgi import Serializer
-
-HOST = '127.0.0.1'
-PORT = 9696
-USE_SSL = False
-TENANT_ID = 'totore'
-
-test_network_data = \
-    {'network': {'network-name': 'test' }}
-
-def print_response(res):
-    content = res.read()
-    print "Status: %s" %res.status
-    print "Content: %s" %content
-    return content
-
-def test_list_networks_and_ports(format = 'xml'):
-    client = MiniClient(HOST, PORT, USE_SSL)
-    print "TEST LIST NETWORKS AND PORTS -- FORMAT:%s" %format 
-    print "----------------------------"
-    print "--> Step 1 - List All Networks"
-    res = client.do_request(TENANT_ID,'GET', "/networks." + format)
-    print_response(res)
-    print "--> Step 2 - Details for Network 001"
-    res = client.do_request(TENANT_ID,'GET', "/networks/001." + format)
-    print_response(res)
-    print "--> Step 3 - Ports for Network 001"
-    res = client.do_request(TENANT_ID,'GET', "/networks/001/ports." + format)
-    print_response(res)
-    print "--> Step 4 - Details for Port 1"
-    res = client.do_request(TENANT_ID,'GET', "/networks/001/ports/1." + format)
-    print_response(res)
-    print "COMPLETED"
-    print "----------------------------"
-    
-def test_create_network(format = 'xml'):
-    client = MiniClient(HOST, PORT, USE_SSL)
-    print "TEST CREATE NETWORK -- FORMAT:%s" %format 
-    print "----------------------------"
-    print "--> Step 1 - Create Network"
-    content_type = "application/" + format
-    body = Serializer().serialize(test_network_data, content_type)
-    res = client.do_request(TENANT_ID,'POST', "/networks." + format, body=body)
-    print_response(res)
-    print "--> Step 2 - List All Networks"
-    res = client.do_request(TENANT_ID,'GET', "/networks." + format)
-    print_response(res)
-    print "COMPLETED"
-    print "----------------------------"
-
-def test_rename_network(format = 'xml'):
-    client = MiniClient(HOST, PORT, USE_SSL)
-    content_type = "application/" + format    
-    print "TEST RENAME NETWORK -- FORMAT:%s" %format 
-    print "----------------------------"
-    print "--> Step 1 - Retrieve network"
-    res = client.do_request(TENANT_ID,'GET', "/networks/001." + format)
-    print_response(res)
-    print "--> Step 2 - Rename network to 'test_renamed'"
-    test_network_data['network']['network-name'] = 'test_renamed'
-    body = Serializer().serialize(test_network_data, content_type)
-    res = client.do_request(TENANT_ID,'PUT', "/networks/001." + format, body=body)
-    print_response(res)
-    print "--> Step 2 - Retrieve network (again)"
-    res = client.do_request(TENANT_ID,'GET', "/networks/001." + format)
-    print_response(res)
-    print "COMPLETED"
-    print "----------------------------"
-    
-def test_delete_network(format = 'xml'):
-    client = MiniClient(HOST, PORT, USE_SSL)
-    content_type = "application/" + format
-    print "TEST DELETE NETWORK -- FORMAT:%s" %format 
-    print "----------------------------"
-    print "--> Step 1 - List All Networks"
-    res = client.do_request(TENANT_ID,'GET', "/networks." + format)
-    content = print_response(res)
-    network_data = Serializer().deserialize(content, content_type)
-    print network_data
-    net_id = network_data['networks'][0]['id']
-    print "--> Step 2 - Delete network %s" %net_id    
-    res = client.do_request(TENANT_ID,'DELETE',
-                            "/networks/" + net_id + "." + format)
-    print_response(res)
-    print "--> Step 3 - List All Networks (Again)"
-    res = client.do_request(TENANT_ID,'GET', "/networks." + format)
-    print_response(res)
-    print "COMPLETED"
-    print "----------------------------"
-
-
-def test_create_port(format = 'xml'):
-    client = MiniClient(HOST, PORT, USE_SSL)
-    print "TEST CREATE PORT -- FORMAT:%s" %format 
-    print "----------------------------"
-    print "--> Step 1 - List Ports for network 001"
-    res = client.do_request(TENANT_ID,'GET', "/networks/001/ports." + format)
-    print_response(res)
-    print "--> Step 2 - Create Port for network 001"
-    res = client.do_request(TENANT_ID,'POST', "/networks/001/ports." + format)
-    print_response(res)
-    print "--> Step 3 - List Ports for network 001 (again)"
-    res = client.do_request(TENANT_ID,'GET', "/networks/001/ports." + format)
-    print_response(res)
-    print "COMPLETED"
-    print "----------------------------"
-
-
-def main():
-    test_list_networks_and_ports('xml')
-    test_list_networks_and_ports('json')
-    test_create_network('xml')
-    test_create_network('json')
-    test_rename_network('xml')
-    test_rename_network('json')
-    # NOTE: XML deserializer does not work properly 
-    # disabling XML test - this is NOT a server-side issue
-    #test_delete_network('xml')
-    test_delete_network('json')
-    test_create_port('xml')
-    test_create_port('json')
-    
-    pass
-    
-
-# Standard boilerplate to call the main() function.
-if __name__ == '__main__':
-    main()
\ No newline at end of file
similarity index 100%
rename from smoketests/__init__.py
rename to tests/__init__.py
diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py
new file mode 100644 (file)
index 0000000..5910e35
--- /dev/null
@@ -0,0 +1,32 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 OpenStack LLC.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+# See http://code.google.com/p/python-nose/issues/detail?id=373
+# The code below enables nosetests to work with i18n _() blocks
+import __builtin__
+import unittest
+setattr(__builtin__, '_', lambda x: x)
+
+
+class BaseTest(unittest.TestCase):
+
+    def setUp(self):
+        pass
+
+
+def setUp():
+    pass
diff --git a/tools/install_venv.py b/tools/install_venv.py
new file mode 100644 (file)
index 0000000..5c3ef37
--- /dev/null
@@ -0,0 +1,137 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Copyright 2010 OpenStack LLC.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""
+Installation script for Quantum's development virtualenv
+"""
+
+import os
+import subprocess
+import sys
+
+
+ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
+VENV = os.path.join(ROOT, '.quantum-venv')
+PIP_REQUIRES = os.path.join(ROOT, 'tools', 'pip-requires')
+
+
+def die(message, *args):
+    print >> sys.stderr, message % args
+    sys.exit(1)
+
+
+def run_command(cmd, redirect_output=True, check_exit_code=True):
+    """
+    Runs a command in an out-of-process shell, returning the
+    output of that command.  Working directory is ROOT.
+    """
+    if redirect_output:
+        stdout = subprocess.PIPE
+    else:
+        stdout = None
+
+    proc = subprocess.Popen(cmd, cwd=ROOT, stdout=stdout)
+    output = proc.communicate()[0]
+    if check_exit_code and proc.returncode != 0:
+        die('Command "%s" failed.\n%s', ' '.join(cmd), output)
+    return output
+
+
+HAS_EASY_INSTALL = bool(run_command(['which', 'easy_install'],
+                                    check_exit_code=False).strip())
+HAS_VIRTUALENV = bool(run_command(['which', 'virtualenv'],
+                                    check_exit_code=False).strip())
+
+
+def check_dependencies():
+    """Make sure virtualenv is in the path."""
+
+    if not HAS_VIRTUALENV:
+        print 'not found.'
+        # Try installing it via easy_install...
+        if HAS_EASY_INSTALL:
+            print 'Installing virtualenv via easy_install...',
+            if not run_command(['which', 'easy_install']):
+                die('ERROR: virtualenv not found.\n\n'
+                    'Quantum requires virtualenv, please install'
+                    ' it using your favorite package management tool')
+            print 'done.'
+    print 'done.'
+
+
+def create_virtualenv(venv=VENV):
+    """Creates the virtual environment and installs PIP only into the
+    virtual environment
+    """
+    print 'Creating venv...',
+    run_command(['virtualenv', '-q', '--no-site-packages', VENV])
+    print 'done.'
+    print 'Installing pip in virtualenv...',
+    if not run_command(['tools/with_venv.sh', 'easy_install', 'pip']).strip():
+        die("Failed to install pip.")
+    print 'done.'
+
+
+def install_dependencies(venv=VENV):
+    print 'Installing dependencies with pip (this can take a while)...'
+
+    # Install greenlet by hand - just listing it in the requires file does not
+    # get it in stalled in the right order
+    venv_tool = 'tools/with_venv.sh'
+    run_command([venv_tool, 'pip', 'install', '-E', venv, '-r', PIP_REQUIRES],
+                redirect_output=False)
+
+    # Tell the virtual env how to "import quantum"
+    pthfile = os.path.join(venv, "lib", "python2.6", "site-packages",
+                                 "quantum.pth")
+    f = open(pthfile, 'w')
+    f.write("%s\n" % ROOT)
+
+
+def print_help():
+    help = """
+ Quantum development environment setup is complete.
+
+ Quantum development uses virtualenv to track and manage Python dependencies
+ while in development and testing.
+
+ To activate the Quantum virtualenv for the extent of your current shell
+ session you can run:
+
+ $ source .quantum-venv/bin/activate
+
+ Or, if you prefer, you can run commands in the virtualenv on a case by case
+ basis by running:
+
+ $ tools/with_venv.sh <your command>
+
+ Also, make test will automatically use the virtualenv.
+    """
+    print help
+
+
+def main(argv):
+    check_dependencies()
+    create_virtualenv()
+    install_dependencies()
+    print_help()
+
+if __name__ == '__main__':
+    main(sys.argv)
diff --git a/tools/pip-requires b/tools/pip-requires
new file mode 100644 (file)
index 0000000..8dbfd85
--- /dev/null
@@ -0,0 +1,10 @@
+eventlet>=0.9.12
+nose
+Paste
+PasteDeploy
+pep8==0.5.0
+python-gflags
+routes
+simplejson
+webob
+webtest
diff --git a/tools/with_venv.sh b/tools/with_venv.sh
new file mode 100755 (executable)
index 0000000..8314946
--- /dev/null
@@ -0,0 +1,21 @@
+#!/bin/bash
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2011 OpenStack LLC.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+TOOLS=`dirname $0`
+VENV=$TOOLS/../.quantum-venv
+source $VENV/bin/activate && $@