#!/usr/bin/env python

# $Id: ST-pulsar-test,v 1.10 2011/06/08 02:23:45 elwinter Exp $

# Program to run Fermi Science Tools unit tests.

#******************************************************************************

# Import external modules.

# Standard modules
from optparse import OptionParser
from os import environ, mkdir, sep, times
from os.path import exists, abspath, dirname
from astropy.io import fits
from platform import node
from shutil import copyfile
from subprocess import CalledProcessError, STDOUT, check_call
import sys

# Third-party modules

# Project modules

#******************************************************************************

# Constants

# Defaults for program parameters.

# Other program constants.

# Name of log file for tests.
logFile = "ST-pulsar-test.log"

# Path to reference data.
refdata_path = sep.join([environ["CONDA_PREFIX"], "share", "fermitools", "data"])

# Path to test script data.
test_data_path = sep.join([abspath(dirname(sys.argv[0])), "data"])

# Path to test script output reference data.
test_outref_path = sep.join([test_data_path, "outref"])

# Constants to specify status codes for test pass and fail.
OK = 0
FAIL = 1

#******************************************************************************

# Utility functions for tests.
    
# Check a list of files for existence. If one or more are not found,
# return FAIL. Otherwise, return OK.
def exists_mass(files):
    status = OK
    for f in files:
        print("Checking for file %s ..." % f, end=' ')
        if exists(f):
            print("found.")
        else:
            print("not found!")
            status = FAIL
    return status

#------------------------------------------------------------------------------

# Compare 2 binary files using cmp. Return OK on success (cmp returns
# 0), or the exit code from cmp if non-zero.
def cmp(f1, f2, *args):
    status = OK
    cmd = "cmp"
    for arg in args:
        cmd += " %s" % arg
    cmd += " %s %s" % (f1, f2)
    try:
        check_call(cmd, shell = True, stdout = sys.stdout, stderr = STDOUT)
    except CalledProcessError as e:
        status = e.returncode
    return status

# Run cmp on a set of files, with reference versions in path
# outref. If one or more files fails the comparison, return
# FAIL. Otherwise, return OK.
def cmp_mass(files, outref):
    status = OK
    for f in files:
        f1 = f
        f2 = sep.join([outref, f])
        print("Using cmp to compare %s to %s ..." % (f1, f2), end=' ')
        s = cmp(f1, f2, *files[f])
        if s == OK:
            print("passed.")
        else:
            print("failed!")
            status = FAIL
    return status

#------------------------------------------------------------------------------

# Compare 2 text files using diff, Return OK on success (diff returns
# 0), or the exit code from diff if non-zero.
def diff(f1, f2, *args):
    status = OK
    cmd = "diff"
    for arg in args:
        cmd += " %s" % arg
    cmd += " %s %s" % (f1, f2)
    try:
        check_call(cmd, shell = True, stdout = sys.stdout, stderr = STDOUT)
    except CalledProcessError as e:
        status = e.returncode
    return status

# Run diff on a set of files, with reference versions in outref. If
# one or more files fails the comparison, return FAIL. Otherwise,
# return OK.
def diff_mass(files, outref):
    status = OK
    for f in files:
        f1 = f
        f2 = sep.join([outref, f])
        s = diff(f1, f2, *files[f])
        print("Using diff to compare %s to %s ..." % (f1, f2), end=' ')
        if s == OK:
            print("passed.")
        else:
            print("failed!")
            status = FAIL
    return status

#------------------------------------------------------------------------------

# Compare 2 FITS files using ftdiff. Return OK on success (ftdiff
# returns 0), or the exit code from ftdiff if non-zero.
def ftdiff(f1, f2, **kwargs):
    exclude = []
    reltol = 0.0
    tolerance = 0.0
    for (kw, val) in list(kwargs.items()):
        if val != "":
            if kw == 'exclude':
                exclude = [val]
            if kw == 'tolerance':
                tolerance = val
            if kw == 'reltol':
                reltol = val
    fd = fits.FITSDiff(f1, f2, ignore_keywords=exclude, rtol = reltol, atol = tolerance)
    return fd.identical

