/usr/share/sagemath/bin/sage-fixdoctests is in sagemath-common 7.4-9.
This file is owned by root:root, with mode 0o755.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | #!/usr/bin/env python
"""
Given the output of doctest and a file, adjust the doctests so they won't fail.
Doctest failures due to exceptions are ignored.
AUTHORS::
- Nicolas M. Thiery <nthiery at users dot sf dot net> Initial version (2008?)
- Andrew Mathas <andrew dot mathas at sydney dot edu dot au> 2013-02-14
Cleaned up the code and hacked it so that the script can now cope with the
situations when either the expected output or computed output are empty.
Added doctest to sage.tests.cmdline
"""
import os, sys, difflib
import subprocess
from optparse import OptionParser
from sage.misc.temporary_file import tmp_dir
usage=r'''usage: sage --fixdoctests <source file> [doctest output] [--long]
Creates a file <source file>.out that passes the doctests (modulo any raised exceptions)'''
parser = OptionParser(usage=usage)
parser.add_option('-l','--long',dest='long', action="store_true", default=False)
options, args = parser.parse_args()
if not (len(args) in [1,2]) :
print(usage)
sys.exit(1)
# set input and output files
test_file = args[0]
test_output = args[1] if len(args)==2 else test_file
try:
with open(test_file,'r') as src:
src_in = src.read()
pass
except IOError:
raise ValueError('The file "%s" either does not exist or it is not readable' % test_file )
sys.exit(1)
# put the output of the test into sage's temporary directory
doc_out = tmp_dir()+'.tmp'
os.system('sage -t %s %s > %s'%('--long' if options.long else '', test_file, doc_out))
doc_out = open(doc_out).read()
sep = "**********************************************************************\n"
doctests = doc_out.split(sep)
src_in_lines = src_in.splitlines()
for block in doctests:
if not 'Failed example:' in block: continue # sanity checking, but shouldn't happen
# Extract the line, what was expected, and was got.
line = block.find('line ') # block should contain: 'line ##, in ...', where ## is an integer
comma = block.find(', in ') # we try to extract the line number which gives the test failure
if line == -1 or comma==-1: continue # but if something goes wrong we give up
line_num=eval(block[line+5:comma])
if 'Expected nothing' in block:
exp = block.find('Expected nothing')
block='\n'+block[exp+17:] # so that split('\nGot:\n') does not fail below
elif 'Expected:' in block:
exp = block.find('Expected:\n')
block=block[exp+10:]
else:
continue
if block[-21:]=='Got:\n <BLANKLINE>\n':
expected=block[:-22]
got=['']
else:
expected, got = block.split('\nGot:\n')
got = got.splitlines() # got can't be the empty string
# If we expected nothing, and got something, then we need to insert the line before line_num
# and match indentation with line number line_num-1
if expected=='':
indent = (len(src_in_lines[line_num-1]) - len(src_in_lines[line_num-1].lstrip()))
src_in_lines[line_num-1]+='\n'+'\n'.join('%s%s'%(' '*indent,line.lstrip()) for line in got)
continue
# Guess how much extra indenting got needs to match with the indentation
# of src_in_lines - we match the indentation with the line in ``got`` which
# has the smallest indentation after lstrip(). Note that the amount of indentation
# required could be negative if the ``got`` block is indented. In this case
# ``indent`` is set to zero.
indent = max(0,(len(src_in_lines[line_num]) - len(src_in_lines[line_num].lstrip())
- min(len(got[j]) - len(got[j].lstrip()) for j in range(len(got)))))
expected=expected.splitlines()
# Double check that what was expected was indeed in the source file and if
# it is not then then print a warning for the user which contains the
# problematic lines.
if any( expected[i].strip() != src_in_lines[line_num+i].strip() for i in range(len(expected)) ):
import warnings
warnings.warn("Did not manage to replace\n%s\n%s\n%s\nwith\n%s\n%s\n%s"%(
'>'*40,'\n'.join(expected),'>'*40,'<'*40, '\n'.join(got), '<'*40))
continue
# If we got something when we expected nothing then we delete the line from the
# output, otherwise, add all of what we `got` onto the end of src_in_lines[line_num]
if got==['']:
src_in_lines[line_num] = None
else:
src_in_lines[line_num] = '\n'.join( (' '*indent + got[i]) for i in range(len(got)) )
# Mark any remaining `expected` lines as ``None`` so as to preserve the line numbering
for i in range(1, len(expected)):
src_in_lines[line_num+i] = None
# Overwrite the source (or whatever output file was specified on the command line)
with open(test_output, 'w') as out:
for line in src_in_lines:
if line is None:
continue
out.write(line)
out.write('\n')
# Show summary of changes
if test_file != test_output:
print('The fixed doctests have been saved as {0}.'.format(test_output))
else:
from sage.env import SAGE_ROOT
relative = os.path.relpath(test_output, SAGE_ROOT)
print(relative)
if relative.startswith('..'):
print('Fixed source file is not part of Sage.')
else:
subprocess.call(['git', 'diff', relative], cwd=SAGE_ROOT)
|