综合平台编程器项目的远程存储
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

185 lines
6.4 KiB

  1. #include "domain/control_logic_model.h"
  2. #include "domain/hmi_model.h"
  3. #include "domain/project_model.h"
  4. #include "domain/register_address.h"
  5. #include "domain/register_repository.h"
  6. #include "domain/runtime_state.h"
  7. #include <cstdint>
  8. #include <exception>
  9. #include <iostream>
  10. #include <stdexcept>
  11. #include <string>
  12. namespace {
  13. void require(bool condition, const std::string &message)
  14. {
  15. if (!condition)
  16. {
  17. throw std::runtime_error(message);
  18. }
  19. }
  20. void testRegisterAddressBoundaries()
  21. {
  22. require(RegisterAddress{RegisterArea::M, 0}.isValid(),
  23. "M0 must be valid");
  24. require(RegisterAddress{RegisterArea::D, 4000}.isValid(),
  25. "D4000 must be valid");
  26. require(!(RegisterAddress{RegisterArea::M, -1}.isValid()),
  27. "negative register index must be rejected");
  28. require(!(RegisterAddress{RegisterArea::D, 4001}.isValid()),
  29. "register index above 4000 must be rejected");
  30. require(!(RegisterAddress{static_cast<RegisterArea>(99), 0}.isValid()),
  31. "unknown register area must be rejected");
  32. }
  33. void testRegisterRepositorySeparatesAreas()
  34. {
  35. VirtualRegisterRepository repository;
  36. const RegisterAddress m0{RegisterArea::M, 0};
  37. const RegisterAddress d0{RegisterArea::D, 0};
  38. require(repository.writeBit(m0, true).succeeded, "M bit write must succeed");
  39. require(repository.readBit(m0).value, "M bit read must return written value");
  40. require(repository.writeWord(d0, static_cast<std::int16_t>(-123)).succeeded,
  41. "D word write must succeed");
  42. require(repository.readWord(d0).value == -123, "D word read must return written value");
  43. require(repository.readBit(d0).error == RegisterError::AreaMismatch,
  44. "D address must not be read as a bit");
  45. require(repository.readWord(m0).error == RegisterError::AreaMismatch,
  46. "M address must not be read as a word");
  47. }
  48. Project makeValidProject()
  49. {
  50. HmiControl start_button;
  51. start_button.id = "start-button";
  52. start_button.type = HmiControlType::Button;
  53. start_button.text = "Start";
  54. start_button.binding = {RegisterArea::M, 0};
  55. HmiPage page;
  56. page.id = "main-page";
  57. page.name = "Main";
  58. page.controls.push_back(start_button);
  59. LogicNode contact;
  60. contact.id = "start-contact";
  61. contact.config = ContactNodeConfig{
  62. RegisterAddress{RegisterArea::M, 0},
  63. ContactMode::NormallyOpen};
  64. LogicNode coil;
  65. coil.id = "run-coil";
  66. coil.config = CoilNodeConfig{
  67. RegisterAddress{RegisterArea::M, 1},
  68. CoilMode::Normal};
  69. ControlLogic logic;
  70. logic.id = "start-logic";
  71. logic.name = "Start logic";
  72. logic.nodes = {contact, coil};
  73. logic.connections.push_back({contact.id, coil.id});
  74. Project project;
  75. project.metadata = {"sample-project", "Sample project", "1.0"};
  76. project.hmiPages.push_back(page);
  77. project.controlLogics.push_back(logic);
  78. return project;
  79. }
  80. void testLogicNodeConfigurationBoundaries()
  81. {
  82. LogicNode contact;
  83. contact.id = "contact";
  84. contact.config = ContactNodeConfig{
  85. RegisterAddress{RegisterArea::M, 0},
  86. ContactMode::NormallyOpen};
  87. require(contact.validate(), "contact node bound to M address must be valid");
  88. contact.config = ContactNodeConfig{
  89. RegisterAddress{RegisterArea::D, 0},
  90. ContactMode::NormallyOpen};
  91. require(!contact.validate(), "contact node bound to D address must be rejected");
  92. LogicNode comparison;
  93. comparison.id = "comparison";
  94. comparison.config = CompareNodeConfig{
  95. RegisterAddress{RegisterArea::D, 0},
  96. ComparisonOperator::GreaterThan,
  97. static_cast<std::int16_t>(100)};
  98. require(comparison.validate(), "comparison node bound to D address must be valid");
  99. }
  100. void testModelsValidateBindingsAndIdentifiers()
  101. {
  102. Project project = makeValidProject();
  103. require(project.validate(), "valid project model must pass validation");
  104. project.hmiPages.front().controls.front().binding =
  105. RegisterAddress{RegisterArea::D, 0};
  106. require(!project.validate(), "button bound to D area must be rejected");
  107. project = makeValidProject();
  108. project.hmiPages.push_back(project.hmiPages.front());
  109. require(!project.validate(), "duplicate HMI page id must be rejected");
  110. project = makeValidProject();
  111. project.hmiPages.front().controls.front().bounds.x = -1;
  112. require(!project.validate(), "controls outside the page must be rejected");
  113. project = makeValidProject();
  114. project.hmiPages.front().controls.front().bounds.width = 801;
  115. require(!project.validate(), "controls wider than the page must be rejected");
  116. project = makeValidProject();
  117. project.hmiPages.front().controls.front().properties.emplace("", "value");
  118. require(!project.validate(), "empty HMI property names must be rejected");
  119. }
  120. void testRuntimeStateBoundaries()
  121. {
  122. RuntimeState state;
  123. require(state.policy().allowsProjectEditing, "editing mode must allow project editing");
  124. require(state.enterOfflineRunning().succeeded, "editing may enter offline running");
  125. require(state.policy().usesVirtualRegisters, "offline mode must use virtual registers");
  126. require(state.policy().runsLogicExecutor, "offline mode must run logic executor");
  127. require(state.enterOnlineRunning(true).error
  128. == ModeTransitionError::MustReturnToEditing,
  129. "offline mode must not directly enter online mode");
  130. require(state.enterEditing().succeeded, "offline mode may return to editing");
  131. require(state.enterOnlineRunning(false).error
  132. == ModeTransitionError::InitialPlcReadRequired,
  133. "online mode must require an initial PLC read");
  134. require(state.enterOnlineRunning(true).succeeded,
  135. "editing may enter online mode after initial PLC read");
  136. require(!state.policy().runsLogicExecutor,
  137. "online mode must keep the software logic executor stopped");
  138. require(state.policy().usesPlcRegisters, "online mode must use PLC registers");
  139. }
  140. } // namespace
  141. int main()
  142. {
  143. try
  144. {
  145. testRegisterAddressBoundaries();
  146. testRegisterRepositorySeparatesAreas();
  147. testLogicNodeConfigurationBoundaries();
  148. testModelsValidateBindingsAndIdentifiers();
  149. testRuntimeStateBoundaries();
  150. }
  151. catch (const std::exception &error)
  152. {
  153. std::cerr << "domain tests failed: " << error.what() << '\n';
  154. return 1;
  155. }
  156. std::cout << "domain tests passed\n";
  157. return 0;
  158. }