# Run ftdiff on a set of files, with reference versions in outref. If
# one or more files fails the comparison, return FAIL. Otherwise,
# return OK.
def ftdiff_mass(fitsFiles, outref):
    status = OK
    for f in fitsFiles: 
        f1 = f
        f2 = sep.join([outref, f])
        print("Using ftdiff to compare %s to %s ..." % (f1, f2), end=' ')
        #s = ftdiff(f1, f2, **fitsFiles[f])
        s = ftdiff(f1, f2)
        if s == OK:
            print("passed.")
        else:
            print("failed!")
            status = FAIL
    return status

#------------------------------------------------------------------------------

# Individual test functions
def check_environment():
    status = OK
    environmentVariableNames = [ "CONDA_PREFIX" ]
    for ev in environmentVariableNames:
        print("Checking if environment variable %s is set ..." % ev, end=' ')
        try:
            val = environ[ev]
            print("yes, value is:", val)
        except KeyError as e:
            print("no!")
            status = FAIL
    return status

def test_gtbary():
    status = OK
    evfile = sep.join([test_data_path, "my_pulsar_events_v3.fits"])
    scfile = sep.join([test_data_path,
                       "my_pulsar_spacecraft_data_v3r1.fits"])
    outfile = "gtbary-out.fits"
    ra = "85.0482"
    dec = "-69.3319"
    tcorrect = "BARY"
    solareph = "JPL DE405"
    angtol = "1.e-8"
    timefield = "TIME"
    sctable = "SC_DATA"
    leapsecfile = "DEFAULT"
    chatter = "2"
    clobber = "yes"
    debug = "no"
    gui = "no"
    mode = "h"
    cmdTemplate = "gtbary evfile='%s' scfile='%s' outfile='%s' ra='%s' " + \
                  "dec='%s' tcorrect='%s' solareph='%s' angtol='%s' " + \
                  "timefield='%s' sctable='%s' leapsecfile='%s' " + \
                  "chatter='%s' clobber='%s' debug='%s' gui='%s' " + \
                  "mode='%s'"
    cmd = cmdTemplate % (evfile, scfile, outfile, ra, dec, tcorrect,
                         solareph, angtol, timefield, sctable, leapsecfile,
                         chatter, clobber, debug, gui, mode)
    try:
        check_call(cmd, shell = True, stdout = sys.stdout, stderr = STDOUT)
    except CalledProcessError as e:
        status = e.returncode
    return status

def check_gtbary_output():
    status = OK
    outref = test_outref_path
    fitsFiles = {
        "gtbary-out.fits" : { "exclude" : "CREATOR,DATASUM",
                              "reltol" : "1e-15" },
        }
    if exists_mass(list(fitsFiles.keys())) != OK:
        status = FAIL
    if ftdiff_mass(fitsFiles, outref) != OK:
        status = FAIL
    return status

def test_gtephem():
    status = OK
    psrdbfile = sep.join([test_data_path, "master_pulsardb_v2.fits"])
    psrname = "PSR B0540-69"
    reftime = "212380785.922"
    timeformat = "GLAST"
    timesys = "TDB"
    strict = "no"
    solareph = "JPL DE405"
    matchsolareph = "NONE"
    leapsecfile = "DEFAULT"
    reportephstatus = "yes"
    chatter = "2"
    clobber = "yes"
    debug = "no"
    gui = "no"
    mode = "h"
    outfile = "ST-gtephem.log"
    cmdTemplate = "gtephem psrdbfile='%s' psrname='%s' reftime='%s' " + \
                  "timeformat='%s' timesys='%s' strict='%s' solareph='%s' " +\
                  "matchsolareph='%s' leapsecfile='%s' reportephstatus='%s' "+\
                  "chatter='%s' clobber='%s' debug='%s' gui='%s' mode='%s' "+\
                  " &> %s"
    cmd = cmdTemplate % (psrdbfile, psrname, reftime, timeformat, timesys,
                         strict, solareph, matchsolareph, leapsecfile,
                         reportephstatus, chatter, clobber, debug, gui, mode,
                         outfile)
    try:
        check_call(cmd, shell = True, stdout = sys.stdout, stderr = STDOUT)
    except CalledProcessError as e:
        status = e.returncode
    return status

