720 lines
31 KiB
C++
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;
|
|
}
|