diff --git a/Lib/slapdtest/__init__.py b/Lib/slapdtest/__init__.py index a2a875f..306eaf7 100644 --- a/Lib/slapdtest/__init__.py +++ b/Lib/slapdtest/__init__.py @@ -8,4 +8,5 @@ __version__ = '3.0.0b2' from slapdtest._slapdtest import SlapdObject, SlapdTestCase, SysLogHandler -from slapdtest._slapdtest import skip_unless_ci, requires_sasl, requires_tls +from slapdtest._slapdtest import requires_ldapi, requires_sasl, requires_tls +from slapdtest._slapdtest import skip_unless_ci diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index 4c7a9e4..09ec154 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -56,6 +56,12 @@ LOCALHOST = '127.0.0.1' +CI_DISABLED = set(os.environ.get('CI_DISABLED', '').split(':')) +if 'LDAPI' in CI_DISABLED: + HAVE_LDAPI = False +else: + HAVE_LDAPI = hasattr(socket, 'AF_UNIX') + def identity(test_item): """Identity decorator @@ -69,7 +75,7 @@ def skip_unless_ci(reason, feature=None): """ if not os.environ.get('CI', False): return unittest.skip(reason) - elif feature in os.environ.get('CI_DISABLED', '').split(':'): + elif feature in CI_DISABLED: return unittest.skip(reason) else: # Don't skip on Travis @@ -95,6 +101,14 @@ def requires_sasl(): return identity +def requires_ldapi(): + if not HAVE_LDAPI: + return skip_unless_ci( + "test needs ldapi support (AF_UNIX)", feature='LDAPI') + else: + return identity + + def combined_logger( log_name, log_level=logging.WARN, @@ -149,8 +163,6 @@ class SlapdObject(object): root_dn = 'cn=%s,%s' % (root_cn, suffix) root_pw = 'password' slapd_loglevel = 'stats stats2' - # use SASL/EXTERNAL via LDAPI when invoking OpenLDAP CLI tools - cli_sasl_external = True local_host = '127.0.0.1' testrunsubdirs = ( 'schema', @@ -192,8 +204,17 @@ def __init__(self): self._slapd_conf = os.path.join(self.testrundir, 'slapd.conf') self._db_directory = os.path.join(self.testrundir, "openldap-data") self.ldap_uri = "ldap://%s:%d/" % (LOCALHOST, self._port) - ldapi_path = os.path.join(self.testrundir, 'ldapi') - self.ldapi_uri = "ldapi://%s" % quote_plus(ldapi_path) + if HAVE_LDAPI: + ldapi_path = os.path.join(self.testrundir, 'ldapi') + self.ldapi_uri = "ldapi://%s" % quote_plus(ldapi_path) + self.default_ldap_uri = self.ldapi_uri + # use SASL/EXTERNAL via LDAPI when invoking OpenLDAP CLI tools + self.cli_sasl_external = True + else: + self.ldapi_uri = None + self.default_ldap_uri = self.ldap_uri + # Use simple bind via LDAP uri + self.cli_sasl_external = False # TLS certs self.cafile = os.path.join(HERE, 'certs/ca.pem') self.servercert = os.path.join(HERE, 'certs/server.pem') @@ -331,11 +352,14 @@ def _start_slapd(self): """ Spawns/forks the slapd process """ + urls = [self.ldap_uri] + if self.ldapi_uri: + urls.append(self.ldapi_uri) slapd_args = [ self.PATH_SLAPD, '-f', self._slapd_conf, '-F', self.testrundir, - '-h', '%s' % ' '.join((self.ldap_uri, self.ldapi_uri)), + '-h', '%s' % ' '.join(urls), ] if self._log.isEnabledFor(logging.DEBUG): slapd_args.extend(['-d', '-1']) @@ -346,18 +370,21 @@ def _start_slapd(self): # Waits until the LDAP server socket is open, or slapd crashed # no cover to avoid spurious coverage changes, see # https://github.com/python-ldap/python-ldap/issues/127 - while 1: # pragma: no cover + for _ in range(10): # pragma: no cover if self._proc.poll() is not None: self._stopped() raise RuntimeError("slapd exited before opening port") time.sleep(self._start_sleep) try: - self._log.debug("slapd connection check to %s", self.ldapi_uri) + self._log.debug( + "slapd connection check to %s", self.default_ldap_uri + ) self.ldapwhoami() except RuntimeError: pass else: return + raise RuntimeError("slapd did not start properly") def start(self): """ @@ -435,9 +462,11 @@ def _cli_auth_args(self): # no cover to avoid spurious coverage changes def _cli_popen(self, ldapcommand, extra_args=None, ldap_uri=None, stdin_data=None): # pragma: no cover + if ldap_uri is None: + ldap_uri = self.default_ldap_uri args = [ ldapcommand, - '-H', ldap_uri or self.ldapi_uri, + '-H', ldap_uri, ] + self._cli_auth_args() + (extra_args or []) self._log.debug('Run command: %r', ' '.join(args)) proc = subprocess.Popen( diff --git a/Tests/t_ldap_sasl.py b/Tests/t_ldap_sasl.py index af6ed51..828fe77 100644 --- a/Tests/t_ldap_sasl.py +++ b/Tests/t_ldap_sasl.py @@ -14,7 +14,8 @@ from ldap.ldapobject import SimpleLDAPObject import ldap.sasl -from slapdtest import SlapdTestCase, requires_sasl, requires_tls +from slapdtest import SlapdTestCase +from slapdtest import requires_ldapi, requires_sasl, requires_tls LDIF = """ @@ -60,7 +61,7 @@ def setUpClass(cls): ) cls.server.ldapadd(ldif) - @unittest.skipUnless(hasattr(socket, 'AF_UNIX'), "needs Unix socket") + @requires_ldapi() def test_external_ldapi(self): # EXTERNAL authentication with LDAPI (AF_UNIX) ldap_conn = self.ldap_object_class(self.server.ldapi_uri) diff --git a/Tests/t_ldap_schema_subentry.py b/Tests/t_ldap_schema_subentry.py index 3c07d35..4e1e09b 100644 --- a/Tests/t_ldap_schema_subentry.py +++ b/Tests/t_ldap_schema_subentry.py @@ -16,7 +16,7 @@ from ldap.ldapobject import SimpleLDAPObject import ldap.schema from ldap.schema.models import ObjectClass -from slapdtest import SlapdTestCase +from slapdtest import SlapdTestCase, requires_ldapi HERE = os.path.abspath(os.path.dirname(__file__)) @@ -88,6 +88,7 @@ def test_urlfetch_ldap(self): dn, schema = ldap.schema.urlfetch(self.server.ldap_uri) self.assertSlapdSchema(dn, schema) + @requires_ldapi() def test_urlfetch_ldapi(self): dn, schema = ldap.schema.urlfetch(self.server.ldapi_uri) self.assertSlapdSchema(dn, schema) diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 62591d7..5075468 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -22,8 +22,8 @@ import unittest import warnings import pickle -import warnings -from slapdtest import SlapdTestCase, requires_sasl, requires_tls +from slapdtest import SlapdTestCase +from slapdtest import requires_ldapi, requires_sasl, requires_tls # Switch off processing .ldaprc or ldap.conf before importing _ldap os.environ['LDAPNOINIT'] = '1' @@ -303,6 +303,7 @@ def test005_invalid_credentials(self): self.fail("expected INVALID_CREDENTIALS, got %r" % r) @requires_sasl() + @requires_ldapi() def test006_sasl_extenal_bind_s(self): l = self.ldap_object_class(self.server.ldapi_uri) l.sasl_external_bind_s() @@ -441,6 +442,7 @@ class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject): ldap_object_class = ReconnectLDAPObject @requires_sasl() + @requires_ldapi() def test101_reconnect_sasl_external(self): l = self.ldap_object_class(self.server.ldapi_uri) l.sasl_external_bind_s() @@ -450,7 +452,7 @@ def test101_reconnect_sasl_external(self): self.assertEqual(l.whoami_s(), authz_id) def test102_reconnect_simple_bind(self): - l = self.ldap_object_class(self.server.ldapi_uri) + l = self.ldap_object_class(self.server.ldap_uri) bind_dn = 'cn=user1,'+self.server.suffix l.simple_bind_s(bind_dn, 'user1_pw') self.assertEqual(l.whoami_s(), 'dn:'+bind_dn) @@ -458,7 +460,7 @@ def test102_reconnect_simple_bind(self): self.assertEqual(l.whoami_s(), 'dn:'+bind_dn) def test103_reconnect_get_state(self): - l1 = self.ldap_object_class(self.server.ldapi_uri) + l1 = self.ldap_object_class(self.server.ldap_uri) bind_dn = 'cn=user1,'+self.server.suffix l1.simple_bind_s(bind_dn, 'user1_pw') self.assertEqual(l1.whoami_s(), 'dn:'+bind_dn) @@ -477,7 +479,7 @@ def test103_reconnect_get_state(self): str('_start_tls'): 0, str('_trace_level'): 0, str('_trace_stack_limit'): 5, - str('_uri'): self.server.ldapi_uri, + str('_uri'): self.server.ldap_uri, str('bytes_mode'): l1.bytes_mode, str('bytes_mode_hardfail'): l1.bytes_mode_hardfail, str('timeout'): -1, @@ -485,7 +487,7 @@ def test103_reconnect_get_state(self): ) def test104_reconnect_restore(self): - l1 = self.ldap_object_class(self.server.ldapi_uri) + l1 = self.ldap_object_class(self.server.ldap_uri) bind_dn = 'cn=user1,'+self.server.suffix l1.simple_bind_s(bind_dn, 'user1_pw') self.assertEqual(l1.whoami_s(), 'dn:'+bind_dn) diff --git a/tox.ini b/tox.ini index fcfc628..58e3cf6 100644 --- a/tox.ini +++ b/tox.ini @@ -33,8 +33,8 @@ basepython = python2 deps = {[testenv]deps} passenv = {[testenv]passenv} setenv = - CI_DISABLED=TLS:SASL -# rebuild without SASL and TLS + CI_DISABLED=LDAPI:SASL:TLS +# rebuild without SASL and TLS, run without LDAPI commands = {envpython} \ -m coverage run --parallel setup.py \ clean --all \