|
| 1 | +import gc |
| 2 | +import os |
| 3 | +import resource |
| 4 | +import sys |
| 5 | + |
| 6 | +from lxml import etree |
| 7 | +import xmlsec |
| 8 | + |
| 9 | +import unittest |
| 10 | + |
| 11 | +etype = type(etree.Element("test")) |
| 12 | + |
| 13 | +ns = {'dsig': xmlsec.constants.DSigNs, 'enc': xmlsec.constants.EncNs} |
| 14 | + |
| 15 | + |
| 16 | +def safe_int(s): |
| 17 | + try: |
| 18 | + return int(s) |
| 19 | + except ValueError: |
| 20 | + return 0 |
| 21 | + |
| 22 | + |
| 23 | +class TestMemoryLeaks(unittest.TestCase): |
| 24 | + maxDiff = None |
| 25 | + |
| 26 | + iterations = safe_int(os.getenv("PYXMLSEC_TEST_ITERATIONS", "10")) |
| 27 | + |
| 28 | + data_dir = os.path.join(os.path.dirname(__file__), "data") |
| 29 | + |
| 30 | + def setUp(self): |
| 31 | + gc.disable() |
| 32 | + self.addTypeEqualityFunc(etype, "assertXmlEqual") |
| 33 | + xmlsec.enable_debug_trace(1) |
| 34 | + |
| 35 | + def run(self, result=None): |
| 36 | + # run first time |
| 37 | + super(TestMemoryLeaks, self).run(result=result) |
| 38 | + if self.iterations == 0: |
| 39 | + return |
| 40 | + |
| 41 | + m_usage = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss |
| 42 | + o_count = gc.get_count()[0] |
| 43 | + m_hits = 0 |
| 44 | + o_hits = 0 |
| 45 | + for i in range(self.iterations): |
| 46 | + super(TestMemoryLeaks, self).run(result=result) |
| 47 | + m_usage_n = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss |
| 48 | + if m_usage_n > m_usage: |
| 49 | + m_usage = m_usage_n |
| 50 | + m_hits += 1 |
| 51 | + o_count_n = gc.get_count()[0] |
| 52 | + if o_count_n > o_count: |
| 53 | + o_count = o_count_n |
| 54 | + o_hits += 1 |
| 55 | + del m_usage_n |
| 56 | + del o_count_n |
| 57 | + |
| 58 | + if m_hits > int(self.iterations * 0.8): |
| 59 | + result.buffer = False |
| 60 | + try: |
| 61 | + raise AssertionError("memory leak detected") |
| 62 | + except AssertionError: |
| 63 | + result.addError(self, sys.exc_info()) |
| 64 | + if o_hits > int(self.iterations * 0.8): |
| 65 | + result.buffer = False |
| 66 | + try: |
| 67 | + raise AssertionError("unreferenced objects detected") |
| 68 | + except AssertionError: |
| 69 | + result.addError(self, sys.exc_info()) |
| 70 | + |
| 71 | + def path(self, name): |
| 72 | + """returns full path for resource""" |
| 73 | + return os.path.join(self.data_dir, name) |
| 74 | + |
| 75 | + def load(self, name): |
| 76 | + """loads resource by name""" |
| 77 | + with open(self.path(name), "rb") as stream: |
| 78 | + return stream.read() |
| 79 | + |
| 80 | + def load_xml(self, name, xpath=None): |
| 81 | + """returns xml.etree""" |
| 82 | + root = etree.parse(self.path(name)).getroot() |
| 83 | + if xpath is None: |
| 84 | + return root |
| 85 | + return root.find(xpath) |
| 86 | + |
| 87 | + def dump(self, root): |
| 88 | + print(etree.tostring(root)) |
| 89 | + |
| 90 | + def assertXmlEqual(self, first, second, msg=None): |
| 91 | + """Checks equality of etree.roots""" |
| 92 | + msg = msg or '' |
| 93 | + if first.tag != second.tag: |
| 94 | + self.fail('Tags do not match: %s and %s. %s' % (first.tag, second.tag, msg)) |
| 95 | + for name, value in first.attrib.items(): |
| 96 | + if second.attrib.get(name) != value: |
| 97 | + self.fail( |
| 98 | + 'Attributes do not match: %s=%r, %s=%r. %s' % (name, value, name, second.attrib.get(name), msg) |
| 99 | + ) |
| 100 | + for name in second.attrib.keys(): |
| 101 | + if name not in first.attrib: |
| 102 | + self.fail('x2 has an attribute x1 is missing: %s. %s' % (name, msg)) |
| 103 | + if not xml_text_compare(first.text, second.text): |
| 104 | + self.fail('text: %r != %r. %s' % (first.text, second.text, msg)) |
| 105 | + if not xml_text_compare(first.tail, second.tail): |
| 106 | + self.fail('tail: %r != %r. %s' % (first.tail, second.tail, msg)) |
| 107 | + cl1 = first.getchildren() |
| 108 | + cl2 = second.getchildren() |
| 109 | + if len(cl1) != len(cl2): |
| 110 | + self.fail('children length differs, %i != %i. %s' % (len(cl1), len(cl2), msg)) |
| 111 | + i = 0 |
| 112 | + for c1, c2 in zip(cl1, cl2): |
| 113 | + i += 1 |
| 114 | + self.assertXmlEqual(c1, c2) |
| 115 | + |
| 116 | + |
| 117 | +def xml_text_compare(t1, t2): |
| 118 | + if not t1 and not t2: |
| 119 | + return True |
| 120 | + if t1 == '*' or t2 == '*': |
| 121 | + return True |
| 122 | + return (t1 or '').strip() == (t2 or '').strip() |
0 commit comments