Code

Add compatibility package to allow us to deal with Python versions 2.3
authorrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Thu, 12 Mar 2009 02:52:56 +0000 (02:52 +0000)
committerrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Thu, 12 Mar 2009 02:52:56 +0000 (02:52 +0000)
through to 2.6.

Outstanding issues noted in roundup/anypy/TODO.txt

git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/roundup/trunk@4181 57a73879-2fb5-44c3-a270-3262357dd7e2

19 files changed:
CHANGES.txt
doc/upgrading.txt
roundup/admin.py
roundup/anypy/README.txt [new file with mode: 0644]
roundup/anypy/TODO.txt [new file with mode: 0644]
roundup/anypy/__init__.py [new file with mode: 0644]
roundup/anypy/hashlib_.py [new file with mode: 0644]
roundup/anypy/sets_.py [new file with mode: 0644]
roundup/backends/indexer_common.py
roundup/backends/indexer_rdbms.py
roundup/hyperdb.py
roundup/install_util.py
roundup/password.py
scripts/import_sf.py
setup.py
share/roundup/templates/classic/detectors/nosyreaction.py
test/db_test_base.py
test/test_anypy_hashlib.py [new file with mode: 0644]
test/test_hyperdbvals.py

index 0cf9de674a8f9bcd04a370cdf4b2c02649845ca1..5df3f55d4ab03be65235d3e1b1a2bfb5c1fbc7b2 100644 (file)
@@ -10,6 +10,7 @@ Fixes:
 - HTML file uploads served as application/octet-stream
 - New item action reject creation of new users
 - Item retirement was not being controlled
+- Roundup is now compatible with Python 2.6
 - XXX need to include Stefan's changes in here too
 
 
index 02a0489a37d015566e092eab9caa039ee072bd5f..39f27f423870964fc6fec708a4ee0b07d502d944 100644 (file)
@@ -61,6 +61,22 @@ Should be replaced with::
      </form>
 
 
+Fix for Python 2.6+ users
+-------------------------
+
+If you use Python 2.6 you should edit your tracker's
+``detectors/nosyreaction.py`` file to change::
+
+   import sets
+
+at the top to::
+
+   from roundup.anypy.sets_ import set
+
+and then all instances of ``sets.Set()`` to ``set()`` in the later code.
+
+
+
 Trackers currently allowing HTML file uploading
 -----------------------------------------------
 
