//
// The following tests need to be added:
//
//  -   Run the bad-shutdown test on an empty log (forcing a special path
//      in CalcLogInfo).
//



#include "pch.cxx"
#pragma hdrstop

#define TRKDATA_ALLOCATE
#include "trkwks.hxx"



BOOL g_fNotified = FALSE;
extern ULONG g_Debug;
BOOL g_fTLogDebug = FALSE;
CTrkWksConfiguration g_cTrkWksConfiguration;
CWorkManager g_WorkManager;
CTestLog g_cTestLog( &g_cTrkWksConfiguration, &g_WorkManager );

inline void TestRaise( HRESULT hr, TCHAR *ptszMessage, va_list Arguments )
{
    CHAR szHR[8];

    sprintf( szHR, "%#08X", hr );

    if( NULL != ptszMessage )
        TrkLogErrorRoutineInternal( TRKDBG_ERROR, szHR, ptszMessage, Arguments );

    RaiseException( hr, 0, 0, NULL );
}

inline void TestRaiseException( HRESULT hr, TCHAR *ptszMessage = NULL, ... )
{
    va_list Arguments;
    va_start( Arguments, ptszMessage );

    TestRaise( hr, ptszMessage, Arguments );
}

class CTestLogCallback : public PLogCallback
{
    void OnEntriesAvailable();
};

void
CTestLogCallback::OnEntriesAvailable()
{
    g_fNotified = TRUE;
}



CTestLog::CTestLog( CTrkWksConfiguration *pTrkWksConfiguration, CWorkManager *pWorkManager )
{

    _pTrkWksConfiguration = pTrkWksConfiguration;
    _pWorkManager = pWorkManager;
    *_tszLogFile = TEXT('\0');

}   // CTestLog::CTestLog


void
CTestLog::ReInitialize()
{
    _cLogFile.UnInitialize();
    DeleteFile( _tszLogFile );
    _cLogFile.Initialize( _tszLogFile, _pTrkWksConfiguration, &_cSimpleTimer );
    _cLog.Initialize( NULL, _pTrkWksConfiguration, &_cLogFile );
}

void
CTestLog::GenerateLogName()
{
    DWORD dw;
    TCHAR tszRootPath[ MAX_PATH + 1 ];
    DWORD cSectorsPerCluster, cNumberOfFreeClusters, cTotalNumberOfClusters;

    if( TEXT('\0') != *_tszLogFile )
        return;

    // Generate a log file name

    if( !GetCurrentDirectory( sizeof(_tszLogFile), _tszLogFile ))
    {
        TrkLog(( TRKDBG_ERROR, TEXT("Couldn't get the current directory") ));
        TrkRaiseLastError( );
    }

    if( TEXT('\\') != _tszLogFile[ _tcslen(_tszLogFile) ] )
        _tcscat( _tszLogFile, TEXT("\\") );
    _tcscat( _tszLogFile, TEXT("TLog.log") );

    // Calculate the sector size

    TrkAssert( TEXT(':') == _tszLogFile[1] );

    _tcsncpy( tszRootPath, _tszLogFile, sizeof("a:\\") - 1 );
    tszRootPath[ sizeof("a:\\") - 1 ] = TEXT('\0');

    if( !GetDiskFreeSpace( tszRootPath,
                           &cSectorsPerCluster, &_cbSector,
                           &cNumberOfFreeClusters, &cTotalNumberOfClusters ))
    {
        TrkLog(( TRKDBG_ERROR, TEXT("Couldn't get bytes-per-sector value on %s"), tszRootPath ));
        TrkRaiseLastError( );
    }

}   // CTestLog::GenerateLogName()


const TCHAR*
CTestLog::LogFileName()
{
    return( _tszLogFile );
}

ULONG
CTestLog::DataSectorOffset() const
{
    return( _cLogFile._header.NumSectors() * CBSector() );
}

void
CTestLog::CreateLog( PLogCallback *pLogCallback, BOOL fValidate )
{
    TCHAR tszLogFile[ MAX_PATH + 1 ];

    GenerateLogName();
    DeleteFile( _tszLogFile );

    _pWorkManager->Initialize();
    _cSimpleTimer.Initialize( this, _pWorkManager, 0, 0, NULL );
    _cLogFile.Initialize( _tszLogFile, _pTrkWksConfiguration, &_cSimpleTimer );
    _cLog.Initialize( pLogCallback, _pTrkWksConfiguration, &_cLogFile );
    StartTestWorkerThread(_pWorkManager);

    if( fValidate )
        ValidateLog();

}   // CTestLog::CreateLog()



void
CTestLog::Timer( DWORD dwTimerId )
{
    _cLog.Flush( FLUSH_TO_CACHE );
    _cLogFile.SetShutdown( TRUE );
    _cLogFile.Flush( FLUSH_THROUGH_CACHE );
    _cLogFile.OnLogCloseTimer();
}


void
CTestLog::OpenLog( PLogCallback *pLogCallback, BOOL fValidate )
{
    GenerateLogName();

    _pWorkManager->Initialize();
    _cSimpleTimer.Initialize( this, _pWorkManager, 0, 0, NULL );
    _cLogFile.Initialize( _tszLogFile, _pTrkWksConfiguration, &_cSimpleTimer );
    _cLog.Initialize( pLogCallback, _pTrkWksConfiguration, &_cLogFile );
    StartTestWorkerThread(_pWorkManager);

    if( fValidate )
        ValidateLog();

}   // CTestLog::CreateLog()



void
CTestLog::CloseLog()
{
    _pWorkManager->StopWorkerThread();
    WaitTestThreadExit();

    _cLog.Flush( FLUSH_TO_CACHE );
    _cLogFile.SetShutdown( TRUE );
    _cLogFile.Flush( FLUSH_THROUGH_CACHE );

    _cLogFile.UnInitialize();
    _cSimpleTimer.UnInitialize();
    _pWorkManager->UnInitialize();
}



