//
// C Implementation: psTest
//
// Description:
//
//
// Author: Robert DeSonia <robert.desonia@mhpcc.hpc.mil>, (C) 2004
//
// Copyright: See COPYING file that comes with this distribution
//
//


#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdbool.h>
#include <string.h>

#include "psTest.h"
#include "psMemory.h"
#include "psError.h"
#include "psTrace.h"

#define HEADER_TOP    "/***************************** TESTPOINT ******************************************\\\n"
#define HEADER_LINE_STRING   "* %20s: %-58s *\n"
#define HEADER_LINE_INT      "* %20s: %-58d *\n"
#define HEADER_BOTTOM "\\**********************************************************************************/\n\n"

psBool p_runTestSuite( FILE *fp, const char* testPointFile, const char* packageName,
                       testDescription tests[], psS32 argc, char * const argv[] )
{
    psBool success = true;
    psBool runAll = true;
    psBool useFork = true;
    psBool found;
    psS32 c;
    psS32 n;
    extern char *optarg;

    if ( argc > 0 ) {
        while ( ( c = getopt( argc, argv, "lhn:dt:" ) ) != -1 ) {
            switch ( c ) {
            case 'h':
                printf( "Usage: %s [-l] [-d] [-h] [-n=Testpoint#] [-t=TestpointName]\n"
                        "    where:\n"
                        "           -l  : lists the testpoints contained in this test driver executable\n"
                        "           -d  : turns on debugger-friendly mode (no forking, aborts/signals are not handled)\n"
                        "           -h  : prints this help\n"
                        "           -n  : specifies a particular testpoint by number to run\n"
                        "           -t  : specifies a particular testpoint by name to run\n"
                        "    (if no -l, -n or -t options are given, all testpoints are run)\n",
                        argv[ 0 ] );
                runAll = false;
                break;
            case 'd':
                useFork = false;
                break;
            case 'l':
                printf( "Test Driver:  %s\n", testPointFile );
                printf( "Package Name: %s\n", packageName );
                printf( "Testpoints:\n" );
                runAll = false;
                for ( psS32 index = 0; tests[ index ].fcn != NULL; index++ ) {
                    printf( "    %6d - %s \n", tests[ index ].testPointNumber,
                            tests[ index ].testPointName );
                }
                printf( "\n" );
                break;
            case 't':
                runAll = false;
                for ( psS32 index = 0; tests[ index ].fcn != NULL; index++ ) {
                    if ( strcmp( optarg, tests[ index ].testPointName ) == 0 ) {
                        success = p_runTest( fp,
                                             testPointFile,
                                             packageName,
                                             tests[ index ].testPointName,
                                             tests[ index ].fcn,
                                             tests[ index ].expectedReturn,
                                             useFork ) && success;
                    }
                }
                break;
            case 'n':
                runAll = false;
                if ( sscanf( optarg, "%i", &n ) != 1 ) {
                    psError(PS_ERR_UNKNOWN, true, "Failed to parse the testpoint number (%s).",
                            optarg );
                    break;
                }
                found = false;
                for ( psS32 index = 0; tests[ index ].fcn != NULL; index++ ) {
                    if ( n == tests[ index ].testPointNumber ) {
                        found = true;
                        success = p_runTest( fp,
                                             testPointFile,
                                             packageName,
                                             tests[ index ].testPointName,
                                             tests[ index ].fcn,
                                             tests[ index ].expectedReturn,
                                             useFork ) && success;
                    }
                }
                if ( ! found ) {
                    psError(PS_ERR_UNKNOWN, true, "The specified testpoint number (%d) doesn't exist in this test driver.",
                            n );
                    break;
                }
                break;
            case '?':
                psError(PS_ERR_UNKNOWN, true, "Option %s is not recognized and is ignored.", optarg );
                break;
            }
        }
    }

    if ( runAll ) {
        for ( psS32 index = 0; tests[ index ].fcn != NULL; index++ ) {
            if ( ! tests[ index ].isDuplicateEntry ) {
                success = p_runTest( fp,
                                     testPointFile,
                                     packageName,
                                     tests[ index ].testPointName,
                                     tests[ index ].fcn,
                                     tests[ index ].expectedReturn,
                                     useFork ) && success;
            }
        }
    }

    if ( ! success ) {
        psError( PS_ERR_UNKNOWN, true,
                 "One or more tests failed" );
    }

    return success;
}

