Ticket #12415: 12415_script.patch
File 12415_script.patch, 67.8 KB (added by , 7 years ago) |
---|
-
deleted file sage-doctest
# HG changeset patch # User David Roe <roed.math@gmail.com> # Date 1332359248 25200 # Node ID f3f3ab2c319e33a3e6f6481d6cfe0817eab9eaaa # Parent 6ec5fc4f65cc62da47d12f315bbe0137951560a7 New doctesting framework. diff --git a/sage-doctest b/sage-doctest deleted file mode 100755
+ - 1 #!/usr/bin/env python2 3 ####################################################################4 # Doctest a single Python, Cython, Sage, TeX or ReST file, using5 # Sage's custom doctesting tags and framework built on top of6 # Python's doctest functionality. In particular, this allows for7 # sage: prompts, preparsing, optional doctests, random behavior, etc.8 #9 # This also searches for TAB characters. If any are found in .py,10 # .pyx, .spyx, or .sage files, then doctesting fails unless the string11 # "SAGE_DOCTEST_ALLOW_TABS" is also present somewhere (anywhere!) in12 # the file.13 #14 # Return value in process exit code:15 # 0: all tests passed16 # 1: file not found17 # 2: KeyboardInterrupt18 # 4: doctest process was terminated by a signal19 # 8: the doctesting framework raised an exception20 # 16: script called with bad options21 # 32: (used internally in sage-ptest)22 # 64: time out23 # 128: failed doctests24 ####################################################################25 26 # System imports27 import os, re, sys, signal, time, shutil, tempfile, random28 29 # if any of the following environment variables are set, use them for30 # the timeout lengths.31 TIMEOUT = os.getenv('SAGE_TIMEOUT')32 TIMEOUT_LONG = os.getenv('SAGE_TIMEOUT_LONG')33 TIMEOUT_VALGRIND = os.getenv('SAGE_TIMEOUT_VALGRIND')34 35 if TIMEOUT is None:36 # the default timeout for doctests: 6 minutes (in seconds)37 TIMEOUT = 6 * 6038 else:39 # output from os.getenv is a string: convert to a number40 TIMEOUT = float(TIMEOUT)41 42 if TIMEOUT_LONG is None:43 # the timeout value for long doctests: 30 minutes (in seconds)44 TIMEOUT_LONG = 30 * 6045 else:46 TIMEOUT_LONG = float(TIMEOUT_LONG)47 48 if TIMEOUT_VALGRIND is None:49 # the timeout for doctests running under valgrind tools: unreasonably long50 TIMEOUT_VALGRIND = 1024 * 102451 else:52 TIMEOUT_VALGRIND = float(TIMEOUT_VALGRIND)53 54 long_time = False55 only_optional = False56 only_optional_tags = set([])57 optional = False58 verbose = False59 random_order = False # default is that tests aren't run in random order60 61 argv = sys.argv62 63 import sage.misc.preparser64 65 ######################################################66 # This code is copied from sage.misc.misc for speed:67 ######################################################68 def is_64bit():69 return sys.maxint == 922337203685477580770 71 ######################################################72 # Temporary files73 ######################################################74 DOT_SAGE = os.environ['DOT_SAGE']75 if 'SAGE_TESTDIR' not in os.environ or os.environ['SAGE_TESTDIR'] is "":76 SAGE_TESTDIR = os.path.join(DOT_SAGE, "tmp")77 else:78 SAGE_TESTDIR = os.environ['SAGE_TESTDIR']79 80 tmpfiles = [] # list of temporary files to be deleted if doctesting succeeds81 82 def delete_tmpfiles():83 for f in tmpfiles:84 try:85 os.remove(f)86 except OSError:87 pass88 89 ######################################################90 # Set environment variables91 ######################################################92 SAGE_ROOT = os.environ["SAGE_ROOT"]93 LD = os.environ["LD_LIBRARY_PATH"]94 os.environ["LD_LIBRARY_PATH"] = os.path.join(SAGE_ROOT, 'local',95 'lib') + ":" + LD96 os.environ["PYTHONPATH"] = os.path.join(SAGE_ROOT, 'local', 'lib', 'python',97 'site-packages')98 if os.environ.has_key('SAGE_PATH'):99 os.environ["PYTHONPATH"] = os.environ["PYTHONPATH"] + ':' + os.environ['SAGE_PATH']100 101 102 ######################################################103 # Custom flags for the valgrind modes104 ######################################################105 logfile = ' --log-file=' + os.path.join(DOT_SAGE, 'valgrind', 'sage-%s') + ' '106 try:107 SAGE_MEMCHECK_FLAGS = os.environ['SAGE_MEMCHECK_FLAGS']108 print SAGE_MEMCHECK_FLAGS109 except:110 suppfile = os.path.join('$SAGE_LOCAL', 'lib', 'valgrind', 'sage.supp')111 SAGE_MEMCHECK_FLAGS = " --leak-resolution=high %s --leak-check=full --num-callers=25 --suppressions=%s " % (logfile % 'memcheck.%p', suppfile)112 113 try:114 SAGE_MASSIF_FLAGS = os.environ['SAGE_MASSIF_FLAGS']115 except:116 SAGE_MASSIF_FLAGS = " --depth=6 " + (logfile % 'massif.%p')117 118 try:119 SAGE_CALLGRIND_FLAGS = os.environ['SAGE_CALLGRIND_FLAGS']120 except:121 SAGE_CALLGRIND_FLAGS = logfile % 'callgrind.%p'122 123 try:124 SAGE_CACHEGRIND_FLAGS = os.environ['SAGE_CACHEGRIND_FLAGS']125 except:126 SAGE_CALLGRIND_FLAGS = logfile % 'cachegrind.%p'127 128 try:129 SAGE_OMEGA_FLAGS = os.environ['SAGE_OMEGA_FLAGS']130 except:131 SAGE_OMEGA_FLAGS = logfile % 'omega.%p'132 133 ######################################################134 # The Python binary135 ######################################################136 PYTHON = "python"137 138 139 def pad_zeros(s, size):140 """141 INPUT:142 - `s` -- something coercible to string143 - `size` -- integer144 145 Return s as a string with 0's padded on the left so the string has146 length exactly size.147 """148 s = str(s)149 return "0"*(size-len(s)) + s150 151 index_cache = set([])152 def new_index(n):153 """154 Return n-th example number. If random_order is set, this is a randomly155 chosen number that we haven't returned before. If random_order is not156 set, this just returns n.157 158 INPUT:159 - `n` -- integer160 """161 if random_order:162 while True:163 a = random.randint(0,2**32)164 if a not in index_cache:165 index_cache.add(a)166 return a167 else:168 return n169 170 def test_code(filename):171 # Process exit codes for the generated doctest runner script:172 # 0: everything passed173 # 1-253: number of failed doctests174 # 254: >= 254 doctests failed175 # 255: exception raised by doctesting framework176 dict = { 'DIR' : repr(os.path.join(SAGE_ROOT, 'local', 'bin')),177 'FILENAME' : repr(filename),178 'OUTPUT_FILENAME' : repr(filename + '.timeit.sobj'),179 'TIMEIT' : do_timeit, # global180 'VERBOSE' : verbose }181 return """182 if __name__ == '__main__':183 verbose = %(VERBOSE)s184 do_timeit = %(TIMEIT)s185 output_filename = %(OUTPUT_FILENAME)s186 187 import sys188 sys.path = sys.path + [%(DIR)s]189 import sagedoctest190 191 # execfile(%(FILENAME)s)192 m = sys.modules[__name__]193 m.__file__ = %(FILENAME)s194 195 try:196 197 # configure special sage doc test runner198 runner = sagedoctest.SageDocTestRunner(checker=None, verbose=verbose, optionflags=0)199 runner._collect_timeit_stats = do_timeit200 runner._reset_random_seed = True201 202 runner = sagedoctest.testmod_returning_runner(m,203 # filename=%(FILENAME)s,204 verbose=verbose,205 globs=globals(),206 runner=runner)207 runner.save_timeit_stats_to_file_named(output_filename)208 except:209 quit_sage(verbose=False)210 import traceback211 traceback.print_exc(file=sys.stdout)212 sys.exit(255)213 quit_sage(verbose=False)214 if runner.failures > 254:215 sys.exit(254)216 sys.exit(runner.failures)217 """ % dict218 219 NONE=0; LONG_TIME=1; RANDOM=2; OPTIONAL=3; NOT_IMPLEMENTED=4; NOT_TESTED=5; TOLERANCE=6220 tolerance_pattern = re.compile(r'\b((?:abs(?:olute)?)|(?:rel(?:ative)?))? *?tol(?:erance)?\b( +[0-9.e+-]+)?')221 222 def comment_modifier(s):223 sind = s.find('#')224 if sind == -1 or s[sind:sind+3] == '###':225 return [], ''226 eind = s.find('###',sind+1)227 L = s[sind+1:eind].lower()228 v = []229 if 'optional' in L:230 v.append(OPTIONAL)231 if 'known bug' in L:232 # Doctests marked like this should be automatically converted233 # to "optional bug", so they will be run by234 #235 # sage -t ... --only-optional=bug236 #237 # So replace 'known' with '' or 'optional', depending on238 # whether 'optional' is already there.239 if 'optional' in L:240 L = L.replace('known', '')241 else:242 v.append(OPTIONAL)243 L = L.replace('known', 'optional')244 if 'long time' in L:245 v.append(LONG_TIME)246 if 'not implemented' in L:247 v.append(NOT_IMPLEMENTED)248 if 'not tested' in L:249 v.append(NOT_TESTED)250 if 'random' in L:251 v.append(RANDOM)252 m = tolerance_pattern.search(L)253 if m:254 v.append(TOLERANCE)255 rel_or_abs, epsilon = m.groups()256 if rel_or_abs is not None:257 rel_or_abs = rel_or_abs[:3]258 if epsilon is None:259 epsilon = 1e-15260 else:261 epsilon = float(epsilon.strip())262 v.append((rel_or_abs, epsilon))263 return v, L264 265 def close_tolerance(comment_tags):266 rel_or_abs, epsilon = comment_tags[comment_tags.index(TOLERANCE) + 1]267 return "... ''', res, %r%s)" % (epsilon, '' if rel_or_abs is None else ", '%s'" % rel_or_abs)268 269 def preparse_line_with_prompt(L):270 i = L.find(':')271 if i == -1:272 return sage.misc.preparser.preparse(L)273 else:274 return L[:i+1] + sage.misc.preparser.preparse(L[i+1:])275 276 def doc_preparse(s):277 """278 Run the preparser on the documentation string s.279 This *only* preparses the input lines, i.e., those280 that begin with "sage:".or with "..."281 """282 sl = s.lower()283 284 # t: Deal with code whose output should be ignored.285 t = []286 287 # used for adapting the output based on comments288 has_tolerance = False289 comment_modifiers = []290 last_prompt_comment = ''291 292 for L in s.splitlines():293 if not L.strip():294 if has_tolerance:295 t.append(close_tolerance(c) + old_cmd)296 has_tolerance = False297 298 begin = L.lstrip()[:5]299 comment = ''300 if begin == 'sage:':301 if has_tolerance:302 # old_cmd contains the command being tested along with303 # the line number.304 t.append(close_tolerance(c) + old_cmd)305 c, comment = comment_modifier(L)306 last_prompt_comment = comment307 line = ''308 if LONG_TIME in c and not long_time:309 L = '\n' # extra line so output ignored310 if RANDOM in c:311 L = L.replace('sage:', 'sage: print "ignore this"; ')312 if NOT_TESTED in c:313 L = '\n' # not tested314 if NOT_IMPLEMENTED in c:315 L = '\n' # not tested316 if OPTIONAL in c and not (only_optional or optional):317 L = '\n'318 if TOLERANCE in c:319 t.append(">>> res = Exception")320 L = "sage: res = %s" % L.lstrip()[5:]321 has_tolerance = True322 else:323 has_tolerance = False324 line = preparse_line_with_prompt(L)325 if RANDOM in c:326 # count spaces at the beginning of line to fix alignment later327 i = 0328 while i < len(line) and line[i].isspace():329 i += 1330 # append a line saying 'ignore' followed by ellipsis (...)331 # and an empty line, to ignore the output given in the test332 line += '\n' + ' '*i + 'ignore ...\n'333 if has_tolerance:334 # save the current command along with the line number335 try:336 idx = line.index('###_sage"line')337 old_cmd = line[idx:]338 except IndexError:339 old_cmd = ''340 line += "\n>>> check_with_tolerance('''"341 t.append(line)342 343 elif begin.startswith('...'):344 comment = last_prompt_comment345 i = L.find('.')346 t.append(L[:i+3] + sage.misc.preparser.preparse(L[i+3:]))347 348 else:349 comment = last_prompt_comment350 if has_tolerance:351 L = "... " + L352 t.append(L)353 354 comment_modifiers.append(comment)355 if has_tolerance:356 # In this case, we added a line to the source code:357 # ">>> res = Exception"358 # So we need to add 'comment' one more time to359 # comment_modifiers so that the length of the list of360 # lines agrees with the length of comment_modifiers.361 comment_modifiers.append(comment)362 363 if has_tolerance:364 t.append(close_tolerance(c))365 366 # The very last line -- which is typically """ -- must never be marked as optional,367 # or it might not get included, which would be a syntax error.368 comment_modifiers[-1] = ''369 370 # if only_optional is True, then we skip the entire block unless certain371 # conditions are met:372 # 1. If the tag list is empty, some line must contain # optional373 # 2. If the tag list is nonempty, some line must contain374 # # optional - nonempty,subset,of,tags375 # In this case, we also strip out all #optional lines that don't376 # contain a nonempty subset of tags, but keep all other lines.377 if only_optional:378 # check if any doctest is optional379 contains_optional_doctests = (len([x for x in comment_modifiers if 'optional' in x]) > 0)380 if len(only_optional_tags) == 0:381 # case 1382 if not contains_optional_doctests:383 t = []384 else:385 # case 2386 # first test that some line contains nonempty subset of tags, since387 # if not we won't bother at all.388 if contains_optional_doctests:389 # make a list of each optional line cut out by our tag choices390 v = [i for i in range(len(t)) if only_optional_include(comment_modifiers[i])]391 if len(v) == 0:392 # if v is empty, don't test this block at all.393 t = []394 else:395 # otherwise, we test everything that is non-optional along with everything396 # listed in v.397 v = set(v)398 t = [t[i] for i in range(len(t)) if i in v or 'optional' not in comment_modifiers[i]]399 else:400 t = []401 # Now put docstring together and return it.402 403 return '\n'.join(t)404 405 def only_optional_include(s):406 """407 Return True if s is of the form408 # optional list,of,tags409 and the list of tags is a nonempty subset the global list only_optional_tags.410 If only_optional_tags is empty, then it is considered to be the411 set of all tags.412 413 INPUT:414 s -- a string415 only_optional_tags -- a global variable416 OUTPUT:417 bool418 """419 i = s.find('optional')420 if i == -1:421 return False422 if len(only_optional_tags) == 0:423 # This doctest has # optional in it, but there are no tags, which424 # mean test everything that is optional.425 return True426 # Delete all white space, colons, periods, commas, and hyphens:427 s = ','.join(s[i+len('optional')+1:].translate(None, '-:,.').split())428 v = set(s.split(','))429 # Delete 'needs' and 'requires'.430 v.discard('needs')431 v.discard('requires')432 return (len(v) > 0 and v.issubset(only_optional_tags))433 434 435 def extract_doc(file_name, library_code=False):436 """437 INPUT:438 file_name -- string; name of the file to extract the439 docstrings from.440 library_code -- bool (default: False); if True, this file is441 Sage core library code, so we do not import it,442 and do doctest in a temporary directory.443 """444 F = open(file_name).readlines()445 # Put line numbers on every input line446 v = []447 i = 1448 j = 0449 while j < len(F):450 L = F[j].rstrip()451 if L.lstrip()[:5] == 'sage:':452 while j < len(F) and L.endswith('\\') and not L.endswith('\\\\'):453 j += 1454 i += 1455 L = L[:-1] + F[j].lstrip().lstrip('...').rstrip()456 L += '###_sage"line %s:_sage_ %s_sage"'%(i, L.strip())457 j += 1458 i += 1459 v.append(L)460 461 # 32/64-bit. If we're on a 32-bit computer, remove all lines that462 # contains "# 64-bit", and if we're on a 64-bit machine remove all463 # lines that contain "# 32-bit". This makes it possible to have464 # different output in the doctests for different bit machines.465 466 if is_64bit():467 exclude_string = "# 32-bit"468 else:469 exclude_string = "# 64-bit"470 471 F = '\n'.join([L for L in v if not exclude_string in L])472 473 if is_64bit():474 F = F.replace('# 64-bit','')475 else:476 F = F.replace('# 32-bit','')477 478 F = F.replace('\'"""\'','')479 480 root_name, ext = os.path.splitext(file_name)481 if ext == ".tex":482 F = pythonify_tex(F)483 elif ext == ".rst":484 F = pythonify_rst(F)485 486 s = "# -*- coding: utf-8 -*-\n"487 s += """488 # This file was generated by 'sage-doctest' from489 # '%s'.490 491 """ % os.path.join(os.getcwd(), file_name)492 s += "from sage.all_cmdline import *; \n"493 s += "import sage.misc.displayhook\n"494 s += "import sys\n"495 s += "sys.displayhook = sage.misc.displayhook.DisplayHook(sys.displayhook)\n"496 s += "import sage.plot.plot; sage.plot.plot.DOCTEST_MODE=True\n" # turn off image popup497 s += r"""498 def warning_function(f):499 import warnings500 501 def doctest_showwarning(message, category, filename, lineno, file=f, line=None):502 try:503 file.write(warnings.formatwarning(message, category, 'doctest', lineno, line))504 except IOError:505 pass # the file (probably stdout) is invalid506 return doctest_showwarning507 508 def change_warning_output(file):509 import warnings510 warnings.showwarning = warning_function(file)511 512 import re513 float_pattern = re.compile('[+-]?((\d*\.?\d+)|(\d+\.?))([eE][+-]?\d+)?')514 515 def check_with_tolerance(expected, actual, epsilon, rel_or_abs=None):516 if actual is Exception:517 return # error computing actual518 else:519 actual = str(actual)520 expected = re.sub('\n +', '\n', expected)521 actual = re.sub('\n +', '\n', actual)522 assert float_pattern.sub('#', expected.strip()) == float_pattern.sub('#', actual.strip()), \523 "Expected '" + expected + "' got '" + actual + "'"524 for expected_value, actual_value in zip(float_pattern.finditer(expected), float_pattern.finditer(actual)):525 expected_value = float(expected_value.group())526 actual_value = float(actual_value.group())527 if rel_or_abs == 'abs' or expected_value == 0:528 assert abs(expected_value - actual_value) < epsilon, "Out of tolerance %s vs %s" % (expected_value, actual_value)529 else:530 assert abs((expected_value - actual_value) / expected_value) < epsilon, "Out of tolerance %s vs %s" % (expected_value, actual_value)531 """532 533 if not library_code:534 if ext in ['.pyx','.spyx']:535 s += "cython(open('%s').read())\n\n" % file_name536 537 elif ext in ['.py', '.sage']:538 539 # For non-libary files, we need two different Python540 # files: one which contains the original Python code,541 # which we import, and one which contains the doctesting542 # framework. First for FILE.py or FILE.sage, we replace543 # FILE with FILE_PID. Then for FILE.sage, the imported544 # file is the preparsed version, so call that545 # FILE_PID_preparsed.py. For FILE.py, the imported546 # version is just a copy of the original file, called547 # FILE_PID_orig.py. The file containing doctesting548 # framework will be FILE_PID.py; this gets run through the549 # actual doctesting procedure by the 'test_file' function550 # below.551 552 root_name = os.path.basename(root_name)553 target_name = "%s_%d" % (root_name, os.getpid()) # like 'root_name', but unique554 target_base = os.path.join(SAGE_TESTDIR, target_name) # like 'target_name' but with full path555 556 if ext == '.sage':557 # TODO: preparse "<file>.sage" with a Sage library call558 # instead and write a string into temp_name.559 560 # For now: "sage -preparse <file>.sage" doesn't have any561 # output options and always creates <file>.py in the same562 # directory, so we first copy the *source* into SAGE_TESTDIR:563 os.system("cp '%s' %s.sage" % (file_name, target_base))564 # Now create SAGE_TESTDIR/<target_name>.py:565 os.system("sage -preparse %s.sage" % target_base)566 os.system("mv '%s'.py %s_preparsed.py" % (target_base, target_base))567 tmpfiles.append(target_base + ".sage")568 s += "\nfrom %s_preparsed import *\n\n" % target_name569 tmpfiles.append(target_base + "_preparsed.py") # preparsed version of original570 tmpfiles.append(target_base + "_preparsed.pyc") # compiled version571 else:572 # TODO: instead of copying the file, add its source573 # directory to PYTHONPATH. We would also have to574 # import from 'name' instead of 'target_name'.575 os.system("cp '%s' %s_orig.py" % (file_name, target_base))576 s += "\nfrom %s_orig import *\n\n" % target_name577 tmpfiles.append(target_base + "_orig.py") # copied original578 tmpfiles.append(target_base + "_orig.pyc") # compiled version579 580 tmpfiles.append(target_base + ".py") # file with doctesting framework581 tmpfiles.append(target_base + ".pyc") # compiled version582 583 # Prefix/suffix for all doctests replacing the starting/ending """584 doc_prefix = 'r""">>> set_random_seed(0L)\n\n>>> change_warning_output(sys.stdout)\n\n'585 doc_suffix = '\n>>> sig_on_count()\n0\n"""'586 587 n = 0588 while True:589 i = F.find('"""')590 if i == -1: break591 k = F[i+3:].find('"""')592 if k == -1: break593 j = i+3 + k594 try:595 doc = doc_preparse(F[i:j+3])596 except SyntaxError:597 doc = F[i:j+3]598 if len(doc):599 doc = doc_prefix + doc[3:-3] + doc_suffix600 if random_order:601 n_str = pad_zeros(new_index(n),10)602 else:603 n_str = str(n)604 s += "def example_%s():" % n_str605 n += 1606 s += "\t"+ doc + "\n\n"607 F = F[j+3:]608 609 if n == 0:610 return ''611 s += test_code(os.path.abspath(file_name))612 613 # Allow for "sage:" instead of the traditional Python ">>>".614 prompt_re = re.compile(r'(\n[ \t]*)sage:', re.MULTILINE)615 s = prompt_re.sub(r'\1>>>', s)616 s = s.replace('_sage"','')617 618 return s619 620 def pythonify_tex(F):621 """622 INPUT:623 F -- string; read in latex file624 OUTPUT:625 string -- python program that has functions with docstrings made from the626 verbatim examples in the latex file.627 """628 # Close links:629 F = F.replace('\\end{verbatim}%link','')630 F = F.replace('%link\n\\begin{verbatim}','')631 632 # Get rid of skipped code633 s = ''634 while True:635 i = F.find('%skip')636 if i == -1:637 s += F638 break639 s += F[:i]640 F = F[i:]641 j = F.find('\\end{verbatim}')642 if j == -1:643 break644 F = F[j + len('\\end{verbatim}')+1:]645 F = s646 647 # Make the verbatim environ's get extracted via the usual parser above648 F = F.replace("\\begin{verbatim}",'"""')649 F = F.replace("\\end{verbatim}",'"""')650 return F651 652 def pythonify_rst(F):653 """654 INPUT:655 F -- string; read in ReST file656 OUTPUT:657 string -- python program that has functions with docstrings made from the658 verbatim examples in the ReST file.659 """660 import re661 662 link_all = re.search(r'^\s*\.\.\s+linkall\s*$', F, re.M)663 664 def get_next_verbatim_block(s, pos):665 while True:666 # regular expression search in string s[pos:] for:667 # whitespace followed by "::" at the start of a line668 srch = re.search('^(\s*).*::\s*$', s[pos:], re.M)669 670 #Return -1 if we don't find anything.671 if srch is None:672 return "", -1, False673 674 pos += srch.start()675 676 prev_line = F.rfind("\n", 0, pos)677 start_of_options = F.rfind("\n", 0, prev_line if prev_line != -1 else 0)678 options = F[start_of_options:pos]679 680 pos += (srch.end() - srch.start())681 682 #Don't return skipped blocks683 if '.. skip' in options:684 continue685 686 #Check to see if we need to link up with the previous687 #block688 link_previous = link_all or ('.. link' in options)689 690 #Find the first line that isn't indented as much as691 #whatever followed the double colon. The whitespace before692 #the double colon is stored in srch.groups()[0]693 lw = len(srch.groups()[0]) # length of whitespace before ::694 #So match anything of the form695 # beg. of line + whitespace of length at most lw + nonwhitespace696 no_indent = re.search("^\s{,%s}\S"%lw, s[pos:], re.M)697 698 if no_indent is None:699 return s[pos:], None, link_previous700 701 block, pos = s[pos:pos+no_indent.start()], pos+no_indent.start()702 if re.compile('^\s*sage: ', re.M).search(block) is not None:703 return block, pos, link_previous704 705 s = ''706 pos = 0707 while pos is not None and pos != -1:708 block, pos, link_previous = get_next_verbatim_block(F, pos)709 710 #Break if we can't find any more verbatim blocks711 if pos == -1:712 break713 714 #Check to see if we have to link this block715 #up with the previous block716 if link_previous and s != "":717 #Get rid of the trailing quotes and newlines from the718 #previous block719 s = s[:-5]720 721 s += '%s\n"""\n\n'%block722 else:723 s += '\n"""\n%s\n"""\n\n'%block724 725 #print s726 #print "--"*100727 return s728 729 730 731 def post_process(s, file, tmpname):732 s = s.replace('.doctest_','')733 if file[-3:] == 'pyx':734 s = s.replace('"%s", line'%file[:-1], '"%s", line'%file)735 i = s.find("Failed example:")736 cnt = 0737 while i != -1:738 k = s[:i].rfind('File')739 k += s[k:].find(',')740 j = s[i:].find('###line')741 s = s[:k] + ', ' + s[i+j+3:]742 i = s.find("Failed example:")743 if i != -1:744 t = s[:i]745 else:746 t = s747 if t.find('check_with_tolerance') != -1:748 j = s.find('Exception raised')749 ass = 'AssertionError: '750 k = s.find(ass)751 s = s[:j] + s[k+len(ass):]752 i = s.find("Failed example:")753 cnt += 1754 if cnt > 1000:755 break756 s = s.replace(':_sage_',':\n').replace('>>>','sage:')757 c = '###line [0-9]*\n'758 r = re.compile(c)759 s = r.sub('\n',s)760 if cnt > 0:761 s += "For whitespace errors, see the file %s"%tmpname762 return (s, cnt)763 764 765 def test_file(file, library_code):766 if os.path.exists(file):767 s = extract_doc(file, library_code=library_code)768 if len(s) == 0:769 delete_tmpfiles()770 sys.exit(0)771 772 name = os.path.basename(file)773 name = name[:name.find(".")]774 f = os.path.join(SAGE_TESTDIR, "%s_%d.py" % (name, os.getpid()))775 776 open(f,"w").write(s)777 tmpfiles.append(f)778 779 cmd = "%s %s"%(PYTHON, f)780 if gdb:781 print "*"*80782 print "Type r at the (gdb) prompt to run the doctests."783 print "Type bt if there is a crash to see a traceback."784 print "*"*80785 cmd = "gdb --args " + cmd786 787 if memcheck:788 cmd = "valgrind --tool=memcheck " + SAGE_MEMCHECK_FLAGS + cmd789 if massif:790 cmd = "valgrind --tool=massif " + SAGE_MASSIF_FLAGS + cmd791 if cachegrind:792 cmd = "valgrind --tool=cachegrind " + SAGE_CACHEGRIND_FLAGS + cmd793 if omega:794 cmd = "valgrind --tool=exp-omega " + SAGE_OMEGA_FLAGS + cmd795 796 VALGRIND = os.path.join(DOT_SAGE, 'valgrind')797 try:798 os.makedirs(VALGRIND)799 except OSError:800 if not os.path.isdir(VALGRIND):801 raise802 803 tm = time.time()804 try:805 out = ''; err = ''806 if verbose or gdb or memcheck or massif or cachegrind:807 import subprocess808 proc = subprocess.Popen(cmd, shell=True)809 while time.time()-tm <= TIMEOUT and proc.poll() == None:810 time.sleep(0.1)811 if time.time()-tm >=TIMEOUT:812 os.kill(proc.pid, 9)813 print "*** *** Error: TIMED OUT! PROCESS KILLED! *** ***"814 e = proc.poll()815 else:816 outf = tempfile.NamedTemporaryFile()817 import subprocess818 proc = subprocess.Popen(cmd, shell=True, \819 stdout=outf.file.fileno(), stderr = outf.file.fileno())820 while time.time()-tm <= TIMEOUT and proc.poll() == None:821 time.sleep(0.1)822 if time.time()-tm >=TIMEOUT:823 os.kill(proc.pid, 9)824 print "*** *** Error: TIMED OUT! PROCESS KILLED! *** ***"825 outf.file.seek(0)826 out = outf.read()827 e = proc.poll()828 except KeyboardInterrupt:829 # TODO: if tests were interrupted but there were no failures, delete tmpfiles.830 print "KeyboardInterrupt -- interrupted after %.1f seconds!" % (time.time()-tm)831 sys.exit(2)832 if 'raise KeyboardInterrupt' in err:833 # TODO: if tests were interrupted but there were no failures, delete tmpfiles.834 print "*"*80 + "Control-C pressed -- interrupting doctests." + "*"*80835 sys.exit(2)836 837 if time.time() - tm >= TIMEOUT:838 print err839 sys.exit(64)840 841 s, numfail = post_process(out, file, f)842 s += err843 844 # search for tabs. if found, the doctest fails, unless the string845 # "SAGE_DOCTEST_ALLOW_TABS" is also present somewhere in the file.846 ext = os.path.splitext(file)[1]847 if ext in [".py", ".pyx", ".sage", ".spyx"]:848 ff = open(file)849 source = ff.read()850 ff.close()851 if (source.find("SAGE_DOCTEST_ALLOW_TABS") == -1852 and source.find("\t") != -1):853 numfail += 1854 s = "*"*70 + "\n" + "Error: TAB character found.\n" + s855 if e == 255:856 # The doctesting code raised an exception857 print "Exception raised by doctesting framework. Use -verbose for details."858 sys.exit(8)859 860 if numfail == 0 and e > 0:861 numfail = e862 if numfail > 0:863 if not (verbose or gdb or memcheck or massif or cachegrind):864 print s865 sys.exit(128)866 elif e < 0:867 print "The doctested process was killed by signal %s" % (-e)868 sys.exit(4)869 else:870 delete_tmpfiles()871 sys.exit(0)872 else:873 print "Error running %s, since file %s does not exist."%(874 argv[0], argv[1])875 sys.exit(1)876 877 878 def has_opt(opt):879 if '-' + opt in argv:880 i = argv.index('-' + opt)881 del argv[i]882 return True883 elif '--' + opt in argv:884 i = argv.index('--' + opt)885 del argv[i]886 return True887 return False888 889 def parse_only_opt():890 """891 Search through argv for the -only-optional=list,of,tags option.892 If it is there, return True and a set of tags as a list of strings.893 If not, return False and an empty set.894 895 NOTE: Because it's very natural/easy to type only_optional instead896 of only-optional as an option (given that variable names have897 underscores in Python), using only_optional is accepted in898 addition to only-optional.899 900 OUTPUT:901 bool, set902 """903 for j, X in enumerate(argv):904 Z = X.lstrip('-')905 if Z.startswith('only-optional=') or Z.startswith('only_optional='):906 i = Z.find('=')907 del argv[j]908 return True, set(Z[i+1:].lower().split(','))909 elif Z.startswith('only-optional') or Z.startswith('only_optional'): # no equals910 del argv[j]911 return True, set([])912 return False, []913 914 def parse_rand():915 """916 Randomize seed and return True if we randomize doctest order.917 Otherwise we leave doctests in the "traditional order" and return False.918 """919 for j, X in enumerate(argv):920 Z = X.lstrip('-')921 if Z.startswith('randorder'):922 del argv[j]923 i = Z.find('=')924 if i != -1:925 random.seed(Z[i+1:])926 return True927 return False928 929 def usage():930 print "\n\nUsage: sage-doctest [same options as sage -t] filenames"931 sys.exit(16)932 933 if __name__ == '__main__':934 import os, sys935 if len(argv) == 1:936 usage()937 else:938 if has_opt('help') or has_opt('h') or has_opt('?'):939 usage()940 optional = has_opt('optional')941 long_time = has_opt('long')942 verbose = has_opt('verbose')943 do_timeit = has_opt('timeit')944 gdb = has_opt('gdb')945 memcheck = has_opt('memcheck') or has_opt('valgrind')946 massif = has_opt('massif')947 cachegrind = has_opt('cachegrind')948 omega = has_opt('omega')949 force_lib = has_opt('force_lib')950 random_order = parse_rand()951 only_optional, only_optional_tags = parse_only_opt()952 if long_time:953 TIMEOUT = TIMEOUT_LONG954 if gdb or memcheck or massif or cachegrind or omega:955 TIMEOUT = TIMEOUT_VALGRIND956 if argv[1][0] == '-':957 usage()958 959 ext = os.path.splitext(argv[1])[1]960 961 library_code = True962 dev_path = os.path.realpath(os.path.join(SAGE_ROOT, 'devel'))963 our_path = os.path.realpath(argv[1])964 965 if not force_lib and (ext in ['.spyx', '.sage'] or966 not dev_path in our_path):967 library_code = False968 969 try:970 test_file(argv[1], library_code = library_code)971 except KeyboardInterrupt:972 sys.exit(1) -
sage-eval
diff --git a/sage-eval b/sage-eval
a b 4 4 from sage.all import * 5 5 from sage.calculus.predefined import x 6 6 from sage.misc.preparser import preparse 7 from sage.misc.misc import tmp_filename8 7 9 8 if len(sys.argv) > 1: 10 9 s = preparse(" ".join(sys.argv[1:])) -
deleted file sage-maketest
diff --git a/sage-maketest b/sage-maketest deleted file mode 100755
+ - 1 #!/usr/bin/env bash2 3 if [ -z "$SAGE_TESTDIR" -a -n "$SAGE_ROOT" ]; then4 SAGE_TESTDIR="$SAGE_ROOT"/tmp5 fi6 mkdir -p "$SAGE_TESTDIR"7 SAGE_TEST_LOG="$SAGE_TESTDIR/test.log"8 cd "$SAGE_TESTDIR"9 10 cat "$SAGE_ROOT/local/bin/sage-banner" > "$SAGE_TEST_LOG"11 echo `date` >> "$SAGE_TEST_LOG"12 13 # Run doctests on documentation and library, which end with a summary14 # report. (We do this all in a single run of "sage -t" so we get a15 # single summary.) We test doc/common and all subdirectories of doc/16 # whose names consist of two lowercase letters: those should match the17 # various languages.18 19 "$SAGE_ROOT"/sage -t --sagenb "$@" \20 "$SAGE_ROOT"/devel/sage/doc/common \21 "$SAGE_ROOT"/devel/sage/doc/[a-z][a-z] \22 "$SAGE_ROOT"/devel/sage/sage 2>&1 | tee -a "$SAGE_TEST_LOG"23 24 if [ "$SAGE_TESTDIR" = "$SAGE_ROOT/tmp" ]; then25 cat "$SAGE_TEST_LOG" >> "$SAGE_ROOT"/test.log26 fi27 28 echo "Please see $SAGE_TEST_LOG for the complete log from this test."29 30 exit 0 -
deleted file sage-ptest
diff --git a/sage-ptest b/sage-ptest deleted file mode 100755
+ - 1 #!/usr/bin/env python2 3 # Usage: sage -tp N <options> <files>4 #5 # <options> may include6 # --long include lines with the phrase 'long time'7 # --verbose debugging output during the test8 # --optional also test all #optional examples9 # --only-optional <tag1,...,tagn> only run tests including one10 # of the #optional tags11 # --randorder[=seed] randomize order of tests12 #13 # This runs doctests on <files> in parallel using N threads. If N is14 # zero or omitted, set N to the value of the environment variable15 # SAGE_NUM_THREADS_PARALLEL, which is set in sage-env.16 17 import os18 import shutil19 import sys20 import time21 import pickle22 import signal23 import thread24 import tempfile25 import subprocess26 import multiprocessing27 import socket28 import stat29 import re30 31 def usage():32 print """Usage: sage -tp N <options> <files>33 34 <options> may include35 --long include lines with the phrase 'long time'36 --verbose debugging output during the test37 --optional also test all #optional examples38 --only-optional <tag1,...,tagn> only run tests including one39 of the #optional tags40 --randorder[=seed] randomize order of tests41 42 This runs doctests on <files> in parallel using N threads. If N is43 zero or omitted, try to use a sensible default number of threads: if44 the '-j' flag of the environment variable 'MAKE' or 'MAKEFLAGS' is set,45 use that setting. Otherwise, use min(8, number of CPU cores)."""46 47 if len(sys.argv) == 1:48 usage()49 exit(1)50 51 SAGE_ROOT = os.path.realpath(os.environ['SAGE_ROOT'])52 SAGE_SITE = os.path.realpath(os.path.join(os.environ['SAGE_LOCAL'],53 'lib', 'python', 'site-packages'))54 BUILD_DIR = os.path.realpath(os.path.join(SAGE_ROOT, 'devel', 'sage', 'build'))55 56 try:57 XML_RESULTS = os.environ['XML_RESULTS']58 except KeyError:59 XML_RESULTS = None60 61 numiteration = int(os.environ.get('SAGE_TEST_ITER', 1))62 numglobaliteration = int(os.environ.get('SAGE_TEST_GLOBAL_ITER', 1))63 print 'Global iterations: ' + str(numglobaliteration)64 print 'File iterations: ' + str(numiteration)65 66 # Exit status for the whole run.67 err = 068 69 # Check that the current directory is sufficiently safe to run Python70 # code from. We use the check added to Python for this, which gives a71 # warning when the current directory is considered unsafe. We promote72 # this warning to an error with -Werror. See sage/tests/cmdline.py73 # for a doctest that this works, see also Sage Trac #13579.74 import subprocess75 with open(os.devnull, 'w') as dev_null:76 if subprocess.call(['python', '-Werror', '-c', ''], stdout=dev_null, stderr=dev_null) != 0:77 raise RuntimeError("refusing to run doctests from the current "\78 "directory '{}' since untrusted users could put files in "\79 "this directory, making it unsafe to run Sage code from"\80 .format(os.getcwd()))81 82 def strip_automount_prefix(filename):83 """84 Strip prefixes added on automounted filesystems in some cases,85 which make the absolute path appear hidden.86 87 AUTHOR:88 -- Kate Minola89 """90 sep = os.path.sep91 str = filename.split(sep,2)92 if len(str) < 2:93 new = sep94 else:95 new = sep + str[1]96 if os.path.exists(new):97 inode1 = os.stat(filename)[1]98 inode2 = os.stat(new)[1]99 if inode1 == inode2:100 filename = new101 return filename102 103 def abspath(x):104 """105 This function returns the absolute path (adjusted for NFS)106 """107 return strip_automount_prefix(os.path.abspath(x))108 109 def abs_sage_path(f):110 """111 Return the absolute path, relative to the sage root or current directory112 """113 global CUR114 115 abs_path = abspath(f)116 if abs_path.startswith(SAGE_ROOT):117 abs_path = abs_path[len(SAGE_ROOT) + 1:]118 elif abs_path.startswith(CUR):119 abs_path = abs_path[len(CUR) + 1:]120 121 return abs_path122 123 def test_cmd(f):124 """125 Return the test command for the file126 """127 global opts128 return "sage -t %s %s" % (opts, abs_sage_path(f))129 130 def skip(F):131 """132 Returns true if the file should not be tested133 """134 if not os.path.exists(F):135 # XXX IMHO this should never happen; in case it does, it's certainly136 # an error to be reported (either filesystem, or bad name specified137 # on the command line). -leif138 return True139 G = abspath(F)140 i = G.rfind(os.path.sep)141 # XXX The following should IMHO be performed in populatefilelist():142 # (Currently, populatefilelist() only looks for "__nodoctest__".)143 if os.path.exists(os.path.join(G[:i], 'nodoctest.py')):144 printmutex.acquire()145 print "%s (skipping) -- nodoctest.py file in directory" % test_cmd(F)146 sys.stdout.flush()147 printmutex.release()148 return True149 filenm = os.path.split(F)[1]150 if (filenm[0] == '.' or (os.path.sep + '.' in G.lstrip(os.path.sep + '.'))151 or 'nodoctest' in open(G).read()[:50]):152 return True153 if G.find(os.path.join('doc', 'output')) != -1:154 return True155 # XXX The following is (also/already) handled in populatefilelist():156 if not (os.path.splitext(F)[1] in ['.py', '.pyx', '.spyx', '.tex', '.pxi', '.sage', '.rst']):157 return True158 return False159 160 def test_file(F):161 """162 This is the function that actually tests a file163 """164 global opts165 outfile = tempfile.NamedTemporaryFile()166 base, ext = os.path.splitext(F)167 168 cmd = 'doctest ' + opts169 if SAGE_SITE in os.path.realpath(F) and not '-force_lib' in cmd:170 cmd += ' -force_lib'171 172 filestr = os.path.split(F)[1]173 for i in range(0,numiteration):174 os.chdir(os.path.dirname(F))175 command = os.path.join(SAGE_ROOT, 'local', 'bin', 'sage-%s' % cmd)176 # FIXME: Why call bash here? (Also, we use 'shell=True' below anyway.)177 s = 'bash -c "%s %s > %s" ' % (command, filestr, outfile.name)178 try:179 t = time.time()180 ret = subprocess.call(s, shell=True)181 finished_time = time.time() - t182 except:183 ol = outfile.read()184 return (F, 32, 0, ol)185 ol = outfile.read()186 if ret != 0:187 break188 return (F, ret, finished_time, ol)189 190 def process_result(result):191 """192 This file takes a tuple in the form193 (F, ret, finished_time, ol)194 and processes it to display/log the appropriate output.195 """196 global err, failed, time_dict197 F = result[0]198 ret = result[1]199 finished_time = result[2]200 ol = result[3]201 err = err | ret202 if ret != 0:203 if ret == 128:204 numfail = ol.count('Expected:') + ol.count('Expected nothing') + ol.count('Exception raised:')205 failed.append(test_cmd(F) + (" # %s doctests failed" % numfail))206 ret = numfail207 elif ret == 64:208 failed.append(test_cmd(F) + " # Time out")209 elif ret == 8:210 failed.append(test_cmd(F) + " # Exception from doctest framework")211 elif ret == 4:212 failed.append(test_cmd(F) + " # Killed/crashed")213 elif ret == 2:214 failed.append(test_cmd(F) + " # KeyboardInterrupt")215 elif ret == 1:216 failed.append(test_cmd(F) + " # File not found")217 else:218 failed.append(test_cmd(F))219 220 print test_cmd(F)221 sys.stdout.flush()222 223 if ol!="" and (not ol.isspace()):224 if (ol[len(ol)-1]=="\n"):225 ol=ol[0:len(ol)-1]226 print ol227 sys.stdout.flush()228 time_dict[abs_sage_path(F)] = finished_time229 if XML_RESULTS:230 t = finished_time231 failures = int(ret == 128)232 errors = int(ret and not failures)233 path = F.split(os.path.sep)234 while 'sage' in path:235 path = path[path.index('sage')+1:]236 path[-1] = os.path.splitext(path[-1])[0]237 module = '.'.join(path)238 if (failures or errors) and '#' in failed[-1]:239 type = "error" if errors else "failure"240 cause = failed[-1].split('#')[-1].strip()241 failure_item = "<%s type='%s'>%s</%s>" % (type, cause, cause, type)242 else:243 failure_item = ""244 f = open(os.path.join(XML_RESULTS, module + '.xml'), 'w')245 f.write("""246 <?xml version="1.0" ?>247 <testsuite name="%(module)s" errors="%(errors)s" failures="%(failures)s" tests="1" time="%(t)s">248 <testcase classname="%(module)s" name="test">249 %(failure_item)s250 </testcase>251 </testsuite>252 """.strip() % locals())253 f.close()254 print "\t [%.1f s]"%(finished_time)255 sys.stdout.flush()256 257 def infiles_cmp(a,b):258 """259 This compare function is used to sort the list of filenames by the time they take to run260 """261 global time_dict262 if time_dict.has_key(abs_sage_path(a)):263 if time_dict.has_key(abs_sage_path(b)):264 return cmp(time_dict[abs_sage_path(a)],time_dict[abs_sage_path(b)])265 else:266 return 1267 else:268 return -1269 270 def populatefilelist(filelist):271 """272 This populates the file list by expanding directories into lists of files273 """274 global CUR275 filemutex.acquire()276 for FF in filelist:277 if os.path.isfile(FF):278 if skip(FF):279 continue280 if not os.path.isabs(FF):281 cwd = os.getcwd()282 files.append(os.path.join(cwd, FF))283 else:284 files.append(FF)285 continue286 287 curdir = os.getcwd()288 walkdir = os.path.join(CUR,FF)289 290 for root, dirs, lfiles in os.walk(walkdir):291 for F in lfiles:292 base, ext = os.path.splitext(F)293 if not (ext in ['.sage', '.py', '.pyx', '.spyx', '.tex', '.pxi', '.rst']):294 continue295 elif '__nodoctest__' in files:296 # XXX Shouldn't this be 'lfiles'?297 # Also, this test should IMHO be in the outer loop (1 level).298 # Furthermore, the current practice is to put "nodoctest.py"299 # files in the directories that should be skipped, not300 # "__nodoctest__". (I haven't found a single instance of the301 # latter in Sage 4.6.1.alpha3.)302 # "nodoctest.py" is handled in skip() (!), to also be fixed.303 # -leif304 continue305 appendstr = os.path.join(root,F)306 if skip(appendstr):307 continue308 if os.path.realpath(appendstr).startswith(BUILD_DIR):309 continue310 files.append(appendstr)311 for D in dirs:312 if '#' in D or (os.path.sep + 'notes' in D):313 dirs.remove(D)314 filemutex.release()315 return 0316 317 for gr in range(0,numglobaliteration):318 argv = sys.argv319 opts = ' '.join([X for X in argv if X[0] == '-'])320 argv = [X for X in argv if X[0] != '-']321 322 try:323 numthreads = int(argv[1])324 infiles = argv[2:]325 except ValueError:326 # can't convert first arg to an integer: arg was probably omitted327 numthreads = 0328 infiles = argv[1:]329 330 if '-sagenb' in opts:331 opts = opts.replace('--sagenb', '').replace('-sagenb', '')332 333 # Find SageNB's home.334 from pkg_resources import Requirement, working_set335 sagenb_loc = working_set.find(Requirement.parse('sagenb')).location336 337 # In case we're using setuptools' "develop" mode.338 if not SAGE_SITE in sagenb_loc:339 opts += ' -force_lib'340 341 infiles.append(os.path.join(sagenb_loc, 'sagenb'))342 343 verbose = ('-verbose' in opts or '--verbose' in opts)344 345 if numthreads == 0:346 try:347 numthreads = int(os.environ['SAGE_NUM_THREADS_PARALLEL'])348 except KeyError:349 numthreads = 1350 351 if numthreads < 1 or len(infiles) == 0:352 if numthreads < 1:353 print "Usage: sage -tp <numthreads> <files or directories>: <numthreads> must be non-negative."354 else:355 print "Usage: sage -tp <numthreads> <files or directories>: no files or directories specified."356 print "For more information, type 'sage --advanced'."357 sys.exit(1)358 359 infiles.sort()360 361 files = list()362 363 t0 = time.time()364 filemutex = thread.allocate_lock()365 printmutex = thread.allocate_lock()366 SAGE_TESTDIR = os.environ['SAGE_TESTDIR']367 #Pick a filename for the timing files -- long vs normal368 # TODO: perhaps these files shouldn't be hidden? Also, don't369 # store them in SAGE_TESTDIR, in case the user wants to test in370 # some temporary directory: store them somewhere more permanent.371 if opts.count("-long"):372 time_file_name = os.path.join(SAGE_TESTDIR,373 '.ptest_timing_long')374 else:375 time_file_name = os.path.join(SAGE_TESTDIR,376 '.ptest_timing')377 time_dict = { }378 try:379 with open(time_file_name) as time_file:380 time_dict = pickle.load(time_file)381 if opts.count("-long"):382 print "Using long cached timings to run longest doctests first."383 else:384 print "Using cached timings to run longest doctests first."385 from copy import copy386 time_dict_old = copy(time_dict)387 except:388 time_dict = { }389 if opts.count("-long"):390 print "No long cached timings exist; will create for successful files."391 else:392 print "No cached timings exist; will create for successful files."393 time_dict_old = None394 done = False395 396 CUR = abspath(os.getcwd())397 398 failed = []399 400 HOSTNAME = socket.gethostname().replace('-','_').replace('/','_').replace('\\','_')401 # Should TMP be a subdirectory of tempfile.gettempdir() rather than SAGE_TESTDIR?402 TMP = os.path.join(SAGE_TESTDIR, '%s-%s' % (HOSTNAME, os.getpid()))403 TMP = os.path.abspath(TMP)404 try:405 os.makedirs(TMP)406 except OSError:407 # If TMP already exists, remove it and re-create it408 if os.path.isdir(TMP):409 shutil.rmtree(TMP)410 os.makedirs(TMP)411 else:412 raise413 414 # Add rwx permissions for user to TMP:415 os.chmod(TMP, os.stat(TMP)[0] | stat.S_IRWXU)416 os.environ['SAGE_TESTDIR'] = TMP417 if verbose:418 print419 print "Using the directory"420 print " '%s'." % TMP421 print "for doctesting. If all doctests pass, this directory will"422 print "be deleted automatically."423 print424 425 populatefilelist(infiles)426 #Sort the files by test time427 files.sort(infiles_cmp)428 files.reverse()429 interrupt = False430 431 numthreads = min(numthreads, len(files)) # don't use more threads than files432 433 if len(files) == 1:434 file_str = "1 file"435 else:436 file_str = "%i files" % len(files)437 if numthreads == 1:438 jobs_str = "using 1 thread" # not in parallel if numthreads is 1439 else:440 jobs_str = "doing %s jobs in parallel" % numthreads441 442 print "Doctesting %s %s" % (file_str, jobs_str)443 444 try:445 p = multiprocessing.Pool(numthreads)446 for r in p.imap_unordered(test_file, files):447 #The format is (F, ret, finished_time, ol)448 process_result(r)449 except KeyboardInterrupt:450 err = err | 2451 interrupt = True452 pass453 print " "454 print "-"*int(70)455 456 os.chdir(CUR)457 458 if verbose:459 print460 print "Removing the directory '%s'." % TMP461 try:462 os.rmdir(TMP)463 if verbose:464 print465 print "-"*int(70)466 except OSError:467 # TODO (probably in sage-doctest): if tests were interrupted468 # but there were no failures in the interrupted files, delete469 # the temporary files, so that this directory is empty.470 print "The temporary doctesting directory"471 print " %s" % TMP472 print "was not removed: it is not empty, presumably because doctests"473 print "failed or doctesting was interrupted."474 print475 print "-"*int(70)476 477 if len(failed) == 0:478 if interrupt == False:479 print "All tests passed!"480 else:481 print "Keyboard Interrupt: All tests that ran passed."482 else:483 if interrupt:484 print "Keyboard Interrupt, not all tests ran"485 elif opts=="-long" or len(opts)==0:486 time_dict_ran = time_dict487 time_dict = { }488 failed_files = { }489 if opts=="-long":490 for F in failed:491 failed_files[F.split('#')[0].split()[3]] = None492 else:493 for F in failed:494 failed_files[F.split('#')[0].split()[2]] = None495 for F in time_dict_ran:496 if F not in failed_files:497 time_dict[F] = time_dict_ran[F]498 if time_dict_old is not None:499 for F in time_dict_old:500 if F not in time_dict:501 time_dict[F] = time_dict_old[F]502 print "\nThe following tests failed:\n"503 for i in range(len(failed)):504 print "\t", failed[i]505 print "-"*int(70)506 507 #Only update timings if we are doing something standard508 opts = opts.strip()509 if (opts=="-long" or len(opts)==0) and not interrupt:510 with open(time_file_name,"w") as time_file:511 pickle.dump(time_dict, time_file)512 print "Timings have been updated."513 514 print "Total time for all tests: %.1f seconds"%(time.time() - t0)515 516 sys.exit(err) -
new file sage-runtests
diff --git a/sage-runtests b/sage-runtests new file mode 100755
- + 1 #!/usr/bin/env python 2 3 import optparse, os, sys 4 5 if __name__ == "__main__": 6 parser = optparse.OptionParser() 7 8 def nthreads_callback(option, opt_str, value, parser): 9 assert value is None 10 if parser.rargs: # there are more arguments 11 try: 12 next_arg = int(parser.rargs[0]) 13 parser.rargs.pop(0) 14 except ValueError: 15 # No explicit number of threads passed 16 next_arg = 0 17 else: 18 next_arg = 0 19 parser.values.nthreads = next_arg 20 21 parser.add_option("-p", "--nthreads", action="callback", callback=nthreads_callback, nargs=0, metavar="N", help="tests in parallel using N threads with 0 interpreted as minimum(8, cpu_count())") 22 parser.add_option("--serial", action="store_true", default=False, help="run tests in a single process in series") 23 parser.add_option("-T", "--timeout", type=int, default=-1, help="timeout (in seconds) for doctesting one file") 24 parser.add_option("-a", "--all", action="store_true", default=False, help="test all files in the Sage library") 25 parser.add_option("--logfile", metavar="FILE", help="log all output to FILE") 26 parser.add_option("--sagenb", action="store_true", default=False, help="test all sagenb files") 27 28 parser.add_option("--long", action="store_true", default=False, help="include lines with the phrase 'long time'") 29 parser.add_option("--optional", metavar="OPTIONAL_PKGS", default="sage", \ 30 help="only run tests including one of the #optional tags listed in OPTIONAL_PKGS; if 'sage' is listed will also test the standard doctests; if OPTIONAL_PKGS='all' all tests will be run") 31 parser.add_option("--randorder", type=int, metavar="SEED", help="randomize order of tests") 32 parser.add_option("--global-iterations", "--global_iterations", type=int, default=0, help="repeat the whole testing process this many times") 33 parser.add_option("--file-iterations", "--file_iterations", type=int, default=0, help="repeat each file this many times, stopping on the first failure") 34 35 parser.add_option("-i", "--initial", action="store_true", default=False, help="only show the first failure in each file") 36 parser.add_option("--force_lib", "--force-lib", action="store_true", default=False, help="assume all files are Sage library files, regardless of location, ie don't import anything from the file tested") 37 parser.add_option("--abspath", action="store_true", default=False, help="print absolute paths rather than relative paths") 38 parser.add_option("--verbose", action="store_true", default=False, help="print debugging output during the test") 39 parser.add_option("-d", "--debug", action="store_true", default=False, help="drop into a python debugger when an unexpected error is raised") 40 41 parser.add_option("--gdb", action="store_true", default=False, help="run doctests under the control of gdb") 42 parser.add_option("--valgrind", "--memcheck", action="store_true", default=False, 43 help="run doctests using Valgrind's memcheck tool. The log " + \ 44 "files are named sage-memcheck.PID and can be found in " + \ 45 os.path.join(os.environ["DOT_SAGE"], "valgrind")) 46 parser.add_option("--massif", action="store_true", default=False, 47 help="run doctests using Valgrind's massif tool. The log " + \ 48 "files are named sage-massif.PID and can be found in " + \ 49 os.path.join(os.environ["DOT_SAGE"], "valgrind")) 50 parser.add_option("--cachegrind", action="store_true", default=False, 51 help="run doctests using Valgrind's cachegrind tool. The log " + \ 52 "files are named sage-cachegrind.PID and can be found in " + \ 53 os.path.join(os.environ["DOT_SAGE"], "valgrind")) 54 parser.add_option("--omega", action="store_true", default=False, 55 help="run doctests using Valgrind's omega tool. The log " + \ 56 "files are named sage-omega.PID and can be found in " + \ 57 os.path.join(os.environ["DOT_SAGE"], "valgrind")) 58 59 parser.add_option("-f", "--failed", action="store_true", default=False, \ 60 help="doctest only those files that failed in the previous run") 61 parser.add_option("--new", action="store_true", default=False, help="doctest only those files that have been changed in the repository and not yet been committed") 62 63 parser.add_option("--stats_path", "--stats-path", default=os.path.join(os.path.expanduser("~/.sage/timings2.json")), \ 64 help="path to a json dictionary for the latest run storing a timing for each file") 65 66 parser.set_usage("sage -t [options] filenames") 67 from sage.doctest.control import DocTestController 68 options, args = parser.parse_args() 69 if len(args) == 0 and not (options.all or options.sagenb or options.new): 70 parser.print_help() 71 sys.exit(8) 72 else: 73 DC = DocTestController(*parser.parse_args()) 74 err = DC.run() 75 76 # We use os._exit rather then sys.exit since sys.exit wasn't 77 # completely quitting on sage.math after a KeyboardInterrupt 78 os._exit(err) -
deleted file sage-test
diff --git a/sage-test b/sage-test deleted file mode 100755
+ - 1 #!/usr/bin/env python2 3 ####################################################################4 # Run the Sage doctest system on the collection of files and5 # directories specified on the command line.6 7 ####################################################################8 9 import os, signal, sys, time10 11 12 argv = sys.argv13 14 opts = ' '.join([X for X in argv if X[0] == '-'])15 argv = [X for X in argv if X[0] != '-']16 17 t0 = time.time()18 19 from os.path import abspath20 21 SAGE_ROOT = os.environ['SAGE_ROOT']22 SAGE_SITE = os.path.realpath(os.path.join(os.environ['SAGE_LOCAL'],23 'lib', 'python', 'site-packages'))24 25 try:26 XML_RESULTS = os.environ['XML_RESULTS']27 except KeyError:28 XML_RESULTS = None29 30 31 # TODO: should we set SAGE_TESTDIR to a temporary directory like32 # tempfile.gettempdir()?33 if 'SAGE_TESTDIR' not in os.environ:34 os.environ['SAGE_TESTDIR'] = os.path.join(SAGE_ROOT, "tmp")35 SAGE_TESTDIR = os.environ['SAGE_TESTDIR']36 try:37 os.makedirs(SAGE_TESTDIR)38 except OSError:39 if not os.path.isdir(SAGE_TESTDIR):40 raise41 42 # Check that the current directory is sufficiently safe to run Python43 # code from. We use the check added to Python for this, which gives a44 # warning when the current directory is considered unsafe. We promote45 # this warning to an error with -Werror. See sage/tests/cmdline.py46 # for a doctest that this works, see also Sage Trac #13579.47 import subprocess48 with open(os.devnull, 'w') as dev_null:49 if subprocess.call(['python', '-Werror', '-c', ''], stdout=dev_null, stderr=dev_null) != 0:50 raise RuntimeError("refusing to run doctests from the current "\51 "directory '{}' since untrusted users could put files in "\52 "this directory, making it unsafe to run Sage code from"\53 .format(os.getcwd()))54 55 def sage_test_command(f):56 g = abspath(f)57 if SAGE_ROOT in g:58 f = g[g.find(SAGE_ROOT)+len(SAGE_ROOT)+1:]59 return 'sage -t %s "%s"'%(opts, f)60 61 def skip(F):62 G = abspath(F)63 i = G.rfind(os.sep)64 if os.path.exists(os.path.join(G[:i], 'nodoctest.py')):65 print "%s (skipping) -- nodoctest.py file in directory"%sage_test_command(F)66 return True67 68 if 'nodoctest' in open(G).read()[:50]:69 return True70 if G.find(os.path.join('doc', 'output')) != -1:71 return True72 73 sys.stdout.write("%-60s"%sage_test_command(F)+"\n")74 sys.stdout.flush()75 return False76 77 failed = []78 79 def test(F, cmd):80 from subprocess import call81 t = time.time()82 if skip(F):83 return 084 s = os.path.join(SAGE_ROOT, 'local', 'bin', 'sage-%s' % cmd) + ' "%s"' % F85 err = call(s, shell=True)86 87 # Check the process exit code that sage-doctest returns88 89 if err == 1: # process exit code 1: File not found90 failed.append(sage_test_command(F)+" # File not found")91 elif err == 2: # process exit code 2: KeyboardInterrupt92 failed.append(sage_test_command(F)+" # KeyboardInterrupt")93 raise KeyboardInterrupt94 elif err == 4: # process exit code 4: Terminated by signal95 failed.append(sage_test_command(F)+" # Killed/crashed")96 elif err == 8: # process exit code 8: Unhandled doctest exception97 failed.append(sage_test_command(F)+" # Exception from doctest framework")98 elif err == 64: # process exit code 64: Time out99 failed.append(sage_test_command(F)+" # Time out")100 elif err == 128: # process exit code 128: Regular doctest failures101 failed.append(sage_test_command(F))102 elif err != 0:103 failed.append(sage_test_command(F))104 t = time.time() - t105 print "\t [%.1f s]" % t106 if XML_RESULTS:107 failures = int(err == 128)108 errors = int(err and not failures)109 path = F.split(os.path.sep)110 while 'sage' in path:111 path = path[path.index('sage')+1:]112 path[-1] = os.path.splitext(path[-1])[0]113 module = '.'.join(path)114 if errors and '#' in failed[-1]:115 cause = failed[-1].split('#')[-1].strip()116 failure_item = "<error type='%s'>%s</error>" % (cause, cause)117 else:118 failure_item = ""119 f = open(os.path.join(XML_RESULTS, module + '.xml'), 'w')120 f.write("""121 <?xml version="1.0" ?>122 <testsuite name="%(module)s" errors="%(errors)s" failures="%(failures)s" tests="1" time="%(t)s">123 <testcase classname="%(module)s" name="test">124 %(failure_item)s125 </testcase>126 </testsuite>127 """.strip() % locals())128 f.close()129 return err130 131 132 def test_file(F):133 if not os.path.exists(F):134 if os.path.exists(os.path.join(SAGE_ROOT, F)):135 F = os.path.join(SAGE_ROOT, F)136 if not os.path.exists(F):137 if F[:6] != "__test" and not F.endswith('.png'):138 print "ERROR: File %s is missing" % os.path.join(os.curdir, F)139 failed.append(os.path.join(os.curdir, F) + " # File not found")140 return 1141 142 extra_opts = ''143 if SAGE_SITE in os.path.realpath(F) and not '-force_lib' in opts:144 extra_opts = ' -force_lib'145 146 base, ext = os.path.splitext(F)147 err = 0148 if ext in ['.py', '.spyx', '.pyx', '.tex', '.pxi', '.sage', '.rst']:149 err = err | test(F, 'doctest ' + opts + extra_opts)150 elif (os.path.isdir(F) and not '#' in F and151 not os.sep + 'notes' in F):152 ld = os.listdir(F)153 if not ('__nodoctest__' in ld):154 for L in ld:155 err = err | test_file(os.path.join(F, L))156 return err157 158 files = argv[1:]159 160 if '-sagenb' in opts:161 opts = opts.replace('--sagenb', '').replace('-sagenb', '')162 163 # Find SageNB's home.164 from pkg_resources import Requirement, working_set165 sagenb_loc = working_set.find(Requirement.parse('sagenb')).location166 167 # In case we're using setuptools' "develop" mode.168 if not SAGE_SITE in sagenb_loc:169 opts += ' -force_lib'170 171 files.append(os.path.join(sagenb_loc, 'sagenb'))172 173 if len(files) == 0:174 print "Usage: sage -t [-verbose] [-long] [-optional] [-only-optional=list,of,tags] <files or directories>"175 print "Test the docstrings in each listed Python or Cython file, and "176 print "if a directory is given, test the docstrings in all files in"177 print "all subdirectories of that directory."178 print ""179 print "OPTIONS:"180 print " -force_lib -- assume all files are Sage library files,"181 print " regardless of location"182 print " -long -- include lines with the phrase 'long time'"183 print " -verbose -- print debugging output during the test"184 print " -optional -- also test all #optional examples"185 print " -only-optional=list,of,tags -- only run doctests with "186 print " #optional <nonempty subset of tags>"187 print " if the list of tags is omitted, run all optional doctests"188 print " -randorder -- if given, randomize *order* of tests"189 print " -randorder=seed-- use seed to get same random order"190 print " -sagenb -- test all sagenb files"191 192 sys.exit(1)193 194 files.sort()195 196 err = 0197 for F in files:198 try:199 err = err | test_file(F)200 except KeyboardInterrupt:201 print "Aborting further tests."202 err = err | 2203 break204 205 print " "206 print "-"*int(70)207 208 if len(failed) == 0:209 print "All tests passed!"210 else:211 print "The following tests failed:\n"212 print "\n\t" + "\n\t".join(failed)213 214 print "Total time for all tests: %.1f seconds"%(time.time() - t0)215 sys.exit(err) -
deleted file sage-test-new
diff --git a/sage-test-new b/sage-test-new deleted file mode 100755
+ - 1 #!/usr/bin/env python2 3 import os4 5 print "Testing all files that have changed in the repository"6 print "that have not yet been commited using 'hg_sage.commit()'."7 8 os.chdir('%s/devel/sage'%os.environ['SAGE_ROOT'])9 10 v = os.popen('hg status').read()11 print v12 F = []13 for X in v.split('\n'):14 try:15 c, filename = X.split()16 except:17 break18 if c == 'M':19 F.append(filename)20 21 if len(F) == 0:22 print "No files to test."23 else:24 cmd = 'sage -t %s'%(' '.join([str(x) for x in F]))25 print cmd26 os.system(cmd)27