def check_gtephem_output():
    status = OK
    outref = test_outref_path
    files = {
        "ST-gtephem.log" : [],
        }
    if exists_mass(list(files.keys())) != OK:
        status = FAIL
    return status

def test_gtpulsardb():
    status = OK
    psrdbfile = sep.join([test_data_path, "groD4-dc2v5.fits"])
    outfile = "gtpulsardb-out.fits"
    filter = "NAME"
    psrname = "Crab"
    tstart = "0."
    tstop = "1.e5"
    solareph = "JPL DE405"
    author = "Anonymous Tester"
    leapsecfile = "DEFAULT"
    chatter = "2"
    clobber = "yes"
    debug = "no"
    gui = "no"
    mode = "h"
    cmdTemplate = "gtpulsardb psrdbfile='%s' outfile='%s' filter='%s' " + \
                  "psrname='%s' tstart='%s' tstop='%s' solareph='%s' " + \
                  "author='%s' leapsecfile='%s' chatter='%s' clobber='%s' " +\
                  "debug='%s' gui='%s' mode='%s'"
    cmd = cmdTemplate % (psrdbfile, outfile, filter, psrname, tstart, tstop,
                         solareph, author, leapsecfile, chatter, clobber,
                         debug, gui, mode)
    try:
        check_call(cmd, shell = True, stdout = sys.stdout, stderr = STDOUT)
    except CalledProcessError as e:
        status = e.returncode
    return status

def check_gtpulsardb_output():
    status = OK
    outref = test_outref_path

    fitsFiles = {
        "gtpulsardb-out.fits" : { "exclude" : "CREATOR,DATASUM" },
        }
    if exists_mass(list(fitsFiles.keys())) != OK:
        status = FAIL
    if ftdiff_mass(fitsFiles, outref) != OK:
        status = FAIL

    return status

def test_gtpphase():
    status = OK
    evfile_ref = sep.join([test_data_path, "my_pulsar_events_v3.fits"])
    evfile = "gtpphase-out.fits"
    copyfile(evfile_ref, evfile)
    scfile = sep.join([test_data_path,
                       "my_pulsar_spacecraft_data_v3r1.fits"])
    psrdbfile = sep.join([test_data_path, "master_pulsardb_v2.fits"])
    psrname = "PSR B0540-69"
    ephstyle = "DB"
    ephepoch = "0."
    timeformat = "FILE"
    timesys = "FILE"
    ra = "0."
    dec = "0."
    phi0 = "0."
    f0 = "1."
    f1 = "0."
    f2 = "0."
    p0 = "1."
    p1 = "0."
    p2 = "0."
    tcorrect = "AUTO"
    solareph = "JPL DE405"
    matchsolareph = "NONE"
    angtol = "1.e-8"
    evtable = "EVENTS"
    timefield = "TIME"
    sctable = "SC_DATA"
    pphasefield = "PULSE_PHASE"
    pphaseoffset = "0."
    leapsecfile = "DEFAULT"
    reportephstatus = "yes"
    chatter = "2"
    clobber = "yes"
    debug = "no"
    gui = "no"
    mode = "h"
    cmdTemplate = "gtpphase evfile='%s' scfile='%s' psrdbfile='%s' " + \
                  "psrname='%s' ephstyle='%s' ephepoch='%s' " + \
                  "timeformat='%s' timesys='%s' ra='%s' dec='%s' " + \
                  "phi0='%s' f0='%s' f1='%s' f2='%s' p0='%s' p1='%s' " + \
                  "p2='%s' tcorrect='%s' solareph='%s' " + \
                  "matchsolareph='%s' angtol='%s' evtable='%s' " + \
                  "timefield='%s' sctable='%s' pphasefield='%s' " + \
                  "pphaseoffset='%s' leapsecfile='%s' " + \
                  "reportephstatus='%s' chatter='%s' clobber='%s' " + \
                  "debug='%s' gui='%s' mode='%s'"
    cmd = cmdTemplate % (evfile, scfile, psrdbfile, psrname, ephstyle,
                         ephepoch, timeformat, timesys, ra, dec, phi0, f0,
                         f1, f2, p0, p1, p2, tcorrect, solareph,
                         matchsolareph, angtol, evtable, timefield,
                         sctable, pphasefield, pphaseoffset, leapsecfile,
                         reportephstatus, chatter, clobber, debug, gui, mode)
    try:
        check_call(cmd, shell = True, stdout = sys.stdout, stderr = STDOUT)
    except CalledProcessError as e:
        status = e.returncode
    return status