void
CTestLog::Append( ULONG cMoves, const TRKSVR_MOVE_NOTIFICATION rgNotifications[] )
{
    LogMoveNotification lmn;
    SequenceNumber seqOriginal, seqFinal;

    if( _cLog.IsEmpty() )
        seqOriginal = -1;
    else
    {
        _cLogFile.ReadMoveNotification( _cLog._loginfo.ilogLast, &lmn );
        seqOriginal = lmn.seq;
    }

    g_fNotified = FALSE;

    for( ULONG i = 0; i < cMoves; i++ )
    {
        _cLog.Append( rgNotifications[i].droidCurrent,
                      rgNotifications[i].droidNew,
                      rgNotifications[i].droidBirth );
    }

    _cLogFile.ReadMoveNotification( _cLog._loginfo.ilogLast, &lmn );
    seqFinal = lmn.seq;

    if( seqFinal != (SequenceNumber)(seqOriginal + cMoves) )
        TestRaiseException( E_FAIL, TEXT("Incorrect sequence numbers after Append (%d + %d = %d?)\n"),
                           seqOriginal, cMoves, seqFinal );

    if( !g_fNotified )
        TestRaiseException( E_FAIL, TEXT("Didn't receive a notification during an append\n") );

}   // CTestLog::Append()



ULONG
CTestLog::Read( ULONG cRead, TRKSVR_MOVE_NOTIFICATION rgNotifications[], SequenceNumber *pseqFirst  )
{
    _cLog.Read( rgNotifications, pseqFirst, &cRead );
    return( cRead );

}   // CTestLog::ReadLog()

void
CTestLog::ReadExtendedHeader( ULONG iOffset, void *pv, ULONG cb )
{
    _cLogFile.ReadExtendedHeader( iOffset, pv, cb );
}


void
CTestLog::WriteExtendedHeader( ULONG iOffset, const void *pv, ULONG cb )
{
    _cLogFile.WriteExtendedHeader( iOffset, pv, cb );
}

void
CTestLog::ReadAndValidate( ULONG cToRead, ULONG cExpected,
                           const TRKSVR_MOVE_NOTIFICATION rgNotificationsExpected[],
                           TRKSVR_MOVE_NOTIFICATION rgNotificationsRead[],
                           SequenceNumber seqExpected )
{
    ULONG cLogEntriesRead = 0;
    SequenceNumber seq;

    memset( rgNotificationsRead, 0, sizeof(*rgNotificationsRead) * cExpected );

    cLogEntriesRead = Read( cToRead, rgNotificationsRead, &seq );

    if( cLogEntriesRead != cExpected )
    {
        TestRaiseException( E_FAIL, TEXT("Bad read from log; expected %d entries, got %d\n"),
                           cExpected, cLogEntriesRead );
    }

    if( seq != seqExpected
        &&
        0 != cExpected 
       )
    {
        TestRaiseException( E_FAIL, TEXT("Invalid sequence number from log (got %d, expected %d)\n"),
                           seq, seqExpected );
    }

    if( 0 != cExpected )
    {

        for( ULONG i = 0; i < cExpected; i++ )
            if( memcmp( &rgNotificationsExpected[i], &rgNotificationsRead[i], sizeof(rgNotificationsRead[i]) ))
            {
                TestRaiseException( E_FAIL, TEXT("Log entries read don't match that which was expected\n") );
            }
    }

}   // CTestLog::ReadAndValidate()


SequenceNumber
CTestLog::GetNextSeqNumber( )
{
    return( _cLog.GetNextSeqNumber() );

}   // CTestLog::GetLatestSeqNumber()


BOOL
CTestLog::Search( const CDomainRelativeObjId &droid, TRKSVR_MOVE_NOTIFICATION *pNotification )
{
    pNotification->droidCurrent = droid;

    return(  _cLog.Search( pNotification->droidCurrent,
                           &pNotification->droidNew,
                           &pNotification->droidBirth ) );


}   // CTestLog::Search()


void
CTestLog::Seek( SequenceNumber seq )
{

    SequenceNumber seqOriginal;
    LogIndex ilogOriginal;
    LogMoveNotification lmn;

    ilogOriginal = _cLog._loginfo.ilogRead;
    if( ilogOriginal != _cLog._loginfo.ilogWrite )
    {
        _cLogFile.ReadMoveNotification( _cLog._loginfo.ilogRead, &lmn );
        seqOriginal = lmn.seq;
    }

    g_fNotified = FALSE;
    _cLog.Seek( seq );

    if( seq != _cLog._loginfo.seqNext )
    {
        _cLogFile.ReadMoveNotification( _cLog._loginfo.ilogRead, &lmn );

        if( ilogOriginal == _cLog._loginfo.ilogWrite
            ||
            seqOriginal > lmn.seq
          )
        {

            if( !g_fNotified && !_cLog.IsEmpty() )
            {
                TrkLog(( TRKDBG_ERROR, TEXT("Didn't receive a notification after a backwards seek") ));
                TrkRaiseException( E_FAIL );
            }
        }
    }
}   // CTestLog::Seek( SequenceNumber ... )


void
CTestLog::Seek( int origin, int iSeek )
{
    SequenceNumber seqOriginal;
    LogMoveNotification lmn;

    _cLogFile.ReadMoveNotification( _cLog._loginfo.ilogRead, &lmn );

    seqOriginal = _cLog._loginfo.ilogRead == _cLog._loginfo.ilogWrite
                  ? _cLog._loginfo.seqNext
                  : lmn.seq;

    g_fNotified = FALSE;
    _cLog.Seek( origin, iSeek );

    _cLogFile.ReadMoveNotification( _cLog._loginfo.ilogRead, &lmn );

    if( !_cLog.IsRead()
        &&
        seqOriginal > lmn.seq
      )
    {
        if( !g_fNotified && !_cLog.IsEmpty() )
            TestRaiseException( E_FAIL, TEXT("Didn't receive a notification after a backwards seek") );
    }

}   // CTestLog::Seek( origin ... )






