diff --git a/Lib/doctest.py b/Lib/doctest.py index 0b485f1f74e1910e588cd693960938f4935b5541..3f0d9d9ca5ad923f23b4c86887f1a40c44f4ab98 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -314,6 +314,32 @@ def _comment_line(line): else: return '#' +def _strip_exception_details(msg): + # Support for IGNORE_EXCEPTION_DETAIL. + # Get rid of everything except the exception name; in particular, drop + # the possibly dotted module path (if any) and the exception message (if + # any). We assume that a colon is never part of a dotted name, or of an + # exception name. + # E.g., given + # "foo.bar.MyError: la di da" + # return "MyError" + # Or for "abc.def" or "abc.def:\n" return "def". + + start, end = 0, len(msg) + # The exception name must appear on the first line. + i = msg.find("\n") + if i >= 0: + end = i + # retain up to the first colon (if any) + i = msg.find(':', 0, end) + if i >= 0: + end = i + # retain just the exception name + i = msg.rfind('.', 0, end) + if i >= 0: + start = i+1 + return msg[start: end] + class _OutputRedirectingPdb(pdb.Pdb): """ A specialized version of the python debugger that redirects stdout @@ -1320,10 +1346,9 @@ class DocTestRunner: # Another chance if they didn't care about the detail. elif self.optionflags & IGNORE_EXCEPTION_DETAIL: - m1 = re.match(r'(?:[^:]*\.)?([^:]*:)', example.exc_msg) - m2 = re.match(r'(?:[^:]*\.)?([^:]*:)', exc_msg) - if m1 and m2 and check(m1.group(1), m2.group(1), - self.optionflags): + if check(_strip_exception_details(example.exc_msg), + _strip_exception_details(exc_msg), + self.optionflags): outcome = SUCCESS # Report the outcome. diff --git a/Lib/test/test_doctest.py b/Lib/test/test_doctest.py index 8f8c7c7e82b11ba642e0fe2208df0d1f0dd6ceeb..86259c3c56eca6f31b5af0581eacfb39ad6e4bd9 100644 --- a/Lib/test/test_doctest.py +++ b/Lib/test/test_doctest.py @@ -1020,6 +1020,33 @@ But IGNORE_EXCEPTION_DETAIL does not allow a mismatch in the exception type: ValueError: message TestResults(failed=1, attempted=1) +If the exception does not have a message, you can still use +IGNORE_EXCEPTION_DETAIL to normalize the modules between Python 2 and 3: + + >>> def f(x): + ... r''' + ... >>> from http.client import HTTPException + ... >>> raise HTTPException() #doctest: +IGNORE_EXCEPTION_DETAIL + ... Traceback (most recent call last): + ... foo.bar.HTTPException + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + TestResults(failed=0, attempted=2) + +Note that a trailing colon doesn't matter either: + + >>> def f(x): + ... r''' + ... >>> from http.client import HTTPException + ... >>> raise HTTPException() #doctest: +IGNORE_EXCEPTION_DETAIL + ... Traceback (most recent call last): + ... foo.bar.HTTPException: + ... ''' + >>> test = doctest.DocTestFinder().find(f)[0] + >>> doctest.DocTestRunner(verbose=False).run(test) + TestResults(failed=0, attempted=2) + If an exception is raised but not expected, then it is reported as an unexpected exception: diff --git a/Misc/NEWS b/Misc/NEWS index 1af0788bdf9e8c66e04a7fc65ba22996395ff6f5..b2f18addf48e46d7304a4a2fe882885b2e84f692 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -18,6 +18,10 @@ Core and Builtins Library ------- +- Issue #19138: doctest's IGNORE_EXCEPTION_DETAIL now allows a match when + no exception detail exists (no colon following the exception's name, or + a colon does follow but no text follows the colon). + - Issue #19834: Support unpickling of exceptions pickled by Python 2. - Issue #15798: Fixed subprocess.Popen() to no longer fail if file