bug 654448 - refactor pyxpt to accept file-like objects for Typelib.{read,write}. r=khuey
authorTed Mielczarek <ted.mielczarek@gmail.com>
Thu, 03 May 2012 13:44:40 -0400
changeset 93004 cb5b1d8212fae795ef3ea5f9c91b9fe2e050fb59
parent 93003 90d25e0f6c68a34723e3cdd03f98999a07295421
child 93005 db0c14b8d2d84270c16c52298a72664a943a8204
push idunknown
push userunknown
push dateunknown
reviewerskhuey
bugs654448
milestone15.0a1
bug 654448 - refactor pyxpt to accept file-like objects for Typelib.{read,write}. r=khuey
xpcom/typelib/xpt/tools/runtests.py
xpcom/typelib/xpt/tools/xpt.py
--- a/xpcom/typelib/xpt/tools/runtests.py
+++ b/xpcom/typelib/xpt/tools/runtests.py
@@ -168,26 +168,42 @@ class TypelibCompareMixin:
         elif isinstance(t1, xpt.ArrayType):
             self.assertEqualTypes(t1.element_type, t2.element_type)
             self.assertEqual(t1.size_is_arg_num, t2.size_is_arg_num)
             self.assertEqual(t1.length_is_arg_num, t2.length_is_arg_num)
         elif isinstance(t1, xpt.StringWithSizeType) or isinstance(t1, xpt.WideStringWithSizeType):
             self.assertEqual(t1.size_is_arg_num, t2.size_is_arg_num)
             self.assertEqual(t1.length_is_arg_num, t2.length_is_arg_num)
 
-#TODO: test flags in various combinations
-class TestTypelibRoundtrip(unittest.TestCase, TypelibCompareMixin):
-    def checkRoundtrip(self, t):
+class TestTypelibReadWrite(unittest.TestCase, TypelibCompareMixin):
+    def test_read_file(self):
+        """
+        Test that a Typelib can be read/written from/to a file.
+        """
+        t = xpt.Typelib()
+        # add an unresolved interface
+        t.interfaces.append(xpt.Interface("IFoo"))
         fd, f = tempfile.mkstemp()
         os.close(fd)
         t.write(f)
         t2 = xpt.Typelib.read(f)
         os.remove(f)
         self.assert_(t2 is not None)
         self.assertEqualTypelibs(t, t2)
+
+
+#TODO: test flags in various combinations
+class TestTypelibRoundtrip(unittest.TestCase, TypelibCompareMixin):
+    def checkRoundtrip(self, t):
+        s = StringIO()
+        t.write(s)
+        s.seek(0)
+        t2 = xpt.Typelib.read(s)
+        self.assert_(t2 is not None)
+        self.assertEqualTypelibs(t, t2)
         
     def test_simple(self):
         t = xpt.Typelib()
         # add an unresolved interface
         t.interfaces.append(xpt.Interface("IFoo"))
         self.checkRoundtrip(t)
         
         t = xpt.Typelib()
