]> granicus.if.org Git - python/commitdiff
#15970: merge with 3.2.
authorEzio Melotti <ezio.melotti@gmail.com>
Wed, 19 Sep 2012 05:25:01 +0000 (08:25 +0300)
committerEzio Melotti <ezio.melotti@gmail.com>
Wed, 19 Sep 2012 05:25:01 +0000 (08:25 +0300)
1  2 
Lib/test/test_xml_etree.py
Lib/xml/etree/ElementTree.py
Misc/NEWS

index da1ad0968686edcc9f11c65ff763ccd23f140924,3092f3bc11ed5080c6cedd4f647ab8d833d88e0a..97d64fcf181349bb4e92922fa7723904631da693
@@@ -1711,654 -1851,25 +1711,666 @@@ def check_issue10777()
      >>> ET.register_namespace('test10777', 'http://myuri/')
      """
  
 -def check_html_empty_elems_serialization(self):
 -    # issue 15970
 -    # from http://www.w3.org/TR/html401/index/elements.html
 -    """
 +# --------------------------------------------------------------------
  
 -    >>> empty_elems = ['AREA', 'BASE', 'BASEFONT', 'BR', 'COL', 'FRAME', 'HR',
 -    ...                'IMG', 'INPUT', 'ISINDEX', 'LINK', 'META', 'PARAM']
 -    >>> elems = ''.join('<%s />' % elem for elem in empty_elems)
 -    >>> serialize(ET.XML('<html>%s</html>' % elems), method='html')
 -    '<html><AREA><BASE><BASEFONT><BR><COL><FRAME><HR><IMG><INPUT><ISINDEX><LINK><META><PARAM></html>'
 -    >>> serialize(ET.XML('<html>%s</html>' % elems.lower()), method='html')
 -    '<html><area><base><basefont><br><col><frame><hr><img><input><isindex><link><meta><param></html>'
 -    >>> elems = ''.join('<%s></%s>' % (elem, elem) for elem in empty_elems)
 -    >>> serialize(ET.XML('<html>%s</html>' % elems), method='html')
 -    '<html><AREA><BASE><BASEFONT><BR><COL><FRAME><HR><IMG><INPUT><ISINDEX><LINK><META><PARAM></html>'
 -    >>> serialize(ET.XML('<html>%s</html>' % elems.lower()), method='html')
 -    '<html><area><base><basefont><br><col><frame><hr><img><input><isindex><link><meta><param></html>'
  
 -    """
 +class BasicElementTest(unittest.TestCase):
 +    def test_augmentation_type_errors(self):
 +        e = ET.Element('joe')
 +        self.assertRaises(TypeError, e.append, 'b')
 +        self.assertRaises(TypeError, e.extend, [ET.Element('bar'), 'foo'])
 +        self.assertRaises(TypeError, e.insert, 0, 'foo')
 +
 +    def test_cyclic_gc(self):
 +        class Dummy:
 +            pass
 +
 +        # Test the shortest cycle: d->element->d
 +        d = Dummy()
 +        d.dummyref = ET.Element('joe', attr=d)
 +        wref = weakref.ref(d)
 +        del d
 +        gc_collect()
 +        self.assertIsNone(wref())
 +
 +        # A longer cycle: d->e->e2->d
 +        e = ET.Element('joe')
 +        d = Dummy()
 +        d.dummyref = e
 +        wref = weakref.ref(d)
 +        e2 = ET.SubElement(e, 'foo', attr=d)
 +        del d, e, e2
 +        gc_collect()
 +        self.assertIsNone(wref())
 +
 +        # A cycle between Element objects as children of one another
 +        # e1->e2->e3->e1
 +        e1 = ET.Element('e1')
 +        e2 = ET.Element('e2')
 +        e3 = ET.Element('e3')
 +        e1.append(e2)
 +        e2.append(e2)
 +        e3.append(e1)
 +        wref = weakref.ref(e1)
 +        del e1, e2, e3
 +        gc_collect()
 +        self.assertIsNone(wref())
 +
 +    def test_weakref(self):
 +        flag = False
 +        def wref_cb(w):
 +            nonlocal flag
 +            flag = True
 +        e = ET.Element('e')
 +        wref = weakref.ref(e, wref_cb)
 +        self.assertEqual(wref().tag, 'e')
 +        del e
 +        self.assertEqual(flag, True)
 +        self.assertEqual(wref(), None)
 +
 +
 +class ElementTreeTest(unittest.TestCase):
 +    def test_istype(self):
 +        self.assertIsInstance(ET.ParseError, type)
 +        self.assertIsInstance(ET.QName, type)
 +        self.assertIsInstance(ET.ElementTree, type)
 +        self.assertIsInstance(ET.Element, type)
 +        self.assertIsInstance(ET.TreeBuilder, type)
 +        self.assertIsInstance(ET.XMLParser, type)
 +
 +    def test_Element_subclass_trivial(self):
 +        class MyElement(ET.Element):
 +            pass
 +
 +        mye = MyElement('foo')
 +        self.assertIsInstance(mye, ET.Element)
 +        self.assertIsInstance(mye, MyElement)
 +        self.assertEqual(mye.tag, 'foo')
 +
 +        # test that attribute assignment works (issue 14849)
 +        mye.text = "joe"
 +        self.assertEqual(mye.text, "joe")
 +
 +    def test_Element_subclass_constructor(self):
 +        class MyElement(ET.Element):
 +            def __init__(self, tag, attrib={}, **extra):
 +                super(MyElement, self).__init__(tag + '__', attrib, **extra)
 +
 +        mye = MyElement('foo', {'a': 1, 'b': 2}, c=3, d=4)
 +        self.assertEqual(mye.tag, 'foo__')
 +        self.assertEqual(sorted(mye.items()),
 +            [('a', 1), ('b', 2), ('c', 3), ('d', 4)])
 +
 +    def test_Element_subclass_new_method(self):
 +        class MyElement(ET.Element):
 +            def newmethod(self):
 +                return self.tag
 +
 +        mye = MyElement('joe')
 +        self.assertEqual(mye.newmethod(), 'joe')
 +
++    def test_html_empty_elems_serialization(self):
++        # issue 15970
++        # from http://www.w3.org/TR/html401/index/elements.html
++        for element in ['AREA', 'BASE', 'BASEFONT', 'BR', 'COL', 'FRAME', 'HR',
++                        'IMG', 'INPUT', 'ISINDEX', 'LINK', 'META', 'PARAM']:
++            for elem in [element, element.lower()]:
++                expected = '<%s>' % elem
++                serialized = serialize(ET.XML('<%s />' % elem), method='html')
++                self.assertEqual(serialized, expected)
++                serialized = serialize(ET.XML('<%s></%s>' % (elem,elem)),
++                                       method='html')
++                self.assertEqual(serialized, expected)
 +
 +class ElementIterTest(unittest.TestCase):
 +    def _ilist(self, elem, tag=None):
 +        return summarize_list(elem.iter(tag))
 +
 +    def test_basic(self):
 +        doc = ET.XML("<html><body>this is a <i>paragraph</i>.</body>..</html>")
 +        self.assertEqual(self._ilist(doc), ['html', 'body', 'i'])
 +        self.assertEqual(self._ilist(doc.find('body')), ['body', 'i'])
 +        self.assertEqual(next(doc.iter()).tag, 'html')
 +        self.assertEqual(''.join(doc.itertext()), 'this is a paragraph...')
 +        self.assertEqual(''.join(doc.find('body').itertext()),
 +            'this is a paragraph.')
 +        self.assertEqual(next(doc.itertext()), 'this is a ')
 +
 +        # iterparse should return an iterator
 +        sourcefile = serialize(doc, to_string=False)
 +        self.assertEqual(next(ET.iterparse(sourcefile))[0], 'end')
 +
 +        tree = ET.ElementTree(None)
 +        self.assertRaises(AttributeError, tree.iter)
 +
 +    def test_corners(self):
 +        # single root, no subelements
 +        a = ET.Element('a')
 +        self.assertEqual(self._ilist(a), ['a'])
 +
 +        # one child
 +        b = ET.SubElement(a, 'b')
 +        self.assertEqual(self._ilist(a), ['a', 'b'])
 +
 +        # one child and one grandchild
 +        c = ET.SubElement(b, 'c')
 +        self.assertEqual(self._ilist(a), ['a', 'b', 'c'])
 +
 +        # two children, only first with grandchild
 +        d = ET.SubElement(a, 'd')
 +        self.assertEqual(self._ilist(a), ['a', 'b', 'c', 'd'])
 +
 +        # replace first child by second
 +        a[0] = a[1]
 +        del a[1]
 +        self.assertEqual(self._ilist(a), ['a', 'd'])
 +
 +    def test_iter_by_tag(self):
 +        doc = ET.XML('''
 +            <document>
 +                <house>
 +                    <room>bedroom1</room>
 +                    <room>bedroom2</room>
 +                </house>
 +                <shed>nothing here
 +                </shed>
 +                <house>
 +                    <room>bedroom8</room>
 +                </house>
 +            </document>''')
 +
 +        self.assertEqual(self._ilist(doc, 'room'), ['room'] * 3)
 +        self.assertEqual(self._ilist(doc, 'house'), ['house'] * 2)
 +
 +        # make sure both tag=None and tag='*' return all tags
 +        all_tags = ['document', 'house', 'room', 'room',
 +                    'shed', 'house', 'room']
 +        self.assertEqual(self._ilist(doc), all_tags)
 +        self.assertEqual(self._ilist(doc, '*'), all_tags)
 +
 +
 +class TreeBuilderTest(unittest.TestCase):
 +    sample1 = ('<!DOCTYPE html PUBLIC'
 +        ' "-//W3C//DTD XHTML 1.0 Transitional//EN"'
 +        ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
 +        '<html>text</html>')
 +
 +    sample2 = '''<toplevel>sometext</toplevel>'''
 +
 +    def test_dummy_builder(self):
 +        class BaseDummyBuilder:
 +            def close(self):
 +                return 42
 +
 +        class DummyBuilder(BaseDummyBuilder):
 +            data = start = end = lambda *a: None
 +
 +        parser = ET.XMLParser(target=DummyBuilder())
 +        parser.feed(self.sample1)
 +        self.assertEqual(parser.close(), 42)
 +
 +        parser = ET.XMLParser(target=BaseDummyBuilder())
 +        parser.feed(self.sample1)
 +        self.assertEqual(parser.close(), 42)
 +
 +        parser = ET.XMLParser(target=object())
 +        parser.feed(self.sample1)
 +        self.assertIsNone(parser.close())
 +
 +    def test_subclass(self):
 +        class MyTreeBuilder(ET.TreeBuilder):
 +            def foobar(self, x):
 +                return x * 2
 +
 +        tb = MyTreeBuilder()
 +        self.assertEqual(tb.foobar(10), 20)
 +
 +        parser = ET.XMLParser(target=tb)
 +        parser.feed(self.sample1)
 +
 +        e = parser.close()
 +        self.assertEqual(e.tag, 'html')
 +
 +    def test_element_factory(self):
 +        lst = []
 +        def myfactory(tag, attrib):
 +            nonlocal lst
 +            lst.append(tag)
 +            return ET.Element(tag, attrib)
 +
 +        tb = ET.TreeBuilder(element_factory=myfactory)
 +        parser = ET.XMLParser(target=tb)
 +        parser.feed(self.sample2)
 +        parser.close()
 +
 +        self.assertEqual(lst, ['toplevel'])
 +
 +    def test_doctype(self):
 +        class DoctypeParser:
 +            _doctype = None
 +
 +            def doctype(self, name, pubid, system):
 +                self._doctype = (name, pubid, system)
 +
 +            def close(self):
 +                return self._doctype
 +
 +        parser = ET.XMLParser(target=DoctypeParser())
 +        parser.feed(self.sample1)
 +
 +        self.assertEqual(parser.close(),
 +            ('html', '-//W3C//DTD XHTML 1.0 Transitional//EN',
 +             'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'))
 +
 +
 +class XincludeTest(unittest.TestCase):
 +    def _my_loader(self, href, parse):
 +        # Used to avoid a test-dependency problem where the default loader
 +        # of ElementInclude uses the pyET parser for cET tests.
 +        if parse == 'xml':
 +            with open(href, 'rb') as f:
 +                return ET.parse(f).getroot()
 +        else:
 +            return None
 +
 +    def test_xinclude_default(self):
 +        from xml.etree import ElementInclude
 +        doc = xinclude_loader('default.xml')
 +        ElementInclude.include(doc, self._my_loader)
 +        s = serialize(doc)
 +        self.assertEqual(s.strip(), '''<document>
 +  <p>Example.</p>
 +  <root>
 +   <element key="value">text</element>
 +   <element>text</element>tail
 +   <empty-element />
 +</root>
 +</document>''')
 +
 +
 +class XMLParserTest(unittest.TestCase):
 +    sample1 = '<file><line>22</line></file>'
 +    sample2 = ('<!DOCTYPE html PUBLIC'
 +        ' "-//W3C//DTD XHTML 1.0 Transitional//EN"'
 +        ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">'
 +        '<html>text</html>')
 +
 +    def _check_sample_element(self, e):
 +        self.assertEqual(e.tag, 'file')
 +        self.assertEqual(e[0].tag, 'line')
 +        self.assertEqual(e[0].text, '22')
 +
 +    def test_constructor_args(self):
 +        # Positional args. The first (html) is not supported, but should be
 +        # nevertheless correctly accepted.
 +        parser = ET.XMLParser(None, ET.TreeBuilder(), 'utf-8')
 +        parser.feed(self.sample1)
 +        self._check_sample_element(parser.close())
 +
 +        # Now as keyword args.
 +        parser2 = ET.XMLParser(encoding='utf-8', html=[{}], target=ET.TreeBuilder())
 +        parser2.feed(self.sample1)
 +        self._check_sample_element(parser2.close())
 +
 +    def test_subclass(self):
 +        class MyParser(ET.XMLParser):
 +            pass
 +        parser = MyParser()
 +        parser.feed(self.sample1)
 +        self._check_sample_element(parser.close())
 +
 +    def test_subclass_doctype(self):
 +        _doctype = None
 +        class MyParserWithDoctype(ET.XMLParser):
 +            def doctype(self, name, pubid, system):
 +                nonlocal _doctype
 +                _doctype = (name, pubid, system)
 +
 +        parser = MyParserWithDoctype()
 +        parser.feed(self.sample2)
 +        parser.close()
 +        self.assertEqual(_doctype,
 +            ('html', '-//W3C//DTD XHTML 1.0 Transitional//EN',
 +             'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'))
 +
 +
 +class NamespaceParseTest(unittest.TestCase):
 +    def test_find_with_namespace(self):
 +        nsmap = {'h': 'hello', 'f': 'foo'}
 +        doc = ET.fromstring(SAMPLE_XML_NS_ELEMS)
 +
 +        self.assertEqual(len(doc.findall('{hello}table', nsmap)), 1)
 +        self.assertEqual(len(doc.findall('.//{hello}td', nsmap)), 2)
 +        self.assertEqual(len(doc.findall('.//{foo}name', nsmap)), 1)
 +
 +
 +class ElementSlicingTest(unittest.TestCase):
 +    def _elem_tags(self, elemlist):
 +        return [e.tag for e in elemlist]
 +
 +    def _subelem_tags(self, elem):
 +        return self._elem_tags(list(elem))
 +
 +    def _make_elem_with_children(self, numchildren):
 +        """Create an Element with a tag 'a', with the given amount of children
 +           named 'a0', 'a1' ... and so on.
 +
 +        """
 +        e = ET.Element('a')
 +        for i in range(numchildren):
 +            ET.SubElement(e, 'a%s' % i)
 +        return e
 +
 +    def test_getslice_single_index(self):
 +        e = self._make_elem_with_children(10)
 +
 +        self.assertEqual(e[1].tag, 'a1')
 +        self.assertEqual(e[-2].tag, 'a8')
 +
 +        self.assertRaises(IndexError, lambda: e[12])
 +
 +    def test_getslice_range(self):
 +        e = self._make_elem_with_children(6)
 +
 +        self.assertEqual(self._elem_tags(e[3:]), ['a3', 'a4', 'a5'])
 +        self.assertEqual(self._elem_tags(e[3:6]), ['a3', 'a4', 'a5'])
 +        self.assertEqual(self._elem_tags(e[3:16]), ['a3', 'a4', 'a5'])
 +        self.assertEqual(self._elem_tags(e[3:5]), ['a3', 'a4'])
 +        self.assertEqual(self._elem_tags(e[3:-1]), ['a3', 'a4'])
 +        self.assertEqual(self._elem_tags(e[:2]), ['a0', 'a1'])
 +
 +    def test_getslice_steps(self):
 +        e = self._make_elem_with_children(10)
 +
 +        self.assertEqual(self._elem_tags(e[8:10:1]), ['a8', 'a9'])
 +        self.assertEqual(self._elem_tags(e[::3]), ['a0', 'a3', 'a6', 'a9'])
 +        self.assertEqual(self._elem_tags(e[::8]), ['a0', 'a8'])
 +        self.assertEqual(self._elem_tags(e[1::8]), ['a1', 'a9'])
 +
 +    def test_getslice_negative_steps(self):
 +        e = self._make_elem_with_children(4)
 +
 +        self.assertEqual(self._elem_tags(e[::-1]), ['a3', 'a2', 'a1', 'a0'])
 +        self.assertEqual(self._elem_tags(e[::-2]), ['a3', 'a1'])
 +
 +    def test_delslice(self):
 +        e = self._make_elem_with_children(4)
 +        del e[0:2]
 +        self.assertEqual(self._subelem_tags(e), ['a2', 'a3'])
 +
 +        e = self._make_elem_with_children(4)
 +        del e[0:]
 +        self.assertEqual(self._subelem_tags(e), [])
 +
 +        e = self._make_elem_with_children(4)
 +        del e[::-1]
 +        self.assertEqual(self._subelem_tags(e), [])
 +
 +        e = self._make_elem_with_children(4)
 +        del e[::-2]
 +        self.assertEqual(self._subelem_tags(e), ['a0', 'a2'])
 +
 +        e = self._make_elem_with_children(4)
 +        del e[1::2]
 +        self.assertEqual(self._subelem_tags(e), ['a0', 'a2'])
 +
 +        e = self._make_elem_with_children(2)
 +        del e[::2]
 +        self.assertEqual(self._subelem_tags(e), ['a1'])
 +
 +
 +class IOTest(unittest.TestCase):
 +    def tearDown(self):
 +        unlink(TESTFN)
 +
 +    def test_encoding(self):
 +        # Test encoding issues.
 +        elem = ET.Element("tag")
 +        elem.text = "abc"
 +        self.assertEqual(serialize(elem), '<tag>abc</tag>')
 +        self.assertEqual(serialize(elem, encoding="utf-8"),
 +                b'<tag>abc</tag>')
 +        self.assertEqual(serialize(elem, encoding="us-ascii"),
 +                b'<tag>abc</tag>')
 +        for enc in ("iso-8859-1", "utf-16", "utf-32"):
 +            self.assertEqual(serialize(elem, encoding=enc),
 +                    ("<?xml version='1.0' encoding='%s'?>\n"
 +                     "<tag>abc</tag>" % enc).encode(enc))
 +
 +        elem = ET.Element("tag")
 +        elem.text = "<&\"\'>"
 +        self.assertEqual(serialize(elem), '<tag>&lt;&amp;"\'&gt;</tag>')
 +        self.assertEqual(serialize(elem, encoding="utf-8"),
 +                b'<tag>&lt;&amp;"\'&gt;</tag>')
 +        self.assertEqual(serialize(elem, encoding="us-ascii"),
 +                b'<tag>&lt;&amp;"\'&gt;</tag>')
 +        for enc in ("iso-8859-1", "utf-16", "utf-32"):
 +            self.assertEqual(serialize(elem, encoding=enc),
 +                    ("<?xml version='1.0' encoding='%s'?>\n"
 +                     "<tag>&lt;&amp;\"'&gt;</tag>" % enc).encode(enc))
 +
 +        elem = ET.Element("tag")
 +        elem.attrib["key"] = "<&\"\'>"
 +        self.assertEqual(serialize(elem), '<tag key="&lt;&amp;&quot;\'&gt;" />')
 +        self.assertEqual(serialize(elem, encoding="utf-8"),
 +                b'<tag key="&lt;&amp;&quot;\'&gt;" />')
 +        self.assertEqual(serialize(elem, encoding="us-ascii"),
 +                b'<tag key="&lt;&amp;&quot;\'&gt;" />')
 +        for enc in ("iso-8859-1", "utf-16", "utf-32"):
 +            self.assertEqual(serialize(elem, encoding=enc),
 +                    ("<?xml version='1.0' encoding='%s'?>\n"
 +                     "<tag key=\"&lt;&amp;&quot;'&gt;\" />" % enc).encode(enc))
 +
 +        elem = ET.Element("tag")
 +        elem.text = '\xe5\xf6\xf6<>'
 +        self.assertEqual(serialize(elem), '<tag>\xe5\xf6\xf6&lt;&gt;</tag>')
 +        self.assertEqual(serialize(elem, encoding="utf-8"),
 +                b'<tag>\xc3\xa5\xc3\xb6\xc3\xb6&lt;&gt;</tag>')
 +        self.assertEqual(serialize(elem, encoding="us-ascii"),
 +                b'<tag>&#229;&#246;&#246;&lt;&gt;</tag>')
 +        for enc in ("iso-8859-1", "utf-16", "utf-32"):
 +            self.assertEqual(serialize(elem, encoding=enc),
 +                    ("<?xml version='1.0' encoding='%s'?>\n"
 +                     "<tag>åöö&lt;&gt;</tag>" % enc).encode(enc))
 +
 +        elem = ET.Element("tag")
 +        elem.attrib["key"] = '\xe5\xf6\xf6<>'
 +        self.assertEqual(serialize(elem), '<tag key="\xe5\xf6\xf6&lt;&gt;" />')
 +        self.assertEqual(serialize(elem, encoding="utf-8"),
 +                b'<tag key="\xc3\xa5\xc3\xb6\xc3\xb6&lt;&gt;" />')
 +        self.assertEqual(serialize(elem, encoding="us-ascii"),
 +                b'<tag key="&#229;&#246;&#246;&lt;&gt;" />')
 +        for enc in ("iso-8859-1", "utf-16", "utf-16le", "utf-16be", "utf-32"):
 +            self.assertEqual(serialize(elem, encoding=enc),
 +                    ("<?xml version='1.0' encoding='%s'?>\n"
 +                     "<tag key=\"åöö&lt;&gt;\" />" % enc).encode(enc))
 +
 +    def test_write_to_filename(self):
 +        tree = ET.ElementTree(ET.XML('''<site />'''))
 +        tree.write(TESTFN)
 +        with open(TESTFN, 'rb') as f:
 +            self.assertEqual(f.read(), b'''<site />''')
 +
 +    def test_write_to_text_file(self):
 +        tree = ET.ElementTree(ET.XML('''<site />'''))
 +        with open(TESTFN, 'w', encoding='utf-8') as f:
 +            tree.write(f, encoding='unicode')
 +            self.assertFalse(f.closed)
 +        with open(TESTFN, 'rb') as f:
 +            self.assertEqual(f.read(), b'''<site />''')
 +
 +    def test_write_to_binary_file(self):
 +        tree = ET.ElementTree(ET.XML('''<site />'''))
 +        with open(TESTFN, 'wb') as f:
 +            tree.write(f)
 +            self.assertFalse(f.closed)
 +        with open(TESTFN, 'rb') as f:
 +            self.assertEqual(f.read(), b'''<site />''')
 +
 +    def test_write_to_binary_file_with_bom(self):
 +        tree = ET.ElementTree(ET.XML('''<site />'''))
 +        # test BOM writing to buffered file
 +        with open(TESTFN, 'wb') as f:
 +            tree.write(f, encoding='utf-16')
 +            self.assertFalse(f.closed)
 +        with open(TESTFN, 'rb') as f:
 +            self.assertEqual(f.read(),
 +                    '''<?xml version='1.0' encoding='utf-16'?>\n'''
 +                    '''<site />'''.encode("utf-16"))
 +        # test BOM writing to non-buffered file
 +        with open(TESTFN, 'wb', buffering=0) as f:
 +            tree.write(f, encoding='utf-16')
 +            self.assertFalse(f.closed)
 +        with open(TESTFN, 'rb') as f:
 +            self.assertEqual(f.read(),
 +                    '''<?xml version='1.0' encoding='utf-16'?>\n'''
 +                    '''<site />'''.encode("utf-16"))
 +
 +    def test_read_from_stringio(self):
 +        tree = ET.ElementTree()
 +        stream = io.StringIO('''<?xml version="1.0"?><site></site>''')
 +        tree.parse(stream)
 +        self.assertEqual(tree.getroot().tag, 'site')
 +
 +    def test_write_to_stringio(self):
 +        tree = ET.ElementTree(ET.XML('''<site />'''))
 +        stream = io.StringIO()
 +        tree.write(stream, encoding='unicode')
 +        self.assertEqual(stream.getvalue(), '''<site />''')
 +
 +    def test_read_from_bytesio(self):
 +        tree = ET.ElementTree()
 +        raw = io.BytesIO(b'''<?xml version="1.0"?><site></site>''')
 +        tree.parse(raw)
 +        self.assertEqual(tree.getroot().tag, 'site')
 +
 +    def test_write_to_bytesio(self):
 +        tree = ET.ElementTree(ET.XML('''<site />'''))
 +        raw = io.BytesIO()
 +        tree.write(raw)
 +        self.assertEqual(raw.getvalue(), b'''<site />''')
 +
 +    class dummy:
 +        pass
 +
 +    def test_read_from_user_text_reader(self):
 +        stream = io.StringIO('''<?xml version="1.0"?><site></site>''')
 +        reader = self.dummy()
 +        reader.read = stream.read
 +        tree = ET.ElementTree()
 +        tree.parse(reader)
 +        self.assertEqual(tree.getroot().tag, 'site')
 +
 +    def test_write_to_user_text_writer(self):
 +        tree = ET.ElementTree(ET.XML('''<site />'''))
 +        stream = io.StringIO()
 +        writer = self.dummy()
 +        writer.write = stream.write
 +        tree.write(writer, encoding='unicode')
 +        self.assertEqual(stream.getvalue(), '''<site />''')
 +
 +    def test_read_from_user_binary_reader(self):
 +        raw = io.BytesIO(b'''<?xml version="1.0"?><site></site>''')
 +        reader = self.dummy()
 +        reader.read = raw.read
 +        tree = ET.ElementTree()
 +        tree.parse(reader)
 +        self.assertEqual(tree.getroot().tag, 'site')
 +        tree = ET.ElementTree()
 +
 +    def test_write_to_user_binary_writer(self):
 +        tree = ET.ElementTree(ET.XML('''<site />'''))
 +        raw = io.BytesIO()
 +        writer = self.dummy()
 +        writer.write = raw.write
 +        tree.write(writer)
 +        self.assertEqual(raw.getvalue(), b'''<site />''')
 +
 +    def test_write_to_user_binary_writer_with_bom(self):
 +        tree = ET.ElementTree(ET.XML('''<site />'''))
 +        raw = io.BytesIO()
 +        writer = self.dummy()
 +        writer.write = raw.write
 +        writer.seekable = lambda: True
 +        writer.tell = raw.tell
 +        tree.write(writer, encoding="utf-16")
 +        self.assertEqual(raw.getvalue(),
 +                '''<?xml version='1.0' encoding='utf-16'?>\n'''
 +                '''<site />'''.encode("utf-16"))
 +
 +    def test_tostringlist_invariant(self):
 +        root = ET.fromstring('<tag>foo</tag>')
 +        self.assertEqual(
 +            ET.tostring(root, 'unicode'),
 +            ''.join(ET.tostringlist(root, 'unicode')))
 +        self.assertEqual(
 +            ET.tostring(root, 'utf-16'),
 +            b''.join(ET.tostringlist(root, 'utf-16')))
 +
 +
 +class ParseErrorTest(unittest.TestCase):
 +    def test_subclass(self):
 +        self.assertIsInstance(ET.ParseError(), SyntaxError)
 +
 +    def _get_error(self, s):
 +        try:
 +            ET.fromstring(s)
 +        except ET.ParseError as e:
 +            return e
 +
 +    def test_error_position(self):
 +        self.assertEqual(self._get_error('foo').position, (1, 0))
 +        self.assertEqual(self._get_error('<tag>&foo;</tag>').position, (1, 5))
 +        self.assertEqual(self._get_error('foobar<').position, (1, 6))
 +
 +    def test_error_code(self):
 +        import xml.parsers.expat.errors as ERRORS
 +        self.assertEqual(self._get_error('foo').code,
 +                ERRORS.codes[ERRORS.XML_ERROR_SYNTAX])
 +
 +
 +class KeywordArgsTest(unittest.TestCase):
 +    # Test various issues with keyword arguments passed to ET.Element
 +    # constructor and methods
 +    def test_issue14818(self):
 +        x = ET.XML("<a>foo</a>")
 +        self.assertEqual(x.find('a', None),
 +                         x.find(path='a', namespaces=None))
 +        self.assertEqual(x.findtext('a', None, None),
 +                         x.findtext(path='a', default=None, namespaces=None))
 +        self.assertEqual(x.findall('a', None),
 +                         x.findall(path='a', namespaces=None))
 +        self.assertEqual(list(x.iterfind('a', None)),
 +                         list(x.iterfind(path='a', namespaces=None)))
 +
 +        self.assertEqual(ET.Element('a').attrib, {})
 +        elements = [
 +            ET.Element('a', dict(href="#", id="foo")),
 +            ET.Element('a', attrib=dict(href="#", id="foo")),
 +            ET.Element('a', dict(href="#"), id="foo"),
 +            ET.Element('a', href="#", id="foo"),
 +            ET.Element('a', dict(href="#", id="foo"), href="#", id="foo"),
 +        ]
 +        for e in elements:
 +            self.assertEqual(e.tag, 'a')
 +            self.assertEqual(e.attrib, dict(href="#", id="foo"))
 +
 +        e2 = ET.SubElement(elements[0], 'foobar', attrib={'key1': 'value1'})
 +        self.assertEqual(e2.attrib['key1'], 'value1')
 +
 +        with self.assertRaisesRegex(TypeError, 'must be dict, not str'):
 +            ET.Element('a', "I'm not a dict")
 +        with self.assertRaisesRegex(TypeError, 'must be dict, not str'):
 +            ET.Element('a', attrib="I'm not a dict")
 +
 +# --------------------------------------------------------------------
 +
 +@unittest.skipUnless(pyET, 'only for the Python version')
 +class NoAcceleratorTest(unittest.TestCase):
 +    # Test that the C accelerator was not imported for pyET
 +    def test_correct_import_pyET(self):
 +        self.assertEqual(pyET.Element.__module__, 'xml.etree.ElementTree')
 +        self.assertEqual(pyET.SubElement.__module__, 'xml.etree.ElementTree')
  
  # --------------------------------------------------------------------
  
Simple merge
diff --cc Misc/NEWS
index 90e3cc188c5855cb8cb0dc7441d962a0c1e491dd,5113ec08ce999f1f5a2bedd2c9b7190c8ed258c5..955a371ce11418fe25cd50969df97b342dadc9b8
+++ b/Misc/NEWS
@@@ -29,102 -21,101 +29,105 @@@ Core and Builtin
  - Issue #15801: Make sure mappings passed to '%' formatting are actually
    subscriptable.
  
 -- Issue #15726: Fix incorrect bounds checking in PyState_FindModule.
 -  Patch by Robin Schreiber.
 +Library
 +-------
  
 -- Issue #15604: Update uses of PyObject_IsTrue() to check for and handle
 -  errors correctly.  Patch by Serhiy Storchaka.
++- Issue #15970: xml.etree.ElementTree now serializes correctly the empty HTML
++  elements 'meta' and 'param'.
 -- Issue #13119: sys.stdout and sys.stderr are now using "\r\n" newline on
 -  Windows, as Python 2.
 +- Issue #15842: the SocketIO.{readable,writable,seekable} methods now
 +  raise ValueError when the file-like object is closed.  Patch by Alessandro
 +  Moura.
  
 -- Issue #14579: Fix CVE-2012-2135: vulnerability in the utf-16 decoder after
 -  error handling.  Patch by Serhiy Storchaka.
 +- Issue #15882: Change _decimal to accept any coefficient tuple when
 +  constructing infinities. This is done for backwards compatibility
 +  with decimal.py: Infinity coefficients are undefined in _decimal
 +  (in accordance with the specification).
  
 -- Issue #15404: Refleak in PyMethodObject repr.
 +- Issue #15876: Fix a refleak in the curses module: window.encoding.
  
 -- Issue #15394: An issue in PyModule_Create that caused references to
 -  be leaked on some error paths has been fixed.  Patch by Julia Lawall.
 +- Issue #15881: Fixed atexit hook in multiprocessing.  Original patch
 +  by Chris McDonough.
  
 -- Issue #15368: An issue that caused bytecode generation to be
 -  non-deterministic when using randomized hashing (-R) has been fixed.
 +- Issue #15340: Fix importing the random module when /dev/urandom cannot
 +  be opened.  This was a regression caused by the hash randomization patch.
  
 -- Issue #15020: The program name used to search for Python's path is now
 -  "python3" under Unix, not "python".
 +- Issue #15841: The readable(), writable() and seekable() methods of BytesIO
 +  and StringIO objects now raise ValueError when the object has been closed.
 +  Patch by Alessandro Moura.
  
 -- Issue #15033: Fix the exit status bug when modules invoked using -m swith,
 -  return the proper failure return value (1). Patch contributed by Jeff Knupp.
 +- Issue #15447: Use subprocess.DEVNULL in webbrowser, instead of opening
 +  os.devnull explicitly and leaving it open.
  
 -- Issue #12268: File readline, readlines and read() or readall() methods
 -  no longer lose data when an underlying read system call is interrupted.
 -  IOError is no longer raised due to a read system call returning EINTR
 -  from within these methods.
 +- Issue #15509: webbrowser.UnixBrowser no longer passes empty arguments to
 +  Popen when %action substitutions produce empty strings.
  
 -- Issue #15142: Fix reference leak when deallocating instances of types
 -  created using PyType_FromSpec().
 +- Issue #12776,#11839: call argparse type function (specified by add_argument)
 +  only once. Before, the type function was called twice in the case where the
 +  default was specified and the argument was given as well.  This was
 +  especially problematic for the FileType type, as a default file would always
 +  be opened, even if a file argument was specified on the command line.
  
 -- Issue #10053: Don't close FDs when FileIO.__init__ fails. Loosely based on
 -  the work by Hirokazu Yamamoto.
 +- Issue #15906: Fix a regression in argparse caused by the preceding change,
 +  when action='append', type='str' and default=[].
  
 -- Issue #14775: Fix a potential quadratic dict build-up due to the garbage
 -  collector repeatedly trying to untrack dicts.
 +Extension Modules
 +-----------------
  
 -- Issue #14494: Fix __future__.py and its documentation to note that
 -  absolute imports are the default behavior in 3.0 instead of 2.7.
 -  Patch by Sven Marnach.
 +Tests
 +-----
  
 -- Issue #14761: Fix potential leak on an error case in the import machinery.
 +- Issue #15802: Fix test logic in TestMaildir.test_create_tmp. Patch
 +  by Serhiy Storchaka.
  
 -- Issue #14699: Fix calling the classmethod descriptor directly.
 +- Issue #15557: Added a test suite for the webbrowser module, thanks
 +  to Anton Barkovsky.
  
 -- Issue #14433: Prevent msvcrt crash in interactive prompt when stdin
 -  is closed.
 +Build
 +-----
  
 -- Issue #11603 (again): Setting __repr__ to __str__ now raises a RuntimeError
 -  when repr() or str() is called on such an object.
 +- Issue #15819: Make sure we can build Python out-of-tree from a readonly
 +  source directory.  (Somewhat related to Issue #9860.)
  
 -- Issue #14658: Fix binding a special method to a builtin implementation of a
 -  special method with a different name.
 +Documentation
 +-------------
  
 -- Issue #14630: Fix a memory access bug for instances of a subclass of int
 -  with value 0.
 +- Issue #11964: Document a change in v3.2 to the behavior of the indent
 +  parameter of json encoding operations.
  
 -- Issue #14612: Fix jumping around with blocks by setting f_lineno.
 +Tools/Demos
 +-----------
  
 -- Issue #14607: Fix keyword-only arguments which started with ``__``.
  
 -- Issue #13889: Check and (if necessary) set FPU control word before calling
 -  any of the dtoa.c string <-> float conversion functions, on MSVC builds of
 -  Python.  This fixes issues when embedding Python in a Delphi app.
 +What's New in Python 3.3.0?
 +===========================
  
 -- Issue #14474: Save and restore exception state in thread.start_new_thread()
 -  while writing error message if the thread leaves a unhandled exception.
 +*Release date: XX-Sep-2012*
  
 -- Issue #13019: Fix potential reference leaks in bytearray.extend().  Patch
 -  by Suman Saha.
 +Core and Builtins
 +-----------------
  
 -- Issue #14378: Fix compiling ast.ImportFrom nodes with a "__future__" string as
 -  the module name that was not interned.
 +Library
 +-------
  
 -- Issue #14331: Use significantly less stack space when importing modules by
 -  allocating path buffers on the heap instead of the stack.
  
 -- Issue #14334: Prevent in a segfault in type.__getattribute__ when it was not
 -  passed strings.
 +What's New in Python 3.3.0 Release Candidate 2?
 +===============================================
  
 -- Issue #1469629: Allow cycles through an object's __dict__ slot to be
 -  collected. (For example if ``x.__dict__ is x``).
 +*Release date: 09-Sep-2012*
  
 -- Issue #14172: Fix reference leak when marshalling a buffer-like object
 -  (other than a bytes object).
 +Core and Builtins
 +-----------------
  
 -- Issue #13521: dict.setdefault() now does only one lookup for the given key,
 -  making it "atomic" for many purposes.  Patch by Filip Gruszczyński.
 +- Issue #13992: The trashcan mechanism is now thread-safe.  This eliminates
 +  sporadic crashes in multi-thread programs when several long deallocator
 +  chains ran concurrently and involved subclasses of built-in container
 +  types.
  
 -- Issue #14471: Fix a possible buffer overrun in the winreg module.
 +- Issue #15784: Modify OSError.__str__() to better distinguish between
 +  errno error numbers and Windows error numbers.
 +
 +- Issue #15781: Fix two small race conditions in import's module locking.
  
  Library
  -------