def check_gtpphase_output():
    status = OK
    outref = test_outref_path

    fitsFiles = {
        "gtpphase-out.fits" : { "exclude" : "DATASUM",
                                "reltol" : "1e-4" },
        }
    if exists_mass(list(fitsFiles.keys())) != OK:
        status = FAIL
    if ftdiff_mass(fitsFiles, outref) != OK:
        status = FAIL

    return status

def test_gtophase():
    status = OK
    evfile_ref = sep.join([test_data_path, "my_pulsar_events_v3.fits"])
    evfile = "gtophase-out.fits"
    copyfile(evfile_ref, evfile)
    scfile = sep.join([test_data_path, "my_pulsar_spacecraft_data_v3r1.fits"])
    psrdbfile = sep.join([test_data_path, "master_pulsardb_v2.fits"])
    psrname = "PSR J1834-0010"
    ra = "85.0482"
    dec = "-69.3319"
    solareph = "JPL DE405"
    matchsolareph = "NONE"
    angtol = "1.e-8"
    evtable = "EVENTS"
    timefield = "TIME"
    sctable = "SC_DATA"
    ophasefield = "ORBITAL_PHASE"
    ophaseoffset = "0."
    leapsecfile = "DEFAULT"
    reportephstatus = "yes"
    chatter = "2"
    clobber = "yes"
    debug = "no"
    gui = "no"
    mode = "h"
    cmdTemplate = "gtophase evfile='%s' scfile='%s' psrdbfile='%s' " + \
                  "psrname='%s' ra='%s' dec='%s' solareph='%s' " + \
                  "matchsolareph='%s' angtol='%s' evtable='%s' " + \
                  "timefield='%s' sctable='%s' ophasefield='%s' " + \
                  "ophaseoffset='%s' leapsecfile='%s' " + \
                  "reportephstatus='%s' chatter='%s' clobber='%s' " + \
                  "debug='%s' gui='%s' mode='%s'"
    cmd = cmdTemplate % (evfile, scfile, psrdbfile, psrname, ra, dec,
                         solareph, matchsolareph, angtol, evtable, timefield,
                         sctable, ophasefield, ophaseoffset, leapsecfile,
                         reportephstatus, chatter, clobber, debug, gui, mode)
    try:
        check_call(cmd, shell = True, stdout = sys.stdout, stderr = STDOUT)
    except CalledProcessError as e:
        status = e.returncode
    return status

def check_gtophase_output():
    status = OK
    outref = test_outref_path

    fitsFiles = {
        "gtophase-out.fits" : { "exclude" : "DATASUM",
                                "reltol" : "1e-10" },
        }
    if exists_mass(list(fitsFiles.keys())) != OK:
        status = FAIL
    if ftdiff_mass(fitsFiles, outref) != OK:
        status = FAIL

    return status

