/** @file  tst_psMemory.c
*
*  @brief Contains the tests for psMemory.[ch]
*
*
*  @author Robert DeSonia, MHPCC
*
*  @version $Revision: 1.25 $ $Name: not supported by cvs2svn $
*  @date $Date: 2004-09-14 23:55:47 $
*
*  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
*/

// need to allow malloc for callback use
#define PS_ALLOW_MALLOC 1

#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#include <stdlib.h>


#include "psTest.h"
#include "pslib.h"

static int TPFreeReferencedMemory( void );
static int TPOutOfMemory( void );
static int TPReallocOutOfMemory( void );
static void* TPOutOfMemoryExhaustedCallback( size_t size );
static int TPCheckBufferPositive( void );
static int TPrealloc( void );
static int TPallocCallback( void );
static psMemoryId memAllocateCallback( const psMemBlock *ptr );
static psMemoryId memFreeCallback( const psMemBlock *ptr );
static int TPcheckLeaks( void );
static int TPmemCorruption( void );
static int TPmultipleFree( void );
void memProblemCallback( const psMemBlock *ptr, const char *file, int lineno );

static int problemCallbackCalled = 0;
static int allocCallbackCalled = 0;
static int freeCallbackCalled = 0;
static int exhaustedCallbackCalled = 0;

testDescription tests[] = {
                              {
                                  TPCheckBufferPositive, 449, "checkBufferPositive", 0, false
                              },
                              {
                                  TPOutOfMemory, 450, "outOfMemory", -6, false
                              },
                              {
                                  TPReallocOutOfMemory, 562, "reallocOutOfMemory", -6, false
                              },
                              {
                                  TPrealloc, 451, "psRealloc", 0, false
                              },
                              {
                                  TPallocCallback, 452, "allocCallback", 0, false
                              },
                              {
                                  TPallocCallback, 453, "allocCallback2", 0, true
                              },
                              {
                                  TPcheckLeaks, 454, "checkLeaks", 0, false
                              },
                              {
                                  TPmemCorruption, 455, "psMemCorruption", 0, false
                              },
                              {
                                  TPFreeReferencedMemory, 456, "freeReferencedMemory", 0, false
                              },
                              {
                                  TPmultipleFree, 699, "multipleFree", 0, false
                              },
                              {
                                  NULL
                              }
                          };

int main( int argc, char* argv[] )
{
    psLogSetLevel( PS_LOG_INFO );

    return ( ! runTestSuite( stderr, "psMemory", tests, argc, argv ) );
}

// Testpoint #449, psAlloc shall allocate memory blocks writeable by caller.
int TPCheckBufferPositive( void )
{
    int * mem;
    const int size = 100;
    int failed = 0;

    psLogMsg( __func__, PS_LOG_INFO, "psAlloc shall allocate memory blocks writeable by caller.\n" );

    mem = ( int* ) psAlloc( size * sizeof( int ) );
    if ( mem == NULL ) {
        psError( __FILE__, "psAlloc returned a NULL value in %s!", __func__ );
        return 1;
    }

    for ( int index = 0;index < size;index++ ) {
        mem[ index ] = index;
    }

    for ( int index = 0;index < size;index++ ) {
        if ( mem[ index ] != index ) {
            failed++;
        }
    }

    psFree( mem );

    return failed;
}

int TPFreeReferencedMemory( void )
{
    // create memory
    int * mem;
    int ref = 0;

    psLogMsg( __func__, PS_LOG_INFO, "memory reference count shall be incrementable/decrementable" );

    mem = ( int* ) psAlloc( 100 * sizeof( int ) );

    ref = psMemGetRefCounter( mem );
    if ( ref != 1 ) {
        psError( __func__, "Expected to buffer reference count to be initially 1, but it was %d.", ref );
        return 1;
    }

    psMemIncrRefCounter( mem );
    psMemIncrRefCounter( mem );
    psMemIncrRefCounter( mem );

    ref = psMemGetRefCounter( mem );
    if ( ref != 4 ) {
        psError( __func__, "Expected to find buffer reference count to be 4, but it was %d.", ref );
        return 1;
    }

    psMemDecrRefCounter( mem );
    psMemDecrRefCounter( mem );

    ref = psMemGetRefCounter( mem );
    if ( ref != 2 ) {
        psError( __func__, "Expected to find buffer reference count to be 2, but it was %d.", ref );
        return 1;
    }

    psLogMsg( __func__, PS_LOG_INFO, "psFree shall be just decrement a multiple refererenced pointer." );

    psFree( mem );

    ref = psMemGetRefCounter( mem );
    if ( ref != 1 ) {
        psError( __func__, "Expected to find buffer reference count to be 1, but it was %d.", ref );
        return 1;
    }

    psFree( mem );

    return 0;
}

