#include #include #include #include #include #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 indices; Vec sizes; FastPollingMutexSem mutex; DebugSignalInfo* ptrs[1] = { service.signals[0] }; volatile bool anyBreak = false; Vec 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 indices; Vec sizes; FastPollingMutexSem mutex; volatile bool anyBreak = false; Vec 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 indices; Vec sizes; FastPollingMutexSem mutex; volatile bool anyBreak = false; Vec 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 indices; Vec sizes; FastPollingMutexSem mutex; volatile bool anyBreak = false; Vec 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 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; }