diff options
Diffstat (limited to 'Lib/doctest.py')
-rw-r--r-- | Lib/doctest.py | 105 |
1 files changed, 62 insertions, 43 deletions
diff --git a/Lib/doctest.py b/Lib/doctest.py index 01f7cb3..2cd96ba 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -469,6 +469,14 @@ class Example: with a newline unless it's empty, in which case it's an empty string. The constructor adds a newline if needed. + - exc_msg: The exception message generated by the example, if + the example is expected to generate an exception; or `None` if + it is not expected to generate an exception. This exception + message is compared against the return value of + `traceback.format_exception_only()`. `exc_msg` ends with a + newline unless it's `None`. The constructor adds a newline + if needed. + - lineno: The line number within the DocTest string containing this Example where the Example begins. This line number is zero-based, with respect to the beginning of the DocTest. @@ -483,12 +491,15 @@ class Example: are left at their default value (as specified by the DocTestRunner's optionflags). By default, no options are set. """ - def __init__(self, source, want, lineno, indent=0, options=None): + def __init__(self, source, want, exc_msg=None, lineno=0, indent=0, + options=None): # Normalize inputs. if not source.endswith('\n'): source += '\n' if want and not want.endswith('\n'): want += '\n' + if exc_msg is not None and not exc_msg.endswith('\n'): + exc_msg += '\n' # Store properties. self.source = source self.want = want @@ -496,6 +507,7 @@ class Example: self.indent = indent if options is None: options = {} self.options = options + self.exc_msg = exc_msg class DocTest: """ @@ -579,6 +591,28 @@ class DocTestParser: )*) ''', re.MULTILINE | re.VERBOSE) + # A regular expression for handling `want` strings that contain + # expected exceptions. It divides `want` into three pieces: + # - the traceback header line (`hdr`) + # - the traceback stack (`stack`) + # - the exception message (`msg`), as generated by + # traceback.format_exception_only() + # `msg` may have multiple lines. We assume/require that the + # exception message is the first non-indented line starting with a word + # character following the traceback header line. + _EXCEPTION_RE = re.compile(r""" + # Grab the traceback header. Different versions of Python have + # said different things on the first traceback line. + ^(?P<hdr> Traceback\ \( + (?: most\ recent\ call\ last + | innermost\ last + ) \) : + ) + \s* $ # toss trailing whitespace on the header. + (?P<stack> .*?) # don't blink: absorb stuff until... + ^ (?P<msg> \w+ .*) # a line *starts* with alphanum. + """, re.VERBOSE | re.MULTILINE | re.DOTALL) + # A callable returning a true value iff its argument is a blank line # or contains a single comment. _IS_BLANK_OR_COMMENT = re.compile(r'^[ ]*(#.*)?$').match @@ -631,13 +665,15 @@ class DocTestParser: # Update lineno (lines before this example) lineno += string.count('\n', charno, m.start()) # Extract source/want from the regexp match. - (source, want) = self._parse_example(m, name, lineno) + (source, want, exc_msg) = self._parse_example(m, name, lineno) # Extract extra options from the source. options = self._find_options(source, name, lineno) # Create an Example, and add it to the list. if not self._IS_BLANK_OR_COMMENT(source): - examples.append( Example(source, want, lineno, - len(m.group('indent')), options) ) + examples.append( Example(source, want, exc_msg, + lineno=lineno, + indent=len(m.group('indent')), + options=options) ) # Update lineno (lines inside this example) lineno += string.count('\n', m.start(), m.end()) # Update charno. @@ -700,7 +736,7 @@ class DocTestParser: lineno += len(lines) # Extract source/want from the regexp match. - (source, want) = self._parse_example(m, name, lineno) + (source, want, exc_msg) = self._parse_example(m, name, lineno) # Display the source output.append(source) # Display the expected output, if any @@ -754,7 +790,14 @@ class DocTestParser: lineno + len(source_lines)) want = '\n'.join([wl[indent:] for wl in want_lines]) - return source, want + # If `want` contains a traceback message, then extract it. + m = self._EXCEPTION_RE.match(want) + if m: + exc_msg = m.group('msg') + else: + exc_msg = None + + return source, want, exc_msg # This regular expression looks for option directives in the # source code of an example. Option directives are comments @@ -1279,28 +1322,6 @@ class DocTestRunner: # DocTest Running #///////////////////////////////////////////////////////////////// - # A regular expression for handling `want` strings that contain - # expected exceptions. It divides `want` into three pieces: - # - the traceback header line (`hdr`) - # - the traceback stack (`stack`) - # - the exception message (`msg`), as generated by - # traceback.format_exception_only() - # `msg` may have multiple lines. We assume/require that the - # exception message is the first non-indented line starting with a word - # character following the traceback header line. - _EXCEPTION_RE = re.compile(r""" - # Grab the traceback header. Different versions of Python have - # said different things on the first traceback line. - ^(?P<hdr> Traceback\ \( - (?: most\ recent\ call\ last - | innermost\ last - ) \) : - ) - \s* $ # toss trailing whitespace on the header. - (?P<stack> .*?) # don't blink: absorb stuff until... - ^ (?P<msg> \w+ .*) # a line *starts* with alphanum. - """, re.VERBOSE | re.MULTILINE | re.DOTALL) - def __run(self, test, compileflags, out): """ Run the examples in `test`. Write the outcome of each example @@ -1365,25 +1386,23 @@ class DocTestRunner: exc_info = sys.exc_info() exc_msg = traceback.format_exception_only(*exc_info[:2])[-1] - # Search the `want` string for an exception. If we don't - # find one, then report an unexpected exception. - m = self._EXCEPTION_RE.match(example.want) - if m is None: + # If `example.exc_msg` is None, then we weren't + # expecting an exception. + if example.exc_msg is None: self.report_unexpected_exception(out, test, example, exc_info) failures += 1 + # If `example.exc_msg` matches the actual exception + # message (`exc_msg`), then the example succeeds. + elif (self._checker.check_output(example.exc_msg, exc_msg, + self.optionflags)): + self.report_success(out, test, example, + got + _exception_traceback(exc_info)) + # Otherwise, the example fails. else: - # The test passes iff the expected exception - # message (`m.group('msg')`) matches the actual - # exception message (`exc_msg`). - if (self._checker.check_output(m.group('msg'), exc_msg, - self.optionflags)): - self.report_success(out, test, example, - got + _exception_traceback(exc_info)) - else: - self.report_failure(out, test, example, - got + _exception_traceback(exc_info)) - failures += 1 + self.report_failure(out, test, example, + got + _exception_traceback(exc_info)) + failures += 1 # Restore the option flags (in case they were modified) self.optionflags = original_optionflags |