def test_gtpsearch():
    status = OK
    evfile = sep.join([test_data_path, "my_pulsar_events_v3.fits"])
    scfile = sep.join([test_data_path, "my_pulsar_spacecraft_data_v3r1.fits"])
    psrdbfile = sep.join([test_data_path, "master_pulsardb_v2.fits"])
    psrname = "PSR B0540-69"
    outfile = "gtpsearch-out.fits"
    algorithm = "Chi2"
    numphase = "10"
    numharm = "10"
    maxharm = "10"
    scanstep = "0.5"
    numtrials = "200"
    timeorigin = "USER"
    usertime = "214380785.922"
    userformat = "FILE"
    usersys = "TDB"
    ephstyle = "DB"
    ephepoch = "0."
    timeformat = "FILE"
    timesys = "FILE"
    ra = "0."
    dec = "0."
    f0 = "1."
    f1 = "0."
    f2 = "0."
    p0 = "1."
    p1 = "0."
    p2 = "0."
    tcorrect = "AUTO"
    solareph = "JPL DE405"
    matchsolareph = "NONE"
    angtol = "1.e-8"
    evtable = "EVENTS"
    timefield = "TIME"
    sctable = "SC_DATA"
    plot = "no"
    title = "My statistical test"
    leapsecfile = "DEFAULT"
    reportephstatus = "yes"
    chatter = "2"
    clobber = "yes"
    debug = "no"
    gui = "no"
    mode = "h"
    cmdTemplate = "gtpsearch evfile='%s' scfile='%s' psrdbfile='%s' " + \
                  "psrname='%s' outfile='%s' algorithm='%s' numphase='%s' " + \
                  "numharm='%s' maxharm='%s' scanstep='%s' numtrials='%s' " + \
                  "timeorigin='%s' usertime='%s' userformat='%s' " + \
                  "usersys='%s' ephstyle='%s' ephepoch='%s' " + \
                  "timeformat='%s' timesys='%s' ra='%s' dec='%s' f0='%s' " + \
                  "f1='%s' f2='%s' p0='%s' p1='%s' p2='%s' tcorrect='%s' " + \
                  "solareph='%s' matchsolareph='%s' angtol='%s' " + \
                  "evtable='%s' timefield='%s' sctable='%s' plot='%s' " + \
                  "title='%s' leapsecfile='%s' reportephstatus='%s' " + \
                  "chatter='%s' clobber='%s' debug='%s' gui='%s' mode='%s'"
    cmd = cmdTemplate % (evfile, scfile, psrdbfile, psrname, outfile,
                         algorithm, numphase, numharm, maxharm, scanstep,
                         numtrials, timeorigin, usertime, userformat, usersys,
                         ephstyle, ephepoch, timeformat, timesys, ra, dec,
                         f0, f1, f2, p0, p1, p2, tcorrect, solareph,
                         matchsolareph, angtol, evtable, timefield, sctable,
                         plot, title, leapsecfile, reportephstatus, chatter,
                         clobber, debug, gui, mode)
    try:
        check_call(cmd, shell = True, stdout = sys.stdout, stderr = STDOUT)
    except CalledProcessError as e:
        status = e.returncode
    return status

def check_gtpsearch_output():
    status = OK
    outref = test_outref_path

    fitsFiles = {
        "gtpsearch-out.fits" : { "exclude" : "CREATOR" },
        }
    if exists_mass(list(fitsFiles.keys())) != OK:
        status = FAIL
    if ftdiff_mass(fitsFiles, outref) != OK:
        status = FAIL

    return status