index 14b7c42c40228528db3d3e2fb7aadad3e5e7cff2..91273bcd125a85c531a9762532ef659159448db0 100644 (file)
@@ -729,7 +729,7 @@ Erase it? Y/N: """))
                 print _('%(key)s: %(value)s')%locals()
 
     def do_display(self, args):
-        """Usage: display designator[,designator]*
+        ''"""Usage: display designator[,designator]*
         Show the property values for the given node(s).
 
         This lists the properties and their associated values for the given
diff --git a/roundup/anypy/README.txt b/roundup/anypy/README.txt
new file mode 100644 (file)
index 0000000..7b0fe72
--- /dev/null
@@ -0,0 +1,57 @@
+roundup.anypy package - Python version compatibility layer
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Roundup currently supports Python 2.3 to 2.6; however, some modules
+have been introduced, while others have been deprecated.  The modules
+in this package provide the functionalities which are used by Roundup
+
+- adapting the most recent Python usage
+- using new built-in functionality
+- avoiding deprecation warnings
+
+Use the modules in this package to preserve Roundup's compatibility.
+
+sets_: sets compatibility module
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Since Python 2.4, there is a built-in type 'set'; therefore, the 'sets'
+module is deprecated since version 2.6.  As far as Roundup is concerned,
+the usage is identical; see 
+http://docs.python.org/library/sets.html#comparison-to-the-built-in-set-types
+
+Uses the built-in type 'set' if available, and thus avoids
+deprecation warnings. Simple usage:
+
+Change all::
+  from sets import Set
+
+to::
+  from roundup.anypy.sets_ import set
+
+and use 'set' instead of 'Set' (or sets.Set, respectively).
+To avoid unnecessary imports, you can::
+
+  try:
+      set
+  except NameError:
+      from roundup.anypy.sets_ import set
+
+hashlib_: md5/sha/hashlib compatibility
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The md5 and sha modules are deprecated since Python 2.6; the hashlib
+module, introduced with Python 2.5, is recommended instead.
+
+Change all::
+  import md5
+  md5.md5(), md5.new()
+  import sha
+  sha.sha(), sha.new()
+
+to::
+  from roundup.anypy.hashlib_ import md5
+  md5()
+  from roundup.anypy.hashlib_ import sha1
+  sha1()
+
+# vim: si
diff --git a/roundup/anypy/TODO.txt b/roundup/anypy/TODO.txt
new file mode 100644 (file)
index 0000000..12103ae
--- /dev/null
@@ -0,0 +1,27 @@
+Python compatiblity TODO
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+- the popen2 module is deprecated as of Python 2.6;
+  the subprocess module is available since Python 2.4,
+  thus a roundup.anypy.subprocess_ module is needed
+
+- the MimeWriter module is deprecated as of Python 2.6.  The email package is
+  available since Python 2.2, thus we should manage without a ...email_
+  module;  however, it has suffered some API changes over the time
+  (http://docs.python.org/library/email.html#package-history),
+  so this is not sure.
+
+  Here's an incomplete replacement table:
+
+  MimeWriter usage                        checked for
+  -> email usage                          Python ...
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~
+  MimeWriter.MimeWriter
+  -> email.Message.Message                (2.3)
+
+  MimeWriter.MimeWrite.addheader
+  -> email.Message.Message.add_header     (2.3)
+
+- test.test_sqlite.sqliteDBTest.testStringUnicode fails
+
+# vim: si
diff --git a/roundup/anypy/__init__.py b/roundup/anypy/__init__.py
new file mode 100644 (file)
index 0000000..21eee1e
--- /dev/null
@@ -0,0 +1,7 @@
+"""
+roundup.anypy - compatibility layer for any Python 2.3+
+"""
+VERSION = '.'.join(map(str,
+                       (0,
+                        1,  # hashlib_, sets_
+                        )))
diff --git a/roundup/anypy/hashlib_.py b/roundup/anypy/hashlib_.py
new file mode 100644 (file)
index 0000000..9365b97
--- /dev/null
@@ -0,0 +1,11 @@
+"""
+anypy.hashlib_: encapsulation of hashlib/md5/sha1/sha
+"""
+
+try:
+    from hashlib import md5, sha1 # new in Python 2.5
+except ImportError:
+    from md5 import md5           # deprecated in Python 2.6
+    from sha import sha as sha1   # deprecated in Python 2.6
+
+# vim: ts=8 sts=4 sw=4 si
diff --git a/roundup/anypy/sets_.py b/roundup/anypy/sets_.py
new file mode 100644 (file)
index 0000000..e1f2f30
--- /dev/null
@@ -0,0 +1,30 @@
+"""
+anypy.sets_: sets compatibility module
+
+uses the built-in type 'set' if available, and thus avoids
+deprecation warnings. Simple usage:
+
+Change all
+    from sets import Set
+to
+    from roundup.anypy.sets_ import set
+
+and use 'set' instead of 'Set'.
+To avoid unnecessary imports, you can:
+
+    try:
+        set
+    except NameError:
+        from roundup.anypy.sets_ import set
+
+see:
+http://docs.python.org/library/sets.html#comparison-to-the-built-in-set-types
+
+"""
+
+try:
+    set = set                     # built-in since Python 2.4
+except NameError, TypeError:
+    from sets import Set as set   # deprecated as of Python 2.6
+
+# vim: ts=8 sts=4 sw=4 si et
index 496475d601dcb8cc2c2130f50d15df5394f64d0b..5b5dd60c69f15fe3b15f0885aadcc2bea99b7d62 100644 (file)
@@ -1,5 +1,7 @@
 #$Id: indexer_common.py,v 1.11 2008-09-11 19:41:07 schlatterbeck Exp $
-import re, sets
+import re
+# Python 2.3 ... 2.6 compatibility:
+from roundup.anypy.sets_ import set
 
 from roundup import hyperdb
 
@@ -17,7 +19,7 @@ def _isLink(propclass):
 
 class Indexer:
     def __init__(self, db):
-        self.stopwords = sets.Set(STOPWORDS)
+        self.stopwords = set(STOPWORDS)
         for word in db.config[('main', 'indexer_stopwords')]:
             self.stopwords.add(word)
 
@@ -28,11 +30,11 @@ class Indexer:
         return self.find(search_terms)
 
     def search(self, search_terms, klass, ignore={}):
-        '''Display search results looking for [search, terms] associated
+        """Display search results looking for [search, terms] associated
         with the hyperdb Class "klass". Ignore hits on {class: property}.
 
         "dre" is a helper, not an argument.