// Bug/Task #562 regression test.  Upon requesting more memory than is available, psRealloc shall call
// the psMemExhaustedCallback.
int TPReallocOutOfMemory( void )
{
    int * mem[ 100 ];
    psMemExhaustedCallback cb;

    for ( int lcv = 0; lcv < 100; lcv++ ) {
        mem[ lcv ] = NULL;
    }

    psLogMsg( __func__, PS_LOG_INFO, "Upon requesting more memory than is available, psRealloc shall call "
              "the psMemExhaustedCallback.\n" );

    exhaustedCallbackCalled = 0;

    cb = psMemExhaustedCallbackSet( TPOutOfMemoryExhaustedCallback );

    for ( int lcv = 0; lcv < 100; lcv++ ) {
        mem[ lcv ] = ( int* ) psAlloc( 10 );
    }

    for ( int lcv = 0; lcv < 100; lcv++ ) {
        mem[ lcv ] = ( int* ) psRealloc( mem[ lcv ], SIZE_MAX/2 - 1000 );
    }

    psMemExhaustedCallbackSet( cb );

    if ( exhaustedCallbackCalled == 0 ) {
        psError( __FILE__, "Called psRealloc with HUGE memory requirement and survived in %s!", __func__ );
        return 1;
    }

    for ( int lcv = 0; lcv < 100; lcv++ ) {
        psFree( mem[ lcv ] );
    }

    return 0;
}
// Testpoint #450,  Upon requesting more memory than is available, psalloc shall call
// the psMemExhaustedCallback.
int TPOutOfMemory( void )
{
    int * mem[ 100 ];
    psMemExhaustedCallback cb;

    for ( int lcv = 0; lcv < 100; lcv++ ) {
        mem[ lcv ] = NULL;
    }

    psLogMsg( __func__, PS_LOG_INFO, "Upon requesting more memory than is available, psalloc shall call "
              "the psMemExhaustedCallback.\n" );

    exhaustedCallbackCalled = 0;

    cb = psMemExhaustedCallbackSet( TPOutOfMemoryExhaustedCallback );

    for ( int lcv = 0; lcv < 100; lcv++ ) {
        mem[ lcv ] = ( int* ) psAlloc( SIZE_MAX/2 - 1000 );
    }

    psMemExhaustedCallbackSet( cb );

    if ( exhaustedCallbackCalled == 0 ) {
        psError( __FILE__, "Called psAlloc with HUGE memory requirement and survived in %s!", __func__ );
        return 1;
    }

    for ( int lcv = 0; lcv < 100; lcv++ ) {
        psFree( mem[ lcv ] );
    }

    return 0;
}

// Testpoint #451,  psRealloc shall increase/decrease memory buffer while preserving contents
int TPrealloc( void )
{
    int * mem1;
    int* mem2;
    int* mem3;
    const int initialSize = 100;

    psLogMsg( __func__, PS_LOG_INFO, "psRealloc shall increase/decrease memory buffer while "
              "preserving contents" );

    // allocate buffer with known values.
    mem1 = ( int* ) psAlloc( initialSize * sizeof( int ) );
    mem2 = ( int* ) psAlloc( initialSize * sizeof( int ) );
    mem3 = ( int* ) psAlloc( initialSize * sizeof( int ) );
    for ( int lcv = 0;lcv < initialSize;lcv++ ) {
        mem1[ lcv ] = mem2[ lcv ] = mem3[ lcv ] = lcv;
    }

    psMemCheckCorruption( 1 );
    psLogMsg( __func__, PS_LOG_INFO, "Expanding memory buffer." );

    // realloc to 2x
    mem1 = ( int* ) psRealloc( mem1, 2 * initialSize * sizeof( int ) );
    mem2 = ( int* ) psRealloc( mem2, 2 * initialSize * sizeof( int ) );
    mem3 = ( int* ) psRealloc( mem3, 2 * initialSize * sizeof( int ) );

    // check values of initial block
    for ( int i = 0;i < initialSize;i++ ) {
        if ( mem1[ i ] != i || mem2[ i ] != i || mem3[ i ] != i ) {
            psError( __FILE__, "Realloc didn't preserve the contents with expanding buffer in %s.",
                     __func__ );
            break;
        }
    }

    psMemCheckCorruption( 1 );
    psLogMsg( __func__, PS_LOG_INFO, "Shrinking memory buffer." );

    // realloc to 1/2 initial value.
    mem1 = ( int* ) psRealloc( mem1, ( initialSize / 2 ) * sizeof( int ) );
    mem2 = ( int* ) psRealloc( mem2, ( initialSize / 2 ) * sizeof( int ) );
    mem3 = ( int* ) psRealloc( mem3, ( initialSize / 2 ) * sizeof( int ) );

    // check values of initial block
    for ( int i = 0;i < initialSize / 2;i++ ) {
        if ( mem1[ i ] != i || mem2[ i ] != i || mem3[ i ] != i ) {
            psError( __FILE__, "Realloc didn't preserve the contents with shrinking buffer in %s.",
                     __func__ );
            break;
        }
    }

    psFree( mem1 );
    psFree( mem2 );
    psFree( mem3 );

    return 0;
}