void
CTestLog::ValidateLog()
{
    ULONG cEntries = _cLogFile.NumEntriesInFile();
    LogIndex ilogEntry, i, j;
    ULONG *rgiNext = NULL;
    ULONG *rgiPrev = NULL;

    __try
    {
        rgiNext = (ULONG*) new ULONG[ cEntries ];
        rgiPrev = (ULONG*) new ULONG[ cEntries ];

        for( ilogEntry = 0; ilogEntry < cEntries; ilogEntry++ )
        {
            rgiNext[ ilogEntry ] = _cLogFile._sector.GetLogEntry( ilogEntry )->ilogNext;
            rgiPrev[ ilogEntry ] = _cLogFile._sector.GetLogEntry( ilogEntry )->ilogPrevious;
        }


        for( i = 0; i < cEntries; i++ )
        {
            // Validate that the entry pointed to by i->next, points
            // back to i with its prev pointer.

            if( rgiPrev[ rgiNext[i] ] != i )
                TestRaiseException( E_FAIL, TEXT("Two entries don't point to each other:  %d, %d, %d\n"),
                                   i, rgiNext[i], rgiPrev[ rgiNext[i] ] );

            // Verify that noone else's next/prev pointers point to
            // i's next/prev pointers.

            for( j = i+1; j < cEntries; j++ )
            {
                if( rgiNext[i] == rgiNext[j] )
                    TestRaiseException( E_FAIL, TEXT("Two entries in the log have the same next pointer:  %d and %d (point to %d)\n"),
                                       i, j, rgiNext[i] );

                if( rgiPrev[i] == rgiPrev[j] )
                    TestRaiseException( E_FAIL, TEXT("Two entries in the log have the same prev pointer:  %d and %d (point to %d)\n"),
                                       i, j, rgiPrev[i] );

            }
        }


    }
    __finally
    {
        delete[] rgiNext;
        delete[] rgiPrev;
    }


}   // CTestLog::ValidateLog()


ULONG
CTestLog::GetCbLog()
{
    return( _cLogFile._cbLogFile );
}

void
CTestLog::DelayUntilClose()
{
    _tprintf( TEXT("    Sleeping so that the log auto-closes\n") );
    Sleep( 1500 * _pTrkWksConfiguration->GetLogFileOpenTime() );

    if( _cLogFile.IsOpen() )
    {
        TrkLog(( TRKDBG_ERROR, TEXT("After delaying, log file did not close") ));
        TrkRaiseException( E_FAIL );
    }
}


void
CTestLog::MakeEntryOld()
{
    _cLogFile._sector.GetLogEntry( _cLog._loginfo.ilogStart )->move.DateWritten
        -= _pTrkWksConfiguration->_dwLogOverwriteAge + 1;
    _cLogFile.Flush( FLUSH_UNCONDITIONALLY );

}   // CTestLog::MakeStartOld()


ULONG
CTestLog::GetNumEntries()
{
    return( _cLogFile.NumEntriesInFile() );

}   // CTestLog::GetNumEntries()


LogIndex
CTestLog::GetStartIndex()
{
    return( _cLog._loginfo.ilogStart );
}

LogIndex
CTestLog::GetEndIndex()
{
    return( _cLog._loginfo.ilogEnd );
}

LogIndex
CTestLog::GetReadIndex()
{
    return( _cLog._loginfo.ilogRead );
}

void
CTestLog::SetReadIndex( LogIndex ilogRead )
{
    _cLog._loginfo.ilogRead = ilogRead;
    _cLog.RefreshHeader();
    _cLogFile.Flush();
}

BOOL
CTestLog::IsEmpty()
{
    BOOL fReturn;
    fReturn = _cLog.IsEmpty();
    return( fReturn );
}
    

ULONG
CTestLog::NumEntriesInFile( )
{
    ULONG cSectors = _cLogFile._cbLogFile / _cbSector - NUM_HEADER_SECTORS;
    ULONG cEntriesPerSector = (_cbSector-sizeof(LogEntryHeader)) / sizeof(LogEntry);

    return( cSectors * cEntriesPerSector );
}

ULONG
CTestLog::NumEntriesPerSector()
{
    return( ( _cbSector-sizeof(LogEntryHeader) ) / sizeof(LogEntry) );
}

ULONG
CTestLog::NumEntriesPerKB()
{
    return( (1024 / _cbSector) * NumEntriesPerSector() );
}

ULONG
CTestLog::CBSector() const
{
    return( _cbSector );
}



void ReadTest( ULONG cEntries,
               TRKSVR_MOVE_NOTIFICATION rgNotificationsExpected[],
               TRKSVR_MOVE_NOTIFICATION rgNotificationsRead[],
               SequenceNumber seqExpected )
{   
    g_cTestLog.ReadAndValidate( cEntries + 1, cEntries,
                                rgNotificationsExpected, rgNotificationsRead,
                                seqExpected );

    g_cTestLog.ReadAndValidate( cEntries, cEntries,
                                rgNotificationsExpected, rgNotificationsRead,
                                seqExpected );

    if( cEntries > 1 )
    {
        g_cTestLog.ReadAndValidate( cEntries - 1, cEntries - 1,
                                    rgNotificationsExpected, rgNotificationsRead,
                                    seqExpected );


        g_cTestLog.ReadAndValidate( 1, 1,
                                    rgNotificationsExpected, rgNotificationsRead,
                                    seqExpected );
    }
}



