/** @file  tst_psMemory.c
*
*  @brief Contains the tests for psMemory.[ch]
*
*
*  @author Robert DeSonia, MHPCC
*
*  @version $Revision: 1.31 $ $Name: not supported by cvs2svn $
*  @date $Date: 2005-02-17 19:26:25 $
*
*  Copyright 2004-2005 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 psS32 TPFreeReferencedMemory( void );
static psS32 TPOutOfMemory( void );
static psS32 TPReallocOutOfMemory( void );
static psPtr TPOutOfMemoryExhaustedCallback( size_t size );
static psS32 TPCheckBufferPositive( void );
static psS32 TPrealloc( void );
static psS32 TPallocCallback( void );
static psMemoryId memAllocateCallback( const psMemBlock *ptr );
static psMemoryId memFreeCallback( const psMemBlock *ptr );
static psS32 TPcheckLeaks( void );
static psS32 TPmemCorruption( void );
static psS32 TPmultipleFree( void );
void memProblemCallback( const psMemBlock *ptr, const char *file, psS32 lineno );

static psS32 problemCallbackCalled = 0;
static psS32 allocCallbackCalled = 0;
static psS32 freeCallbackCalled = 0;
static psS32 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", -6, false},
                              {NULL}
                          };

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

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

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

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

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

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

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

    psFree( mem );

    return failed;
}

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

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

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

    ref = psMemGetRefCounter( mem );
    if ( ref != 1 ) {
        psError(PS_ERR_UNKNOWN, true, "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(PS_ERR_UNKNOWN, true, "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(PS_ERR_UNKNOWN, true, "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(PS_ERR_UNKNOWN, true, "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.
psS32 TPReallocOutOfMemory( void )
{
    psS32 * mem[ 100 ];
    psMemExhaustedCallback cb;

    for ( psS32 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 ( psS32 lcv = 0; lcv < 100; lcv++ ) {
        mem[ lcv ] = ( psS32* ) psAlloc( 10 );
    }

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

    psMemExhaustedCallbackSet( cb );

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

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

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

    for ( psS32 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 ( psS32 lcv = 0; lcv < 100; lcv++ ) {
        mem[ lcv ] = ( psS32* ) psAlloc( SIZE_MAX/2 - 1000 );
    }

    psMemExhaustedCallbackSet( cb );

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

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

    return 0;
}

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

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

    // allocate buffer with known values.
    mem1 = ( psS32* ) psAlloc( initialSize * sizeof( psS32 ) );
    mem2 = ( psS32* ) psAlloc( initialSize * sizeof( psS32 ) );
    mem3 = ( psS32* ) psAlloc( initialSize * sizeof( psS32 ) );
    for ( psS32 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 = ( psS32* ) psRealloc( mem1, 2 * initialSize * sizeof( psS32 ) );
    mem2 = ( psS32* ) psRealloc( mem2, 2 * initialSize * sizeof( psS32 ) );
    mem3 = ( psS32* ) psRealloc( mem3, 2 * initialSize * sizeof( psS32 ) );

    // check values of initial block
    for ( psS32 i = 0;i < initialSize;i++ ) {
        if ( mem1[ i ] != i || mem2[ i ] != i || mem3[ i ] != i ) {
            psError(PS_ERR_UNKNOWN,true, "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 = ( psS32* ) psRealloc( mem1, ( initialSize / 2 ) * sizeof( psS32 ) );
    mem2 = ( psS32* ) psRealloc( mem2, ( initialSize / 2 ) * sizeof( psS32 ) );
    mem3 = ( psS32* ) psRealloc( mem3, ( initialSize / 2 ) * sizeof( psS32 ) );

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

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

    return 0;
}

psS32 TPallocCallback( void )
{
    psS32 * mem1;
    psS32* mem2;
    psS32* mem3;
    psS32 currentId = psMemGetId();
    const psS32 initialSize = 100;
    psS32 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 = ( psS32* ) psAlloc( initialSize * sizeof( psS32 ) );
    mem2 = ( psS32* ) psAlloc( initialSize * sizeof( psS32 ) );
    mem3 = ( psS32* ) psAlloc( initialSize * sizeof( psS32 ) );

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

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

    allocCallbackCalled = 0;
    freeCallbackCalled = 0;

    mark = psMemGetId();

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

    psMemAllocateCallbackSetID( mark );

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

    psFree( mem1 );

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

    return 0;

}

psS32 TPcheckLeaks( void )
{
    const psS32 numBuffers = 5;
    psS32* buffers[ 5 ];
    psS32 lcv;
    psS32 currentId = psMemGetId();
    psMemBlock** blks;
    psS32 nLeaks = 0;
    psS32 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( psS32 ) );
    }

    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, false );

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

    if ( blks[ 0 ] ->lineno != lineMark ) {
        psError(PS_ERR_UNKNOWN,true, "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, false);

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

    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, false );

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

    if ( blks[ 0 ] ->lineno != lineMark ) {
        psError(PS_ERR_UNKNOWN,true, "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( psS32 ) );
    }

    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, false );

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

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

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

    return 0;
}

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

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

    buffer = psAlloc( sizeof( psS32 ) );

    // 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(PS_ERR_UNKNOWN,true, "Expected one memory corruption but found %d in %s.",
                corruptions, __func__ );
        return 1;
    }

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

    return 0;

}

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

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

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

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

psS32 TPmultipleFree( void )
{

    psPtr  buffer = psAlloc( 1024 );

    psFree( buffer );

    psLogMsg( __func__, PS_LOG_INFO, "Next should abort due to multiple freeing." );
    psFree( buffer );

    psError(PS_ERR_UNKNOWN,true,
            "Multiple psFree call survived");

    return 0;
}
