fix(daemon)+test: accurate duplicate-ARRIVED message; broaden E90/E157 + names coverage
The duplicate-ARRIVED fix from the previous commit returned INVALID_OBJECT with the message "no substrate 'X'" — a lie, since the substrate exists. Rewrite ReportSubstrate so ARRIVED has its own ack mapping: a duplicate is CANNOT_DO_NOW with "substrate 'X' already exists" (a state conflict, not a missing object), and we never silently re-create over live FSM state. Coverage gaps closed: - C++: ARRIVED records carrier_id/slot (now asserted); module NOT_EXECUTING reset transition; duplicate-ARRIVED expects CANNOT_DO_NOW. - Interop: @eq.command now drives the real host S2F41 path (was @eq.on, so the headline decorator had zero wire coverage); @eq.command NameError on unknown name; eq.names var/alarm + dir() + typo-suggestion; replaced the two `check(..., True)` tautologies with full E90 journey + AT_DESTINATION and real error paths (ghost wafer raises, illegal module jump raises). All 8 daemon test cases (248 assertions) and 24 pyclient interop checks pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -759,8 +759,30 @@ TEST_CASE("ReportSubstrate (E90) and ReportModule (E157) drive the FSMs") {
|
||||
return ack.code();
|
||||
};
|
||||
|
||||
auto sub_with = [&](const std::string& sid, pb::SubstrateReport::Milestone m,
|
||||
const std::string& cid, uint32_t slot) {
|
||||
grpc::ClientContext ctx;
|
||||
pb::SubstrateReport req;
|
||||
pb::Ack ack;
|
||||
req.set_substrate_id(sid);
|
||||
req.set_milestone(m);
|
||||
req.set_carrier_id(cid);
|
||||
req.set_slot(slot);
|
||||
REQUIRE(stub->ReportSubstrate(&ctx, req, &ack).ok());
|
||||
return ack.code();
|
||||
};
|
||||
|
||||
// A wafer's journey: arrive -> picked up -> process -> done -> deposited.
|
||||
CHECK(sub("WFR-1", pb::SubstrateReport::ARRIVED) == pb::Ack::ACCEPT);
|
||||
// ARRIVED also records where the wafer came from (carrier + slot).
|
||||
CHECK(sub_with("WFR-1", pb::SubstrateReport::ARRIVED, "FOUP-7", 3) == pb::Ack::ACCEPT);
|
||||
auto origin = rt.read_sync([&rt]() {
|
||||
const auto* s = rt.model().substrates.get("WFR-1");
|
||||
return s ? std::make_pair(s->carrierid, s->slot)
|
||||
: std::make_pair(std::string{}, uint8_t{0});
|
||||
});
|
||||
REQUIRE(origin.has_value());
|
||||
CHECK(origin->first == "FOUP-7");
|
||||
CHECK(static_cast<int>(origin->second) == 3);
|
||||
CHECK(sub("WFR-1", pb::SubstrateReport::AT_WORK) == pb::Ack::ACCEPT);
|
||||
CHECK(sub("WFR-1", pb::SubstrateReport::PROCESSING) == pb::Ack::ACCEPT);
|
||||
CHECK(sub("WFR-1", pb::SubstrateReport::PROCESSED) == pb::Ack::ACCEPT);
|
||||
@@ -772,10 +794,11 @@ TEST_CASE("ReportSubstrate (E90) and ReportModule (E157) drive the FSMs") {
|
||||
REQUIRE(loc.has_value());
|
||||
CHECK(*loc == gem::SubstrateState::AtDestination);
|
||||
|
||||
// Reporting on a substrate that never ARRIVED is rejected.
|
||||
// Reporting on a substrate that never ARRIVED is rejected (no such object).
|
||||
CHECK(sub("WFR-GHOST", pb::SubstrateReport::AT_WORK) == pb::Ack::INVALID_OBJECT);
|
||||
// Duplicate ARRIVED (same substrate ID already in the store) is rejected.
|
||||
CHECK(sub("WFR-1", pb::SubstrateReport::ARRIVED) == pb::Ack::INVALID_OBJECT);
|
||||
// Duplicate ARRIVED is a conflict, not a missing object: CANNOT_DO_NOW so we
|
||||
// never silently wipe the existing wafer's state/history.
|
||||
CHECK(sub("WFR-1", pb::SubstrateReport::ARRIVED) == pb::Ack::CANNOT_DO_NOW);
|
||||
|
||||
auto mod = [&](const std::string& mid, pb::ModuleReport::State st) {
|
||||
grpc::ClientContext ctx;
|
||||
@@ -787,7 +810,7 @@ TEST_CASE("ReportSubstrate (E90) and ReportModule (E157) drive the FSMs") {
|
||||
return ack.code();
|
||||
};
|
||||
|
||||
// Module: auto-created, then walked General -> Step -> StepCompleted.
|
||||
// Module: auto-created, then walked General -> Step -> StepCompleted -> Reset.
|
||||
CHECK(mod("MOD-1", pb::ModuleReport::GENERAL_EXECUTING) == pb::Ack::ACCEPT);
|
||||
CHECK(mod("MOD-1", pb::ModuleReport::STEP_EXECUTING) == pb::Ack::ACCEPT);
|
||||
CHECK(mod("MOD-1", pb::ModuleReport::STEP_COMPLETED) == pb::Ack::ACCEPT);
|
||||
@@ -798,6 +821,15 @@ TEST_CASE("ReportSubstrate (E90) and ReportModule (E157) drive the FSMs") {
|
||||
REQUIRE(mstate.has_value());
|
||||
CHECK(*mstate == gem::ModuleState::StepCompleted);
|
||||
|
||||
// NOT_EXECUTING resets the module back to idle (re-usable for the next wafer).
|
||||
CHECK(mod("MOD-1", pb::ModuleReport::NOT_EXECUTING) == pb::Ack::ACCEPT);
|
||||
auto reset = rt.read_sync([&rt]() {
|
||||
const auto* m = rt.model().modules.get("MOD-1");
|
||||
return m ? m->fsm->state() : gem::ModuleState::NoState;
|
||||
});
|
||||
REQUIRE(reset.has_value());
|
||||
CHECK(*reset == gem::ModuleState::NotExecuting);
|
||||
|
||||
// An illegal jump (StepExecuting straight from a fresh NotExecuting module)
|
||||
// is rejected by the E157 table.
|
||||
CHECK(mod("MOD-2", pb::ModuleReport::STEP_EXECUTING) == pb::Ack::CANNOT_DO_NOW);
|
||||
|
||||
Reference in New Issue
Block a user