// // 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(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()