Ticket #9739: trac_9739.patch

File trac_9739.patch, 16.7 KB (added by jhpalmieri, 9 years ago)

scripts repo

  • sage-doctest

    # HG changeset patch
    # User Mitesh Patel <qed777@gmail.com>, J. H. Palmieri <palmieri@math.washington.edu>
    # Date 1283582692 25200
    # Node ID db0b93a121b3dc95d93144f55d09f701939fa9e5
    # Parent  9bfb3d088a9f591eb4d23aa12ec788ac9217b2b7
    #9739: Run doctests with unique temporary files, other small improvements
    
    Other improvements:
    
     * Make 'sage -tp' test .spyx files, too.
    
     * Better handling of when to pickle timings.
    
     * Use a better test command string in 'sage -tp'.  This also makes it
       more likely that we'll pickle reusable timings.
    
    diff --git a/sage-doctest b/sage-doctest
    a b if 'SAGE_TESTDIR' not in os.environ or o 
    7777else:
    7878    SAGE_TESTDIR = os.environ['SAGE_TESTDIR']
    7979
     80def filename_mangler(name):
     81    """
     82    Replace ``name`` with 'doctest_PID_CWD-name.py', where PID is the
     83    process ID and CWD is the current working directory, which should
     84    be the directory containing ``name``, but with all slashes
     85    replaced by periods (to avoid name clashes between files from
     86    different directories with the same name).
     87    """
     88    new = os.path.join(os.getcwd(), name).replace(os.path.sep, '.')
     89    new = new.lstrip('.') # remove leading '.'
     90    return os.path.join(SAGE_TESTDIR,
     91                        "doctest_%s_%s" % (os.getpid(), new))
     92
    8093tmpfiles = [] # list of temporary files to be deleted if doctesting succeeds
    8194
    8295def delete_tmpfiles():
    def extract_doc(file_name, library_code= 
    409422   
    410423    F = F.replace('\'"""\'','')
    411424
    412     base, ext = os.path.splitext(file_name)
    413     name = os.path.basename(base)
     425    root_name, ext = os.path.splitext(file_name)
    414426    if ext == ".tex":
    415427        F = pythonify_tex(F)
    416428    elif ext == ".rst":
    def change_warning_output(file): 
    436448"""
    437449
    438450    if not library_code:
    439         if ext in ['.py', '.pyx','.spyx']:
    440             os.system('cp -f %s %s' % (file_name, SAGE_TESTDIR))
    441             tmpfiles.append(os.path.join(SAGE_TESTDIR, '%s%s' % (name, ext)))
    442             if ext == '.py':
    443                 s += "from %s import *\n\n" % name
     451        if ext in ['.pyx','.spyx']:
     452            s += "cython(open('%s').read())\n\n" % file_name
     453
     454        elif ext in ['.py', '.sage']:
     455
     456            target_name = "%s_%d" % (file_name, os.getpid()) # like 'name', but unique
     457            target_base = os.path.join(SAGE_TESTDIR, target_name) # like 'base', also unique
     458
     459            if ext == '.sage':
     460                # TODO: preparse "<file>.sage" with a Sage library call
     461                # instead and write a string into temp_name.
     462
     463                # For now: "sage -preparse <file>.sage" doesn't have any
     464                # output options and always creates <file>.py in the same
     465                # directory, so we first copy the *source* into SAGE_TESTDIR:
     466                os.system("cp '%s' %s.sage" % (file_name, target_base))
     467                # Now create SAGE_TESTDIR/<target_name>.py:
     468                os.system("sage -preparse %s.sage" % target_base)
     469                tmpfiles.append(target_base + ".sage")
    444470            else:
    445                 s += "cython(open('%s').read())\n\n" % file_name
    446         elif ext == '.sage':
    447             os.system('sage -preparse %s' % file_name)
    448             os.system('mv -f %s.py %s' % (base, SAGE_TESTDIR))
    449             tmpfiles.append(os.path.join(SAGE_TESTDIR, name + '.py'))
    450             s += "from %s import *\n\n" % (name)
    451         if ext in ['.py', '.sage']:
    452             tmpfiles.append(os.path.join(SAGE_TESTDIR, name + '.pyc'))
     471                # TODO: instead of copying the file, add its source
     472                # directory to PYTHONPATH.  We would also have to
     473                # import from 'name' instead of 'target_name'.
     474                os.system("cp '%s' %s.py" % (source, target_base))
     475
     476            s += "from %s import *\n\n" % target_name
     477
     478            tmpfiles.append(target_base + ".py") # preparsed or copied original
     479            tmpfiles.append(target_base + ".pyc") # compiled version of it
    453480
    454481    # Prefix/suffix for all doctests replacing the starting/ending """
    455482    doc_prefix = 'r""">>> set_random_seed(0L)\n\n>>> change_warning_output(sys.stdout)\n\n'
    def change_warning_output(file): 
    459486    while True:
    460487        i = F.find('"""')
    461488        if i == -1: break
    462         name = "example"       
    463489        k = F[i+3:].find('"""')
    464490        if k == -1: break
    465491        j = i+3 + k
    def change_warning_output(file): 
    473499                n_str = pad_zeros(new_index(n),10)
    474500            else:
    475501                n_str = str(n)
    476             s += "def %s_%s():"%(name,n_str)
     502            s += "def example_%s():" % n_str
    477503            n += 1
    478504            s += "\t"+ doc + "\n\n"
    479505        F = F[j+3:]
    def post_process(s, file, tmpname): 
    624650
    625651def test_file(file, library_code):
    626652    if os.path.exists(file):
     653        s = extract_doc(file, library_code=library_code)
     654        if len(s) == 0:
     655            delete_tmpfiles()
     656            sys.exit(0)
     657
    627658        name = os.path.basename(file)
    628659        name = name[:name.find(".")]
    629         s = extract_doc(file, library_code=library_code)
    630         if len(s) == 0:
    631             sys.exit(0)
    632 
    633         f = os.path.join(SAGE_TESTDIR, ".doctest_%s.py" % name)
     660        f = os.path.splitext(filename_mangler(file))[0] + '.py'
    634661
    635662        open(f,"w").write(s)
    636663        tmpfiles.append(f)
     664
    637665        cmd = "%s %s"%(PYTHON, f)
    638666        if gdb:
    639667            print "*"*80
    def test_file(file, library_code): 
    681709                out = outf.read()
    682710                e = proc.poll()
    683711        except KeyboardInterrupt:
     712            # TODO: if tests were interrupted but there were no failures, delete tmpfiles.
    684713            print "KeyboardInterrupt -- interrupted after %.1f seconds!" % (time.time()-tm)
    685714            sys.exit(2)
    686715        if 'raise KeyboardInterrupt' in err:
     716            # TODO: if tests were interrupted but there were no failures, delete tmpfiles.
    687717            print "*"*80 + "Control-C pressed -- interrupting doctests." + "*"*80
    688718            sys.exit(2)
    689719
  • sage-env

    diff --git a/sage-env b/sage-env
    a b if [ -z "${SAGE_ORIG_LD_LIBRARY_PATH_SET 
    191191    SAGE_ORIG_LD_LIBRARY_PATH_SET=True && export SAGE_ORIG_LD_LIBRARY_PATH_SET
    192192fi
    193193
     194# Should this be a temporary directory instead?  Since sage-ptest and
     195# sage-test are written in Python, we can set it to
     196# tempfile.gettempdir() there.
    194197if [ -z "$SAGE_TESTDIR" ]; then
    195198    SAGE_TESTDIR="$DOT_SAGE"/tmp && export SAGE_TESTDIR
    196199fi
  • sage-ptest

    diff --git a/sage-ptest b/sage-ptest
    a b import thread 
    1010import tempfile
    1111import subprocess
    1212import multiprocessing
     13import socket
     14import stat
    1315
    14 SAGE_ROOT = os.environ['SAGE_ROOT']
     16SAGE_ROOT = os.path.realpath(os.environ['SAGE_ROOT'])
    1517SAGE_SITE = os.path.realpath(os.path.join(os.environ['SAGE_LOCAL'],
    1618                                          'lib', 'python', 'site-packages'))
    1719BUILD_DIR = os.path.realpath(os.path.join(SAGE_ROOT, 'devel', 'sage', 'build'))
    print 'File iterations: ' + str(numitera 
    2931# Exit status for the whole run.
    3032err = 0
    3133
    32 def abspath(x):
    33     """
    34     This function returns the absolute path (adjusted for NFS)
    35     """
    36     return strip_automount_prefix(os.path.abspath(x))
    37 
    3834def strip_automount_prefix(filename):
    3935    """
    4036    Strip prefixes added on automounted filesystems in some cases,
    def strip_automount_prefix(filename): 
    5652            filename = new
    5753    return filename
    5854
    59 def abs(f):
     55def abspath(x):
     56    """
     57    This function returns the absolute path (adjusted for NFS)
     58    """
     59    return strip_automount_prefix(os.path.abspath(x))
     60
     61def abs_sage_path(f):
     62    """
     63    Return the absolute path, relative to the sage root or current directory
     64    """
     65    global CUR
     66
     67    abs_path = abspath(f)
     68    if abs_path.startswith(SAGE_ROOT):
     69        abs_path = abs_path[len(SAGE_ROOT) + 1:]
     70    elif abs_path.startswith(CUR):
     71        abs_path = abs_path[len(CUR) + 1:]
     72
     73    return abs_path
     74
     75def test_cmd(f):
    6076    """
    6177    Return the test command for the file
    6278    """
    6379    global opts
    64     return "sage -t %s %s"%(opts, abspath(f)[len(SAGE_ROOT)+1:])
    65 
    66 def sage_test_cmd(f):
    67     """
    68     Return the test command for the file as given
    69     """
    70     global opts
    71     return "sage -t %s %s"%(opts, f)
    72 
    73 def abs_sage_path(f):
    74     """
    75     Return the absolute path, relative to the sage root directory
    76     """
    77     return abspath(f)[len(SAGE_ROOT)+1:]
     80    return "sage -t %s %s" % (opts, abs_sage_path(f))
    7881
    7982def skip(F):
    8083    """
    8184    Returns true if the file should not be tested
    8285    """
    8386    if not os.path.exists(F):
     87        # XXX IMHO this should never happen; in case it does, it's certainly
     88        #     an error to be reported (either filesystem, or bad name specified
     89        #     on the command line). -leif
    8490        return True
    8591    G = abspath(F)
    8692    i = G.rfind(os.path.sep)
     93    # XXX The following should IMHO be performed in populatefilelist():
     94    #     (Currently, populatefilelist() only looks for "__nodoctest__".)
    8795    if os.path.exists(os.path.join(G[:i], 'nodoctest.py')):
    8896        printmutex.acquire()
    89         print "%s (skipping) -- nodoctest.py file in directory"%abs(F)
     97        print "%s (skipping) -- nodoctest.py file in directory" % test_cmd(F)
     98        sys.stdout.flush()
    9099        printmutex.release()
    91100        return True
    92101    filenm = os.path.split(F)[1]
    def skip(F): 
    95104        return True
    96105    if G.find(os.path.join('doc', 'output')) != -1:
    97106        return True
    98     if not (os.path.splitext(F)[1] in ['.py', '.pyx', '.tex', '.pxi', '.sage', '.rst']):
     107    # XXX The following is (also/already) handled in populatefilelist():
     108    if not (os.path.splitext(F)[1] in ['.py', '.pyx', '.spyx', '.tex', '.pxi', '.sage', '.rst']):
    99109        return True
    100110    return False
    101111
    def test_file(F): 
    115125    for i in range(0,numiteration):
    116126        os.chdir(os.path.dirname(F))
    117127        command = os.path.join(SAGE_ROOT, 'local', 'bin', 'sage-%s' % cmd)
     128        # FIXME: Why call bash here? (Also, we use 'shell=True' below anyway.)
    118129        s = 'bash -c "%s %s > %s" ' % (command, filestr, outfile.name)
    119130        try:
    120131            t = time.time()
    def process_result(result): 
    143154    if ret != 0:
    144155        if ret == 128:
    145156            numfail = ol.count('Expected:') + ol.count('Expected nothing') + ol.count('Exception raised:')
    146             failed.append(abs(F)+(" # %s doctests failed" % numfail))
     157            failed.append(test_cmd(F) + (" # %s doctests failed" % numfail))
    147158            ret = numfail
    148159        elif ret == 64:
    149             failed.append(abs(F)+" # Time out")
     160            failed.append(test_cmd(F) + " # Time out")
    150161        elif ret == 8:
    151             failed.append(abs(F)+" # Exception from doctest framework")
     162            failed.append(test_cmd(F) + " # Exception from doctest framework")
    152163        elif ret == 4:
    153             failed.append(abs(F)+" # Killed/crashed")
     164            failed.append(test_cmd(F) + " # Killed/crashed")
    154165        elif ret == 2:
    155             failed.append(abs(F)+" # KeyboardInterrupt")
     166            failed.append(test_cmd(F) + " # KeyboardInterrupt")
    156167        elif ret == 1:
    157             failed.append(abs(F)+" # File not found")
     168            failed.append(test_cmd(F) + " # File not found")
    158169        else:
    159             failed.append(abs(F))
    160     if abspath(F)[:len(CUR)]==CUR:
    161         print sage_test_cmd(F[len(CUR)+1:])
    162     else:
    163         print abs(F)
     170            failed.append(test_cmd(F))
     171
     172    print test_cmd(F)
     173    sys.stdout.flush()
     174
    164175    if ol!="" and (not ol.isspace()):
    165176        if (ol[len(ol)-1]=="\n"):
    166177            ol=ol[0:len(ol)-1]
    167178        print ol
     179        sys.stdout.flush()
    168180    time_dict[abs_sage_path(F)] = finished_time
    169181    if XML_RESULTS:
    170182        t = finished_time
    def process_result(result): 
    192204        """.strip() % locals())
    193205        f.close()
    194206    print "\t [%.1f s]"%(finished_time)
     207    sys.stdout.flush()
    195208
    196209def infiles_cmp(a,b):
    197210    """
    def populatefilelist(filelist): 
    229242        for root, dirs, lfiles in os.walk(walkdir):
    230243            for F in lfiles:
    231244                base, ext = os.path.splitext(F)
    232                 if not (ext in ['.sage', '.py', '.pyx', '.tex', '.pxi', '.rst']):
     245                if not (ext in ['.sage', '.py', '.pyx', '.spyx', '.tex', '.pxi', '.rst']):
    233246                    continue
    234247                elif '__nodoctest__' in files:
     248                    # XXX Shouldn't this be 'lfiles'?
     249                    # Also, this test should IMHO be in the outer loop (1 level).
     250                    # Furthermore, the current practice is to put "nodoctest.py"
     251                    # files in the directories that should be skipped, not
     252                    # "__nodoctest__". (I haven't found a single instance of the
     253                    # latter in Sage 4.6.1.alpha3.)
     254                    # "nodoctest.py" is handled in skip() (!), to also be fixed.
     255                    # -leif
    235256                    continue
    236257                appendstr = os.path.join(root,F)
    237258                if skip(appendstr):
    for gr in range(0,numglobaliteration): 
    251272    opts = ' '.join([X for X in argv if X[0] == '-'])
    252273    argv = [X for X in argv if X[0] != '-']
    253274
    254     try:
     275    try:
     276        # FIXME: Nice, but <NUMTHREADS> should immediately follow '-tp' etc.,
     277        #        i.e., be the next argument. We might have file or directory
     278        #        names that properly convert to an int...
    255279        numthreads = int(argv[1])
    256280        infiles = argv[2:]
    257281    except ValueError: # can't convert first arg to an integer: arg was probably omitted
    for gr in range(0,numglobaliteration): 
    307331    t0 = time.time()
    308332    filemutex = thread.allocate_lock()
    309333    printmutex = thread.allocate_lock()
     334    SAGE_TESTDIR = os.environ['SAGE_TESTDIR']
    310335    #Pick a filename for the timing files -- long vs normal
     336    # TODO: perhaps these files shouldn't be hidden?  Also, don't
     337    # store them in SAGE_TESTDIR, in case the user wants to test in
     338    # some temporary directory: store them somewhere more permanent.
    311339    if opts.count("-long"):
    312         time_file_name = os.path.join(os.environ["SAGE_TESTDIR"],
     340        time_file_name = os.path.join(SAGE_TESTDIR,
    313341                                      '.ptest_timing_long')
    314342    else:
    315         time_file_name = os.path.join(os.environ["SAGE_TESTDIR"],
     343        time_file_name = os.path.join(SAGE_TESTDIR,
    316344                                      '.ptest_timing')
    317345    time_dict = { }
    318346    try:
    for gr in range(0,numglobaliteration): 
    337365
    338366    failed = []
    339367
    340     TMP = os.path.join(os.environ['SAGE_TESTDIR'], 'tmp', 'test')
    341     if not os.path.exists(TMP):
    342         os.makedirs(TMP)
     368    HOSTNAME = socket.gethostname().replace('-','_').replace('/','_').replace('\\','_')
     369    # Should TMP be a subdirectory of tempfile.gettempdir() rather than SAGE_TESTDIR?
     370    TMP = os.path.join(SAGE_TESTDIR, '%s-%s' % (HOSTNAME, os.getpid()))
     371    TMP = os.path.abspath(TMP)
     372    if os.path.exists(TMP):
     373        shutil.rmtree(TMP)
     374    os.makedirs(TMP)
     375    # Add rwx permissions for user to TMP:
     376    os.chmod(TMP, os.stat(TMP)[0] | stat.S_IRWXU)
     377    os.environ['SAGE_TESTDIR'] = TMP
     378    print
     379    print "Using the directory"
     380    print "   '%s'." % TMP
     381    print "for doctesting.  If all doctests pass, this directory will"
     382    print "be deleted automatically."
     383    print
    343384
    344385    populatefilelist(infiles)
    345386    #Sort the files by test time
    for gr in range(0,numglobaliteration): 
    374415
    375416    os.chdir(CUR)
    376417   
     418    print
     419    print "Removing the directory '%s'." % TMP
     420    try:
     421        os.rmdir(TMP)
     422    except OSError:
     423        # TODO (probably in sage-doctest): if tests were interrupted
     424        # but there were no failures in the interrupted files, delete
     425        # the temporary files, so that this directory is empty.
     426        print "Warning: the directory was not removed: it is not empty,"
     427        print "probably because doctesting failed or was interrupted."
     428
     429    print
     430    print "-"*int(70)
    377431    if len(failed) == 0:
    378432        if interrupt == False:
    379433            print "All tests passed!"
    for gr in range(0,numglobaliteration): 
    403457        for i in range(len(failed)):
    404458               print "\t", failed[i]
    405459        print "-"*int(70)
     460
    406461    #Only update timings if we are doing something standard
     462    opts = opts.strip()
    407463    if (opts=="-long" or len(opts)==0) and not interrupt:
    408464        with open(time_file_name,"w") as time_file:
    409465            pickle.dump(time_dict, time_file)
  • sage-test

    diff --git a/sage-test b/sage-test
    a b except KeyError: 
    2828    XML_RESULTS = None
    2929
    3030
     31# TODO: should we set SAGE_TESTDIR to a temporary directory like
     32# tempfile.gettempdir()?
    3133if 'SAGE_TESTDIR' not in os.environ:
    3234    os.environ['SAGE_TESTDIR'] = os.path.join(SAGE_ROOT, "tmp")
    3335TMP = os.path.join(os.environ['SAGE_TESTDIR'], "tmp")