#!/usr/bin/env sage-python23

import optparse
import os
import sys

# Note: the DOT_SAGE and SAGE_STARTUP_FILE environment variables have already been set by sage-env
DOT_SAGE = os.environ['DOT_SAGE']

# Override to not pick up user configuration, see Trac #20270
os.environ['SAGE_STARTUP_FILE'] = os.path.join(DOT_SAGE, 'init-doctests.sage')


if __name__ == "__main__":
    parser = optparse.OptionParser()

    def optional_argument(option, opt_str, value, parser, typ, default_arg):
        assert value is None
        try:
            next_arg = typ(parser.rargs[0])
        except Exception:
            next_arg = default_arg
        else:
            parser.rargs.pop(0)
        setattr(parser.values, option.dest, next_arg)

    parser.add_option("-p", "--nthreads", dest="nthreads", default=1, action="callback",
        callback=optional_argument, callback_args=(int, 0), nargs=0,
        metavar="N", help="tests in parallel using N threads with 0 interpreted as max(2, min(8, cpu_count()))")
    parser.add_option("-T", "--timeout", type=int, default=-1, help="timeout (in seconds) for doctesting one file, 0 for no timeout")
    parser.add_option("-m", "--memlimit", type=int, default=3300,
                      help='maximum virtual memory to allow each test '
                           'process, in megabytes; no limit if zero or less, '
                           'but tests tagged "high mem" are skipped if no limit '
                           'is set (default: 3300 MB)')
    parser.add_option("-a", "--all", action="store_true", default=False, help="test all files in the Sage library")
    parser.add_option("--logfile", metavar="FILE", help="log all output to FILE")
    parser.add_option("--sagenb", action="store_true", default=False, help="test all files from the Sage notebook sources")

    parser.add_option("-l", "--long", action="store_true", default=False, help="include lines with the phrase 'long time'")
    parser.add_option("-s", "--short", dest="target_walltime", default=None, action="callback",
        callback=optional_argument, callback_args=(int, 300), nargs=0,
        metavar="SECONDS", help="run as many doctests as possible in about 300 seconds (or the number of seconds given as an optional argument)")
    parser.add_option("--warn-long", dest="warn_long", default=None, action="callback",
        callback=optional_argument, callback_args=(float, 1.0), nargs=0,
        metavar="SECONDS", help="warn if tests take more time than SECONDS")
    # By default, include all tests marked 'dochtml' -- see
    # https://trac.sagemath.org/ticket/25345 and
    # https://trac.sagemath.org/ticket/26110:
    parser.add_option("--optional", metavar="PKGS", default="sage,dochtml,optional",
        help='only run tests including one of the "# optional" tags listed in PKGS; '
             'if "sage" is listed, will also run the standard doctests; '
             'if "dochtml" is listed, will also run the tests relying on the HTML documentation; '
             'if "optional" is listed, will also run tests for installed optional (new-style) packages; '
             'if "external" is listed, will also run tests for available external software; '
             'if set to "all", then all tests will be run')
    parser.add_option("--randorder", type=int, metavar="SEED", help="randomize order of tests")
    parser.add_option("--global-iterations", "--global_iterations", type=int, default=0, help="repeat the whole testing process this many times")
    parser.add_option("--file-iterations", "--file_iterations", type=int, default=0, help="repeat each file this many times, stopping on the first failure")

    parser.add_option("-i", "--initial", action="store_true", default=False, help="only show the first failure in each file")
    parser.add_option("--exitfirst", action="store_true", default=False, help="end the test run immediately after the first failure or unexpected exception")
    parser.add_option("--force_lib", "--force-lib", action="store_true", default=False, help="do not import anything from the tested file(s)")
    parser.add_option("--abspath", action="store_true", default=False, help="print absolute paths rather than relative paths")
    parser.add_option("--verbose", action="store_true", default=False, help="print debugging output during the test")
    parser.add_option("-d", "--debug", action="store_true", default=False, help="drop into a python debugger when an unexpected error is raised")
    parser.add_option("--only-errors", action="store_true", default=False, help="only output failures, not test successes")

    parser.add_option("--gdb", action="store_true", default=False, help="run doctests under the control of gdb")
    parser.add_option("--valgrind", "--memcheck", action="store_true", default=False,
                      help="run doctests using Valgrind's memcheck tool.  The log "
                         "files are named sage-memcheck.PID and can be found in " +
                         os.path.join(DOT_SAGE, "valgrind"))
    parser.add_option("--massif", action="store_true", default=False,
                      help="run doctests using Valgrind's massif tool.  The log "
                         "files are named sage-massif.PID and can be found in " +
                         os.path.join(DOT_SAGE, "valgrind"))
    parser.add_option("--cachegrind", action="store_true", default=False,
                      help="run doctests using Valgrind's cachegrind tool.  The log "
                         "files are named sage-cachegrind.PID and can be found in " +
                         os.path.join(DOT_SAGE, "valgrind"))
    parser.add_option("--omega", action="store_true", default=False,
                      help="run doctests using Valgrind's omega tool.  The log "
                         "files are named sage-omega.PID and can be found in " +
                         os.path.join(DOT_SAGE, "valgrind"))

    parser.add_option("-f", "--failed", action="store_true", default=False,
        help="doctest only those files that failed in the previous run")
    parser.add_option("-n", "--new", action="store_true", default=False,
        help="doctest only those files that have been changed in the repository and not yet been committed")
    parser.add_option("--show-skipped", "--show_skipped", action="store_true", default=False,
        help="print a summary at the end of each file of optional tests that were skipped")

    parser.add_option("--stats_path", "--stats-path", default=os.path.join(DOT_SAGE, "timings2.json"),
                          help="path to a json dictionary for the latest run storing a timing for each file")

    def gc_option(option, opt_str, value, parser):
        gcopts = dict(DEFAULT=0, ALWAYS=1, NEVER=-1)
        try:
            arg = gcopts[value.upper()]
        except KeyError:
            parser.error("unknown value {0}={1}".format(opt_str, value))
        setattr(parser.values, option.dest, arg)

    parser.add_option("--gc", type="string", action="callback", callback=gc_option,
                      help="control garbarge collection "
                           "(ALWAYS: collect garbage before every test; NEVER: disable gc; DEFAULT: Python default)")

    # The --serial option is only really for internal use, better not
    # document it.
    parser.add_option("--serial", action="store_true", default=False, help=optparse.SUPPRESS_HELP)

    parser.set_usage("sage -t [options] filenames")


    options, args = parser.parse_args()

    if len(args) == 0 and not (options.all or options.sagenb or options.new):
        parser.print_help()
        sys.exit(2)

    # Ensure that all doctests can be run with virtual memory limited to 3300
    # MiB (or a user-provided value). We must set this limit before starting
    # Sage. Note that this is a per-process limit, so we do not need to worry
    # about running multiple doctest processes in parallel. It is in
    # particular doctests in src/sage/schemes/elliptic_curves/heegner.py
    # which need this much memory.
    memlimit = options.memlimit << 20

    # Python's resource module only supports limits up to sys.maxsize,
    # even if the OS does support higher limits.
    if memlimit > 0 and memlimit <= sys.maxsize:
        import resource
        lim, hard = resource.getrlimit(resource.RLIMIT_AS)
        if lim == resource.RLIM_INFINITY or lim > memlimit:
            try:
                resource.setrlimit(resource.RLIMIT_AS, (memlimit, hard))
            except ValueError:
                options.memlimit = -1
                if sys.platform != 'cygwin':
                    # RLIMIT_AS is not currently supported on Cygwin so
                    # this will probably fail there:
                    # https://trac.sagemath.org/ticket/23979
                    raise
            else:
                if resource.RLIMIT_AS == getattr(resource, 'RLIMIT_RSS', None):
                    # On some platforms (e.g. OSX) RLIMIT_AS is just an alias
                    # for RLIMIT_RSS (see
                    # https://trac.sagemath.org/ticket/24190)
                    # In this case we still call setrlimit, but then disable
                    # high mem tests since we don't want such tests to actually
                    # cause us to run out of physical memory, leading to system
                    # instability (as opposed to virtual memory allocs failing)
                    options.memlimit = -1

    # Limit the number of threads to 2 to save system resources.
    # See Trac #23713 and #23892
    os.environ["OMP_NUM_THREADS"] = "2"
    os.environ["SAGE_NUM_THREADS"] = "2"

    from sage.doctest.control import DocTestController
    DC = DocTestController(options, args)
    err = DC.run()

    sys.exit(err)
