#!/usr/bin/env python

####################################################################
# Run the Sage doctest system on the collection of files and
# directories specified on the command line.

####################################################################

import os, signal, sys, time


argv = sys.argv

opts = ' '.join([X for X in argv if X[0] == '-'])
argv = [X for X in argv if X[0] != '-']

t0 = time.time()

from os.path import abspath

SAGE_ROOT = os.environ['SAGE_ROOT']
SAGE_SITE = os.path.realpath(os.path.join(os.environ['SAGE_LOCAL'],
                                          'lib', 'python', 'site-packages'))

try:
    XML_RESULTS = os.environ['XML_RESULTS']
except KeyError:
    XML_RESULTS = None


# TODO: should we set SAGE_TESTDIR to a temporary directory like
# tempfile.gettempdir()?
if 'SAGE_TESTDIR' not in os.environ:
    os.environ['SAGE_TESTDIR'] = os.path.join(SAGE_ROOT, "tmp")
SAGE_TESTDIR = os.environ['SAGE_TESTDIR']
try:
    os.makedirs(SAGE_TESTDIR)
except OSError:
    if not os.path.isdir(SAGE_TESTDIR):
        raise

def sage_test_command(f):
    g = abspath(f)
    if SAGE_ROOT in g:
        f = g[g.find(SAGE_ROOT)+len(SAGE_ROOT)+1:]
    return 'sage -t %s "%s"'%(opts, f)

def skip(F):
    G = abspath(F)
    i = G.rfind(os.sep)
    if os.path.exists(os.path.join(G[:i], 'nodoctest.py')):
        print "%s (skipping) -- nodoctest.py file in directory"%sage_test_command(F)
        return True

    if 'nodoctest' in open(G).read()[:50]:
        return True
    if G.find(os.path.join('doc', 'output')) != -1:
        return True

    sys.stdout.write("%-60s"%sage_test_command(F)+"\n")
    sys.stdout.flush()
    return False

failed = []

def test(F, cmd):
    from subprocess import call
    t = time.time()
    if skip(F):
        return 0
    s = os.path.join(SAGE_ROOT, 'local', 'bin', 'sage-%s' % cmd) + ' "%s"' % F
    err = call(s, shell=True)

    # Check the process exit code that sage-doctest returns

    if err == 1: # process exit code 1: File not found
        failed.append(sage_test_command(F)+" # File not found")
    elif err == 2: # process exit code 2: KeyboardInterrupt
        failed.append(sage_test_command(F)+" # KeyboardInterrupt")
        raise KeyboardInterrupt
    elif err == 4: # process exit code 4: Terminated by signal
        failed.append(sage_test_command(F)+" # Killed/crashed")
    elif err == 8: # process exit code 8: Unhandled doctest exception
        failed.append(sage_test_command(F)+" # Exception from doctest framework")
    elif err == 64: # process exit code 64: Time out
        failed.append(sage_test_command(F)+" # Time out")
    elif err == 128: # process exit code 128: Regular doctest failures
        failed.append(sage_test_command(F))
    elif err != 0:
        failed.append(sage_test_command(F))
    t = time.time() - t
    print "\t [%.1f s]" % t
    if XML_RESULTS:
        failures = int(err == 128)
        errors = int(err and not failures)
        path = F.split(os.path.sep)
        while 'sage' in path:
            path = path[path.index('sage')+1:]
        path[-1] = os.path.splitext(path[-1])[0]
        module = '.'.join(path)
        if errors and '#' in failed[-1]:
            cause = failed[-1].split('#')[-1].strip()
            failure_item = "<error type='%s'>%s</error>" % (cause, cause)
        else:
            failure_item = ""
        f = open(os.path.join(XML_RESULTS, module + '.xml'), 'w')
        f.write("""
            <?xml version="1.0" ?>
            <testsuite name="%(module)s" errors="%(errors)s" failures="%(failures)s" tests="1" time="%(t)s">
            <testcase classname="%(module)s" name="test">
            %(failure_item)s
            </testcase>
            </testsuite>
        """.strip() % locals())
        f.close()
    return err


def test_file(F):
    if not os.path.exists(F):
        if os.path.exists(os.path.join(SAGE_ROOT, F)):
            F = os.path.join(SAGE_ROOT, F)
    if not os.path.exists(F):
        if F[:6] != "__test" and not F.endswith('.png'):
            print "ERROR: File %s is missing" % os.path.join(os.curdir, F)
            failed.append(os.path.join(os.curdir, F) + " # File not found")
        return 1

    extra_opts = ''
    if SAGE_SITE in os.path.realpath(F) and not '-force_lib' in opts:
        extra_opts = ' -force_lib'

    base, ext = os.path.splitext(F)
    err = 0
    if ext in ['.py', '.spyx', '.pyx', '.tex', '.pxi', '.sage', '.rst']:
        err = err | test(F, 'doctest ' + opts + extra_opts)
    elif (os.path.isdir(F) and  not '#' in F and
          not os.sep + 'notes' in F):
        ld = os.listdir(F)
        if not ('__nodoctest__' in ld):
            for L in ld:
                err = err | test_file(os.path.join(F, L))
    return err

files = argv[1:]

if '-sagenb' in opts:
    opts = opts.replace('--sagenb', '').replace('-sagenb', '')

    # Find SageNB's home.
    from pkg_resources import Requirement, working_set
    sagenb_loc = working_set.find(Requirement.parse('sagenb')).location

    # In case we're using setuptools' "develop" mode.
    if not SAGE_SITE in sagenb_loc:
        opts += ' -force_lib'

    files.append(os.path.join(sagenb_loc, 'sagenb'))

if len(files) == 0:
    print "Usage: sage -t [-verbose] [-long] [-optional] [-only-optional=list,of,tags] <files or directories>"
    print "Test the docstrings in each listed Python or Cython file, and "
    print "if a directory is given, test the docstrings in all files in"
    print "all subdirectories of that directory."
    print ""
    print "OPTIONS:"
    print "     -force_lib     -- assume all files are Sage library files,"
    print "                       regardless of location"
    print "     -long          -- include lines with the phrase 'long time'"
    print "     -verbose       -- print debugging output during the test"
    print "     -optional      -- also test all #optional examples"
    print "     -only-optional=list,of,tags -- only run doctests with "
    print "                       #optional <nonempty subset of tags>"
    print "      if the list of tags is omitted, run all optional doctests"
    print "     -randorder     -- if given, randomize *order* of tests"
    print "     -randorder=seed-- use seed to get same random order"
    print "     -sagenb        -- test all sagenb files"

    sys.exit(1)

files.sort()

err = 0
for F in files:
    try:
        err = err | test_file(F)
    except KeyboardInterrupt:
        print "Aborting further tests."
        err = err | 2
        break

print " "
print "-"*int(70)

if len(failed) == 0:
    print "All tests passed!"
else:
    print "The following tests failed:\n"
    print "\n\t" + "\n\t".join(failed)

print "Total time for all tests: %.1f seconds"%(time.time() - t0)
sys.exit(err)