def test_gtpspec():
    status = OK
    evfile = sep.join([test_data_path, "my_pulsar_events_v3.fits"])
    scfile = sep.join([test_data_path, "my_pulsar_spacecraft_data_v3r1.fits"])
    psrdbfile = sep.join([test_data_path, "master_pulsardb_v2.fits"])
    psrname = "PSR B0540-69"
    outfile = "gtpspec-out.fits"
    binwidth = "0.01"
    numbins = "1000000"
    timeorigin = "MIDDLE"
    usertime = "0."
    userformat = "FILE"
    usersys = "FILE"
    ra = "85.0482"
    dec = "-69.3319"
    ephstyle = "FREQ"
    f1f0ratio = "0."
    f2f0ratio = "0."
    p1p0ratio = "0."
    p2p0ratio = "0."
    tcorrect = "BARY"
    solareph = "JPL DE405"
    matchsolareph = "ALL"
    angtol = "1.e-8"
    evtable = "EVENTS"
    timefield = "TIME"
    sctable = "SC_DATA"
    lowfcut = ".01005"
    plot = "no"
    title = "My Fourier analysis"
    leapsecfile = "DEFAULT"
    reportephstatus = "yes"
    chatter = "2"
    clobber = "yes"
    debug = "no"
    gui = "no"
    mode = "h"
    cmdTemplate = "gtpspec evfile='%s' scfile='%s' psrdbfile='%s' " + \
                  "psrname='%s' outfile='%s' binwidth='%s' numbins='%s' " + \
                  "timeorigin='%s' usertime='%s' userformat='%s' " + \
                  "usersys='%s' ra='%s' dec='%s' ephstyle='%s' " + \
                  "f1f0ratio='%s' f2f0ratio='%s' p1p0ratio='%s' " + \
                  "p2p0ratio='%s' tcorrect='%s' solareph='%s' " + \
                  "matchsolareph='%s' angtol='%s' evtable='%s' " + \
                  "timefield='%s' sctable='%s' lowfcut='%s' plot='%s' " + \
                  "title='%s' leapsecfile='%s' reportephstatus='%s' " + \
                  "chatter='%s' clobber='%s' debug='%s' gui='%s' mode='%s'"
    cmd = cmdTemplate % (evfile, scfile, psrdbfile, psrname, outfile, binwidth,
                         numbins, timeorigin, usertime, userformat, usersys,
                         ra, dec, ephstyle, f1f0ratio, f2f0ratio, p1p0ratio,
                         p2p0ratio, tcorrect, solareph, matchsolareph, angtol,
                         evtable, timefield, sctable, lowfcut, plot, title,
                         leapsecfile, reportephstatus, chatter, clobber, debug,
                         gui, mode)
    try:
        check_call(cmd, shell = True, stdout = sys.stdout, stderr = STDOUT)
    except CalledProcessError as e:
        status = e.returncode
    return status

def check_gtpspec_output():
    status = OK
    outref = test_outref_path

    fitsFiles = {
        "gtpspec-out.fits" : { "exclude" : "CREATOR,DATASUM",
                               "reltol" : "1e-15" },
        }
    if exists_mass(list(fitsFiles.keys())) != OK:
        status = FAIL
    if ftdiff_mass(fitsFiles, outref) != OK:
        status = FAIL

    return status

def test_gtptest():
    status = OK
    evfile = sep.join([test_data_path, "my_pulsar_events_phase_v3r1.fits"])
    outfile = "gtptest-out.fits"
    numphase = "10"
    numharm = "3"
    maxharm = "5"
    evtable = "EVENTS"
    pphasefield = "PULSE_PHASE"
    plot = "no"
    title = "All statistical test results"
    chatter = "2"
    clobber = "yes"
    debug = "no"
    gui = "no"
    mode = "h"
    cmdTemplate = "gtptest evfile='%s' outfile='%s' numphase='%s' " + \
                  "numharm='%s' maxharm='%s' evtable='%s' pphasefield='%s' " +\
                  "plot='%s' title='%s' chatter='%s' clobber='%s' " + \
                  "debug='%s' gui='%s' mode='%s'"
    cmd = cmdTemplate % (evfile, outfile, numphase, numharm, maxharm, evtable,
                         pphasefield, plot, title, chatter, clobber, debug,
                         gui, mode)
    try:
        check_call(cmd, shell = True, stdout = sys.stdout, stderr = STDOUT)
    except CalledProcessError as e:
        status = e.returncode
    return status

def check_gtptest_output():
    status = OK
    outref = test_outref_path

    fitsFiles = {
        "gtptest-out.fits" : { "exclude" : "CREATOR,DATASUM",
                               "reltol" : "1e-15" },
        }
    if exists_mass(list(fitsFiles.keys())) != OK:
        status = FAIL
    if ftdiff_mass(fitsFiles, outref) != OK:
        status = FAIL

    return status

