First attempt at adding devstack2 tests.
This commit is contained in:
parent
d78e709ee9
commit
0ac71edcf4
92
run_tests.py
Executable file
92
run_tests.py
Executable file
@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
To run all tests
|
||||
python run_tests.py
|
||||
|
||||
To run a single test:
|
||||
python run_tests.py
|
||||
functional.test_extensions:TestExtensions.test_extensions_json
|
||||
|
||||
To run a single test module:
|
||||
python run_tests.py functional.test_extensions
|
||||
|
||||
"""
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
TESTS = [
|
||||
]
|
||||
|
||||
|
||||
def parse_suite_filter():
|
||||
""" Parses out -O or --only argument and returns the value after it as the
|
||||
filter. Removes it from sys.argv in the process. """
|
||||
|
||||
filter = None
|
||||
if '-O' in sys.argv or '--only' in sys.argv:
|
||||
for i in range(len(sys.argv)):
|
||||
if sys.argv[i] in ['-O', '--only']:
|
||||
if len(sys.argv) > i + 1:
|
||||
# Remove -O/--only settings from sys.argv
|
||||
sys.argv.pop(i)
|
||||
filter = sys.argv.pop(i)
|
||||
break
|
||||
return filter
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
filter = parse_suite_filter()
|
||||
if filter:
|
||||
TESTS = [t for t in TESTS if filter in str(t)]
|
||||
if not TESTS:
|
||||
print 'No test configuration by the name %s found' % filter
|
||||
sys.exit(2)
|
||||
#Run test suites
|
||||
if len(TESTS) > 1:
|
||||
directory = os.getcwd()
|
||||
for test_num, test_cls in enumerate(TESTS):
|
||||
try:
|
||||
result = test_cls().run()
|
||||
if result:
|
||||
logger.error("Run returned %s for test %s. Exiting" %
|
||||
(result, test_cls.__name__))
|
||||
sys.exit(result)
|
||||
except Exception, e:
|
||||
print "Error:", e
|
||||
logger.exception(e)
|
||||
sys.exit(1)
|
||||
# Collect coverage from each run. They'll be combined later in .sh
|
||||
if '--with-coverage' in sys.argv:
|
||||
coverage_file = os.path.join(directory, ".coverage")
|
||||
target_file = "%s.%s" % (coverage_file, test_cls.__name__)
|
||||
try:
|
||||
if os.path.exists(target_file):
|
||||
logger.info("deleting %s" % target_file)
|
||||
os.unlink(target_file)
|
||||
if os.path.exists(coverage_file):
|
||||
logger.info("Saving %s to %s" % (coverage_file,
|
||||
target_file))
|
||||
os.rename(coverage_file, target_file)
|
||||
except Exception, e:
|
||||
logger.exception(e)
|
||||
print ("Failed to move coverage file while running test"
|
||||
": %s. Error reported was: %s" %
|
||||
(test_cls.__name__, e))
|
||||
sys.exit(1)
|
||||
else:
|
||||
for test_num, test_cls in enumerate(TESTS):
|
||||
try:
|
||||
result = test_cls().run()
|
||||
if result:
|
||||
logger.error("Run returned %s for test %s. Exiting" %
|
||||
(result, test_cls.__name__))
|
||||
sys.exit(result)
|
||||
except Exception, e:
|
||||
print "Error:", e
|
||||
logger.exception(e)
|
||||
sys.exit(1)
|
193
run_tests.sh
Executable file
193
run_tests.sh
Executable file
@ -0,0 +1,193 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
|
||||
function usage {
|
||||
echo "Usage: $0 [OPTION]..."
|
||||
echo "Run Devstacks's test suite(s)"
|
||||
echo ""
|
||||
echo " -O, --only test_suite Only run the specified test suite. Valid values are:"
|
||||
echo " Note: by default, run_tests will run all suites"
|
||||
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 " -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 " Note: you might need to 'sudo' this since it pip installs into the vitual environment"
|
||||
echo " -P, --skip-pep8 Just run tests; skip pep8 check"
|
||||
echo " -p, --pep8 Just run pep8"
|
||||
echo " -l, --pylint Just run pylint"
|
||||
echo " -j, --json Just validate JSON"
|
||||
echo " -c, --with-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 " --verbose Print additional logging"
|
||||
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."
|
||||
echo ""
|
||||
echo "Note: with no options specified, the script will run the pep8 check after completing the tests."
|
||||
echo " If you prefer not to run pep8, simply pass the -P option."
|
||||
exit
|
||||
}
|
||||
|
||||
only_run_flag=0
|
||||
only_run=""
|
||||
function process_option {
|
||||
if [ $only_run_flag -eq 1 ]; then
|
||||
only_run_flag=0
|
||||
only_run=$1
|
||||
return
|
||||
else
|
||||
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;;
|
||||
-O|--only) only_run_flag=1;;
|
||||
-f|--force) force=1;;
|
||||
-P|--skip-pep8) skip_pep8=1;;
|
||||
-p|--pep8) just_pep8=1;;
|
||||
-l|--pylint) just_pylint=1;;
|
||||
-j|--json) just_json=1;;
|
||||
-c|--with-coverage) coverage=1;;
|
||||
-*) addlopts="$addlopts $1";;
|
||||
*) addlargs="$addlargs $1"
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
venv=.venv
|
||||
with_venv=tools/with_venv.sh
|
||||
always_venv=0
|
||||
never_venv=0
|
||||
force=0
|
||||
addlargs=
|
||||
addlopts=
|
||||
wrapper=""
|
||||
just_pep8=0
|
||||
skip_pep8=0
|
||||
just_pylint=0
|
||||
just_json=0
|
||||
coverage=0
|
||||
|
||||
for arg in "$@"; do
|
||||
process_option $arg
|
||||
done
|
||||
|
||||
# If enabled, tell nose/unittest to collect coverage data
|
||||
if [ $coverage -eq 1 ]; then
|
||||
addlopts="$addlopts --with-coverage --cover-package=devstack"
|
||||
fi
|
||||
|
||||
if [ "x$only_run" = "x" ]; then
|
||||
RUNTESTS="python run_tests.py$addlopts$addlargs"
|
||||
else
|
||||
RUNTESTS="python run_tests.py$addlopts$addlargs -O $only_run"
|
||||
fi
|
||||
|
||||
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
|
||||
|
||||
function run_tests {
|
||||
# Just run the test suites in current environment
|
||||
${wrapper} $RUNTESTS 2> run_tests.log
|
||||
# If we get some short import error right away, print the error log directly
|
||||
RESULT=$?
|
||||
if [ "$RESULT" -ne "0" ];
|
||||
then
|
||||
ERRSIZE=`wc -l run_tests.log | awk '{print \$1}'`
|
||||
if [ "$ERRSIZE" -lt "40" ];
|
||||
then
|
||||
cat run_tests.log
|
||||
fi
|
||||
fi
|
||||
return $RESULT
|
||||
}
|
||||
|
||||
function run_pep8 {
|
||||
echo "Running pep8 ..."
|
||||
# Opt-out files from pep8
|
||||
ignore_scripts="*.sh"
|
||||
ignore_files="*pip-requires,*.log"
|
||||
ignore_dirs="*tools*"
|
||||
GLOBIGNORE="$ignore_scripts,$ignore_files,$ignore_dirs"
|
||||
srcfiles=`find bin -type f -not -name "*.log" -not -name "*.db"`
|
||||
srcfiles+=" devstack run_tests.py"
|
||||
# Just run PEP8 in current environment
|
||||
${wrapper} pep8 --repeat --show-pep8 --show-source \
|
||||
--exclude=$GLOBIGNORE ${srcfiles}
|
||||
}
|
||||
|
||||
function run_pylint {
|
||||
echo "Running pylint ..."
|
||||
PYLINT_OPTIONS="--rcfile=pylintrc --output-format=parseable"
|
||||
PYLINT_INCLUDE="devstack"
|
||||
echo "Pylint messages count: "
|
||||
pylint $PYLINT_OPTIONS $PYLINT_INCLUDE | grep 'keystone/' | wc -l
|
||||
echo "Run 'pylint $PYLINT_OPTIONS $PYLINT_INCLUDE' for a full report."
|
||||
}
|
||||
|
||||
function validate_json {
|
||||
echo "Validating JSON..."
|
||||
python tools/validate_json.py
|
||||
}
|
||||
|
||||
|
||||
# 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
|
||||
|
||||
if [ $just_pylint -eq 1 ]; then
|
||||
run_pylint
|
||||
exit
|
||||
fi
|
||||
|
||||
if [ $just_json -eq 1 ]; then
|
||||
validate_json
|
||||
exit
|
||||
fi
|
||||
|
||||
|
||||
run_tests
|
||||
if [ $skip_pep8 -eq 0 ]; then
|
||||
# Run the pep8 check
|
||||
run_pep8
|
||||
fi
|
||||
|
||||
# Since we run multiple test suites, we need to execute 'coverage combine'
|
||||
if [ $coverage -eq 1 ]; then
|
||||
echo "Generating coverage report in covhtml/"
|
||||
${wrapper} coverage combine
|
||||
${wrapper} coverage html -d covhtml -i
|
||||
${wrapper} coverage report --omit='/usr*,keystone/test*,.,setup.py,*egg*,/Library*,*.xml,*.tpl'
|
||||
fi
|
||||
|
142
tools/install_venv.py
Normal file
142
tools/install_venv.py
Normal file
@ -0,0 +1,142 @@
|
||||
# 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 Keystone'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, '.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'
|
||||
'Keystone development 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>1.0']).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 keystone"
|
||||
|
||||
for version in ['python2.7', 'python2.6']:
|
||||
pth = os.path.join(venv, "lib", version, "site-packages")
|
||||
if os.path.exists(pth):
|
||||
pthfile = os.path.join(pth, "keystone.pth")
|
||||
f = open(pthfile, 'w')
|
||||
f.write("%s\n" % ROOT)
|
||||
|
||||
|
||||
def print_help():
|
||||
help = """
|
||||
Keystone development environment setup is complete.
|
||||
|
||||
Keystone development uses virtualenv to track and manage Python dependencies
|
||||
while in development and testing.
|
||||
|
||||
To activate the Keystone 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 <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)
|
14
tools/pip-requires
Normal file
14
tools/pip-requires
Normal file
@ -0,0 +1,14 @@
|
||||
# NOTE: You may need to install additional binary packages prior to using 'pip install'
|
||||
# See the Contributor Documentation for more information
|
||||
|
||||
# Development
|
||||
netifaces
|
||||
termcolor
|
||||
|
||||
# Testing
|
||||
nose # for test discovery and console feedback
|
||||
unittest2 # backport of unittest lib in python 2.7
|
||||
pylint # static code analysis
|
||||
pep8 # checks for PEP8 code style compliance
|
||||
mox # mock object framework
|
||||
coverage # computes code coverage percentages
|
118
tools/validate_json.py
Normal file
118
tools/validate_json.py
Normal file
@ -0,0 +1,118 @@
|
||||
"""
|
||||
Searches the given path for JSON files, and validates their contents.
|
||||
|
||||
Optionally, replaces valid JSON files with their pretty-printed
|
||||
counterparts.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import collections
|
||||
import errno
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(format='%(levelname)s: %(message)s')
|
||||
|
||||
# Configure commandlineability
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('-p', type=str, required=False, default='.',
|
||||
help='the path to search for JSON files', dest='path')
|
||||
parser.add_argument('-r', type=str, required=False, default='.json$',
|
||||
help='the regular expression to match filenames against ' \
|
||||
'(not absolute paths)', dest='regexp')
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
files = find_matching_files(args.path, args.regexp)
|
||||
|
||||
results = True
|
||||
for path in files:
|
||||
results &= validate_json(path)
|
||||
|
||||
# Invert our test results to produce a status code
|
||||
exit(not results)
|
||||
|
||||
|
||||
def validate_json(path):
|
||||
"""Open a file and validate it's contents as JSON"""
|
||||
try:
|
||||
contents = read_file(path)
|
||||
|
||||
if contents is False:
|
||||
logging.warning('Insufficient permissions to open: %s' % path)
|
||||
return False
|
||||
except:
|
||||
logging.warning('Unable to open: %s' % path)
|
||||
return False
|
||||
|
||||
#knock off comments
|
||||
ncontents = list()
|
||||
for line in contents.splitlines():
|
||||
tmp_line = line.strip()
|
||||
if tmp_line.startswith("#"):
|
||||
continue
|
||||
else:
|
||||
ncontents.append(line)
|
||||
|
||||
contents = os.linesep.join(ncontents)
|
||||
try:
|
||||
ordered_dict = json.loads(contents,
|
||||
object_pairs_hook=collections.OrderedDict)
|
||||
except:
|
||||
logging.error('Unable to parse: %s' % path)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def find_matching_files(path, pattern):
|
||||
"""Search the given path for files matching the given pattern"""
|
||||
|
||||
regex = re.compile(pattern)
|
||||
|
||||
json_files = []
|
||||
for root, dirs, files in os.walk(path):
|
||||
for name in files:
|
||||
if regex.search(name):
|
||||
full_name = os.path.join(root, name)
|
||||
json_files.append(full_name)
|
||||
return json_files
|
||||
|
||||
|
||||
def read_file(path):
|
||||
"""Attempt to read a file safely
|
||||
|
||||
Returns the contents of the file as a string on success, False otherwise"""
|
||||
try:
|
||||
fp = open(path)
|
||||
except IOError as e:
|
||||
if e.errno == errno.EACCES:
|
||||
# permission error
|
||||
return False
|
||||
raise
|
||||
else:
|
||||
with fp:
|
||||
return fp.read()
|
||||
|
||||
|
||||
def replace_file(path, new_contents):
|
||||
"""Overwrite the file at the given path with the new contents
|
||||
|
||||
Returns True on success, False otherwise."""
|
||||
try:
|
||||
f = open(path, 'w')
|
||||
f.write(new_contents)
|
||||
f.close()
|
||||
except:
|
||||
logging.error('Unable to write: %s' % f)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
4
tools/with_venv.sh
Executable file
4
tools/with_venv.sh
Executable file
@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
TOOLS=`dirname $0`
|
||||
VENV=$TOOLS/../.venv
|
||||
source $VENV/bin/activate && $@
|
Loading…
Reference in New Issue
Block a user