minor changes and addeed debug tests

This commit is contained in:
Martino Ferrari
2026-07-02 16:27:40 +02:00
parent f2042d624b
commit 2d5ca20ae4
9 changed files with 724 additions and 158 deletions
+1 -1
View File
@@ -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`. 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 <id>] [--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 <id>] [--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). Build output goes to `Build/x86-linux/` (shared libs per component, `.ex` executables).
@@ -1147,23 +1147,33 @@ void DebugServiceBase::InfoNode(const char8 *path, StreamString &out) {
} }
void DebugServiceBase::ListNodes(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 || (path == NULL_PTR(const char8 *) || StringHelper::Length(path) == 0 ||
StringHelper::Compare(path, "/") == 0) StringHelper::Compare(path, "/") == 0);
? ObjectRegistryDatabase::Instance()
: ObjectRegistryDatabase::Instance()->Find(path); // 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<ReferenceContainer *>(ref.operator->());
}
}
out.Printf("Nodes under %s:\n", path ? path : "/"); out.Printf("Nodes under %s:\n", path ? path : "/");
if (ref.IsValid()) { if (rc != NULL_PTR(ReferenceContainer *)) {
ReferenceContainer *rc = uint32 n = rc->Size();
dynamic_cast<ReferenceContainer *>(ref.operator->()); for (uint32 i = 0u; i < n; i++) {
if (rc != NULL_PTR(ReferenceContainer *)) { Reference c = rc->Get(i);
uint32 n = rc->Size(); if (c.IsValid()) {
for (uint32 i = 0u; i < n; i++) { out.Printf(" %s [%s]\n", c->GetName(),
Reference c = rc->Get(i); c->GetClassProperties()->GetName());
if (c.IsValid()) {
out.Printf(" %s [%s]\n", c->GetName(),
c->GetClassProperties()->GetName());
}
} }
} }
} else { } else {
@@ -1198,141 +1208,6 @@ void DebugServiceBase::RebuildConfigFromRegistry() {
RebuildTransportConfig(); 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<ReferenceContainer *>(child.operator->());
DataSourceI *ds = dynamic_cast<DataSourceI *>(child.operator->());
GAM *gam = dynamic_cast<GAM *>(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 // EnrichWithConfig
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -174,8 +174,6 @@ protected:
void UpdateBrokersBreakStatus(); void UpdateBrokersBreakStatus();
void PatchRegistry(); void PatchRegistry();
uint32 ExportTree(ReferenceContainer *container, StreamString &json,
const char8 *pathPrefix);
void ExportTreeNode(const char8 *path, StreamString &out); void ExportTreeNode(const char8 *path, StreamString &out);
void EnrichWithConfig(const char8 *path, StreamString &json); void EnrichWithConfig(const char8 *path, StreamString &json);
static void JsonifyDatabase(ConfigurationDatabase &db, StreamString &json); static void JsonifyDatabase(ConfigurationDatabase &db, StreamString &json);
Binary file not shown.
+6 -3
View File
@@ -279,12 +279,15 @@ def cpp_coverage(repo, target):
if rc != 0 or not os.path.exists(raw): if rc != 0 or not os.path.exists(raw):
cov["note"] = "lcov capture failed: " + (err or out or "")[-160:] cov["note"] = "lcov capture failed: " + (err or out or "")[-160:]
return cov return cov
# Keep only this repo's own sources so the number reflects project code, # Keep only this repo's own Source/ code so the number reflects project
# not the MARTe2 framework headers dragged in by templates/inlines. # 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") info = os.path.join(build, "coverage.info")
rc2, _, e2 = _run(["lcov", "--extract", raw, rc2, _, e2 = _run(["lcov", "--extract", raw,
os.path.join(repo, "Source", "*"), os.path.join(repo, "Source", "*"),
os.path.join(repo, "Test", "*"),
"--output-file", info, "--quiet"] + ign, timeout=300) "--output-file", info, "--quiet"] + ign, timeout=300)
summ_file = info if (rc2 == 0 and os.path.exists(info)) else raw 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 # Parse the tracefile directly for per-file detail; this also yields the
Binary file not shown.
+680
View File
@@ -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 <stdio.h>
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":"<v>", ...}` 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<DebugService> service =
ObjectRegistryDatabase::Instance()->Find("DebugService");
if (!service.IsValid()) {
printf("ERROR: DebugService not found\n");
return;
}
service->SetFullConfig(cdb);
ReferenceT<RealTimeApplication> 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<DebugService> 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<RealTimeApplication> 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();
}
+11 -1
View File
@@ -31,6 +31,8 @@ void RunValidationTest();
void TestConfigCommands(); void TestConfigCommands();
void TestGAMSignalTracing(); void TestGAMSignalTracing();
void TestTreeCommand(); void TestTreeCommand();
void TestDebugCommands();
void TestDebugConfigAutoRebuild();
int main() { int main() {
signal(SIGALRM, timeout_handler); signal(SIGALRM, timeout_handler);
@@ -90,7 +92,15 @@ int main() {
printf("\n--- Test 7: Custom MARTe Message (MSG) ---\n"); printf("\n--- Test 7: Custom MARTe Message (MSG) ---\n");
TestMessageCommand(); TestMessageCommand();
Sleep::MSec(1000); 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"); printf("\nAll Integration Tests Finished.\n");
return 0; return 0;
+1 -1
View File
@@ -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 PACKAGE = Test/Integration