Files
marte-debug/Test/UnitTests/UnitTests.cpp
T
2026-05-07 10:49:24 +02:00

720 lines
31 KiB
C++

#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include "DebugCore.h"
#include "DebugService.h"
#include "DebugBrokerWrapper.h"
#include "TcpLogger.h"
#include "ConfigurationDatabase.h"
#include "ObjectRegistryDatabase.h"
#include "GlobalObjectsDatabase.h"
namespace MARTe {
void TestTcpLogger() {
printf("Stability Logger Tests...\n");
TcpLogger logger;
ConfigurationDatabase config;
config.Write("Port", (uint32)0); // Random port
assert(logger.Initialise(config));
}
class DebugServiceTest {
public:
static void TestAll() {
printf("Stability Logic Tests...\n");
ObjectRegistryDatabase::Instance()->Purge();
DebugService service;
assert(service.traceBuffer.Init(1024 * 1024));
ConfigurationDatabase cfg;
cfg.Write("ControlPort", (uint32)0);
cfg.Write("StreamPort", (uint32)0);
cfg.Write("SuppressTimeoutLogs", (uint32)1);
assert(service.Initialise(cfg));
// 1. Signal logic
uint32 val = 0;
service.RegisterSignal(&val, UnsignedInteger32Bit, "X.Y.Z", 0, 1);
assert(service.TraceSignal("Z", true) == 1);
assert(service.ForceSignal("Z", "123") == 1);
uint64 ts = (uint64)((float64)HighResolutionTimer::Counter() * HighResolutionTimer::Period() * 1.0e9);
service.ProcessSignal(service.signals[0], 4, ts);
assert(val == 123);
service.UnforceSignal("Z");
// 2. Commands
service.HandleCommand("TREE", NULL_PTR(BasicTCPSocket*));
service.HandleCommand("DISCOVER", NULL_PTR(BasicTCPSocket*));
service.HandleCommand("CONFIG", NULL_PTR(BasicTCPSocket*));
service.HandleCommand("PAUSE", NULL_PTR(BasicTCPSocket*));
service.HandleCommand("RESUME", NULL_PTR(BasicTCPSocket*));
service.HandleCommand("LS /", NULL_PTR(BasicTCPSocket*));
service.HandleCommand("INFO X.Y.Z", NULL_PTR(BasicTCPSocket*));
service.HandleCommand("MSG DebugService DummyFunc 0 K=V", NULL_PTR(BasicTCPSocket*));
// 3. Broker Active Status
volatile bool active = false;
Vec<uint32> indices;
Vec<uint32> sizes;
FastPollingMutexSem mutex;
DebugSignalInfo* ptrs[1] = { service.signals[0] };
volatile bool anyBreak = false;
Vec<uint32> breakIdx;
service.RegisterBroker(ptrs, 1, NULL_PTR(MemoryMapBroker*), &active, &indices, &sizes, &mutex, &anyBreak, &breakIdx);
service.UpdateBrokersActiveStatus();
assert(active == true);
assert(indices.Size() == 1);
assert(indices[0] == 0);
// Helper Process
DebugBrokerHelper::Process(&service, ptrs, indices, sizes, mutex, &anyBreak, &breakIdx);
// 4. Step / per-thread filter
// STEP with no thread filter — all threads can consume
service.Step(2u, NULL_PTR(const char8 *));
assert(!service.IsPaused());
assert(service.stepRemaining == 2u);
service.ConsumeStepIfNeeded("GAM1", "ThreadA");
assert(service.stepRemaining == 1u);
assert(!service.IsPaused());
service.ConsumeStepIfNeeded("GAM1", "ThreadB");
assert(service.stepRemaining == 0u);
assert(service.IsPaused());
assert(service.pausedAtGam == "GAM1");
// STEP with a thread filter — only matching thread consumes
service.Step(2u, "ThreadA");
assert(!service.IsPaused());
assert(service.stepThreadFilter == "ThreadA");
// ThreadB should be ignored
service.ConsumeStepIfNeeded("GAMB", "ThreadB");
assert(service.stepRemaining == 2u); // unchanged
assert(!service.IsPaused());
// ThreadA consumes both credits
service.ConsumeStepIfNeeded("GAMA1", "ThreadA");
assert(service.stepRemaining == 1u);
assert(!service.IsPaused());
service.ConsumeStepIfNeeded("GAMA2", "ThreadA");
assert(service.stepRemaining == 0u);
assert(service.IsPaused());
assert(service.pausedAtGam == "GAMA2");
// STEP with unknown thread (NULL threadName arg to ConsumeStep) is also filtered out
service.Step(1u, "ThreadA");
service.ConsumeStepIfNeeded("X", NULL_PTR(const char8 *));
assert(service.stepRemaining == 1u); // not consumed
service.SetPaused(false);
service.Step(0u, NULL_PTR(const char8 *));
// 5. VALUE command (NULL client — smoke test, no crash)
service.HandleCommand("VALUE X.Y.Z", NULL_PTR(BasicTCPSocket*));
service.HandleCommand("VALUE NoSuchSignal", NULL_PTR(BasicTCPSocket*));
service.HandleCommand("STEP 1 ThreadA", NULL_PTR(BasicTCPSocket*));
assert(service.stepThreadFilter == "ThreadA");
service.HandleCommand("STEP 3", NULL_PTR(BasicTCPSocket*));
assert(service.stepThreadFilter.Size() == 0u);
}
// -------------------------------------------------------------------------
// FIX #4: ForceSignal overflow guard
// -------------------------------------------------------------------------
static void TestForceSignalOversizeRejected() {
printf("FIX #4: ForceSignal oversized signal rejected...\n");
ObjectRegistryDatabase::Instance()->Purge();
DebugService service;
assert(service.traceBuffer.Init(1024 * 1024));
ConfigurationDatabase cfg;
cfg.Write("ControlPort", (uint32)0);
cfg.Write("StreamPort", (uint32)0);
cfg.Write("SuppressTimeoutLogs", (uint32)1);
assert(service.Initialise(cfg));
// A 256-element float64 array requires 2048 bytes — exceeds forcedValue[1024].
// RegisterSignal itself is fine; the overflow guard is in ForceSignal.
float64 bigArray[256];
memset(bigArray, 0, sizeof(bigArray));
service.RegisterSignal(bigArray, Float64Bit, "Big.Array", 1, 256);
// ForceSignal must reject the request and return 0 matches applied.
// (The signal IS found by alias but the size check must veto it.)
uint32 forced = service.ForceSignal("Array", "1.0");
assert(forced == 0); // rejected — not forced
printf(" -> PASS: oversized force correctly rejected\n");
// A normal scalar float64 (8 bytes) must still be forceable.
float64 scalarVal = 0.0;
service.RegisterSignal(&scalarVal, Float64Bit, "Small.Scalar", 0, 1);
uint32 forcedSmall = service.ForceSignal("Scalar", "3.14");
assert(forcedSmall == 1); // accepted
printf(" -> PASS: normal scalar force accepted\n");
}
// -------------------------------------------------------------------------
// FIX #1: Streamer buffer bounds — Pop maxSize respected
// -------------------------------------------------------------------------
static void TestStreamerPopMaxSize() {
printf("FIX #1: TraceRingBuffer Pop respects maxSize...\n");
TraceRingBuffer rb;
assert(rb.Init(4096));
// Push a 512-byte sample
uint8 bigSample[512];
memset(bigSample, 0xAB, sizeof(bigSample));
assert(rb.Push(42u, 999u, bigSample, 512u));
// Pop with a maxSize smaller than the pushed size — must return false
// and not overflow the destination buffer.
uint8 smallBuf[64];
uint32 id = 0, size = 0;
uint64 ts = 0;
bool ok = rb.Pop(id, ts, smallBuf, size, 64u);
assert(!ok); // must be rejected, not a buffer overrun
printf(" -> PASS: Pop with insufficient maxSize correctly rejected\n");
// After skipping the oversized sample the buffer is empty (only one
// sample was pushed), so a second Pop must also return false.
ok = rb.Pop(id, ts, smallBuf, size, 64u);
assert(!ok);
printf(" -> PASS: Ring buffer empty after oversized pop skipped\n");
}
// -------------------------------------------------------------------------
// FIX #5: TraceRingBuffer Pop skips only the oversized entry, not all data
// -------------------------------------------------------------------------
static void TestRingBufferSkipOversizedEntry() {
printf("FIX #5: Oversized Pop skips only that entry, not all data...\n");
TraceRingBuffer rb;
assert(rb.Init(4096));
// Push sample A (oversized for a 64-byte receiver)
uint8 bigSample[512];
memset(bigSample, 0xAA, sizeof(bigSample));
assert(rb.Push(1u, 100u, bigSample, 512u));
// Push sample B (fits in 64-byte receiver)
uint8 smallSample[4] = {0xDE, 0xAD, 0xBE, 0xEF};
assert(rb.Push(2u, 200u, smallSample, 4u));
uint8 dst[64];
uint32 id = 0, sz = 0;
uint64 rts = 0;
// Pop with maxSize=64: sample A (512 bytes) must be skipped, not crash
bool ok1 = rb.Pop(id, rts, dst, sz, 64u);
assert(!ok1); // A skipped
printf(" -> PASS: oversized sample A correctly skipped\n");
// Sample B (4 bytes) must still be readable — before the fix the whole
// ring was discarded so this would also return false.
bool ok2 = rb.Pop(id, rts, dst, sz, 64u);
assert(ok2);
assert(id == 2u);
assert(sz == 4u);
assert(dst[0] == 0xDE && dst[3] == 0xEF);
printf(" -> PASS: subsequent sample B retrieved after A was skipped\n");
// Buffer should now be empty
bool ok3 = rb.Pop(id, rts, dst, sz, 64u);
assert(!ok3);
printf(" -> PASS: no further samples (buffer empty)\n");
}
// -------------------------------------------------------------------------
// FIX #3: signalPointers null guard in UpdateBrokers*
// -------------------------------------------------------------------------
static void TestUpdateBrokersNullSignalPointers() {
printf("FIX #3: UpdateBrokers* with null signalPointers — no crash...\n");
ObjectRegistryDatabase::Instance()->Purge();
DebugService service;
assert(service.traceBuffer.Init(1024 * 1024));
ConfigurationDatabase cfg;
cfg.Write("ControlPort", (uint32)0);
cfg.Write("StreamPort", (uint32)0);
cfg.Write("SuppressTimeoutLogs", (uint32)1);
assert(service.Initialise(cfg));
// Register a broker whose signalPointers is NULL but numSignals > 0.
// Before the fix this dereferenced NULL → segfault.
volatile bool active = false;
Vec<uint32> indices;
Vec<uint32> sizes;
FastPollingMutexSem mutex;
volatile bool anyBreak = false;
Vec<uint32> breakIdx;
service.RegisterBroker(NULL_PTR(DebugSignalInfo**), 1u,
NULL_PTR(MemoryMapBroker*),
&active, &indices, &sizes, &mutex,
&anyBreak, &breakIdx);
// These must not crash
service.UpdateBrokersActiveStatus();
service.UpdateBrokersBreakStatus();
printf(" -> PASS: no crash on null signalPointers\n");
}
// -------------------------------------------------------------------------
// FIX #9: GetSignalValue — large array truncated at GET_VALUE_MAX_ELEMENTS
// -------------------------------------------------------------------------
static void TestGetSignalValueTruncation() {
printf("FIX #9: GetSignalValue large array capped at %u elements...\n",
(unsigned)DebugService::GET_VALUE_MAX_ELEMENTS);
ObjectRegistryDatabase::Instance()->Purge();
DebugService service;
assert(service.traceBuffer.Init(1024 * 1024));
ConfigurationDatabase cfg;
cfg.Write("ControlPort", (uint32)0);
cfg.Write("StreamPort", (uint32)0);
cfg.Write("SuppressTimeoutLogs", (uint32)1);
assert(service.Initialise(cfg));
// Register a 512-element uint8 array (> GET_VALUE_MAX_ELEMENTS = 256)
static uint8 bigBuf[512];
for (uint32 i = 0; i < 512; i++) bigBuf[i] = (uint8)(i & 0xFF);
service.RegisterSignal(bigBuf, UnsignedInteger8Bit, "Big.Signal", 1, 512);
// VALUE command with NULL client is a smoke test — must not crash or loop
service.HandleCommand("VALUE Signal", NULL_PTR(BasicTCPSocket*));
printf(" -> PASS: VALUE on large array completed without hang\n");
// Verify directly: nElem is capped before the byte limit
// The signal has 512 uint8 elements; after fix nElem must be <= 256
DebugSignalInfo *sig = service.signals[0];
assert(sig->numberOfElements == 512u); // original unchanged
// We can't call GetSignalValue with a real socket here, but we can
// verify the constant is properly visible and correctly set
assert(DebugService::GET_VALUE_MAX_ELEMENTS == 256u);
printf(" -> PASS: GET_VALUE_MAX_ELEMENTS constant is %u\n",
(unsigned)DebugService::GET_VALUE_MAX_ELEMENTS);
}
// -------------------------------------------------------------------------
// FIX #6/#8: rate-limit and idle-timeout constants exist and are sane
// -------------------------------------------------------------------------
static void TestServerConstantsSane() {
printf("FIX #6/#8: server protection constants are valid...\n");
assert(DebugService::CMD_RATE_LIMIT > 0u);
assert(DebugService::CLIENT_IDLE_TIMEOUT_MS > 0u);
// Idle timeout must be large enough to not disconnect a healthy client
// in a single polling cycle (which is ~100 ms)
assert(DebugService::CLIENT_IDLE_TIMEOUT_MS >= 1000u);
printf(" -> PASS: CMD_RATE_LIMIT=%u, CLIENT_IDLE_TIMEOUT_MS=%u\n",
(unsigned)DebugService::CMD_RATE_LIMIT,
(unsigned)DebugService::CLIENT_IDLE_TIMEOUT_MS);
}
// -------------------------------------------------------------------------
// FIX #7: MSG key overflow — oversized key line is skipped, no buffer overrun
// -------------------------------------------------------------------------
static void TestMsgKeyBoundsCheck() {
printf("FIX #7: MSG command oversized key is skipped without crash...\n");
ObjectRegistryDatabase::Instance()->Purge();
DebugService service;
assert(service.traceBuffer.Init(1024 * 1024));
ConfigurationDatabase cfg;
cfg.Write("ControlPort", (uint32)0);
cfg.Write("StreamPort", (uint32)0);
cfg.Write("SuppressTimeoutLogs", (uint32)1);
assert(service.Initialise(cfg));
// Build a MSG command whose key is 300 characters long (> keyBuf[256]).
// Before the fix the Read() would overflow the 256-byte stack buffer.
StreamString cmd = "MSG DebugService DummyFunc 0 ";
for (uint32 i = 0; i < 300u; i++) cmd += 'K';
cmd += "=value";
// Must not crash or assert
service.HandleCommand(cmd, NULL_PTR(BasicTCPSocket*));
printf(" -> PASS: oversized key handled without buffer overflow\n");
// A normal-length key should still be accepted (regression check)
service.HandleCommand("MSG DebugService DummyFunc 0 NormalKey=NormalValue",
NULL_PTR(BasicTCPSocket*));
printf(" -> PASS: normal key not rejected by bounds check\n");
}
// -------------------------------------------------------------------------
// FIX #10: Multi-segment TCP command buffering via inputBuffer
// -------------------------------------------------------------------------
static void TestServerInputBuffering() {
printf("FIX #10: Multi-segment TCP command buffering...\n");
ObjectRegistryDatabase::Instance()->Purge();
DebugService service;
assert(service.traceBuffer.Init(1024 * 1024));
ConfigurationDatabase cfg;
cfg.Write("ControlPort", (uint32)0);
cfg.Write("StreamPort", (uint32)0);
cfg.Write("SuppressTimeoutLogs", (uint32)1);
assert(service.Initialise(cfg));
// Verify inputBuffer is initialised empty
assert(service.inputBuffer.Size() == 0u);
printf(" -> PASS: inputBuffer initialised to empty\n");
// Simulate fragment 1 arriving: "FORCE Signal " (no newline yet)
// In the real Server() this would be written via Read(); here we
// directly manipulate the carry-over buffer (friend-class access).
{
(void)service.inputBuffer.Seek(service.inputBuffer.Size());
uint32 f1Len = 13u; // length of "FORCE Signal "
service.inputBuffer.Write("FORCE Signal ", f1Len);
}
assert(service.inputBuffer.Size() == 13u);
printf(" -> PASS: partial command held in inputBuffer after fragment 1\n");
// Simulate fragment 2 completing the command: "123\n"
{
(void)service.inputBuffer.Seek(service.inputBuffer.Size());
uint32 f2Len = 4u;
service.inputBuffer.Write("123\n", f2Len);
}
// Now scan inputBuffer for complete lines — mirrors the Server() loop
const char8 *raw = service.inputBuffer.Buffer();
uint32 total = (uint32)service.inputBuffer.Size();
uint32 lineStart = 0u;
uint32 cmdsFound = 0u;
char8 cmdBuf[64];
memset(cmdBuf, 0, sizeof(cmdBuf));
for (uint32 pos = 0u; pos < total; pos++) {
if (raw[pos] != '\n') continue;
uint32 len = pos - lineStart;
if (len > 0u && raw[lineStart + len - 1u] == '\r') len--;
if (len > 0u) {
cmdsFound++;
uint32 cp = (len < 63u) ? len : 63u;
memcpy(cmdBuf, raw + lineStart, cp);
cmdBuf[cp] = '\0';
}
lineStart = pos + 1u;
}
assert(cmdsFound == 1u);
// The two fragments assembled into "FORCE Signal 123"
assert(strncmp(cmdBuf, "FORCE Signal 123", 16) == 0);
printf(" -> PASS: fragmented command reassembled to '%s'\n", cmdBuf);
// Remainder after the last '\n' should be empty (no trailing bytes)
assert(lineStart == total);
printf(" -> PASS: no partial remainder after complete command\n");
// Verify INPUT_BUFFER_MAX constant is reachable
assert(DebugService::INPUT_BUFFER_MAX > 4096u);
printf(" -> PASS: INPUT_BUFFER_MAX=%u\n",
(unsigned)DebugService::INPUT_BUFFER_MAX);
}
// -------------------------------------------------------------------------
// FIX #11: Streamer monitored-signal Push protected by tracePushMutex
// -------------------------------------------------------------------------
static void TestStreamerMonitoredPushSerialised() {
printf("FIX #11: Streamer monitored-signal Push uses tracePushMutex...\n");
ObjectRegistryDatabase::Instance()->Purge();
DebugService service;
assert(service.traceBuffer.Init(1024 * 1024));
ConfigurationDatabase cfg;
cfg.Write("ControlPort", (uint32)0);
cfg.Write("StreamPort", (uint32)0);
cfg.Write("SuppressTimeoutLogs", (uint32)1);
assert(service.Initialise(cfg));
uint32 val = 0xDEADBEEF;
service.RegisterSignal(&val, UnsignedInteger32Bit, "Mon.Val", 0, 1);
assert(service.TraceSignal("Val", true) == 1);
uint64 ts = (uint64)((float64)HighResolutionTimer::Counter() *
HighResolutionTimer::Period() * 1000000.0);
// Push via ProcessSignal (uses tracePushMutex internally — broker path)
service.ProcessSignal(service.signals[0], 4u, ts);
// Push directly via tracePushMutex (simulates the Streamer monitored-
// signal path after FIX #11 — it now also acquires tracePushMutex so
// both push paths are serialised with respect to each other).
uint32 monId = 0x80000001u;
uint32 monVal = 0x12345678u;
service.tracePushMutex.FastLock();
(void)service.traceBuffer.Push(monId, ts + 1u, &monVal, 4u);
service.tracePushMutex.FastUnLock();
// Both samples must be present in FIFO order — if there were a race
// one or both would be corrupt or missing.
uint32 id = 0, sz = 0; uint64 rts = 0; uint8 dst[64];
assert(service.traceBuffer.Pop(id, rts, dst, sz, 64u));
assert(id == service.signals[0]->internalID);
assert(sz == 4u);
assert(service.traceBuffer.Pop(id, rts, dst, sz, 64u));
assert(id == monId);
assert(sz == 4u);
assert(!service.traceBuffer.Pop(id, rts, dst, sz, 64u)); // buffer empty
printf(" -> PASS: broker and monitored-signal pushes both retrieved in order\n");
}
// -------------------------------------------------------------------------
// FIX #2: tracePushMutex — concurrent Push from two logical threads
// (single-threaded simulation: verify no data corruption across two pushes
// that would race without the mutex)
// -------------------------------------------------------------------------
static void TestTracePushSerialised() {
printf("FIX #2: Concurrent ProcessSignal serialised via tracePushMutex...\n");
ObjectRegistryDatabase::Instance()->Purge();
DebugService service;
assert(service.traceBuffer.Init(1024 * 1024));
ConfigurationDatabase cfg;
cfg.Write("ControlPort", (uint32)0);
cfg.Write("StreamPort", (uint32)0);
cfg.Write("SuppressTimeoutLogs", (uint32)1);
assert(service.Initialise(cfg));
uint32 valA = 0xAABBCCDD;
uint32 valB = 0x11223344;
service.RegisterSignal(&valA, UnsignedInteger32Bit, "P.A", 0, 1);
service.RegisterSignal(&valB, UnsignedInteger32Bit, "P.B", 0, 1);
assert(service.TraceSignal("A", true) == 1);
assert(service.TraceSignal("B", true) == 1);
uint64 ts = (uint64)((float64)HighResolutionTimer::Counter() *
HighResolutionTimer::Period() * 1000000.0);
// Simulate two sequential calls as would happen from two RT threads.
// Without tracePushMutex they could interleave and corrupt the ring.
service.ProcessSignal(service.signals[0], 4, ts);
service.ProcessSignal(service.signals[1], 4, ts + 1u);
// Both samples must be retrievable in FIFO order with correct IDs.
uint32 id, size;
uint64 rts;
uint8 buf[64];
uint32 id0 = service.signals[0]->internalID;
uint32 id1 = service.signals[1]->internalID;
assert(service.traceBuffer.Pop(id, rts, buf, size, 64u));
assert(id == id0);
assert(size == 4u);
assert(service.traceBuffer.Pop(id, rts, buf, size, 64u));
assert(id == id1);
assert(size == 4u);
printf(" -> PASS: Both samples intact after serialised push\n");
}
// -------------------------------------------------------------------------
// Fix #2 (Swap): UpdateBrokersActiveStatus publishes all active-signal
// indices correctly when multiple signals are tracing/forcing.
// -------------------------------------------------------------------------
static void TestUpdateBrokersSwapPublishesAllIndices() {
printf("Fix #2 (Swap): UpdateBrokersActiveStatus publishes all active indices...\n");
ObjectRegistryDatabase::Instance()->Purge();
DebugService service;
assert(service.traceBuffer.Init(1024 * 1024));
ConfigurationDatabase cfg;
cfg.Write("ControlPort", (uint32)0);
cfg.Write("StreamPort", (uint32)0);
cfg.Write("SuppressTimeoutLogs", (uint32)1);
assert(service.Initialise(cfg));
uint32 v0 = 0, v1 = 0, v2 = 0;
service.RegisterSignal(&v0, UnsignedInteger32Bit, "S.X", 0, 1);
service.RegisterSignal(&v1, UnsignedInteger32Bit, "S.Y", 0, 1);
service.RegisterSignal(&v2, UnsignedInteger32Bit, "S.Z", 0, 1);
// Enable tracing on X and Z (not Y)
assert(service.TraceSignal("X", true) == 1);
assert(service.TraceSignal("Z", true) == 1);
volatile bool active = false;
Vec<uint32> indices;
Vec<uint32> sizes;
FastPollingMutexSem mutex;
volatile bool anyBreak = false;
Vec<uint32> breakIdx;
DebugSignalInfo *ptrs[3] = {
service.signals[0], service.signals[1], service.signals[2]
};
service.RegisterBroker(ptrs, 3u, NULL_PTR(MemoryMapBroker *),
&active, &indices, &sizes, &mutex,
&anyBreak, &breakIdx);
service.UpdateBrokersActiveStatus();
// After Swap the broker's activeIndices must hold exactly indices 0 and 2
assert(active == true);
assert(indices.Size() == 2u);
// indices are in order of signal position in the broker (0 then 2)
assert(indices[0] == 0u);
assert(indices[1] == 2u);
printf(" -> PASS: Swap correctly published 2 active indices out of 3\n");
// Disable one; Swap again — only index 0 should remain
assert(service.TraceSignal("Z", false) == 1);
service.UpdateBrokersActiveStatus();
assert(indices.Size() == 1u);
assert(indices[0] == 0u);
printf(" -> PASS: After disabling Z, only index 0 remains\n");
}
// -------------------------------------------------------------------------
// Fix #3 (copy-before-unlock): break evaluation still works after the
// copy-indices-then-unlock refactor in DebugBrokerHelper::Process().
// -------------------------------------------------------------------------
static void TestBreakEvaluationAfterLockRelease() {
printf("Fix #3 (copy-before-unlock): break evaluation works after lock release...\n");
ObjectRegistryDatabase::Instance()->Purge();
DebugService service;
assert(service.traceBuffer.Init(1024 * 1024));
ConfigurationDatabase cfg;
cfg.Write("ControlPort", (uint32)0);
cfg.Write("StreamPort", (uint32)0);
cfg.Write("SuppressTimeoutLogs", (uint32)1);
assert(service.Initialise(cfg));
uint32 val = 5u;
service.RegisterSignal(&val, UnsignedInteger32Bit, "Brk.Val", 0, 1);
// Set break: pause when Val > 3
assert(service.SetBreak("Val", BREAK_GT, 3.0) == 1u);
service.UpdateBrokersBreakStatus();
volatile bool active = false;
Vec<uint32> indices;
Vec<uint32> sizes;
FastPollingMutexSem mutex;
volatile bool anyBreak = false;
Vec<uint32> breakIdx;
DebugSignalInfo *ptrs[1] = { service.signals[0] };
service.RegisterBroker(ptrs, 1u, NULL_PTR(MemoryMapBroker *),
&active, &indices, &sizes, &mutex,
&anyBreak, &breakIdx);
// UpdateBrokersBreakStatus must propagate the break flag to the broker
service.UpdateBrokersBreakStatus();
assert(anyBreak == true);
assert(breakIdx.Size() == 1u);
// Process() copies break indices, releases lock, evaluates break.
// val = 5 > 3 — service must become paused.
DebugBrokerHelper::Process(&service, ptrs, indices, sizes, mutex,
&anyBreak, &breakIdx);
assert(service.IsPaused());
printf(" -> PASS: break condition triggered correctly via copy-before-unlock path\n");
// Reset; change value below threshold — must NOT pause.
service.SetPaused(false);
val = 1u;
DebugBrokerHelper::Process(&service, ptrs, indices, sizes, mutex,
&anyBreak, &breakIdx);
assert(!service.IsPaused());
printf(" -> PASS: value below threshold does not trigger break\n");
}
// -------------------------------------------------------------------------
// Fix #4 (atomic decimation): decimationFactor > 1 causes exactly one Push
// after factor calls to ProcessSignal.
// -------------------------------------------------------------------------
static void TestDecimationCounterAtomic() {
printf("Fix #4 (atomic decimation): one push per decimation factor...\n");
ObjectRegistryDatabase::Instance()->Purge();
DebugService service;
assert(service.traceBuffer.Init(1024 * 1024));
ConfigurationDatabase cfg;
cfg.Write("ControlPort", (uint32)0);
cfg.Write("StreamPort", (uint32)0);
cfg.Write("SuppressTimeoutLogs", (uint32)1);
assert(service.Initialise(cfg));
uint32 val = 0x42424242u;
service.RegisterSignal(&val, UnsignedInteger32Bit, "Dec.Val", 0, 1);
// TraceSignal with decimation = 3
assert(service.TraceSignal("Val", true, 3u) == 1u);
assert(service.signals[0]->decimationFactor == 3u);
uint64 ts = (uint64)((float64)HighResolutionTimer::Counter() *
HighResolutionTimer::Period() * 1000000.0);
// Calls 1 and 2 — counter advances but no push
service.ProcessSignal(service.signals[0], 4u, ts);
service.ProcessSignal(service.signals[0], 4u, ts + 1u);
uint32 id, sz; uint64 rts; uint8 buf[64];
bool hasData = service.traceBuffer.Pop(id, rts, buf, sz, 64u);
assert(!hasData); // no sample yet
printf(" -> PASS: no push after 2 calls with decimation=3\n");
// Call 3 — counter reaches factor, one push occurs
service.ProcessSignal(service.signals[0], 4u, ts + 2u);
hasData = service.traceBuffer.Pop(id, rts, buf, sz, 64u);
assert(hasData);
assert(id == service.signals[0]->internalID);
assert(sz == 4u);
printf(" -> PASS: exactly one push after %u calls with decimation=3\n", 3u);
// Call 4 and 5 — no push again
service.ProcessSignal(service.signals[0], 4u, ts + 3u);
service.ProcessSignal(service.signals[0], 4u, ts + 4u);
hasData = service.traceBuffer.Pop(id, rts, buf, sz, 64u);
assert(!hasData);
printf(" -> PASS: no push for calls 4 and 5 (decimation reset)\n");
// Call 6 — second push
service.ProcessSignal(service.signals[0], 4u, ts + 5u);
hasData = service.traceBuffer.Pop(id, rts, buf, sz, 64u);
assert(hasData);
printf(" -> PASS: second push on call 6\n");
}
};
}
#include <signal.h>
void timeout_handler(int sig) {
printf("Test timed out!\n");
_exit(1);
}
int main() {
signal(SIGALRM, timeout_handler);
alarm(10);
printf("--- MARTe2 Debug Suite COVERAGE V34 ---\n");
MARTe::TestTcpLogger();
MARTe::DebugServiceTest::TestAll();
// FIX #1, #2, #4
MARTe::DebugServiceTest::TestForceSignalOversizeRejected();
MARTe::DebugServiceTest::TestStreamerPopMaxSize();
MARTe::DebugServiceTest::TestTracePushSerialised();
// FIX #3, #6, #7, #8, #9
MARTe::DebugServiceTest::TestUpdateBrokersNullSignalPointers();
MARTe::DebugServiceTest::TestGetSignalValueTruncation();
MARTe::DebugServiceTest::TestServerConstantsSane();
MARTe::DebugServiceTest::TestMsgKeyBoundsCheck();
// FIX #5, #10, #11
MARTe::DebugServiceTest::TestRingBufferSkipOversizedEntry();
MARTe::DebugServiceTest::TestServerInputBuffering();
MARTe::DebugServiceTest::TestStreamerMonitoredPushSerialised();
// Fix #2 (Swap), Fix #3 (copy-before-unlock), Fix #4 (atomic decimation)
MARTe::DebugServiceTest::TestUpdateBrokersSwapPublishesAllIndices();
MARTe::DebugServiceTest::TestBreakEvaluationAfterLockRelease();
MARTe::DebugServiceTest::TestDecimationCounterAtomic();
printf("\nCOVERAGE V34 PASSED!\n");
return 0;
}