#include "DebugService.h" #include "GlobalObjectsDatabase.h" #include "ObjectRegistryDatabase.h" #include "RealTimeApplication.h" #include "StandardParser.h" #include "StreamString.h" #include "StringHelper.h" #include "TestCommon.h" #include using namespace MARTe; namespace { const char8 * const debug_commands_config = "DebugService = {" " Class = DebugService " " ControlPort = 8120 " " UdpPort = 8121 " " StreamIP = \"127.0.0.1\" " "}" "App = {" " Class = RealTimeApplication " " +Functions = {" " Class = ReferenceContainer " " +GAM1 = {" " Class = IOGAM " " InputSignals = {" " Counter = { DataSource = Timer Type = uint32 Frequency = 1000 }" " Time = { DataSource = Timer Type = uint32 }" " }" " OutputSignals = {" " Counter = { DataSource = DDB Type = uint32 }" " Time = { DataSource = DDB Type = uint32 }" " }" " }" " }" " +Data = {" " Class = ReferenceContainer " " DefaultDataSource = DDB " " +Timer = { Class = LinuxTimer SleepTime = 1000 Signals = { Counter = { Type = uint32 } Time = { Type = uint32 } } }" " +DDB = { Class = GAMDataSource Signals = { Counter = { Type = uint32 } Time = { Type = uint32 } } }" " +DAMS = { Class = TimingDataSource }" " }" " +States = {" " Class = ReferenceContainer " " +State1 = { Class = RealTimeState +Threads = { Class = ReferenceContainer +Thread1 = { Class = RealTimeThread Functions = {GAM1} } } }" " }" " +Scheduler = { Class = GAMScheduler TimingDataSource = DAMS }" "}"; const uint16 CONTROL_PORT = 8120u; const char8 * const SIGNAL_NAME = "App.Data.DDB.Counter"; // Pulls the value out of a `{"Name":...,"Value":"", ...}` VALUE reply. // The command replies are fixed-format enough that a plain substring scan // is sufficient (no need for a full JSON parser in these integration tests). bool ExtractValueStr(const char8 *reply, StreamString &val) { const char8 *key = "\"Value\":\""; const char8 *p = StringHelper::SearchString(reply, key); if (p == NULL_PTR(const char8 *)) return false; p += StringHelper::Length(key); const char8 *end = StringHelper::SearchString(p, "\""); if (end == NULL_PTR(const char8 *)) return false; val = ""; while (p < end) { val += *p; p++; } return true; } bool ExtractValueUint32(const char8 *reply, uint32 &val) { StreamString s; if (!ExtractValueStr(reply, s)) return false; const char8 *p = s.Buffer(); if (p == NULL_PTR(const char8 *) || *p == '\0') return false; val = 0u; while (*p != '\0') { if (*p < '0' || *p > '9') return false; val = (val * 10u) + (uint32)(*p - '0'); p++; } return true; } bool QueryValueUint32(uint32 &val) { StreamString reply; if (!SendCommandGAM(CONTROL_PORT, "VALUE App.Data.DDB.Counter\n", reply)) return false; return ExtractValueUint32(reply.Buffer(), val); } } // namespace void TestDebugCommands() { printf("--- Test: FORCE/UNFORCE/BREAK/STEP/STEP_STATUS/VALUE/LS Commands ---\n"); ObjectRegistryDatabase::Instance()->Purge(); ConfigurationDatabase cdb; StreamString ss = debug_commands_config; ss.Seek(0); StandardParser parser(ss, cdb); if (!parser.Parse()) { printf("ERROR: Failed to parse config\n"); return; } cdb.MoveToRoot(); uint32 n = cdb.GetNumberOfChildren(); for (uint32 i = 0; i < n; i++) { const char8 *name = cdb.GetChildName(i); ConfigurationDatabase child; cdb.MoveRelative(name); cdb.Copy(child); cdb.MoveToAncestor(1u); StreamString className; child.Read("Class", className); Reference ref(className.Buffer(), GlobalObjectsDatabase::Instance()->GetStandardHeap()); if (!ref.IsValid()) { printf("ERROR: Could not create object %s of class %s\n", name, className.Buffer()); continue; } ref->SetName(name); if (!ref->Initialise(child)) { printf("ERROR: Failed to initialise object %s\n", name); continue; } ObjectRegistryDatabase::Instance()->Insert(ref); } ReferenceT service = ObjectRegistryDatabase::Instance()->Find("DebugService"); if (!service.IsValid()) { printf("ERROR: DebugService not found\n"); return; } service->SetFullConfig(cdb); ReferenceT app = ObjectRegistryDatabase::Instance()->Find("App"); if (!app.IsValid()) { printf("ERROR: App not found\n"); return; } if (!app->ConfigureApplication()) { printf("ERROR: ConfigureApplication failed.\n"); return; } if (app->PrepareNextState("State1") != ErrorManagement::NoError) { printf("ERROR: PrepareNextState failed.\n"); return; } if (app->StartNextStateExecution() != ErrorManagement::NoError) { printf("ERROR: StartNextStateExecution failed.\n"); return; } printf("Application started.\n"); Sleep::MSec(1000); // Step 1: VALUE baseline. printf("\n--- Step 1: VALUE ---\n"); { StreamString reply; if (SendCommandGAM(CONTROL_PORT, "VALUE App.Data.DDB.Counter\n", reply)) { printf("VALUE response: %s", reply.Buffer()); uint32 v = 0u; if (StringHelper::SearchString(reply.Buffer(), "OK VALUE") != NULL_PTR(const char8 *) && ExtractValueUint32(reply.Buffer(), v)) { printf("SUCCESS: VALUE returned a parseable counter value (%u).\n", v); } else { printf("FAILURE: VALUE reply malformed.\n"); } } else { printf("FAILURE: VALUE command failed.\n"); } } // Step 2: VALUE on an unknown signal should report an error, not crash. printf("\n--- Step 2: VALUE on unknown signal ---\n"); { StreamString reply; if (SendCommandGAM(CONTROL_PORT, "VALUE App.Data.DDB.NoSuchSignal\n", reply)) { printf("VALUE response: %s", reply.Buffer()); if (StringHelper::SearchString(reply.Buffer(), "Error") != NULL_PTR(const char8 *)) { printf("SUCCESS: Unknown signal reported as an error.\n"); } else { printf("FAILURE: Unknown signal did not report an error.\n"); } } else { printf("FAILURE: VALUE command failed.\n"); } } // Step 3: LS with an explicit path. printf("\n--- Step 3: LS App.Data ---\n"); { StreamString reply; if (SendCommandGAM(CONTROL_PORT, "LS App.Data\n", reply)) { printf("LS response: %s", reply.Buffer()); if (StringHelper::SearchString(reply.Buffer(), "DDB") != NULL_PTR(const char8 *) && StringHelper::SearchString(reply.Buffer(), "OK LS") != NULL_PTR(const char8 *)) { printf("SUCCESS: LS App.Data listed the DDB data source.\n"); } else { printf("FAILURE: LS App.Data did not list expected children.\n"); } } else { printf("FAILURE: LS command failed.\n"); } } // Step 4: bare LS (root). Regression test: this used to wrap the // ObjectRegistryDatabase singleton in a Reference, which decremented its // refcount to zero and deleted it when the Reference went out of scope, // permanently killing the TCP control server. Verify both that LS itself // succeeds AND that the control server is still alive afterwards. printf("\n--- Step 4: bare LS (root) ---\n"); { StreamString reply; bool lsOk = SendCommandGAM(CONTROL_PORT, "LS\n", reply); if (lsOk) { printf("LS response: %s", reply.Buffer()); } bool lsGood = lsOk && StringHelper::SearchString(reply.Buffer(), "App") != NULL_PTR(const char8 *) && StringHelper::SearchString(reply.Buffer(), "DebugService") != NULL_PTR(const char8 *) && StringHelper::SearchString(reply.Buffer(), "OK LS") != NULL_PTR(const char8 *); StreamString postReply; bool postOk = SendCommandGAM(CONTROL_PORT, "VALUE App.Data.DDB.Counter\n", postReply); bool postGood = postOk && StringHelper::SearchString(postReply.Buffer(), "OK VALUE") != NULL_PTR(const char8 *); if (lsGood && postGood) { printf("SUCCESS: bare LS listed the root and control server survived.\n"); } else { printf("FAILURE: bare LS broke the control server (lsGood=%d postGood=%d).\n", lsGood, postGood); } } // Step 5: FORCE holds the signal at a fixed value. printf("\n--- Step 5: FORCE ---\n"); { StreamString reply; bool ok = SendCommandGAM(CONTROL_PORT, "FORCE App.Data.DDB.Counter 424242\n", reply); printf("FORCE response: %s", ok ? reply.Buffer() : "(no reply)"); bool forceAccepted = ok && StringHelper::SearchString(reply.Buffer(), "OK FORCE 1") != NULL_PTR(const char8 *); Sleep::MSec(150); StreamString v1; bool v1Ok = SendCommandGAM(CONTROL_PORT, "VALUE App.Data.DDB.Counter\n", v1); Sleep::MSec(150); StreamString v2; bool v2Ok = SendCommandGAM(CONTROL_PORT, "VALUE App.Data.DDB.Counter\n", v2); bool bothForced = v1Ok && v2Ok && StringHelper::SearchString(v1.Buffer(), "\"Value\":\"424242\"") != NULL_PTR(const char8 *) && StringHelper::SearchString(v2.Buffer(), "\"Value\":\"424242\"") != NULL_PTR(const char8 *); if (forceAccepted && bothForced) { printf("SUCCESS: FORCE held the signal at 424242 across two reads.\n"); } else { printf("FAILURE: FORCE did not hold the forced value.\n"); } } // Step 6: UNFORCE lets the signal resume advancing. printf("\n--- Step 6: UNFORCE ---\n"); { StreamString reply; bool ok = SendCommandGAM(CONTROL_PORT, "UNFORCE App.Data.DDB.Counter\n", reply); printf("UNFORCE response: %s", ok ? reply.Buffer() : "(no reply)"); bool unforceAccepted = ok && StringHelper::SearchString(reply.Buffer(), "OK UNFORCE 1") != NULL_PTR(const char8 *); Sleep::MSec(200); uint32 v1 = 0u; bool v1Ok = QueryValueUint32(v1); Sleep::MSec(200); uint32 v2 = 0u; bool v2Ok = QueryValueUint32(v2); bool advancing = v1Ok && v2Ok && v1 != 424242u && v2 > v1; if (unforceAccepted && advancing) { printf("SUCCESS: UNFORCE resumed live updates (%u -> %u).\n", v1, v2); } else { printf("FAILURE: UNFORCE did not resume live updates (%u -> %u).\n", v1, v2); } } // Step 7: PAUSE holds execution, STEP advances one command-cycle at a // time while STEP_STATUS reports the paused state, RESUME lets it run // free again. // // NOTE: OutputPauseAndStep() (DebugBrokerWrapper.h) blocks the RT thread // in a nested pause/step spin *after* the current cycle's data has // already been committed. As a result, the very first STEP issued right // after a manual PAUSE only moves the thread from the first spin to the // second spin without producing a new committed cycle (no observable // value change) -- it takes a second STEP to see the counter actually // advance. This is verified/tolerated below rather than asserted away. printf("\n--- Step 7: PAUSE / STEP / STEP_STATUS / RESUME ---\n"); { StreamString pauseReply; bool pauseOk = SendCommandGAM(CONTROL_PORT, "PAUSE\n", pauseReply); printf("PAUSE response: %s", pauseOk ? pauseReply.Buffer() : "(no reply)"); Sleep::MSec(150); uint32 vPaused1 = 0u; QueryValueUint32(vPaused1); Sleep::MSec(150); uint32 vPaused2 = 0u; QueryValueUint32(vPaused2); bool heldWhilePaused = (vPaused1 == vPaused2); if (heldWhilePaused) { printf("SUCCESS: Counter held steady while paused (%u).\n", vPaused1); } else { printf("FAILURE: Counter moved while paused (%u -> %u).\n", vPaused1, vPaused2); } StreamString statusReply; bool statusOk = SendCommandGAM(CONTROL_PORT, "STEP_STATUS\n", statusReply); printf("STEP_STATUS response: %s", statusOk ? statusReply.Buffer() : "(no reply)"); bool pausedReported = statusOk && StringHelper::SearchString(statusReply.Buffer(), "\"Paused\":true") != NULL_PTR(const char8 *); if (pausedReported) { printf("SUCCESS: STEP_STATUS reports Paused=true.\n"); } else { printf("FAILURE: STEP_STATUS did not report Paused=true.\n"); } // "Primer" STEP: settles the one-time spin-transition quirk described // above; no value-change assertion here by design. StreamString step1Reply; SendCommandGAM(CONTROL_PORT, "STEP 1\n", step1Reply); Sleep::MSec(150); uint32 vAfterPrimer = 0u; QueryValueUint32(vAfterPrimer); StreamString step2Reply; bool step2Ok = SendCommandGAM(CONTROL_PORT, "STEP 2\n", step2Reply); printf("STEP 2 response: %s", step2Ok ? step2Reply.Buffer() : "(no reply)"); Sleep::MSec(200); uint32 vAfterStep = 0u; bool vAfterStepOk = QueryValueUint32(vAfterStep); bool stepAdvanced = vAfterStepOk && vAfterStep > vAfterPrimer; if (stepAdvanced) { printf("SUCCESS: STEP advanced the counter (%u -> %u).\n", vAfterPrimer, vAfterStep); } else { printf("FAILURE: STEP did not advance the counter (%u -> %u).\n", vAfterPrimer, vAfterStep); } StreamString resumeReply; bool resumeOk = SendCommandGAM(CONTROL_PORT, "RESUME\n", resumeReply); printf("RESUME response: %s", resumeOk ? resumeReply.Buffer() : "(no reply)"); Sleep::MSec(200); uint32 vResumed1 = 0u; QueryValueUint32(vResumed1); Sleep::MSec(200); uint32 vResumed2 = 0u; bool resumedOk = QueryValueUint32(vResumed2); bool freeRunning = resumedOk && vResumed2 > vResumed1; if (freeRunning) { printf("SUCCESS: RESUME let execution run free again (%u -> %u).\n", vResumed1, vResumed2); } else { printf("FAILURE: Execution did not resume (%u -> %u).\n", vResumed1, vResumed2); } } // Step 8: BREAK auto-pauses when the condition is met and holds the // value; BREAK ... OFF clears the condition without auto-resuming. printf("\n--- Step 8: BREAK / BREAK OFF ---\n"); { uint32 baseline = 0u; QueryValueUint32(baseline); // Counter increments ~1 per RT cycle (1 kHz); +300 is reachable well // within the retry budget below without being trivially already-true. uint32 target = baseline + 300u; StreamString cmd; cmd.Printf("BREAK App.Data.DDB.Counter > %u\n", target); StreamString breakReply; bool breakOk = SendCommandGAM(CONTROL_PORT, cmd.Buffer(), breakReply); printf("BREAK response: %s", breakOk ? breakReply.Buffer() : "(no reply)"); bool breakAccepted = breakOk && StringHelper::SearchString(breakReply.Buffer(), "OK BREAK 1") != NULL_PTR(const char8 *); bool pausedByBreak = false; for (int retry = 0; retry < 30 && !pausedByBreak; retry++) { Sleep::MSec(100); StreamString statusReply; if (SendCommandGAM(CONTROL_PORT, "STEP_STATUS\n", statusReply)) { if (StringHelper::SearchString(statusReply.Buffer(), "\"Paused\":true") != NULL_PTR(const char8 *)) { pausedByBreak = true; } } } if (pausedByBreak) { printf("SUCCESS: BREAK auto-paused once the condition was met.\n"); } else { printf("FAILURE: BREAK never paused execution (target=%u).\n", target); } uint32 held1 = 0u, held2 = 0u; QueryValueUint32(held1); Sleep::MSec(200); QueryValueUint32(held2); bool heldAfterBreak = (held1 == held2) && (held1 >= target); if (heldAfterBreak) { printf("SUCCESS: Value held at/above the break target (%u).\n", held1); } else { printf("FAILURE: Value not held after break (%u -> %u, target=%u).\n", held1, held2, target); } StreamString offReply; bool offOk = SendCommandGAM(CONTROL_PORT, "BREAK App.Data.DDB.Counter OFF\n", offReply); printf("BREAK OFF response: %s", offOk ? offReply.Buffer() : "(no reply)"); bool offAccepted = offOk && StringHelper::SearchString(offReply.Buffer(), "OK BREAK") != NULL_PTR(const char8 *); StreamString statusAfterOff; bool statusOk = SendCommandGAM(CONTROL_PORT, "STEP_STATUS\n", statusAfterOff); bool stillPaused = statusOk && StringHelper::SearchString(statusAfterOff.Buffer(), "\"Paused\":true") != NULL_PTR(const char8 *); if (stillPaused) { printf("SUCCESS: BREAK OFF cleared the condition without auto-resuming.\n"); } else { printf("FAILURE: Clearing BREAK unexpectedly changed the paused state.\n"); } if (!breakAccepted || !offAccepted) { printf("FAILURE: BREAK/BREAK OFF command replies malformed.\n"); } StreamString resumeReply; SendCommandGAM(CONTROL_PORT, "RESUME\n", resumeReply); Sleep::MSec(200); uint32 vFinal1 = 0u, vFinal2 = 0u; QueryValueUint32(vFinal1); Sleep::MSec(200); bool finalOk = QueryValueUint32(vFinal2); if (finalOk && vFinal2 > vFinal1) { printf("SUCCESS: Execution resumed after BREAK OFF + RESUME.\n"); } else { printf("FAILURE: Execution did not resume after BREAK OFF + RESUME.\n"); } } // Step 9: TREE on a DataSource path exercises the signal-listing branch // of ExportTreeNode (as opposed to the plain child-container branch that // a bare/root TREE hits). printf("\n--- Step 9: TREE App.Data.Timer (DataSource) ---\n"); { StreamString reply; if (SendCommandGAM(CONTROL_PORT, "TREE App.Data.Timer\n", reply)) { printf("TREE response: %s", reply.Buffer()); if (StringHelper::SearchString(reply.Buffer(), "\"Class\":\"Signal\"") != NULL_PTR(const char8 *) && StringHelper::SearchString(reply.Buffer(), "OK TREE") != NULL_PTR(const char8 *)) { printf("SUCCESS: TREE on a DataSource listed its signals.\n"); } else { printf("FAILURE: TREE on a DataSource did not list signals.\n"); } } else { printf("FAILURE: TREE command failed.\n"); } } // Step 10: TREE on a GAM path exercises the InputSignal/OutputSignal // listing branches of ExportTreeNode. printf("\n--- Step 10: TREE App.Functions.GAM1 (GAM) ---\n"); { StreamString reply; if (SendCommandGAM(CONTROL_PORT, "TREE App.Functions.GAM1\n", reply)) { printf("TREE response: %s", reply.Buffer()); if (StringHelper::SearchString(reply.Buffer(), "\"Class\":\"InputSignal\"") != NULL_PTR(const char8 *) && StringHelper::SearchString(reply.Buffer(), "\"Class\":\"OutputSignal\"") != NULL_PTR(const char8 *) && StringHelper::SearchString(reply.Buffer(), "OK TREE") != NULL_PTR(const char8 *)) { printf("SUCCESS: TREE on a GAM listed its input and output signals.\n"); } else { printf("FAILURE: TREE on a GAM did not list In/Out signals.\n"); } } else { printf("FAILURE: TREE command failed.\n"); } } // Step 11: INFO on a name that matches neither a registry object nor a // signal alias must report an error rather than crash. printf("\n--- Step 11: INFO on unknown name ---\n"); { StreamString reply; if (SendCommandGAM(CONTROL_PORT, "INFO App.Data.DDB.NoSuchThing\n", reply)) { printf("INFO response: %s", reply.Buffer()); if (StringHelper::SearchString(reply.Buffer(), "Object not found") != NULL_PTR(const char8 *) && StringHelper::SearchString(reply.Buffer(), "OK INFO") != NULL_PTR(const char8 *)) { printf("SUCCESS: INFO on an unknown name reported an error.\n"); } else { printf("FAILURE: INFO on an unknown name did not report an error.\n"); } } else { printf("FAILURE: INFO command failed.\n"); } } app->StopCurrentStateExecution(); ObjectRegistryDatabase::Instance()->Purge(); } // --------------------------------------------------------------------------- // TestDebugConfigAutoRebuild // --------------------------------------------------------------------------- // // CONFIG normally serves back the ConfigurationDatabase handed to // SetFullConfig() by the application's own startup code. If SetFullConfig() // is never called (manualConfigSet stays false), ServeConfig() instead // falls back to RebuildConfigFromRegistry(), which walks the live // ObjectRegistryDatabase tree (BuildCDBFromContainer) to reconstruct an // equivalent configuration on demand. This exercises that fallback path. void TestDebugConfigAutoRebuild() { printf("--- Test: CONFIG auto-rebuild from registry (no SetFullConfig) ---\n"); ObjectRegistryDatabase::Instance()->Purge(); ConfigurationDatabase cdb; StreamString ss = debug_commands_config; ss.Seek(0); StandardParser parser(ss, cdb); if (!parser.Parse()) { printf("ERROR: Failed to parse config\n"); return; } cdb.MoveToRoot(); uint32 n = cdb.GetNumberOfChildren(); for (uint32 i = 0; i < n; i++) { const char8 *name = cdb.GetChildName(i); ConfigurationDatabase child; cdb.MoveRelative(name); cdb.Copy(child); cdb.MoveToAncestor(1u); StreamString className; child.Read("Class", className); Reference ref(className.Buffer(), GlobalObjectsDatabase::Instance()->GetStandardHeap()); if (!ref.IsValid()) { printf("ERROR: Could not create object %s of class %s\n", name, className.Buffer()); continue; } ref->SetName(name); if (!ref->Initialise(child)) { printf("ERROR: Failed to initialise object %s\n", name); continue; } ObjectRegistryDatabase::Instance()->Insert(ref); } ReferenceT service = ObjectRegistryDatabase::Instance()->Find("DebugService"); if (!service.IsValid()) { printf("ERROR: DebugService not found\n"); return; } // Deliberately do NOT call service->SetFullConfig(cdb) here: CONFIG must // still work by rebuilding from the registry on the fly. ReferenceT app = ObjectRegistryDatabase::Instance()->Find("App"); if (!app.IsValid()) { printf("ERROR: App not found\n"); return; } if (!app->ConfigureApplication()) { printf("ERROR: ConfigureApplication failed.\n"); return; } if (app->PrepareNextState("State1") != ErrorManagement::NoError) { printf("ERROR: PrepareNextState failed.\n"); return; } if (app->StartNextStateExecution() != ErrorManagement::NoError) { printf("ERROR: StartNextStateExecution failed.\n"); return; } printf("Application started.\n"); Sleep::MSec(1000); printf("\n--- Step 1: CONFIG without a prior SetFullConfig ---\n"); { StreamString reply; if (SendCommandGAM(CONTROL_PORT, "CONFIG\n", reply)) { printf("CONFIG response (len=%llu)\n", reply.Size()); if (StringHelper::SearchString(reply.Buffer(), "GAM1") != NULL_PTR(const char8 *) && StringHelper::SearchString(reply.Buffer(), "DebugService") != NULL_PTR(const char8 *) && StringHelper::SearchString(reply.Buffer(), "OK CONFIG") != NULL_PTR(const char8 *)) { printf("SUCCESS: CONFIG rebuilt the configuration from the " "registry.\n"); } else { printf("FAILURE: CONFIG (auto-rebuild) reply missing expected " "content.\n"); } } else { printf("FAILURE: CONFIG command failed.\n"); } } app->StopCurrentStateExecution(); ObjectRegistryDatabase::Instance()->Purge(); }