minor changes and addeed debug tests
This commit is contained in:
@@ -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 <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).
|
||||
|
||||
|
||||
@@ -1147,15 +1147,26 @@ 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);
|
||||
out.Printf("Nodes under %s:\n", path ? 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()) {
|
||||
ReferenceContainer *rc =
|
||||
dynamic_cast<ReferenceContainer *>(ref.operator->());
|
||||
rc = dynamic_cast<ReferenceContainer *>(ref.operator->());
|
||||
}
|
||||
}
|
||||
out.Printf("Nodes under %s:\n", path ? path : "/");
|
||||
if (rc != NULL_PTR(ReferenceContainer *)) {
|
||||
uint32 n = rc->Size();
|
||||
for (uint32 i = 0u; i < n; i++) {
|
||||
@@ -1165,7 +1176,6 @@ void DebugServiceBase::ListNodes(const char8 *path, StreamString &out) {
|
||||
c->GetClassProperties()->GetName());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out += " (not found)\n";
|
||||
}
|
||||
@@ -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<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
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -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);
|
||||
|
||||
Binary file not shown.
@@ -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
|
||||
|
||||
Binary file not shown.
@@ -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();
|
||||
}
|
||||
@@ -31,6 +31,8 @@ void RunValidationTest();
|
||||
void TestConfigCommands();
|
||||
void TestGAMSignalTracing();
|
||||
void TestTreeCommand();
|
||||
void TestDebugCommands();
|
||||
void TestDebugConfigAutoRebuild();
|
||||
|
||||
int main() {
|
||||
signal(SIGALRM, timeout_handler);
|
||||
@@ -91,6 +93,14 @@ int main() {
|
||||
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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user