# List of pulsar tests to run when ROOT is available. The "cmd" entry
# should contain the complete invocation string for a command to run
# on the command line, or the name of a function to be invoked. Either
# should return 0 on success, or non-0 on failure. The standard output
# and standard error of the test will be saved in the output log for
# this script.

pulsarTestsWithRoot = [
    { "cmd"  : check_environment,
      "name" : "Checking environment variables" },
    { "cmd"  : test_gtbary,
      "name" : "gtbary" },
    { "cmd"  : check_gtbary_output,
      "name" : "gtbary: Check output" },
    { "cmd"  : test_gtephem,
      "name" : "gtephem" },
    { "cmd"  : check_gtephem_output,
      "name" : "gtephem: Check output" },
    { "cmd"  : test_gtpulsardb,
      "name" : "gtpulsardb" },
    { "cmd"  : check_gtpulsardb_output,
      "name" : "gtpulsardb: Check output" },
    { "cmd"  : test_gtpphase,
      "name" : "gtpphase" },
    { "cmd"  : check_gtpphase_output,
      "name" : "gtpphase: Check output" },
    { "cmd"  : test_gtophase,
      "name" : "gtophase" },
    { "cmd"  : check_gtophase_output,
      "name" : "gtophase: Check output" },
    { "cmd"  : test_gtpsearch,
      "name" : "gtpsearch" },
    { "cmd"  : check_gtpsearch_output,
      "name" : "gtpsearch: Check output" },
    { "cmd"  : test_gtpspec,
      "name" : "gtpspec" },
    { "cmd"  : check_gtpspec_output,
      "name" : "gtpspec: Check output" },
    { "cmd"  : test_gtptest,
      "name" : "gtptest" },
    { "cmd"  : check_gtptest_output,
      "name" : "gtptest: Check output" },
    ]

# List of pulsar tests to run when ROOT is not available. The "cmd"
# entry should contain the complete invocation string for a command to
# run on the command line, or the name of a function to be
# invoked. Either should return 0 on success, or non-0 on failure. The
# standard output and standard error of the test will be saved in the
# output log for this script.

pulsarTestsWithoutRoot = [
    { "cmd"  : check_environment,
      "name" : "Checking environment variables" },
    { "cmd"  : test_gtbary,
      "name" : "gtbary" },
    { "cmd"  : check_gtbary_output,
      "name" : "gtbary: Check output" },
    { "cmd"  : test_gtephem,
      "name" : "gtephem" },
    { "cmd"  : check_gtephem_output,
      "name" : "gtephem: Check output" },
    { "cmd"  : test_gtpulsardb,
      "name" : "gtpulsardb" },
    { "cmd"  : check_gtpulsardb_output,
      "name" : "gtpulsardb: Check output" },
    { "cmd"  : test_gtpphase,
      "name" : "gtpphase" },
    { "cmd"  : check_gtpphase_output,
      "name" : "gtpphase: Check output" },
    { "cmd"  : test_gtophase,
      "name" : "gtophase" },
    { "cmd"  : check_gtophase_output,
      "name" : "gtophase: Check output" },
    { "cmd"  : test_gtpsearch,
      "name" : "gtpsearch" },
    { "cmd"  : check_gtpsearch_output,
      "name" : "gtpsearch: Check output" },
    { "cmd"  : test_gtpspec,
      "name" : "gtpspec" },
    { "cmd"  : check_gtpspec_output,
      "name" : "gtpspec: Check output" },
    { "cmd"  : test_gtptest,
      "name" : "gtptest" },
    { "cmd"  : check_gtptest_output,
      "name" : "gtptest: Check output" },
    ]

#******************************************************************************

# Process the program command line to extract options and arguments.

