From 1acbbb0c2d1fc152102635f0b7f66910a945b479 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Wed, 14 Dec 2011 10:14:08 -0500 Subject: [PATCH] Align run_tests.sh with nova. We need for run_tests.sh to accept the same inputs and outputs from a CI perspective. In this case, the easiest way was just to port in run_test.py. Additionally, we need with_venv.sh to exist. Change-Id: I25e659ed796c8a70239aab610f5b014b35443b67 --- run_tests.sh | 241 +++++++++++++++++++++++------------------ tools/install_venv.py | 243 ++++++++++++++++++++++++++++++++++++++++++ tools/pip-requires | 7 ++ tools/with_venv.sh | 4 + 4 files changed, 393 insertions(+), 102 deletions(-) create mode 100644 tools/install_venv.py create mode 100644 tools/pip-requires create mode 100755 tools/with_venv.sh diff --git a/run_tests.sh b/run_tests.sh index 755f7912c..94ff3a14a 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -1,116 +1,153 @@ #!/bin/bash -# -# Copyright 2011, Piston Cloud Computing, Inc. -# -# 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. + +set -eu function usage { - echo "Usage: $0 [OPTION] [nosearg1[=val]] [nosearg2[=val]]..." - echo - echo "Run python-novaclient test suite" - echo - echo " -f, --force Delete the virtualenv before running tests." - echo " -h, --help Print this usage message" - echo " -N, --no-virtual-env Don't use a virtualenv" - echo " -p, --pep8 Run pep8 in addition" + echo "Usage: $0 [OPTION]..." + echo "Run python-novaclient test suite" + 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 " -s, --no-site-packages Isolate the virtualenv from the global Python environment" + echo " -x, --stop Stop running tests after the first error or failure." + echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added." + echo " -p, --pep8 Just run pep8" + echo " -P, --no-pep8 Don't run pep8" + echo " -c, --coverage Generate coverage report" + echo " -h, --help Print this usage message" + echo " --hide-elapsed Don't print the elapsed time for each test along with slow test list" + 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 die { - echo $@ - exit 1 +function process_option { + case "$1" in + -h|--help) usage;; + -V|--virtual-env) always_venv=1; never_venv=0;; + -N|--no-virtual-env) always_venv=0; never_venv=1;; + -s|--no-site-packages) no_site_packages=1;; + -f|--force) force=1;; + -p|--pep8) just_pep8=1;; + -P|--no-pep8) no_pep8=1;; + -c|--coverage) coverage=1;; + -*) noseopts="$noseopts $1";; + *) noseargs="$noseargs $1" + esac } - -function process_args { - case "$1" in - -h|--help) usage && exit ;; - -p|--pep8) let just_pep8=1; let use_venv=0 ;; - -N|--no-virtual-env) let use_venv=0;; - -f|--force) let force=1;; - *) noseargs="$noseargs $1" - esac -} - - -function run-command { - res=$($@) - - if [ $? -ne 0 ]; then - die "Command failed:", $res - fi -} - - -function install-dependency { - echo -n "installing $@..." - run-command "pip install -E $venv $@" - echo done -} - - -function build-venv-if-necessary { - if [ $force -eq 1 ]; then - echo -n "Removing virtualenv..." - rm -rf $venv - echo done - fi - - if [ -d $venv ]; then - echo -n # nothing to be done - else - if [ -z $(which virtualenv) ]; then - echo "Installing virtualenv" - run-command "easy_install virtualenv" - fi - - echo -n "creating virtualenv..." - run-command "virtualenv -q --no-site-packages ${venv}" - echo done - - for dep in $dependencies; do - install-dependency $dep - done - fi -} - - -function wrapper { - if [ $use_venv -eq 1 ]; then - build-venv-if-necessary - source "$(dirname $0)/${venv}/bin/activate" && $@ - else - $@ - fi -} - - -dependencies="httplib2 argparse prettytable simplejson nose mock coverage" +venv=.venv +with_venv=tools/with_venv.sh +always_venv=0 +never_venv=0 force=0 -venv=.novaclient-venv -use_venv=1 -verbose=0 +no_site_packages=0 +installvenvopts= noseargs= +noseopts= +wrapper="" just_pep8=0 +no_pep8=0 +coverage=0 -for arg in "$@"; do - process_args $arg +for arg in "$@"; do + process_option $arg done -NOSETESTS="nosetests ${noseargs}" - -if [ $just_pep8 -ne 0 ]; then - wrapper "pep8 -r --show-pep8 novaclient tests" -else - wrapper $NOSETESTS +# If enabled, tell nose to collect coverage data +if [ $coverage -eq 1 ]; then + noseopts="$noseopts --with-coverage --cover-package=novaclient" +fi + +if [ $no_site_packages -eq 1 ]; then + installvenvopts="--no-site-packages" +fi + +function run_tests { + # Just run the test suites in current environment + ${wrapper} $NOSETESTS + # If we get some short import error right away, print the error log directly + RESULT=$? + return $RESULT +} + +function run_pep8 { + echo "Running pep8 ..." + srcfiles="novaclient tests" + # Just run PEP8 in current environment + # + # NOTE(sirp): W602 (deprecated 3-arg raise) is being ignored for the + # following reasons: + # + # 1. It's needed to preserve traceback information when re-raising + # exceptions; this is needed b/c Eventlet will clear exceptions when + # switching contexts. + # + # 2. There doesn't appear to be an alternative, "pep8-tool" compatible way of doing this + # in Python 2 (in Python 3 `with_traceback` could be used). + # + # 3. Can find no corroborating evidence that this is deprecated in Python 2 + # other than what the PEP8 tool claims. It is deprecated in Python 3, so, + # perhaps the mistake was thinking that the deprecation applied to Python 2 + # as well. + ${wrapper} pep8 --repeat --show-pep8 --show-source \ + --ignore=E202,W602 \ + ${srcfiles} +} + +NOSETESTS="nosetests $noseopts $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 $installvenvopts + 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 $installvenvopts + wrapper=${with_venv} + fi + fi + fi +fi + +# Delete old coverage data from previous runs +if [ $coverage -eq 1 ]; then + ${wrapper} coverage erase +fi + +if [ $just_pep8 -eq 1 ]; then + run_pep8 + exit +fi + +run_tests + +# NOTE(sirp): we only want to run pep8 when we're running the full-test suite, +# not when we're running tests individually. To handle this, we need to +# distinguish between options (noseopts), which begin with a '-', and +# arguments (noseargs). +if [ -z "$noseargs" ]; then + if [ $no_pep8 -eq 0 ]; then + run_pep8 + fi +fi + +if [ $coverage -eq 1 ]; then + echo "Generating coverage report in covhtml/" + ${wrapper} coverage html -d covhtml -i fi diff --git a/tools/install_venv.py b/tools/install_venv.py new file mode 100644 index 000000000..24f2ac02e --- /dev/null +++ b/tools/install_venv.py @@ -0,0 +1,243 @@ +# 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 Nova's development virtualenv +""" + +import optparse +import os +import subprocess +import sys +import platform + + +ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) +VENV = os.path.join(ROOT, '.venv') +PIP_REQUIRES = os.path.join(ROOT, 'tools', 'pip-requires') +PY_VERSION = "python%s.%s" % (sys.version_info[0], sys.version_info[1]) + + +def die(message, *args): + print >> sys.stderr, message % args + sys.exit(1) + + +def check_python_version(): + if sys.version_info < (2, 6): + die("Need Python Version >= 2.6") + + +def run_command_with_code(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, proc.returncode) + + +def run_command(cmd, redirect_output=True, check_exit_code=True): + return run_command_with_code(cmd, redirect_output, check_exit_code)[0] + + +class Distro(object): + + def check_cmd(self, cmd): + return bool(run_command(['which', cmd], check_exit_code=False).strip()) + + def install_virtualenv(self): + if self.check_cmd('virtualenv'): + return + + if self.check_cmd('easy_install'): + print 'Installing virtualenv via easy_install...', + if run_command(['easy_install', 'virtualenv']): + print 'Succeeded' + return + else: + print 'Failed' + + die('ERROR: virtualenv not found.\n\nDevelopment' + ' requires virtualenv, please install it using your' + ' favorite package management tool') + + def post_process(self): + """Any distribution-specific post-processing gets done here. + + In particular, this is useful for applying patches to code inside + the venv.""" + pass + + +class Debian(Distro): + """This covers all Debian-based distributions.""" + + def check_pkg(self, pkg): + return run_command_with_code(['dpkg', '-l', pkg], + check_exit_code=False)[1] == 0 + + def apt_install(self, pkg, **kwargs): + run_command(['sudo', 'apt-get', 'install', '-y', pkg], **kwargs) + + def apply_patch(self, originalfile, patchfile): + run_command(['patch', originalfile, patchfile]) + + def install_virtualenv(self): + if self.check_cmd('virtualenv'): + return + + if not self.check_pkg('python-virtualenv'): + self.apt_install('python-virtualenv', check_exit_code=False) + + super(Debian, self).install_virtualenv() + + +class Fedora(Distro): + """This covers all Fedora-based distributions. + + Includes: Fedora, RHEL, CentOS, Scientific Linux""" + + def check_pkg(self, pkg): + return run_command_with_code(['rpm', '-q', pkg], + check_exit_code=False)[1] == 0 + + def yum_install(self, pkg, **kwargs): + run_command(['sudo', 'yum', 'install', '-y', pkg], **kwargs) + + def apply_patch(self, originalfile, patchfile): + run_command(['patch', originalfile, patchfile]) + + def install_virtualenv(self): + if self.check_cmd('virtualenv'): + return + + if not self.check_pkg('python-virtualenv'): + self.yum_install('python-virtualenv', check_exit_code=False) + + super(Fedora, self).install_virtualenv() + + +def get_distro(): + if os.path.exists('/etc/fedora-release') or \ + os.path.exists('/etc/redhat-release'): + return Fedora() + elif os.path.exists('/etc/debian_version'): + return Debian() + else: + return Distro() + + +def check_dependencies(): + get_distro().install_virtualenv() + + +def create_virtualenv(venv=VENV, no_site_packages=True): + """Creates the virtual environment and installs PIP only into the + virtual environment + """ + print 'Creating venv...', + if no_site_packages: + run_command(['virtualenv', '-q', '--no-site-packages', VENV]) + else: + run_command(['virtualenv', '-q', 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 pip_install(*args): + run_command(['tools/with_venv.sh', + 'pip', 'install', '--upgrade'] + list(args), + redirect_output=False) + + +def install_dependencies(venv=VENV): + print 'Installing dependencies with pip (this can take a while)...' + + # First things first, make sure our venv has the latest pip and distribute. + pip_install('pip') + pip_install('distribute') + + pip_install('-r', PIP_REQUIRES) + + # Tell the virtual env how to "import nova" + pthfile = os.path.join(venv, "lib", PY_VERSION, "site-packages", + "novaclient.pth") + f = open(pthfile, 'w') + f.write("%s\n" % ROOT) + + +def post_process(): + get_distro().post_process() + + +def print_help(): + help = """ + python-novaclient development environment setup is complete. + + python-novaclient development uses virtualenv to track and manage Python + dependencies while in development and testing. + + To activate the python-novaclient virtualenv for the extent of your current + shell session you can run: + + $ source .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 parse_args(): + """Parse command-line arguments""" + parser = optparse.OptionParser() + parser.add_option("-n", "--no-site-packages", dest="no_site_packages", + default=False, action="store_true", + help="Do not inherit packages from global Python install") + return parser.parse_args() + + +def main(argv): + (options, args) = parse_args() + check_python_version() + check_dependencies() + create_virtualenv(no_site_packages=options.no_site_packages) + install_dependencies() + post_process() + print_help() + +if __name__ == '__main__': + main(sys.argv) diff --git a/tools/pip-requires b/tools/pip-requires new file mode 100644 index 000000000..475d0018f --- /dev/null +++ b/tools/pip-requires @@ -0,0 +1,7 @@ +argparse +coverage +httplib2 +mock +nose +prettytable +simplejson diff --git a/tools/with_venv.sh b/tools/with_venv.sh new file mode 100755 index 000000000..c8d2940fc --- /dev/null +++ b/tools/with_venv.sh @@ -0,0 +1,4 @@ +#!/bin/bash +TOOLS=`dirname $0` +VENV=$TOOLS/../.venv +source $VENV/bin/activate && $@