diff options
author | Tim Peters <tim.peters@gmail.com> | 2004-08-13 03:55:05 (GMT) |
---|---|---|
committer | Tim Peters <tim.peters@gmail.com> | 2004-08-13 03:55:05 (GMT) |
commit | 41a65ea7fec64be031b79041ebba875bde1155d6 (patch) | |
tree | b44ebe3063263cf2a32ad121d4e008b6bed64692 | |
parent | f076953eb13caf629c81c3656cc0f178c7a91b1d (diff) | |
download | cpython-41a65ea7fec64be031b79041ebba875bde1155d6.zip cpython-41a65ea7fec64be031b79041ebba875bde1155d6.tar.gz cpython-41a65ea7fec64be031b79041ebba875bde1155d6.tar.bz2 |
Doctest has new traceback gimmicks in 2.4. While trying to document
them (which they are now), I had to rewrite the code to understand
it. This has got to be the most DWIM part of doctest -- but in context
is really necessary.
-rw-r--r-- | Doc/lib/libdoctest.tex | 98 | ||||
-rw-r--r-- | Lib/doctest.py | 42 |
2 files changed, 106 insertions, 34 deletions
diff --git a/Doc/lib/libdoctest.tex b/Doc/lib/libdoctest.tex index e5b637a..7558309 100644 --- a/Doc/lib/libdoctest.tex +++ b/Doc/lib/libdoctest.tex @@ -108,7 +108,8 @@ Expecting: [1, 1, 2, 6, 24, 120] ok Trying: [factorial(long(n)) for n in range(6)] Expecting: [1, 1, 2, 6, 24, 120] -ok\end{verbatim} +ok +\end{verbatim} And so on, eventually ending with: @@ -129,12 +130,14 @@ $ \end{verbatim} That's all you need to know to start making productive use of -\module{doctest}! Jump in. +\module{doctest}! Jump in. The following sections provide full +details. Note that there are many examples of doctests in +the standard Python test suite and libraries. \subsection{Simple Usage} -The simplest (not necessarily the best) way to start using doctest is to -end each module \module{M} with: +The simplest way to start using doctest (but not necessarily the way +you'll continue to do it) is to end each module \module{M} with: \begin{verbatim} def _test(): @@ -146,8 +149,7 @@ if __name__ == "__main__": \end{verbatim} \module{doctest} then examines docstrings in the module calling -\function{testmod()}. If you want to test a different module, you can -pass that module object to \function{testmod()}. +\function{testmod()}. Running the module as a script causes the examples in the docstrings to get executed and verified: @@ -292,35 +294,95 @@ their contained methods and nested classes. \subsection{What's the Execution Context?} -By default, each time \function{testmod()} finds a docstring to test, it uses -a \emph{copy} of \module{M}'s globals, so that running tests on a module +By default, each time \function{testmod()} finds a docstring to test, it +uses a \emph{shallow copy} of \module{M}'s globals, so that running tests doesn't change the module's real globals, and so that one test in \module{M} can't leave behind crumbs that accidentally allow another test to work. This means examples can freely use any names defined at top-level in \module{M}, and names defined earlier in the docstring being run. +Examples cannot see names defined in other docstrings. You can force use of your own dict as the execution context by passing -\code{globs=your_dict} to \function{testmod()} instead. Presumably this -would be a copy of \code{M.__dict__} merged with the globals from other -imported modules. +\code{globs=your_dict} to \function{testmod()} instead. \subsection{What About Exceptions?} -No problem, as long as the only output generated by the example is the -traceback itself. For example: +No problem: just paste in the expected traceback. Since +tracebacks contain details that are likely to change +rapidly (for example, exact file paths and line numbers), this is one +case where doctest works hard to be flexible in what it accepts. +This makes the full story involved, but you really don't have +to remember much. Simple example: \begin{verbatim} >>> [1, 2, 3].remove(42) Traceback (most recent call last): File "<stdin>", line 1, in ? ValueError: list.remove(x): x not in list ->>> \end{verbatim} -Note that only the exception type and value are compared (specifically, -only the last line in the traceback). The various ``File'' lines in -between can be left out (unless they add significantly to the documentation -value of the example). +That doctest succeeds if, and only if, \exception{ValueError} is raised, +with the \samp{list.remove(x): x not in list} detail as shown. + +The expected output for an exception is divided into four parts. +First, an example may produce some normal output before an exception +is raised, although that's unusual. The "normal output" is taken to +be everything until the first "Traceback" line, and is usually an +empty string. Next, the traceback line must be one of these two, and +indented the same as the first line in the example: + +\begin{verbatim} +Traceback (most recent call last): +Traceback (innermost last): +\end{verbatim} + +The most interesting part is the last part: the line(s) starting with the +exception type and detail. This is usually the last line of a traceback, +but can extend across any number of lines. After the "Traceback" line, +doctest simply ignores everything until the first line indented the same as +the first line of the example, \emph{and} starting with an alphanumeric +character. This example illustrates the complexities that are possible: + +\begin{verbatim} +>>> print 1, 2; raise ValueError('printed 1\nand 2\n but not 3') +1 2 +Traceback (most recent call last): +... indented the same, but doesn't start with an alphanumeric + not indented the same, so ignored too + File "/Python23/lib/doctest.py", line 442, in _run_examples_inner + compileflags, 1) in globs + File "<string>", line 1, in ? # and all these are ignored +ValueError: printed 1 +and 2 + but not 3 +\end{verbatim} + +The first (\samp{1 2}) and last three (starting with +\exception{ValueError}) lines are compared, and the rest are ignored. + +Best practice is to omit the ``File'' lines, unless they add +significant documentation value to the example. So the example above +is probably better as: + +\begin{verbatim} +>>> print 1, 2; raise ValueError('printed 1\nand 2\n but not 3') +1 2 +Traceback (most recent call last): + ... +ValueError: printed 1 +and 2 + but not 3 +\end{verbatim} + +Note the tracebacks are treated very specially. In particular, in the +rewritten example, the use of \samp{...} is independent of doctest's +\constant{ELLIPSIS} option. The ellipsis in that example could +be left out, or could just as well be three (or three hundred) commas. + +\versionchanged[The abilities to check both normal output and an + exception in a single example, and to have a multi-line + exception detail, were added]{2.4} + \subsection{Option Flags and Directive Names\label{doctest-options}} diff --git a/Lib/doctest.py b/Lib/doctest.py index fe23064..aa523a6 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -1191,16 +1191,27 @@ class DocTestRunner: #///////////////////////////////////////////////////////////////// # A regular expression for handling `want` strings that contain - # expected exceptions. It divides `want` into two pieces: the - # pre-exception output (`out`) and the exception message (`exc`), - # as generated by traceback.format_exception_only(). (I assume - # that the exception_only message is the first non-indented line - # starting with word characters after the "Traceback ...".) - _EXCEPTION_RE = re.compile(('^(?P<out>.*)' - '^(?P<hdr>Traceback \((?:%s|%s)\):)\s*$.*?' - '^(?P<exc>\w+.*)') % - ('most recent call last', 'innermost last'), - re.MULTILINE | re.DOTALL) + # expected exceptions. It divides `want` into three pieces: + # - the pre-exception output (`want`) + # - the traceback header line (`hdr`) + # - 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""" + (?P<want> .*?) # suck up everything until traceback header + # 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 traceback header + .*? # don't blink: absorb stuff until a line *starts* with \w + ^ (?P<msg> \w+ .*) + """, re.VERBOSE | re.MULTILINE | re.DOTALL) def __run(self, test, compileflags, out): """ @@ -1274,20 +1285,19 @@ class DocTestRunner: exc_info) failures += 1 else: - exc_hdr = m.group('hdr')+'\n' # Exception header + e_want, e_msg = m.group('want', 'msg') # The test passes iff the pre-exception output and # the exception description match the values given # in `want`. - if (self._checker.check_output(m.group('out'), got, + if (self._checker.check_output(e_want, got, self.optionflags) and - self._checker.check_output(m.group('exc'), exc_msg, + self._checker.check_output(e_msg, exc_msg, self.optionflags)): - # Is +exc_msg the right thing here?? self.report_success(out, test, example, - got+_exception_traceback(exc_info)) + got + _exception_traceback(exc_info)) else: self.report_failure(out, test, example, - got+_exception_traceback(exc_info)) + got + _exception_traceback(exc_info)) failures += 1 # Restore the option flags (in case they were modified) |