Merge "Use testr instead of nose."

This commit is contained in:
Jenkins
2013-05-17 21:16:16 +00:00
committed by Gerrit Code Review
10 changed files with 266 additions and 218 deletions

2
.gitignore vendored
View File

@@ -1,4 +1,6 @@
.coverage
.testrepository
subunit.log
.venv
*,cover
cover

4
.testr.conf Normal file
View File

@@ -0,0 +1,4 @@
[DEFAULT]
test_command=${PYTHON:-python} -m subunit.run discover -t ./ ./tests $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

14
HACKING
View File

@@ -81,3 +81,17 @@ Exceptions
When dealing with exceptions from underlying libraries, translate those
exceptions to an instance or subclass of ClientException.
=======
Testing
=======
python-keystoneclient uses testtools and testr for its unittest suite
and its test runner. Basic workflow around our use of tox and testr can
be found at http://wiki.openstack.org/testr. If you'd like to learn more
in depth:
https://testtools.readthedocs.org/
https://testrepository.readthedocs.org/
Happy hacking!

View File

@@ -1,67 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 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.
#
# Test support for middleware authentication
#
import os
import sys
ROOTDIR = os.path.abspath(os.curdir)
def rootdir(*p):
return os.path.join(ROOTDIR, *p)
class NoModule(object):
"""A mixin class to provide support for unloading/disabling modules."""
def __init__(self, *args, **kw):
super(NoModule, self).__init__(*args, **kw)
self._finders = []
self._cleared_modules = {}
def tearDown(self):
super(NoModule, self).tearDown()
for finder in self._finders:
sys.meta_path.remove(finder)
sys.modules.update(self._cleared_modules)
def clear_module(self, module):
cleared_modules = {}
for fullname in sys.modules.keys():
if fullname == module or fullname.startswith(module + '.'):
cleared_modules[fullname] = sys.modules.pop(fullname)
return cleared_modules
def disable_module(self, module):
"""Ensure ImportError for the specified module."""
# Clear 'module' references in sys.modules
self._cleared_modules.update(self.clear_module(module))
# Disallow further imports of 'module'
class NoModule(object):
def find_module(self, fullname, path):
if fullname == module or fullname.startswith(module + '.'):
raise ImportError
finder = NoModule()
self._finders.append(finder)
sys.meta_path.insert(0, finder)

View File

