From 86b0fb23e5efa3e4bdc351d839fec353d82bb588 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Tue, 9 Oct 2012 13:17:49 -0700 Subject: [PATCH 1/7] Issue #16170: Remove Linux skip from test_subprocess's test_executable test. --- Lib/test/test_subprocess.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 07e2b4b688d..8f5a58c68b9 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -200,13 +200,16 @@ class ProcessTestCase(BaseTestCase): p.wait() self.assertEqual(47, p.returncode) - # TODO: make this test work on Linux. - # This may be failing on Linux because of issue #7774. - @unittest.skipIf(sys.platform not in ('win32', 'darwin'), - "possible bug using executable argument on Linux") def test_executable(self): # Check that the executable argument works. - self._assert_python(["doesnotexist", "-c"], executable=sys.executable) + # + # On Unix (non-Mac and non-Windows), Python looks at args[0] to + # determine where its standard library is, so we need the directory + # of args[0] to be valid for the Popen() call to Python to succeed. + # See also issue #16170 and issue #7774. + doesnotexist = os.path.join(os.path.dirname(sys.executable), + "doesnotexist") + self._assert_python([doesnotexist, "-c"], executable=sys.executable) def test_executable_takes_precedence(self): # Check that the executable argument takes precedence over args[0]. From 5591b02a4c96c4b530ee024e6b1581f5ba72945d Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Wed, 10 Oct 2012 03:34:47 -0700 Subject: [PATCH 2/7] Fixes Issue #16114: The subprocess module no longer provides a misleading error message stating that args[0] did not exist when either the cwd or executable keyword arguments specified a path that did not exist. It now keeps track of if the child got as far as preexec and reports it if not back to the parent via a special "noexec" error message value in the error pipe so that the cwd can be blamed for a failed chdir instead of the exec of the executable being blamed instead. The executable is also always reported accurately when exec fails. Unittests enhanced to cover these cases. --- Lib/subprocess.py | 14 ++++++++++- Lib/test/test_subprocess.py | 47 +++++++++++++++++++++++++++++++------ Misc/NEWS | 4 ++++ Modules/_posixsubprocess.c | 7 +++++- 4 files changed, 63 insertions(+), 9 deletions(-) diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 93262df7ab4..83c79ef6245 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -1169,6 +1169,7 @@ class Popen(object): if executable is None: executable = args[0] + orig_executable = executable # For transferring possible exec failure from child to parent. # Data format: "exception name:hex errno:description" @@ -1224,6 +1225,7 @@ class Popen(object): self._child_created = True if self.pid == 0: # Child + reached_preexec = False try: # Close parent's pipe ends if p2cwrite != -1: @@ -1288,6 +1290,7 @@ class Popen(object): if start_new_session and hasattr(os, 'setsid'): os.setsid() + reached_preexec = True if preexec_fn: preexec_fn() @@ -1303,6 +1306,8 @@ class Popen(object): errno_num = exc_value.errno else: errno_num = 0 + if not reached_preexec: + exc_value = "noexec" message = '%s:%x:%s' % (exc_type.__name__, errno_num, exc_value) message = message.encode(errors="surrogatepass") @@ -1364,10 +1369,17 @@ class Popen(object): err_msg = err_msg.decode(errors="surrogatepass") if issubclass(child_exception_type, OSError) and hex_errno: errno_num = int(hex_errno, 16) + child_exec_never_called = (err_msg == "noexec") + if child_exec_never_called: + err_msg = "" if errno_num != 0: err_msg = os.strerror(errno_num) if errno_num == errno.ENOENT: - err_msg += ': ' + repr(args[0]) + if child_exec_never_called: + # The error must be from chdir(cwd). + err_msg += ': ' + repr(cwd) + else: + err_msg += ': ' + repr(orig_executable) raise child_exception_type(errno_num, err_msg) raise child_exception_type(err_msg) diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 28796411106..15fb498d246 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -884,24 +884,30 @@ class _SuppressCoreFiles(object): @unittest.skipIf(mswindows, "POSIX specific tests") class POSIXProcessTestCase(BaseTestCase): - def test_exceptions(self): - nonexistent_dir = "/_this/pa.th/does/not/exist" + def setUp(self): + super().setUp() + self._nonexistent_dir = "/_this/pa.th/does/not/exist" + + def _get_chdir_exception(self): try: - os.chdir(nonexistent_dir) + os.chdir(self._nonexistent_dir) except OSError as e: # This avoids hard coding the errno value or the OS perror() # string and instead capture the exception that we want to see # below for comparison. desired_exception = e - desired_exception.strerror += ': ' + repr(sys.executable) + desired_exception.strerror += ': ' + repr(self._nonexistent_dir) else: self.fail("chdir to nonexistant directory %s succeeded." % - nonexistent_dir) + self._nonexistent_dir) + return desired_exception - # Error in the child re-raised in the parent. + def test_exception_cwd(self): + """Test error in the child raised in the parent for a bad cwd.""" + desired_exception = self._get_chdir_exception() try: p = subprocess.Popen([sys.executable, "-c", ""], - cwd=nonexistent_dir) + cwd=self._nonexistent_dir) except OSError as e: # Test that the child process chdir failure actually makes # it up to the parent process as the correct exception. @@ -910,6 +916,33 @@ class POSIXProcessTestCase(BaseTestCase): else: self.fail("Expected OSError: %s" % desired_exception) + def test_exception_bad_executable(self): + """Test error in the child raised in the parent for a bad executable.""" + desired_exception = self._get_chdir_exception() + try: + p = subprocess.Popen([sys.executable, "-c", ""], + executable=self._nonexistent_dir) + except OSError as e: + # Test that the child process exec failure actually makes + # it up to the parent process as the correct exception. + self.assertEqual(desired_exception.errno, e.errno) + self.assertEqual(desired_exception.strerror, e.strerror) + else: + self.fail("Expected OSError: %s" % desired_exception) + + def test_exception_bad_args_0(self): + """Test error in the child raised in the parent for a bad args[0].""" + desired_exception = self._get_chdir_exception() + try: + p = subprocess.Popen([self._nonexistent_dir, "-c", ""]) + except OSError as e: + # Test that the child process exec failure actually makes + # it up to the parent process as the correct exception. + self.assertEqual(desired_exception.errno, e.errno) + self.assertEqual(desired_exception.strerror, e.strerror) + else: + self.fail("Expected OSError: %s" % desired_exception) + def test_restore_signals(self): # Code coverage for both values of restore_signals to make sure it # at least does not blow up. diff --git a/Misc/NEWS b/Misc/NEWS index 759a12cffd1..9e1e96b55e4 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -129,6 +129,10 @@ Core and Builtins Library ------- +- Issue #16114: The subprocess module no longer provides a misleading + error message stating that args[0] did not exist when either the cwd or + executable keyword arguments specified a path that did not exist. + - Issue #15756: subprocess.poll() now properly handles errno.ECHILD to return a returncode of 0 when the child has already exited or cannot be waited on. diff --git a/Modules/_posixsubprocess.c b/Modules/_posixsubprocess.c index 59673f4969f..19ca31f56dd 100644 --- a/Modules/_posixsubprocess.c +++ b/Modules/_posixsubprocess.c @@ -354,7 +354,7 @@ child_exec(char *const exec_array[], PyObject *preexec_fn, PyObject *preexec_fn_args_tuple) { - int i, saved_errno, unused; + int i, saved_errno, unused, reached_preexec = 0; PyObject *result; const char* err_msg = ""; /* Buffer large enough to hold a hex integer. We can't malloc. */ @@ -438,6 +438,7 @@ child_exec(char *const exec_array[], POSIX_CALL(setsid()); #endif + reached_preexec = 1; if (preexec_fn != Py_None && preexec_fn_args_tuple) { /* This is where the user has asked us to deadlock their program. */ result = PyObject_Call(preexec_fn, preexec_fn_args_tuple, NULL); @@ -487,6 +488,10 @@ error: } unused = write(errpipe_write, cur, hex_errno + sizeof(hex_errno) - cur); unused = write(errpipe_write, ":", 1); + if (!reached_preexec) { + /* Indicate to the parent that the error happened before exec(). */ + unused = write(errpipe_write, "noexec", 6); + } /* We can't call strerror(saved_errno). It is not async signal safe. * The parent process will look the error message up. */ } else { From 3650ea2a9fb7a846a79c3d0c5c6e7e314f008d5e Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Wed, 10 Oct 2012 06:52:08 -0700 Subject: [PATCH 3/7] Issue #12947: Divide doctest "Option Flags and Directives" section into two. This changeset also applies the rendering workaround to the last remaining example in the file that has a doctest directive that should be displayed. --- Doc/library/doctest.rst | 66 ++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/Doc/library/doctest.rst b/Doc/library/doctest.rst index f00a879c7c5..40cc73712cd 100644 --- a/Doc/library/doctest.rst +++ b/Doc/library/doctest.rst @@ -318,7 +318,8 @@ The fine print: Tabs in output generated by the tested code are not modified. Because any hard tabs in the sample output *are* expanded, this means that if the code output includes hard tabs, the only way the doctest can pass is if the - :const:`NORMALIZE_WHITESPACE` option or directive is in effect. + :const:`NORMALIZE_WHITESPACE` option or :ref:`directive ` + is in effect. Alternatively, the test can be rewritten to capture the output and compare it to an expected value as part of the test. This handling of tabs in the source was arrived at through trial and error, and has proven to be the least @@ -483,15 +484,16 @@ Some details you should read once, but won't need to remember: SyntaxError: invalid syntax +.. _option-flags-and-directives: .. _doctest-options: -Option Flags and Directives -^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Option Flags +^^^^^^^^^^^^ A number of option flags control various aspects of doctest's behavior. Symbolic names for the flags are supplied as module constants, which can be or'ed together and passed to various functions. The names can also be used in -doctest directives (see below). +:ref:`doctest directives `. The first group of options define test semantics, controlling aspects of how doctest decides whether actual output matches an example's expected output: @@ -545,8 +547,8 @@ doctest decides whether actual output matches an example's expected output: :exc:`TypeError` is raised. It will also ignore the module name used in Python 3 doctest reports. Hence - both these variations will work regardless of whether the test is run under - Python 2.7 or Python 3.2 (or later versions): + both of these variations will work with the flag specified, regardless of + whether the test is run under Python 2.7 or Python 3.2 (or later versions):: >>> raise CustomError('message') #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): @@ -562,15 +564,16 @@ doctest decides whether actual output matches an example's expected output: exception name. Using :const:`IGNORE_EXCEPTION_DETAIL` and the details from Python 2.3 is also the only clear way to write a doctest that doesn't care about the exception detail yet continues to pass under Python 2.3 or - earlier (those releases do not support doctest directives and ignore them - as irrelevant comments). For example, :: + earlier (those releases do not support :ref:`doctest directives + ` and ignore them as irrelevant comments). For example:: >>> (1, 2)[3] = 'moo' #doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): File "", line 1, in ? TypeError: object doesn't support item assignment - passes under Python 2.3 and later Python versions, even though the detail + passes under Python 2.3 and later Python versions with the flag specified, + even though the detail changed in Python 2.4 to say "does not" instead of "doesn't". .. versionchanged:: 3.2 @@ -632,9 +635,30 @@ The second group of options controls how test failures are reported: A bitmask or'ing together all the reporting flags above. -"Doctest directives" may be used to modify the option flags for individual -examples. Doctest directives are expressed as a special Python comment -following an example's source code: + +There is also a way to register new option flag names, though this isn't +useful unless you intend to extend :mod:`doctest` internals via subclassing: + + +.. function:: register_optionflag(name) + + Create a new option flag with a given name, and return the new flag's integer + value. :func:`register_optionflag` can be used when subclassing + :class:`OutputChecker` or :class:`DocTestRunner` to create new options that are + supported by your subclasses. :func:`register_optionflag` should always be + called using the following idiom:: + + MY_FLAG = register_optionflag('MY_FLAG') + + +.. _doctest-directives: + +Directives +^^^^^^^^^^ + +Doctest directives may be used to modify the :ref:`option flags +` for an individual example. Doctest directives are +special Python comments following an example's source code: .. productionlist:: doctest directive: "#" "doctest:" `directive_options` @@ -708,20 +732,6 @@ usually the only meaningful choice. However, option flags can also be passed to functions that run doctests, establishing different defaults. In such cases, disabling an option via ``-`` in a directive can be useful. -There's also a way to register new option flag names, although this isn't useful -unless you intend to extend :mod:`doctest` internals via subclassing: - - -.. function:: register_optionflag(name) - - Create a new option flag with a given name, and return the new flag's integer - value. :func:`register_optionflag` can be used when subclassing - :class:`OutputChecker` or :class:`DocTestRunner` to create new options that are - supported by your subclasses. :func:`register_optionflag` should always be - called using the following idiom:: - - MY_FLAG = register_optionflag('MY_FLAG') - .. _doctest-warnings: @@ -759,7 +769,9 @@ Another bad idea is to print things that embed an object address, like :: >>> C() # the default repr() for instances embeds an address <__main__.C instance at 0x00AC18F0> -The :const:`ELLIPSIS` directive gives a nice approach for the last example:: +The :const:`ELLIPSIS` directive gives a nice approach for the last example: + +.. code-block:: text >>> C() #doctest: +ELLIPSIS <__main__.C instance at 0x...> From 3a2e101be564ffcb73dac708533d9178d90c2da8 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 10 Oct 2012 16:45:11 +0200 Subject: [PATCH 4/7] Issue #12947: revert earlier workaround and use a monkey-patch to enable showing doctest directives only in the doctest docs. --- Doc/library/doctest.rst | 7 +++++-- Doc/tools/sphinxext/pyspecific.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/Doc/library/doctest.rst b/Doc/library/doctest.rst index ed53f060517..ec8edbe0ac3 100644 --- a/Doc/library/doctest.rst +++ b/Doc/library/doctest.rst @@ -1,3 +1,5 @@ +:keepdoctest: + :mod:`doctest` --- Test interactive Python examples =================================================== @@ -652,7 +654,7 @@ example. Use ``+`` to enable the named behavior, or ``-`` to disable it. For example, this test passes:: - >>> print(list(range(20))) #doctest: +NORMALIZE_WHITESPACE + >>> print(list(range(20))) # doctest: +NORMALIZE_WHITESPACE [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] @@ -664,7 +666,8 @@ so:: >>> print(list(range(20))) # doctest: +ELLIPSIS [0, 1, ..., 18, 19] -Multiple directives can be used on a single physical line, separated by commas:: +Multiple directives can be used on a single physical line, separated by +commas:: >>> print(list(range(20))) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE [0, 1, ..., 18, 19] diff --git a/Doc/tools/sphinxext/pyspecific.py b/Doc/tools/sphinxext/pyspecific.py index 89bb86fc22c..9b2cc47c88f 100644 --- a/Doc/tools/sphinxext/pyspecific.py +++ b/Doc/tools/sphinxext/pyspecific.py @@ -33,9 +33,38 @@ def new_visit_versionmodified(self, node): self.body.append('%s ' % text) from sphinx.writers.html import HTMLTranslator +from sphinx.writers.latex import LaTeXTranslator from sphinx.locale import versionlabels HTMLTranslator.visit_versionmodified = new_visit_versionmodified +HTMLTranslator.visit_versionmodified = new_visit_versionmodified +# monkey-patch HTML and LaTeX translators to keep doctest blocks in the +# doctest docs themselves +orig_visit_literal_block = HTMLTranslator.visit_literal_block +def new_visit_literal_block(self, node): + meta = self.builder.env.metadata[self.builder.current_docname] + old_trim_doctest_flags = self.highlighter.trim_doctest_flags + if 'keepdoctest' in meta: + self.highlighter.trim_doctest_flags = False + try: + orig_visit_literal_block(self, node) + finally: + self.highlighter.trim_doctest_flags = old_trim_doctest_flags + +HTMLTranslator.visit_literal_block = new_visit_literal_block + +orig_depart_literal_block = LaTeXTranslator.depart_literal_block +def new_depart_literal_block(self, node): + meta = self.builder.env.metadata[self.curfilestack[-1]] + old_trim_doctest_flags = self.highlighter.trim_doctest_flags + if 'keepdoctest' in meta: + self.highlighter.trim_doctest_flags = False + try: + orig_depart_literal_block(self, node) + finally: + self.highlighter.trim_doctest_flags = old_trim_doctest_flags + +LaTeXTranslator.depart_literal_block = new_depart_literal_block # Support for marking up and linking to bugs.python.org issues From 83e51f48a8f8d2671caa99206e77752eb6c42a4d Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 10 Oct 2012 16:45:11 +0200 Subject: [PATCH 5/7] Issue #12947: revert earlier workaround and use a monkey-patch to enable showing doctest directives only in the doctest docs. --- Doc/library/doctest.rst | 31 ++++++++----------------------- Doc/tools/sphinxext/pyspecific.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/Doc/library/doctest.rst b/Doc/library/doctest.rst index 40cc73712cd..802113b7a15 100644 --- a/Doc/library/doctest.rst +++ b/Doc/library/doctest.rst @@ -1,3 +1,5 @@ +:keepdoctest: + :mod:`doctest` --- Test interactive Python examples =================================================== @@ -674,43 +676,28 @@ above. An example's doctest directives modify doctest's behavior for that single example. Use ``+`` to enable the named behavior, or ``-`` to disable it. -.. note:: - Due to an `unfortunate limitation`_ of our current documentation - publishing process, syntax highlighting has been disabled in the examples - below in order to ensure the doctest directives are correctly displayed. +For example, this test passes:: - .. _unfortunate limitation: http://bugs.python.org/issue12947 - -For example, this test passes: - -.. code-block:: text - - >>> print(list(range(20))) #doctest: +NORMALIZE_WHITESPACE + >>> print(list(range(20))) # doctest: +NORMALIZE_WHITESPACE [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] Without the directive it would fail, both because the actual output doesn't have two blanks before the single-digit list elements, and because the actual output is on a single line. This test also passes, and also requires a directive to do -so: - -.. code-block:: text +so:: >>> print(list(range(20))) # doctest: +ELLIPSIS [0, 1, ..., 18, 19] Multiple directives can be used on a single physical line, separated by -commas: - -.. code-block:: text +commas:: >>> print(list(range(20))) # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE [0, 1, ..., 18, 19] If multiple directive comments are used for a single example, then they are -combined: - -.. code-block:: text +combined:: >>> print(list(range(20))) # doctest: +ELLIPSIS ... # doctest: +NORMALIZE_WHITESPACE @@ -718,9 +705,7 @@ combined: As the previous example shows, you can add ``...`` lines to your example containing only directives. This can be useful when an example is too long for -a directive to comfortably fit on the same line: - -.. code-block:: text +a directive to comfortably fit on the same line:: >>> print(list(range(5)) + list(range(10, 20)) + list(range(30, 40))) ... # doctest: +ELLIPSIS diff --git a/Doc/tools/sphinxext/pyspecific.py b/Doc/tools/sphinxext/pyspecific.py index d4f17d8fc3e..e8eb70368a3 100644 --- a/Doc/tools/sphinxext/pyspecific.py +++ b/Doc/tools/sphinxext/pyspecific.py @@ -33,9 +33,38 @@ def new_visit_versionmodified(self, node): self.body.append('%s ' % text) from sphinx.writers.html import HTMLTranslator +from sphinx.writers.latex import LaTeXTranslator from sphinx.locale import versionlabels HTMLTranslator.visit_versionmodified = new_visit_versionmodified +HTMLTranslator.visit_versionmodified = new_visit_versionmodified +# monkey-patch HTML and LaTeX translators to keep doctest blocks in the +# doctest docs themselves +orig_visit_literal_block = HTMLTranslator.visit_literal_block +def new_visit_literal_block(self, node): + meta = self.builder.env.metadata[self.builder.current_docname] + old_trim_doctest_flags = self.highlighter.trim_doctest_flags + if 'keepdoctest' in meta: + self.highlighter.trim_doctest_flags = False + try: + orig_visit_literal_block(self, node) + finally: + self.highlighter.trim_doctest_flags = old_trim_doctest_flags + +HTMLTranslator.visit_literal_block = new_visit_literal_block + +orig_depart_literal_block = LaTeXTranslator.depart_literal_block +def new_depart_literal_block(self, node): + meta = self.builder.env.metadata[self.curfilestack[-1]] + old_trim_doctest_flags = self.highlighter.trim_doctest_flags + if 'keepdoctest' in meta: + self.highlighter.trim_doctest_flags = False + try: + orig_depart_literal_block(self, node) + finally: + self.highlighter.trim_doctest_flags = old_trim_doctest_flags + +LaTeXTranslator.depart_literal_block = new_depart_literal_block # Support for marking up and linking to bugs.python.org issues From 23a87de96f11661979ed805b5cf7bda639116747 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 10 Oct 2012 16:56:15 +0200 Subject: [PATCH 6/7] Missed one instance of code-block:: text highlighting to revert. --- Doc/library/doctest.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Doc/library/doctest.rst b/Doc/library/doctest.rst index 802113b7a15..8f8e5446ca7 100644 --- a/Doc/library/doctest.rst +++ b/Doc/library/doctest.rst @@ -754,9 +754,7 @@ Another bad idea is to print things that embed an object address, like :: >>> C() # the default repr() for instances embeds an address <__main__.C instance at 0x00AC18F0> -The :const:`ELLIPSIS` directive gives a nice approach for the last example: - -.. code-block:: text +The :const:`ELLIPSIS` directive gives a nice approach for the last example:: >>> C() #doctest: +ELLIPSIS <__main__.C instance at 0x...> From 3fa8c59024418e904abd4394b5ec47642f076ae3 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Wed, 10 Oct 2012 08:34:38 -0700 Subject: [PATCH 7/7] Issue #12947: Remove doctest directives from the doctest examples in the "Option Flag" section. The doctest examples that illustrate the use of doctest option flags independent of doctest directives should not display doctest directives. --- Doc/library/doctest.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/doctest.rst b/Doc/library/doctest.rst index 8f8e5446ca7..222c7195289 100644 --- a/Doc/library/doctest.rst +++ b/Doc/library/doctest.rst @@ -552,11 +552,11 @@ doctest decides whether actual output matches an example's expected output: both of these variations will work with the flag specified, regardless of whether the test is run under Python 2.7 or Python 3.2 (or later versions):: - >>> raise CustomError('message') #doctest: +IGNORE_EXCEPTION_DETAIL + >>> raise CustomError('message') Traceback (most recent call last): CustomError: message - >>> raise CustomError('message') #doctest: +IGNORE_EXCEPTION_DETAIL + >>> raise CustomError('message') Traceback (most recent call last): my_module.CustomError: message @@ -569,7 +569,7 @@ doctest decides whether actual output matches an example's expected output: earlier (those releases do not support :ref:`doctest directives ` and ignore them as irrelevant comments). For example:: - >>> (1, 2)[3] = 'moo' #doctest: +IGNORE_EXCEPTION_DETAIL + >>> (1, 2)[3] = 'moo' Traceback (most recent call last): File "", line 1, in ? TypeError: object doesn't support item assignment