void
ExerciseLog( ULONG cEntries, SequenceNumber seqFirst, 
             TRKSVR_MOVE_NOTIFICATION rgNotificationsExpected[],
             TRKSVR_MOVE_NOTIFICATION rgNotificationsRead[] )
{
    SequenceNumber seqExpected = seqFirst;
    SequenceNumber seqRead;
    ULONG cRead;

    ULONG iReadOriginal = g_cTestLog.GetReadIndex();

    ReadTest( cEntries, rgNotificationsExpected, rgNotificationsRead, seqExpected );

    if( 0 != cEntries )
    {
        // Skip forward by one entry

        g_cTestLog.Seek( SEEK_CUR, 1 );
        seqExpected++;

        ReadTest( cEntries-1, &rgNotificationsExpected[1], &rgNotificationsRead[1], seqExpected );

        if( cEntries > 1 )
        {
            // Skip forward by one entry, but using an absolute seek.

            g_cTestLog.Seek( SEEK_SET, 2 );
            seqExpected++;

            ReadTest( cEntries-2, &rgNotificationsExpected[2], &rgNotificationsRead[2], seqExpected );

            // Do a relative seek back in the log.

            g_cTestLog.Seek( SEEK_CUR, -1 );
            seqExpected--;

            ReadTest( cEntries-1, &rgNotificationsExpected[1], &rgNotificationsRead[1], seqExpected );

        }

        // Do a relative seek back to the beginning of the log

        g_cTestLog.Seek( SEEK_CUR, -1000 );
        seqExpected--;

        ReadTest( cEntries, &rgNotificationsExpected[0], &rgNotificationsRead[0], seqExpected );

        // Skip forward by the remaining entries

        g_cTestLog.Seek( SEEK_CUR, cEntries );
        seqExpected += cEntries;

        cRead = g_cTestLog.Read( 1, rgNotificationsRead, &seqRead );
        if( 0 != cRead )
            TestRaiseException( E_FAIL, TEXT("Shouldn't have been able to read an already-read log\n") );

        // Seek to the end (which is where the read index already is), to ensure
        // that nothing happens.

        g_fNotified = FALSE;
        g_cTestLog.Seek( seqFirst + cEntries );

        if( g_fNotified )
            TestRaiseException( E_FAIL, TEXT("A seek-to-current shouldn't have caused a notification") );

        cRead = g_cTestLog.Read( 1, rgNotificationsRead, &seqRead );
        if( 0 != cRead )
            TestRaiseException( E_FAIL, TEXT("Shouldn't have been able to read an already-read log\n") );

        // Over-seek to the end.

        g_fNotified = FALSE;
        g_cTestLog.Seek( SEEK_CUR, 1000 );

        if( g_fNotified )
            TestRaiseException( E_FAIL, TEXT("A seek-to-current shouldn't have caused a notification") );

        cRead = g_cTestLog.Read( 1, rgNotificationsRead, &seqRead );
        if( 0 != cRead )
            TestRaiseException( E_FAIL, TEXT("Shouldn't have been able to read an already-read log\n") );

    }

    // Seek to the start of the log

    g_cTestLog.Seek( seqFirst );
    seqExpected = seqFirst;
    ReadTest( cEntries, rgNotificationsExpected, rgNotificationsRead, seqExpected );

    if( 0 != cEntries )
    {
        // Seek to the end of the log

        seqExpected = seqFirst + (ULONG)(cEntries - 1);
        g_cTestLog.Seek( seqExpected );
        ReadTest( 1, &rgNotificationsExpected[cEntries-1], &rgNotificationsRead[cEntries-1], seqExpected );

        g_cTestLog.Seek( SEEK_CUR, 1 );
    }

    // Search for each of the log entries

    for( ULONG i = 0; i < cEntries; i++ )
    {
        if( !g_cTestLog.Search( rgNotificationsExpected[i].droidCurrent,
                                rgNotificationsRead ))
        {
            TestRaiseException( E_FAIL, TEXT("Search failed to find entry") );
        }

        if( memcmp( &rgNotificationsExpected[i], rgNotificationsRead, sizeof(*rgNotificationsRead) ))
            TestRaiseException( E_FAIL, TEXT("Search failed on entry %d"), i );
    }

    g_cTestLog.SetReadIndex( iReadOriginal );

}   // ExerciseLog()


void
FillAndExerciseLog( ULONG cEntriesOriginal, ULONG cEntriesTotal, SequenceNumber seqFirst,
                    TRKSVR_MOVE_NOTIFICATION rgNotificationsWrite[],
                    TRKSVR_MOVE_NOTIFICATION rgNotificationsRead[] )
{
    // Test the log as-is

    ExerciseLog( cEntriesOriginal, seqFirst, rgNotificationsWrite, rgNotificationsRead );

    // Add an entry to the log and re-test

    g_cTestLog.Append( 1, &rgNotificationsWrite[ cEntriesOriginal ] );
    ExerciseLog( cEntriesOriginal + 1, seqFirst,
                 rgNotificationsWrite, rgNotificationsRead );

    // Test a full log

    g_cTestLog.Append( cEntriesTotal - cEntriesOriginal - 2,
                       &rgNotificationsWrite[ cEntriesOriginal + 1] );
    ExerciseLog( cEntriesTotal - 1, seqFirst,
                 rgNotificationsWrite, rgNotificationsRead );

}


ULONG
LogIndex2SectorIndex( ULONG cbSector, LogIndex ilog )
{
    ULONG cEntriesPerSector = ( cbSector - sizeof(LogEntryHeader) ) / sizeof(LogEntry);
    return( ilog / cEntriesPerSector + NUM_HEADER_SECTORS );
}



void
ReadLogSector( HANDLE hFile, LogIndex ilog, ULONG cbSector, BYTE rgbSector[] )
{
    ULONG iSector = LogIndex2SectorIndex( cbSector, ilog );
    ULONG cb;

    if( 0xFFFFFFFF == SetFilePointer(hFile, iSector * cbSector, NULL, FILE_BEGIN ))
    {
        TrkLog(( TRKDBG_ERROR, TEXT("Couldn't seek file to %lu (in test)"), iSector*cbSector ));
        TrkRaiseLastError( );
    }

    if( !ReadFile( hFile, rgbSector, cbSector, &cb, NULL )
        ||
        cbSector != cb
      )
    {
        TrkLog(( TRKDBG_ERROR, TEXT("Couldn't read from logfile (in test), cbRead = %d"), cb ));
        TrkRaiseLastError( );
    }
}

