Unverified Commit 4aef915c authored by Ian Briggs's avatar Ian Briggs Committed by GitHub
Browse files

Merge pull request #11 from utah-cs4962-fa21/better-doctest

Make doctest output more helpful via black magic
parents 84f2148c 3a8cce90
......@@ -6,6 +6,7 @@ import doctest
import os
import sys
import re
import io
......@@ -91,12 +92,106 @@ CURRENT_TESTS["all"] = all_tests
# Below this are a variety of "fixes" to doctest that make it more user-friendly
old_truncate = doctest._SpoofOut.truncate
def patched_truncate(self, size=None):
Patch the fake doctest stdout to save the output long enough to use when reporting errors
self._old_getvalue = self.getvalue()
old_truncate(self, size)
def patched_report_unexpected_exception(self, out, test, example, exc_info):
Patch the doctest printer to print output when exceptions occur.
Note that this uses the _old_getvalue saved above, which doctest otherwise throws out
when exceptions are thrown.
got = self._fakeout._old_getvalue
out(self._failure_header(test, example) +
self._checker.output_difference(example, got, self.optionflags) +
'Exception Raised:\n' + doctest._indent(doctest._exception_traceback(exc_info)))
old_parse = doctest.DocTestParser.parse
def patched_parse(self, string, name="<string>"):
Save the text introducing each example to the parser object, so
that we can use it in the _failure_header.
output = old_parse(self, string, name)
self._parsed = output
return output
old_get_doctest = doctest.DocTestParser.get_doctest
def patched_get_doctest(self, string, globs, name, filename, lineno):
Copy the text introducing each example from the parser object to
the doctest object, so that we can use it in the _failure_header.
out = old_get_doctest(self, string, globs, name, filename, lineno)
out._parsed = self._parsed
return out
def patched_failure_header(self, test, example):
Print the full block being executed, including the text
introducing the block and going up to the failing example, any
time a failure occurs.
To do so, we make use of the _parsed field on the doctest object,
which saves the complete parsed doctest file. In it, we find the
example that failed, and walk backward until we find a non-empty
piece of explanatory text. That starts a block, and we output it
in faux-markdown style until we get to the example that failed.
out = [self.DIVIDER]
if test.filename:
if test.lineno is not None and example.lineno is not None:
lineno = test.lineno + example.lineno + 1
lineno = '?'
out.append('File "%s", line %s, in %s' %
(test.filename, lineno, test.name))
out.append('Line %s, in %s' % (example.lineno+1, test.name))
example_idx = test._parsed.index(example)
header_idx = example_idx
while header_idx > 0 \
and (isinstance(test._parsed[header_idx], doctest.Example)
or test._parsed[header_idx] == ''):
header_idx -= 1
s = ""
for i in range(header_idx, example_idx + 1):
x = test._parsed[i]
if isinstance(x, str):
s += x
s += ">>> " + x.source
if x.want:
s += x.want
return '\n'.join(out) + "\n"
def patch_doctest():
doctest.DocTestRunner.report_unexpected_exception = patched_report_unexpected_exception
doctest.DocTestRunner._failure_header = patched_failure_header
doctest._SpoofOut.truncate = patched_truncate
doctest.DocTestParser.parse = patched_parse
doctest.DocTestParser.get_doctest = patched_get_doctest
def run_doctests(files):
mapped_results = dict()
for fname in files:
mapped_results[fname] = doctest.testfile(fname,
mapped_results[fname] = doctest.testfile(
fname, optionflags=doctest.ELLIPSIS | doctest.REPORT_ONLY_FIRST_FAILURE)
return mapped_results
def parse_arguments(argv):
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment