| 30 | | ok = doctest.OutputChecker.check_output(self, want, got, optionflags) |
|---|
| 31 | | |
|---|
| 32 | | # Doctest does an exact string comparison of output, which means long |
|---|
| 33 | | # integers aren't equal to normal integers ("22L" vs. "22"). The |
|---|
| 34 | | # following code normalizes long integers so that they equal normal |
|---|
| 35 | | # integers. |
|---|
| 36 | | if not ok: |
|---|
| 37 | | return normalize_long_ints(want) == normalize_long_ints(got) |
|---|
| 38 | | return ok |
|---|
| | 32 | "The entry method for doctest output checking. Defers to a sequence of child checkers" |
|---|
| | 33 | checks = (self.check_output_default, |
|---|
| | 34 | self.check_output_long, |
|---|
| | 35 | self.check_output_xml, |
|---|
| | 36 | self.check_output_json) |
|---|
| | 37 | for check in checks: |
|---|
| | 38 | if check(want, got, optionflags): |
|---|
| | 39 | return True |
|---|
| | 40 | return False |
|---|
| | 41 | |
|---|
| | 42 | def check_output_default(self, want, got, optionflags): |
|---|
| | 43 | "The default comparator provided by doctest - not perfect, but good for most purposes" |
|---|
| | 44 | return doctest.OutputChecker.check_output(self, want, got, optionflags) |
|---|
| | 45 | |
|---|
| | 46 | def check_output_long(self, want, got, optionflags): |
|---|
| | 47 | """Doctest does an exact string comparison of output, which means long |
|---|
| | 48 | integers aren't equal to normal integers ("22L" vs. "22"). The |
|---|
| | 49 | following code normalizes long integers so that they equal normal |
|---|
| | 50 | integers. |
|---|
| | 51 | """ |
|---|
| | 52 | return normalize_long_ints(want) == normalize_long_ints(got) |
|---|
| | 53 | |
|---|
| | 54 | def check_output_xml(self, want, got, optionsflags): |
|---|
| | 55 | """Tries to do a 'xml-comparision' of want and got. Plain string |
|---|
| | 56 | comparision doesn't always work because, for example, attribute |
|---|
| | 57 | ordering should not be important. |
|---|
| | 58 | |
|---|
| | 59 | Based on http://codespeak.net/svn/lxml/trunk/src/lxml/doctestcompare.py |
|---|
| | 60 | """ |
|---|
| | 61 | _norm_whitespace_re = re.compile(r'[ \t\n][ \t\n]+') |
|---|
| | 62 | def norm_whitespace(v): |
|---|
| | 63 | return _norm_whitespace_re.sub(' ', v) |
|---|
| | 64 | |
|---|
| | 65 | def child_text(element): |
|---|
| | 66 | return ''.join([c.data for c in element.childNodes |
|---|
| | 67 | if c.nodeType == Node.TEXT_NODE]) |
|---|
| | 68 | |
|---|
| | 69 | def children(element): |
|---|
| | 70 | return [c for c in element.childNodes |
|---|
| | 71 | if c.nodeType == Node.ELEMENT_NODE] |
|---|
| | 72 | |
|---|
| | 73 | def norm_child_text(element): |
|---|
| | 74 | return norm_whitespace(child_text(element)) |
|---|
| | 75 | |
|---|
| | 76 | def attrs_dict(element): |
|---|
| | 77 | return dict(element.attributes.items()) |
|---|
| | 78 | |
|---|
| | 79 | def check_element(want_element, got_element): |
|---|
| | 80 | if want_element.tagName != got_element.tagName: |
|---|
| | 81 | return False |
|---|
| | 82 | if norm_child_text(want_element) != norm_child_text(got_element): |
|---|
| | 83 | return False |
|---|
| | 84 | if attrs_dict(want_element) != attrs_dict(got_element): |
|---|
| | 85 | return False |
|---|
| | 86 | want_children = children(want_element) |
|---|
| | 87 | got_children = children(got_element) |
|---|
| | 88 | if len(want_children) != len(got_children): |
|---|
| | 89 | return False |
|---|
| | 90 | for want, got in zip(want_children, got_children): |
|---|
| | 91 | if not check_element(want, got): |
|---|
| | 92 | return False |
|---|
| | 93 | return True |
|---|
| | 94 | |
|---|
| | 95 | want, got = self._strip_quotes(want, got) |
|---|
| | 96 | want = want.replace('\\n','\n') |
|---|
| | 97 | got = got.replace('\\n','\n') |
|---|
| | 98 | |
|---|
| | 99 | # If the string is not a complete xml document, we may need to add a |
|---|
| | 100 | # root element. This allow us to compare fragments, like "<foo/><bar/>" |
|---|
| | 101 | if not want.startswith('<?xml'): |
|---|
| | 102 | wrapper = '<root>%s</root>' |
|---|
| | 103 | want = wrapper % want |
|---|
| | 104 | got = wrapper % got |
|---|
| | 105 | |
|---|
| | 106 | # Parse the want and got strings, and compare the parsings. |
|---|
| | 107 | try: |
|---|
| | 108 | want_root = parseString(want).firstChild |
|---|
| | 109 | got_root = parseString(got).firstChild |
|---|
| | 110 | except: |
|---|
| | 111 | return False |
|---|
| | 112 | return check_element(want_root, got_root) |
|---|
| | 113 | |
|---|
| | 114 | def check_output_json(self, want, got, optionsflags): |
|---|
| | 115 | "Tries to compare want and got as if they were JSON-encoded data" |
|---|
| | 116 | want, got = self._strip_quotes(want, got) |
|---|
| | 117 | try: |
|---|
| | 118 | want_json = simplejson.loads(want) |
|---|
| | 119 | got_json = simplejson.loads(got) |
|---|
| | 120 | except: |
|---|
| | 121 | return False |
|---|
| | 122 | return want_json == got_json |
|---|
| | 123 | |
|---|
| | 124 | def _strip_quotes(self, want, got): |
|---|
| | 125 | """ |
|---|
| | 126 | Strip quotes of doctests output values: |
|---|
| | 127 | |
|---|
| | 128 | >>> o = OutputChecker() |
|---|
| | 129 | >>> o._strip_quotes("'foo'") |
|---|
| | 130 | "foo" |
|---|
| | 131 | >>> o._strip_quotes('"foo"') |
|---|
| | 132 | "foo" |
|---|
| | 133 | >>> o._strip_quotes("u'foo'") |
|---|
| | 134 | "foo" |
|---|
| | 135 | >>> o._strip_quotes('u"foo"') |
|---|
| | 136 | "foo" |
|---|
| | 137 | """ |
|---|
| | 138 | def is_quoted_string(s): |
|---|
| | 139 | s = s.strip() |
|---|
| | 140 | return (len(s) >= 2 |
|---|
| | 141 | and s[0] == s[-1] |
|---|
| | 142 | and s[0] in ('"', "'")) |
|---|
| | 143 | |
|---|
| | 144 | def is_quoted_unicode(s): |
|---|
| | 145 | s = s.strip() |
|---|
| | 146 | return (len(s) >= 3 |
|---|
| | 147 | and s[0] == 'u' |
|---|
| | 148 | and s[1] == s[-1] |
|---|
| | 149 | and s[1] in ('"', "'")) |
|---|
| | 150 | |
|---|
| | 151 | if is_quoted_string(want) and is_quoted_string(got): |
|---|
| | 152 | want = want.strip()[1:-1] |
|---|
| | 153 | got = got.strip()[1:-1] |
|---|
| | 154 | elif is_quoted_unicode(want) and is_quoted_unicode(got): |
|---|
| | 155 | want = want.strip()[2:-1] |
|---|
| | 156 | got = got.strip()[2:-1] |
|---|
| | 157 | return want, got |
|---|
| | 158 | |
|---|