bug 654448 - refactor pyxpt to accept file-like objects for Typelib.{read,write}. r=khuey
--- 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)