Merge pull request #1 from morganfainberg/import

Add support for tox unit testing
This commit is contained in:
Morgan Fainberg 2016-05-12 09:25:09 -07:00
commit a4fbd7263e
9 changed files with 169 additions and 68 deletions

4
.testr.conf Normal file
View File

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

View File

@ -35,12 +35,12 @@
# ***** END LICENSE BLOCK *****
""" LDAP Connection Pool.
"""
import time
from contextlib import contextmanager
from threading import RLock
import time
from ldap.ldapobject import ReconnectLDAPObject
import ldap
from ldap.ldapobject import ReconnectLDAPObject
class MaxConnectionReachedError(Exception):
@ -54,7 +54,8 @@ class BackendError(Exception):
class StateConnector(ReconnectLDAPObject):
"""Just remembers who is connected, and if connected"""
"""Just remembers who is connected, and if connected."""
def __init__(self, *args, **kw):
ReconnectLDAPObject.__init__(self, *args, **kw)
self.connected = False
@ -117,6 +118,7 @@ class ConnectionManager(object):
Provides a context manager for LDAP connectors.
"""
def __init__(self, uri, bind=None, passwd=None, size=10, retry_max=3,
retry_delay=.1, use_tls=False, timeout=-1,
connector_cls=StateConnector, use_pool=True,
@ -174,7 +176,7 @@ class ConnectionManager(object):
try:
self._bind(conn, bind, passwd)
return conn
except:
except Exception:
self._pool.remove(conn)
return None
@ -193,7 +195,7 @@ class ConnectionManager(object):
conn.active = True
def _create_connector(self, bind, passwd):
"""Creates a connector, binds it, and returns it
"""Creates a connector, binds it, and returns it.
Args:
- bind: login
@ -214,7 +216,7 @@ class ConnectionManager(object):
conn.timeout = self.timeout
self._bind(conn, bind, passwd)
connected = True
except ldap.LDAPError, exc:
except ldap.LDAPError as exc:
time.sleep(self.retry_delay)
tries += 1
@ -280,13 +282,12 @@ class ConnectionManager(object):
@contextmanager
def connection(self, bind=None, passwd=None):
"""Creates a context'ed connector, binds it, and returns it
"""Creates a context'ed connector, binds it, and returns it.
Args:
- bind: login
- passwd: password
"""
tries = 0
conn = None
while tries < self.retry_max:
@ -315,13 +316,12 @@ class ConnectionManager(object):
self._release_connection(conn)
def purge(self, bind, passwd=None):
"""Purge a connector
"""Purge a connector.
Args:
- bind: login
- passwd: password
"""
if self.use_pool:
return

View File

@ -34,10 +34,10 @@
#
# ***** END LICENSE BLOCK *****
import unittest
import ldap
from ldappool import (ConnectionManager, StateConnector, BackendError,
MaxConnectionReachedError)
import ldappool
def _bind(self, who='', cred='', **kw):
@ -58,17 +58,17 @@ def _bind_fails2(self, who='', cred='', **kw):
class TestLDAPConnection(unittest.TestCase):
def setUp(self):
self.old = StateConnector.simple_bind_s
StateConnector.simple_bind_s = _bind
self.old = ldappool.StateConnector.simple_bind_s
ldappool.StateConnector.simple_bind_s = _bind
def tearDown(self):
StateConnector.simple_bind_s = self.old
ldappool.StateConnector.simple_bind_s = self.old
def test_connection(self):
uri = ''
dn = 'uid=adminuser,ou=logins,dc=mozilla'
passwd = 'adminuser'
cm = ConnectionManager(uri, dn, passwd, use_pool=True, size=2)
cm = ldappool.ConnectionManager(uri, dn, passwd, use_pool=True, size=2)
self.assertEqual(len(cm), 0)
with cm.connection('dn', 'pass'):
@ -86,7 +86,7 @@ class TestLDAPConnection(unittest.TestCase):
try:
with cm.connection('dn', 'pass'):
pass
except MaxConnectionReachedError:
except ldappool.MaxConnectionReachedError:
pass
else:
raise AssertionError()
@ -122,18 +122,18 @@ class TestLDAPConnection(unittest.TestCase):
unbinds.append(1)
# the binding fails with an LDAPError
StateConnector.simple_bind_s = _bind_fails2
StateConnector.unbind_s = _unbind
ldappool.StateConnector.simple_bind_s = _bind_fails2
ldappool.StateConnector.unbind_s = _unbind
uri = ''
dn = 'uid=adminuser,ou=logins,dc=mozilla'
passwd = 'adminuser'
cm = ConnectionManager(uri, dn, passwd, use_pool=True, size=2)
cm = ldappool.ConnectionManager(uri, dn, passwd, use_pool=True, size=2)
self.assertEqual(len(cm), 0)
try:
with cm.connection('dn', 'pass'):
pass
except BackendError:
except ldappool.BackendError:
pass
else:
raise AssertionError()

View File

@ -33,29 +33,34 @@
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
import unittest
import threading
import time
import unittest
import ldap
from ldappool import (ConnectionManager, StateConnector,
MaxConnectionReachedError)
import ldappool
# patching StateConnector
StateConnector.users = {'uid=tarek,ou=users,dc=mozilla':
{'uidNumber': ['1'],
'account-enabled': ['Yes'],
'mail': ['tarek@mozilla.com'],
'cn': ['tarek']},
'cn=admin,dc=mozilla': {'cn': ['admin'],
'mail': ['admin'],
'uidNumber': ['100']}}
ldappool.StateConnector.users = {
'uid=tarek,ou=users,dc=mozilla':
{'uidNumber': ['1'],
'account-enabled': ['Yes'],
'mail': ['tarek@mozilla.com'],
'cn': ['tarek']},
'cn=admin,dc=mozilla': {'cn': ['admin'],
'mail': ['admin'],
'uidNumber': ['100']}}
def _simple_bind(self, who='', cred='', *args):
self.connected = True
self.who = who
self.cred = cred
StateConnector.simple_bind_s = _simple_bind
ldappool.StateConnector.simple_bind_s = _simple_bind
def _search(self, dn, *args, **kw):
if dn in self.users:
@ -69,7 +74,9 @@ def _search(self, dn, *args, **kw):
raise ldap.NO_SUCH_OBJECT
StateConnector.search_s = _search
ldappool.StateConnector.search_s = _search
def _add(self, dn, user):
self.users[dn] = {}
@ -80,7 +87,9 @@ def _add(self, dn, user):
return ldap.RES_ADD, ''
StateConnector.add_s = _add
ldappool.StateConnector.add_s = _add
def _modify(self, dn, user):
if dn in self.users:
@ -90,14 +99,17 @@ def _modify(self, dn, user):
self.users[dn][key] = value
return ldap.RES_MODIFY, ''
StateConnector.modify_s = _modify
ldappool.StateConnector.modify_s = _modify
def _delete(self, dn):
if dn in self.users:
del self.users[dn]
return ldap.RES_DELETE, ''
StateConnector.delete_s = _delete
ldappool.StateConnector.delete_s = _delete
class LDAPWorker(threading.Thread):
@ -119,13 +131,13 @@ class LDAPWorker(threading.Thread):
class TestLDAPSQLAuth(unittest.TestCase):
def test_ctor_args(self):
pool = ConnectionManager('ldap://localhost', use_tls=True)
pool = ldappool.ConnectionManager('ldap://localhost', use_tls=True)
self.assertEqual(pool.use_tls, True)
def test_pool(self):
dn = 'uid=adminuser,ou=logins,dc=mozilla'
passwd = 'adminuser'
pool = ConnectionManager('ldap://localhost', dn, passwd)
pool = ldappool.ConnectionManager('ldap://localhost', dn, passwd)
workers = [LDAPWorker(pool) for i in range(10)]
for worker in workers:
@ -133,16 +145,16 @@ class TestLDAPSQLAuth(unittest.TestCase):
for worker in workers:
worker.join()
self.assertEquals(len(worker.results), 10)
self.assertEqual(len(worker.results), 10)
cn = worker.results[0][0][1]['cn']
self.assertEquals(cn, ['admin'])
self.assertEqual(cn, ['admin'])
def test_pool_full(self):
dn = 'uid=adminuser,ou=logins,dc=mozilla'
passwd = 'adminuser'
pool = ConnectionManager('ldap://localhost', dn, passwd, size=1,
retry_delay=1., retry_max=5,
use_pool=True)
pool = ldappool.ConnectionManager(
'ldap://localhost', dn, passwd, size=1, retry_delay=1.,
retry_max=5, use_pool=True)
class Worker(threading.Thread):
@ -182,7 +194,7 @@ class TestLDAPSQLAuth(unittest.TestCase):
worker1 = Worker(pool, 1.1)
worker1.start()
try:
self.assertRaises(MaxConnectionReachedError, tryit)
self.assertRaises(ldappool.MaxConnectionReachedError, tryit)
finally:
worker1.join()
@ -192,8 +204,8 @@ class TestLDAPSQLAuth(unittest.TestCase):
def test_pool_cleanup(self):
dn = 'uid=adminuser,ou=logins,dc=mozilla'
passwd = 'adminuser'
pool = ConnectionManager('ldap://localhost', dn, passwd, size=1,
use_pool=True)
pool = ldappool.ConnectionManager('ldap://localhost', dn, passwd,
size=1, use_pool=True)
with pool.connection('bind1') as conn: # NOQA
pass
@ -207,8 +219,8 @@ class TestLDAPSQLAuth(unittest.TestCase):
def test_pool_reuse(self):
dn = 'uid=adminuser,ou=logins,dc=mozilla'
passwd = 'adminuser'
pool = ConnectionManager('ldap://localhost', dn, passwd,
use_pool=True)
pool = ldappool.ConnectionManager('ldap://localhost', dn, passwd,
use_pool=True)
with pool.connection() as conn:
self.assertTrue(conn.active)

1
requirements.txt Normal file
View File

@ -0,0 +1 @@
python-ldap>=2.4:python_version=='2.7' # PSF

36
setup.cfg Normal file
View File

@ -0,0 +1,36 @@
[metadata]
name = ldappool
summary = A simple connector pool for python-ldap.
description-file =
README.rst
author = OpenStack
home-page = https://git.openstack.org/cgit/openstack/ldappool
classifier =
Intended Audience :: Developers
License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
Operating System :: POSIX :: Linux
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
[files]
packages =
ldappool
[build_sphinx]
source-dir = doc/source
build-dir = doc/build
all_files = 1
[upload_sphinx]
upload-dir = doc/build/html
[pbr]
warnerrors = True
autodoc_tree_index_modules = True
autodoc_tree_excludes =
setup.py
ldappool/tests/
[wheel]
universal = 1

View File

@ -33,25 +33,16 @@
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
import setuptools
# In python < 2.7.4, a lazy loading of package `pbr` will break
# setuptools if some other modules registered functions in `atexit`.
# solution from: http://bugs.python.org/issue15881#msg170215
try:
from setuptools import setup
import multiprocessing # noqa
except ImportError:
from distutils.core import setup
pass
with open('README.rst') as f:
README = f.read()
with open('CHANGES.rst') as f:
CHANGES = f.read()
setup(name='ldappool', version='1.1',
packages=['ldappool', 'ldappool.tests'],
author='Mozilla Services', author_email='services-dev@mozilla.org',
description="A connection pool for python-ldap",
long_description=README + '\n' + CHANGES,
url='https://github.com/mozilla-services/ldappool',
keywords=['python-ldap', 'ldap', 'pool'],
license="MPL")
setuptools.setup(
setup_requires=['pbr>=1.8'],
pbr=True)

12
test-requirements.txt Normal file
View File

@ -0,0 +1,12 @@
# The order of packages is significant, because pip processes them in the order
# of appearance.
hacking<0.11,>=0.10.0
flake8-docstrings==0.2.1.post1
coverage>=3.6
fixtures>=1.3.1
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2
testrepository>=0.0.18
testresources>=0.2.4
testtools>=1.4.0

45
tox.ini Normal file
View File

@ -0,0 +1,45 @@
[tox]
minversion = 1.6
skipsdist = True
envlist = py27,pep8,cover,doc
[testenv]
usedevelop = True
install_command = pip install -U {opts} {packages}
setenv = VIRTUAL_ENV={envdir}
OS_STDOUT_NOCAPTURE=False
OS_STDERR_NOCAPTURE=False
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
commands = find . -type f -name "*.pyc" -delete
python setup.py testr --slowest --testr-args='{posargs}'
whitelist_externals = find
[testenv:pep8]
commands =
flake8
[testenv:venv]
commands = {posargs}
[testenv:cover]
commands = python setup.py testr --coverage --testr-args='{posargs}'
[flake8]
# D100: Missing docstring in public module
# D101: Missing docstring in public class
# D102: Missing docstring in public method
# D103: Missing docstring in public function
# D104: Missing docstring in public package
# D105: Missing docstring in magic method
# D200: One-line docstring should fit on one line with quotes
# D210: No whitespaces allowed surrounding docstring text
# D401: First line should be in imperative mood
ignore = D100,D101,D102,D104,D105,D200,D210,D401
show-source = True
exclude = .venv,.tox,dist,doc,*egg,build
[testenv:docs]
commands=
python setup.py build_sphinx