#!/usr/bin/env python

"""
A script to help track down the commit that caused a test to fail. This
script is intended to be run by git bisect.
"""

import acme_util
acme_util.check_minimum_python_version(2, 7)

import argparse, sys, os, doctest, traceback

from acme_util import expect, warning, verbose_print, run_cmd

###############################################################################
def parse_command_line(args, description):
###############################################################################
    parser = argparse.ArgumentParser(
        usage="""\n%s <testname> <good> [<bad>] [--compare=<baseline-id>] [--no-batch]  [--verbose]
OR
%s --help
OR
%s --test

\033[1mEXAMPLES:\033[0m
    > %s ERS.f45_g37.B1850C5 HEAD~4 HEAD
""" % ((os.path.basename(args[0]), ) * 4),

description=description,

formatter_class=argparse.ArgumentDefaultsHelpFormatter
)

    default_project  = acme_util.get_machine_project()
    default_compiler = acme_util.get_machine_info("COMPILERS")[0]

    parser.add_argument("testname", help="Name of failing test.")

    parser.add_argument("good", help="Name of most recent known good commit.")

    parser.add_argument("bad", nargs="?", default="HEAD", help="Name of bad commits, default is current commit.")

    parser.add_argument("-r", "--test-root",
                        help="Path to testroot to use for testcases for bisect. WARNING. This will be cleared by this script.")

    parser.add_argument("-c", "--compiler", default=default_compiler,
                        help="What compiler to use to build ACME")

    parser.add_argument("-p", "--project", default=default_project,
                        help="Project to be given to create_test.")

    parser.add_argument("-b", "--baseline-name",
                        help="Baseline id for comparing baselines. Not specifying means no comparisons will be done.")

    parser.add_argument("-v", "--verbose", action="store_true",
                        help="Print extra information")

    parser.add_argument("-n", "--check-namelists", action="store_true",
                        help="Consider a commit to be broken if namelist check fails")

    parser.add_argument("-t", "--check-throughput", action="store_true",
                        help="Consider a commit to be broken if throughput check fails (fail if tests slow down)")

    parser.add_argument("-m", "--check-memory", action="store_true",
                        help="Consider a commit to be broken if memory check fails (fail if tests footprint grows)")

    args = parser.parse_args(args[1:])

    acme_util.set_verbosity(args.verbose)

    if (args.test_root is None):
        args.test_root = os.path.join(acme_util.get_machine_info("CESMSCRATCHROOT", project=args.project), "acme_bisect")

    return args.testname, args.good, args.bad, args.test_root, args.compiler, args.project, args.baseline_name, args.check_namelists, args.check_throughput, args.check_memory

###############################################################################
def acme_bisect(testname, good, bad, testroot, compiler, project, baseline_name, check_namelists, check_throughput, check_memory):
###############################################################################
    create_test_rel = os.path.join(acme_util.get_acme_scripts_location_within_acme(), "create_test")
    wait_for_test_rel = os.path.join(acme_util.get_acme_scripts_location_within_acme(), "wait_for_tests")
    expect(create_test_rel, "Please run from root of repository")

    # Important: we only want to test merges
    commits_we_want_to_test = run_cmd("git rev-list %s..%s --merges --first-parent" % (good, bad)).splitlines()
    all_commits             = run_cmd("git rev-list %s..%s" % (good, bad)).splitlines()
    commits_to_skip         = set(all_commits) - set(commits_we_want_to_test)
    print "Skipping these non-merge commits"
    for item in commits_to_skip:
        print item

    # Basic setup
    run_cmd("git bisect start")
    run_cmd("git bisect good %s" % good)
    run_cmd("git bisect bad %s" % bad)
    run_cmd("git bisect skip %s" % " ".join(commits_to_skip))

    # Formulate the create_test command

    compare_args = "-c -b %s" % baseline_name if baseline_name is not None else ""
    project_args = "-p %s" % project if project else ""
    bisect_cmd = "/bin/rm -rf %s/*acme_bisect && %s %s --test-root %s -t acme_bisect --compiler %s %s %s" % \
        (testroot, create_test_rel, testname, testroot, compiler, project_args, compare_args)

    is_batch = acme_util.does_machine_have_batch()
    if (is_batch):
        # Forumulate the wait_for_tests command.

        wait_for_tests_cmd = "%s %s/*acme_bisect/TestStatus" % (wait_for_test_rel, testroot)
        if (check_throughput):
            wait_for_tests_cmd += " -t"
        if (check_memory):
            wait_for_tests_cmd += " -m"
        if (not check_namelists):
            wait_for_tests_cmd += " -i"

        bisect_cmd += " && %s" % wait_for_tests_cmd

    run_cmd("git bisect run sh -c '%s'" % bisect_cmd, ok_to_fail=True, verbose=True)

    run_cmd("git bisect reset")

###############################################################################
def _main_func(description):
###############################################################################
    if ("--test" in sys.argv):
        test_results = doctest.testmod(verbose=True)
        sys.exit(1 if test_results.failed > 0 else 0)

    acme_util.stop_buffering_output()

    testname, good, bad, testroot, compiler, project, baseline_name, check_namelists, check_throughput, check_memory = \
        parse_command_line(sys.argv, description)

    acme_bisect(testname, good, bad, testroot, compiler, project, baseline_name, check_namelists, check_throughput, check_memory)

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

if (__name__ == "__main__"):
    _main_func(__doc__)
