#include "services/runtime_mode_service.h" #include "services/offline_simulation_service.h" #include "services/project_service.h" #include "domain/active_register_repository.h" #include "domain/project_storage.h" #include "domain/register_repository.h" #include #include #include #include #include namespace { class TestProjectStorage final : public ProjectStorage { public: ProjectSaveResult save(const Project &, const std::string &) override { return {true, ProjectStorageError::None, {}}; } ProjectLoadResult load(const std::string &) override { return {false, {}, ProjectStorageError::FileReadFailed, {}}; } }; class ReadyPlcGateway final : public PlcCommunicationGateway { public: PlcCommunicationResult connectDevice(const PlcSerialConfiguration &) override { connection_state = PlcConnectionState::Connected; if (state_changed) { state_changed(); } return {true, {}}; } void disconnectDevice() override { connection_state = PlcConnectionState::Disconnected; initial_read = false; } void setPollAddresses( const std::vector &addresses) override { poll_addresses = addresses; } PlcConnectionState state() const override { return connection_state; } bool initialReadCompleted() const override { return initial_read; } PlcCommunicationError lastErrorType() const override { return PlcCommunicationError::None; } const std::string &lastError() const override { return last_error; } void setCallbacks( std::function state_callback, std::function initial_callback, std::function cache_callback, std::function error_callback) override { state_changed = std::move(state_callback); initial_read_changed = std::move(initial_callback); cache_updated = std::move(cache_callback); error_reported = std::move(error_callback); } void completeInitialRead() { initial_read = true; if (initial_read_changed) { initial_read_changed(true); } } const std::vector &pollAddresses() const { return poll_addresses; } private: PlcConnectionState connection_state = PlcConnectionState::Disconnected; bool initial_read = false; std::string last_error; std::function state_changed; std::function initial_read_changed; std::function cache_updated; std::function error_reported; std::vector poll_addresses; }; void require(bool condition, const std::string &message) { if (!condition) { throw std::runtime_error(message); } } void testModeTransitions() { // 验证服务将 PLC 首读状态与领域模式切换规则正确组合 TestProjectStorage storage; ProjectService project_service(storage); VirtualRegisterRepository virtual_repository; VirtualRegisterRepository plc_repository; ActiveRegisterRepository active_repository(virtual_repository); OfflineSimulationService simulation_service(virtual_repository); ReadyPlcGateway gateway; RuntimeModeService service(project_service, simulation_service); service.configurePlc( gateway, active_repository, virtual_repository, plc_repository); Project &project = project_service.editProject(); project.alarmDefinitions.push_back( {"alarm-m", {RegisterArea::M, 12}, AlarmCondition::MOn, 0, "M alarm"}); project.alarmDefinitions.push_back( {"alarm-d", {RegisterArea::D, 34}, AlarmCondition::DHigh, 100, "D alarm"}); require(service.mode() == ApplicationMode::Editing, "service must start in editing mode"); require(service.policy().allowsProjectEditing, "editing mode must allow project editing"); require(service.enterOfflineRunning().succeeded, "editing mode must enter offline running"); require(service.simulationState() == SimulationState::Running, "offline mode must start the software executor"); require(service.policy().usesVirtualRegisters, "offline running must use virtual registers"); require(service.enterOnlineRunning().error == ModeTransitionError::MustReturnToEditing, "running modes must not switch directly"); require(service.enterEditing().succeeded, "offline running must return to editing"); require(service.simulationState() == SimulationState::Stopped, "returning to editing must stop the software executor first"); require(service.enterOnlineRunning().error == ModeTransitionError::InitialPlcReadRequired, "online running must require an initial PLC read"); require(service.connectPlc( {"COM9", 1, 9600, 8, 2, 1, 1000, 2, 200}).succeeded, "PLC connection must be established before its initial read can complete"); require(gateway.pollAddresses() == std::vector({ {RegisterArea::M, 12}, {RegisterArea::D, 34}}), "M and D alarm addresses must be included in PLC polling"); gateway.completeInitialRead(); require(service.initialPlcReadCompleted(), "service must retain the initial PLC read state"); require(service.enterOnlineRunning().succeeded, "online running must start after an initial PLC read"); require(service.simulationState() == SimulationState::Stopped, "online running must never start the software executor"); require(service.policy().usesPlcRegisters, "online running must use PLC registers"); require(!service.policy().runsLogicExecutor, "online running must keep the software executor stopped"); } } // namespace int main() { try { // 运行模式只有这一组状态机边界测试 testModeTransitions(); } catch (const std::exception &error) { std::cerr << "runtime mode service tests failed: " << error.what() << '\n'; return 1; } std::cout << "runtime mode service tests passed\n"; return 0; }