Skip to content

Commit f58020a

Browse files
committed
Add more tests
1 parent 9b21ece commit f58020a

File tree

3 files changed

+343
-3
lines changed

3 files changed

+343
-3
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,4 +303,7 @@ __pycache__/
303303
*.xsd.cs
304304

305305
# OpenCover UI analysis results
306-
OpenCover/
306+
OpenCover/
307+
308+
# Integration test log output
309+
LogData.txt

Logger/it/Logger_IT.cpp

Lines changed: 161 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "Logger.h"
1212
#include "DelegateMQ.h"
1313
#include "SignalThread.h"
14+
#include <filesystem>
1415
#include "IT_Util.h" // Include this last
1516

1617
using namespace std;
@@ -23,6 +24,16 @@ static vector<string> callbackStatus;
2324
static milliseconds flushDuration;
2425
static 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
2738
void FlushTimeCb(milliseconds duration)
2839
{
@@ -37,7 +48,7 @@ void FlushTimeCb(milliseconds duration)
3748
// Logger callback handler function invoked from Logger thread context
3849
void 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.
5289
TEST(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
297457
void Logger_IT_ForceLink() { }

0 commit comments

Comments
 (0)