void
WriteLogSector( HANDLE hFile, LogIndex ilog, ULONG cbSector, BYTE rgbSector[] )
{
    ULONG iSector = LogIndex2SectorIndex( cbSector, ilog );
    ULONG cb;

    if( 0xFFFFFFFF == SetFilePointer(hFile, iSector * cbSector, NULL, FILE_BEGIN ))
    {
        TrkLog(( TRKDBG_ERROR, TEXT("Couldn't seek file to %lu (in test)"), iSector*cbSector ));
        TrkRaiseLastError( );
    }

    if( !WriteFile( hFile, rgbSector, cbSector, &cb, NULL )
        ||
        cbSector != cb
      )
    {
        TrkLog(( TRKDBG_ERROR, TEXT("Couldn't write to logfile (in test), cbWritten = %d"), cb ));
        TrkRaiseLastError( );
    }
}


void
CreateNewLog( CTestLogCallback *pcTestLogCallback, ULONG *pcLogEntries, ULONG *piNotifications,
              SequenceNumber *pseqFirst)
{

    _tprintf( TEXT("    Creating a log") );

    g_cTrkWksConfiguration._dwMinLogKB = 1;
    g_cTrkWksConfiguration._dwMaxLogKB = 1;

    g_cTestLog.CreateLog( pcTestLogCallback );

    *pcLogEntries = g_cTestLog.NumEntriesInFile();
    *piNotifications = 0;
    *pseqFirst = 0;

    if( 0 != g_cTestLog.GetNextSeqNumber( ))
    {
        TrkLog(( TRKDBG_ERROR, TEXT("Next sequence number should be zero after a create") ));
        TrkRaiseException( E_FAIL );
    }

    _tprintf( TEXT(" (%d entries)\n"), *pcLogEntries );

}