-        '''
+        """
         # do the index lookup
         hits = self.getHits(search_terms, klass)
         if not hits:
index 2547300f693d22e3bb7ac825a89937ba37da470a..b992c70d09a0f86c9d0e7beb70c5f19b5e9033eb 100644 (file)
@@ -1,9 +1,11 @@
 #$Id: indexer_rdbms.py,v 1.18 2008-09-01 00:43:02 richard Exp $
-''' This implements the full-text indexer over two RDBMS tables. The first
+""" This implements the full-text indexer over two RDBMS tables. The first
 is a mapping of words to occurance IDs. The second maps the IDs to (Class,
 propname, itemid) instances.
-'''
-import re, sets
+"""
+import re
+# Python 2.3 ... 2.6 compatibility:
+from roundup.anypy.sets_ import set
 
 from roundup.backends.indexer_common import Indexer as IndexerBase
 
@@ -14,27 +16,27 @@ class Indexer(IndexerBase):
         self.reindex = 0
 
     def close(self):
-        '''close the indexing database'''
+        """close the indexing database"""
         # just nuke the circular reference
         self.db = None
 
     def save_index(self):
-        '''Save the changes to the index.'''
+        """Save the changes to the index."""
         # not necessary - the RDBMS connection will handle this for us
         pass
 
     def force_reindex(self):
-        '''Force a reindexing of the database.  This essentially
+        """Force a reindexing of the database.  This essentially
         empties the tables ids and index and sets a flag so
-        that the databases are reindexed'''
+        that the databases are reindexed"""
         self.reindex = 1
 
     def should_reindex(self):
-        '''returns True if the indexes need to be rebuilt'''
+        """returns True if the indexes need to be rebuilt"""
         return self.reindex
 
     def add_text(self, identifier, text, mime_type='text/plain'):
-        ''' "identifier" is  (classname, itemid, property) '''
+        """ "identifier" is  (classname, itemid, property) """
         if mime_type != 'text/plain':
             return
 
@@ -65,7 +67,7 @@ class Indexer(IndexerBase):
         text = unicode(text, "utf-8", "replace").upper()
         wordlist = [w.encode("utf-8", "replace")
                 for w in re.findall(r'(?u)\b\w{2,25}\b', text)]
-        words = sets.Set()
+        words = set()
         for word in wordlist:
             if self.is_stopword(word): continue
             if len(word) > 25: continue
@@ -77,10 +79,10 @@ class Indexer(IndexerBase):
         self.db.cursor.executemany(sql, words)
 
     def find(self, wordlist):
-        '''look up all the words in the wordlist.
+        """look up all the words in the wordlist.
         If none are found return an empty dictionary
         * more rules here
