7a326c5d78
Add DebugServiceGTest.cpp (TraceRingBuffer, DebugSignalInfo, BreakOp regression coverage for the HI-4/HI-9 fixes) to Test/GTest's OBJSX so it builds and runs as part of ./Build/x86-linux/GTest/MainGTest.ex alongside the rest of the unit suite. 17/17 tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
213 lines
8.1 KiB
C++
213 lines
8.1 KiB
C++
/**
|
|
* @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<uint32>(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<uint32>(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<uint32>(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);
|
|
}
|