Clean up py3compat a bit and add some tests
authorEli Bendersky <eliben@gmail.com>
Wed, 30 Aug 2017 13:28:56 +0000 (06:28 -0700)
committerEli Bendersky <eliben@gmail.com>
Wed, 30 Aug 2017 13:28:56 +0000 (06:28 -0700)
elftools/common/py3compat.py
test/test_py3compat.py [new file with mode: 0644]

index f48104364b8ff64df4fcd1ad8a0563264af8a860..9285d66b279aa9326adece25e3e38416699002a0 100644 (file)
@@ -1,7 +1,7 @@
 #-------------------------------------------------------------------------------
 # elftools: common/py3compat.py
 #
-# Python 3 compatibility code
+# Python 2/3 compatibility code
 #
 # Eli Bendersky (eliben@gmail.com)
 # This code is in the public domain
@@ -15,15 +15,22 @@ if PY3:
     StringIO = io.StringIO
     BytesIO = io.BytesIO
 
-    _iterkeys = "keys"
-    _iteritems = "items"
-    _itervalues = "values"
+    # Functions for acting on bytestrings and strings. In Python 2 and 3,
+    # strings and bytes are the same and chr/ord can be used to convert between
+    # numeric byte values and their string pepresentations. In Python 3, bytes
+    # and strings are different types and bytes hold numeric values when
+    # iterated over.
 
     def bytes2str(b): return b.decode('latin-1')
     def str2bytes(s): return s.encode('latin-1')
-    def int2byte(i):return bytes((i,))
+    def int2byte(i): return bytes((i,))
     def byte2int(b): return b
+
     def iterbytes(b):
+        """Return an iterator over the elements of a bytes object.
+        
+        For example, for b'abc' yields b'a', b'b' and then b'c'.
+        """
         for i in range(len(b)):
             yield b[i:i+1]
 
@@ -34,10 +41,6 @@ else:
     import cStringIO
     StringIO = BytesIO = cStringIO.StringIO
 
-    _iterkeys = "iterkeys"
-    _iteritems = "iteritems"
-    _itervalues = "itervalues"
-
     def bytes2str(b): return b
     def str2bytes(s): return s
     int2byte = chr
@@ -52,13 +55,12 @@ else:
 
 def iterkeys(d):
     """Return an iterator over the keys of a dictionary."""
-    return getattr(d, _iterkeys)()
+    return getattr(d, 'keys' if PY3 else 'iterkeys')()
 
 def itervalues(d):
     """Return an iterator over the values of a dictionary."""
-    return getattr(d, _itervalues)()
+    return getattr(d, 'values' if PY3 else 'itervalues')()
 
 def iteritems(d):
     """Return an iterator over the items of a dictionary."""
-    return getattr(d, _iteritems)()
-
+    return getattr(d, 'items' if PY3 else 'iteritems')()
diff --git a/test/test_py3compat.py b/test/test_py3compat.py
new file mode 100644 (file)
index 0000000..9c8fb13
--- /dev/null
@@ -0,0 +1,30 @@
+#-------------------------------------------------------------------------------
+# elftools tests
+#
+# Eli Bendersky (eliben@gmail.com)
+# This code is in the public domain
+#-------------------------------------------------------------------------------
+import unittest
+
+from elftools.common.py3compat import (iterbytes, iterkeys, itervalues,
+                                       iteritems)
+
+
+class TestPy3Compat(unittest.TestCase):
+    def test_iterbytes(self):
+        bi = iterbytes(b'fo1')
+        self.assertEqual(next(bi), b'f')
+        self.assertEqual(next(bi), b'o')
+        self.assertEqual(next(bi), b'1')
+        with self.assertRaises(StopIteration):
+            next(bi)
+
+    def test_iterdict(self):
+        d = {1: 'foo', 2: 'bar'}
+        self.assertEqual(list(sorted(iterkeys(d))), [1, 2])
+        self.assertEqual(list(sorted(itervalues(d))), ['bar', 'foo'])
+        self.assertEqual(list(sorted(iteritems(d))), [(1, 'foo'), (2, 'bar')])
+
+
+if __name__ == '__main__':
+    unittest.main()