cb85199f49
- name_index: add resolve_event(name) -> CEID (unit-tested). - equipment_service.hpp: extract the gRPC service + value/state conversion into a shared header; add FireEvent (optional per-fire variable values, then trigger the collection event by name). secs_gemd slims to main(). - test_daemon_service: real in-process gRPC integration test (client stub -> service -> EquipmentRuntime) proving SetVariables lands in the model, GetControlState reports the state, FireEvent and unknown-name paths behave. Separate secs_gemd_tests target (links grpc++/proto), gated on the daemon. Core suite 459/459 (2799 assertions); daemon gRPC tests 15/15. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
92 lines
2.9 KiB
C++
92 lines
2.9 KiB
C++
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
|
#include <doctest/doctest.h>
|
|
|
|
#include <grpcpp/grpcpp.h>
|
|
|
|
#include "equipment_service.hpp"
|
|
|
|
using namespace secsgem;
|
|
namespace gem = secsgem::gem;
|
|
namespace s2 = secsgem::secs2;
|
|
namespace pb = secsgem::v1;
|
|
namespace dmn = secsgem::daemon;
|
|
|
|
#ifndef SECSGEM_DATA_DIR
|
|
#error "SECSGEM_DATA_DIR not defined; see CMakeLists.txt"
|
|
#endif
|
|
|
|
static gem::EquipmentRuntime::Config test_config() {
|
|
gem::EquipmentRuntime::Config c;
|
|
c.equipment_yaml = SECSGEM_DATA_DIR "/equipment.yaml";
|
|
c.control_state_yaml = SECSGEM_DATA_DIR "/control_state.yaml";
|
|
c.process_job_yaml = SECSGEM_DATA_DIR "/process_job_state.yaml";
|
|
c.control_job_yaml = SECSGEM_DATA_DIR "/control_job_state.yaml";
|
|
c.port = 0; // ephemeral; the engine isn't run() here, only poll()ed
|
|
return c;
|
|
}
|
|
|
|
// Exercises the real gRPC service over an in-process channel: client stub ->
|
|
// service -> EquipmentRuntime, proving the RPCs move data, not just compile.
|
|
TEST_CASE("Equipment gRPC service over an in-process channel") {
|
|
gem::EquipmentRuntime rt(test_config());
|
|
dmn::EquipmentService svc(rt);
|
|
|
|
grpc::ServerBuilder builder;
|
|
builder.RegisterService(&svc);
|
|
std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
|
|
REQUIRE(server);
|
|
auto stub = pb::Equipment::NewStub(server->InProcessChannel(grpc::ChannelArguments{}));
|
|
|
|
SUBCASE("GetControlState returns the initial control state") {
|
|
grpc::ClientContext ctx;
|
|
pb::Empty req;
|
|
pb::ControlState resp;
|
|
auto st = stub->GetControlState(&ctx, req, &resp);
|
|
CHECK(st.ok());
|
|
CHECK(resp.state() == pb::ControlState::HOST_OFFLINE);
|
|
}
|
|
|
|
SUBCASE("SetVariables accepts a known name and updates the model") {
|
|
grpc::ClientContext ctx;
|
|
pb::VariableUpdate req;
|
|
pb::Ack resp;
|
|
(*req.mutable_values())["ChamberPressure"].set_real(2.5);
|
|
auto st = stub->SetVariables(&ctx, req, &resp);
|
|
CHECK(st.ok());
|
|
CHECK(resp.code() == pb::Ack::ACCEPT);
|
|
rt.poll(); // drain the posted set_variable onto this thread
|
|
CHECK(rt.model().dvids.value(101) == s2::Item::f8(2.5));
|
|
}
|
|
|
|
SUBCASE("SetVariables rejects an unknown variable name") {
|
|
grpc::ClientContext ctx;
|
|
pb::VariableUpdate req;
|
|
pb::Ack resp;
|
|
(*req.mutable_values())["definitely_not_a_var"].set_real(1.0);
|
|
auto st = stub->SetVariables(&ctx, req, &resp);
|
|
CHECK(st.ok());
|
|
CHECK(resp.code() == pb::Ack::PARAMETER_INVALID);
|
|
}
|
|
|
|
SUBCASE("FireEvent accepts a known event and rejects an unknown one") {
|
|
{
|
|
grpc::ClientContext ctx;
|
|
pb::Event req;
|
|
pb::Ack resp;
|
|
req.set_name("ProcessStarted");
|
|
CHECK(stub->FireEvent(&ctx, req, &resp).ok());
|
|
CHECK(resp.code() == pb::Ack::ACCEPT);
|
|
}
|
|
{
|
|
grpc::ClientContext ctx;
|
|
pb::Event req;
|
|
pb::Ack resp;
|
|
req.set_name("NoSuchEvent");
|
|
CHECK(stub->FireEvent(&ctx, req, &resp).ok());
|
|
CHECK(resp.code() == pb::Ack::PARAMETER_INVALID);
|
|
}
|
|
}
|
|
|
|
server->Shutdown();
|
|
}
|