EXTERN_C void __cdecl _tmain( int argc, TCHAR *argv[] )
{
    CTestLogCallback cTestLogCallback;

    TRKSVR_MOVE_NOTIFICATION rgNotificationsWritten[ 50 ];
    TRKSVR_MOVE_NOTIFICATION rgNotificationsRead[ 50 ];
    TRKSVR_MOVE_NOTIFICATION tempNotificationWrite, tempNotificationRead;

    __try
    {

        ULONG cLogEntries = 0;
        ULONG i;
        DWORD dw;
        ULONG cRead, cb, cbFile;
        SequenceNumber seqFirst = 0;
        BOOL fAppendFailed;
        HANDLE hFile = INVALID_HANDLE_VALUE;
        BYTE rgbSector[ 2048 ];
        LogHeader *plogheader = NULL;
        LogEntry *plogentry = NULL;
        ULONG iNotifications = 0;

        BYTE rgbExtendedHeaderWrite[ 16 ];
        BYTE rgbExtendedHeaderRead[ 16 ];

        LogIndex ilogStart, ilogEnd;


        //  --------------
        //  Initialization
        //  --------------

        _tprintf( TEXT("\nCLog Unit Test\n") );
        _tprintf( TEXT(  "==============\n\n") );

        if( argc > 1 )
        {
            if( !_tcscmp( TEXT("/D"), argv[1] )
                ||
                !_tcscmp( TEXT("/d"), argv[1] )
                ||
                !_tcscmp( TEXT("-D"), argv[1] )
                ||
                !_tcscmp( TEXT("-d"), argv[1] )
              )
            {
                g_fTLogDebug = TRUE;
            }
        }   // if( argc > 1 )


        TrkDebugCreate( TRK_DBG_FLAGS_WRITE_TO_DBG | (g_fTLogDebug ? TRK_DBG_FLAGS_WRITE_TO_STDOUT : 0), "TLog" );

        g_cTrkWksConfiguration.Initialize();

        g_cTrkWksConfiguration._dwMinLogKB = 1;
        g_cTrkWksConfiguration._dwMaxLogKB = 1;
        g_cTrkWksConfiguration._dwLogDeltaKB = 1;
        g_cTrkWksConfiguration._dwLogOverwriteAge = 10;
        g_cTrkWksConfiguration._dwLogFileOpenTime = 10;
        g_cTrkWksConfiguration._dwDebugFlags = (0xFFFFFFFF & ~TRKDBG_WORKMAN);

        g_Debug = g_cTrkWksConfiguration._dwDebugFlags;

        for( i = 0; i < sizeof(rgNotificationsWritten); i++ )
            ((BYTE*) rgNotificationsWritten)[ i ] = (BYTE) i;



        //  -----------
        //  Basic Tests
        //  -----------

        _tprintf( TEXT("Basic exercises\n") );

        CreateNewLog( &cTestLogCallback, &cLogEntries, &iNotifications, &seqFirst );

        // Test the initial log

        FillAndExerciseLog( 0, cLogEntries, seqFirst, rgNotificationsWritten, rgNotificationsRead );

        // Seek to a non-existent entry in the log

        g_cTestLog.Seek( 1 );
        g_cTestLog.Seek( -1 );
        ExerciseLog( cLogEntries-1, 0,
                     &rgNotificationsWritten[ iNotifications ],
                     &rgNotificationsRead[ iNotifications ] );

        // Cause the log to expand.  Note that in this case, the start/end indices are
        // currently at the start/end of the file.

        _tprintf( TEXT("Cause the log to expand") );

        g_cTrkWksConfiguration._dwLogDeltaKB = 1;
        g_cTrkWksConfiguration._dwMaxLogKB = g_cTrkWksConfiguration._dwMaxLogKB + 1;

        g_cTestLog.Append( 1, &rgNotificationsWritten[ cLogEntries - 1] );

        _tprintf( TEXT(" (%d entries)\n"), g_cTestLog.NumEntriesInFile() );

        g_cTestLog.DelayUntilClose();

        FillAndExerciseLog( cLogEntries,
                            g_cTestLog.NumEntriesInFile(),
                            seqFirst,
                            &rgNotificationsWritten[ iNotifications ],
                            &rgNotificationsRead[ iNotifications ] );
        cLogEntries = g_cTestLog.NumEntriesInFile();

        // Close and re-open the log

        _tprintf( TEXT("Close and re-open the log\n") );

        g_cTestLog.CloseLog();
        g_cTestLog.OpenLog( &cTestLogCallback );

        ExerciseLog( cLogEntries - 1, seqFirst,
                     &rgNotificationsWritten[ iNotifications ],
                     &rgNotificationsRead[ iNotifications ] );

        // Ensure that we can't add to a full log (where the log can't be expanded,
        // the start entry isn't old enough to throw away, and the start entry
        // hasn't yet been read).

        __try
        {
            fAppendFailed = FALSE;
            TrkLog(( TRKDBG_ERROR, TEXT("TLog Unit Test:  Causing an intentional Append exception") ));
            g_cTestLog.Seek( SEEK_SET, 0 ); // Make the start entry un-read
            g_cTestLog.Append( 1, rgNotificationsWritten );
        }
        __except( BreakOnAccessViolation() )
        {
            if( GetExceptionCode() != STATUS_LOG_FILE_FULL )
                TestRaiseException( GetExceptionCode(), TEXT("Wrong exception raised when attempting to write to a full log") );
            fAppendFailed = TRUE;
        }

        if( !fAppendFailed )
            TestRaiseException( E_FAIL, TEXT("Append to a full log should have failed") );

        // Overwrite an entry in the log that's overwritable since it's been read already.

        _tprintf( TEXT("Try to add to a max log (the start entry's been read)\n") );

        g_cTestLog.Seek( SEEK_SET, 1 );
        g_cTestLog.Append( 1, &rgNotificationsWritten[ cLogEntries - 1 ] );

        seqFirst++;
        iNotifications++;

        ExerciseLog( cLogEntries-1, seqFirst,
                     &rgNotificationsWritten[ iNotifications ],
                     &rgNotificationsRead[ iNotifications ] );
        
        g_cTestLog.ValidateLog();

        // Overwrite an old entry in the log

        _tprintf( TEXT("Try to add to a max log (the start entry's old)\n") );
        g_cTestLog.Seek( SEEK_SET, 0 );
        g_cTestLog.MakeEntryOld();
        g_cTestLog.Append( 1, &rgNotificationsWritten[cLogEntries] );

        seqFirst++;
        iNotifications++;

        ExerciseLog( cLogEntries-1, seqFirst,
                     &rgNotificationsWritten[ iNotifications ],
                     &rgNotificationsRead[ iNotifications ] );

        g_cTestLog.ValidateLog();

        // Grow again (note that this time, the start/end indices are in the middle of
        // the file).  Also, this time, we show that the log can grow to up to the max
        // size, even if it means we can't grow an entire delta.

        _tprintf( TEXT("Cause the log to expand again") );

        g_cTrkWksConfiguration._dwLogDeltaKB = 10;
        g_cTrkWksConfiguration._dwMaxLogKB = g_cTrkWksConfiguration._dwMaxLogKB + 1;

        g_cTestLog.Append( 1, &rgNotificationsWritten[ cLogEntries+1 ] );

        if( g_cTestLog.NumEntriesInFile()
            >
            cLogEntries + g_cTestLog.NumEntriesPerKB()
          )
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Log grew by more than the max allowable") ));
            TrkRaiseWin32Error( E_FAIL );
        }

        _tprintf( TEXT(" (%d entries)\n"), g_cTestLog.NumEntriesInFile() );

        FillAndExerciseLog( cLogEntries, g_cTestLog.NumEntriesInFile(),
                            seqFirst,
                            &rgNotificationsWritten[ iNotifications ],
                            &rgNotificationsRead[ iNotifications ] );
        cLogEntries = g_cTestLog.NumEntriesInFile();

        g_cTestLog.ValidateLog();


        //  --------------
        //  Extended Tests
        //  --------------

        // Test the extended header

        _tprintf( TEXT("Extended header area\n") );

        for( i = 0; i < sizeof(rgbExtendedHeaderWrite); i++ )
            rgbExtendedHeaderWrite[ i ] = (BYTE)i;

        g_cTestLog.WriteExtendedHeader( 32, (void*) rgbExtendedHeaderWrite, sizeof(rgbExtendedHeaderWrite) );
        g_cTestLog.ReadExtendedHeader(  32, (void*) rgbExtendedHeaderRead,  sizeof(rgbExtendedHeaderRead) );

        for( i = 0; i < sizeof(rgbExtendedHeaderWrite); i++ )
            rgbExtendedHeaderWrite[ i ] = (BYTE)(i + 1);

        g_cTestLog.WriteExtendedHeader( 32, (void*) rgbExtendedHeaderWrite, sizeof(rgbExtendedHeaderWrite) );
        g_cTestLog.DelayUntilClose();
        g_cTestLog.ReadExtendedHeader(  32, (void*) rgbExtendedHeaderRead,  sizeof(rgbExtendedHeaderRead) );


        if( memcmp( rgbExtendedHeaderWrite, rgbExtendedHeaderRead, sizeof(rgbExtendedHeaderWrite) ))
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Extended header information couldn't be written/read") ));
            TrkRaiseWin32Error( E_FAIL );
        }

        // Make the log look abnormally shutdown, then open it.

        _tprintf( TEXT("Make log look abnormally shut down\n") );

        g_cTestLog.CloseLog();

        hFile = CreateFile( g_cTestLog.LogFileName(), GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ,
                            NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
        if( INVALID_HANDLE_VALUE == hFile )
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Couldn't open the logfile from the test") ));
            TrkRaiseLastError( );
        }

        if( !ReadFile( hFile, rgbSector, g_cTestLog.CBSector(), &cb, NULL )
            ||
            g_cTestLog.CBSector() != cb
          )
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Couldn't read from logfile (in test), cbRead = %d"), cb ));
            TrkRaiseLastError( );
        }

        plogheader = (LogHeader*) rgbSector;
        plogheader->fProperShutdown = FALSE;

        if( 0xFFFFFFFF == SetFilePointer( hFile, 0, NULL, FILE_BEGIN ))
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Couldn't seek logfile (in test)") ));
            TrkRaiseLastError( );
        }

        if( !WriteFile( hFile, rgbSector, g_cTestLog.CBSector(), &cb, NULL )
            ||
            g_cTestLog.CBSector() != cb
          )
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Couldn't write to logfile (in test), cbWritten = %d"), cb ));
            TrkRaiseLastError( );
        }

        CloseHandle( hFile );
        hFile = INVALID_HANDLE_VALUE;
        plogheader = NULL;

        g_cTestLog.OpenLog( &cTestLogCallback );
        ExerciseLog( cLogEntries - 1, seqFirst,
                     &rgNotificationsWritten[ iNotifications ],
                     &rgNotificationsRead[ iNotifications ] );


        // Make the log look like it crashed during an expansion.

        _tprintf( TEXT("Expansion crash recovery\n") );

        ilogStart = g_cTestLog.GetStartIndex();
        ilogEnd = g_cTestLog.GetEndIndex();

        g_cTestLog.CloseLog();

        hFile = CreateFile( g_cTestLog.LogFileName(), GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ,
                            NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
        if( INVALID_HANDLE_VALUE == hFile )
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Couldn't open the logfile from the test") ));
            TrkRaiseLastError( );
        }

        cbFile = GetFileSize( hFile, NULL );
        if( 0xffffffff == cbFile )
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Couldn't get log file size (in test)") ));
            TrkRaiseLastError( );
        }

        if( !ReadFile( hFile, rgbSector, g_cTestLog.CBSector(), &cb, NULL )
            ||
            g_cTestLog.CBSector() != cb
          )
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Couldn't read from logfile (in test), cbRead = %d"), cb ));
            TrkRaiseLastError( );
        }

        plogheader = (LogHeader*) rgbSector;

        plogheader->fProperShutdown = FALSE;
        plogheader->expand.cbFile = cbFile;
        plogheader->expand.ilogStart = ilogStart;
        plogheader->expand.ilogEnd = ilogEnd;

        if( 0xFFFFFFFF == SetFilePointer( hFile, 0, NULL, FILE_BEGIN ))
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Couldn't seek logfile (in test)") ));
            TrkRaiseLastError( );
        }

        if( !WriteFile( hFile, rgbSector, g_cTestLog.CBSector(), &cb, NULL )
            ||
            g_cTestLog.CBSector() != cb
          )
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Couldn't write to logfile (in test), cbWritten = %d"), cb ));
            TrkRaiseLastError( );
        }

        ReadLogSector( hFile, ilogStart, g_cTestLog.CBSector(), rgbSector );
        plogentry = &( (LogEntry*)rgbSector )[ ilogStart % (g_cTestLog.CBSector()/sizeof(LogEntry)) ];
        plogentry->ilogPrevious = -1;
        WriteLogSector( hFile, ilogStart, g_cTestLog.CBSector(), rgbSector );
        
        ReadLogSector( hFile, ilogEnd, g_cTestLog.CBSector(), rgbSector );
        plogentry = &( (LogEntry*)rgbSector )[ ilogEnd % (g_cTestLog.CBSector()/sizeof(LogEntry)) ];
        plogentry->ilogNext = -1;
        WriteLogSector( hFile, ilogEnd, g_cTestLog.CBSector(), rgbSector );
        

        cbFile += g_cTestLog.CBSector();
        if ( 0xFFFFFFFF == SetFilePointer(hFile, cbFile, NULL, FILE_BEGIN)
             ||
             !SetEndOfFile(hFile) )
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Couldn't reset log file size to %lu (in test)"), cbFile ));
            TrkRaiseLastError( );
        }

        CloseHandle( hFile );
        hFile = INVALID_HANDLE_VALUE;
        plogheader = NULL;

        g_cTestLog.OpenLog( &cTestLogCallback );
        ExerciseLog( cLogEntries - 1, seqFirst,
                     &rgNotificationsWritten[ iNotifications ],
                     &rgNotificationsRead[ iNotifications ] );


        // Corrupt the log header, but in a way that is recoverable.

        _tprintf( TEXT("Make log look corrupted (recoverable)\n") );

        g_cTestLog.CloseLog();

        hFile = CreateFile( g_cTestLog.LogFileName(), GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ,
                            NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
        if( INVALID_HANDLE_VALUE == hFile )
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Couldn't open the logfile from the test") ));
            TrkRaiseLastError( );
        }

        if( !ReadFile( hFile, rgbSector, g_cTestLog.CBSector(), &cb, NULL )
            ||
            g_cTestLog.CBSector() != cb
          )
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Couldn't read from logfile (in test), cbRead = %d"), cb ));
            TrkRaiseLastError( );
        }

        plogheader = (LogHeader*) rgbSector;
        plogheader->fProperShutdown = FALSE;

        memset( &reinterpret_cast<BYTE*>(plogheader)[CLOG_LOGINFO_START], 0, CLOG_LOGINFO_LENGTH );


        if( 0xFFFFFFFF == SetFilePointer( hFile, 0, NULL, FILE_BEGIN ))
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Couldn't seek logfile (in test)") ));
            TrkRaiseLastError( );
        }

        if( !WriteFile( hFile, rgbSector, g_cTestLog.CBSector(), &cb, NULL )
            ||
            g_cTestLog.CBSector() != cb
          )
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Couldn't write to logfile (in test), cbWritten = %d"), cb ));
            TrkRaiseLastError( );
        }

        CloseHandle( hFile );
        hFile = INVALID_HANDLE_VALUE;
        plogheader = NULL;

        g_cTestLog.OpenLog( &cTestLogCallback );
        if( g_cTestLog.IsEmpty() )
        {
            TrkLog(( TRKDBG_ERROR, TEXT("We got a new log file after what should have been a recoverable corruption") ));
            TrkRaiseWin32Error( E_FAIL );
        }

        ExerciseLog( cLogEntries - 1, seqFirst,
                     &rgNotificationsWritten[ iNotifications ],
                     &rgNotificationsRead[ iNotifications ] );
        

        // Make the log look corrupted and un-recoverable in the header

        _tprintf( TEXT("Make log header look corrupted (un-recoverable)\n") );

        g_cTestLog.CloseLog();

        hFile = CreateFile( g_cTestLog.LogFileName(), GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ,
                            NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
        if( INVALID_HANDLE_VALUE == hFile )
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Couldn't open the logfile from the test") ));
            TrkRaiseLastError( );
        }

        if( !ReadFile( hFile, rgbSector, g_cTestLog.CBSector(), &cb, NULL )
            ||
            g_cTestLog.CBSector() != cb
          )
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Couldn't read from logfile (in test), cbRead = %d"), cb ));
            TrkRaiseLastError( );
        }

        plogheader = (LogHeader*) rgbSector;
        plogheader->fProperShutdown = FALSE;
        plogheader->ulSignature = 0;

        if( 0xFFFFFFFF == SetFilePointer( hFile, 0, NULL, FILE_BEGIN ))
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Couldn't seek logfile (in test)") ));
            TrkRaiseLastError( );
        }

        if( !WriteFile( hFile, rgbSector, g_cTestLog.CBSector(), &cb, NULL )
            ||
            g_cTestLog.CBSector() != cb
          )
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Couldn't write to logfile (in test), cbWritten = %d"), cb ));
            TrkRaiseLastError( );
        }

        CloseHandle( hFile );
        hFile = INVALID_HANDLE_VALUE;
        plogheader = NULL;

        g_cTestLog.OpenLog( &cTestLogCallback );
        if( !g_cTestLog.IsEmpty() )
        {
            TrkLog(( TRKDBG_ERROR,TEXT("After opening a corrupt log, we should have a new log file") ));
            TrkRaiseWin32Error( E_FAIL );
        }

        
        // Make the log look corrupted and un-recoverable in the sectors

        _tprintf( TEXT("Make log sectors look corrupted (un-recoverable)\n") );

        g_cTestLog.CloseLog();

        CreateNewLog( &cTestLogCallback, &cLogEntries, &iNotifications, &seqFirst );
        FillAndExerciseLog( 0, cLogEntries, seqFirst, rgNotificationsWritten, rgNotificationsRead );
        g_cTestLog.CloseLog();

        hFile = CreateFile( g_cTestLog.LogFileName(), GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ,
                            NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
        if( INVALID_HANDLE_VALUE == hFile )
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Couldn't open the logfile from the test") ));
            TrkRaiseLastError( );
        }

        if( 0xFFFFFFFF == SetFilePointer( hFile,
                                          g_cTestLog.DataSectorOffset(),
                                          NULL, FILE_BEGIN ))
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Couldn't seek logfile to start of first sector (in test)") ));
            TrkRaiseLastError( );
        }

        if( !ReadFile( hFile, rgbSector, g_cTestLog.CBSector(), &cb, NULL )
            ||
            g_cTestLog.CBSector() != cb
          )
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Couldn't read from logfile (in test), cbRead = %d"), cb ));
            TrkRaiseLastError( );
        }

        memset( rgbSector, 0, sizeof(rgbSector) );

        if( 0xFFFFFFFF == SetFilePointer( hFile, 
                                          g_cTestLog.DataSectorOffset(),
                                          NULL, FILE_BEGIN ))
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Couldn't re-seek logfile to start of first sector (in test)") ));
            TrkRaiseLastError( );
        }

        if( !WriteFile( hFile, rgbSector, g_cTestLog.CBSector(), &cb, NULL )
            ||
            g_cTestLog.CBSector() != cb
          )
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Couldn't write to logfile (in test), cbWritten = %d"), cb ));
            TrkRaiseLastError( );
        }

        CloseHandle( hFile );
        hFile = INVALID_HANDLE_VALUE;

        BOOL fExceptionRaised = FALSE;
        __try
        {
            TrkLog(( TRKDBG_ERROR, TEXT("About to open a corrupted log (this should raise)") ));

            // The open should succeed
            g_cTestLog.OpenLog( &cTestLogCallback,
                                FALSE // => Don't validate
                              );

            // This should raise
            ExerciseLog( cLogEntries - 1, seqFirst,
                         &rgNotificationsWritten[ iNotifications ],
                         &rgNotificationsRead[ iNotifications ] );

        }
        __except( EXCEPTION_EXECUTE_HANDLER )
        {
            fExceptionRaised = TRUE;
            if( GetExceptionCode() != TRK_E_CORRUPT_LOG )
            {
                TrkLog(( TRKDBG_ERROR, TEXT("After corrupting a sector, Open should have raised TRK_E_CORRUPT_LOG") ));
                TrkRaiseException( GetExceptionCode() );
            }
        }

        if( !fExceptionRaised )
        {
            TrkLog(( TRKDBG_ERROR, TEXT("We should have gotten an exception after corrupting log sectors") ));
            TrkRaiseWin32Error( E_FAIL );
        }

        g_cTestLog.ReInitialize();

        if( !g_cTestLog.IsEmpty() )
        {
            TrkLog(( TRKDBG_ERROR, TEXT("After opening a corrupt log, we should have a new log file") ));
            TrkRaiseWin32Error( E_FAIL );
        }

        cLogEntries = g_cTestLog.NumEntriesInFile();
        iNotifications = 0;
        seqFirst = 0;

        FillAndExerciseLog( 0, cLogEntries, seqFirst, rgNotificationsWritten, rgNotificationsRead );

        // Test that tunneling works correctly (if there are duplicate entries, we should
        // get the most recent).

        _tprintf( TEXT("Test tunneling\n") );

        tempNotificationWrite = rgNotificationsWritten[0];

        g_cTestLog.Append( 1, &tempNotificationWrite );

        strncpy( (LPSTR) &tempNotificationWrite.droidBirth, "abcdefghijklmnopqrstuvwxyznowiknowmyabcsnexttimewontyousingwithme",
                 sizeof(tempNotificationWrite.droidBirth) );

        g_cTestLog.Append( 1, &tempNotificationWrite );
        g_cTestLog.Search( tempNotificationWrite.droidCurrent, &tempNotificationRead );


        if( memcmp( &tempNotificationWrite, &tempNotificationRead, sizeof(tempNotificationWrite) ))
        {
            TrkLog(( TRKDBG_ERROR, TEXT("Didn't get the tunneled move notification") ));
            TrkRaiseWin32Error( E_FAIL );
        }



        _tprintf( TEXT("\nTests Passed\n") );

        g_cTestLog.CloseLog();

    }   // __try

    __finally
    {
    }

}   // _tmain()