/** * @file DebugServiceGTest.cpp * @brief Unit tests for DebugService header-only core components: * TraceRingBuffer, DebugSignalInfo struct, BreakOp enum. * * These tests exercise self-contained logic from DebugCore.h without * requiring a running MARTe2 application or linking DebugServiceBase.cpp. * ProcessSignal/HandleCommand/RegisterSignal are covered by the * DebugService integration tests in Test/Integration/. */ #include "DebugCore.h" #include "gtest/gtest.h" using namespace MARTe; /* ═════════════════════════════════════════════════════════════════════════ TraceRingBuffer tests (DebugCore.h / HI-9 atomic fix validation) ═════════════════════════════════════════════════════════════════════════ */ class TraceRingBufferTest : public ::testing::Test { protected: void SetUp() { buf = new TraceRingBuffer(); } void TearDown() { delete buf; } TraceRingBuffer *buf; }; TEST_F(TraceRingBufferTest, Init_ValidSize) { ASSERT_TRUE(buf->Init(1024)); EXPECT_EQ(buf->Count(), 0u); } TEST_F(TraceRingBufferTest, Init_TwiceReplacesBuffer) { ASSERT_TRUE(buf->Init(256)); EXPECT_TRUE(buf->Init(512)); EXPECT_EQ(buf->Count(), 0u); } TEST_F(TraceRingBufferTest, PushPop_SingleEntryRoundtrip) { ASSERT_TRUE(buf->Init(256)); uint8 data[8] = {0xAA, 0xBB, 0xCC, 0xDD, 0x11, 0x22, 0x33, 0x44}; ASSERT_TRUE(buf->Push(42, 0xDEADBEEFDEADBEEFULL, data, 8)); uint32 id = 0; uint64 ts = 0; uint8 out[8] = {}; uint32 sz = 0; ASSERT_TRUE(buf->Pop(id, ts, out, sz, 16)); EXPECT_EQ(id, 42u); EXPECT_EQ(ts, 0xDEADBEEFDEADBEEFULL); EXPECT_EQ(sz, 8u); EXPECT_EQ(memcmp(data, out, 8), 0); } TEST_F(TraceRingBufferTest, Pop_EmptyBufferReturnsFalse) { ASSERT_TRUE(buf->Init(256)); uint32 id = 0; uint64 ts = 0; uint8 out[1] = {}; uint32 sz = 0; EXPECT_FALSE(buf->Pop(id, ts, out, sz, 16)); } TEST_F(TraceRingBufferTest, Push_FullBufferReturnsFalse) { /* 32-byte buffer: entry = 4+8+4+10 = 26 bytes. Fits 1, not 2. */ ASSERT_TRUE(buf->Init(32)); uint8 data[10] = {}; ASSERT_TRUE(buf->Push(1, 1, data, 10)); EXPECT_FALSE(buf->Push(2, 2, data, 10)); } TEST_F(TraceRingBufferTest, CountAfterMultiplePushes) { ASSERT_TRUE(buf->Init(256)); uint8 data[4] = {}; const uint32 entrySize = 4 + 8 + 4 + 4; for (int i = 0; i < 5; i++) { ASSERT_TRUE(buf->Push(static_cast(i), 100 + i, data, 4)); } EXPECT_EQ(buf->Count(), entrySize * 5u); } TEST_F(TraceRingBufferTest, PushPop_Wraparound) { /* 64-byte buffer: entry=24 bytes (4+8+4+8). Fits 2 entries. */ ASSERT_TRUE(buf->Init(64)); uint8 data[8] = {}; ASSERT_TRUE(buf->Push(1, 100, data, 8)); ASSERT_TRUE(buf->Push(2, 200, data, 8)); uint32 id = 0; uint64 ts = 0; uint8 out[8] = {}; uint32 sz = 0; ASSERT_TRUE(buf->Pop(id, ts, out, sz, 16)); EXPECT_EQ(id, 1u); /* Push a third entry causing wraparound */ ASSERT_TRUE(buf->Push(3, 300, data, 8)); ASSERT_TRUE(buf->Pop(id, ts, out, sz, 16)); EXPECT_EQ(id, 2u); ASSERT_TRUE(buf->Pop(id, ts, out, sz, 16)); EXPECT_EQ(id, 3u); EXPECT_FALSE(buf->Pop(id, ts, out, sz, 16)); } TEST_F(TraceRingBufferTest, Pop_SkipOversizedEntry) { /* When entry size > maxSize, Pop skips it and returns false. * The caller must loop: the next Pop call delivers the subsequent entry. */ ASSERT_TRUE(buf->Init(512)); uint8 smallData[4] = {1, 2, 3, 4}; uint8 bigData[128] = {}; ASSERT_TRUE(buf->Push(1, 100, bigData, 128)); ASSERT_TRUE(buf->Push(2, 200, smallData, 4)); uint32 id = 0; uint64 ts = 0; uint8 out[4] = {}; uint32 sz = 0; /* First Pop: skip oversized entry */ EXPECT_FALSE(buf->Pop(id, ts, out, sz, 16)); /* Second Pop: deliver small entry */ ASSERT_TRUE(buf->Pop(id, ts, out, sz, 16)); EXPECT_EQ(id, 2u); EXPECT_EQ(sz, 4u); } TEST_F(TraceRingBufferTest, Pop_SkipOversized_EmptyAfterSkip) { /* If the last entry is oversized and skipped, Pop returns false. */ ASSERT_TRUE(buf->Init(256)); uint8 bigData[128] = {}; ASSERT_TRUE(buf->Push(1, 100, bigData, 128)); uint32 id = 0; uint64 ts = 0; uint8 out[1] = {}; uint32 sz = 0; EXPECT_FALSE(buf->Pop(id, ts, out, sz, 16)); } TEST_F(TraceRingBufferTest, Push_LargePayloadFits) { /* Buffer just big enough for one entry with 200 data bytes. */ ASSERT_TRUE(buf->Init(256)); uint8 bigData[200] = {}; uint32 entryBytes = 4 + 8 + 4 + 200; // 216 ASSERT_LE(entryBytes, 255u); /* fits with 1-byte ring gap */ ASSERT_TRUE(buf->Push(1, 100, bigData, 200)); uint32 id = 0; uint64 ts = 0; uint8 out[200] = {}; uint32 sz = 0; ASSERT_TRUE(buf->Pop(id, ts, out, sz, 256)); EXPECT_EQ(id, 1u); EXPECT_EQ(sz, 200u); } TEST_F(TraceRingBufferTest, Push_ZeroSizeData) { ASSERT_TRUE(buf->Init(64)); ASSERT_TRUE(buf->Push(1, 100, NULL_PTR(void *), 0)); uint32 id = 0; uint64 ts = 0; uint32 sz = 0; ASSERT_TRUE(buf->Pop(id, ts, NULL_PTR(void *), sz, 16)); EXPECT_EQ(sz, 0u); } TEST_F(TraceRingBufferTest, DestructorDoesNotLeak) { TraceRingBuffer *b = new TraceRingBuffer(); b->Init(1024); delete b; SUCCEED(); } TEST_F(TraceRingBufferTest, ThreadSafety_SingleThreadSPSC) { /* Single-threaded SPSC: Push then Pop in same thread. The HI-9 atomic * fix uses __atomic_load_n/__atomic_store_n with acquire/release ordering. * In a single thread these are equivalent to regular loads/stores. */ ASSERT_TRUE(buf->Init(2048)); /* room for 50 entries of 20 bytes each */ for (int i = 0; i < 50; i++) { uint8 data[4] = {}; ASSERT_TRUE(buf->Push(static_cast(i), 1000 + i, data, 4)); } for (int i = 0; i < 50; i++) { uint32 id = 0; uint64 ts = 0; uint8 out[4] = {}; uint32 sz = 0; ASSERT_TRUE(buf->Pop(id, ts, out, sz, 16)); EXPECT_EQ(id, static_cast(i)); } /* Buffer should now be empty */ uint32 id = 0; uint64 ts = 0; uint32 sz = 0; EXPECT_FALSE(buf->Pop(id, ts, NULL_PTR(void *), sz, 16)); } /* ═════════════════════════════════════════════════════════════════════════ DebugSignalInfo struct layout tests ═════════════════════════════════════════════════════════════════════════ */ TEST(DebugSignalInfoTest, ForcedValueSizeMatchesSpec) { EXPECT_EQ(sizeof(((DebugSignalInfo *)0)->forcedValue), 1024u); } TEST(DebugSignalInfoTest, ForcedMaskSupports256Elements) { /* forcedMask is 32 bytes = 256 bits, supporting up to 256 elements. */ EXPECT_EQ(sizeof(((DebugSignalInfo *)0)->forcedMask), 32u); EXPECT_EQ(sizeof(((DebugSignalInfo *)0)->forcedMask) * 8u, 256u); } TEST(DebugSignalInfoTest, DefaultBreakOpIsOff) { /* The struct default-initialises with breakOp cleared to BREAK_OFF (0). * We cannot memset because StreamString is non-POD; verify with sizeof. * The RegisterSignal code in DebugServiceBase.cpp explicit sets breakOp=0. */ EXPECT_EQ(BREAK_OFF, 0); } /* ═════════════════════════════════════════════════════════════════════════ BreakOp enum tests ═════════════════════════════════════════════════════════════════════════ */ TEST(BreakOpTest, ValuesMatchSpec) { EXPECT_EQ(BREAK_OFF, 0); EXPECT_EQ(BREAK_GT, 1); EXPECT_EQ(BREAK_LT, 2); EXPECT_EQ(BREAK_EQ, 3); EXPECT_EQ(BREAK_GEQ, 4); EXPECT_EQ(BREAK_LEQ, 5); EXPECT_EQ(BREAK_NEQ, 6); }