conformance: expand harness from 8 to 47 host-driven checks
The previous harness only exercised S1F1/F11/F13/F19, S2F17/F29, S5F5, S7F19 — about 15% of what COMPLIANCE.md claims as ✅. Customers running secs_conformance against their tool got near-zero conformance signal on dynamic event reports, GEM 300, alarm management, exception recovery, terminal services, spool, and PP management. This expansion covers, in one sequential run: - Establish comms + identification (S1F13/F1) - Status / DVID / CEID / EC namelists + values (S1F11/F3/F21/F23, S2F29/F13) - Dynamic event reports: define / link / enable + readback paths (S2F33/F35/F37, S6F15/F19/F21) - All three remote-command forms (S2F41/F21/F49) - Equipment-initiated S6F11 observation triggered by RCMD=START - Trace init, limits attrs, spool reset + transmit (S2F23, S2F47, S2F43, S6F23) - Alarm management: list, list-enabled, enable (S5F5/F7/F3) - Exception recovery: request + abort (S5F13/F17) - PP load-inquire / list / request (S7F1/F19/F5) - Terminal display both directions (S10F3, S10F5) - E40 PJ create / monitor / command / dequeue (S16F11/F7/F5/F13) - E94 CJ create / command / delete (S14F9, S16F27, S14F11) - E87 carrier action / slot map / transfer / cancel (S3F17/F19/F25/F27) - E39 GetAttr (S14F1) - GEM compliance self-report (S1F19) Pass criterion is the spec-mandated reply function code, not any specific ACK value — CarrierIDUnknown / Denied_UnknownObject / PpidNotFound / Error are well-formed F-coded replies and count as protocol-conformant. This lets the harness run against any equipment without preloading state. 47 / 47 PASS against the in-repo demo server. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -351,6 +351,20 @@ edge cases — not just the happy path.
|
|||||||
The E30 §6.5 Communication state machine is unit-tested independently of
|
The E30 §6.5 Communication state machine is unit-tested independently of
|
||||||
the transport (timer firings simulated via test callbacks).
|
the transport (timer firings simulated via test callbacks).
|
||||||
|
|
||||||
|
Live conformance harness: **`build/secs_conformance --host <ip> --port <p>`**
|
||||||
|
walks 47 host-driven checks against a passive equipment, covering every
|
||||||
|
E30 fundamental + additional capability that COMPLIANCE.md ✅ — establish
|
||||||
|
comms, identification, status/DVID/CEID/EC namelists + values, dynamic
|
||||||
|
event reports (define/link/enable + S6F15/F19/F21 readbacks), unsolicited
|
||||||
|
S6F11 observation after RCMD, all three remote-command forms
|
||||||
|
(S2F41/F21/F49), trace init, limits, spool reset + transmit, alarms
|
||||||
|
(list/list-enabled/enable), exception recover/abort (S5F13/F17), PP
|
||||||
|
load-inquire/list/request, both terminal-display directions
|
||||||
|
(S10F3/F5), E40 PJ create/monitor/command/dequeue, E94 CJ
|
||||||
|
create/command/delete, E87 carrier action/slot-map/transfer/cancel,
|
||||||
|
and E39 GetAttr. Exits 0 with PASS/FAIL summary; intended to be run
|
||||||
|
against vendor equipment as the first-line conformance probe.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Interoperability with secsgem-py 0.3.0
|
## 7. Interoperability with secsgem-py 0.3.0
|
||||||
|
|||||||
+275
-81
@@ -1,6 +1,6 @@
|
|||||||
// secs_conformance — runnable conformance harness.
|
// secs_conformance — runnable conformance harness.
|
||||||
//
|
//
|
||||||
// Drives a passive GEM-300 equipment through a fixed set of host-initiated
|
// Drives a passive GEM-300 equipment through a fixed sequence of host-initiated
|
||||||
// checks that exercise every claimed E5/E30/E37 + GEM 300 capability,
|
// checks that exercise every claimed E5/E30/E37 + GEM 300 capability,
|
||||||
// reports per-check pass/fail, and exits 0 if every check passed. Point
|
// reports per-check pass/fail, and exits 0 if every check passed. Point
|
||||||
// it at any HSMS-SS equipment to validate end-to-end conformance from
|
// it at any HSMS-SS equipment to validate end-to-end conformance from
|
||||||
@@ -9,7 +9,14 @@
|
|||||||
// Run: secs_conformance --host <addr> --port 5000 --device 0
|
// Run: secs_conformance --host <addr> --port 5000 --device 0
|
||||||
//
|
//
|
||||||
// The check list deliberately mirrors COMPLIANCE.md so that anything
|
// The check list deliberately mirrors COMPLIANCE.md so that anything
|
||||||
// the audit claims as ✅ has a runnable assertion behind it here.
|
// the audit claims as ✅ has a runnable assertion behind it here. The
|
||||||
|
// pass criterion is the spec-mandated reply *function code* — we don't
|
||||||
|
// require any particular ACK value, since the harness has no equipment
|
||||||
|
// state to predict. CarrierIDUnknown / Denied_UnknownObject / etc. are
|
||||||
|
// well-formed F-coded replies and count as protocol-conformant.
|
||||||
|
//
|
||||||
|
// One observation step waits for an unsolicited S6F11 after issuing an
|
||||||
|
// RCMD that the demo equipment binds to a CEID emission.
|
||||||
|
|
||||||
#include <asio.hpp>
|
#include <asio.hpp>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
@@ -20,12 +27,14 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <system_error>
|
#include <system_error>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "secsgem/endpoint.hpp"
|
#include "secsgem/endpoint.hpp"
|
||||||
#include "secsgem/gem/messages.hpp"
|
#include "secsgem/gem/messages.hpp"
|
||||||
#include "secsgem/gem/messages_helpers.hpp"
|
#include "secsgem/gem/messages_helpers.hpp"
|
||||||
#include "secsgem/hsms/connection.hpp"
|
#include "secsgem/hsms/connection.hpp"
|
||||||
|
#include "secsgem/secs2/codec.hpp"
|
||||||
#include "secsgem/secs2/message.hpp"
|
#include "secsgem/secs2/message.hpp"
|
||||||
|
|
||||||
using namespace secsgem;
|
using namespace secsgem;
|
||||||
@@ -47,43 +56,78 @@ struct Check {
|
|||||||
std::string detail;
|
std::string detail;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<Check> checks;
|
|
||||||
|
|
||||||
void log(const std::string& m) {
|
void log(const std::string& m) {
|
||||||
std::cout << "[conformance] " << m << std::endl;
|
std::cout << "[conformance] " << m << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shared harness state. Built on the io_context heap so async steps
|
||||||
|
// can capture by raw pointer without lifetime juggling.
|
||||||
|
struct Harness {
|
||||||
|
std::vector<Check> checks;
|
||||||
|
// Every unsolicited primary the equipment sends us, in arrival order.
|
||||||
|
std::vector<std::pair<uint8_t, uint8_t>> observed;
|
||||||
|
|
||||||
void record(const std::string& name, bool ok, const std::string& detail = "") {
|
void record(const std::string& name, bool ok, const std::string& detail = "") {
|
||||||
checks.push_back({name, ok, detail});
|
checks.push_back({name, ok, detail});
|
||||||
log((ok ? "PASS " : "FAIL ") + name + (detail.empty() ? "" : " — " + detail));
|
log((ok ? "PASS " : "FAIL ") + name + (detail.empty() ? "" : " — " + detail));
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// One check in the async chain: send `req` and dispatch `ok` /
|
// One sequential step: send `req`, expect a reply with (expected_stream,
|
||||||
// `fail` based on whether a reply matching (expected_stream,
|
// expected_function), record pass/fail, advance.
|
||||||
// expected_function) arrives within T3.
|
void request_check(asio::io_context& io,
|
||||||
void chain_check(std::shared_ptr<Connection> conn,
|
std::shared_ptr<Connection> conn,
|
||||||
|
Harness* h,
|
||||||
const std::string& name,
|
const std::string& name,
|
||||||
s2::Message req,
|
s2::Message req,
|
||||||
uint8_t expected_stream,
|
uint8_t expected_stream,
|
||||||
uint8_t expected_function,
|
uint8_t expected_function,
|
||||||
std::function<void()> next) {
|
std::function<void()> next) {
|
||||||
conn->send_request(std::move(req),
|
conn->send_request(std::move(req),
|
||||||
[name, expected_stream, expected_function, next, conn]
|
[h, name, expected_stream, expected_function, next, &io]
|
||||||
(std::error_code ec, const s2::Message& m) {
|
(std::error_code ec, const s2::Message& m) {
|
||||||
if (ec) {
|
if (ec) {
|
||||||
record(name, false, "no reply / " + ec.message());
|
h->record(name, false, "no reply / " + ec.message());
|
||||||
} else {
|
} else {
|
||||||
const bool ok = m.stream == expected_stream &&
|
const bool ok = m.stream == expected_stream &&
|
||||||
m.function == expected_function;
|
m.function == expected_function;
|
||||||
record(name, ok,
|
h->record(name, ok,
|
||||||
ok ? "" : "got S" +
|
ok ? "" : "got S" +
|
||||||
std::to_string(m.stream) + "F" +
|
std::to_string(m.stream) + "F" +
|
||||||
std::to_string(m.function));
|
std::to_string(m.function));
|
||||||
}
|
}
|
||||||
next();
|
asio::post(io, next);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait `dt` then run `next` on the io_context.
|
||||||
|
void pause_then(asio::io_context& io, std::chrono::milliseconds dt,
|
||||||
|
std::function<void()> next) {
|
||||||
|
auto t = std::make_shared<asio::steady_timer>(io);
|
||||||
|
t->expires_after(dt);
|
||||||
|
t->async_wait([t, next](std::error_code) { next(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sequential runner that walks a vector of steps to completion.
|
||||||
|
struct Sequence : std::enable_shared_from_this<Sequence> {
|
||||||
|
using Step = std::function<void(std::function<void()>)>;
|
||||||
|
std::vector<Step> steps;
|
||||||
|
std::size_t i = 0;
|
||||||
|
std::function<void()> finish;
|
||||||
|
|
||||||
|
void run() {
|
||||||
|
if (i >= steps.size()) {
|
||||||
|
if (finish) finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto self = shared_from_this();
|
||||||
|
steps[i]([self] {
|
||||||
|
++self->i;
|
||||||
|
self->run();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
@@ -92,105 +136,255 @@ int main(int argc, char** argv) {
|
|||||||
cfg.port = static_cast<uint16_t>(std::stoi(arg(argc, argv, "--port", "5000")));
|
cfg.port = static_cast<uint16_t>(std::stoi(arg(argc, argv, "--port", "5000")));
|
||||||
cfg.device_id = static_cast<uint16_t>(std::stoi(arg(argc, argv, "--device", "0")));
|
cfg.device_id = static_cast<uint16_t>(std::stoi(arg(argc, argv, "--device", "0")));
|
||||||
cfg.timers.linktest = 0ms;
|
cfg.timers.linktest = 0ms;
|
||||||
cfg.timers.t3 = 5s;
|
cfg.timers.t3 = 3s;
|
||||||
|
|
||||||
asio::io_context io;
|
asio::io_context io;
|
||||||
Client client(io, cfg);
|
Client client(io, cfg);
|
||||||
client.on_log([](const std::string& m) { log("hsms: " + m); });
|
client.on_log([](const std::string& m) { log("hsms: " + m); });
|
||||||
|
|
||||||
|
auto h = std::make_shared<Harness>();
|
||||||
|
|
||||||
asio::steady_timer deadline(io);
|
asio::steady_timer deadline(io);
|
||||||
deadline.expires_after(60s);
|
deadline.expires_after(5min);
|
||||||
deadline.async_wait([&](std::error_code ec) {
|
deadline.async_wait([&](std::error_code ec) {
|
||||||
if (!ec) {
|
if (!ec) {
|
||||||
log("global 60s deadline reached, halting");
|
log("global 5min deadline reached, halting");
|
||||||
io.stop();
|
io.stop();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on_connection([&](std::shared_ptr<Connection> conn) {
|
client.on_connection([&io, h](std::shared_ptr<Connection> conn) {
|
||||||
// Reply to equipment-initiated primaries so the equipment's T3 doesn't
|
// Capture every equipment-initiated primary into the mailbox and
|
||||||
// fire on our watch.
|
// reply to the ones with W=1 so the equipment's T3 doesn't fire on
|
||||||
|
// our watch. S16F9 (E40 PRJobAlert) is W=0 — no reply.
|
||||||
conn->set_message_handler(
|
conn->set_message_handler(
|
||||||
[](const s2::Message& msg) -> std::optional<s2::Message> {
|
[h](const s2::Message& msg) -> std::optional<s2::Message> {
|
||||||
|
h->observed.push_back({msg.stream, msg.function});
|
||||||
if (msg.stream == 1 && msg.function == 13) {
|
if (msg.stream == 1 && msg.function == 13) {
|
||||||
return gem::s1f14_establish_comms_ack(
|
return gem::s1f14_establish_comms_ack(
|
||||||
gem::CommAck::Accept, {"CONFORMANCE", "1.0"});
|
gem::CommAck::Accept, {"CONFORMANCE", "1.0"});
|
||||||
}
|
}
|
||||||
|
if (msg.stream == 5 && msg.function == 1) {
|
||||||
|
return gem::s5f2_alarm_ack(gem::AlarmAck::Accept);
|
||||||
|
}
|
||||||
|
if (msg.stream == 6 && msg.function == 11) {
|
||||||
|
return gem::s6f12_event_report_ack(gem::EventReportAck::Accept);
|
||||||
|
}
|
||||||
|
if (msg.stream == 6 && msg.function == 25) {
|
||||||
|
return gem::s6f26_spool_data_ready_ack(gem::EventReportAck::Accept);
|
||||||
|
}
|
||||||
|
if (msg.stream == 16 && msg.function == 9) {
|
||||||
|
return std::nullopt; // W=0 primary
|
||||||
|
}
|
||||||
|
if (msg.reply_expected) return s2::Message(msg.stream, 0, false);
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
});
|
});
|
||||||
|
|
||||||
conn->set_selected_handler([&, conn] {
|
conn->set_selected_handler([&io, h, conn] {
|
||||||
record("E37 §7.2 SELECT handshake", true,
|
h->record("E37 §7.2 SELECT handshake", true, "selected");
|
||||||
"selected against " + cfg.host + ":" + std::to_string(cfg.port));
|
|
||||||
|
|
||||||
// The chain runs forward through async callbacks: each check's
|
auto seq = std::make_shared<Sequence>();
|
||||||
// reply handler kicks off the next check. Final closure calls
|
|
||||||
// separate() and posts the summary.
|
// Helper closure that pushes a request/reply step.
|
||||||
auto finish = [&, conn] {
|
auto add_req = [&io, h, conn, seq](const std::string& name,
|
||||||
|
s2::Message req,
|
||||||
|
uint8_t es, uint8_t ef) {
|
||||||
|
seq->steps.push_back([&io, h, conn, name, req = std::move(req), es, ef]
|
||||||
|
(std::function<void()> next) mutable {
|
||||||
|
request_check(io, conn, h.get(), name, std::move(req), es, ef,
|
||||||
|
std::move(next));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// === E30 §6.5 / §6.7 — establish comms + identification ===
|
||||||
|
add_req("E30 §6.5 S1F13/F14 Establish Comms",
|
||||||
|
gem::s1f13_establish_comms("HOST", "1.0"), 1, 14);
|
||||||
|
add_req("E30 §6.7 S1F1/F2 Are You There",
|
||||||
|
s2::Message(1, 1, true), 1, 2);
|
||||||
|
|
||||||
|
// === E30 §6.13 — status data collection ===
|
||||||
|
add_req("E30 §6.13 S1F11/F12 SVID Namelist",
|
||||||
|
gem::s1f11_status_namelist_request({}), 1, 12);
|
||||||
|
add_req("E30 §6.13 S1F3/F4 SVID Values",
|
||||||
|
gem::s1f3_selected_status_request({}), 1, 4);
|
||||||
|
|
||||||
|
// === E30 §6.11 / §6.10 — DVID + CEID namelists ===
|
||||||
|
add_req("E30 §6.11 S1F21/F22 DVID Namelist",
|
||||||
|
gem::s1f21_data_variable_namelist_request({}), 1, 22);
|
||||||
|
add_req("E30 §6.10 S1F23/F24 CEID Namelist",
|
||||||
|
gem::s1f23_collection_event_namelist_request({}), 1, 24);
|
||||||
|
|
||||||
|
// === E30 §6.16 — equipment constants ===
|
||||||
|
add_req("E30 §6.16 S2F29/F30 ECID Namelist",
|
||||||
|
gem::s2f29_ec_namelist_request({}), 2, 30);
|
||||||
|
add_req("E30 §6.16 S2F13/F14 EC Values",
|
||||||
|
gem::s2f13_ec_request({}), 2, 14);
|
||||||
|
|
||||||
|
// === E30 §6.20 — clock ===
|
||||||
|
add_req("E30 §6.20 S2F17/F18 Clock",
|
||||||
|
s2::Message(2, 17, true), 2, 18);
|
||||||
|
|
||||||
|
// === E30 §6.6 — dynamic event reports ===
|
||||||
|
constexpr uint32_t kDataId = 9001;
|
||||||
|
constexpr uint32_t kRptid = 9000;
|
||||||
|
constexpr uint32_t kCeidStart = 300; // ProcessStarted, from equipment.yaml
|
||||||
|
add_req("E30 §6.6 S2F33/F34 Define Report",
|
||||||
|
gem::s2f33_define_report(kDataId, {{kRptid, {1}}}), 2, 34);
|
||||||
|
add_req("E30 §6.6 S2F35/F36 Link Event Report",
|
||||||
|
gem::s2f35_link_event_report(kDataId, {{kCeidStart, {kRptid}}}),
|
||||||
|
2, 36);
|
||||||
|
add_req("E30 §6.6 S2F37/F38 Enable Event",
|
||||||
|
gem::s2f37_enable_event(true, {kCeidStart}), 2, 38);
|
||||||
|
|
||||||
|
// === E30 §6.15 — remote control ===
|
||||||
|
add_req("E30 §6.15 S2F41/F42 Host Command",
|
||||||
|
gem::s2f41_host_command("START", {}), 2, 42);
|
||||||
|
// Observe the unsolicited S6F11 that START triggers. Pause 400ms
|
||||||
|
// to give the equipment's async post a chance to land, then walk
|
||||||
|
// the mailbox.
|
||||||
|
seq->steps.push_back([&io, h](std::function<void()> next) {
|
||||||
|
pause_then(io, 400ms, [h, next] {
|
||||||
|
const bool seen = std::any_of(
|
||||||
|
h->observed.begin(), h->observed.end(),
|
||||||
|
[](const auto& p) { return p.first == 6 && p.second == 11; });
|
||||||
|
h->record("E30 §6.6 S6F11 Unsolicited Event Report (after START)",
|
||||||
|
seen, seen ? "" : "no S6F11 observed within 400ms");
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
add_req("E30 §6.15 S2F21/F22 Legacy Remote Command",
|
||||||
|
gem::s2f21_remote_command("STOP"), 2, 22);
|
||||||
|
add_req("E30 §6.15 S2F49/F50 Enhanced Remote Command",
|
||||||
|
gem::s2f49_enhanced_host_command(0u, "EQUIPMENT", "NOP", {}),
|
||||||
|
2, 50);
|
||||||
|
|
||||||
|
// === E30 §6.12 — trace data collection ===
|
||||||
|
add_req("E30 §6.12 S2F23/F24 Trace Initialize",
|
||||||
|
gem::s2f23_trace_initialize_send(8001, "000001", 0, 1, {1}),
|
||||||
|
2, 24);
|
||||||
|
|
||||||
|
// === E30 §6.21 — limits monitoring (read-only) ===
|
||||||
|
add_req("E30 §6.21 S2F47/F48 Limit Attributes Request",
|
||||||
|
gem::s2f47_variable_limit_attribute_request({}), 2, 48);
|
||||||
|
|
||||||
|
// === E30 §6.22 — spooling ===
|
||||||
|
add_req("E30 §6.22 S2F43/F44 Reset Spooling",
|
||||||
|
gem::s2f43_reset_spooling({5, 6}), 2, 44);
|
||||||
|
add_req("E30 §6.22 S6F23/F24 Request Spool Data",
|
||||||
|
gem::s6f23_request_spool_data(gem::SpoolRequestCode::Transmit),
|
||||||
|
6, 24);
|
||||||
|
|
||||||
|
// === E30 §6.14 — alarm management ===
|
||||||
|
add_req("E30 §6.14 S5F5/F6 List Alarms",
|
||||||
|
gem::s5f5_list_alarms_request({}), 5, 6);
|
||||||
|
add_req("E30 §6.14 S5F7/F8 List Enabled Alarms",
|
||||||
|
s2::Message(5, 7, true), 5, 8);
|
||||||
|
add_req("E30 §6.14 S5F3/F4 Enable Alarm",
|
||||||
|
gem::s5f3_enable_alarm(gem::kAlarmEnableByte, 1), 5, 4);
|
||||||
|
|
||||||
|
// === E5 §13 — exception recovery (well-formed reply even if EXID
|
||||||
|
// unknown) ===
|
||||||
|
add_req("E5 §13 S5F13/F14 Exception Recover Request",
|
||||||
|
gem::s5f13_exception_recover_request(999, "NOP"), 5, 14);
|
||||||
|
add_req("E5 §13 S5F17/F18 Exception Recover Abort",
|
||||||
|
gem::s5f17_exception_recover_abort_request(999), 5, 18);
|
||||||
|
|
||||||
|
// === E30 §6.6 — host-initiated event/report read-back ===
|
||||||
|
add_req("E30 §6.6 S6F15/F16 Event Report Request",
|
||||||
|
gem::s6f15_event_report_request(kCeidStart), 6, 16);
|
||||||
|
add_req("E30 §6.6 S6F19/F20 Individual Report Request",
|
||||||
|
gem::s6f19_individual_report_request(kRptid), 6, 20);
|
||||||
|
add_req("E30 §6.6 S6F21/F22 Annotated Report Request",
|
||||||
|
gem::s6f21_annotated_report_request(kRptid), 6, 22);
|
||||||
|
|
||||||
|
// === E30 §6.17 — process program management ===
|
||||||
|
add_req("E30 §6.17 S7F19/F20 PP List",
|
||||||
|
s2::Message(7, 19, true), 7, 20);
|
||||||
|
add_req("E30 §6.17 S7F5/F6 PP Request",
|
||||||
|
gem::s7f5_process_program_request("RECIPE-A"), 7, 6);
|
||||||
|
add_req("E30 §6.17 S7F1/F2 PP Load Inquire",
|
||||||
|
gem::s7f1_pp_load_inquire("RECIPE-X", 64u), 7, 2);
|
||||||
|
|
||||||
|
// === E30 §6.19 — equipment terminal services ===
|
||||||
|
add_req("E30 §6.19 S10F3/F4 Terminal Display Single",
|
||||||
|
gem::s10f3_terminal_display_single(0, "conformance probe"),
|
||||||
|
10, 4);
|
||||||
|
add_req("E30 §6.19 S10F5/F6 Terminal Display Multi",
|
||||||
|
gem::s10f5_terminal_display_multi(0, {"line 1", "line 2"}),
|
||||||
|
10, 6);
|
||||||
|
|
||||||
|
// === E40 — process jobs ===
|
||||||
|
const std::string kConfPj = "PJ-CONF-1";
|
||||||
|
gem::PRJobCreateRequest pj_req{
|
||||||
|
kConfPj, gem::MaterialFlag::Substrate,
|
||||||
|
gem::ProcessRecipeMethod::RecipeOnly,
|
||||||
|
gem::RecipeSpec{"RECIPE-A", {}},
|
||||||
|
{"WFR-C-1"}, {}};
|
||||||
|
add_req("E40 S16F11/F12 PRJobCreate",
|
||||||
|
gem::s16f11_pr_job_create(pj_req), 16, 12);
|
||||||
|
add_req("E40 S16F7/F8 PRJobMonitor",
|
||||||
|
gem::s16f7_pr_job_monitor({{kConfPj, gem::kAlarmEnableByte}}),
|
||||||
|
16, 8);
|
||||||
|
|
||||||
|
// === E94 — control jobs ===
|
||||||
|
const std::string kConfCj = "CJ-CONF-1";
|
||||||
|
add_req("E94 S14F9/F10 CreateControlJob",
|
||||||
|
gem::s14f9_create_control_job(kConfCj, {kConfPj}), 14, 10);
|
||||||
|
add_req("E94 S16F27/F28 CJobCommand (CJSTOP)",
|
||||||
|
gem::s16f27_cj_command(kConfCj, "CJSTOP"), 16, 28);
|
||||||
|
add_req("E94 S14F11/F12 DeleteControlJob",
|
||||||
|
gem::s14f11_delete_control_job(kConfCj), 14, 12);
|
||||||
|
add_req("E40 S16F5/F6 PRJobCommand (PJABORT)",
|
||||||
|
gem::s16f5_pr_job_command(kConfPj, "PJABORT"), 16, 6);
|
||||||
|
add_req("E40 S16F13/F14 PRJobDequeue",
|
||||||
|
gem::s16f13_pr_job_dequeue(kConfPj), 16, 14);
|
||||||
|
|
||||||
|
// === E87 — carrier management (well-formed reply even when the
|
||||||
|
// carrier id is unknown to the equipment) ===
|
||||||
|
add_req("E87 S3F17/F18 CarrierAction",
|
||||||
|
gem::s3f17_carrier_action(0u, "ProceedWithCarrier",
|
||||||
|
"CAR-CONF-1", {}),
|
||||||
|
3, 18);
|
||||||
|
add_req("E87 S3F19/F20 Slot Map Verify",
|
||||||
|
gem::s3f19_slot_map_verify("CAR-CONF-1", {}), 3, 20);
|
||||||
|
add_req("E87 S3F25/F26 Carrier Transfer",
|
||||||
|
gem::s3f25_carrier_transfer("CAR-CONF-1", 1, 2), 3, 26);
|
||||||
|
add_req("E87 S3F27/F28 Cancel Carrier",
|
||||||
|
gem::s3f27_cancel_carrier("CAR-CONF-1"), 3, 28);
|
||||||
|
|
||||||
|
// === E39 — generic object service (Denied_UnknownObject is a
|
||||||
|
// well-formed F-coded reply) ===
|
||||||
|
add_req("E39 S14F1/F2 GetAttr",
|
||||||
|
gem::s14f1_get_attr("CONF-OBJ", "EQUIPMENT", {}),
|
||||||
|
14, 2);
|
||||||
|
|
||||||
|
// === E30 §6.10 — documentation (last so failures earlier are
|
||||||
|
// visible against the equipment's self-report) ===
|
||||||
|
add_req("E30 §6.10 S1F19/F20 GEM Compliance",
|
||||||
|
gem::s1f19_get_gem_compliance_request(), 1, 20);
|
||||||
|
|
||||||
|
// Cleanup + summary.
|
||||||
|
seq->finish = [&io, h, conn] {
|
||||||
log("all checks done; separating");
|
log("all checks done; separating");
|
||||||
conn->separate();
|
conn->separate();
|
||||||
asio::post(io, [&] {
|
asio::post(io, [&io, h] {
|
||||||
std::cout << "\n========================================" << std::endl;
|
std::cout << "\n========================================\n";
|
||||||
std::cout << " conformance summary (" << cfg.host << ":"
|
std::cout << " conformance summary\n";
|
||||||
<< cfg.port << ")" << std::endl;
|
std::cout << "========================================\n";
|
||||||
std::cout << "========================================" << std::endl;
|
|
||||||
int passed = 0;
|
int passed = 0;
|
||||||
for (const auto& c : checks) {
|
for (const auto& c : h->checks) {
|
||||||
std::cout << (c.passed ? " [PASS] " : " [FAIL] ") << c.name;
|
std::cout << (c.passed ? " [PASS] " : " [FAIL] ") << c.name;
|
||||||
if (!c.detail.empty()) std::cout << " — " << c.detail;
|
if (!c.detail.empty()) std::cout << " — " << c.detail;
|
||||||
std::cout << std::endl;
|
std::cout << "\n";
|
||||||
if (c.passed) ++passed;
|
if (c.passed) ++passed;
|
||||||
}
|
}
|
||||||
std::cout << "\n " << passed << " / " << checks.size()
|
std::cout << "\n " << passed << " / " << h->checks.size()
|
||||||
<< " checks passed" << std::endl;
|
<< " checks passed\n";
|
||||||
io.stop();
|
io.stop();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// E30 §6.10 documentation (last in chain)
|
seq->run();
|
||||||
auto step8 = [conn, finish] {
|
|
||||||
chain_check(conn, "E30 §6.10 S1F19/F20 GEM Compliance",
|
|
||||||
s2::Message(1, 19, true), 1, 20, finish);
|
|
||||||
};
|
|
||||||
// E30 §6.17 PP list
|
|
||||||
auto step7 = [conn, step8] {
|
|
||||||
chain_check(conn, "E30 §6.17 S7F19/F20 PP List",
|
|
||||||
s2::Message(7, 19, true), 7, 20, step8);
|
|
||||||
};
|
|
||||||
// E30 §6.14 alarms
|
|
||||||
auto step6 = [conn, step7] {
|
|
||||||
chain_check(conn, "E30 §6.14 S5F5/F6 List Alarms",
|
|
||||||
s2::Message(5, 5, true, s2::Item::list({})),
|
|
||||||
5, 6, step7);
|
|
||||||
};
|
|
||||||
// E30 §6.20 clock
|
|
||||||
auto step5 = [conn, step6] {
|
|
||||||
chain_check(conn, "E30 §6.20 S2F17/F18 Clock",
|
|
||||||
s2::Message(2, 17, true), 2, 18, step6);
|
|
||||||
};
|
|
||||||
// E30 §6.16 ECID namelist
|
|
||||||
auto step4 = [conn, step5] {
|
|
||||||
chain_check(conn, "E30 §6.16 S2F29/F30 ECID Namelist",
|
|
||||||
s2::Message(2, 29, true, s2::Item::list({})),
|
|
||||||
2, 30, step5);
|
|
||||||
};
|
|
||||||
// E30 §6.13 SVID namelist
|
|
||||||
auto step3 = [conn, step4] {
|
|
||||||
chain_check(conn, "E30 §6.13 S1F11/F12 SVID Namelist",
|
|
||||||
s2::Message(1, 11, true, s2::Item::list({})),
|
|
||||||
1, 12, step4);
|
|
||||||
};
|
|
||||||
// E30 §6.7 S1F1/F2
|
|
||||||
auto step2 = [conn, step3] {
|
|
||||||
chain_check(conn, "E30 §6.7 S1F1/F2 Are You There",
|
|
||||||
s2::Message(1, 1, true), 1, 2, step3);
|
|
||||||
};
|
|
||||||
// E30 §6.5 establish comms
|
|
||||||
auto step1 = [conn, step2] {
|
|
||||||
chain_check(conn, "E30 §6.5 S1F13/F14 Establish Comms",
|
|
||||||
gem::s1f13_establish_comms("HOST", "1.0"), 1, 14, step2);
|
|
||||||
};
|
|
||||||
step1();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -200,6 +394,6 @@ int main(int argc, char** argv) {
|
|||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& c : checks) if (!c.passed) return 1;
|
for (const auto& c : h->checks) if (!c.passed) return 1;
|
||||||
return checks.empty() ? 3 : 0;
|
return h->checks.empty() ? 3 : 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user