@@ -742,68 +758,63 @@ class TestTypelibMerge(unittest.TestCase
         self.assert_(t1.interfaces[1].resolved)
         # Ensure that IRetval's method's param type has been updated.
         self.assertEqual(1, len(t1.interfaces[0].methods))
         self.assert_(t1.interfaces[0].methods[0].params[0].type.element_type.iface.resolved)
         self.assertEqual(t1.interfaces[1],
                          t1.interfaces[0].methods[0].params[0].type.element_type.iface)
 
 class TestXPTLink(unittest.TestCase):
-    def setUp(self):
-        self.tempdir = tempfile.mkdtemp()
-
-    def tearDown(self):
-        shutil.rmtree(self.tempdir, True)
-
-    def gettempfile(self):
-        fd, f = tempfile.mkstemp(dir=self.tempdir)
-        os.close(fd)
-        return f
-
     def test_xpt_link(self):
         """
         Test the xpt_link method.
         
         """
         t1 = xpt.Typelib()
         # add an unresolved interface
         t1.interfaces.append(xpt.Interface("IFoo"))
-        f1 = self.gettempfile()
+        f1 = StringIO()
         t1.write(f1)
+        f1.seek(0)
 
         t2 = xpt.Typelib()
         # add an unresolved interface
         t2.interfaces.append(xpt.Interface("IBar"))
-        f2 = self.gettempfile()
+        f2 = StringIO()
         t2.write(f2)
+        f2.seek(0)
 
-        f3 = self.gettempfile()
+        f3 = StringIO()
         xpt.xpt_link(f3, [f1, f2])
+        f3.seek(0)
         t3 = xpt.Typelib.read(f3)
         
         self.assertEqual(2, len(t3.interfaces))
         # Interfaces should wind up sorted
         self.assertEqual("IBar", t3.interfaces[0].name)
         self.assertEqual("IFoo", t3.interfaces[1].name)
 
         # Add some IID values
         t1 = xpt.Typelib()
         # add an unresolved interface
         t1.interfaces.append(xpt.Interface("IFoo", iid="11223344-5566-7788-9900-aabbccddeeff"))
-        f1 = self.gettempfile()
+        f1 = StringIO()
         t1.write(f1)
+        f1.seek(0)
 
         t2 = xpt.Typelib()
         # add an unresolved interface
         t2.interfaces.append(xpt.Interface("IBar", iid="44332211-6655-8877-0099-aabbccddeeff"))
-        f2 = self.gettempfile()
+        f2 = StringIO()
         t2.write(f2)
+        f2.seek(0)
 
-        f3 = self.gettempfile()
+        f3 = StringIO()
         xpt.xpt_link(f3, [f1, f2])
+        f3.seek(0)
         t3 = xpt.Typelib.read(f3)
         
         self.assertEqual(2, len(t3.interfaces))
         # Interfaces should wind up sorted
         self.assertEqual("IFoo", t3.interfaces[0].name)
         self.assertEqual("IBar", t3.interfaces[1].name)
 
 if __name__ == '__main__':
--- a/xpcom/typelib/xpt/tools/xpt.py
+++ b/xpcom/typelib/xpt/tools/xpt.py
@@ -38,18 +38,18 @@ http://www.mozilla.org/scriptable/typeli
 to provide type information for calling methods on XPCOM objects
 from scripting languages such as JavaScript.
 
 This module provides a set of classes representing the parts of
 a typelib in a high-level manner, as well as methods for reading
 and writing them from files.
 
 The usable public interfaces are currently:
-Typelib.read(filename) - read a typelib from a file on disk, return
-                         a Typelib object.
+Typelib.read(input_file) - read a typelib from a file on disk or file-like
+                           object, return a Typelib object.
 
 xpt_dump(filename)     - read a typelib from a file on disk, dump
                          the contents to stdout in a human-readable
                          format.
 
 Typelib()              - construct a new Typelib object
 Interface()            - construct a new Interface object
 Method()               - construct a new object representing a method
@@ -1046,65 +1046,78 @@ class Typelib(object):
         if offset == 0:
             return ""
         sz = map.find('\x00', data_pool + offset - 1)
         if sz == -1:
             return ""
         return map[data_pool + offset - 1:sz]
 
     @staticmethod
-    def read(filename):
+    def read(input_file):
         """
-        Read a typelib from the file named |filename| and return
-        the constructed Typelib object.
+        Read a typelib from |input_file| and return
+        the constructed Typelib object. |input_file| can be a filename
+        or a file-like object.
 
         """
-        with open(filename, "r+b") as f:
-            st = os.fstat(f.fileno())
-            map = f.read(st.st_size)
-            data = Typelib._header.unpack(map[:Typelib._header.size])
-            if data[0] != XPT_MAGIC:
-                raise FileFormatError, "Bad magic: %s" % data[0]
-            xpt = Typelib((data[1], data[2]))
-            xpt.filename = filename
-            num_interfaces = data[3]
-            file_length = data[4]
-            if file_length != st.st_size:
-                raise FileFormatError, "File is of wrong length, got %d bytes, expected %d" % (st.st_size, file_length)
-            #XXX: by spec this is a zero-based file offset. however,
-            # the xpt_xdr code always subtracts 1 from data offsets
-            # (because that's what you do in the data pool) so it
-            # winds up accidentally treating this as 1-based.
-            # Filed as: https://bugzilla.mozilla.org/show_bug.cgi?id=575343
-            interface_directory_offset = data[5] - 1
-            data_pool_offset = data[6]
-            # make a half-hearted attempt to read Annotations,
-            # since XPIDL doesn't produce any anyway.
-            start = Typelib._header.size
-            (anno, ) = struct.unpack(">B", map[start:start + struct.calcsize(">B")])
-            islast = anno & 0x80
-            tag = anno & 0x7F
-            if tag == 0: # EmptyAnnotation
-                xpt.annotations.append(None)
-            # We don't bother handling PrivateAnnotations or anything
-            
-            for i in range(num_interfaces):
-                # iid, name, namespace, interface_descriptor
-                start = interface_directory_offset + i * Interface._direntry.size
-                end = interface_directory_offset + (i+1) * Interface._direntry.size
-                ide = Interface._direntry.unpack(map[start:end])
-                iid = Typelib.iid_to_string(ide[0])
-                name = Typelib.read_string(map, data_pool_offset, ide[1])
-                namespace = Typelib.read_string(map, data_pool_offset, ide[2])
-                iface = Interface(name, iid, namespace)
-                iface._descriptor_offset = ide[3]
-                iface.xpt_filename = xpt.filename
-                xpt.interfaces.append(iface)
-            for iface in xpt.interfaces:
-                iface.read_descriptor(xpt, map, data_pool_offset)
+        filename = ""
+        data = None
+        expected_size = None
+        if isinstance(input_file, basestring):
+            filename = input_file
+            with open(input_file, "r+b") as f:
+                st = os.fstat(f.fileno())
+                data = f.read(st.st_size)
+                expected_size = st.st_size
+        else:
+            data = input_file.read()
+
+        (magic,
+         major_ver,
+         minor_ver,
+         num_interfaces,
+         file_length,
+         interface_directory_offset,
+         data_pool_offset) = Typelib._header.unpack(data[:Typelib._header.size])
+        if magic != XPT_MAGIC:
+            raise FileFormatError, "Bad magic: %s" % magic
+        xpt = Typelib((major_ver, minor_ver))
+        xpt.filename = filename
+        if expected_size and file_length != expected_size:
+            raise FileFormatError, "File is of wrong length, got %d bytes, expected %d" % (expected_size, file_length)
+        #XXX: by spec this is a zero-based file offset. however,
+        # the xpt_xdr code always subtracts 1 from data offsets
+        # (because that's what you do in the data pool) so it
+        # winds up accidentally treating this as 1-based.
+        # Filed as: https://bugzilla.mozilla.org/show_bug.cgi?id=575343
+        interface_directory_offset -= 1
+        # make a half-hearted attempt to read Annotations,
+        # since XPIDL doesn't produce any anyway.
+        start = Typelib._header.size
+        (anno, ) = struct.unpack(">B", data[start:start + struct.calcsize(">B")])
+        islast = anno & 0x80
+        tag = anno & 0x7F
+        if tag == 0: # EmptyAnnotation
+            xpt.annotations.append(None)
+        # We don't bother handling PrivateAnnotations or anything
+
+        for i in range(num_interfaces):
+            # iid, name, namespace, interface_descriptor
+            start = interface_directory_offset + i * Interface._direntry.size
+            end = interface_directory_offset + (i+1) * Interface._direntry.size
+            ide = Interface._direntry.unpack(data[start:end])
+            iid = Typelib.iid_to_string(ide[0])
+            name = Typelib.read_string(data, data_pool_offset, ide[1])
+            namespace = Typelib.read_string(data, data_pool_offset, ide[2])
+            iface = Interface(name, iid, namespace)
+            iface._descriptor_offset = ide[3]
+            iface.xpt_filename = xpt.filename
+            xpt.interfaces.append(iface)
+        for iface in xpt.interfaces:
+            iface.read_descriptor(xpt, data, data_pool_offset)
         return xpt
 
     def __repr__(self):
         return "<Typelib with %d interfaces>" % len(self.interfaces)
 
     def _sanityCheck(self):
         """
         Check certain assumptions about data contained in this typelib.
@@ -1155,24 +1168,28 @@ class Typelib(object):
         fd.write(struct.pack(">B", 0x80))
         # now write the interface directory
         #XXX: bug-compatible with existing xpt lib, put it one byte
         # ahead of where it's supposed to be.
         fd.seek(interface_directory_offset - 1)
         for i in self.interfaces:
             i.write_directory_entry(fd)
 
-    def write(self, filename):
+    def write(self, output_file):
         """
-        Write the contents of this typelib to the file named |filename|.
+        Write the contents of this typelib to |output_file|,
+        which can be either a filename or a file-like object.
 
         """
         self._sanityCheck()
-        with open(filename, "wb") as f:
-            self.writefd(f)
+        if isinstance(output_file, basestring):
+            with open(output_file, "wb") as f:
+                self.writefd(f)
+        else:
+            self.writefd(output_file)
 
     def merge(self, other, sanitycheck=True):
         """
         Merge the contents of Typelib |other| into this typelib.
         If |sanitycheck| is False, don't sort the interface table
         after merging.
 
         """
@@ -1316,17 +1333,17 @@ def xpt_dump(file):
 
     """
     t = Typelib.read(file)
     t.dump(sys.stdout)
 
 def xpt_link(dest, inputs):
     """
     Link all of the xpt files in |inputs| together and write the
-    result ot |dest|.
+    result to |dest|. All parameters may be filenames or file-like objects.
 
     """
     if not inputs:
         print >>sys.stderr, "Usage: xpt_link <destination file> <input files>"
         return
     t1 = Typelib.read(inputs[0])
     for f in inputs[1:]:
         t2 = Typelib.read(f)