bpo-30365: Backport warnings and fix bugs in ElementTree. (#1581)

Running Python with the -3 option now emits deprecation warnings for
getchildren() and getiterator() methods of the Element class in the
xml.etree.cElementTree module and when pass the html argument to
xml.etree.ElementTree.XMLParser().

Fixed a deprecation warning about the doctype() method of the
xml.etree.ElementTree.XMLParser class.  Now it is emitted only when
define the doctype() method in the subclass of XMLParser.

Fixed a bug in the test_bug_200708_close test method.  An EchoTarget
instance was incorrectly passed to XMLParser() as the html argument and
silently ignored.

Tests no longer failed when use the -m option for running only selected
test methods. Checking warnings now is more specific, warnings are
expected only when use deprecated features.
diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py
index 201266a..60b26ea 100644
--- a/Lib/test/test_xml_etree.py
+++ b/Lib/test/test_xml_etree.py
@@ -8,6 +8,7 @@
 
 import cgi
 import copy
+import functools
 import io
 import pickle
 import StringIO
@@ -86,6 +87,16 @@
 """
 
 
+def checkwarnings(*filters):
+    def decorator(test):
+        def newtest(*args, **kwargs):
+            with support.check_warnings(*filters):
+                test(*args, **kwargs)
+        functools.update_wrapper(newtest, test)
+        return newtest
+    return decorator
+
+
 class ModuleTest(unittest.TestCase):
     # TODO: this should be removed once we get rid of the global module vars
 
@@ -656,6 +667,10 @@
             ])
 
 
+    # Element.getchildren() and ElementTree.getiterator() are deprecated.
+    @checkwarnings(("This method will be removed in future versions.  "
+                    "Use .+ instead.",
+                    (DeprecationWarning, PendingDeprecationWarning)))
     def test_getchildren(self):
         # Test Element.getchildren()
 
@@ -1356,9 +1371,15 @@
 
         # Test custom builder.
         class EchoTarget:
+            def start(self, tag, attrib):
+                pass
+            def end(self, tag):
+                pass
+            def data(self, text):
+                pass
             def close(self):
                 return ET.Element("element") # simulate root
-        parser = ET.XMLParser(EchoTarget())
+        parser = ET.XMLParser(target=EchoTarget())
         parser.feed("<element>some text</element>")
         self.assertEqual(parser.close().tag, 'element')
 
@@ -1908,7 +1929,12 @@
         e = ET.XML(SAMPLE_XML)
         self.assertEqual(ET.ElementTree(e).find('tag').tag, 'tag')
         self.assertEqual(ET.ElementTree(e).find('./tag').tag, 'tag')
-        self.assertEqual(ET.ElementTree(e).find('/tag').tag, 'tag')
+        # this produces a warning
+        msg = ("This search is broken in 1.3 and earlier, and will be fixed "
+               "in a future version.  If you rely on the current behaviour, "
+               "change it to '.+'")
+        with support.check_warnings((msg, FutureWarning)):
+            self.assertEqual(ET.ElementTree(e).find('/tag').tag, 'tag')
         e[2] = ET.XML(SAMPLE_SECTION)
         self.assertEqual(ET.ElementTree(e).find('section/tag').tag, 'tag')
         self.assertIsNone(ET.ElementTree(e).find('tog'))
@@ -1919,14 +1945,15 @@
         self.assertEqual(ET.ElementTree(e).findtext('tog/foo', 'default'),
              'default')
         self.assertEqual(ET.ElementTree(e).findtext('./tag'), 'text')
-        self.assertEqual(ET.ElementTree(e).findtext('/tag'), 'text')
+        with support.check_warnings((msg, FutureWarning)):
+            self.assertEqual(ET.ElementTree(e).findtext('/tag'), 'text')
         self.assertEqual(ET.ElementTree(e).findtext('section/tag'), 'subtext')
 
         self.assertEqual(summarize_list(ET.ElementTree(e).findall('./tag')),
             ['tag'] * 2)
-        # this produces a warning
-        self.assertEqual(summarize_list(ET.ElementTree(e).findall('/tag')),
-            ['tag'] * 2)
+        with support.check_warnings((msg, FutureWarning)):
+            it = ET.ElementTree(e).findall('/tag')
+        self.assertEqual(summarize_list(it), ['tag'] * 2)
 
 
 class ElementIterTest(unittest.TestCase):
@@ -2014,6 +2041,15 @@
         self.assertEqual(self._ilist(doc, '*'), all_tags)
 
     def test_getiterator(self):
+        # Element.getiterator() is deprecated.
+        if sys.py3kwarning or ET is pyET:
+            with support.check_warnings(("This method will be removed in future versions.  "
+                                         "Use .+ instead.", PendingDeprecationWarning)):
+                self._test_getiterator()
+        else:
+            self._test_getiterator()
+
+    def _test_getiterator(self):
         doc = ET.XML('''
             <document>
                 <house>
@@ -2178,13 +2214,13 @@
     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')
+        with support.check_py3k_warnings((r'.*\bhtml\b', DeprecationWarning)):
+            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())
@@ -2593,44 +2629,6 @@
 # --------------------------------------------------------------------
 
 
-class CleanContext(object):
-    """Provide default namespace mapping and path cache."""
-    checkwarnings = None
-
-    def __init__(self, quiet=False):
-        deprecations = (
-            ("This method of XMLParser is deprecated.  Define doctype\(\) "
-             "method on the TreeBuilder target.", DeprecationWarning),
-            # Search behaviour is broken if search path starts with "/".
-            ("This search is broken in 1.3 and earlier, and will be fixed "
-             "in a future version.  If you rely on the current behaviour, "
-             "change it to '.+'", FutureWarning),
-            # Element.getchildren() and Element.getiterator() are deprecated.
-            ("This method will be removed in future versions.  "
-             "Use .+ instead.", DeprecationWarning),
-            ("This method will be removed in future versions.  "
-             "Use .+ instead.", PendingDeprecationWarning))
-        self.checkwarnings = support.check_warnings(*deprecations, quiet=quiet)
-
-    def __enter__(self):
-        from xml.etree import ElementPath
-        self._nsmap = pyET._namespace_map
-        # Copy the default namespace mapping
-        self._nsmap_copy = self._nsmap.copy()
-        # Copy the path cache (should be empty)
-        self._path_cache = ElementPath._cache
-        ElementPath._cache = self._path_cache.copy()
-        self.checkwarnings.__enter__()
-
-    def __exit__(self, *args):
-        from xml.etree import ElementPath
-        # Restore mapping and path cache
-        self._nsmap.clear()
-        self._nsmap.update(self._nsmap_copy)
-        ElementPath._cache = self._path_cache
-        self.checkwarnings.__exit__(*args)
-
-
 def test_main(module=None):
     # When invoked without a module, runs the Python ET tests by loading pyET.
     # Otherwise, uses the given module as the ET.
@@ -2666,11 +2664,22 @@
             NoAcceleratorTest,
             ])
 
+    # Provide default namespace mapping and path cache.
+    from xml.etree import ElementPath
+    nsmap = pyET._namespace_map
+    # Copy the default namespace mapping
+    nsmap_copy = nsmap.copy()
+    # Copy the path cache (should be empty)
+    path_cache = ElementPath._cache
+    ElementPath._cache = path_cache.copy()
     try:
-        # XXX the C module should give the same warnings as the Python module
-        with CleanContext(quiet=(pyET is not ET)):
-            support.run_unittest(*test_classes)
+        support.run_unittest(*test_classes)
     finally:
+        from xml.etree import ElementPath
+        # Restore mapping and path cache
+        nsmap.clear()
+        nsmap.update(nsmap_copy)
+        ElementPath._cache = path_cache
         # don't interfere with subsequent tests
         ET = None
 
diff --git a/Lib/xml/etree/ElementTree.py b/Lib/xml/etree/ElementTree.py
index cf6402f..dca6910 100644
--- a/Lib/xml/etree/ElementTree.py
+++ b/Lib/xml/etree/ElementTree.py
@@ -1450,6 +1450,8 @@
         self._tail = 1
         return self._last
 
+_sentinel = ['sentinel']
+
 ##
 # Element structure builder for XML source data, based on the
 # <b>expat</b> parser.
@@ -1465,7 +1467,11 @@
 
 class XMLParser(object):
 
-    def __init__(self, html=0, target=None, encoding=None):
+    def __init__(self, html=_sentinel, target=None, encoding=None):
+        if html is not _sentinel:
+            warnings.warnpy3k(
+                "The html argument of XMLParser() is deprecated",
+                DeprecationWarning, stacklevel=2)
         try:
             from xml.parsers import expat
         except ImportError:
@@ -1617,7 +1623,7 @@
                     pubid = pubid[1:-1]
                 if hasattr(self.target, "doctype"):
                     self.target.doctype(name, pubid, system[1:-1])
-                elif self.doctype is not self._XMLParser__doctype:
+                elif self.doctype != self._XMLParser__doctype:
                     # warn about deprecated call
                     self._XMLParser__doctype(name, pubid, system[1:-1])
                     self.doctype(name, pubid, system[1:-1])
diff --git a/Misc/NEWS b/Misc/NEWS
index c3fb3c8..dd6ec1b 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -42,6 +42,15 @@
 Library
 -------
 
+- bpo-30365: Running Python with the -3 option now emits deprecation warnings
+  for getchildren() and getiterator() methods of the Element class in the
+  xml.etree.cElementTree module and when pass the html argument to
+  xml.etree.ElementTree.XMLParser().
+
+- bpo-30365: Fixed a deprecation warning about the doctype() method of the
+  xml.etree.ElementTree.XMLParser class.  Now it is emitted only when define
+  the doctype() method in the subclass of XMLParser.
+
 - bpo-30329: imaplib now catchs the Windows socket WSAEINVAL error
   (code 10022) on shutdown(SHUT_RDWR): An invalid operation was attempted.
   This error occurs sometimes on SSL connections.
diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c
index 0a01c3c..b9e9b3a 100644
--- a/Modules/_elementtree.c
+++ b/Modules/_elementtree.c
@@ -962,7 +962,11 @@
     int i;
     PyObject* list;
 
-    /* FIXME: report as deprecated? */
+    if (PyErr_WarnPy3k("This method will be removed in future versions.  "
+                       "Use 'list(elem)' or iteration over elem instead.",
+                       1) < 0) {
+        return NULL;
+    }
 
     if (!PyArg_ParseTuple(args, ":getchildren"))
         return NULL;
@@ -984,13 +988,10 @@
 }
 
 static PyObject*
-element_iter(ElementObject* self, PyObject* args)
+element_iter_impl(ElementObject* self, PyObject* tag)
 {
+    PyObject* args;
     PyObject* result;
-    
-    PyObject* tag = Py_None;
-    if (!PyArg_ParseTuple(args, "|O:iter", &tag))
-        return NULL;
 
     if (!elementtree_iter_obj) {
         PyErr_SetString(
@@ -1014,6 +1015,34 @@
     return result;
 }
 
+static PyObject*
+element_iter(ElementObject* self, PyObject* args)
+{
+    PyObject* tag = Py_None;
+    if (!PyArg_ParseTuple(args, "|O:iter", &tag))
+        return NULL;
+
+    return element_iter_impl(self, tag);
+}
+
+static PyObject*
+element_getiterator(ElementObject* self, PyObject* args)
+{
+    PyObject* tag = Py_None;
+    if (!PyArg_ParseTuple(args, "|O:getiterator", &tag))
+        return NULL;
+
+    /* Change for a DeprecationWarning in 1.4 */
+    if (Py_Py3kWarningFlag &&
+        PyErr_WarnEx(PyExc_PendingDeprecationWarning,
+                     "This method will be removed in future versions.  "
+                     "Use 'tree.iter()' or 'list(tree.iter())' instead.",
+                     1) < 0) {
+        return NULL;
+    }
+    return element_iter_impl(self, tag);
+}
+
 
 static PyObject*
 element_itertext(ElementObject* self, PyObject* args)
@@ -1510,7 +1539,7 @@
     {"itertext", (PyCFunction) element_itertext, METH_VARARGS},
     {"iterfind", (PyCFunction) element_iterfind, METH_VARARGS},
 
-    {"getiterator", (PyCFunction) element_iter, METH_VARARGS},
+    {"getiterator", (PyCFunction) element_getiterator, METH_VARARGS},
     {"getchildren", (PyCFunction) element_getchildren, METH_VARARGS},
 
     {"items", (PyCFunction) element_items, METH_VARARGS},