@@ -14,6 +14,7 @@ function usage {
echo " -p, --pep8 Just run pep8"
echo " -P, --no-pep8 Don't run pep8"
echo " -c, --coverage Generate coverage report"
echo " -d, --debug Run tests with testtools instead of testr. This allows you to use the debugger."
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 ""
@@ -33,8 +34,9 @@ function process_option {
-p|--pep8) just_pep8=1;;
-P|--no-pep8) no_pep8=1;;
-c|--coverage) coverage=1;;
-*) noseopts="$noseopts $1";;
*) noseargs="$noseargs $1"
-d|--debug) debug=1;;
-*) testropts="$testropts $1";;
*) testrargs="$testrargs $1"
esac
}
@@ -45,34 +47,86 @@ never_venv=0
force=0
no_site_packages=0
installvenvopts=
noseargs=
noseopts=
testrargs=
testropts=
wrapper=""
just_pep8=0
no_pep8=0
coverage=0
debug=0
LANG=en_US.UTF-8
LANGUAGE=en_US:en
LC_ALL=C
OS_STDOUT_NOCAPTURE=False
OS_STDERR_NOCAPTURE=False
for arg in "$@"; do
process_option $arg
done
# If enabled, tell nose to collect coverage data
if [ $coverage -eq 1 ]; then
noseopts="$noseopts --with-coverage --cover-package=keystoneclient"
fi
if [ $no_site_packages -eq 1 ]; then
installvenvopts="--no-site-packages"
fi
function init_testr {
if [ ! -d .testrepository ]; then
${wrapper} testr init
fi
}
function run_tests {
# Cleanup *.pyc
${wrapper} find . -type f -name "*.pyc" -delete
if [ $debug -eq 1 ]; then
if [ "$testropts" = "" ] && [ "$testrargs" = "" ]; then
# Default to running all tests if specific test is not
# provided.
testrargs="discover ./tests"
fi
${wrapper} python -m testtools.run $testropts $testrargs
# Short circuit because all of the testr and coverage stuff
# below does not make sense when running testtools.run for
# debugging purposes.
return $?
fi
if [ $coverage -eq 1 ]; then
TESTRTESTS="$TESTRTESTS --coverage"
else
TESTRTESTS="$TESTRTESTS"
fi
# Just run the test suites in current environment
${wrapper} $NOSETESTS
# If we get some short import error right away, print the error log directly
set +e
testrargs=`echo "$testrargs" | sed -e's/^\s*\(.*\)\s*$/\1/'`
TESTRTESTS="$TESTRTESTS --testr-args='$testropts $testrargs'"
echo "Running \`${wrapper} $TESTRTESTS\`"
bash -c "${wrapper} $TESTRTESTS"
RESULT=$?
set -e
copy_subunit_log
if [ $coverage -eq 1 ]; then
echo "Generating coverage report in covhtml/"
# Don't compute coverage for common code, which is tested elsewhere
${wrapper} coverage combine
${wrapper} coverage html --include='keystoneclient/*' --omit='keystoneclient/openstack/common/*' -d covhtml -i
fi
return $RESULT
}
function copy_subunit_log {
LOGNAME=`cat .testrepository/next-stream`
LOGNAME=$(($LOGNAME - 1))
LOGNAME=".testrepository/${LOGNAME}"
cp $LOGNAME subunit.log
}
function run_pep8 {
echo "Running pep8 ..."
srcfiles="keystoneclient tests"
@@ -85,7 +139,7 @@ function run_pep8 {
${srcfiles}
}
NOSETESTS="nosetests $noseopts $noseargs"
TESTRTESTS="python setup.py testr"
if [ $never_venv -eq 0 ]
then
@@ -123,19 +177,15 @@ if [ $just_pep8 -eq 1 ]; then
exit
fi
init_testr
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
# distinguish between options (testropts), which begin with a '-', and
# arguments (testrargs).
if [ -z "$testrargs" ]; 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

View File

@@ -1,10 +1,3 @@
[nosetests]
verbosity=2
detailed-errors=1
cover-package = keystoneclient
cover-erase = true
cover-inclusive = true
[build_sphinx]
source-dir = doc/source
build-dir = doc/build

View File

@@ -37,7 +37,6 @@ setuptools.setup(
cmdclass=setup.get_cmdclass(),
tests_require=tests_require,
test_suite="nose.collector",
entry_points={
'console_scripts': ['keystone = keystoneclient.shell:main']

View File

@@ -22,6 +22,7 @@ import sys
import tempfile
import testtools
import fixtures
import webob
from keystoneclient.common import cms
@@ -31,12 +32,13 @@ from keystoneclient.middleware import memcache_crypt
from keystoneclient.openstack.common import memorycache
from keystoneclient.openstack.common import jsonutils
from keystoneclient.openstack.common import timeutils
from keystoneclient.middleware import test
CERTDIR = test.rootdir('examples', 'pki', 'certs')
KEYDIR = test.rootdir('examples', 'pki', 'private')
CMSDIR = test.rootdir('examples', 'pki', 'cms')
ROOTDIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
CERTDIR = os.path.join(ROOTDIR, "examples/pki/certs")
KEYDIR = os.path.join(ROOTDIR, "examples/pki/private")
CMSDIR = os.path.join(ROOTDIR, "examples/pki/cms")
SIGNING_CERT = os.path.join(CERTDIR, 'signing_cert.pem')
SIGNING_KEY = os.path.join(KEYDIR, 'signing_key.pem')
CA = os.path.join(CERTDIR, 'ca.pem')
@@ -239,95 +241,96 @@ EXPECTED_V2_DEFAULT_ENV_RESPONSE = {
FAKE_RESPONSE_STACK = []
# @TODO(mordred) This should become a testresources resource attached to the
# class
# The data for these tests are signed using openssl and are stored in files
# in the signing subdirectory. In order to keep the values consistent between
# the tests and the signed documents, we read them in for use in the tests.
def setUpModule(self):
signing_path = CMSDIR
with open(os.path.join(signing_path, 'auth_token_scoped.pem')) as f:
self.SIGNED_TOKEN_SCOPED = cms.cms_to_token(f.read())
with open(os.path.join(signing_path, 'auth_token_unscoped.pem')) as f:
self.SIGNED_TOKEN_UNSCOPED = cms.cms_to_token(f.read())
with open(os.path.join(signing_path, 'auth_v3_token_scoped.pem')) as f:
self.SIGNED_v3_TOKEN_SCOPED = cms.cms_to_token(f.read())
with open(os.path.join(signing_path, 'auth_token_revoked.pem')) as f:
self.REVOKED_TOKEN = cms.cms_to_token(f.read())
self.REVOKED_TOKEN_HASH = utils.hash_signed_token(self.REVOKED_TOKEN)
with open(os.path.join(signing_path, 'auth_v3_token_revoked.pem')) as f:
self.REVOKED_v3_TOKEN = cms.cms_to_token(f.read())
self.REVOKED_v3_TOKEN_HASH = utils.hash_signed_token(self.REVOKED_v3_TOKEN)
with open(os.path.join(signing_path, 'revocation_list.json')) as f:
self.REVOCATION_LIST = jsonutils.loads(f.read())
with open(os.path.join(signing_path, 'revocation_list.pem')) as f:
self.VALID_SIGNED_REVOCATION_LIST = jsonutils.dumps(
{'signed': f.read()})
self.SIGNED_TOKEN_SCOPED_KEY = (
cms.cms_hash_token(self.SIGNED_TOKEN_SCOPED))
self.SIGNED_TOKEN_UNSCOPED_KEY = (
cms.cms_hash_token(self.SIGNED_TOKEN_UNSCOPED))
self.SIGNED_v3_TOKEN_SCOPED_KEY = (
cms.cms_hash_token(self.SIGNED_v3_TOKEN_SCOPED))
signing_path = CMSDIR
with open(os.path.join(signing_path, 'auth_token_scoped.pem')) as f:
SIGNED_TOKEN_SCOPED = cms.cms_to_token(f.read())
with open(os.path.join(signing_path, 'auth_token_unscoped.pem')) as f:
SIGNED_TOKEN_UNSCOPED = cms.cms_to_token(f.read())
with open(os.path.join(signing_path, 'auth_v3_token_scoped.pem')) as f:
SIGNED_v3_TOKEN_SCOPED = cms.cms_to_token(f.read())
with open(os.path.join(signing_path, 'auth_token_revoked.pem')) as f:
REVOKED_TOKEN = cms.cms_to_token(f.read())
REVOKED_TOKEN_HASH = utils.hash_signed_token(REVOKED_TOKEN)
with open(os.path.join(signing_path, 'auth_v3_token_revoked.pem')) as f:
REVOKED_v3_TOKEN = cms.cms_to_token(f.read())
REVOKED_v3_TOKEN_HASH = utils.hash_signed_token(REVOKED_v3_TOKEN)
with open(os.path.join(signing_path, 'revocation_list.json')) as f:
REVOCATION_LIST = jsonutils.loads(f.read())
with open(os.path.join(signing_path, 'revocation_list.pem')) as f:
VALID_SIGNED_REVOCATION_LIST = jsonutils.dumps(
{'signed': f.read()})
SIGNED_TOKEN_SCOPED_KEY =\
cms.cms_hash_token(SIGNED_TOKEN_SCOPED)
SIGNED_TOKEN_UNSCOPED_KEY =\
cms.cms_hash_token(SIGNED_TOKEN_UNSCOPED)
SIGNED_v3_TOKEN_SCOPED_KEY = (
cms.cms_hash_token(SIGNED_v3_TOKEN_SCOPED))
self.TOKEN_RESPONSES[self.SIGNED_TOKEN_SCOPED_KEY] = {
'access': {
'token': {
'id': self.SIGNED_TOKEN_SCOPED_KEY,
},
'user': {
'id': 'user_id1',
'name': 'user_name1',
'tenantId': 'tenant_id1',
'tenantName': 'tenant_name1',
'roles': [
{'name': 'role1'},
{'name': 'role2'},
],
},
TOKEN_RESPONSES[SIGNED_TOKEN_SCOPED_KEY] = {
'access': {
'token': {
'id': SIGNED_TOKEN_SCOPED_KEY,
},
}
self.TOKEN_RESPONSES[SIGNED_TOKEN_UNSCOPED_KEY] = {
'access': {
'token': {
'id': SIGNED_TOKEN_UNSCOPED_KEY,
},
'user': {
'id': 'user_id1',
'name': 'user_name1',
'roles': [
{'name': 'role1'},
{'name': 'role2'},
],
},
'user': {
'id': 'user_id1',
'name': 'user_name1',
'tenantId': 'tenant_id1',
'tenantName': 'tenant_name1',
'roles': [
{'name': 'role1'},
{'name': 'role2'},
],
},
},
}
self.TOKEN_RESPONSES[self.SIGNED_v3_TOKEN_SCOPED_KEY] = {
TOKEN_RESPONSES[SIGNED_TOKEN_UNSCOPED_KEY] = {
'access': {
'token': {
'expires': '2020-01-01T00:00:10.000123Z',
'user': {
'id': 'user_id1',
'name': 'user_name1',
'domain': {
'id': 'domain_id1',
'name': 'domain_name1'
}
},
'project': {
'id': 'tenant_id1',
'name': 'tenant_name1',
'domain': {
'id': 'domain_id1',
'name': 'domain_name1'
}
},
'id': SIGNED_TOKEN_UNSCOPED_KEY,
},
'user': {
'id': 'user_id1',
'name': 'user_name1',
'roles': [
{'name': 'role1'},
{'name': 'role2'}
{'name': 'role1'},
{'name': 'role2'},
],
'catalog': {}
}
},
},
}
TOKEN_RESPONSES[SIGNED_v3_TOKEN_SCOPED_KEY] = {
'token': {
'expires': '2020-01-01T00:00:10.000123Z',
'user': {
'id': 'user_id1',
'name': 'user_name1',
'domain': {
'id': 'domain_id1',
'name': 'domain_name1'
}
},
'project': {
'id': 'tenant_id1',
'name': 'tenant_name1',
'domain': {
'id': 'domain_id1',
'name': 'domain_name1'
}
},
'roles': [
{'name': 'role1'},
{'name': 'role2'}
],
'catalog': {}
}
}
VERSION_LIST_v3 = {
"versions": {
@@ -362,6 +365,53 @@ VERSION_LIST_v2 = {
}
class NoModuleFinder(object):
""" Disallow further imports of 'module' """
def __init__(self, module):
self.module = module
def find_module(self, fullname, path):
if fullname == self.module or fullname.startswith(self.module + '.'):
raise ImportError
class DisableModuleFixture(fixtures.Fixture):
"""A fixture to provide support for unloading/disabling modules."""
def __init__(self, module, *args, **kw):
super(DisableModuleFixture, self).__init__(*args, **kw)
self.module = module
self._finders = []
self._cleared_modules = {}
def tearDown(self):
super(DisableModuleFixture, self).tearDown()
for finder in self._finders:
sys.meta_path.remove(finder)
sys.modules.update(self._cleared_modules)
def clear_module(self):
cleared_modules = {}
for fullname in sys.modules.keys():
if (fullname == self.module or
fullname.startswith(self.module + '.')):
cleared_modules[fullname] = sys.modules.pop(fullname)
return cleared_modules
def setUp(self):
"""Ensure ImportError for the specified module."""
super(DisableModuleFixture, self).setUp()
# Clear 'module' references in sys.modules
self._cleared_modules.update(self.clear_module())
finder = NoModuleFinder(self.module)
self._finders.append(finder)
sys.meta_path.insert(0, finder)
class FakeSwiftMemcacheRing(memorycache.Client):
# NOTE(vish): swift memcache uses param timeout instead of time
def set(self, key, value, timeout=0, min_compress_len=0):
@@ -741,7 +791,36 @@ class DiabloAuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest):
self.assertTrue('keystone.token_info' in req.environ)
class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest):
class NoMemcacheAuthToken(BaseAuthTokenMiddlewareTest):
def setUp(self):
super(NoMemcacheAuthToken, self).setUp()
self.useFixture(DisableModuleFixture('memcache'))
def test_nomemcache(self):
conf = {
'admin_token': 'admin_token1',
'auth_host': 'keystone.example.com',
'auth_port': 1234,
'memcache_servers': 'localhost:11211',
}
auth_token.AuthProtocol(FakeApp(), conf)
def test_not_use_cache_from_env(self):
env = {'swift.cache': 'CACHE_TEST'}
conf = {
'auth_host': 'keystone.example.com',
'auth_port': 1234,
'auth_admin_prefix': '/testadmin',
'memcache_servers': 'localhost:11211'
}
self.set_middleware(conf=conf)
self.middleware._init_cache(env)
self.assertNotEqual(self.middleware._cache, 'CACHE_TEST')
class AuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest):
def test_init_does_not_call_http(self):
conf = {
@@ -975,17 +1054,6 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest):
self.middleware._cache_initialized = True
self.test_memcache_set_expired()
def test_nomemcache(self):
self.disable_module('memcache')
conf = {
'auth_host': 'keystone.example.com',
'auth_port': 1234,
'auth_admin_prefix': '/testadmin',
'memcache_servers': 'localhost:11211'
}
self.set_middleware(conf=conf)
def test_use_cache_from_env(self):
env = {'swift.cache': 'CACHE_TEST'}
conf = {
@@ -999,18 +1067,6 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest):
self.middleware._init_cache(env)
self.assertEqual(self.middleware._cache, 'CACHE_TEST')
def test_not_use_cache_from_env(self):
env = {'swift.cache': 'CACHE_TEST'}
conf = {
'auth_host': 'keystone.example.com',
'auth_port': 1234,
'auth_admin_prefix': '/testadmin',
'memcache_servers': 'localhost:11211'
}
self.set_middleware(conf=conf)
self.middleware._init_cache(env)
self.assertNotEqual(self.middleware._cache, 'CACHE_TEST')
def test_will_expire_soon(self):
tenseconds = datetime.datetime.utcnow() + datetime.timedelta(
seconds=10)
@@ -1176,7 +1232,7 @@ class AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest):
datetime.timedelta(seconds=24))
class v2AuthTokenMiddlewareTest(test.NoModule, BaseAuthTokenMiddlewareTest):
class v2AuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest):
""" v2 token specific tests.
There are some differences between how the auth-token middleware handles

View File

@@ -5,13 +5,10 @@ fixtures
keyring
mock
mox
nose
nose-exclude
openstack.nose_plugin
nosehtmloutput
pep8==1.3.3
pycrypto
sphinx>=1.1.2
testrepository>=0.0.13
testtools>=0.9.22
WebOb>=1.0.8

26
tox.ini
View File

@@ -3,24 +3,24 @@ envlist = py26,py27,pep8
[testenv]
setenv = VIRTUAL_ENV={envdir}
NOSE_WITH_OPENSTACK=1
NOSE_OPENSTACK_COLOR=1
NOSE_OPENSTACK_RED=0.05
NOSE_OPENSTACK_YELLOW=0.025
NOSE_OPENSTACK_SHOW_ELAPSED=1
NOSE_OPENSTACK_STDOUT=1
LANG=en_US.UTF-8
LANGUAGE=en_US:en
LC_ALL=C
OS_STDOUT_NOCAPTURE=False
OS_STDERR_NOCAPTURE=False
deps = -r{toxinidir}/tools/pip-requires
-r{toxinidir}/tools/test-requires
commands = nosetests {posargs}
[tox:jenkins]
downloadcache = ~/cache/pip
commands = python setup.py testr --testr-args='{posargs}'
[testenv:pep8]
commands = pep8 --repeat --show-source --ignore=E711,E712,E125,E126 --exclude=.venv,.tox,dist,doc .
[testenv:cover]
setenv = NOSE_WITH_COVERAGE=1
[testenv:venv]
commands = {posargs}
[testenv:cover]
commands = python setup.py testr --coverage --testr-args='{posargs}'
[tox:jenkins]
downloadcache = ~/cache/pip