From 6350323b7f6ecbb54f70fd2e847190c99b826d94 Mon Sep 17 00:00:00 2001 From: Morgan Fainberg Date: Thu, 12 May 2016 08:36:14 -0700 Subject: [PATCH 1/3] Add support for tox unit testing Add support for tox unit testing prior to import into openstack's gerrit/CI system. --- .testr.conf | 4 ++++ requirements.txt | 1 + setup.cfg | 36 ++++++++++++++++++++++++++++++++++++ setup.py | 29 ++++++++++------------------- test_requirements.txt | 5 +++++ tox.ini | 41 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 97 insertions(+), 19 deletions(-) create mode 100644 .testr.conf create mode 100644 requirements.txt create mode 100644 setup.cfg create mode 100644 test_requirements.txt create mode 100644 tox.ini diff --git a/.testr.conf b/.testr.conf new file mode 100644 index 0000000..07b4075 --- /dev/null +++ b/.testr.conf @@ -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 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..02164f4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +python-ldap>=2.4:python_version=='2.7' # PSF \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..7ef13fe --- /dev/null +++ b/setup.cfg @@ -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 \ No newline at end of file diff --git a/setup.py b/setup.py index 0f2ff3d..89bddf4 100644 --- a/setup.py +++ b/setup.py @@ -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) \ No newline at end of file diff --git a/test_requirements.txt b/test_requirements.txt new file mode 100644 index 0000000..86a2f57 --- /dev/null +++ b/test_requirements.txt @@ -0,0 +1,5 @@ +# 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 \ No newline at end of file diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..830bd10 --- /dev/null +++ b/tox.ini @@ -0,0 +1,41 @@ +[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 +ignore = D100,D101,D102,D103,D104 +show-source = True +exclude = .venv,.tox,dist,doc,*egg,build + +[testenv:docs] +commands= + python setup.py build_sphinx \ No newline at end of file From ceca46e006170a837ee34dbaec69f41cc7d3f461 Mon Sep 17 00:00:00 2001 From: Morgan Fainberg Date: Thu, 12 May 2016 09:20:06 -0700 Subject: [PATCH 2/3] PEP8 fixes Fix pep8 errors, add appropriate flake8 exceptions. --- ldappool/__init__.py | 20 +++--- ldappool/tests/test_ldapconnection.py | 22 +++--- ldappool/tests/test_ldappool.py | 68 +++++++++++-------- setup.py | 2 +- ..._requirements.txt => test-requirements.txt | 0 tox.ini | 6 +- 6 files changed, 67 insertions(+), 51 deletions(-) rename test_requirements.txt => test-requirements.txt (100%) diff --git a/ldappool/__init__.py b/ldappool/__init__.py index d3dd86d..9cc8a9b 100644 --- a/ldappool/__init__.py +++ b/ldappool/__init__.py @@ -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 diff --git a/ldappool/tests/test_ldapconnection.py b/ldappool/tests/test_ldapconnection.py index 1706b04..317824b 100644 --- a/ldappool/tests/test_ldapconnection.py +++ b/ldappool/tests/test_ldapconnection.py @@ -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() diff --git a/ldappool/tests/test_ldappool.py b/ldappool/tests/test_ldappool.py index da1f187..ac3602a 100644 --- a/ldappool/tests/test_ldappool.py +++ b/ldappool/tests/test_ldappool.py @@ -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) diff --git a/setup.py b/setup.py index 89bddf4..51f3e01 100644 --- a/setup.py +++ b/setup.py @@ -45,4 +45,4 @@ except ImportError: setuptools.setup( setup_requires=['pbr>=1.8'], - pbr=True) \ No newline at end of file + pbr=True) diff --git a/test_requirements.txt b/test-requirements.txt similarity index 100% rename from test_requirements.txt rename to test-requirements.txt diff --git a/tox.ini b/tox.ini index 830bd10..34a0576 100644 --- a/tox.ini +++ b/tox.ini @@ -32,7 +32,11 @@ commands = python setup.py testr --coverage --testr-args='{posargs}' # D102: Missing docstring in public method # D103: Missing docstring in public function # D104: Missing docstring in public package -ignore = D100,D101,D102,D103,D104 +# 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 From a6495a7af03a90dd42441ff7194d252332c09645 Mon Sep 17 00:00:00 2001 From: Morgan Fainberg Date: Thu, 12 May 2016 09:24:14 -0700 Subject: [PATCH 3/3] Add test-requirements for py27 testing Add the requirements for py27 testing. --- test-requirements.txt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 86a2f57..101393b 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -2,4 +2,11 @@ # of appearance. hacking<0.11,>=0.10.0 -flake8-docstrings==0.2.1.post1 \ No newline at end of file +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 \ No newline at end of file