def getCommandLineOptions():

    # Create the command line option parser.
    optionParser = OptionParser()

    # Specify allowed command-line options.
    optionParser.add_option("-d", "--debug", action = "store_true",
                            default = False,
                            help = "print debugging information")
    optionParser.add_option("-v", "--verbose", action = "store_true",
                            default = False,
                            help = "print verbose output")
    optionParser.add_option("-w", "--warn", action = "store_true",
                            default = False,
                            help = "print warning messages as needed")
    optionParser.add_option("", "--without-root", action = "store_true",
                            default = False,
                            help = "skip ROOT-dependent tests")

    # Parse the command-line options.
    (options, args) = optionParser.parse_args()

    # Return the options.
    return (options, args)

#******************************************************************************

# Use these globals to keep track of the number of the current test,
# starting at 1 (to be compatible with the Perl ok() function), and
# the number of passed and failed tests.
nTest = 0
nPass = 0
nFail = 0

# Run a test command. A test command can be a string (indicating a
# shell command), or a function object (indicating a Python
# function). Both should return 0 (defined as OK) on success, and
# non-0 on failure. In either case, stdout and stderr are redirected
# to the test log file for post-run analysis.

def run_test(cmd):
    global nTest

    # Open the test log to append the results from this test.
    f = open(logFile, "a", 0) # 0 means unbuffered
    f.write("*" * 80 + "\n")

    # Initialize the status to 0 (success).
    status = OK

    # If the test command is a string, it represents a shell
    # command. Otherwise, it is the name of a function to call.
    if type(cmd) == str:
        f.write("Test %d: shell command '%s'\n" % (nTest, cmd))
        try:
            check_call(cmd, shell = True, stdout = f, stderr = STDOUT)
        except CalledProcessError as e:
            status = e.returncode
    else:
        f.write("Test %d: function %s()\n" % (nTest, cmd.__name__))
        stdout_orig = sys.stdout # Should be console
        stderr_orig = sys.stderr # Should be console
        sys.stdout = f           # Send stdout to file
        sys.stderr = f           # Send stderr to file
        status = cmd()
        sys.stdout = stdout_orig # Back to console
        sys.stderr = stderr_orig # Back to console

    # Close the test log.
    f.close()

    # Return the test status.
    return status

#******************************************************************************

# Run a single unit test. If it succeeds (returns a value of 0), print
# the 'ok' message. Otherwise, print the 'not ok' message.

def ok(testCmd, testName):
    global nTest
    global nPass
    global nFail
    nTest += 1
    status = run_test(testCmd)
    if status == OK:
        print("ok %s - %s" % (nTest, testName))
        nPass += 1
    else:
        print("not ok %s - %s" % (nTest, testName))
        nFail += 1

#******************************************************************************

# Begin main program.
if __name__ == "__main__":

    # Process the command line.
    (options, args) = getCommandLineOptions()
    if options.debug:
        print("options = %s" % options)
        print("args = %s" % args)

    #--------------------------------------------------------------------------

    # Prepend the syspfiles directory from the ScienceTools
    # installatiomn toi the PFILES path.
    environ['PFILES'] = environ['CONDA_PREFIX'] + '/share/fermitools/syspfiles' + ':' + \
                        environ['PFILES']

    # Fetch the test start time.
    tStart = times()[4]
    if options.debug: print("tStart = %s" % tStart)

    # Run each pulsar test.
    if options.without_root == False:
        for pulsarTest in pulsarTestsWithRoot:
            ok(pulsarTest["cmd"], pulsarTest["name"])
    else:
        for pulsarTest in pulsarTestsWithoutRoot:
            ok(pulsarTest["cmd"], pulsarTest["name"])

    # Fetch the test stop time.
    tStop = times()[4]
    if options.debug: print("tStop = %s" % tStop)

    # Compute the elapsed time for the tests.
    tElapsed = tStop - tStart
    if options.debug: print("tElapsed = %s" % tElapsed)

    # Fetch the name of the testing machine.
    hostname = node()
    if options.debug: print("hostname = %s" % hostname)

    # Print the test summary.
    print("Total run time: %s s on %s" % (tElapsed, hostname))
    print("1..%d" % nTest)
    if nFail > 0:
        print("# Looks like you failed %d %s of %d" % \
              (nFail, "test" if nFail == 1 else "tests", nTest))
