ci(interop): one-command external-validation suite + CI lanes for the daemon
tests / build-and-test (push) Successful in 2m42s
tests / thread-sanitizer (push) Successful in 2m50s
tests / tshark-dissector (push) Successful in 2m24s
tests / secs4j-interop (push) Successful in 37s
tests / python-interop (push) Successful in 2m56s
tests / libfuzzer (push) Successful in 3m44s
tests / build-and-test (push) Successful in 2m42s
tests / thread-sanitizer (push) Successful in 2m50s
tests / tshark-dissector (push) Successful in 2m24s
tests / secs4j-interop (push) Successful in 37s
tests / python-interop (push) Successful in 2m56s
tests / libfuzzer (push) Successful in 3m44s
tools/run_interop.sh runs ALL nine validation steps with a PASS/FAIL summary: build, unit (464), daemon-unit (41), secsgem-py host vs server (31 checks), secs_conformance (47), gRPC+secsgem-py daemon bridge, spool persistence across restart, tshark HSMS dissector, secs4java8 (55 checks). Verified green end-to-end. The unit suite is partly self-referential (our parsers validate our builders); these external validators are the real oracle — now they run with one command instead of by hand. Two bugs found by running it: unbounded ninja at -O3 OOM-kills cc1plus in memory-constrained Docker VMs (build with -j 2) and bash-3.2 lacks negative array subscripts. CI: grpc deps added to the build job so secs_gemd + secs_gemd_tests build and RUN in CI (previously the daemon silently dropped out — now fails loudly if missing), plus a python-interop lane running py-host/conformance/daemon harnesses against localhost in one container (no docker-in-docker). Service hardening while in there: reject proto Values with no kind set at the RPC edge (previously silently became ASCII ""), TODO markers for list element formats and daemon graceful shutdown. New tests: unset-Value guard + a property test iterating ALL configured variables via gRPC asserting each keeps its declared SECS-II format (daemon tests 16 -> 41 assertions). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -73,6 +73,58 @@ TEST_CASE("Equipment gRPC service over an in-process channel") {
|
||||
CHECK(resp.code() == pb::Ack::PARAMETER_INVALID);
|
||||
}
|
||||
|
||||
SUBCASE("SetVariables rejects a Value with no kind set (silent-'' guard)") {
|
||||
grpc::ClientContext ctx;
|
||||
pb::VariableUpdate req;
|
||||
pb::Ack resp;
|
||||
(*req.mutable_values())["ChamberPressure"]; // map entry, kind never set
|
||||
auto st = stub->SetVariables(&ctx, req, &resp);
|
||||
CHECK(st.ok());
|
||||
CHECK(resp.code() == pb::Ack::PARAMETER_INVALID);
|
||||
// The previous value must be untouched (no silent ASCII "" write).
|
||||
rt.poll();
|
||||
CHECK(rt.model().dvids.value(101)->format() == s2::Format::F4);
|
||||
}
|
||||
|
||||
SUBCASE("property: every configured variable keeps its declared format") {
|
||||
// Iterate ALL SVIDs+DVIDs from the live config: set each via gRPC with a
|
||||
// type-appropriate plain value and assert the stored Item keeps the
|
||||
// declared wire format. Catches any future variable whose format the
|
||||
// conversion table doesn't handle — not just the two pinned above.
|
||||
auto check_all = [&](const auto& entries) {
|
||||
for (const auto& v : entries) {
|
||||
const auto declared = v.value.format();
|
||||
grpc::ClientContext ctx;
|
||||
pb::VariableUpdate req;
|
||||
pb::Ack resp;
|
||||
pb::Value val;
|
||||
switch (declared) {
|
||||
case s2::Format::ASCII: case s2::Format::JIS8:
|
||||
val.set_text("x"); break;
|
||||
case s2::Format::Boolean:
|
||||
val.set_boolean(true); break;
|
||||
case s2::Format::Binary:
|
||||
val.set_binary("\x01"); break;
|
||||
case s2::Format::F4: case s2::Format::F8:
|
||||
val.set_real(1.5); break;
|
||||
default: // all integer widths
|
||||
val.set_integer(1); break;
|
||||
}
|
||||
(*req.mutable_values())[v.name] = val;
|
||||
REQUIRE(stub->SetVariables(&ctx, req, &resp).ok());
|
||||
REQUIRE_MESSAGE(resp.code() == pb::Ack::ACCEPT, v.name);
|
||||
rt.poll();
|
||||
auto stored = rt.model().vid_value(v.id);
|
||||
REQUIRE_MESSAGE(stored.has_value(), v.name);
|
||||
CHECK_MESSAGE(stored->format() == declared,
|
||||
v.name, ": declared ", static_cast<int>(declared),
|
||||
" stored ", static_cast<int>(stored->format()));
|
||||
}
|
||||
};
|
||||
check_all(rt.model().svids.all());
|
||||
check_all(rt.model().dvids.all());
|
||||
}
|
||||
|
||||
SUBCASE("FireEvent accepts a known event and rejects an unknown one") {
|
||||
{
|
||||
grpc::ClientContext ctx;
|
||||
|
||||
Reference in New Issue
Block a user