From 4e2b67798b75e0c2b364fc0d9fdf327b0f0ddca4 Mon Sep 17 00:00:00 2001 From: Adi Roiban Date: Mon, 15 Jan 2018 18:00:56 +0000 Subject: [PATCH] Fix module imports for py3 and use replacements for methods removed in py3. (#87) --- .travis.yml | 10 +- CONTRIBUTING.rst | 6 +- docs/PULL_REQUEST_TEMPLATE.md | 2 +- ldaptor/config.py | 22 +-- ldaptor/delta.py | 5 +- ldaptor/dns.py | 21 +- ldaptor/insensitive.py | 51 ----- ldaptor/ldiftree.py | 16 +- ldaptor/protocols/ldap/distinguishedname.py | 27 ++- ldaptor/protocols/ldap/ldapsyntax.py | 8 +- ldaptor/protocols/ldap/ldifprotocol.py | 2 +- ldaptor/protocols/ldap/merger.py | 3 +- ldaptor/protocols/pureber.py | 13 +- ldaptor/protocols/pureldap.py | 13 +- ldaptor/samba/smbpassword.py | 6 +- ldaptor/schema.py | 4 +- ldaptor/test/test_config.py | 203 +++++++++++++++++--- ldaptor/test/test_distinguishedname.py | 16 +- ldaptor/test/test_inmemory.py | 95 +++++---- ldaptor/test/test_ldiftree.py | 10 +- ldaptor/test/test_pureber.py | 122 +++++++----- ldaptor/test/test_pureldap.py | 11 +- ldaptor/test/test_server.py | 44 +++-- ldaptor/test/util.py | 5 +- tox.ini | 14 +- 25 files changed, 460 insertions(+), 269 deletions(-) delete mode 100644 ldaptor/insensitive.py diff --git a/.travis.yml b/.travis.yml index ca1dabb2..2a3518ad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,15 +10,15 @@ python: - 2.7 env: - - TOX_ENV=py27-twlatest - - TOX_ENV=py27-twtrunk - - TOX_ENV=py34-twtrunk + - TOX_ENV=py27-test-twlatest + - TOX_ENV=py27-test-twtrunk + - TOX_ENV=py34-test-twtrunk - TOX_ENV=linters matrix: fast_finish: true allow_failures: - - env: TOX_ENV=py34-twtrunk + - env: TOX_ENV=py34-test-twtrunk install: - pip install tox coveralls codecov @@ -26,7 +26,7 @@ install: script: - tox -e $TOX_ENV -after_success: +after_script: - codecov - coveralls diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 3287cac7..b3e0384d 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -20,7 +20,7 @@ Development environment Tox is used to manage both local development and CI environment. -The recommended local dev enviroment is `tox -e py27-dev` +The recommended local dev enviroment is `tox -e py27-test-dev` When running on local dev env, you will get a coverage report for whole code as well as for the changes since `master`. @@ -32,8 +32,8 @@ The reports are also produced in HTML at: You can run a subset of the test by passing the dotted path to the test or test case, test module or test package:: - tox -e py27-dev ldaptor.test.test_delta.TestModifyOp.testAsLDIF - tox -e py27-dev ldaptor.test.test_usage + tox -e py27-test-dev ldaptor.test.test_delta.TestModifyOp.testAsLDIF + tox -e py27-test-dev ldaptor.test.test_usage Release notes diff --git a/docs/PULL_REQUEST_TEMPLATE.md b/docs/PULL_REQUEST_TEMPLATE.md index be125ffc..5957b46b 100644 --- a/docs/PULL_REQUEST_TEMPLATE.md +++ b/docs/PULL_REQUEST_TEMPLATE.md @@ -8,4 +8,4 @@ https://github.com/twisted/ldaptor/blob/master/CONTRIBUTING.rst * [ ] I have updated the release notes at `docs/source/NEWS.rst` * [ ] I have updated the automated tests. -* [ ] All tests pass on your local system for `tox -e py27-dev` +* [ ] All tests pass on your local dev environment. See `CONTRIBUTING.rst`. diff --git a/ldaptor/config.py b/ldaptor/config.py index 4923896b..680fd2b3 100644 --- a/ldaptor/config.py +++ b/ldaptor/config.py @@ -1,10 +1,9 @@ import os.path -import ConfigParser +from six.moves import configparser from zope.interface import implementer from ldaptor import interfaces -from ldaptor.insensitive import InsensitiveString from ldaptor.protocols.ldap import distinguishedname @@ -48,8 +47,8 @@ def getBaseDN(self): cfg = loadConfig() try: return cfg.get('ldap', 'base') - except (ConfigParser.NoOptionError, - ConfigParser.NoSectionError): + except (configparser.NoOptionError, + configparser.NoSectionError): raise MissingBaseDNError() def getServiceLocationOverrides(self): @@ -99,8 +98,8 @@ def getIdentityBaseDN(self): cfg = loadConfig() try: return cfg.get('authentication', 'identity-base') - except (ConfigParser.NoOptionError, - ConfigParser.NoSectionError): + except (configparser.NoOptionError, + configparser.NoSectionError): return self.getBaseDN() def getIdentitySearch(self, name): @@ -113,10 +112,10 @@ def getIdentitySearch(self, name): else: cfg = loadConfig() try: - f=cfg.get('authentication', 'identity-search', vars=data) - except (ConfigParser.NoOptionError, - ConfigParser.NoSectionError): - f='(|(cn=%(name)s)(uid=%(name)s))' % data + f = cfg.get('authentication', 'identity-search', vars=data) + except (configparser.NoOptionError, + configparser.NoSectionError): + f = '(|(cn=%(name)s)(uid=%(name)s))' % data return f @@ -139,8 +138,7 @@ def loadConfig(configFiles=None, """ global __config if __config is None or reload: - x = ConfigParser.SafeConfigParser() - x.optionxform = InsensitiveString + x = configparser.SafeConfigParser() for section, options in DEFAULTS.items(): x.add_section(section) diff --git a/ldaptor/delta.py b/ldaptor/delta.py index 617b3fa7..fad12eba 100644 --- a/ldaptor/delta.py +++ b/ldaptor/delta.py @@ -4,10 +4,13 @@ (This means these do not belong here: adding or deleting of entries, changing of location in tree) """ +import six from ldaptor import attributeset from ldaptor.protocols import pureldap, pureber from ldaptor.protocols.ldap import ldif, distinguishedname + + class Modification(attributeset.LDAPAttributeSet): def patch(self, entry): raise NotImplementedError( @@ -22,7 +25,7 @@ def asLDAP(self): tmplist = list(self) newlist = [] for x in range(len(tmplist)): - if (isinstance(tmplist[x], unicode)): + if (isinstance(tmplist[x], six.text_type)): value = tmplist[x].encode('utf-8') newlist.append(value) else: diff --git a/ldaptor/dns.py b/ldaptor/dns.py index e327bd40..b0e33d09 100644 --- a/ldaptor/dns.py +++ b/ldaptor/dns.py @@ -1,16 +1,13 @@ """DNS-related utilities.""" from socket import inet_aton, inet_ntoa +import struct def aton_octets(ip): s = inet_aton(ip) - octets = list(s) - n = 0 - for o in octets: - n = n << 8 - n += ord(o) - return n + return struct.unpack('!I', s)[0] + def aton_numbits(num): n = 0 @@ -20,6 +17,7 @@ def aton_numbits(num): num-=1 return n + def aton(ip): try: i=int(ip) @@ -28,16 +26,13 @@ def aton(ip): else: return aton_numbits(i) + def ntoa(n): - s=( - chr((n>>24)&0xFF) - + chr((n>>16)&0xFF) - + chr((n>>8)&0xFF) - + chr(n&0xFF) - ) - ip=inet_ntoa(s) + s = struct.pack('!I', n) + ip = inet_ntoa(s) return ip + def netmaskToNumbits(netmask): bits = aton(netmask) i = 2**31 diff --git a/ldaptor/insensitive.py b/ldaptor/insensitive.py deleted file mode 100644 index 54159ff3..00000000 --- a/ldaptor/insensitive.py +++ /dev/null @@ -1,51 +0,0 @@ -class InsensitiveString(str): - """A str subclass that performs all matching without regard to case.""" - - def __eq__(self, other): - if isinstance(other, basestring): - return self.lower() == other.lower() - else: - return super(InsensitiveString, self).__eq__(other) - - def __ne__(self, other): - if isinstance(other, basestring): - return self.lower() != other.lower() - else: - return super(InsensitiveString, self).__ne__(self, other) - - def __ge__(self, other): - if isinstance(other, basestring): - return self.lower() >= other.lower() - else: - return super(InsensitiveString, self).__ge__(self, other) - - def __gt__(self, other): - if isinstance(other, basestring): - return self.lower() > other.lower() - else: - return super(InsensitiveString, self).__gt__(self, other) - - def __le__(self, other): - if isinstance(other, basestring): - return self.lower() <= other.lower() - else: - return super(InsensitiveString, self).__le__(self, other) - - def __lt__(self, other): - if isinstance(other, basestring): - return self.lower() < other.lower() - else: - return super(InsensitiveString, self).__lt__(self, other) - - def __hash__(self): - return hash(self.lower()) - - def __contains__(self, other): - if isinstance(other, basestring): - return other.lower() in self.lower() - else: - return super(InsensitiveString, self).__contains__(self, other) - - def __getslice__(self, *a, **kw): - r = super(InsensitiveString, self).__getslice__(*a, **kw) - return self.__class__(r) diff --git a/ldaptor/ldiftree.py b/ldaptor/ldiftree.py index be192dc6..bce4daac 100644 --- a/ldaptor/ldiftree.py +++ b/ldaptor/ldiftree.py @@ -3,10 +3,10 @@ """ import errno import os +import uuid from twisted.internet import defer, error from twisted.python import failure -from twisted.mail.maildir import _generateMaildirName as tempName from zope.interface import implementer from ldaptor import entry, interfaces, attributeset, entryhelpers @@ -57,7 +57,7 @@ def _get(path, dn): entry = os.path.join(path, *['%s.dir' % rdn for rdn in l[:-1]]) entry = os.path.join(entry, '%s.ldif' % l[-1]) - f = file(entry) + f = open(entry) while 1: data = f.read(8192) if not data: @@ -77,8 +77,8 @@ def _get(path, dn): def _putEntry(fileName, entry): """fileName is without extension.""" - tmp = fileName + '.' + tempName() + '.tmp' - f = file(tmp, 'w') + tmp = fileName + '.' + str(uuid.uuid4()) + '.tmp' + f = open(tmp, 'w') f.write(str(entry)) f.close() os.rename(tmp, fileName+'.ldif') @@ -139,7 +139,7 @@ def _load(self): parser = StoreParsedLDIF() try: - f = file(entryPath) + f = open(entryPath) except IOError as e: if e.errno == errno.ENOENT: return @@ -241,8 +241,8 @@ def _addChild(self, rdn, attributes): if not os.path.exists(self.path): os.mkdir(self.path) fileName = os.path.join(self.path, '%s' % rdn) - tmp = fileName + '.' + tempName() + '.tmp' - f = file(tmp, 'w') + tmp = fileName + '.' + str(uuid.uuid4()) + '.tmp' + f = open(tmp, 'w') f.write(str(e)) f.close() os.rename(tmp, fileName+'.ldif') @@ -287,7 +287,7 @@ def __repr__(self): def __cmp__(self, other): if not isinstance(other, LDIFTreeEntry): return NotImplemented - return cmp(self.dn, other.dn) + return (self.dn > other.dn) - (self.dn < other.dn) def commit(self): assert self.path.endswith('.dir') diff --git a/ldaptor/protocols/ldap/distinguishedname.py b/ldaptor/protocols/ldap/distinguishedname.py index d7c0cdad..840e8fc0 100644 --- a/ldaptor/protocols/ldap/distinguishedname.py +++ b/ldaptor/protocols/ldap/distinguishedname.py @@ -1,5 +1,8 @@ -# See rfc2253 +from functools import total_ordering + +import six +# See rfc2253 # Note that RFC 2253 sections 2.4 and 3 disagree whether "=" needs to # be quoted. Let's trust the syntax, slapd refuses to accept unescaped # "=" in RDN values. @@ -151,15 +154,14 @@ def __init__(self, magic=None, stringValue=None, attributeTypesAndValues=None): assert attributeTypesAndValues is None if isinstance(magic, RelativeDistinguishedName): attributeTypesAndValues = magic.split() - elif isinstance(magic, basestring): + elif isinstance(magic, six.string_types): stringValue = magic else: attributeTypesAndValues = magic if stringValue is None: assert attributeTypesAndValues is not None - import types - assert not isinstance(attributeTypesAndValues, types.StringType) + assert not isinstance(attributeTypesAndValues, six.string_types) self.attributeTypesAndValues = tuple(attributeTypesAndValues) else: assert attributeTypesAndValues is None @@ -208,6 +210,7 @@ def count(self): return len(self.attributeTypesAndValues) +@total_ordering class DistinguishedName: """LDAP Distinguished Name.""" listOfRDNs = None @@ -221,7 +224,7 @@ def __init__(self, magic=None, stringValue=None, listOfRDNs=None): assert listOfRDNs is None if isinstance(magic, DistinguishedName): listOfRDNs = magic.split() - elif isinstance(magic, basestring): + elif isinstance(magic, six.string_types): stringValue = magic else: listOfRDNs = magic @@ -255,7 +258,7 @@ def __hash__(self): return hash(str(self)) def __eq__(self, other): - if isinstance(other, basestring): + if isinstance(other, six.string_types): return str(self) == other if not isinstance(other, DistinguishedName): return NotImplemented @@ -264,12 +267,16 @@ def __eq__(self, other): def __ne__(self, other): return not (self == other) - def __cmp__(self, other): - if isinstance(other, basestring): - return cmp(str(self), other) + def __lt__(self, other): + """ + Comparison used for determining the hierarchy. + """ if not isinstance(other, DistinguishedName): return NotImplemented - return cmp(self.split(), other.split()) + + # The comparison is naive and broken. + # See https://github.com/twisted/ldaptor/issues/94 + return self.split() < other.split() def getDomainName(self): domainParts = [] diff --git a/ldaptor/protocols/ldap/ldapsyntax.py b/ldaptor/protocols/ldap/ldapsyntax.py index d321794d..ad41fc0a 100644 --- a/ldaptor/protocols/ldap/ldapsyntax.py +++ b/ldaptor/protocols/ldap/ldapsyntax.py @@ -1,6 +1,7 @@ """Pythonic API for LDAP operations.""" import functools +import six from twisted.internet import defer from twisted.python.failure import Failure from zope.interface import implementer @@ -179,8 +180,7 @@ def _canRemoveAll(self, key): """ self._checkState() - import types - assert not isinstance(self.dn, types.StringType) + assert not isinstance(self.dn, six.string_types) for keyval in self.dn.split()[0].split(): if keyval.attributeType == key: raise CannotRemoveRDNError(key) @@ -414,7 +414,7 @@ def addChild(self, rdn, attributes): ldapAttrType = pureldap.LDAPAttributeDescription(attrType) l = [] for value in values: - if (isinstance(value, unicode)): + if (isinstance(value, six.text_type)): value = value.encode('utf-8') l.append(pureldap.LDAPAttributeValue(value)) ldapValues = pureber.BERSet(l) @@ -563,7 +563,7 @@ def setPassword(self, newPasswd): def _passwordChangerPriorityComparison(me, other): mePri = getattr(self, '_setPasswordPriority_' + me) otherPri = getattr(self, '_setPasswordPriority_' + other) - return cmp(mePri, otherPri) + return (mePri > otherPri) - (mePri < otherPri) prefix = 'setPasswordMaybe_' names = [name[len(prefix):] for name in dir(self) if name.startswith(prefix)] diff --git a/ldaptor/protocols/ldap/ldifprotocol.py b/ldaptor/protocols/ldap/ldifprotocol.py index 8b4cbefa..4b58c24f 100644 --- a/ldaptor/protocols/ldap/ldifprotocol.py +++ b/ldaptor/protocols/ldap/ldifprotocol.py @@ -49,7 +49,7 @@ class LDIFTruncatedError(LDIFParseError): WAIT_FOR_DN = 'WAIT_FOR_DN' IN_ENTRY = 'IN_ENTRY' -class LDIF(object, basic.LineReceiver): +class LDIF(basic.LineReceiver, object): delimiter = '\n' mode = HEADER diff --git a/ldaptor/protocols/ldap/merger.py b/ldaptor/protocols/ldap/merger.py index d750d513..b9bedaf1 100644 --- a/ldaptor/protocols/ldap/merger.py +++ b/ldaptor/protocols/ldap/merger.py @@ -5,11 +5,12 @@ """ from twisted.internet import reactor, defer +from six.moves.queue import Queue + from ldaptor.protocols.ldap import ldapclient, ldapconnector from ldaptor.protocols.ldap import ldapserver from ldaptor.protocols.ldap import ldaperrors from ldaptor.protocols import pureldap -from Queue import Queue from ldaptor.config import LDAPConfig diff --git a/ldaptor/protocols/pureber.py b/ldaptor/protocols/pureber.py index 9dfd2d14..cffae1b2 100644 --- a/ldaptor/protocols/pureber.py +++ b/ldaptor/protocols/pureber.py @@ -29,9 +29,6 @@ # (4) If a value of a type is its default value, it MUST be absent. # Only some BOOLEAN and INTEGER types have default values in # this protocol definition. - -import string - from six.moves import UserList # xxxxxxxx @@ -131,12 +128,6 @@ def __init__(self, tag=None): def __len__(self): return len(str(self)) - def __cmp__(self, other): - if isinstance(other, BERBase): - return cmp(str(self), str(other)) - else: - return -1 - def __eq__(self, other): if not isinstance(other, BERBase): return NotImplemented @@ -310,8 +301,8 @@ def __init__(self, value=None, tag=None): UserList.__init__(self, value) def __str__(self): - r=string.join(map(str, self.data), '') - return chr(self.identification())+int2berlen(len(r))+r + r = ''.join(map(str, self.data)) + return chr(self.identification()) + int2berlen(len(r)) + r def __repr__(self): if self.tag == self.__class__.tag: diff --git a/ldaptor/protocols/pureldap.py b/ldaptor/protocols/pureldap.py index d23762c3..f1452737 100644 --- a/ldaptor/protocols/pureldap.py +++ b/ldaptor/protocols/pureldap.py @@ -17,6 +17,8 @@ import string +import six + from ldaptor.protocols.pureber import ( BERBoolean, BERDecoderContext, BEREnumerated, BERInteger, BERNull, @@ -676,13 +678,13 @@ def fromBER(klass, tag, content, berdecoder=None): def __init__(self, matchingRule=None, type=None, matchValue=None, dnAttributes=None, tag=None): BERSequence.__init__(self, value=[], tag=tag) assert matchValue is not None - if isinstance(matchingRule, basestring): + if isinstance(matchingRule, six.string_types): matchingRule = LDAPMatchingRuleAssertion_matchingRule(matchingRule) - if isinstance(type, basestring): + if isinstance(type, six.string_types): type = LDAPMatchingRuleAssertion_type(type) - if isinstance(matchValue, basestring): + if isinstance(matchValue, six.string_types): matchValue = LDAPMatchingRuleAssertion_matchValue(matchValue) if isinstance(dnAttributes, bool): @@ -1344,8 +1346,9 @@ def __init__(self, requestName=None, requestValue=None, LDAPProtocolRequest.__init__(self) BERSequence.__init__(self, [], tag=tag) assert requestName is not None - assert isinstance(requestName, basestring) - assert requestValue is None or isinstance(requestValue, basestring) + assert isinstance(requestName, six.string_types) + assert requestValue is None or isinstance( + requestValue, six.string_types) self.requestName = requestName self.requestValue = requestValue diff --git a/ldaptor/samba/smbpassword.py b/ldaptor/samba/smbpassword.py index 8e21141f..aa78dea6 100644 --- a/ldaptor/samba/smbpassword.py +++ b/ldaptor/samba/smbpassword.py @@ -1,8 +1,10 @@ import warnings + +import string try: - import string maketrans = string.maketrans -except ImportError: +except AttributeError: + # On Python3 we get it from bytes. maketrans = bytes.maketrans from ldaptor import md4, config diff --git a/ldaptor/schema.py b/ldaptor/schema.py index abd54a09..2353787b 100644 --- a/ldaptor/schema.py +++ b/ldaptor/schema.py @@ -1,3 +1,5 @@ +import six + def extractWord(text): if not text: return None @@ -562,7 +564,7 @@ def __str__(self): if self.usage is not None: r.append('USAGE %s' % self.usage) for name, value in self.x_attrs: - if isinstance(value, basestring): + if isinstance(value, six.string_types): r.append("%s '%s'" % (name, value)) else: r.append( diff --git a/ldaptor/test/test_config.py b/ldaptor/test/test_config.py index 76c6c72e..b35318ac 100644 --- a/ldaptor/test/test_config.py +++ b/ldaptor/test/test_config.py @@ -14,8 +14,34 @@ def writeFile(path, content): f.close() -class TestConfig(unittest.TestCase): - def testSomething(self): +def reloadFromContent(testCase, content): + """ + Reload the global configuration file with raw `content`. + """ + base_path = testCase.mktemp() + os.mkdir(base_path) + config_path = os.path.join(base_path, 'test.cfg') + writeFile(config_path, content) + + # Reload with empty content to reduce the side effects. + testCase.addCleanup(reloadFromContent, testCase, "") + + return config.loadConfig( + configFiles=[config_path], + reload=True, + ) + + +class TestLoadConfig(unittest.TestCase): + """ + Tests for loadConfig. + """ + + def testMultileConfigurationFile(self): + """ + It can read configuration from multiple files, merging the + loaded values. + """ self.dir = self.mktemp() os.mkdir(self.dir) self.f1 = os.path.join(self.dir, 'one.cfg') @@ -41,30 +67,159 @@ def testSomething(self): val = self.cfg.get('barSection', 'barVar') self.assertEqual(val, 'anotherVal') -class IdentitySearch(unittest.TestCase): - def setUp(self): - self.dir = self.mktemp() - os.mkdir(self.dir) - self.f1 = os.path.join(self.dir, 'one.cfg') - writeFile(self.f1, """\ -[authentication] + +class TestLDAPConfig(unittest.TestCase): + """ + Unit tests for LDAPConfig. + """ + + def testGetBaseDNOK(self): + """ + It will return the base DN found in the configuration in the [ldap] + section as `base` option. + """ + reloadFromContent(self, b'[ldap]\nbase=dc=test,dc=net\n') + sut = config.LDAPConfig() + + result = sut.getBaseDN() + + self.assertEqual('dc=test,dc=net', result) + + def testGetBaseDNNoSection(self): + """ + It raise an exception when the the configuration has no [ldap] + section. + """ + reloadFromContent(self, b'[other]\nbase=dc=test,dc=net\n') + sut = config.LDAPConfig() + + self.assertRaises( + config.MissingBaseDNError, + sut.getBaseDN, + ) + + def testGetBaseDNNoOption(self): + """ + It raise an exception when the the configuration has [ldap] + section but no `base` option. + """ + reloadFromContent(self, b'[ldap]\nbaseless=dc=test,dc=net\n') + sut = config.LDAPConfig() + + self.assertRaises( + config.MissingBaseDNError, + sut.getBaseDN, + ) + + def testGetIdentityBaseDNOK(self): + """ + It will return the value found in the configuration in the + [authentication] section as `identity-base` option. + """ + reloadFromContent( + self, + b'[authentication]\n' + b'identity-base=ou=users,dc=test,dc=net\n' + ) + sut = config.LDAPConfig() + + result = sut.getIdentityBaseDN() + + self.assertEqual('ou=users,dc=test,dc=net', result) + + def testGetIdentityBaseSectionSection(self): + """ + When the configuration does not contains the + `[authentication]` section it will return the configured Base DN. + """ + reloadFromContent( + self, + b'[ldap]\n' + b'basE=dc=test,dc=net\n' + ) + sut = config.LDAPConfig() + + result = sut.getIdentityBaseDN() + + self.assertEqual('dc=test,dc=net', result) + + def testGetIdentityBaseNoOption(self): + """ + When the configuration does not contains the `identity-base` option + inside the `[authentication]` section it will return the configured + Base DN. + """ + reloadFromContent( + self, + b'[ldap]\n' + b'BASE=dc=test,dc=net\n' + b'[authentication]\n' + b'no-identity-base=dont care\n' + ) + sut = config.LDAPConfig() + + result = sut.getIdentityBaseDN() + + self.assertEqual('dc=test,dc=net', result) + + def testGetIdentitySearchOK(self): + """ + It will use the value from to configuration for its return value. + """ + reloadFromContent(self, """[authentication] identity-search = (something=%(name)s) """) - self.cfg = config.loadConfig( - configFiles=[self.f1], - reload=True) - self.config = config.LDAPConfig() + sut = config.LDAPConfig() + + result = sut.getIdentitySearch('foo') + + self.assertEqual('(something=foo)', result) + + def testGetIdentitySearchNoSection(self): + """ + When the configuration file does not contains the `authentication` + section it will use a default expression. + """ + sut = config.LDAPConfig() + + result = sut.getIdentitySearch('foo') - def testConfig(self): - self.assertEqual(self.config.getIdentitySearch('foo'), - '(something=foo)') + self.assertEqual('(|(cn=foo)(uid=foo))', result) + + def testGetIdentitySearchNoOption(self): + """ + When the configuration file contains the `authentication` + section but without the identity search option, + it will use a default expression. + """ + reloadFromContent(self, b'[authentication]\nother_key=value') + sut = config.LDAPConfig() + + result = sut.getIdentitySearch('foo') + + self.assertEqual('(|(cn=foo)(uid=foo))', result) + + def testgetIdentitySearchFromInitArguments(self): + """ + When data is provided at LDAPConfig initialization it is used + as the backend data. + """ + sut = config.LDAPConfig(identitySearch='(&(bar=thud)(quux=%(name)s))') + + result = sut.getIdentitySearch('foo') + + self.assertEqual('(&(bar=thud)(quux=foo))', result) def testCopy(self): - conf = self.config.copy(identitySearch='(&(bar=baz)(quux=%(name)s))') - self.assertEqual(conf.getIdentitySearch('foo'), - '(&(bar=baz)(quux=foo))') - - def testInitArg(self): - conf = config.LDAPConfig(identitySearch='(&(bar=thud)(quux=%(name)s))') - self.assertEqual(conf.getIdentitySearch('foo'), - '(&(bar=thud)(quux=foo))') + """ + It returns a copy of the configuration. + """ + sut = config.LDAPConfig() + + copied = sut.copy(identitySearch='(&(bar=baz)(quux=%(name)s))') + + self.assertIsInstance(copied, config.LDAPConfig) + + result = copied.getIdentitySearch('foo') + + self.assertEqual('(&(bar=baz)(quux=foo))', result) diff --git a/ldaptor/test/test_distinguishedname.py b/ldaptor/test/test_distinguishedname.py index fb56c10b..8a64293e 100644 --- a/ldaptor/test/test_distinguishedname.py +++ b/ldaptor/test/test_distinguishedname.py @@ -322,8 +322,16 @@ def testRDN(self): self.assertEqual(str(rdn), 'dc=example') class DistinguishedName_Comparison(unittest.TestCase): - # TODO test more carefully - def testGT(self): + """ + Tests for comparing DistinguishedName. + """ + + def test_parent_child(self): + """ + The parent is greater than the child. + """ dn1=dn.DistinguishedName('dc=example,dc=com') - dn2=dn.DistinguishedName('dc=bar,dc=example,dc=com') - self.failUnless(dn1 > dn2) + dn2=dn.DistinguishedName('dc=and,dc=example,dc=com') + + self.assertLess(dn2, dn1) + self.assertGreater(dn1, dn2) diff --git a/ldaptor/test/test_inmemory.py b/ldaptor/test/test_inmemory.py index 154163e7..bb4f6e61 100644 --- a/ldaptor/test/test_inmemory.py +++ b/ldaptor/test/test_inmemory.py @@ -1,8 +1,11 @@ """ Test cases for ldaptor.inmemory module. """ +from io import BytesIO + from twisted.trial import unittest -from cStringIO import StringIO +import six + from ldaptor import inmemory, delta, testutil from ldaptor.protocols.ldap import distinguishedname, ldaperrors @@ -51,7 +54,7 @@ def setUp(self): def test_children_empty(self): d = self.empty.children() - d.addCallback(self.assertItemsEqual, []) + d.addCallback(lambda actual: six.assertCountEqual(self, actual, [])) return d def test_children_oneChild(self): @@ -62,7 +65,7 @@ def cb(children): want = [distinguishedname.DistinguishedName('cn=theChild,ou=oneChild,dc=example,dc=com')] got.sort() want.sort() - self.assertItemsEqual(got, want) + six.assertCountEqual(self, got, want) d.addCallback(cb) return d @@ -92,7 +95,7 @@ def cb(children): distinguishedname.DistinguishedName('cn=bar,ou=metasyntactic,dc=example,dc=com'), ] got = [e.dn for e in children] - self.assertItemsEqual(got, want) + six.assertCountEqual(self, got, want) d.addCallback(cb) return d @@ -112,7 +115,7 @@ def cb(children): ] got.sort() want.sort() - self.assertItemsEqual(got, want) + six.assertCountEqual(self, got, want) d.addCallback(cb) return d @@ -140,10 +143,13 @@ def cb(entries): def test_subtree_oneChild(self): d = self.oneChild.subtree() - d.addCallback(self.assertItemsEqual, [ + d.addCallback(lambda actual: six.assertCountEqual( + self, + actual, + [ self.oneChild, self.theChild, - ]) + ])) return d def test_subtree_oneChild_cb(self): @@ -155,7 +161,7 @@ def cb(dummy): self.oneChild, self.theChild, ] - self.assertItemsEqual(got, want) + six.assertCountEqual(self, got, want) d.addCallback(cb) return d @@ -172,7 +178,7 @@ def cb(results): self.bar, self.foo, ] - self.assertItemsEqual(got, want) + six.assertCountEqual(self, got, want) d.addCallback(cb) return d @@ -191,7 +197,7 @@ def cb(r): self.bar, self.foo, ] - self.assertItemsEqual(got, want) + six.assertCountEqual(self, got, want) d.addCallback(cb) return d @@ -239,14 +245,16 @@ def test_delete(self): d = self.foo.delete() d.addCallback(self.assertEqual, self.foo) d.addCallback(lambda _: self.meta.children()) - d.addCallback(self.assertItemsEqual, [self.bar]) + d.addCallback(lambda actual: six.assertCountEqual( + self, actual, [self.bar])) return d def test_deleteChild(self): d = self.meta.deleteChild('cn=bar') d.addCallback(self.assertEqual, self.bar) d.addCallback(lambda _: self.meta.children()) - d.addCallback(self.assertItemsEqual, [self.foo]) + d.addCallback(lambda actual: six.assertCountEqual( + self, actual, [self.foo])) return d def test_deleteChild_NonExisting(self): @@ -257,7 +265,7 @@ def eb(fail): return d def test_setPassword(self): - self.foo.setPassword('s3krit', salt='\xf2\x4a') + self.foo.setPassword('s3krit', salt=b'\xf2\x4a') self.failUnless('userPassword' in self.foo) self.assertEqual(self.foo['userPassword'], ['{SSHA}0n/Iw1NhUOKyaI9gm9v5YsO3ZInySg==']) @@ -284,16 +292,19 @@ def cb(r): self.bar, self.foo, ] - self.assertItemsEqual(got, want) + six.assertCountEqual(self, got, want) d.addCallback(cb) return d def testSearch_withoutCallback(self): d = self.root.search(filterText='(|(cn=foo)(cn=bar))') - d.addCallback(self.assertItemsEqual, [ + d.addCallback(lambda actual: six.assertCountEqual( + self, + actual, + [ self.bar, self.foo, - ]) + ])) return d def test_move_noChildren_sameSuperior(self): @@ -301,7 +312,10 @@ def test_move_noChildren_sameSuperior(self): def getChildren(dummy): return self.root.children() d.addCallback(getChildren) - d.addCallback(self.assertItemsEqual, [ + d.addCallback(lambda actual: six.assertCountEqual( + self, + actual, + [ self.meta, inmemory.ReadOnlyInMemoryLDAPEntry( dn='ou=moved,dc=example,dc=com', @@ -309,7 +323,7 @@ def getChildren(dummy): 'ou': ['moved'], }), self.oneChild, - ]) + ])) return d def test_move_children_sameSuperior(self): @@ -317,7 +331,10 @@ def test_move_children_sameSuperior(self): def getChildren(dummy): return self.root.children() d.addCallback(getChildren) - d.addCallback(self.assertItemsEqual, [ + d.addCallback(lambda actual: six.assertCountEqual( + self, + actual, + [ inmemory.ReadOnlyInMemoryLDAPEntry( dn='ou=moved,dc=example,dc=com', attributes={ 'objectClass': ['a', 'b'], @@ -325,7 +342,7 @@ def getChildren(dummy): }), self.empty, self.oneChild, - ]) + ])) return d @@ -334,21 +351,27 @@ def test_move_noChildren_newSuperior(self): def getChildren(dummy): return self.root.children() d.addCallback(getChildren) - d.addCallback(self.assertItemsEqual, [ + d.addCallback(lambda actual: six.assertCountEqual( + self, + actual, + [ self.meta, self.oneChild, - ]) + ])) def getChildren2(dummy): return self.oneChild.children() d.addCallback(getChildren2) - d.addCallback(self.assertItemsEqual, [ + d.addCallback(lambda actual: six.assertCountEqual( + self, + actual, + [ self.theChild, inmemory.ReadOnlyInMemoryLDAPEntry( dn='ou=moved,ou=oneChild,dc=example,dc=com', attributes={ 'objectClass': ['a', 'b'], 'ou': ['moved'], }), - ]) + ])) return d def test_move_children_newSuperior(self): @@ -356,21 +379,27 @@ def test_move_children_newSuperior(self): def getChildren(dummy): return self.root.children() d.addCallback(getChildren) - d.addCallback(self.assertItemsEqual, [ + d.addCallback(lambda actual: six.assertCountEqual( + self, + actual, + [ self.empty, self.oneChild, - ]) + ])) def getChildren2(dummy): return self.oneChild.children() d.addCallback(getChildren2) - d.addCallback(self.assertItemsEqual, [ + d.addCallback(lambda actual: six.assertCountEqual( + self, + actual, + [ self.theChild, inmemory.ReadOnlyInMemoryLDAPEntry( dn='ou=moved,ou=oneChild,dc=example,dc=com', attributes={ 'objectClass': ['a', 'b'], 'ou': ['moved'], }), - ]) + ])) return d def test_commit(self): @@ -381,7 +410,7 @@ def test_commit(self): class FromLDIF(unittest.TestCase): def test_single(self): - ldif = StringIO('''\ + ldif = BytesIO(b'''\ dn: cn=foo,dc=example,dc=com objectClass: a objectClass: b @@ -397,11 +426,11 @@ def cb1(db): distinguishedname.DistinguishedName('cn=foo,dc=example,dc=com')) return db.children() d.addCallback(cb1) - d.addCallback(self.assertItemsEqual, []) + d.addCallback(self.assertEqual, []) return d def test_two(self): - ldif = StringIO('''\ + ldif = BytesIO(b'''\ dn: dc=example,dc=com objectClass: dcObject dc: example @@ -425,12 +454,12 @@ def cb2(children): distinguishedname.DistinguishedName('cn=foo,dc=example,dc=com'), ] got = [e.dn for e in children] - self.assertItemsEqual(got, want) + six.assertCountEqual(self, got, want) d.addCallback(cb2) return d def test_missingNode(self): - ldif = StringIO('''\ + ldif = BytesIO(b'''\ dn: dc=example,dc=com objectClass: dcObject dc: example diff --git a/ldaptor/test/test_ldiftree.py b/ldaptor/test/test_ldiftree.py index e3d4263b..d7a148f8 100644 --- a/ldaptor/test/test_ldiftree.py +++ b/ldaptor/test/test_ldiftree.py @@ -15,7 +15,7 @@ def writeFile(path, content): - f = file(path, 'w') + f = open(path, 'w') f.write(content) f.close() @@ -188,7 +188,7 @@ def testSimpleWrite(self): def _cb_testSimpleWrite(self, entry): path = os.path.join(self.tree, 'dc=com.dir', 'dc=example.dir', 'cn=foo.ldif') self.failUnless(os.path.isfile(path)) - self.failUnlessEqual(file(path).read(), + self.failUnlessEqual(open(path).read(), """\ dn: cn=foo,dc=example,dc=com objectClass: top @@ -210,7 +210,7 @@ def _cb_testDirCreation(self, entry): path = os.path.join(self.tree, 'dc=com.dir', 'dc=example.dir', 'ou=OrgUnit.dir', 'cn=create-me.ldif') self.failUnless(os.path.isfile(path)) - self.failUnlessEqual(file(path).read(), + self.failUnlessEqual(open(path).read(), """\ dn: cn=create-me,ou=OrgUnit,dc=example,dc=com objectClass: top @@ -234,7 +234,7 @@ def testDirExists(self): def _cb_testDirExists(self, entry, dirpath): path = os.path.join(dirpath, 'cn=create-me.ldif') self.failUnless(os.path.isfile(path)) - self.failUnlessEqual(file(path).read(), + self.failUnlessEqual(open(path).read(), """\ dn: cn=create-me,ou=OrgUnit,dc=example,dc=com objectClass: top @@ -267,7 +267,7 @@ def testAddTopLevel(self): def _cb_testAddTopLevel(self, entry): path = os.path.join(self.tree, 'dc=org.ldif') self.failUnless(os.path.isfile(path)) - self.failUnlessEqual(file(path).read(), + self.failUnlessEqual(open(path).read(), """\ dn: dc=org objectClass: dcObject diff --git a/ldaptor/test/test_pureber.py b/ldaptor/test/test_pureber.py index e75b79ff..2d5e7b1e 100644 --- a/ldaptor/test/test_pureber.py +++ b/ldaptor/test/test_pureber.py @@ -16,8 +16,9 @@ """ Test cases for ldaptor.protocols.pureber module. """ - +import six from twisted.trial import unittest + from ldaptor.protocols import pureber @@ -103,8 +104,8 @@ def testInequalityWithBER(self): """ BER objects do not equal BER objects with different type or content """ - for i in xrange(len(self.valuesToTest)): - for j in xrange(len(self.valuesToTest)): + for i in six.moves.range(len(self.valuesToTest)): + for j in six.moves.range(len(self.valuesToTest)): if i!=j: i_class, i_args = self.valuesToTest[i] j_class, j_args = self.valuesToTest[j] @@ -240,16 +241,6 @@ def testSanity(self): assert n*'x'==result - - - - - - - - - - class BERNullKnownValues(unittest.TestCase): def testToBERNullKnownValues(self): """str(BERNull()) should give known result""" @@ -275,9 +266,6 @@ def testPartialBERNullEncodings(self): self.assertEqual((None, 0), pureber.berDecodeObject(pureber.BERDecoderContext(), '')) - - - class BERBooleanKnownValues(unittest.TestCase): knownValues=( (0, [0x01, 0x01, 0], 0), @@ -327,12 +315,6 @@ def testPartialBERBooleanEncodings(self): self.assertEqual((None, 0), pureber.berDecodeObject(pureber.BERDecoderContext(), '')) - - - - - - class BEREnumeratedKnownValues(unittest.TestCase): knownValues=( (0, [0x0a, 0x01, 0]), @@ -391,39 +373,88 @@ def testSanity(self): assert n==result -class BERSequenceKnownValues(unittest.TestCase): - knownValues=( - ([], [0x30, 0x00]), - ([pureber.BERInteger(2)], [0x30, 0x03, 0x02, 0x01, 2]), - ([pureber.BERInteger(3)], [0x30, 0x03, 0x02, 0x01, 3]), - ([pureber.BERInteger(128)], [0x30, 0x04, 0x02, 0x02, 0, 128]), - ([pureber.BERInteger(2), pureber.BERInteger(3), pureber.BERInteger(128)], - [0x30, 0x0a]+[0x02, 0x01, 2]+[0x02, 0x01, 3]+[0x02, 0x02, 0, 128]), - ) +class TestBERSequence(unittest.TestCase): + """ + Unit test for BERSequence. + """ - def testToBERSequenceKnownValues(self): - """str(BERSequence(x)) should give known result with known input""" - for content, encoded in self.knownValues: - result = pureber.BERSequence(content) - result = str(result) - result = map(ord, result) - assert encoded==result + def testStringRepresentationEmpty(self): + """ + It can return the string representation for empty sequence which + is just the zero/null byte. + """ + sut = pureber.BERSequence([]) + + result = str(sut) + + self.assertEqual('0\x00', result) + + def testStringRepresentatinSmallInteger(self): + """ + It can represent a sequence of a single integer which has a + single byte value. + """ + sut = pureber.BERSequence([pureber.BERInteger(2)]) + + result = str(sut) + + self.assertEqual('0\x03\x02\x01\x02', result) + + def testStringRepresentatinLargerInteger(self): + """ + It can represent a sequence of a single integer which has a + multi bites value. + """ + sut = pureber.BERSequence([pureber.BERInteger(128)]) + + result = str(sut) + + self.assertEqual('0\x04\x02\x02\x00\x80', result) + + def testStringRepresentatinMultipleIntegers(self): + """ + It can represent a sequence of multiple integer. + """ + sut = pureber.BERSequence([ + pureber.BERInteger(3), pureber.BERInteger(128)]) + + result = str(sut) - def testFromBERSequenceKnownValues(self): - """BERSequence(encoded="...") should give known result with known input""" - for content, encoded in self.knownValues: + self.assertEqual('0\x07\x02\x01\x03\x02\x02\x00\x80', result) + + def testDecodeValidInput(self): + """ + It can be decoded from its bytes serialization. + """ + knownValues=( + ([], [0x30, 0x00]), + ([pureber.BERInteger(2)], [0x30, 0x03, 0x02, 0x01, 2]), + ([pureber.BERInteger(3)], [0x30, 0x03, 0x02, 0x01, 3]), + ([pureber.BERInteger(128)], [0x30, 0x04, 0x02, 0x02, 0, 128]), + ([ + pureber.BERInteger(2), + pureber.BERInteger(3), + pureber.BERInteger(128), + ], + [0x30, 0x0a] + [0x02, 0x01, 2] + [0x02, 0x01, 3] + [0x02, 0x02, 0, 128]), + ) + + for content, encoded in knownValues: m=s(*encoded) result, bytes = pureber.berDecodeObject(pureber.BERDecoderContext(), m) self.assertEqual(bytes, len(m)) assert isinstance(result, pureber.BERSequence) result = result.data assert len(content)==len(result) - for i in xrange(len(content)): + for i in six.moves.range(len(content)): assert content[i]==result[i] assert content==result - def testPartialBERSequenceEncodings(self): - """BERSequence(encoded="...") with too short input should throw BERExceptionInsufficientData""" + def testDecdeInvalidInput(self): + """ + It raises BERExceptionInsufficientData when trying to decode from + data which is not valid. + """ m=str(pureber.BERSequence([pureber.BERInteger(2)])) assert len(m)==5 @@ -432,6 +463,3 @@ def testPartialBERSequenceEncodings(self): self.assertRaises(pureber.BERExceptionInsufficientData, pureber.berDecodeObject, pureber.BERDecoderContext(), m[:2]) self.assertRaises(pureber.BERExceptionInsufficientData, pureber.berDecodeObject, pureber.BERDecoderContext(), m[:1]) self.assertEqual((None, 0), pureber.berDecodeObject(pureber.BERDecoderContext(), '')) - -# TODO BERSequenceOf -# TODO BERSet diff --git a/ldaptor/test/test_pureldap.py b/ldaptor/test/test_pureldap.py index 68e6ee75..0f72804b 100644 --- a/ldaptor/test/test_pureldap.py +++ b/ldaptor/test/test_pureldap.py @@ -16,10 +16,13 @@ """ Test cases for ldaptor.protocols.pureldap module. """ +import types +import six from twisted.trial import unittest + from ldaptor.protocols import pureldap, pureber -import types + def s(*l): """Join all members of list to a string. Integer members are chr()ed""" @@ -648,7 +651,7 @@ def testPartial(self): if decoder is None: decoder = pureldap.LDAPBERDecoderContext( fallback=pureber.BERDecoderContext()) - for i in xrange(1, len(encoded)): + for i in six.moves.range(1, len(encoded)): m=s(*encoded)[:i] self.assertRaises(pureber.BERExceptionInsufficientData, pureber.berDecodeObject, @@ -678,8 +681,8 @@ def testEquality(self): def testInEquality(self): """LDAP objects do not equal LDAP objects with different type or content""" - for i in xrange(len(self.valuesToTest)): - for j in xrange(len(self.valuesToTest)): + for i in six.moves.range(len(self.valuesToTest)): + for j in six.moves.range(len(self.valuesToTest)): if i!=j: i_class, i_args = self.valuesToTest[i] j_class, j_args = self.valuesToTest[j] diff --git a/ldaptor/test/test_server.py b/ldaptor/test/test_server.py index 192be9c6..5a9f6ea4 100644 --- a/ldaptor/test/test_server.py +++ b/ldaptor/test/test_server.py @@ -2,17 +2,21 @@ Test cases for ldaptor.protocols.ldap.ldapserver module. """ from __future__ import print_function + import base64 import types + +import six from twisted.internet import address, protocol from twisted.python import components +from twisted.test import proto_helpers +from twisted.trial import unittest + from ldaptor import inmemory, interfaces, schema, delta, entry from ldaptor.protocols.ldap import ldapserver, ldapclient, ldaperrors, \ fetchschema from ldaptor.protocols import pureldap, pureber from ldaptor.test import util, test_schema -from twisted.test import proto_helpers -from twisted.trial import unittest def wrapCommit(entry, cb, *args, **kwds): @@ -23,7 +27,7 @@ def commit_(self): d.addCallback(cb, *args, **kwds) return d - f = types.MethodType(commit_, entry, entry.__class__) + f = types.MethodType(commit_, entry) entry.commit = f @@ -323,7 +327,7 @@ def test_search_matchAll_oneResult(self): pureldap.LDAPSearchRequest( baseObject='cn=thingie,ou=stuff,dc=example,dc=com'), id=2))) - self.assertItemsEqual( + six.assertCountEqual(self, self._makeResultList(self.server.transport.value()), [str( pureldap.LDAPMessage( @@ -346,7 +350,7 @@ def test_search_matchAll_oneResult_filtered(self): baseObject='cn=thingie,ou=stuff,dc=example,dc=com', attributes=['cn']), id=2))) - self.assertItemsEqual( + six.assertCountEqual(self, self._makeResultList(self.server.transport.value()), [str( pureldap.LDAPMessage( @@ -382,7 +386,7 @@ def test_search_matchAll_manyResults(self): pureldap.LDAPSearchRequest( baseObject='ou=stuff,dc=example,dc=com'), id=2))) - self.assertItemsEqual( + six.assertCountEqual(self, [str( pureldap.LDAPMessage( pureldap.LDAPSearchResultEntry( @@ -421,7 +425,7 @@ def test_search_scope_oneLevel(self): baseObject='ou=stuff,dc=example,dc=com', scope=pureldap.LDAP_SCOPE_singleLevel), id=2))) - self.assertItemsEqual( + six.assertCountEqual(self, self._makeResultList(self.server.transport.value()), [str( pureldap.LDAPMessage( @@ -452,7 +456,7 @@ def test_search_scope_wholeSubtree(self): baseObject='ou=stuff,dc=example,dc=com', scope=pureldap.LDAP_SCOPE_wholeSubtree), id=2))) - self.assertItemsEqual( + six.assertCountEqual(self, self._makeResultList(self.server.transport.value()), [str( pureldap.LDAPMessage( @@ -491,7 +495,7 @@ def test_search_scope_baseObject(self): baseObject='ou=stuff,dc=example,dc=com', scope=pureldap.LDAP_SCOPE_baseObject), id=2))) - self.assertItemsEqual( + six.assertCountEqual(self, self._makeResultList(self.server.transport.value()), [str( pureldap.LDAPMessage( @@ -515,7 +519,7 @@ def test_rootDSE(self): scope=pureldap.LDAP_SCOPE_baseObject, filter=pureldap.LDAPFilter_present('objectClass')), id=2))) - self.assertItemsEqual( + six.assertCountEqual(self, self._makeResultList(self.server.transport.value()), [str( pureldap.LDAPMessage( @@ -546,7 +550,8 @@ def test_delete(self): pureldap.LDAPDelResponse(resultCode=0), id=2))) d = self.stuff.children() - d.addCallback(self.assertItemsEqual, [self.another]) + d.addCallback(lambda actual: six.assertCountEqual( + self, actual, [self.another])) return d def test_add_success(self): @@ -575,15 +580,16 @@ def test_add_success(self): id=2))) # tree changed d = self.stuff.children() - d.addCallback( - self.assertItemsEqual, + d.addCallback(lambda actual: six.assertCountEqual( + self, + actual, [ self.thingie, self.another, inmemory.ReadOnlyInMemoryLDAPEntry( 'cn=new,ou=stuff,dc=example,dc=com', {'objectClass': ['something']}) - ]) + ])) return d def test_add_fail_existsAlready(self): @@ -612,7 +618,8 @@ def test_add_fail_existsAlready(self): id=2))) # tree did not change d = self.stuff.children() - d.addCallback(self.assertItemsEqual, [self.thingie, self.another]) + d.addCallback(lambda actual: six.assertCountEqual( + self, actual, [self.thingie, self.another])) return d def test_modifyDN_rdnOnly_deleteOldRDN_success(self): @@ -634,8 +641,9 @@ def test_modifyDN_rdnOnly_deleteOldRDN_success(self): id=2))) # tree changed d = self.stuff.children() - d.addCallback( - self.assertItemsEqual, + d.addCallback(lambda actual: six.assertCountEqual( + self, + actual, [ inmemory.ReadOnlyInMemoryLDAPEntry( '%s,ou=stuff,dc=example,dc=com' % newrdn, @@ -644,7 +652,7 @@ def test_modifyDN_rdnOnly_deleteOldRDN_success(self): 'cn': ['thingamagic'] }), self.another, - ]) + ])) return d def test_modify(self): diff --git a/ldaptor/test/util.py b/ldaptor/test/util.py index 386d08ff..96c8bb76 100644 --- a/ldaptor/test/util.py +++ b/ldaptor/test/util.py @@ -1,16 +1,17 @@ +from io import BytesIO + from twisted.python import failure from twisted.internet import reactor, protocol, address, error from twisted.test import testutils from twisted.trial import unittest -from StringIO import StringIO class FakeTransport(protocol.FileWrapper): disconnecting = False disconnect_done = False def __init__(self, addr, peerAddr): - self.data = StringIO() + self.data = BytesIO() protocol.FileWrapper.__init__(self, self.data) self.addr = addr self.peerAddr = peerAddr diff --git a/tox.ini b/tox.ini index 2fecd033..71bc3c28 100644 --- a/tox.ini +++ b/tox.ini @@ -6,8 +6,13 @@ [tox] toxworkdir=build/ +; +; Tags explanation: +; * test - run trial tests +; * dev - run things in local dev mode envlist = - {py27}-{twlatest,dev,twtrunk},{py33,py34,py35}-{twtrunk,dev}, + py27-test-{twlatest,dev,twtrunk}, + {py33,py34,py35}-test-{twtrunk,dev}, linters, documentation, @@ -37,10 +42,13 @@ deps = ; All environment variables are passed. passenv = * +; Each command should have an associated tag as we use the same config for +; all the environments. +; This is done in order to reuse commands = {envpython} --version - trial --version - coverage run --rcfile={toxinidir}/.coveragerc -m twisted.trial {posargs:ldaptor} + test: trial --version + test: coverage run --rcfile={toxinidir}/.coveragerc -m twisted.trial {posargs:ldaptor} ; Only run on local dev env. dev: coverage report --show-missing dev: coverage xml -o {toxinidir}/build/coverage.xml