综合平台编程器项目的远程存储
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 

248 wiersze
11 KiB

  1. #include "domain/project_storage.h"
  2. #include "services/logic_editor_service.h"
  3. #include "services/project_service.h"
  4. #include <iostream>
  5. #include <stdexcept>
  6. namespace {
  7. class TestProjectStorage final : public ProjectStorage
  8. {
  9. public:
  10. ProjectSaveResult save(const Project &, const std::string &) override
  11. {
  12. return {true, ProjectStorageError::None, {}};
  13. }
  14. ProjectLoadResult load(const std::string &) override
  15. {
  16. return {false, {}, ProjectStorageError::FileReadFailed, {}};
  17. }
  18. };
  19. void require(bool condition, const std::string &message)
  20. {
  21. if (!condition)
  22. {
  23. throw std::runtime_error(message);
  24. }
  25. }
  26. ContactNodeConfig contact(int address)
  27. {
  28. return {RegisterAddress{RegisterArea::M, address}, ContactMode::NormallyOpen};
  29. }
  30. void testStructuredEditingAndNormalization()
  31. {
  32. TestProjectStorage storage;
  33. ProjectService project_service(storage);
  34. LogicEditorService service(project_service);
  35. const std::string logic_id = service.ensureDefaultLogic().id;
  36. const std::string rung_id = service.firstRungId(logic_id);
  37. const LogicEditorResult first = service.appendCondition(logic_id, rung_id, contact(0));
  38. const LogicEditorResult second = service.appendCondition(logic_id, rung_id, contact(1));
  39. require(first.succeeded && second.succeeded, "series append must succeed");
  40. const LadderRung *rung = service.findRung(logic_id, rung_id);
  41. require(rung->condition->kind == ConditionExpressionKind::Series
  42. && rung->condition->children.size() == 2U,
  43. "two appended nodes must form a series expression");
  44. const std::string second_expression_id = second.id;
  45. const LogicEditorResult parallel = service.addParallelBranch(
  46. logic_id, rung_id, {second_expression_id}, contact(2));
  47. require(parallel.succeeded, "parallel insertion must succeed");
  48. rung = service.findRung(logic_id, rung_id);
  49. const ConditionExpression *parallel_expression = service.findExpression(
  50. logic_id, rung_id, rung->condition->children.at(1).id);
  51. require(parallel_expression != nullptr
  52. && parallel_expression->kind == ConditionExpressionKind::Parallel,
  53. "selected node must become a parallel expression");
  54. const LogicEditorResult nested_series = service.insertConditionAfter(
  55. logic_id,
  56. rung_id,
  57. parallel.id,
  58. contact(3));
  59. require(nested_series.succeeded, "a parallel branch must accept a series node");
  60. rung = service.findRung(logic_id, rung_id);
  61. require(rung->condition->kind == ConditionExpressionKind::Series,
  62. "root must remain a series expression");
  63. const ConditionExpression &nested_parallel_expression = rung->condition->children.at(1);
  64. require(nested_parallel_expression.kind == ConditionExpressionKind::Parallel
  65. && nested_parallel_expression.children.at(1).kind
  66. == ConditionExpressionKind::Series,
  67. "editor must express A AND (B OR (C AND D))");
  68. require(service.removeNode(logic_id, nested_series.id).succeeded,
  69. "nested series node deletion must succeed");
  70. rung = service.findRung(logic_id, rung_id);
  71. require(rung->condition->children.at(1).kind == ConditionExpressionKind::Parallel
  72. && rung->condition->children.at(1).children.at(1).kind
  73. == ConditionExpressionKind::Node,
  74. "single-child series container must collapse after deletion");
  75. require(service.removeNode(logic_id, parallel.id).succeeded,
  76. "parallel leaf deletion must succeed");
  77. rung = service.findRung(logic_id, rung_id);
  78. require(rung->condition->kind == ConditionExpressionKind::Series
  79. && rung->condition->children.size() == 2U,
  80. "single-child parallel container must collapse after deletion");
  81. require(service.setOutput(
  82. logic_id,
  83. rung_id,
  84. CoilNodeConfig{RegisterAddress{RegisterArea::M, 10}, CoilMode::Normal})
  85. .succeeded,
  86. "output coil must be set");
  87. require(service.findLogic(logic_id)->validate(),
  88. "structured editing result must remain a valid draft");
  89. }
  90. void testRangeParallelInsertion()
  91. {
  92. TestProjectStorage storage;
  93. ProjectService project_service(storage);
  94. LogicEditorService service(project_service);
  95. const std::string logic_id = service.ensureDefaultLogic().id;
  96. const std::string rung_id = service.firstRungId(logic_id);
  97. service.appendCondition(logic_id, rung_id, contact(0));
  98. service.appendCondition(logic_id, rung_id, contact(1));
  99. service.appendCondition(logic_id, rung_id, contact(2));
  100. const LogicEditorResult branch = service.addParallelBranch(
  101. logic_id, rung_id, {"contact-2", "contact-3"}, contact(3));
  102. require(branch.succeeded, "a continuous series range must accept a parallel branch");
  103. const ConditionExpression &root = *service.findRung(logic_id, rung_id)->condition;
  104. require(root.kind == ConditionExpressionKind::Series
  105. && root.children.size() == 2U
  106. && root.children.at(1).kind == ConditionExpressionKind::Parallel
  107. && root.children.at(1).children.front().kind
  108. == ConditionExpressionKind::Series,
  109. "range insertion must express A AND ((B AND C) OR D)");
  110. require(root.validate(), "range insertion must preserve normalized topology");
  111. const LogicEditorResult invalid = service.addParallelBranch(
  112. logic_id, rung_id, {"contact-1", "contact-3"}, contact(4));
  113. require(!invalid.succeeded
  114. && invalid.error == LogicEditorError::InvalidOperation,
  115. "a non-contiguous selection must be rejected");
  116. }
  117. void testLogicLifecycleAndOrdering()
  118. {
  119. TestProjectStorage storage;
  120. ProjectService project_service(storage);
  121. LogicEditorService service(project_service);
  122. const std::string first_id = service.ensureDefaultLogic().id;
  123. const LogicEditorResult second = service.addLogic("Safety logic");
  124. const LogicEditorResult third = service.addLogic("Alarm logic");
  125. require(second.succeeded && third.succeeded,
  126. "multiple control logic modules must be creatable");
  127. require(service.renameLogic(second.id, "Interlock logic").succeeded,
  128. "control logic modules must be renamable by stable id");
  129. require(service.renameLogic(third.id, "Interlock logic").error
  130. == LogicEditorError::DuplicateName,
  131. "control logic names must remain unique");
  132. require(service.moveLogic(third.id, -1).succeeded
  133. && project_service.project().controlLogics.at(1).id == third.id,
  134. "logic scan order must follow editable vector order");
  135. require(service.setLogicEnabled(second.id, false).succeeded
  136. && !service.findLogic(second.id)->enabled,
  137. "a control logic module must support explicit disable and enable");
  138. require(service.setLogicEnabled(second.id, true).succeeded
  139. && service.findLogic(second.id)->enabled,
  140. "a disabled control logic module must be re-enableable");
  141. require(service.removeLogic(third.id).succeeded,
  142. "a non-final control logic module must be deletable");
  143. require(service.removeLogic(second.id).succeeded,
  144. "logic deletion must preserve the remaining module");
  145. require(service.removeLogic(first_id).error
  146. == LogicEditorError::LastLogicRequired,
  147. "the project must retain at least one control logic module");
  148. }
  149. void testEdgeTimerNodesAndRungComments()
  150. {
  151. TestProjectStorage storage;
  152. ProjectService project_service(storage);
  153. LogicEditorService service(project_service);
  154. const std::string logic_id = service.ensureDefaultLogic().id;
  155. const std::string rung_id = service.firstRungId(logic_id);
  156. const LogicEditorResult rising = service.appendCondition(
  157. logic_id,
  158. rung_id,
  159. EdgeContactNodeConfig{
  160. RegisterAddress{RegisterArea::M, 3}, EdgeMode::Rising});
  161. require(rising.succeeded && rising.id == "edge-1",
  162. "the editor must create rising edge nodes with a stable prefix");
  163. const LogicEditorResult timer = service.addParallelBranch(
  164. logic_id,
  165. rung_id,
  166. {rising.id},
  167. TimerContactNodeConfig{TimerAddress{2}, ContactMode::NormallyOpen});
  168. require(timer.succeeded && timer.id == "timer-contact-1",
  169. "the editor must insert T contacts as conditions");
  170. const LogicEditorResult ton = service.setOutput(
  171. logic_id, rung_id, TonNodeConfig{TimerAddress{2}, 500});
  172. require(ton.succeeded && ton.id == "ton-1",
  173. "the editor must create TON outputs with a stable prefix");
  174. require(service.updateNodeConfig(
  175. logic_id,
  176. rising.id,
  177. EdgeContactNodeConfig{
  178. RegisterAddress{RegisterArea::M, 4}, EdgeMode::Falling})
  179. .succeeded,
  180. "the editor must apply edge mode and M address properties");
  181. require(service.updateNodeConfig(
  182. logic_id,
  183. timer.id,
  184. TimerContactNodeConfig{TimerAddress{4}, ContactMode::NormallyClosed})
  185. .succeeded,
  186. "the editor must apply T contact mode and address properties");
  187. require(service.updateNodeConfig(
  188. logic_id,
  189. ton.id,
  190. TonNodeConfig{TimerAddress{4}, 750})
  191. .succeeded,
  192. "the editor must apply TON preset properties");
  193. require(service.updateRungComment(logic_id, rung_id, "延时启动网络").succeeded,
  194. "the editor must update a network comment by stable rung id");
  195. const LadderRung *rung = service.findRung(logic_id, rung_id);
  196. require(rung != nullptr && rung->comment == "延时启动网络"
  197. && std::get<EdgeContactNodeConfig>(
  198. rung->condition->children.front().node->config).mode
  199. == EdgeMode::Falling
  200. && std::get<TimerContactNodeConfig>(
  201. rung->condition->children.at(1).node->config).address
  202. == TimerAddress{4}
  203. && std::get<TonNodeConfig>(rung->output->config).presetMs == 750,
  204. "edge, T contact, TON and rung comment updates must remain in the model");
  205. }
  206. } // namespace
  207. int main()
  208. {
  209. try
  210. {
  211. testStructuredEditingAndNormalization();
  212. testRangeParallelInsertion();
  213. testLogicLifecycleAndOrdering();
  214. testEdgeTimerNodesAndRungComments();
  215. }
  216. catch (const std::exception &error)
  217. {
  218. std::cerr << "logic editor service tests failed: " << error.what() << '\n';
  219. return 1;
  220. }
  221. std::cout << "logic editor service tests passed\n";
  222. return 0;
  223. }