int TPallocCallback( void )
{
    int * mem1;
    int* mem2;
    int* mem3;
    int currentId = psMemGetId();
    const int initialSize = 100;
    int mark;

    allocCallbackCalled = 0;
    freeCallbackCalled = 0;
    psMemAllocateCallbackSet( memAllocateCallback );
    psMemFreeCallbackSet( memFreeCallback );

    psMemAllocateCallbackSetID( currentId + 1 );
    psMemFreeCallbackSetID( currentId + 1 );

    psLogMsg( __func__, PS_LOG_INFO, "call to psAlloc/psRealloc shall generate a callback if specified "
              "memory ID is allocated." );

    // allocate buffer with known values.
    mem1 = ( int* ) psAlloc( initialSize * sizeof( int ) );
    mem2 = ( int* ) psAlloc( initialSize * sizeof( int ) );
    mem3 = ( int* ) psAlloc( initialSize * sizeof( int ) );

    psFree( mem1 );
    psFree( mem2 );
    psFree( mem3 );

    if ( allocCallbackCalled != 2 || freeCallbackCalled != 2 ) {
        psError( __FILE__, "alloc/free callbacks were not called the proper number of times in %s",
                 __func__ );
        return 1;
    }

    allocCallbackCalled = 0;
    freeCallbackCalled = 0;

    mark = psMemGetId();

    mem1 = ( int* ) psAlloc( initialSize * sizeof( int ) );

    psMemAllocateCallbackSetID( mark );

    mem1 = ( int* ) psRealloc( mem1, initialSize * 2 * sizeof( int ) );

    psFree( mem1 );

    if ( allocCallbackCalled != 2 ) {
        psError( __FILE__, "realloc callbacks were not called the proper number of times in %s",
                 __func__ );
        return 1;
    }

    return 0;

}

int TPcheckLeaks( void )
{
    const int numBuffers = 5;
    int* buffers[ 5 ];
    int lcv;
    int currentId = psMemGetId();
    psMemBlock** blks;
    int nLeaks = 0;
    int lineMark = 0;

    psLogMsg( __func__, PS_LOG_INFO, "psMemCheckLeaks shall return the number of blocks above an ID "
              "that are still allocated" );

    for ( lcv = 0;lcv < numBuffers;lcv++ ) {
        lineMark = __LINE__ + 1;
        buffers[ lcv ] = psAlloc( sizeof( int ) );
    }

    for ( lcv = 1;lcv < numBuffers;lcv++ ) {
        psFree( buffers[ lcv ] );
    }

    psLogMsg( __func__, PS_LOG_INFO, "following psMemCheckLeaks call should produce one instance." );

    nLeaks = psMemCheckLeaks( currentId, &blks, stderr );

    if ( nLeaks != 1 ) {
        psError( __FILE__, "psMemCheckLeaks should have found 1 leak, but found %d in %s.", nLeaks, __func__ );
        return 1;
    }

    if ( blks[ 0 ] ->lineno != lineMark ) {
        psError( __FILE__, "psMemCheckLeaks found a leak other than the expected one (line %d vs %d) in %s.",
                 lineMark, blks[ 0 ] ->lineno, __func__ );
        return 1;
    }

    psFree( buffers[ 0 ] );
    psFree( blks );

    psLogMsg( __func__, PS_LOG_INFO, "Testing psMemCheckLeaks again with a different leak location" );
    psMemCheckLeaks(currentId,NULL,stderr);

    for ( lcv = 0;lcv < numBuffers;lcv++ ) {
        lineMark = __LINE__ + 1;
        buffers[ lcv ] = psAlloc( sizeof( int ) );
    }

    for ( lcv = 0;lcv < numBuffers - 1;lcv++ ) {
        psFree( buffers[ lcv ] );
    }

    psLogMsg( __func__, PS_LOG_INFO, "following psMemCheckLeaks call should produce one error." );

    nLeaks = psMemCheckLeaks( currentId, &blks, stderr );

    if ( nLeaks != 1 ) {
        psError( __FILE__, "psMemCheckLeaks should have found 1 leak, but found %d in %s.", nLeaks, __func__ );
        return 1;
    }

    if ( blks[ 0 ] ->lineno != lineMark ) {
        psError( __FILE__, "psMemCheckLeaks found a leak other than the expected one in %s.", __func__ );
        return 1;
    }

    psFree( buffers[ 4 ] );
    psFree( blks );

    psLogMsg( __func__, PS_LOG_INFO, "Testing psMemCheckLeaks again with multiple leak locations." );

    for ( lcv = 0;lcv < numBuffers;lcv++ ) {
        lineMark = __LINE__ + 1;
        buffers[ lcv ] = psAlloc( sizeof( int ) );
    }

    for ( lcv = 0;lcv < numBuffers;lcv++ ) {
        if ( lcv % 2 == 0 ) {
            psFree( buffers[ lcv ] );
        }
    }

    psLogMsg( __func__, PS_LOG_INFO, "following psMemCheckLeaks call should produce two errors." );

    nLeaks = psMemCheckLeaks( currentId, &blks, stderr );

    if ( nLeaks != 2 ) {
        psError( __FILE__, "psMemCheckLeaks should have found 1 leak, but found %d in %s.", nLeaks, __func__ );
        return 1;
    }

    if ( blks[ 0 ] ->lineno != lineMark ) {
        psError( __FILE__, "psMemCheckLeaks found a leak other than the expected one in %s.", __func__ );
        return 1;
    }

    psFree( blks );
    psFree( buffers[ 1 ] );
    psFree( buffers[ 3 ] );

    return 0;
}

