Code

Ticket #7441: doctest_xml_and_json_checkers.patch

File doctest_xml_and_json_checkers.patch, 5.7 KB (added by leosoto, 6 years ago)
  • django/test/testcases.py

    diff -r 70fcef3980a0 django/test/testcases.py
    a b import re 
    11import re 
    22import unittest 
    33from urlparse import urlsplit, urlunsplit 
     4from xml.dom.minidom import parseString, Node 
    45 
    56from django.http import QueryDict 
    67from django.db import transaction 
    from django.core.management import call_ 
    89from django.core.management import call_command 
    910from django.test import _doctest as doctest 
    1011from django.test.client import Client 
     12from django.utils import simplejson 
    1113 
    1214normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s) 
    1315 
    def to_list(value): 
    2527 
    2628class OutputChecker(doctest.OutputChecker): 
    2729    def check_output(self, want, got, optionflags): 
    28         ok = doctest.OutputChecker.check_output(self, want, got, optionflags) 
     30        checks = (self.check_output_default, 
     31                  self.check_output_long, 
     32                  self.check_output_xml, 
     33                  self.check_output_json) 
     34        for check in checks: 
     35            if check(want, got, optionflags): 
     36                return True 
     37        return False 
    2938 
     39    def check_output_default(self, want, got, optionflags): 
     40        return doctest.OutputChecker.check_output(self, want, got, optionflags) 
     41 
     42    def check_output_long(self, want, got, optionflags): 
    3043        # Doctest does an exact string comparison of output, which means long 
    3144        # integers aren't equal to normal integers ("22L" vs. "22"). The 
    3245        # following code normalizes long integers so that they equal normal 
    3346        # integers. 
    34         if not ok: 
    35             return normalize_long_ints(want) == normalize_long_ints(got) 
    36         return ok 
     47        return normalize_long_ints(want) == normalize_long_ints(got) 
     48 
     49    def check_output_xml(self, want, got, optionsflags): 
     50        # Tries to do a 'xml-comparision' of want and got.  Plan string 
     51        # comparision doesn't always work, because, for example, attribute 
     52        # ordering should not be important. 
     53        # 
     54        # Based on http://codespeak.net/svn/lxml/trunk/src/lxml/doctestcompare.py 
     55 
     56        # We use this to distinguish repr()s from elements: 
     57        _repr_re = re.compile(r'^<[^>]+ (at|object) ') 
     58 
     59        _norm_whitespace_re = re.compile(r'[ \t\n][ \t\n]+') 
     60        def norm_whitespace(v): 
     61            return _norm_whitespace_re.sub(' ', v) 
     62 
     63        def looks_like_markup(s): 
     64            s = s.strip() 
     65            return (s.startswith('<') 
     66                    and not _repr_re.search(s)) 
     67 
     68        def child_text(element): 
     69            return ''.join([c.data for c in element.childNodes 
     70                            if c.nodeType == Node.TEXT_NODE]) 
     71 
     72        def children(element): 
     73            return [c for c in element.childNodes 
     74                    if c.nodeType == Node.ELEMENT_NODE] 
     75 
     76        def norm_child_text(element): 
     77            return norm_whitespace(child_text(element)) 
     78 
     79        def attrs_dict(element): 
     80            return dict(element.attributes.items()) 
     81 
     82        def check_element(want_element, got_element): 
     83            if want_element.tagName != got_element.tagName: 
     84                return False 
     85            if norm_child_text(want_element) != norm_child_text(got_element): 
     86                return False 
     87            if attrs_dict(want_element) != attrs_dict(got_element): 
     88                return False 
     89            want_children = children(want_element) 
     90            got_children = children(got_element) 
     91            if len(want_children) != len(got_children): 
     92                return False 
     93            for want, got in zip(want_children, got_children): 
     94                if not check_element(want, got): 
     95                    return False 
     96            return True 
     97 
     98        want, got = self._strip_quotes(want, got) 
     99        if not looks_like_markup(want): 
     100            return False 
     101 
     102        # Wrapper to support XML fragments 
     103        wrapper = u"<root>%s</root>" 
     104        try: 
     105            want_root = parseString(wrapper % want).firstChild 
     106            got_root = parseString(wrapper % got).firstChild 
     107        except: 
     108            return False 
     109        return check_element(want_root, got_root) 
     110 
     111    def check_output_json(self, want, got, optionsflags): 
     112        # Tries do compare want and got as if they were JSON-encoded data 
     113        want, got = self._strip_quotes(want, got) 
     114        try: 
     115            want_json = simplejson.loads(want) 
     116            got_json = simplejson.loads(got) 
     117        except: 
     118            return False 
     119        return  want_json == got_json 
     120 
     121    def _strip_quotes(self, want, got): 
     122        """ 
     123        Strip quotes of doctests output values: 
     124 
     125        >>> o = OutputChecker() 
     126        >>> o._strip_quotes("'foo'") 
     127        "foo" 
     128        >>> o._strip_quotes('"foo"') 
     129        "foo" 
     130        >>> o._strip_quotes("u'foo'") 
     131        "foo" 
     132        >>> o._strip_quotes('u"foo"') 
     133        "foo" 
     134        """ 
     135        def is_quoted_string(s): 
     136            s = s.strip() 
     137            return (len(s) >= 2 
     138                    and s[0] == s[-1] 
     139                    and s[0] in ('"', "'")) 
     140 
     141        def is_quoted_unicode(s): 
     142            s = s.strip() 
     143            return (len(s) >= 3 
     144                    and s[0] == 'u' 
     145                    and s[1] == s[-1] 
     146                    and s[1] in ('"', "'")) 
     147 
     148        if is_quoted_string(want) and is_quoted_string(got): 
     149            want = want.strip()[1:-1] 
     150            got = got.strip()[1:-1] 
     151        elif is_quoted_unicode(want) and is_quoted_unicode(got): 
     152            want = want.strip()[2:-1] 
     153            got = got.strip()[2:-1] 
     154        return want, got 
     155 
    37156 
    38157class DocTestRunner(doctest.DocTestRunner): 
    39158    def __init__(self, *args, **kwargs):