psBool p_runTest( FILE *fp, const char* testPointFile, const char* packageName, const char* testPointName,
                  testFcn fcn, psS32 expectedReturn, psBool useFork )
{
    psS32 childReturn = 0;
    pid_t child;

    p_printPositiveTestHeader( fp, testPointFile, packageName, testPointName );

    if ( useFork ) {
        child = fork();
        if ( child == 0 ) {                   // I am the child process, run the test
            psS32 currentId = psMemGetId();
            psS32 retVal = fcn();
            fflush(stdout);
            fflush(stderr);
            if ( retVal == 0 ) { // only bother checking memory if test executed to end.
                if ( psMemCheckLeaks( currentId, NULL, stderr, false ) != 0 ) {
                    psError(PS_ERR_UNKNOWN, true, "Memory Leaks Detected" );
                    retVal = 64;
                }
                psMemCheckCorruption( 1 );
            }
            exit( retVal );
        } else
            if ( child < 0 ) {
                fprintf( fp, "Couldn't fork a process to run a negative test (%s|%s)",
                         packageName, testPointName );
                abort();
            }

        waitpid( child, &childReturn, 0 );
        if ( WIFSIGNALED( childReturn ) ) {
            childReturn = -WTERMSIG( childReturn );
        } else {
            childReturn = WEXITSTATUS( childReturn );
        }
    } else {
        psS32 currentId = psMemGetId();
        childReturn = fcn();
        fflush(stdout);
        fflush(stderr);
        if ( childReturn == 0 ) { // only bother checking memory if test executed to end.
            if ( psMemCheckLeaks( currentId, NULL, stderr, false ) != 0 ) {
                psError(PS_ERR_UNKNOWN, true, "Memory Leaks Detected" );
                childReturn = 64;
            }
            psMemCheckCorruption( 1 );
        }
    }


    if ( childReturn != expectedReturn ) {
        fprintf( fp, "Return value mismatch: expected %d, got %d",
                 expectedReturn, childReturn );
    }

    p_printFooter( fp, testPointFile, packageName, testPointName,
                   ( childReturn == expectedReturn ) );

    return ( childReturn == expectedReturn );
}

void p_printPositiveTestHeader( FILE *fp,
                                const char* testPointFile,
                                const char* packageName,
                                const char* testPointName )
{
    char TP[ 80 ];

    snprintf( TP, 80, "%s{%s}", packageName, testPointName );

    fprintf( fp, HEADER_TOP );
    fprintf( fp, HEADER_LINE_STRING, "TestFile", testPointFile );
    fprintf( fp, HEADER_LINE_STRING, "TestPoint", TP );
    fprintf( fp, HEADER_LINE_STRING, "TestType", "Positive" );
    fprintf( fp, HEADER_BOTTOM );
}

void p_printNegativeTestHeader( FILE *fp,
                                const char* testPointFile,
                                const char* packageName,
                                const char* testPointName,
                                const char* expectedError,
                                psS32 exitValue )
{
    char TP[ 80 ];

    snprintf( TP, 80, "%s{%s}", packageName, testPointName );

    fprintf( fp, HEADER_TOP );
    fprintf( fp, HEADER_LINE_STRING, "TestFile", testPointFile );
    fprintf( fp, HEADER_LINE_STRING, "TestPoint", TP );
    fprintf( fp, HEADER_LINE_STRING, "TestType", "Negative" );
    fprintf( fp, HEADER_LINE_STRING, "ExpectedErrorText", expectedError );
    fprintf( fp, HEADER_LINE_INT, "ExpectedStatusValue", exitValue );
    fprintf( fp, HEADER_BOTTOM );
}


void p_printFooter( FILE *fp,
                    const char* testPointFile,
                    const char* packageName,
                    const char* testPointName,
                    psBool success )
{
    if ( success ) {
        fprintf( fp, "\n---> TESTPOINT PASSED (%s{%s} | %s)\n\n", packageName, testPointName, testPointFile );
    } else {
        fprintf( fp, "\n---> TESTPOINT FAILED (%s{%s} | %s)\n\n", packageName, testPointName, testPointFile );
    }
}
