diff --git a/run_tests.py b/run_tests.py new file mode 100644 index 0000000000..acc8260b81 --- /dev/null +++ b/run_tests.py @@ -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 index 0000000000..aa72cbe22d --- /dev/null +++ b/run_tests.sh @@ -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 index fb1ebc8fec..0000000000 --- a/test_scripts/miniclient.py +++ /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 index 589d9da224..0000000000 --- a/test_scripts/tests.py +++ /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 diff --git a/smoketests/__init__.py b/tests/__init__.py similarity index 100% rename from smoketests/__init__.py rename to tests/__init__.py diff --git a/test_scripts/__init__.py b/tests/functional/__init__.py similarity index 100% rename from test_scripts/__init__.py rename to tests/functional/__init__.py diff --git a/smoketests/miniclient.py b/tests/functional/miniclient.py similarity index 100% rename from smoketests/miniclient.py rename to tests/functional/miniclient.py diff --git a/smoketests/tests.py b/tests/functional/test_service.py similarity index 100% rename from smoketests/tests.py rename to tests/functional/test_service.py diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 0000000000..5910e35494 --- /dev/null +++ b/tests/unit/__init__.py @@ -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 index 0000000000..5c3ef374b6 --- /dev/null +++ b/tools/install_venv.py @@ -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 + + 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 index 0000000000..8dbfd85d31 --- /dev/null +++ b/tools/pip-requires @@ -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 index 0000000000..83149462c1 --- /dev/null +++ b/tools/with_venv.sh @@ -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 && $@