From 2d5ca20ae49dcb6403bc76d0d7d1cedbb2982d9c Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Thu, 2 Jul 2026 16:27:40 +0200 Subject: [PATCH] minor changes and addeed debug tests --- CLAUDE.md | 2 +- .../DebugService/DebugServiceBase.cpp | 175 +---- .../DebugService/DebugServiceBase.h | 2 - Test/E2E/suite/client/chain-client | Bin 8739721 -> 8739721 bytes Test/E2E/suite/collect.py | 9 +- Test/E2E/suite/debugclient/debugclient | Bin 6820164 -> 6820164 bytes Test/Integration/DebugCommandsTest.cpp | 680 ++++++++++++++++++ Test/Integration/IntegrationTests.cpp | 12 +- Test/Integration/Makefile.inc | 2 +- 9 files changed, 724 insertions(+), 158 deletions(-) create mode 100644 Test/Integration/DebugCommandsTest.cpp diff --git a/CLAUDE.md b/CLAUDE.md index 47d2863..a5ef047 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -39,7 +39,7 @@ cd Client/streamhub-qt && cmake -B build && cmake --build build End-to-end demo script (build + launch full stack, see header for ports/options): `./run_streamhub.sh`. -**Streaming-chain E2E suite** (`Test/E2E/suite/`): `./run_e2e.sh [--skip-build] [--only ] [--cpp-coverage]` drives the full chain per scenario (`scenarios.py`) — generates typed/shaped input + both cfgs, runs MARTe2+StreamHub, records via the Go `chain-client` (live/zoom/window/trigger), and validates the recorded waveform against an analytic/fed oracle (`validate_waveform.py`: fidelity gates correctness, sine shape-fit is a gross-sanity gate + tracked metric pending Phase-A timestamp calibration). It then runs the unit suites + coverage (`collect.py`: C++ GTest, Go, Python; `--cpp-coverage` does an instrumented `--coverage` rebuild, captures with lcov restricted to `Source/*`+`Test/*`, then restores the clean build), consolidates everything into `report_data.json` with per-field progression/regression vs the previous run and trend plots (`report_build.py`, history in `Build/x86-linux/E2E/chain/history.jsonl`), and compiles a Typst PDF (`E2E_Report.typ`). Python framework unit tests: `python3 -m unittest tests_py` (in `Test/E2E/suite/`). +**Streaming-chain E2E suite** (`Test/E2E/suite/`): `./run_e2e.sh [--skip-build] [--only ] [--cpp-coverage]` drives the full chain per scenario (`scenarios.py`) — generates typed/shaped input + both cfgs, runs MARTe2+StreamHub, records via the Go `chain-client` (live/zoom/window/trigger), and validates the recorded waveform against an analytic/fed oracle (`validate_waveform.py`: fidelity gates correctness, sine shape-fit is a gross-sanity gate + tracked metric pending Phase-A timestamp calibration). It then runs the unit suites + coverage (`collect.py`: C++ GTest, Go, Python; `--cpp-coverage` does an instrumented `--coverage` rebuild, captures with lcov restricted to `Source/*` (the `Test/` harness itself is excluded — it executes every line by construction and would just inflate the number), then restores the clean build), consolidates everything into `report_data.json` with per-field progression/regression vs the previous run and trend plots (`report_build.py`, history in `Build/x86-linux/E2E/chain/history.jsonl`), and compiles a Typst PDF (`E2E_Report.typ`). Python framework unit tests: `python3 -m unittest tests_py` (in `Test/E2E/suite/`). Build output goes to `Build/x86-linux/` (shared libs per component, `.ex` executables). diff --git a/Source/Components/Interfaces/DebugService/DebugServiceBase.cpp b/Source/Components/Interfaces/DebugService/DebugServiceBase.cpp index 16212be..69703b9 100644 --- a/Source/Components/Interfaces/DebugService/DebugServiceBase.cpp +++ b/Source/Components/Interfaces/DebugService/DebugServiceBase.cpp @@ -1147,23 +1147,33 @@ void DebugServiceBase::InfoNode(const char8 *path, StreamString &out) { } void DebugServiceBase::ListNodes(const char8 *path, StreamString &out) { - Reference ref = + bool isRoot = (path == NULL_PTR(const char8 *) || StringHelper::Length(path) == 0 || - StringHelper::Compare(path, "/") == 0) - ? ObjectRegistryDatabase::Instance() - : ObjectRegistryDatabase::Instance()->Find(path); + StringHelper::Compare(path, "/") == 0); + + // NOTE: ObjectRegistryDatabase::Instance() is a raw, long-lived singleton + // pointer that is never itself owned by a Reference. Wrapping it in a + // Reference here (as previously done via a ternary) would increment its + // reference count and then delete it when the local Reference goes out of + // scope, destroying the registry. Keep the root case as a raw pointer. + ReferenceContainer *rc = NULL_PTR(ReferenceContainer *); + Reference ref; + if (isRoot) { + rc = ObjectRegistryDatabase::Instance(); + } else { + ref = ObjectRegistryDatabase::Instance()->Find(path); + if (ref.IsValid()) { + rc = dynamic_cast(ref.operator->()); + } + } out.Printf("Nodes under %s:\n", path ? path : "/"); - if (ref.IsValid()) { - ReferenceContainer *rc = - dynamic_cast(ref.operator->()); - if (rc != NULL_PTR(ReferenceContainer *)) { - uint32 n = rc->Size(); - for (uint32 i = 0u; i < n; i++) { - Reference c = rc->Get(i); - if (c.IsValid()) { - out.Printf(" %s [%s]\n", c->GetName(), - c->GetClassProperties()->GetName()); - } + if (rc != NULL_PTR(ReferenceContainer *)) { + uint32 n = rc->Size(); + for (uint32 i = 0u; i < n; i++) { + Reference c = rc->Get(i); + if (c.IsValid()) { + out.Printf(" %s [%s]\n", c->GetName(), + c->GetClassProperties()->GetName()); } } } else { @@ -1198,141 +1208,6 @@ void DebugServiceBase::RebuildConfigFromRegistry() { RebuildTransportConfig(); } -// --------------------------------------------------------------------------- -// Tree export -// --------------------------------------------------------------------------- - -uint32 DebugServiceBase::ExportTree(ReferenceContainer *container, - StreamString &json, - const char8 *pathPrefix) { - if (container == NULL_PTR(ReferenceContainer *)) - return 0u; - uint32 size = container->Size(); - uint32 valid = 0u; - for (uint32 i = 0u; i < size; i++) { - Reference child = container->Get(i); - if (!child.IsValid()) - continue; - if (valid > 0u) - json += ",\n"; - const char8 *cname = child->GetName(); - if (cname == NULL_PTR(const char8 *)) - cname = "unnamed"; - StreamString cp; - if (pathPrefix != NULL_PTR(const char8 *)) - cp.Printf("%s.%s", pathPrefix, cname); - else - cp = cname; - - StreamString nj; - nj += "{\"Name\":\""; - EscapeJson(cname, nj); - nj += "\",\"Class\":\""; - EscapeJson(child->GetClassProperties()->GetName(), nj); - nj += "\""; - - ReferenceContainer *inner = - dynamic_cast(child.operator->()); - DataSourceI *ds = dynamic_cast(child.operator->()); - GAM *gam = dynamic_cast(child.operator->()); - - if (inner != NULL_PTR(ReferenceContainer *) || - ds != NULL_PTR(DataSourceI *) || gam != NULL_PTR(GAM *)) { - nj += ",\"Children\":[\n"; - uint32 sc = 0u; - if (inner != NULL_PTR(ReferenceContainer *)) - sc += ExportTree(inner, nj, cp.Buffer()); - if (ds != NULL_PTR(DataSourceI *)) { - uint32 ns = ds->GetNumberOfSignals(); - for (uint32 j = 0u; j < ns; j++) { - if (sc > 0u) { - nj += ",\n"; - } - sc++; - StreamString sn; - (void)ds->GetSignalName(j, sn); - const char8 *st = TypeDescriptor::GetTypeNameFromTypeDescriptor( - ds->GetSignalType(j)); - uint8 d = 0u; - (void)ds->GetSignalNumberOfDimensions(j, d); - uint32 el = 0u; - (void)ds->GetSignalNumberOfElements(j, el); - StreamString sfp; - sfp.Printf("%s.%s", cp.Buffer(), sn.Buffer()); - bool tr = false, fo = false; - (void)IsInstrumented(sfp.Buffer(), tr, fo); - nj += "{\"Name\":\""; - EscapeJson(sn.Buffer(), nj); - nj += "\",\"Class\":\"Signal\",\"Type\":\""; - EscapeJson(st ? st : "Unknown", nj); - nj.Printf("\",\"Dimensions\":%u,\"Elements\":%u," - "\"IsTraceable\":%s,\"IsForcable\":%s}", - d, el, tr ? "true" : "false", fo ? "true" : "false"); - } - } - if (gam != NULL_PTR(GAM *)) { - uint32 nIn = gam->GetNumberOfInputSignals(); - for (uint32 j = 0u; j < nIn; j++) { - if (sc > 0u) { - nj += ",\n"; - } - sc++; - StreamString sn; - (void)gam->GetSignalName(InputSignals, j, sn); - const char8 *st = TypeDescriptor::GetTypeNameFromTypeDescriptor( - gam->GetSignalType(InputSignals, j)); - uint32 d = 0u; - (void)gam->GetSignalNumberOfDimensions(InputSignals, j, d); - uint32 el = 0u; - (void)gam->GetSignalNumberOfElements(InputSignals, j, el); - StreamString sfp; - sfp.Printf("%s.In.%s", cp.Buffer(), sn.Buffer()); - bool tr = false, fo = false; - (void)IsInstrumented(sfp.Buffer(), tr, fo); - nj += "{\"Name\":\"In."; - EscapeJson(sn.Buffer(), nj); - nj += "\",\"Class\":\"InputSignal\",\"Type\":\""; - EscapeJson(st ? st : "Unknown", nj); - nj.Printf("\",\"Dimensions\":%u,\"Elements\":%u," - "\"IsTraceable\":%s,\"IsForcable\":%s}", - d, el, tr ? "true" : "false", fo ? "true" : "false"); - } - uint32 nOut = gam->GetNumberOfOutputSignals(); - for (uint32 j = 0u; j < nOut; j++) { - if (sc > 0u) { - nj += ",\n"; - } - sc++; - StreamString sn; - (void)gam->GetSignalName(OutputSignals, j, sn); - const char8 *st = TypeDescriptor::GetTypeNameFromTypeDescriptor( - gam->GetSignalType(OutputSignals, j)); - uint32 d = 0u; - (void)gam->GetSignalNumberOfDimensions(OutputSignals, j, d); - uint32 el = 0u; - (void)gam->GetSignalNumberOfElements(OutputSignals, j, el); - StreamString sfp; - sfp.Printf("%s.Out.%s", cp.Buffer(), sn.Buffer()); - bool tr = false, fo = false; - (void)IsInstrumented(sfp.Buffer(), tr, fo); - nj += "{\"Name\":\"Out."; - EscapeJson(sn.Buffer(), nj); - nj += "\",\"Class\":\"OutputSignal\",\"Type\":\""; - EscapeJson(st ? st : "Unknown", nj); - nj.Printf("\",\"Dimensions\":%u,\"Elements\":%u," - "\"IsTraceable\":%s,\"IsForcable\":%s}", - d, el, tr ? "true" : "false", fo ? "true" : "false"); - } - } - nj += "\n]"; - } - nj += "}"; - json += nj; - valid++; - } - return valid; -} - // --------------------------------------------------------------------------- // EnrichWithConfig // --------------------------------------------------------------------------- diff --git a/Source/Components/Interfaces/DebugService/DebugServiceBase.h b/Source/Components/Interfaces/DebugService/DebugServiceBase.h index 3b1dd2d..4185379 100644 --- a/Source/Components/Interfaces/DebugService/DebugServiceBase.h +++ b/Source/Components/Interfaces/DebugService/DebugServiceBase.h @@ -174,8 +174,6 @@ protected: void UpdateBrokersBreakStatus(); void PatchRegistry(); - uint32 ExportTree(ReferenceContainer *container, StreamString &json, - const char8 *pathPrefix); void ExportTreeNode(const char8 *path, StreamString &out); void EnrichWithConfig(const char8 *path, StreamString &json); static void JsonifyDatabase(ConfigurationDatabase &db, StreamString &json); diff --git a/Test/E2E/suite/client/chain-client b/Test/E2E/suite/client/chain-client index e555e9fa431aea6c265973a41a73848bed779535..d578e0776b3261d513c8e0671178dcb916ea9cbb 100755 GIT binary patch delta 801 zcmbWyxpop^06<|hXf!Uk#3inAiMWlk@G~rK!~`)!$sl0BEi#!Qj6+n262v9O=;5SE zB@3FAs1=P|$jg}TPK7!X*u=vVb+{N~?cM)GIzPW558Bfnn7(?ZTG-Xv&Qb^V0 zQYATP=i=^Ytu~e#HWDGVoRLMLnY8nb5o;!1&5fIqld6|x&CMIyWFg@iO+y#)j9Z^h zd%PZxrx#sFq;j66Z{Krzcz*Twrw`_5zqH!@@#}zGaZFvv3mspp<6DEZ=zMeBx);uPH*abWNBd7ztU5s@RsQ2W(X+rC_O8jMzo3sFlo>KbGk0 z(XvW7sw&Z7XvGmZsTH#Yg3u8{7!gEq9v5&Cmv9*|4B`rgFpM~^B7tidK@y|5jvGi} z3)<1H5OuI-5T{rERGzWqlzlXvnH;8Cp?yj9}0)G)FG!x?;;x(F)2^NC{bnvi`@`272YJ zqJ$+atSIXhE$44X91S`G2qFXnVO+vxT)|b0AOaKDa2=zFB8E6_Ab})O7{fTy$lxY! zVFHt#h?D*O?SB{AEZ{b#kVgS`a2NM*9}n;lk5EJj7Hl{u<1waD!3>_j#Z%1U8LFtE kjycR@0gHH!2AXK$1zzG6mavT1c!RfC!MjdHdq3#=3za1y)&Kwi diff --git a/Test/E2E/suite/collect.py b/Test/E2E/suite/collect.py index d42535b..5a8c0f8 100644 --- a/Test/E2E/suite/collect.py +++ b/Test/E2E/suite/collect.py @@ -279,12 +279,15 @@ def cpp_coverage(repo, target): if rc != 0 or not os.path.exists(raw): cov["note"] = "lcov capture failed: " + (err or out or "")[-160:] return cov - # Keep only this repo's own sources so the number reflects project code, - # not the MARTe2 framework headers dragged in by templates/inlines. + # Keep only this repo's own Source/ code so the number reflects project + # code under test, not the MARTe2 framework headers dragged in by + # templates/inlines, and not the Test/ harness itself (GTest/Integration + # test .cpp files execute every line by construction and sit at ~100%, + # which would just inflate the aggregate and clutter the per-file table + # with files that were never meant to be "covered"). info = os.path.join(build, "coverage.info") rc2, _, e2 = _run(["lcov", "--extract", raw, os.path.join(repo, "Source", "*"), - os.path.join(repo, "Test", "*"), "--output-file", info, "--quiet"] + ign, timeout=300) summ_file = info if (rc2 == 0 and os.path.exists(info)) else raw # Parse the tracefile directly for per-file detail; this also yields the diff --git a/Test/E2E/suite/debugclient/debugclient b/Test/E2E/suite/debugclient/debugclient index ce096513ab747f43eaf928c1ae9e3e764c1b3969..cee52f816b30f5b36c85b0f1274ee67b8a60cc8b 100755 GIT binary patch delta 719 zcmb`>$xagi0LJmr;sP#p!F^wGE3~-Yo9-b*6=k5uPjBp#-zSjsuqlL;8hK) zW|^W{cvjZr>P$T!eIgg@FZ7ITiGe>{s^q;|J*zJ*m_~ZBW);N=Z!AAL{(N@cZ>Ag8 zva1_YS)of1gkFST<}6`#B=`FF%P;T0fBX5W_V(AnyC16=F{n72D!%E2Tb=M0Y()rN z*oN)cft~0^4|ZWU_FykIzx%Ks2XGL*IE2GEf}=Qw<2ZqnIEB+VgR?k?^SFSExP;5? zPPp&S-?SpBimNHAqs9_(SCS>qm9?lRtCAn{Z9C!FimmugkF#6}-0r94Mt`&N*c((N zMTvG|Ae<{i0}+UUa-T*Wn9#|=c#4-pbJw+f|(0B&Ls zw=mRBZQPz)`H$r-$}x-)jKaWOm`Gs^X=ISaIC7Z4B=VR-0n@N>4@JzNgjvktJ|5s9 U=J5zN9F*b0gWq0nE)+lh10uF4_5c6? delta 719 zcmb`>Idc+m0ETgtcqD4@i1!uGcx`s~pJfj;9%K;$$Pq4MykJ?v3gQsaj25P_ptsQR zC>$+pZ5+lvpTQ5Hc1-M2*!ko;Sp2&8oq3i&ee^HmYcsG^*B&$~o@v|9vhA6~qS0(D zl;vW&Wrs7m{X8FObQ~#{4(AebvlgE9vTkc3X}0I1k$AmTD9QTbqiMq}3$s?wW|E0` zF%=cQ0iVw|0zaauf=_sVUcJwMZhw36<;UM&!_vFAufNMpucB-6%YLBS53Iv__%VPD z*oaNoj6n=x3$|h#hOzeDjvd&ET^PY`?7?2_!+spVK^($i9KlhH;uwzO1Ww`+RrTl$aXUCI&06l_Fr9Z|$EjW`lW;s#PkBZDkvkV75?6j8!W%;Fa2a2t1U7x!=< RWmMpx3KzBBN@u?G;U9m!CoBK} diff --git a/Test/Integration/DebugCommandsTest.cpp b/Test/Integration/DebugCommandsTest.cpp new file mode 100644 index 0000000..8e7ad6c --- /dev/null +++ b/Test/Integration/DebugCommandsTest.cpp @@ -0,0 +1,680 @@ +#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(); +} diff --git a/Test/Integration/IntegrationTests.cpp b/Test/Integration/IntegrationTests.cpp index 4e53b27..ed91341 100644 --- a/Test/Integration/IntegrationTests.cpp +++ b/Test/Integration/IntegrationTests.cpp @@ -31,6 +31,8 @@ void RunValidationTest(); void TestConfigCommands(); void TestGAMSignalTracing(); void TestTreeCommand(); +void TestDebugCommands(); +void TestDebugConfigAutoRebuild(); int main() { signal(SIGALRM, timeout_handler); @@ -90,7 +92,15 @@ int main() { printf("\n--- Test 7: Custom MARTe Message (MSG) ---\n"); TestMessageCommand(); Sleep::MSec(1000); - + + printf("\n--- Test 8: FORCE/UNFORCE/BREAK/STEP/STEP_STATUS/VALUE/LS Commands ---\n"); + TestDebugCommands(); + Sleep::MSec(1000); + + printf("\n--- Test 9: CONFIG Auto-Rebuild From Registry ---\n"); + TestDebugConfigAutoRebuild(); + Sleep::MSec(1000); + printf("\nAll Integration Tests Finished.\n"); return 0; diff --git a/Test/Integration/Makefile.inc b/Test/Integration/Makefile.inc index e72f1ac..9895c74 100644 --- a/Test/Integration/Makefile.inc +++ b/Test/Integration/Makefile.inc @@ -1,4 +1,4 @@ -OBJSX = SchedulerTest.x TraceTest.x ValidationTest.x ConfigCommandTest.x TreeCommandTest.x MessageCommandTest.x TestCommon.x +OBJSX = SchedulerTest.x TraceTest.x ValidationTest.x ConfigCommandTest.x TreeCommandTest.x MessageCommandTest.x DebugCommandsTest.x TestCommon.x PACKAGE = Test/Integration