From a9e62ba49af1d3a525a2429215417055a90de9f1 2013-12-16 23:14:23 From: chebee7i Date: 2013-12-16 23:14:23 Subject: [PATCH] Provide helpful error messages on doctest failures. --- diff --git a/IPython/sphinxext/custom_doctests.py b/IPython/sphinxext/custom_doctests.py index a0bbb61..f0ec179 100644 --- a/IPython/sphinxext/custom_doctests.py +++ b/IPython/sphinxext/custom_doctests.py @@ -81,10 +81,26 @@ def float_doctest(sphinx_shell, args, input_lines, found, submitted): submitted[~submitted_isnan], rtol=rtol, atol=atol) + TAB = ' ' * 4 + directive = sphinx_shell.directive + if directive is None: + source = 'Unavailable' + content = 'Unavailable' + else: + source = directive.state.document.current_source + # Add tabs and make into a single string. + content = '\n'.join([TAB + line for line in directive.content]) + if error: - e = ('doctest float comparison failure for input_lines={0} with ' - 'found_output={1} and submitted ' - 'output="{2}"'.format(input_lines, repr(found), repr(submitted)) ) + + e = ('doctest float comparison failure\n\n' + 'Document source: {0}\n\n' + 'Raw content: \n{1}\n\n' + 'On input line(s):\n{TAB}{2}\n\n' + 'we found output:\n{TAB}{3}\n\n' + 'instead of the expected:\n{TAB}{4}\n\n') + e = e.format(source, content, '\n'.join(input_lines), repr(found), + repr(submitted), TAB=TAB) raise RuntimeError(e) doctests = { diff --git a/IPython/sphinxext/ipython_directive.py b/IPython/sphinxext/ipython_directive.py index 9a34de8..6e23fb0 100644 --- a/IPython/sphinxext/ipython_directive.py +++ b/IPython/sphinxext/ipython_directive.py @@ -155,7 +155,6 @@ def block_parser(part, rgxin, rgxout, fmtin, fmtout): INPUT_LINE: the input as string (possibly multi-line) REST : any stdout generated by the input line (not OUTPUT) - OUTPUT: the output string, possibly multi-line """ @@ -279,6 +278,9 @@ class EmbeddedSphinxShell(object): self.is_doctest = False self.is_suppress = False + # Optionally, provide more detailed information to shell. + self.directive = None + # on the first call to the savefig decorator, we'll import # pyplot as plt so we can make a call to the plt.gcf().savefig self._pyplot_imported = False @@ -293,7 +295,7 @@ class EmbeddedSphinxShell(object): def process_input_line(self, line, store_history=True): """process the input, capturing stdout""" - #print "input='%s'"%self.input + stdout = sys.stdout splitter = self.IP.input_splitter try: @@ -337,11 +339,14 @@ class EmbeddedSphinxShell(object): # Callbacks for each type of token def process_input(self, data, input_prompt, lineno): - """Process data block for INPUT token.""" + """ + Process data block for INPUT token. + + """ decorator, input, rest = data image_file = None image_directive = None - #print 'INPUT:', data # dbg + is_verbatim = decorator=='@verbatim' or self.is_verbatim is_doctest = (decorator is not None and \ decorator.startswith('@doctest')) or self.is_doctest @@ -403,22 +408,41 @@ class EmbeddedSphinxShell(object): self.cout.truncate(0) return (ret, input_lines, output, is_doctest, decorator, image_file, image_directive) - #print 'OUTPUT', output # dbg + def process_output(self, data, output_prompt, input_lines, output, is_doctest, decorator, image_file): - """Process data block for OUTPUT token.""" + """ + Process data block for OUTPUT token. + + """ + TAB = ' ' * 4 + if is_doctest and output is not None: found = output found = found.strip() submitted = data.strip() + if self.directive is None: + source = 'Unavailable' + content = 'Unavailable' + else: + source = self.directive.state.document.current_source + content = self.directive.content + # Add tabs and join into a single string. + content = '\n'.join([TAB + line for line in content]) + # Make sure the output contains the output prompt. ind = found.find(output_prompt) if ind < 0: - e = ('output prompt="{0}" does ' - 'not match out line={1}'.format(output_prompt, found)) + e = ('output does not contain output prompt\n\n' + 'Document source: {0}\n\n' + 'Raw content: \n{1}\n\n' + 'Input line(s):\n{TAB}{2}\n\n' + 'Output line(s):\n{TAB}{3}\n\n') + e = e.format(source, content, '\n'.join(input_lines), + repr(found), TAB=TAB) raise RuntimeError(e) found = found[len(output_prompt):].strip() @@ -426,9 +450,14 @@ class EmbeddedSphinxShell(object): if decorator.strip() == '@doctest': # Standard doctest if found != submitted: - e = ('doctest failure for input_lines="{0}" with ' - 'found_output="{1}" and submitted ' - 'output="{2}"'.format(input_lines, found, submitted) ) + e = ('doctest failure\n\n' + 'Document source: {0}\n\n' + 'Raw content: \n{1}\n\n' + 'On input line(s):\n{TAB}{2}\n\n' + 'we found output:\n{TAB}{3}\n\n' + 'instead of the expected:\n{TAB}{4}\n\n') + e = e.format(source, content, '\n'.join(input_lines), + repr(found), repr(submitted), TAB=TAB) raise RuntimeError(e) else: self.custom_doctest(decorator, input_lines, found, submitted) @@ -653,6 +682,9 @@ class IPythonDirective(Directive): # setting its backend since exec_lines might import pylab. self.shell = EmbeddedSphinxShell(exec_lines) + # Store IPython directive to enable better error messages + self.shell.directive = self + # reset the execution count if we haven't processed this doc #NOTE: this may be borked if there are multiple seen_doc tmp files #check time stamp? @@ -676,7 +708,6 @@ class IPythonDirective(Directive): return rgxin, rgxout, promptin, promptout - def teardown(self): # delete last bookmark self.shell.process_input_line('bookmark -d ipy_savedir', @@ -702,7 +733,7 @@ class IPythonDirective(Directive): parts = '\n'.join(self.content).split('\n\n') - lines = ['.. code-block:: ipython',''] + lines = ['.. code-block:: ipython', ''] figures = [] for part in parts: @@ -724,7 +755,9 @@ class IPythonDirective(Directive): if debug: print('\n'.join(lines)) else: - # This is what makes the lines appear in the final output. + # This has to do with input, not output. But if we comment + # these lines out, then no IPython code will appear in the + # final output. self.state_machine.insert_input( lines, self.state_machine.input_lines.source(0))