-        '''
+        """
         if not wordlist:
             return []
 
index 856f2d4e040a1ad84fe6ae47babef5843d0c790e..91a9fef974b905b5b1163da219a4b61f0fd7fd4a 100644 (file)
@@ -22,7 +22,8 @@ __docformat__ = 'restructuredtext'
 
 # standard python modules
 import os, re, shutil, weakref
-from sets import Set
+# Python 2.3 ... 2.6 compatibility:
+from roundup.anypy.sets_ import set
 
 # roundup modules
 import date, password
@@ -193,7 +194,7 @@ class Multilink(_Pointer):
         # definitely in the new list (in case of e.g.
         # <propname>=A,+B, which should replace the old
         # list with A,B)
-        set = 1
+        do_set = 1
         newvalue = []
         for item in value:
             item = item.strip()
@@ -206,10 +207,10 @@ class Multilink(_Pointer):
             if item.startswith('-'):
                 remove = 1
                 item = item[1:]
-                set = 0
+                do_set = 0
             elif item.startswith('+'):
                 item = item[1:]
-                set = 0
+                do_set = 0
 
             # look up the value
             itemid = convertLinkValue(db, propname, self, item)
@@ -228,7 +229,7 @@ class Multilink(_Pointer):
 
         # that's it, set the new Multilink property value,
         # or overwrite it completely
-        if set:
+        if do_set:
             value = newvalue
         else:
             value = curvalue
@@ -487,7 +488,7 @@ class Proptree(object):
             v = self._val
             if not isinstance(self._val, type([])):
                 v = [self._val]
-            vals = Set(v)
+            vals = set(v)
             vals.intersection_update(val)
             self._val = [v for v in vals]
         else:
index 2e59a5baadee9713e572a408f5e755ba62938245..51becb0880fffc929ee93ee67a914d96392ababa 100644 (file)
@@ -21,7 +21,8 @@
 """
 __docformat__ = 'restructuredtext'
 
-import os, sha, shutil
+import os, shutil
+from roundup.anypy.hashlib_ import sha1
 
 sgml_file_types = [".xml", ".ent", ".html"]
 hash_file_types = [".py", ".sh", ".conf", ".cgi"]
@@ -59,7 +60,7 @@ def checkDigest(filename):
     del lines[-1]
 
     # calculate current digest
-    digest = sha.new()
+    digest = sha1()
     for line in lines:
         digest.update(line)
 
@@ -74,7 +75,7 @@ class DigestFile:
 
     def __init__(self, filename):
         self.filename = filename
-        self.digest = sha.new()
+        self.digest = sha1()
         self.file = open(self.filename, "w")
 
     def write(self, data):
index 6a555bb908ae9c5187db03dc414a6d1545251713..201a6a979edd837918fdb5c6dd8e16c8039994c4 100644 (file)
 """
 __docformat__ = 'restructuredtext'
 
-import sha, md5, re, string, random
+import re, string, random
+from roundup.anypy.hashlib_ import md5, sha1
 try:
     import crypt
-except:
+except ImportError:
     crypt = None
-    pass
 
 class PasswordValueError(ValueError):
-    ''' The password value is not valid '''
+    """ The password value is not valid """
     pass
 
 def encodePassword(plaintext, scheme, other=None):
-    '''Encrypt the plaintext password.
-    '''
+    """Encrypt the plaintext password.
+    """
     if plaintext is None:
         plaintext = ""
     if scheme == 'SHA':
-        s = sha.sha(plaintext).hexdigest()
+        s = sha1(plaintext).hexdigest()
     elif scheme == 'MD5':
-        s = md5.md5(plaintext).hexdigest()
+        s = md5(plaintext).hexdigest()
     elif scheme == 'crypt' and crypt is not None:
         if other is not None:
             salt = other
@@ -59,7 +59,7 @@ def generatePassword(length=8):
     return ''.join([random.choice(chars) for x in range(length)])
 
 class Password:
-    '''The class encapsulates a Password property type value in the database.
+    """The class encapsulates a Password property type value in the database.
 
     The encoding of the password is one if None, 'SHA', 'MD5' or 'plaintext'.
     The encodePassword function is used to actually encode the password from
@@ -79,13 +79,13 @@ class Password:
     1
     >>> 'not sekrit' != p
     1
-    '''
+    """
 
     default_scheme = 'SHA'        # new encryptions use this scheme
     pwre = re.compile(r'{(\w+)}(.+)')
 
     def __init__(self, plaintext=None, scheme=None, encrypted=None):
