Update xml to 3.14.3

This commit is contained in:
ShaharNaveh
2026-02-26 13:35:49 +01:00
parent 09cf49204b
commit fd5e91dbff
10 changed files with 3990 additions and 335 deletions

1788
Lib/test/test_minidom.py vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -23,8 +23,8 @@ SMALL_SAMPLE = """<?xml version="1.0"?>
class PullDOMTestCase(unittest.TestCase):
# TODO: RUSTPYTHON FileNotFoundError: [Errno 2] No such file or directory (os error 2): 'xmltestdata/test.xml' -> 'None'
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; FileNotFoundError: [Errno 2] No such file or directory (os error 2): 'xmltestdata/test.xml' -> 'None'
def test_parse(self):
"""Minimal test of DOMEventStream.parse()"""
@@ -41,15 +41,14 @@ class PullDOMTestCase(unittest.TestCase):
with open(tstfile, "rb") as fin:
list(pulldom.parse(fin))
# TODO: RUSTPYTHON implement DOM semantic
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; implement DOM semantic
def test_parse_semantics(self):
"""Test DOMEventStream parsing semantics."""
items = pulldom.parseString(SMALL_SAMPLE)
evt, node = next(items)
# Just check the node is a Document:
self.assertTrue(hasattr(node, "createElement"))
self.assertHasAttr(node, "createElement")
self.assertEqual(pulldom.START_DOCUMENT, evt)
evt, node = next(items)
self.assertEqual(pulldom.START_ELEMENT, evt)
@@ -105,8 +104,7 @@ class PullDOMTestCase(unittest.TestCase):
#evt, node = next(items)
#self.assertEqual(pulldom.END_DOCUMENT, evt)
# TODO: RUSTPYTHON pulldom.parseString(SMALL_SAMPLE) return iterator with tuple with 2 elements
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; pulldom.parseString(SMALL_SAMPLE) return iterator with tuple with 2 elements
def test_expandItem(self):
"""Ensure expandItem works as expected."""
items = pulldom.parseString(SMALL_SAMPLE)
@@ -197,7 +195,7 @@ class ThoroughTestCase(unittest.TestCase):
evt, node = next(pd)
self.assertEqual(pulldom.START_DOCUMENT, evt)
# Just check the node is a Document:
self.assertTrue(hasattr(node, "createElement"))
self.assertHasAttr(node, "createElement")
if before_root:
evt, node = next(pd)
@@ -303,8 +301,7 @@ class SAX2DOMTestCase(unittest.TestCase):
def confirm(self, test, testname="Test"):
self.assertTrue(test, testname)
# TODO: RUSTPYTHON read from stream io
@unittest.expectedFailure
@unittest.expectedFailure # TODO: RUSTPYTHON; read from stream io
def test_basic(self):
"""Ensure SAX2DOM can parse from a stream."""
with io.StringIO(SMALL_SAMPLE) as fin:

View File

@@ -1,14 +1,18 @@
# XXX TypeErrors on calling handlers, or on bad return values from a
# handler, are obscure and unhelpful.
import abc
import functools
import os
import re
import sys
import sysconfig
import textwrap
import unittest
import traceback
from io import BytesIO
from test import support
from test.support import os_helper
from test.support import import_helper, os_helper
from xml.parsers import expat
from xml.parsers.expat import errors
@@ -261,7 +265,7 @@ class ParseTest(unittest.TestCase):
operations = out.out
self._verify_parse_output(operations)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_parse_again(self):
parser = expat.ParserCreate()
file = BytesIO(data)
@@ -282,7 +286,7 @@ class NamespaceSeparatorTest(unittest.TestCase):
expat.ParserCreate(namespace_separator=None)
expat.ParserCreate(namespace_separator=' ')
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_illegal(self):
with self.assertRaisesRegex(TypeError,
r"ParserCreate\(\) argument (2|'namespace_separator') "
@@ -309,7 +313,7 @@ class NamespaceSeparatorTest(unittest.TestCase):
class InterningTest(unittest.TestCase):
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test(self):
# Test the interning machinery.
p = expat.ParserCreate()
@@ -325,7 +329,7 @@ class InterningTest(unittest.TestCase):
# L should have the same string repeated over and over.
self.assertTrue(tag is entry)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_issue9402(self):
# create an ExternalEntityParserCreate with buffer text
class ExternalOutputter:
@@ -383,7 +387,7 @@ class BufferTextTest(unittest.TestCase):
parser = expat.ParserCreate()
self.assertFalse(parser.buffer_text)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_buffering_enabled(self):
# Make sure buffering is turned on
self.assertTrue(self.parser.buffer_text)
@@ -391,7 +395,7 @@ class BufferTextTest(unittest.TestCase):
self.assertEqual(self.stuff, ['123'],
"buffered text not properly collapsed")
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test1(self):
# XXX This test exposes more detail of Expat's text chunking than we
# XXX like, but it tests what we need to concisely.
@@ -401,7 +405,7 @@ class BufferTextTest(unittest.TestCase):
["<a>", "1", "<b>", "2", "\n", "3", "<c>", "4\n5"],
"buffering control not reacting as expected")
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test2(self):
self.parser.Parse(b"<a>1<b/>&lt;2&gt;<c/>&#32;\n&#x20;3</a>", True)
self.assertEqual(self.stuff, ["1<2> \n 3"],
@@ -434,7 +438,7 @@ class BufferTextTest(unittest.TestCase):
["<a>", "1", "<b>", "</b>", "2", "<c>", "</c>", "345", "</a>"],
"buffered text not properly split")
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test7(self):
self.setHandlers(["CommentHandler", "EndElementHandler",
"StartElementHandler"])
@@ -536,7 +540,7 @@ class PositionTest(unittest.TestCase):
class sf1296433Test(unittest.TestCase):
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: Expected type 'str' but 'bytes' found.
@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: Expected type 'str' but 'bytes' found.
def test_parse_only_xml_data(self):
# https://bugs.python.org/issue1296433
#
@@ -560,15 +564,15 @@ class ChardataBufferTest(unittest.TestCase):
test setting of chardata buffer size
"""
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_1025_bytes(self):
self.assertEqual(self.small_buffer_test(1025), 2)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_1000_bytes(self):
self.assertEqual(self.small_buffer_test(1000), 1)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_wrong_size(self):
parser = expat.ParserCreate()
parser.buffer_text = 1
@@ -581,7 +585,7 @@ class ChardataBufferTest(unittest.TestCase):
with self.assertRaises(TypeError):
parser.buffer_size = 512.0
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_unchanged_size(self):
xml1 = b"<?xml version='1.0' encoding='iso8859'?><s>" + b'a' * 512
xml2 = b'a'*512 + b'</s>'
@@ -605,7 +609,7 @@ class ChardataBufferTest(unittest.TestCase):
self.assertEqual(self.n, 2)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_disabling_buffer(self):
xml1 = b"<?xml version='1.0' encoding='iso8859'?><a>" + b'a' * 512
xml2 = b'b' * 1024
@@ -650,7 +654,7 @@ class ChardataBufferTest(unittest.TestCase):
parser.Parse(xml)
return self.n
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_change_size_1(self):
xml1 = b"<?xml version='1.0' encoding='iso8859'?><a><s>" + b'a' * 1024
xml2 = b'aaa</s><s>' + b'a' * 1025 + b'</s></a>'
@@ -667,7 +671,7 @@ class ChardataBufferTest(unittest.TestCase):
parser.Parse(xml2, True)
self.assertEqual(self.n, 2)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_change_size_2(self):
xml1 = b"<?xml version='1.0' encoding='iso8859'?><a>a<s>" + b'a' * 1023
xml2 = b'aaa</s><s>' + b'a' * 1025 + b'</s></a>'
@@ -684,8 +688,25 @@ class ChardataBufferTest(unittest.TestCase):
parser.Parse(xml2, True)
self.assertEqual(self.n, 4)
class ElementDeclHandlerTest(unittest.TestCase):
def test_trigger_leak(self):
# Unfixed, this test would leak the memory of the so-called
# "content model" in function ``my_ElementDeclHandler`` of pyexpat.
# See https://github.com/python/cpython/issues/140593.
data = textwrap.dedent('''\
<!DOCTYPE quotations SYSTEM "quotations.dtd" [
<!ELEMENT root ANY>
]>
<root/>
''').encode('UTF-8')
parser = expat.ParserCreate()
parser.NotStandaloneHandler = lambda: 1.234 # arbitrary float
parser.ElementDeclHandler = lambda _1, _2: None
self.assertRaises(TypeError, parser.Parse, data, True)
class MalformedInputTest(unittest.TestCase):
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test1(self):
xml = b"\0\r\n"
parser = expat.ParserCreate()
@@ -695,7 +716,7 @@ class MalformedInputTest(unittest.TestCase):
except expat.ExpatError as e:
self.assertEqual(str(e), 'unclosed token: line 2, column 0')
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test2(self):
# \xc2\x85 is UTF-8 encoded U+0085 (NEXT LINE)
xml = b"<?xml version\xc2\x85='1.0'?>\r\n"
@@ -705,13 +726,13 @@ class MalformedInputTest(unittest.TestCase):
parser.Parse(xml, True)
class ErrorMessageTest(unittest.TestCase):
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_codes(self):
# verify mapping of errors.codes and errors.messages
self.assertEqual(errors.XML_ERROR_SYNTAX,
errors.messages[errors.codes[errors.XML_ERROR_SYNTAX]])
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_expaterror(self):
xml = b'<'
parser = expat.ParserCreate()
@@ -727,7 +748,7 @@ class ForeignDTDTests(unittest.TestCase):
"""
Tests for the UseForeignDTD method of expat parser objects.
"""
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_use_foreign_dtd(self):
"""
If UseForeignDTD is passed True and a document without an external
@@ -756,7 +777,7 @@ class ForeignDTDTests(unittest.TestCase):
parser.Parse(b"<?xml version='1.0'?><element/>")
self.assertEqual(handler_call_args, [(None, None)])
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_ignore_use_foreign_dtd(self):
"""
If UseForeignDTD is passed True and a document with an external
@@ -785,7 +806,7 @@ class ParentParserLifetimeTest(unittest.TestCase):
See https://github.com/python/cpython/issues/139400.
"""
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'ExternalEntityParserCreate'
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'ExternalEntityParserCreate'
def test_parent_parser_outlives_its_subparsers__single(self):
parser = expat.ParserCreate()
subparser = parser.ExternalEntityParserCreate(None)
@@ -794,7 +815,7 @@ class ParentParserLifetimeTest(unittest.TestCase):
# while it's still being referenced by a related subparser.
del parser
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'ExternalEntityParserCreate'
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'ExternalEntityParserCreate'
def test_parent_parser_outlives_its_subparsers__multiple(self):
parser = expat.ParserCreate()
subparser_one = parser.ExternalEntityParserCreate(None)
@@ -804,7 +825,7 @@ class ParentParserLifetimeTest(unittest.TestCase):
# while it's still being referenced by a related subparser.
del parser
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'ExternalEntityParserCreate'
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'ExternalEntityParserCreate'
def test_parent_parser_outlives_its_subparsers__chain(self):
parser = expat.ParserCreate()
subparser = parser.ExternalEntityParserCreate(None)
@@ -817,7 +838,7 @@ class ParentParserLifetimeTest(unittest.TestCase):
class ReparseDeferralTest(unittest.TestCase):
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'GetReparseDeferralEnabled'
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'GetReparseDeferralEnabled'
def test_getter_setter_round_trip(self):
parser = expat.ParserCreate()
enabled = (expat.version_info >= (2, 6, 0))
@@ -828,7 +849,7 @@ class ReparseDeferralTest(unittest.TestCase):
parser.SetReparseDeferralEnabled(True)
self.assertIs(parser.GetReparseDeferralEnabled(), enabled)
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'GetReparseDeferralEnabled'
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'GetReparseDeferralEnabled'
def test_reparse_deferral_enabled(self):
if expat.version_info < (2, 6, 0):
self.skipTest(f'Expat {expat.version_info} does not '
@@ -853,7 +874,7 @@ class ReparseDeferralTest(unittest.TestCase):
self.assertEqual(started, ['doc'])
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'SetReparseDeferralEnabled'
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'xmlparser' object has no attribute 'SetReparseDeferralEnabled'
def test_reparse_deferral_disabled(self):
started = []
@@ -873,5 +894,199 @@ class ReparseDeferralTest(unittest.TestCase):
self.assertEqual(started, ['doc'])
class AttackProtectionTestBase(abc.ABC):
"""
Base class for testing protections against XML payloads with
disproportionate amplification.
The protections being tested should detect and prevent attacks
that leverage disproportionate amplification from small inputs.
"""
@staticmethod
def exponential_expansion_payload(*, nrows, ncols, text='.'):
"""Create a billion laughs attack payload.
Be careful: the number of total items is pow(n, k), thereby
requiring at least pow(ncols, nrows) * sizeof(text) memory!
"""
template = textwrap.dedent(f"""\
<?xml version="1.0"?>
<!DOCTYPE doc [
<!ENTITY row0 "{text}">
<!ELEMENT doc (#PCDATA)>
{{body}}
]>
<doc>&row{nrows};</doc>
""").rstrip()
body = '\n'.join(
f'<!ENTITY row{i + 1} "{f"&row{i};" * ncols}">'
for i in range(nrows)
)
body = textwrap.indent(body, ' ' * 4)
return template.format(body=body)
def test_payload_generation(self):
# self-test for exponential_expansion_payload()
payload = self.exponential_expansion_payload(nrows=2, ncols=3)
self.assertEqual(payload, textwrap.dedent("""\
<?xml version="1.0"?>
<!DOCTYPE doc [
<!ENTITY row0 ".">
<!ELEMENT doc (#PCDATA)>
<!ENTITY row1 "&row0;&row0;&row0;">
<!ENTITY row2 "&row1;&row1;&row1;">
]>
<doc>&row2;</doc>
""").rstrip())
def assert_root_parser_failure(self, func, /, *args, **kwargs):
"""Check that func(*args, **kwargs) is invalid for a sub-parser."""
msg = "parser must be a root parser"
self.assertRaisesRegex(expat.ExpatError, msg, func, *args, **kwargs)
@abc.abstractmethod
def assert_rejected(self, func, /, *args, **kwargs):
"""Assert that func(*args, **kwargs) triggers the attack protection.
Note: this method must ensure that the attack protection being tested
is the one that is actually triggered at runtime, e.g., by matching
the exact error message.
"""
@abc.abstractmethod
def set_activation_threshold(self, parser, threshold):
"""Set the activation threshold for the tested protection."""
@abc.abstractmethod
def set_maximum_amplification(self, parser, max_factor):
"""Set the maximum amplification factor for the tested protection."""
@abc.abstractmethod
def test_set_activation_threshold__threshold_reached(self):
"""Test when the activation threshold is exceeded."""
@abc.abstractmethod
def test_set_activation_threshold__threshold_not_reached(self):
"""Test when the activation threshold is not exceeded."""
def test_set_activation_threshold__invalid_threshold_type(self):
parser = expat.ParserCreate()
setter = functools.partial(self.set_activation_threshold, parser)
self.assertRaises(TypeError, setter, 1.0)
self.assertRaises(TypeError, setter, -1.5)
self.assertRaises(ValueError, setter, -5)
def test_set_activation_threshold__invalid_threshold_range(self):
_testcapi = import_helper.import_module("_testcapi")
parser = expat.ParserCreate()
setter = functools.partial(self.set_activation_threshold, parser)
self.assertRaises(OverflowError, setter, _testcapi.ULLONG_MAX + 1)
def test_set_activation_threshold__fail_for_subparser(self):
parser = expat.ParserCreate()
subparser = parser.ExternalEntityParserCreate(None)
setter = functools.partial(self.set_activation_threshold, subparser)
self.assert_root_parser_failure(setter, 12345)
@abc.abstractmethod
def test_set_maximum_amplification__amplification_exceeded(self):
"""Test when the amplification factor is exceeded."""
@abc.abstractmethod
def test_set_maximum_amplification__amplification_not_exceeded(self):
"""Test when the amplification factor is not exceeded."""
def test_set_maximum_amplification__infinity(self):
inf = float('inf') # an 'inf' threshold is allowed by Expat
parser = expat.ParserCreate()
self.assertIsNone(self.set_maximum_amplification(parser, inf))
def test_set_maximum_amplification__invalid_max_factor_type(self):
parser = expat.ParserCreate()
setter = functools.partial(self.set_maximum_amplification, parser)
self.assertRaises(TypeError, setter, None)
self.assertRaises(TypeError, setter, 'abc')
def test_set_maximum_amplification__invalid_max_factor_range(self):
parser = expat.ParserCreate()
setter = functools.partial(self.set_maximum_amplification, parser)
msg = re.escape("'max_factor' must be at least 1.0")
self.assertRaisesRegex(expat.ExpatError, msg, setter, float('nan'))
self.assertRaisesRegex(expat.ExpatError, msg, setter, 0.99)
def test_set_maximum_amplification__fail_for_subparser(self):
parser = expat.ParserCreate()
subparser = parser.ExternalEntityParserCreate(None)
setter = functools.partial(self.set_maximum_amplification, subparser)
self.assert_root_parser_failure(setter, 123.45)
@unittest.skipIf(expat.version_info < (2, 7, 2), "requires Expat >= 2.7.2")
class MemoryProtectionTest(AttackProtectionTestBase, unittest.TestCase):
# NOTE: with the default Expat configuration, the billion laughs protection
# may hit before the allocation limiter if exponential_expansion_payload()
# is not carefully parametrized. As such, the payloads should be chosen so
# that either the allocation limiter is hit before other protections are
# triggered or no protection at all is triggered.
def assert_rejected(self, func, /, *args, **kwargs):
"""Check that func(*args, **kwargs) hits the allocation limit."""
msg = r"out of memory: line \d+, column \d+"
self.assertRaisesRegex(expat.ExpatError, msg, func, *args, **kwargs)
def set_activation_threshold(self, parser, threshold):
return parser.SetAllocTrackerActivationThreshold(threshold)
def set_maximum_amplification(self, parser, max_factor):
return parser.SetAllocTrackerMaximumAmplification(max_factor)
def test_set_activation_threshold__threshold_reached(self):
parser = expat.ParserCreate()
# Choose a threshold expected to be always reached.
self.set_activation_threshold(parser, 3)
# Check that the threshold is reached by choosing a small factor
# and a payload whose peak amplification factor exceeds it.
self.assertIsNone(self.set_maximum_amplification(parser, 1.0))
payload = self.exponential_expansion_payload(ncols=10, nrows=4)
self.assert_rejected(parser.Parse, payload, True)
def test_set_activation_threshold__threshold_not_reached(self):
parser = expat.ParserCreate()
# Choose a threshold expected to be never reached.
self.set_activation_threshold(parser, pow(10, 5))
# Check that the threshold is reached by choosing a small factor
# and a payload whose peak amplification factor exceeds it.
self.assertIsNone(self.set_maximum_amplification(parser, 1.0))
payload = self.exponential_expansion_payload(ncols=10, nrows=4)
self.assertIsNotNone(parser.Parse(payload, True))
def test_set_maximum_amplification__amplification_exceeded(self):
parser = expat.ParserCreate()
# Unconditionally enable maximum activation factor.
self.set_activation_threshold(parser, 0)
# Choose a max amplification factor expected to always be exceeded.
self.assertIsNone(self.set_maximum_amplification(parser, 1.0))
# Craft a payload for which the peak amplification factor is > 1.0.
payload = self.exponential_expansion_payload(ncols=1, nrows=2)
self.assert_rejected(parser.Parse, payload, True)
def test_set_maximum_amplification__amplification_not_exceeded(self):
parser = expat.ParserCreate()
# Unconditionally enable maximum activation factor.
self.set_activation_threshold(parser, 0)
# Choose a max amplification factor expected to never be exceeded.
self.assertIsNone(self.set_maximum_amplification(parser, 1e4))
# Craft a payload for which the peak amplification factor is < 1e4.
payload = self.exponential_expansion_payload(ncols=1, nrows=2)
self.assertIsNotNone(parser.Parse(payload, True))
if __name__ == "__main__":
unittest.main()

1577
Lib/test/test_sax.py vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -155,9 +155,9 @@ class ModuleTest(unittest.TestCase):
def test_sanity(self):
# Import sanity.
from xml.etree import ElementTree
from xml.etree import ElementInclude
from xml.etree import ElementPath
from xml.etree import ElementTree # noqa: F401
from xml.etree import ElementInclude # noqa: F401
from xml.etree import ElementPath # noqa: F401
def test_all(self):
names = ("xml.etree.ElementTree", "_elementtree")
@@ -252,8 +252,7 @@ class ElementTreeTest(unittest.TestCase):
self.assertTrue(ET.iselement(element), msg="not an element")
direlem = dir(element)
for attr in 'tag', 'attrib', 'text', 'tail':
self.assertTrue(hasattr(element, attr),
msg='no %s member' % attr)
self.assertHasAttr(element, attr)
self.assertIn(attr, direlem,
msg='no %s visible by dir' % attr)
@@ -278,7 +277,7 @@ class ElementTreeTest(unittest.TestCase):
# Make sure all standard element methods exist.
def check_method(method):
self.assertTrue(hasattr(method, '__call__'),
self.assertHasAttr(method, '__call__',
msg="%s not callable" % method)
check_method(element.append)
@@ -339,7 +338,7 @@ class ElementTreeTest(unittest.TestCase):
element.attrib = {'A': 'B', 'C': 'D'}
self.assertEqual(element.attrib, {'A': 'B', 'C': 'D'})
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_simpleops(self):
# Basic method sanity checks.
@@ -372,9 +371,9 @@ class ElementTreeTest(unittest.TestCase):
self.serialize_check(element, '<tag key="value"><subtag /></tag>') # 4
element.remove(subelement)
self.serialize_check(element, '<tag key="value" />') # 5
with self.assertRaises(ValueError) as cm:
with self.assertRaisesRegex(ValueError,
r'Element\.remove\(.+\): element not found'):
element.remove(subelement)
self.assertEqual(str(cm.exception), 'list.remove(x): x not in list')
self.serialize_check(element, '<tag key="value" />') # 6
element[0:0] = [subelement, subelement, subelement]
self.serialize_check(element[1], '<subtag />')
@@ -394,7 +393,7 @@ class ElementTreeTest(unittest.TestCase):
self.serialize_check(ET.XML("<tag><![CDATA[hello]]></tag>"),
'<tag>hello</tag>')
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_file_init(self):
stringfile = io.BytesIO(SAMPLE_XML.encode("utf-8"))
tree = ET.ElementTree(file=stringfile)
@@ -510,7 +509,7 @@ class ElementTreeTest(unittest.TestCase):
elem[:] = tuple([subelem])
self.serialize_check(elem, '<tag><subtag key="value" /></tag>')
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_parsefile(self):
# Test parsing from file.
@@ -556,7 +555,7 @@ class ElementTreeTest(unittest.TestCase):
' <empty-element />\n'
'</root>')
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_parseliteral(self):
element = ET.XML("<html><body>text</body></html>")
self.assertEqual(ET.tostring(element, encoding='unicode'),
@@ -579,210 +578,6 @@ class ElementTreeTest(unittest.TestCase):
self.assertEqual(len(ids), 1)
self.assertEqual(ids["body"].tag, 'body')
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_iterparse(self):
# Test iterparse interface.
iterparse = ET.iterparse
context = iterparse(SIMPLE_XMLFILE)
self.assertIsNone(context.root)
action, elem = next(context)
self.assertIsNone(context.root)
self.assertEqual((action, elem.tag), ('end', 'element'))
self.assertEqual([(action, elem.tag) for action, elem in context], [
('end', 'element'),
('end', 'empty-element'),
('end', 'root'),
])
self.assertEqual(context.root.tag, 'root')
context = iterparse(SIMPLE_NS_XMLFILE)
self.assertEqual([(action, elem.tag) for action, elem in context], [
('end', '{namespace}element'),
('end', '{namespace}element'),
('end', '{namespace}empty-element'),
('end', '{namespace}root'),
])
with open(SIMPLE_XMLFILE, 'rb') as source:
context = iterparse(source)
action, elem = next(context)
self.assertEqual((action, elem.tag), ('end', 'element'))
self.assertEqual([(action, elem.tag) for action, elem in context], [
('end', 'element'),
('end', 'empty-element'),
('end', 'root'),
])
self.assertEqual(context.root.tag, 'root')
events = ()
context = iterparse(SIMPLE_XMLFILE, events)
self.assertEqual([(action, elem.tag) for action, elem in context], [])
events = ()
context = iterparse(SIMPLE_XMLFILE, events=events)
self.assertEqual([(action, elem.tag) for action, elem in context], [])
events = ("start", "end")
context = iterparse(SIMPLE_XMLFILE, events)
self.assertEqual([(action, elem.tag) for action, elem in context], [
('start', 'root'),
('start', 'element'),
('end', 'element'),
('start', 'element'),
('end', 'element'),
('start', 'empty-element'),
('end', 'empty-element'),
('end', 'root'),
])
events = ("start", "end", "start-ns", "end-ns")
context = iterparse(SIMPLE_NS_XMLFILE, events)
self.assertEqual([(action, elem.tag) if action in ("start", "end")
else (action, elem)
for action, elem in context], [
('start-ns', ('', 'namespace')),
('start', '{namespace}root'),
('start', '{namespace}element'),
('end', '{namespace}element'),
('start', '{namespace}element'),
('end', '{namespace}element'),
('start', '{namespace}empty-element'),
('end', '{namespace}empty-element'),
('end', '{namespace}root'),
('end-ns', None),
])
events = ('start-ns', 'end-ns')
context = iterparse(io.StringIO(r"<root xmlns=''/>"), events)
res = [action for action, elem in context]
self.assertEqual(res, ['start-ns', 'end-ns'])
events = ("start", "end", "bogus")
with open(SIMPLE_XMLFILE, "rb") as f:
with self.assertRaises(ValueError) as cm:
iterparse(f, events)
self.assertFalse(f.closed)
self.assertEqual(str(cm.exception), "unknown event 'bogus'")
with warnings_helper.check_no_resource_warning(self):
with self.assertRaises(ValueError) as cm:
iterparse(SIMPLE_XMLFILE, events)
self.assertEqual(str(cm.exception), "unknown event 'bogus'")
del cm
source = io.BytesIO(
b"<?xml version='1.0' encoding='iso-8859-1'?>\n"
b"<body xmlns='http://&#233;ffbot.org/ns'\n"
b" xmlns:cl\xe9='http://effbot.org/ns'>text</body>\n")
events = ("start-ns",)
context = iterparse(source, events)
self.assertEqual([(action, elem) for action, elem in context], [
('start-ns', ('', 'http://\xe9ffbot.org/ns')),
('start-ns', ('cl\xe9', 'http://effbot.org/ns')),
])
source = io.StringIO("<document />junk")
it = iterparse(source)
action, elem = next(it)
self.assertEqual((action, elem.tag), ('end', 'document'))
with self.assertRaises(ET.ParseError) as cm:
next(it)
self.assertEqual(str(cm.exception),
'junk after document element: line 1, column 12')
self.addCleanup(os_helper.unlink, TESTFN)
with open(TESTFN, "wb") as f:
f.write(b"<document />junk")
it = iterparse(TESTFN)
action, elem = next(it)
self.assertEqual((action, elem.tag), ('end', 'document'))
with warnings_helper.check_no_resource_warning(self):
with self.assertRaises(ET.ParseError) as cm:
next(it)
self.assertEqual(str(cm.exception),
'junk after document element: line 1, column 12')
del cm, it
# Not exhausting the iterator still closes the resource (bpo-43292)
with warnings_helper.check_no_resource_warning(self):
it = iterparse(SIMPLE_XMLFILE)
del it
with warnings_helper.check_no_resource_warning(self):
it = iterparse(SIMPLE_XMLFILE)
it.close()
del it
with warnings_helper.check_no_resource_warning(self):
it = iterparse(SIMPLE_XMLFILE)
action, elem = next(it)
self.assertEqual((action, elem.tag), ('end', 'element'))
del it, elem
with warnings_helper.check_no_resource_warning(self):
it = iterparse(SIMPLE_XMLFILE)
action, elem = next(it)
it.close()
self.assertEqual((action, elem.tag), ('end', 'element'))
del it, elem
with self.assertRaises(FileNotFoundError):
iterparse("nonexistent")
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_iterparse_close(self):
iterparse = ET.iterparse
it = iterparse(SIMPLE_XMLFILE)
it.close()
with self.assertRaises(StopIteration):
next(it)
it.close() # idempotent
with open(SIMPLE_XMLFILE, 'rb') as source:
it = iterparse(source)
it.close()
self.assertFalse(source.closed)
with self.assertRaises(StopIteration):
next(it)
it.close() # idempotent
it = iterparse(SIMPLE_XMLFILE)
action, elem = next(it)
self.assertEqual((action, elem.tag), ('end', 'element'))
it.close()
with self.assertRaises(StopIteration):
next(it)
it.close() # idempotent
with open(SIMPLE_XMLFILE, 'rb') as source:
it = iterparse(source)
action, elem = next(it)
self.assertEqual((action, elem.tag), ('end', 'element'))
it.close()
self.assertFalse(source.closed)
with self.assertRaises(StopIteration):
next(it)
it.close() # idempotent
it = iterparse(SIMPLE_XMLFILE)
list(it)
it.close()
with self.assertRaises(StopIteration):
next(it)
it.close() # idempotent
with open(SIMPLE_XMLFILE, 'rb') as source:
it = iterparse(source)
list(it)
it.close()
self.assertFalse(source.closed)
with self.assertRaises(StopIteration):
next(it)
it.close() # idempotent
def test_writefile(self):
elem = ET.Element("tag")
elem.text = "text"
@@ -800,7 +595,7 @@ class ElementTreeTest(unittest.TestCase):
elem[0] = ET.PI("key", "value")
self.serialize_check(elem, 'text<?key value?><subtag>subtext</subtag>')
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_custom_builder(self):
# Test parser w. custom builder.
@@ -862,7 +657,7 @@ class ElementTreeTest(unittest.TestCase):
('end-ns', ''),
])
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_custom_builder_only_end_ns(self):
class Builder(list):
def end_ns(self, prefix):
@@ -894,7 +689,7 @@ class ElementTreeTest(unittest.TestCase):
parser2 = ET.XMLParser()
self.assertIsInstance(parser2.target, ET.TreeBuilder)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_children(self):
# Test Element children iteration
@@ -1174,7 +969,7 @@ class ElementTreeTest(unittest.TestCase):
self.assertRegex(stringlist[0], r"^<\?xml version='1.0' encoding='.+'?>")
self.assertEqual(['<body', '>', '<tag', ' />', '</body>'], stringlist[1:])
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_encoding(self):
def check(encoding, body=''):
xml = ("<?xml version='1.0' encoding='%s'?><xml>%s</xml>" %
@@ -1256,7 +1051,7 @@ class ElementTreeTest(unittest.TestCase):
self.assertEqual(serialize(e, method="html"),
'<html><CamelCase>text</CamelCase></html>')
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_entity(self):
# Test entity handling.
@@ -1294,7 +1089,7 @@ class ElementTreeTest(unittest.TestCase):
self.assertEqual(str(cm.exception),
'undefined entity &entity;: line 4, column 10')
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_namespace(self):
# Test namespace issues.
@@ -1505,13 +1300,249 @@ class ElementTreeTest(unittest.TestCase):
self.assertEqual(serialize(root, method='html'),
'<cirriculum status="public" company="example"></cirriculum>')
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_attlist_default(self):
# Test default attribute values; See BPO 42151.
root = ET.fromstring(ATTLIST_XML)
self.assertEqual(root[0].attrib,
{'{http://www.w3.org/XML/1998/namespace}lang': 'eng'})
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_iterparse(self):
return super().test_iterparse()
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_iterparse_close(self):
return super().test_iterparse_close()
class IterparseTest(unittest.TestCase):
# Test iterparse interface.
def test_basic(self):
iterparse = ET.iterparse
it = iterparse(SIMPLE_XMLFILE)
self.assertIsNone(it.root)
action, elem = next(it)
self.assertIsNone(it.root)
self.assertEqual((action, elem.tag), ('end', 'element'))
self.assertEqual([(action, elem.tag) for action, elem in it], [
('end', 'element'),
('end', 'empty-element'),
('end', 'root'),
])
self.assertEqual(it.root.tag, 'root')
it.close()
it = iterparse(SIMPLE_NS_XMLFILE)
self.assertEqual([(action, elem.tag) for action, elem in it], [
('end', '{namespace}element'),
('end', '{namespace}element'),
('end', '{namespace}empty-element'),
('end', '{namespace}root'),
])
it.close()
def test_external_file(self):
with open(SIMPLE_XMLFILE, 'rb') as source:
it = ET.iterparse(source)
action, elem = next(it)
self.assertEqual((action, elem.tag), ('end', 'element'))
self.assertEqual([(action, elem.tag) for action, elem in it], [
('end', 'element'),
('end', 'empty-element'),
('end', 'root'),
])
self.assertEqual(it.root.tag, 'root')
def test_events(self):
iterparse = ET.iterparse
events = ()
it = iterparse(SIMPLE_XMLFILE, events)
self.assertEqual([(action, elem.tag) for action, elem in it], [])
it.close()
events = ()
it = iterparse(SIMPLE_XMLFILE, events=events)
self.assertEqual([(action, elem.tag) for action, elem in it], [])
it.close()
events = ("start", "end")
it = iterparse(SIMPLE_XMLFILE, events)
self.assertEqual([(action, elem.tag) for action, elem in it], [
('start', 'root'),
('start', 'element'),
('end', 'element'),
('start', 'element'),
('end', 'element'),
('start', 'empty-element'),
('end', 'empty-element'),
('end', 'root'),
])
it.close()
def test_namespace_events(self):
iterparse = ET.iterparse
events = ("start", "end", "start-ns", "end-ns")
it = iterparse(SIMPLE_NS_XMLFILE, events)
self.assertEqual([(action, elem.tag) if action in ("start", "end")
else (action, elem)
for action, elem in it], [
('start-ns', ('', 'namespace')),
('start', '{namespace}root'),
('start', '{namespace}element'),
('end', '{namespace}element'),
('start', '{namespace}element'),
('end', '{namespace}element'),
('start', '{namespace}empty-element'),
('end', '{namespace}empty-element'),
('end', '{namespace}root'),
('end-ns', None),
])
it.close()
events = ('start-ns', 'end-ns')
it = iterparse(io.BytesIO(br"<root xmlns=''/>"), events)
res = [action for action, elem in it]
self.assertEqual(res, ['start-ns', 'end-ns'])
it.close()
def test_unknown_events(self):
iterparse = ET.iterparse
events = ("start", "end", "bogus")
with open(SIMPLE_XMLFILE, "rb") as f:
with self.assertRaises(ValueError) as cm:
iterparse(f, events)
self.assertFalse(f.closed)
self.assertEqual(str(cm.exception), "unknown event 'bogus'")
with warnings_helper.check_no_resource_warning(self):
with self.assertRaises(ValueError) as cm:
iterparse(SIMPLE_XMLFILE, events)
self.assertEqual(str(cm.exception), "unknown event 'bogus'")
del cm
gc_collect()
def test_non_utf8(self):
source = io.BytesIO(
b"<?xml version='1.0' encoding='iso-8859-1'?>\n"
b"<body xmlns='http://&#233;ffbot.org/ns'\n"
b" xmlns:cl\xe9='http://effbot.org/ns'>text</body>\n")
events = ("start-ns",)
it = ET.iterparse(source, events)
self.assertEqual([(action, elem) for action, elem in it], [
('start-ns', ('', 'http://\xe9ffbot.org/ns')),
('start-ns', ('cl\xe9', 'http://effbot.org/ns')),
])
def test_parsing_error(self):
source = io.BytesIO(b"<document />junk")
it = ET.iterparse(source)
action, elem = next(it)
self.assertEqual((action, elem.tag), ('end', 'document'))
with self.assertRaises(ET.ParseError) as cm:
next(it)
self.assertEqual(str(cm.exception),
'junk after document element: line 1, column 12')
def test_nonexistent_file(self):
with self.assertRaises(FileNotFoundError):
ET.iterparse("nonexistent")
def test_resource_warnings_not_exhausted(self):
# Not exhausting the iterator still closes the underlying file (bpo-43292)
it = ET.iterparse(SIMPLE_XMLFILE)
with warnings_helper.check_no_resource_warning(self):
del it
gc_collect()
it = ET.iterparse(SIMPLE_XMLFILE)
with warnings_helper.check_no_resource_warning(self):
action, elem = next(it)
self.assertEqual((action, elem.tag), ('end', 'element'))
del it, elem
gc_collect()
def test_resource_warnings_failed_iteration(self):
self.addCleanup(os_helper.unlink, TESTFN)
with open(TESTFN, "wb") as f:
f.write(b"<document />junk")
it = ET.iterparse(TESTFN)
action, elem = next(it)
self.assertEqual((action, elem.tag), ('end', 'document'))
with warnings_helper.check_no_resource_warning(self):
with self.assertRaises(ET.ParseError) as cm:
next(it)
self.assertEqual(str(cm.exception),
'junk after document element: line 1, column 12')
del cm, it
gc_collect()
def test_resource_warnings_exhausted(self):
it = ET.iterparse(SIMPLE_XMLFILE)
with warnings_helper.check_no_resource_warning(self):
list(it)
del it
gc_collect()
def test_close_not_exhausted(self):
iterparse = ET.iterparse
it = iterparse(SIMPLE_XMLFILE)
it.close()
with self.assertRaises(StopIteration):
next(it)
it.close() # idempotent
with open(SIMPLE_XMLFILE, 'rb') as source:
it = iterparse(source)
it.close()
self.assertFalse(source.closed)
with self.assertRaises(StopIteration):
next(it)
it.close() # idempotent
it = iterparse(SIMPLE_XMLFILE)
action, elem = next(it)
self.assertEqual((action, elem.tag), ('end', 'element'))
it.close()
with self.assertRaises(StopIteration):
next(it)
it.close() # idempotent
with open(SIMPLE_XMLFILE, 'rb') as source:
it = iterparse(source)
action, elem = next(it)
self.assertEqual((action, elem.tag), ('end', 'element'))
it.close()
self.assertFalse(source.closed)
with self.assertRaises(StopIteration):
next(it)
it.close() # idempotent
def test_close_exhausted(self):
iterparse = ET.iterparse
it = iterparse(SIMPLE_XMLFILE)
list(it)
it.close()
with self.assertRaises(StopIteration):
next(it)
it.close() # idempotent
with open(SIMPLE_XMLFILE, 'rb') as source:
it = iterparse(source)
list(it)
it.close()
self.assertFalse(source.closed)
with self.assertRaises(StopIteration):
next(it)
it.close() # idempotent
class XMLPullParserTest(unittest.TestCase):
@@ -1540,7 +1571,7 @@ class XMLPullParserTest(unittest.TestCase):
self.assertEqual([(action, elem.tag) for action, elem in events],
expected)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_simple_xml(self, chunk_size=None, flush=False):
parser = ET.XMLPullParser()
self.assert_event_tags(parser, [])
@@ -1562,19 +1593,19 @@ class XMLPullParserTest(unittest.TestCase):
self.assert_event_tags(parser, [('end', 'root')])
self.assertIsNone(parser.close())
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_simple_xml_chunk_1(self):
self.test_simple_xml(chunk_size=1, flush=True)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_simple_xml_chunk_5(self):
self.test_simple_xml(chunk_size=5, flush=True)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_simple_xml_chunk_22(self):
self.test_simple_xml(chunk_size=22)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_feed_while_iterating(self):
parser = ET.XMLPullParser()
it = parser.read_events()
@@ -1587,7 +1618,7 @@ class XMLPullParserTest(unittest.TestCase):
with self.assertRaises(StopIteration):
next(it)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_simple_xml_with_ns(self):
parser = ET.XMLPullParser()
self.assert_event_tags(parser, [])
@@ -1609,7 +1640,7 @@ class XMLPullParserTest(unittest.TestCase):
self.assert_event_tags(parser, [('end', '{namespace}root')])
self.assertIsNone(parser.close())
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_ns_events(self):
parser = ET.XMLPullParser(events=('start-ns', 'end-ns'))
self._feed(parser, "<!-- comment -->\n")
@@ -1625,7 +1656,7 @@ class XMLPullParserTest(unittest.TestCase):
self.assertEqual(list(parser.read_events()), [('end-ns', None)])
self.assertIsNone(parser.close())
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_ns_events_start(self):
parser = ET.XMLPullParser(events=('start-ns', 'start', 'end'))
self._feed(parser, "<tag xmlns='abc' xmlns:p='xyz'>\n")
@@ -1649,7 +1680,7 @@ class XMLPullParserTest(unittest.TestCase):
('end', '{abc}tag'),
])
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_ns_events_start_end(self):
parser = ET.XMLPullParser(events=('start-ns', 'start', 'end', 'end-ns'))
self._feed(parser, "<tag xmlns='abc' xmlns:p='xyz'>\n")
@@ -1677,7 +1708,7 @@ class XMLPullParserTest(unittest.TestCase):
('end-ns', None),
])
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_events(self):
parser = ET.XMLPullParser(events=())
self._feed(parser, "<root/>\n")
@@ -1724,7 +1755,7 @@ class XMLPullParserTest(unittest.TestCase):
self._feed(parser, "</root>")
self.assertIsNone(parser.close())
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_events_comment(self):
parser = ET.XMLPullParser(events=('start', 'comment', 'end'))
self._feed(parser, "<!-- text here -->\n")
@@ -1744,7 +1775,7 @@ class XMLPullParserTest(unittest.TestCase):
self._feed(parser, "<!-- text here -->\n")
self.assert_events(parser, [('comment', (ET.Comment, ' text here '))])
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_events_pi(self):
parser = ET.XMLPullParser(events=('start', 'pi', 'end'))
self._feed(parser, "<?pitarget?>\n")
@@ -1775,8 +1806,10 @@ class XMLPullParserTest(unittest.TestCase):
def test_unknown_event(self):
with self.assertRaises(ValueError):
ET.XMLPullParser(events=('start', 'end', 'bogus'))
with self.assertRaisesRegex(ValueError, "unknown event 'bogus'"):
ET.XMLPullParser(events=(x.decode() for x in (b'start', b'end', b'bogus')))
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.skipIf(pyexpat.version_info < (2, 6, 0),
f'Expat {pyexpat.version_info} does not '
'support reparse deferral')
@@ -1801,7 +1834,7 @@ class XMLPullParserTest(unittest.TestCase):
self.assert_event_tags(parser, [('end', 'doc')])
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_flush_reparse_deferral_disabled(self):
parser = ET.XMLPullParser(events=('start', 'end'))
@@ -1984,7 +2017,7 @@ class XIncludeTest(unittest.TestCase):
else:
return None
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_xinclude_default(self):
from xml.etree import ElementInclude
doc = self.xinclude_loader('default.xml')
@@ -1999,7 +2032,7 @@ class XIncludeTest(unittest.TestCase):
'</root>\n'
'</document>')
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_xinclude(self):
from xml.etree import ElementInclude
@@ -2064,7 +2097,7 @@ class XIncludeTest(unittest.TestCase):
' </ns0:include>\n'
'</div>') # C5
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_xinclude_repeated(self):
from xml.etree import ElementInclude
@@ -2072,7 +2105,7 @@ class XIncludeTest(unittest.TestCase):
ElementInclude.include(document, self.xinclude_loader)
self.assertEqual(1+4*2, len(document.findall(".//p")))
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_xinclude_failures(self):
from xml.etree import ElementInclude
@@ -2177,7 +2210,7 @@ class BugsTest(unittest.TestCase):
elem.set("123", 123)
check(elem) # attribute value
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_bug_xmltoolkit25(self):
# typo in ElementTree.findtext
@@ -2201,7 +2234,7 @@ class BugsTest(unittest.TestCase):
ET.dump(tree)
self.assertEqual(stdout.getvalue(), '<doc><table><tbody /></table></doc>\n')
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_bug_xmltoolkit39(self):
# non-ascii element and attribute names doesn't work
@@ -2236,7 +2269,7 @@ class BugsTest(unittest.TestCase):
b'<doc>&#33328;</doc>')
self.assertEqual(serialize(e), '<doc>\u8230</doc>')
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_bug_xmltoolkit55(self):
# make sure we're reporting the first error, not the last
@@ -2255,7 +2288,7 @@ class BugsTest(unittest.TestCase):
self.assertRaises(OSError, ET.parse, ExceptionFile())
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_bug_xmltoolkit62(self):
# Don't crash when using custom entities.
@@ -2288,7 +2321,7 @@ class BugsTest(unittest.TestCase):
xmltoolkit63()
self.assertEqual(sys.getrefcount(None), count)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_bug_200708_newline(self):
# Preserve newlines in attributes.
@@ -2404,7 +2437,7 @@ class BugsTest(unittest.TestCase):
b"<?xml version='1.0' encoding='ascii'?>\n"
b'<body>t&#227;g</body>')
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_issue6565(self):
elem = ET.XML("<body><tag/></body>")
self.assertEqual(summarize_list(elem), ['tag'])
@@ -2450,7 +2483,7 @@ class BugsTest(unittest.TestCase):
self.assertIsInstance(e[0].tail, str)
self.assertEqual(e[0].tail, 'changed')
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_lost_elem(self):
# Issue #25902: Borrowed element can disappear
class Tag:
@@ -2476,7 +2509,7 @@ class BugsTest(unittest.TestCase):
root = ET.XML(xml)
self.assertEqual(root.get('b'), text.decode('utf-8'))
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_expat224_utf8_bug(self):
# bpo-31170: Expat 2.2.3 had a bug in its UTF-8 decoder.
# Check that Expat 2.2.4 fixed the bug.
@@ -2489,7 +2522,7 @@ class BugsTest(unittest.TestCase):
text = b'x' + b'\xc3\xa0' * 1024
self.check_expat224_utf8_bug(text)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_expat224_utf8_bug_file(self):
with open(UTF8_BUG_XMLFILE, 'rb') as fp:
raw = fp.read()
@@ -2639,7 +2672,7 @@ class BasicElementTest(ElementTestCase, unittest.TestCase):
e[:] = [E('bar')]
self.assertRaises(TypeError, copy.deepcopy, e)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_cyclic_gc(self):
class Dummy:
pass
@@ -2977,32 +3010,72 @@ class BadElementTest(ElementTestCase, unittest.TestCase):
elem = b.close()
self.assertEqual(elem[0].tail, 'ABCDEFGHIJKL')
def test_subscr(self):
# Issue #27863
def test_subscr_with_clear(self):
# See https://github.com/python/cpython/issues/143200.
self.do_test_subscr_with_mutating_slice(use_clear_method=True)
def test_subscr_with_delete(self):
# See https://github.com/python/cpython/issues/72050.
self.do_test_subscr_with_mutating_slice(use_clear_method=False)
def do_test_subscr_with_mutating_slice(self, *, use_clear_method):
class X:
def __init__(self, i=0):
self.i = i
def __index__(self):
del e[:]
return 1
if use_clear_method:
e.clear()
else:
del e[:]
return self.i
e = ET.Element('elem')
e.append(ET.Element('child'))
e[:X()] # shouldn't crash
for s in self.get_mutating_slices(X, 10):
with self.subTest(s):
e = ET.Element('elem')
e.extend([ET.Element(f'c{i}') for i in range(10)])
e[s] # shouldn't crash
e.append(ET.Element('child'))
e[0:10:X()] # shouldn't crash
def test_ass_subscr_with_mutating_slice(self):
# See https://github.com/python/cpython/issues/72050
# and https://github.com/python/cpython/issues/143200.
def test_ass_subscr(self):
# Issue #27863
class X:
def __init__(self, i=0):
self.i = i
def __index__(self):
e[:] = []
return 1
return self.i
for s in self.get_mutating_slices(X, 10):
with self.subTest(s):
e = ET.Element('elem')
e.extend([ET.Element(f'c{i}') for i in range(10)])
e[s] = [] # shouldn't crash
def get_mutating_slices(self, index_class, n_children):
self.assertGreaterEqual(n_children, 10)
return [
slice(index_class(), None, None),
slice(index_class(2), None, None),
slice(None, index_class(), None),
slice(None, index_class(2), None),
slice(0, 2, index_class(1)),
slice(0, 2, index_class(2)),
slice(0, n_children, index_class(1)),
slice(0, n_children, index_class(2)),
slice(0, 2 * n_children, index_class(1)),
slice(0, 2 * n_children, index_class(2)),
]
def test_ass_subscr_with_mutating_iterable_value(self):
class V:
def __iter__(self):
e.clear()
return iter([ET.Element('a'), ET.Element('b')])
e = ET.Element('elem')
for _ in range(10):
e.insert(0, ET.Element('child'))
e[0:10:X()] = [] # shouldn't crash
e.extend([ET.Element(f'c{i}') for i in range(10)])
e[:] = V()
def test_treebuilder_start(self):
# Issue #27863
@@ -3235,7 +3308,7 @@ class ElementTreeTypeTest(unittest.TestCase):
class ElementFindTest(unittest.TestCase):
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_find_simple(self):
e = ET.XML(SAMPLE_XML)
self.assertEqual(e.find('tag').tag, 'tag')
@@ -3259,7 +3332,7 @@ class ElementFindTest(unittest.TestCase):
# Issue #16922
self.assertEqual(ET.XML('<tag><empty /></tag>').findtext('empty'), '')
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_find_xpath(self):
LINEAR_XML = '''
<body>
@@ -3282,7 +3355,7 @@ class ElementFindTest(unittest.TestCase):
self.assertRaisesRegex(SyntaxError, 'XPath', e.find, './tag[last()-0]')
self.assertRaisesRegex(SyntaxError, 'XPath', e.find, './tag[last()+1]')
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_findall(self):
e = ET.XML(SAMPLE_XML)
e[2] = ET.XML(SAMPLE_SECTION)
@@ -3471,7 +3544,7 @@ class ElementFindTest(unittest.TestCase):
with self.assertRaisesRegex(SyntaxError, 'cannot use absolute path'):
e.findall('/tag')
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_find_through_ElementTree(self):
e = ET.XML(SAMPLE_XML)
self.assertEqual(ET.ElementTree(e).find('tag').tag, 'tag')
@@ -3674,7 +3747,7 @@ class TreeBuilderTest(unittest.TestCase):
a = parser.close()
self.assertEqual(a.text, "texttail")
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_late_tail_mix_pi_comments(self):
# Issue #37399: The tail of an ignored comment could overwrite the text before it.
# Test appending tails to comments/pis.
@@ -3787,7 +3860,7 @@ class TreeBuilderTest(unittest.TestCase):
pass
self._check_element_factory_class(MyElement)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_doctype(self):
class DoctypeParser:
_doctype = None
@@ -3865,7 +3938,7 @@ class XMLParserTest(unittest.TestCase):
parser.feed(self.sample2)
parser.close()
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_subclass_doctype(self):
_doctype = None
class MyParserWithDoctype(ET.XMLParser):
@@ -3907,7 +3980,7 @@ class XMLParserTest(unittest.TestCase):
parser.feed(self.sample2)
parser.close()
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_parse_string(self):
parser = ET.XMLParser(target=ET.TreeBuilder())
parser.feed(self.sample3)
@@ -4365,13 +4438,13 @@ class ParseErrorTest(unittest.TestCase):
except ET.ParseError as e:
return e
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
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))
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_error_code(self):
import xml.parsers.expat.errors as ERRORS
self.assertEqual(self._get_error('foo').code,
@@ -4431,7 +4504,7 @@ class NoAcceleratorTest(unittest.TestCase):
# --------------------------------------------------------------------
class BoolTest(unittest.TestCase):
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_warning(self):
e = ET.fromstring('<a style="new"></a>')
msg = (
@@ -4461,7 +4534,7 @@ class C14NTest(unittest.TestCase):
#
# simple roundtrip tests (from c14n.py)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_simple_roundtrip(self):
# Basics
self.assertEqual(c14n_roundtrip("<doc/>"), '<doc></doc>')
@@ -4502,7 +4575,7 @@ class C14NTest(unittest.TestCase):
xml = '<X xmlns="http://nps/a"><Y xmlns:b="http://nsp/b" b:targets="abc,xyz"></Y></X>'
self.assertEqual(c14n_roundtrip(xml), xml)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_c14n_exclusion(self):
xml = textwrap.dedent("""\
<root xmlns:x="http://example.com/x">
@@ -4583,7 +4656,7 @@ class C14NTest(unittest.TestCase):
# note that this uses generated C14N versions of the standard ET.write
# output, not roundtripped C14N (see above).
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_xml_c14n2(self):
datadir = findfile("c14n-20", subdir="xmltestdata")
full_path = partial(os.path.join, datadir)

View File

@@ -137,4 +137,4 @@ XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml"
EMPTY_NAMESPACE = None
EMPTY_PREFIX = None
from .domreg import getDOMImplementation, registerDOMImplementation
from .domreg import getDOMImplementation, registerDOMImplementation # noqa: F401

View File

@@ -292,13 +292,6 @@ def _append_child(self, node):
childNodes.append(node)
node.parentNode = self
def _in_document(node):
# return True iff node is part of a document tree
while node is not None:
if node.nodeType == Node.DOCUMENT_NODE:
return True
node = node.parentNode
return False
def _write_data(writer, text, attr):
"Writes datachars to writer."
@@ -371,6 +364,7 @@ class Attr(Node):
def __init__(self, qName, namespaceURI=EMPTY_NAMESPACE, localName=None,
prefix=None):
self.ownerElement = None
self.ownerDocument = None
self._name = qName
self.namespaceURI = namespaceURI
self._prefix = prefix
@@ -696,6 +690,7 @@ class Element(Node):
def __init__(self, tagName, namespaceURI=EMPTY_NAMESPACE, prefix=None,
localName=None):
self.ownerDocument = None
self.parentNode = None
self.tagName = self.nodeName = tagName
self.prefix = prefix
@@ -1555,7 +1550,7 @@ def _clear_id_cache(node):
if node.nodeType == Node.DOCUMENT_NODE:
node._id_cache.clear()
node._id_search_stack = None
elif _in_document(node):
elif node.ownerDocument:
node.ownerDocument._id_cache.clear()
node.ownerDocument._id_search_stack= None

View File

@@ -267,7 +267,11 @@ class Element:
"""
# assert iselement(element)
self._children.remove(subelement)
try:
self._children.remove(subelement)
except ValueError:
# to align the error message with the C implementation
raise ValueError("Element.remove(x): element not found") from None
def find(self, path, namespaces=None):
"""Find first matching element by tag name or path.

View File

@@ -21,9 +21,9 @@ expatreader -- Driver that allows use of the Expat parser with SAX.
from .xmlreader import InputSource
from .handler import ContentHandler, ErrorHandler
from ._exceptions import SAXException, SAXNotRecognizedException, \
SAXParseException, SAXNotSupportedException, \
SAXReaderNotAvailable
from ._exceptions import (SAXException, SAXNotRecognizedException,
SAXParseException, SAXNotSupportedException,
SAXReaderNotAvailable)
def parse(source, handler, errorHandler=ErrorHandler()):
@@ -55,7 +55,7 @@ default_parser_list = ["xml.sax.expatreader"]
# tell modulefinder that importing sax potentially imports expatreader
_false = 0
if _false:
import xml.sax.expatreader
import xml.sax.expatreader # noqa: F401
import os, sys
if not sys.flags.ignore_environment and "PY_SAX_PARSER" in os.environ:
@@ -92,3 +92,9 @@ def make_parser(parser_list=()):
def _create_parser(parser_name):
drv_module = __import__(parser_name,{},{},['create_parser'])
return drv_module.create_parser()
__all__ = ['ContentHandler', 'ErrorHandler', 'InputSource', 'SAXException',
'SAXNotRecognizedException', 'SAXNotSupportedException',
'SAXParseException', 'SAXReaderNotAvailable',
'default_parser_list', 'make_parser', 'parse', 'parseString']

View File

@@ -371,7 +371,7 @@ class LexicalHandler:
name is the name of the document element type, public_id the
public identifier of the DTD (or None if none were supplied)
and system_id the system identfier of the external subset (or
and system_id the system identifier of the external subset (or
None if none were supplied)."""
def endDTD(self):