int TPmemCorruption( void )
{
    int * buffer = NULL;
    int oldValue = 0;
    int corruptions = 0;
    psMemProblemCallback cb;

    psLogMsg( __func__, PS_LOG_INFO, "psMemCheckCorruption shall detect memory corruptions" );

    buffer = psAlloc( sizeof( int ) );

    // cause memory corruption via buffer underflow
    *buffer = 1;
    buffer--;
    oldValue = *buffer;
    *buffer = 2;

    problemCallbackCalled = 0;
    cb = psMemProblemCallbackSet( memProblemCallback );

    psLogMsg( __func__, PS_LOG_INFO, "psMemCheckCorruption should output an error message and "
              "memProblemCallback callback should be called." );

    corruptions = psMemCheckCorruption( 0 );

    // restore the memory problem callback
    psMemProblemCallbackSet( cb );

    // restore the value, 'uncorrupting' the buffer
    *buffer = oldValue;
    buffer++;

    psFree( buffer );

    if ( corruptions != 1 ) {
        psError( __FILE__, "Expected one memory corruption but found %d in %s.",
                 corruptions, __func__ );
        return 1;
    }

    if ( problemCallbackCalled != 1 ) {
        psError( __FILE__, "The memProblemCallback was not invoked but should have been in %s",
                 __func__ );
        return 1;
    }

    return 0;

}

void memProblemCallback( const psMemBlock *ptr, const char *file, int lineno )
{
    psLogMsg( __func__, PS_LOG_INFO, "memory callback called for id %d (%s:%d).",
              ptr->id, file, lineno );
    problemCallbackCalled++;
    return ;
}

psMemoryId memAllocateCallback( const psMemBlock *ptr )
{
    psLogMsg( __func__, PS_LOG_INFO, "block %d was (re)allocated", ptr->id );
    allocCallbackCalled++;
    return 1;
}

psMemoryId memFreeCallback( const psMemBlock *ptr )
{
    psLogMsg( __func__, PS_LOG_INFO, "block %d was freed", ptr->id );
    freeCallbackCalled++;
    return 1;
}

void* TPOutOfMemoryExhaustedCallback( size_t size )
{
    psLogMsg( __func__, PS_LOG_INFO, "Custom MemExhaustedCallback was invoked." );
    exhaustedCallbackCalled++;
    return NULL;
}

int TPmultipleFree( void )
{

    void * buffer = psAlloc( 1024 );

    psFree( buffer );

    psLogMsg( __func__, PS_LOG_INFO, "Next should be an error about multiple freeing." );
    psFree( buffer );

    psErr* err = (psErr*)psErrorLast();

    if (err->code != PS_ERR_MEMORY_DEREF_USAGE) {
        psErrorMsg("psLibTest.sysUtils.tst_psMemory",PS_ERR_UNKNOWN,true,
                   "Multiple psFree calls generated wrong error code (%d)",
                   err->code);
        psFree(err);
        return 1;
    }

    psFree(err);

    return 0;
}
