windows-nt/Source/XPSP1/NT/com/svcdlls/trksvcs/trkwks/tlog.cxx

1423 lines
42 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
//
// 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()