-        '''Call setPassword if plaintext is not None.'''
+        """Call setPassword if plaintext is not None."""
         if scheme is None:
             scheme = self.default_scheme
         if plaintext is not None:
@@ -98,9 +98,9 @@ class Password:
             self.plaintext = None
 
     def unpack(self, encrypted, scheme=None):
-        '''Set the password info from the scheme:<encryted info> string
+        """Set the password info from the scheme:<encryted info> string
            (the inverse of __str__)
-        '''
+        """
         m = self.pwre.match(encrypted)
         if m:
             self.scheme = m.group(1)
@@ -111,7 +111,7 @@ class Password:
             self.setPassword(encrypted, scheme)
 
     def setPassword(self, plaintext, scheme=None):
-        '''Sets encrypts plaintext.'''
+        """Sets encrypts plaintext."""
         if scheme is None:
             scheme = self.default_scheme
         self.scheme = scheme
@@ -119,7 +119,7 @@ class Password:
         self.plaintext = plaintext
 
     def __cmp__(self, other):
-        '''Compare this password against another password.'''
+        """Compare this password against another password."""
         # check to see if we're comparing instances
         if isinstance(other, Password):
             if self.scheme != other.scheme:
@@ -133,7 +133,7 @@ class Password:
             self.password))
 
     def __str__(self):
-        '''Stringify the encrypted password for database storage.'''
+        """Stringify the encrypted password for database storage."""
         if self.password is None:
             raise ValueError, 'Password not set'
         return '{%s}%s'%(self.scheme, self.password)
index c5ab933db89af90478e8aebe13b6f9be70bdc52e..b64b616da0ce0b500124240e85fa28e31ff03fcd 100644 (file)
@@ -1,4 +1,4 @@
-''' Import tracker data from Sourceforge.NET
+""" Import tracker data from Sourceforge.NET
 
 This script needs four steps to work:
 
@@ -19,9 +19,11 @@ This script needs four steps to work:
     roundup-admin -i <tracker home> import /tmp/imported
 
 And you're done!
