1111#include " Logger.h"
1212#include " DelegateMQ.h"
1313#include " SignalThread.h"
14+ #include < filesystem>
1415#include " IT_Util.h" // Include this last
1516
1617using namespace std ;
@@ -23,6 +24,16 @@ static vector<string> callbackStatus;
2324static milliseconds flushDuration;
2425static mutex mtx;
2526
27+ // State for WriteSequence test
28+ static atomic<int > writeSeqCount{ 0 };
29+ static int writeSeqTarget = 0 ;
30+ static SignalThread writeSeqSignal;
31+
32+ // State for ConcurrentWrites test
33+ static atomic<int > concurrentWriteCount{ 0 };
34+ static int concurrentWriteTarget = 0 ;
35+ static SignalThread concurrentDoneSignal;
36+
2637// Logger callback handler function invoked from Logger thread context
2738void FlushTimeCb (milliseconds duration)
2839{
@@ -37,7 +48,7 @@ void FlushTimeCb(milliseconds duration)
3748// Logger callback handler function invoked from Logger thread context
3849void LoggerStatusCb (const string& status)
3950{
40- // Protect callbackStatus against multiple thread access by IntegrationTest
51+ // Protect callbackStatus against multiple thread access by IntegrationTest
4152 // thread and Logger thread
4253 lock_guard<mutex> lock (mtx);
4354
@@ -48,6 +59,32 @@ void LoggerStatusCb(const string& status)
4859 signalThread.SetSignal ();
4960}
5061
62+ // Logger callback for WriteSequence test. Records Write success! callbacks into
63+ // callbackStatus and signals once writeSeqTarget writes have been confirmed.
64+ // Flush callbacks from the Logger timer are ignored to avoid signal interference.
65+ void WriteSeqCb (const string& status)
66+ {
67+ if (status != " Write success!" )
68+ return ;
69+
70+ {
71+ lock_guard<mutex> lock (mtx);
72+ callbackStatus.push_back (status);
73+ }
74+
75+ if (++writeSeqCount == writeSeqTarget)
76+ writeSeqSignal.SetSignal ();
77+ }
78+
79+ // Logger callback for ConcurrentWrites test. Counts Write success! callbacks
80+ // and signals once all concurrentWriteTarget writes have been confirmed.
81+ void ConcurrentWriteCb (const string& status)
82+ {
83+ if (status == " Write success!" )
84+ if (++concurrentWriteCount == concurrentWriteTarget)
85+ concurrentDoneSignal.SetSignal ();
86+ }
87+
5188// Test the Logger::Write() subsystem public API.
5289TEST (Logger_IT, Write)
5390{
@@ -293,5 +330,128 @@ TEST(Logger_IT, FlushTimeSimplifiedWithLambda)
293330 Logger::GetInstance ().m_logData .FlushTimeDelegate -= MakeDelegate (FlushTimeLambdaCb);
294331}
295332
333+ // Verify that callbacks from multiple Write operations arrive in the correct sequence.
334+ // Fires 3 writes without waiting between them, then confirms the Logger thread
335+ // delivered all 3 callbacks in FIFO order — the same order the writes were issued.
336+ // Flush callbacks from the timer are filtered out by WriteSeqCb to avoid
337+ // interference with the sequence count.
338+ TEST (Logger_IT, WriteSequence)
339+ {
340+ writeSeqCount = 0 ;
341+ writeSeqTarget = 3 ;
342+ {
343+ lock_guard<mutex> lock (mtx);
344+ callbackStatus.clear ();
345+ }
346+
347+ Logger::GetInstance ().SetCallback (&WriteSeqCb);
348+
349+ // Queue all 3 writes without waiting between them
350+ Logger::GetInstance ().Write (" First" );
351+ Logger::GetInstance ().Write (" Second" );
352+ Logger::GetInstance ().Write (" Third" );
353+
354+ // Wait until all 3 write callbacks have arrived from the Logger thread
355+ EXPECT_TRUE (writeSeqSignal.WaitForSignal (1000 ));
356+
357+ {
358+ lock_guard<mutex> lock (mtx);
359+
360+ // Verify all 3 write callbacks arrived in sequence.
361+ // The Logger's FIFO message queue guarantees the callbacks are
362+ // delivered in the same order the Write calls were submitted.
363+ ASSERT_EQ (callbackStatus.size (), 3u );
364+ EXPECT_EQ (callbackStatus[0 ], " Write success!" );
365+ EXPECT_EQ (callbackStatus[1 ], " Write success!" );
366+ EXPECT_EQ (callbackStatus[2 ], " Write success!" );
367+ }
368+
369+ Logger::GetInstance ().SetCallback (nullptr );
370+ }
371+
372+ // Verify that a Flush failure is correctly detected and that buffered log data
373+ // is preserved across the failure for a subsequent retry.
374+ // Injects a fault by removing write permission from the log file, confirms
375+ // Flush returns false, restores permissions, then confirms Flush recovers.
376+ TEST (Logger_IT, FlushFaultInjection)
377+ {
378+ namespace fs = std::filesystem;
379+
380+ // Ensure LogData.txt exists on disk so permissions can be applied to it
381+ auto ensureRet = AsyncInvoke (
382+ &Logger::GetInstance ().m_logData ,
383+ &LogData::Flush,
384+ Logger::GetInstance (),
385+ milliseconds (100 ));
386+ ASSERT_TRUE (ensureRet.has_value ());
387+
388+ // Write a data entry that will be held in the buffer during the fault
389+ AsyncInvoke (
390+ &Logger::GetInstance ().m_logData ,
391+ &LogData::Write,
392+ Logger::GetInstance (),
393+ milliseconds (50 ),
394+ string (" Fault injection test data" ));
395+
396+ // Inject fault: remove write permission from the log file
397+ std::error_code ec;
398+ fs::permissions (" LogData.txt" ,
399+ fs::perms::owner_write | fs::perms::group_write | fs::perms::others_write,
400+ fs::perm_options::remove, ec);
401+ ASSERT_FALSE (ec) << " Failed to remove write permission: " << ec.message ();
402+
403+ // Flush must fail because the file is now read-only
404+ auto failRet = AsyncInvoke (
405+ &Logger::GetInstance ().m_logData ,
406+ &LogData::Flush,
407+ Logger::GetInstance (),
408+ milliseconds (100 ));
409+ EXPECT_TRUE (failRet.has_value ());
410+ if (failRet.has_value ())
411+ EXPECT_FALSE (failRet.value ()); // Flush should return false on fault
412+
413+ // Restore write permission to recover from the fault
414+ fs::permissions (" LogData.txt" , fs::perms::owner_write, fs::perm_options::add, ec);
415+ ASSERT_FALSE (ec) << " Failed to restore write permission: " << ec.message ();
416+
417+ // Buffered data must have survived the failed flush - retry must succeed.
418+ // LogData::Flush does not clear m_msgData on failure, preserving the entry.
419+ auto retryRet = AsyncInvoke (
420+ &Logger::GetInstance ().m_logData ,
421+ &LogData::Flush,
422+ Logger::GetInstance (),
423+ milliseconds (100 ));
424+ EXPECT_TRUE (retryRet.has_value ());
425+ if (retryRet.has_value ())
426+ EXPECT_TRUE (retryRet.value ()); // Flush should succeed after recovery
427+ }
428+
429+ // Stress test that fires many Write calls without waiting between them and
430+ // verifies the Logger thread processes every one without dropping a callback.
431+ // Demonstrates that the Logger's message queue handles concurrent producer load
432+ // and that every callback is delivered exactly once.
433+ TEST (Logger_IT, ConcurrentWrites)
434+ {
435+ const int NUM_WRITES = 20 ;
436+ concurrentWriteCount = 0 ;
437+ concurrentWriteTarget = NUM_WRITES;
438+
439+ Logger::GetInstance ().SetCallback (&ConcurrentWriteCb);
440+
441+ // Fire all writes without waiting — stress the Logger's message queue
442+ for (int i = 0 ; i < NUM_WRITES; i++)
443+ Logger::GetInstance ().Write (" ConcurrentWrite " + to_string (i));
444+
445+ // Wait until all NUM_WRITES callbacks have been received
446+ EXPECT_TRUE (concurrentDoneSignal.WaitForSignal (2000 ))
447+ << " Timed out: only " << concurrentWriteCount.load ()
448+ << " of " << NUM_WRITES << " write callbacks received" ;
449+
450+ // Every write must have produced exactly one callback
451+ EXPECT_EQ (concurrentWriteCount.load (), NUM_WRITES);
452+
453+ Logger::GetInstance ().SetCallback (nullptr );
454+ }
455+
296456// Dummy function to force linker to keep the code in this file
297457void Logger_IT_ForceLink () { }
0 commit comments