-'''
+"""
 
-import sys, sets, os, csv, time, urllib2, httplib, mimetypes, urlparse
+import sys, os, csv, time, urllib2, httplib, mimetypes, urlparse
+# Python 2.3 ... 2.6 compatibility:
+from roundup.anypy.sets_ import set
 
 try:
     import cElementTree as ElementTree
@@ -53,8 +55,8 @@ def get_url(aid):
 def fetch_files(xml_file, file_dir):
     """ Fetch files referenced in the xml_file into the dir file_dir. """
     root = ElementTree.parse(xml_file).getroot()
-    to_fetch = sets.Set()
-    deleted = sets.Set()
+    to_fetch = set()
+    deleted = set()
     for artifact in root.find('artifacts'):
         for field in artifact.findall('field'):
             if field.get('name') == 'artifact_id':
@@ -73,7 +75,7 @@ def fetch_files(xml_file, file_dir):
                     deleted.add((aid, fid))
     to_fetch = to_fetch - deleted
 
-    got = sets.Set(os.listdir(file_dir))
+    got = set(os.listdir(file_dir))
     to_fetch = to_fetch - got
 
     # load cached urls (sigh)
@@ -122,10 +124,10 @@ def import_xml(tracker_home, xml_file, file_dir):
 
     # parse out the XML
     artifacts = []
-    categories = sets.Set()
-    users = sets.Set()
-    add_files = sets.Set()
-    remove_files = sets.Set()
+    categories = set()
+    users = set()
+    add_files = set()
+    remove_files = set()
     for artifact in root.find('artifacts'):
         d = {}
         op = {}
@@ -254,7 +256,7 @@ def import_xml(tracker_home, xml_file, file_dir):
         else:
             d['status'] = unread
 
-        nosy = sets.Set()
+        nosy = set()
         for message in artifact.get('messages', []):
             authid = users[message['user_name']]
             if not message['body']: continue
@@ -338,7 +340,7 @@ def import_xml(tracker_home, xml_file, file_dir):
     f.close()
 
 def convert_message(content, id):
-    ''' Strip off the useless sf message header crap '''
+    """ Strip off the useless sf message header crap """
     if content[:14] == 'Logged In: YES':
         return '\n'.join(content.splitlines()[3:]).strip()
     return content
index 3a0209762d63fa4e66390c17e5056259b382919d..e2e226d7889d0f0caaca584192a440f96da340b6 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -58,6 +58,7 @@ def main():
     # template munching
     packagelist = [
         'roundup',
+        'roundup.anypy',
         'roundup.cgi',
         'roundup.cgi.PageTemplates',
         'roundup.cgi.TAL',
index c732fd013307e779eb71cdc7b1c6cbe10bdfb5f4..ae007952b8d0cc300b1c9472758e75c8d3598bd2 100644 (file)
@@ -17,7 +17,8 @@
 # 
 #$Id: nosyreaction.py,v 1.4 2005-04-04 08:47:14 richard Exp $
 
-import sets
+# Python 2.3 ... 2.6 compatibility:
+from roundup.anypy.sets_ import set
 
 from roundup import roundupdb, hyperdb
 
@@ -67,7 +68,7 @@ def updatenosy(db, cl, nodeid, newvalues):
     '''Update the nosy list for changes to the assignedto
     '''
     # nodeid will be None if this is a new node
-    current_nosy = sets.Set()
+    current_nosy = set()
     if nodeid is None:
         ok = ('new', 'yes')
     else:
@@ -87,7 +88,7 @@ def updatenosy(db, cl, nodeid, newvalues):
                 continue
             current_nosy.add(value)
 
-    new_nosy = sets.Set(current_nosy)
+    new_nosy = set(current_nosy)
 
     # add assignedto(s) to the nosy list
     if newvalues.has_key('assignedto') and newvalues['assignedto'] is not None:
index 94d34d333960e28c197ff282518bfb7217f29950..241fd9528e067f8c4368c3c55a0ed864fec4307c 100644 (file)
@@ -17,7 +17,9 @@
 #
 # $Id: db_test_base.py,v 1.101 2008-08-19 01:40:59 richard Exp $
 
-import unittest, os, shutil, errno, imp, sys, time, pprint, sets, base64, os.path
+import unittest, os, shutil, errno, imp, sys, time, pprint, base64, os.path
+# Python 2.3 ... 2.6 compatibility:
+from roundup.anypy.sets_ import set
 
 from roundup.hyperdb import String, Password, Link, Multilink, Date, \
     Interval, DatabaseError, Boolean, Number, Node
@@ -284,7 +286,7 @@ class DBTest(MyTestCase):
             # try a couple of the built-in iterable types to make
             # sure that we accept them and handle them properly
             # try a set as input for the multilink
-            nid = self.db.issue.create(title="spam", nosy=sets.Set(u1))
+            nid = self.db.issue.create(title="spam", nosy=set(u1))
             if commit: self.db.commit()
             self.assertEqual(self.db.issue.get(nid, "nosy"), [u1])
             self.assertRaises(TypeError, self.db.issue.set, nid,
@@ -294,7 +296,7 @@ class DBTest(MyTestCase):
             if commit: self.db.commit()
             self.assertEqual(self.db.issue.get(nid, "nosy"), [])
             # make sure we accept a frozen set
-            self.db.issue.set(nid, nosy=sets.Set([u1,u2]))
+            self.db.issue.set(nid, nosy=set([u1,u2]))
             if commit: self.db.commit()
             l = [u1,u2]; l.sort()
             m = self.db.issue.get(nid, "nosy"); m.sort()
@@ -487,12 +489,12 @@ class DBTest(MyTestCase):
         others = nodeids[:]
         others.remove('1')
 
-        self.assertEqual(sets.Set(self.db.status.getnodeids()),
-            sets.Set(nodeids))
-        self.assertEqual(sets.Set(self.db.status.getnodeids(retired=True)),
-            sets.Set(['1']))
-        self.assertEqual(sets.Set(self.db.status.getnodeids(retired=False)),
-            sets.Set(others))
+        self.assertEqual(set(self.db.status.getnodeids()),
+            set(nodeids))
+        self.assertEqual(set(self.db.status.getnodeids(retired=True)),
+            set(['1']))
+        self.assertEqual(set(self.db.status.getnodeids(retired=False)),
+            set(others))
 
         self.assert_(self.db.status.is_retired('1'))
 
@@ -2054,7 +2056,7 @@ class SchemaTest(MyTestCase):
         self.db.getjournal('a', aid)
 
 class RDBMSTest:
-    ''' tests specific to RDBMS backends '''
+    """ tests specific to RDBMS backends """
     def test_indexTest(self):
         self.assertEqual(self.db.sql_index_exists('_issue', '_issue_id_idx'), 1)
         self.assertEqual(self.db.sql_index_exists('_issue', '_issue_x_idx'), 0)
diff --git a/test/test_anypy_hashlib.py b/test/test_anypy_hashlib.py
new file mode 100644 (file)
index 0000000..cdfb4f1
--- /dev/null
@@ -0,0 +1,139 @@
+#! /usr/bin/env python
+import unittest
+import warnings
+
+import roundup.anypy.hashlib_
+
+class UntestableWarning(Warning):
+    pass
+
+# suppress deprecation warnings; -> warnings.filters[0]:
+warnings.simplefilter(action='ignore',
+                      category=DeprecationWarning,
+                      append=0)
+
+try:
+    import sha
+except:
+    warnings.warn('sha module functions', UntestableWarning)
+    sha = None
+
+try:
+    import md5
+except:
+    warnings.warn('md5 module functions', UntestableWarning)
+    md5 = None
+
+try:
+    import hashlib
+except:
+    warnings.warn('hashlib module functions', UntestableWarning)
+    hashlib = None
+
+# preserve other warning filters set elsewhere:
+del warnings.filters[0]
+
+if not ((sha or md5) and hashlib):
+    warnings.warn('anypy.hashlib_ continuity', UntestableWarning)
+
+class TestCase_anypy_hashlib(unittest.TestCase):
+    """test the hashlib compatibility layer"""
+
+    testdata = (
+           ('',
+            'da39a3ee5e6b4b0d3255bfef95601890afd80709',
+            'd41d8cd98f00b204e9800998ecf8427e'),
+           ('Strange women lying in ponds distributing swords'
+            ' is no basis for a system of government.',
+            'da9b2b00466b00411038c057681fe67349f92d7d',
+            'b71c5178d316ec446c25386f4857d4f9'),
+           ('Ottos Mops hopst fort',
+            'fdf7e6c54cf07108c86edd8d47c90450671c2c81',
+            'a3dce74bee59ee92f1038263e5252500'),
+           ('Dieser Satz kein Verb',
+            '3030aded8a079b92043a39dc044a35443959dcdd',
+            '2f20c69d514228011fb0d32e14dd5d80'),
+           )
+
+    # the following two are always excecuted: 
+    def test_sha1_expected_anypy(self):
+        """...anypy.hashlib_.sha1().hexdigest() yields expected results"""
+        for src, SHA, MD5 in self.testdata:
+            self.assertEqual(roundup.anypy.hashlib_.sha1(src).hexdigest(), SHA)
+
+    def test_md5_expected_anypy(self):
+        """...anypy.hashlib_.md5().hexdigest() yields expected results"""
+        for src, SHA, MD5 in self.testdata:
+            self.assertEqual(roundup.anypy.hashlib_.md5(src).hexdigest(), MD5)
+
+    # execution depending on availability of modules: 
+    if md5 and hashlib:
+        def test_md5_continuity(self):
+            """md5.md5().digest() == hashlib.md5().digest()"""
+            if md5.md5 is hashlib.md5:
+                return
+            else:
+                for s, i1, i2 in self.testdata:
+                    self.assertEqual(md5.md5(s).digest(),
+                                     hashlib.md5().digest())
+
+    if md5:
+        def test_md5_expected(self):
+            """md5.md5().hexdigest() yields expected results"""
+            for src, SHA, MD5 in self.testdata:
+                self.assertEqual(md5.md5(src).hexdigest(), MD5)
+
+        def test_md5_new_expected(self):
+            """md5.new is md5.md5, or at least yields expected results"""
+            if md5.new is md5.md5:
+                return
+            else:
+                for src, SHA, MD5 in self.testdata:
+                    self.assertEqual(md5.new(src).hexdigest(), MD5)
+
+    if sha and hashlib:
+        def test_sha1_continuity(self):
+            """sha.sha().digest() == hashlib.sha1().digest()"""
+            if sha.sha is hashlib.sha1:
+                return
+            else:
+                for s in self.testdata:
+                    self.assertEqual(sha.sha(s).digest(),
+                                     hashlib.sha1().digest())
+
+    if sha:
+        def test_sha_expected(self):
+            """sha.sha().hexdigest() yields expected results"""
+            for src, SHA, MD5 in self.testdata:
+                self.assertEqual(sha.sha(src).hexdigest(), SHA)
+
+        # fails for me with Python 2.3; unittest module bug?
+        def test_sha_new_expected(self):
+            """sha.new is sha.sha, or at least yields expected results"""
+            if sha.new is sha.sha:
+                return
+            else:
+                for src, SHA, MD5 in self.testdata:
+                    self.assertEqual(sha.new(src).hexdigest(), SHA)
+
+    if hashlib:
+        def test_sha1_expected_hashlib(self):
+            """hashlib.sha1().hexdigest() yields expected results"""
+            for src, SHA, MD5 in self.testdata:
+                self.assertEqual(hashlib.sha1(src).hexdigest(), SHA)
+
+        def test_md5_expected_hashlib(self):
+            """hashlib.md5().hexdigest() yields expected results"""
+            for src, SHA, MD5 in self.testdata:
+                self.assertEqual(hashlib.md5(src).hexdigest(), MD5)
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(TestCase_anypy_hashlib))
+    return suite
+
+if __name__ == '__main__':
+    runner = unittest.TextTestRunner()
+    unittest.main(testRunner=runner)
+
+# vim: ts=8 et sts=4 sw=4 si
index 9d01d4df681b685f25eda4caad8e3acbde7183ad..9b644421605a36f7a5afc70ad49c9c4e5da46c56 100644 (file)
@@ -10,7 +10,8 @@
 #
 # $Id: test_hyperdbvals.py,v 1.3 2006-08-18 01:26:19 richard Exp $
 
-import unittest, os, shutil, errno, sys, difflib, cgi, re, sha
+import unittest, os, shutil, errno, sys, difflib, cgi, re
+from roundup.anypy.hashlib_ import sha1
 
 from roundup import init, instance, password, hyperdb, date
 
@@ -80,7 +81,7 @@ class RawToHyperdbTest(unittest.TestCase):
         self.assert_(isinstance(val, password.Password))
         val = self._test('password', '{crypt}a string')
         self.assert_(isinstance(val, password.Password))
-        s = sha.sha('a string').hexdigest()
+        s = sha1('a string').hexdigest()
         val = self._test('password', '{SHA}'+s)
         self.assert_(isinstance(val, password.Password))
         self.assertEqual(val, 'a string')