综合平台编程器项目的远程存储
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 
 

515 строки
25 KiB

  1. #include "domain/project_storage.h"
  2. #include "domain/register_repository.h"
  3. #include "domain/virtual_register_repository.h"
  4. #include "services/hmi_editor_service.h"
  5. #include "services/editor_history.h"
  6. #include "services/hmi_runtime_service.h"
  7. #include "services/hmi_navigation_service.h"
  8. #include "services/project_service.h"
  9. #include "support/test_support.h"
  10. #include <exception>
  11. #include <iostream>
  12. #include <limits>
  13. #include <stdexcept>
  14. #include <string>
  15. namespace {
  16. using TestProjectStorage = TestSupport::InMemoryProjectStorage;
  17. using TestSupport::require;
  18. void testControlEditing()
  19. {
  20. // 覆盖控件创建、移动、绑定校验、重命名冲突和删除流程
  21. TestProjectStorage storage;
  22. ProjectService project_service(storage);
  23. HmiEditorService service(project_service);
  24. const HmiEditorResult page_result = service.ensureDefaultPage();
  25. require(page_result.succeeded, "default HMI page creation must succeed");
  26. const std::string page_id = page_result.id;
  27. const HmiEditorResult button = service.addControl(page_id, HmiControlType::Button);
  28. const HmiEditorResult indicator = service.addControl(page_id, HmiControlType::Indicator);
  29. const HmiEditorResult display = service.addControl(
  30. page_id, HmiControlType::NumericDisplay);
  31. const HmiEditorResult input = service.addControl(page_id, HmiControlType::NumericInput);
  32. require(button.succeeded && indicator.succeeded && display.succeeded
  33. && input.succeeded,
  34. "basic HMI controls must be added");
  35. const HmiPage *page = service.findPage(page_id);
  36. require(page != nullptr && page->controls.size() == 4,
  37. "all added controls must be kept in the page model");
  38. require(service.moveControl(page_id, button.id, {120, 80, 120, 40}).succeeded,
  39. "a valid control move must succeed");
  40. require(!service.moveControl(page_id, button.id, {790, 460, 120, 40}).succeeded,
  41. "a move outside the page must be rejected");
  42. HmiControl updated = *service.findControl(page_id, button.id);
  43. updated.binding = RegisterAddress{RegisterArea::D, 0};
  44. require(!service.updateControl(page_id, button.id, updated).succeeded,
  45. "buttons must reject D address bindings during editing");
  46. updated.binding = RegisterAddress{RegisterArea::M, 7};
  47. updated.text = "启动";
  48. require(service.updateControl(page_id, button.id, updated).succeeded,
  49. "a button M binding and edited text must be accepted");
  50. HmiControl duplicate = *service.findControl(page_id, indicator.id);
  51. duplicate.id = button.id;
  52. require(service.updateControl(page_id, indicator.id, duplicate).error
  53. == HmiEditorError::DuplicateId,
  54. "duplicate control ids must be rejected");
  55. require(service.removeControl(page_id, indicator.id).succeeded,
  56. "deleting a selected control must succeed");
  57. require(service.findControl(page_id, indicator.id) == nullptr,
  58. "deleted controls must not remain in the model");
  59. }
  60. void testRuntimeUsesRegisterRepository()
  61. {
  62. // 运行服务只能经由仓库接口读写 M/D,不依赖具体离线实现
  63. VirtualRegisterRepository repository;
  64. HmiRuntimeService runtime_service(repository);
  65. HmiControl button;
  66. button.id = "start";
  67. button.type = HmiControlType::Button;
  68. button.binding = RegisterAddress{RegisterArea::M, 12};
  69. require(button.buttonOperation == HmiButtonOperation::MomentaryOn,
  70. "new buttons must default to momentary ON");
  71. require(runtime_service.operateButton(button, HmiButtonEvent::Pressed).succeeded,
  72. "momentary button press must be accepted by the repository");
  73. require(repository.readBit(*button.binding).value,
  74. "momentary button press must write ON");
  75. require(runtime_service.operateButton(button, HmiButtonEvent::Released).succeeded,
  76. "momentary button release must be accepted by the repository");
  77. require(!repository.readBit(*button.binding).value,
  78. "momentary button release must write OFF");
  79. button.buttonOperation = HmiButtonOperation::SetOn;
  80. require(runtime_service.operateButton(button, HmiButtonEvent::Pressed).succeeded,
  81. "set ON button press must succeed");
  82. require(repository.readBit(*button.binding).value,
  83. "set ON button press must write ON");
  84. require(runtime_service.operateButton(button, HmiButtonEvent::Released).succeeded,
  85. "set ON button release must be ignored successfully");
  86. require(repository.readBit(*button.binding).value,
  87. "set ON button release must keep the bit ON");
  88. button.buttonOperation = HmiButtonOperation::SetOff;
  89. require(runtime_service.operateButton(button, HmiButtonEvent::Pressed).succeeded,
  90. "set OFF button press must succeed");
  91. require(!repository.readBit(*button.binding).value,
  92. "set OFF button press must write OFF");
  93. button.buttonOperation = HmiButtonOperation::Toggle;
  94. require(runtime_service.operateButton(button, HmiButtonEvent::Pressed).succeeded,
  95. "toggle button press must succeed");
  96. require(repository.readBit(*button.binding).value,
  97. "toggle button press must invert the current value");
  98. HmiControl indicator;
  99. indicator.id = "running";
  100. indicator.type = HmiControlType::Indicator;
  101. indicator.binding = RegisterAddress{RegisterArea::M, 12};
  102. const HmiRuntimeReadResult indicator_value = runtime_service.readControl(indicator);
  103. require(indicator_value.succeeded && indicator_value.bit_value,
  104. "indicators must read M values through the repository");
  105. HmiControl numeric_input;
  106. numeric_input.id = "target";
  107. numeric_input.type = HmiControlType::NumericInput;
  108. numeric_input.binding = RegisterAddress{RegisterArea::D, 9};
  109. require(runtime_service.writeNumericInput(numeric_input, -18).succeeded,
  110. "numeric input runtime writes must be accepted by the repository");
  111. HmiControl numeric_display;
  112. numeric_display.id = "actual";
  113. numeric_display.type = HmiControlType::NumericDisplay;
  114. numeric_display.binding = RegisterAddress{RegisterArea::D, 9};
  115. const HmiRuntimeReadResult numeric_value = runtime_service.readControl(numeric_display);
  116. require(numeric_value.succeeded && numeric_value.word_value == -18,
  117. "numeric display must read D values through the repository");
  118. HmiControl float_input = numeric_input;
  119. float_input.dataType = RegisterDataType::Float32;
  120. float_input.binding = RegisterAddress{RegisterArea::D, 20};
  121. require(runtime_service.writeNumericInput(float_input, -2.5).succeeded,
  122. "Float32 numeric input must write two consecutive D words");
  123. const HmiRuntimeReadResult float_value = runtime_service.readControl(float_input);
  124. require(float_value.succeeded && float_value.float_value == -2.5f,
  125. "Float32 numeric control must decode two consecutive D words");
  126. require(!runtime_service.writeNumericInput(
  127. float_input, std::numeric_limits<double>::infinity()).succeeded,
  128. "Float32 numeric input must reject infinity");
  129. }
  130. void testHistoryAndAtomicBatchDelete()
  131. {
  132. TestProjectStorage storage;
  133. ProjectService project_service(storage);
  134. HmiEditorService service(project_service);
  135. const std::string page_id = service.ensureDefaultPage().id;
  136. const HmiEditorResult first = service.addControl(page_id, HmiControlType::Label);
  137. const HmiEditorResult second = service.addControl(page_id, HmiControlType::Label);
  138. const HmiEditorResult third = service.addControl(page_id, HmiControlType::Label);
  139. require(first.succeeded && second.succeeded && third.succeeded,
  140. "controls for history testing must be created");
  141. service.clearHistory();
  142. require(!service.removeControls(page_id, {first.id, "missing-control"}).succeeded,
  143. "batch deletion must reject an unknown control before changing the page");
  144. require(service.findPage(page_id)->controls.size() == 3U
  145. && !service.canUndo(),
  146. "failed batch deletion must be atomic and leave history unchanged");
  147. require(service.removeControls(page_id, {first.id, second.id}).succeeded,
  148. "batch deletion of valid controls must succeed");
  149. require(service.findPage(page_id)->controls.size() == 1U
  150. && service.canUndo(),
  151. "valid batch deletion must remove all selected controls in one history step");
  152. require(service.undo().succeeded && service.findPage(page_id)->controls.size() == 3U,
  153. "HMI undo must restore a deleted batch");
  154. require(service.redo().succeeded && service.findPage(page_id)->controls.size() == 1U,
  155. "HMI redo must reapply a deleted batch");
  156. service.clearHistory();
  157. const HmiControl *remaining = service.findControl(page_id, third.id);
  158. require(remaining != nullptr,
  159. "the unselected control must survive a batch deletion");
  160. require(service.moveControl(page_id, third.id, remaining->bounds).succeeded
  161. && !service.canUndo(),
  162. "a no-op control move must not consume an undo step");
  163. service.clearHistory();
  164. HmiControl candidate = *service.findControl(page_id, third.id);
  165. for (int index = 1; index <= 101; ++index)
  166. {
  167. candidate.bounds.x = index;
  168. require(service.updateControl(page_id, third.id, candidate).succeeded,
  169. "repeated valid HMI edits must succeed");
  170. }
  171. int undo_count = 0;
  172. while (service.undo().succeeded)
  173. {
  174. ++undo_count;
  175. }
  176. require(undo_count == static_cast<int>(EditorHistory<int>::kMaximumEntries),
  177. "HMI history must retain exactly the configured most recent steps");
  178. }
  179. void testBatchPasteControls()
  180. {
  181. TestProjectStorage storage;
  182. ProjectService project_service(storage);
  183. HmiEditorService service(project_service);
  184. const std::string page_id = service.ensureDefaultPage().id;
  185. const HmiEditorResult first = service.addControl(page_id, HmiControlType::Label);
  186. const HmiEditorResult second = service.addControl(page_id, HmiControlType::Label);
  187. require(first.succeeded && second.succeeded,
  188. "controls for paste testing must be created");
  189. HmiControl first_copy = *service.findControl(page_id, first.id);
  190. HmiControl second_copy = *service.findControl(page_id, second.id);
  191. first_copy.bounds = {100, 80, 80, 32};
  192. first_copy.text = "复制标签";
  193. first_copy.properties[HmiAppearanceProperty::kTextColor] = "#E53935";
  194. second_copy.bounds = {220, 80, 80, 32};
  195. require(service.updateControl(page_id, first.id, first_copy).succeeded
  196. && service.updateControl(page_id, second.id, second_copy).succeeded,
  197. "source controls must be movable before paste");
  198. service.clearHistory();
  199. const HmiEditorResult pasted = service.pasteControls(
  200. page_id, {first_copy, second_copy});
  201. require(pasted.succeeded
  202. && service.findPage(page_id)->controls.size() == 4U
  203. && pasted.id != first.id && pasted.id != second.id,
  204. "batch paste must create controls with fresh ids");
  205. const HmiControl *pasted_control = service.findControl(page_id, pasted.id);
  206. require(pasted_control != nullptr
  207. && pasted_control->bounds.x == first_copy.bounds.x + 20
  208. && pasted_control->bounds.y == first_copy.bounds.y + 20
  209. && pasted_control->text == first_copy.text
  210. && pasted_control->properties == first_copy.properties,
  211. "pasted controls must preserve content and appearance with an offset");
  212. require(service.undo().succeeded
  213. && service.findPage(page_id)->controls.size() == 2U,
  214. "batch control paste must be one undoable operation");
  215. }
  216. void testRuntimeValidationAndPasteBoundaries()
  217. {
  218. TestProjectStorage storage;
  219. ProjectService project_service(storage);
  220. HmiEditorService editor(project_service);
  221. const std::string page_id = editor.ensureDefaultPage().id;
  222. HmiControl label;
  223. label.id = "label-source";
  224. label.type = HmiControlType::Label;
  225. label.bounds = {760, 360, 32, 32};
  226. label.text = "边界";
  227. require(editor.pasteControls(page_id, {label}, 20, 20).succeeded,
  228. "a paste near the page edge must be clamped into the page");
  229. const HmiPage *page = editor.findPage(page_id);
  230. require(page != nullptr && page->controls.size() == 1U,
  231. "edge paste must add exactly one control");
  232. const HmiControl &edge_copy = page->controls.front();
  233. require(edge_copy.bounds.x + edge_copy.bounds.width <= page->width
  234. && edge_copy.bounds.y + edge_copy.bounds.height <= page->height
  235. && edge_copy.bounds.x >= 0 && edge_copy.bounds.y >= 0,
  236. "edge paste must keep the copied control inside page bounds");
  237. require(!editor.pasteControls(page_id, {}).succeeded,
  238. "an empty HMI clipboard must be rejected");
  239. HmiControl oversized = label;
  240. oversized.bounds = {0, 0, page->width + 1, 32};
  241. require(!editor.pasteControls(page_id, {oversized}).succeeded
  242. && editor.findPage(page_id)->controls.size() == 1U,
  243. "a copied control wider than the target page must fail atomically");
  244. HmiControl unsupported = label;
  245. unsupported.type = HmiControlType::Count;
  246. require(!editor.pasteControls(page_id, {unsupported}).succeeded
  247. && editor.findPage(page_id)->controls.size() == 1U,
  248. "an unsupported copied HMI type must fail atomically");
  249. std::vector<HmiControl> too_many(513U, label);
  250. require(!editor.pasteControls(page_id, too_many).succeeded
  251. && editor.findPage(page_id)->controls.size() == 1U,
  252. "a paste batch above the page limit must be rejected before mutation");
  253. VirtualRegisterRepository repository;
  254. HmiRuntimeService runtime(repository);
  255. HmiControl unbound_indicator;
  256. unbound_indicator.type = HmiControlType::Indicator;
  257. require(runtime.readControl(unbound_indicator).error
  258. == HmiRuntimeError::MissingBinding,
  259. "an unbound runtime indicator must report MissingBinding");
  260. HmiControl wrong_area_indicator = unbound_indicator;
  261. wrong_area_indicator.binding = RegisterAddress{RegisterArea::D, 0};
  262. require(runtime.readControl(wrong_area_indicator).error
  263. == HmiRuntimeError::InvalidBinding,
  264. "an indicator bound to D must report InvalidBinding");
  265. HmiControl invalid_indicator = unbound_indicator;
  266. invalid_indicator.binding = RegisterAddress{RegisterArea::M, 4001};
  267. require(runtime.readControl(invalid_indicator).error
  268. == HmiRuntimeError::InvalidBinding,
  269. "an indicator with an out-of-range address must report InvalidBinding");
  270. require(runtime.readControl(label).error
  271. == HmiRuntimeError::UnsupportedControl,
  272. "a static label must not be treated as a register runtime control");
  273. HmiControl wrong_button = unbound_indicator;
  274. wrong_button.type = HmiControlType::Button;
  275. wrong_button.binding = RegisterAddress{RegisterArea::D, 0};
  276. require(runtime.operateButton(wrong_button, HmiButtonEvent::Pressed).error
  277. == HmiRuntimeError::InvalidBinding,
  278. "a button bound to D must reject runtime writes");
  279. HmiControl wrong_numeric = unbound_indicator;
  280. wrong_numeric.type = HmiControlType::NumericInput;
  281. wrong_numeric.binding = RegisterAddress{RegisterArea::M, 0};
  282. require(runtime.writeNumericInput(wrong_numeric, 1).error
  283. == HmiRuntimeError::InvalidBinding,
  284. "a numeric input bound to M must reject runtime writes");
  285. }
  286. void testAppearanceEditing()
  287. {
  288. TestProjectStorage storage;
  289. ProjectService project_service(storage);
  290. HmiEditorService service(project_service);
  291. const std::string page_id = service.ensureDefaultPage().id;
  292. const HmiEditorResult label = service.addControl(page_id, HmiControlType::Label);
  293. require(label.succeeded, "a label must be available for appearance editing");
  294. HmiControl appearance = *service.findControl(page_id, label.id);
  295. appearance.properties[HmiAppearanceProperty::kTextColor] = "#E53935";
  296. appearance.properties[HmiAppearanceProperty::kFontSize] = "18";
  297. appearance.properties[HmiAppearanceProperty::kFontBold] = "true";
  298. appearance.properties[HmiAppearanceProperty::kFontItalic] = "false";
  299. require(service.updateControl(page_id, label.id, appearance).succeeded,
  300. "valid appearance properties must be applied atomically");
  301. const HmiControl *updated = service.findControl(page_id, label.id);
  302. require(updated != nullptr
  303. && updated->properties.at(HmiAppearanceProperty::kTextColor) == "#E53935"
  304. && updated->properties.at(HmiAppearanceProperty::kFontSize) == "18"
  305. && updated->properties.at(HmiAppearanceProperty::kFontBold) == "true",
  306. "appearance properties must be stored on the HMI control");
  307. HmiControl invalid = *updated;
  308. invalid.properties[HmiAppearanceProperty::kTextColor] = "invalid";
  309. require(!service.updateControl(page_id, label.id, invalid).succeeded,
  310. "invalid appearance properties must be rejected without a partial update");
  311. require(service.findControl(page_id, label.id)->properties.at(
  312. HmiAppearanceProperty::kTextColor) == "#E53935",
  313. "failed appearance updates must leave the old color intact");
  314. require(service.undo().succeeded,
  315. "appearance updates must participate in HMI undo history");
  316. require(service.findControl(page_id, label.id)->properties.empty(),
  317. "undo must remove the applied appearance properties");
  318. require(service.redo().succeeded
  319. && service.findControl(page_id, label.id)->properties.at(
  320. HmiAppearanceProperty::kFontSize) == "18",
  321. "redo must restore the applied appearance properties");
  322. }
  323. void testPageLifecycleAndNavigation()
  324. {
  325. TestProjectStorage storage;
  326. ProjectService project_service(storage);
  327. HmiEditorService service(project_service);
  328. const std::string main_id = service.ensureDefaultPage().id;
  329. require(project_service.project().initialHmiPageId == main_id,
  330. "the first page must become the initial HMI page");
  331. const HmiEditorResult settings = service.addPage("Settings");
  332. const HmiEditorResult maintenance = service.addPage("Maintenance");
  333. require(settings.succeeded && maintenance.succeeded,
  334. "multiple HMI pages must be creatable");
  335. require(service.renamePage(settings.id, "Parameters").succeeded,
  336. "an HMI page must be renamable by stable id");
  337. require(service.renamePage(maintenance.id, "Parameters").error
  338. == HmiEditorError::DuplicateName,
  339. "HMI page names must remain unique");
  340. require(service.movePage(maintenance.id, -1).succeeded
  341. && project_service.project().hmiPages.at(1).id == maintenance.id,
  342. "HMI page order must be editable independently from page ids");
  343. const HmiEditorResult label = service.addControl(main_id, HmiControlType::Label);
  344. require(label.succeeded
  345. && service.findControl(main_id, label.id)->isConfigured(),
  346. "the editor service must expose a configured static Label control");
  347. const HmiEditorResult alarm_list = service.addControl(
  348. main_id, HmiControlType::AlarmList);
  349. require(alarm_list.succeeded
  350. && service.findControl(main_id, alarm_list.id)->isConfigured()
  351. && !service.findControl(main_id, alarm_list.id)->binding.has_value(),
  352. "AlarmList must be configured without a register binding");
  353. HmiControl bound_alarm_list = *service.findControl(main_id, alarm_list.id);
  354. bound_alarm_list.binding = RegisterAddress{RegisterArea::M, 20};
  355. require(!service.updateControl(
  356. main_id, alarm_list.id, bound_alarm_list).succeeded,
  357. "AlarmList must reject direct register bindings");
  358. const HmiEditorResult jump = service.addControl(main_id, HmiControlType::PageJump);
  359. require(jump.succeeded, "the editor service must expose PageJump creation");
  360. HmiControl jump_control = *service.findControl(main_id, jump.id);
  361. jump_control.pageJump = HmiPageJumpConfig{maintenance.id};
  362. require(service.updateControl(main_id, jump.id, jump_control).succeeded,
  363. "PageJump target updates must resolve stable page ids");
  364. require(service.removePage(maintenance.id).error == HmiEditorError::PageReferenced,
  365. "a page referenced by PageJump must not be deletable");
  366. require(service.setInitialPage(settings.id).succeeded,
  367. "the user must be able to select a different initial page");
  368. require(service.removePage(settings.id).error
  369. == HmiEditorError::InitialPageCannotBeRemoved,
  370. "the initial page must not be deleted implicitly");
  371. HmiNavigationService navigation(project_service);
  372. const HmiNavigationResult start = navigation.start();
  373. require(start.succeeded && start.pageId == settings.id,
  374. "runtime navigation must start from the persisted initial page");
  375. require(navigation.navigateTo(maintenance.id).succeeded
  376. && navigation.currentPageId() == maintenance.id,
  377. "runtime navigation must switch to an existing page");
  378. require(navigation.navigateTo("missing").error
  379. == HmiNavigationError::PageNotFound,
  380. "runtime navigation must reject an unknown page id");
  381. navigation.stop();
  382. require(navigation.currentPageId().empty(),
  383. "stopping runtime navigation must clear session state");
  384. }
  385. void testPageResizeIsAtomicAndUndoable()
  386. {
  387. TestProjectStorage storage;
  388. ProjectService project_service(storage);
  389. HmiEditorService service(project_service);
  390. const std::string page_id = service.ensureDefaultPage().id;
  391. const HmiEditorResult label = service.addControl(page_id, HmiControlType::Label);
  392. require(label.succeeded, "page resize fixture must add a control");
  393. HmiControl control = *service.findControl(page_id, label.id);
  394. control.bounds = {700, 340, 80, 32};
  395. require(service.updateControl(page_id, label.id, control).succeeded,
  396. "page resize fixture must place the control near the page edge");
  397. require(service.resizePage(page_id, 1024, 600).succeeded,
  398. "an HMI page must be resizable to a larger valid rectangle");
  399. require(service.findPage(page_id)->width == 1024
  400. && service.findPage(page_id)->height == 600,
  401. "page resize must update both dimensions");
  402. require(service.resizePage(page_id, 700, 300).error
  403. == HmiEditorError::InvalidPage,
  404. "page resize must reject dimensions that clip an existing control");
  405. require(service.findPage(page_id)->width == 1024
  406. && service.findPage(page_id)->height == 600,
  407. "a rejected page resize must leave the original dimensions intact");
  408. require(service.undo().succeeded
  409. && service.findPage(page_id)->width == 800
  410. && service.findPage(page_id)->height == 400,
  411. "page resize must participate in HMI undo history");
  412. require(service.redo().succeeded
  413. && service.findPage(page_id)->width == 1024
  414. && service.findPage(page_id)->height == 600,
  415. "page resize redo must restore the new dimensions");
  416. require(service.resizePage(page_id, 319, 600).error
  417. == HmiEditorError::InvalidPage,
  418. "page width below the business minimum must be rejected");
  419. }
  420. void testConfiguredPageDefaultsAndLimits()
  421. {
  422. TestProjectStorage storage;
  423. ProjectLimitSettings limits;
  424. limits.maximumHmiPages = 1U;
  425. limits.maximumHmiControlsPerPage = 1U;
  426. HmiDefaultSettings defaults;
  427. defaults.pageWidth = 1024;
  428. defaults.pageHeight = 600;
  429. ProjectService project_service(storage, limits);
  430. HmiEditorService service(project_service, defaults);
  431. const HmiEditorResult page = service.ensureDefaultPage();
  432. require(page.succeeded
  433. && service.findPage(page.id)->width == 1024
  434. && service.findPage(page.id)->height == 600,
  435. "configured HMI dimensions must initialize the default page");
  436. require(!service.addPage("Second page").succeeded,
  437. "the HMI editor must use the configured page limit");
  438. require(service.addControl(page.id, HmiControlType::Label).succeeded
  439. && !service.addControl(page.id, HmiControlType::Label).succeeded,
  440. "the HMI editor must use the configured per-page control limit");
  441. }
  442. } // namespace
  443. int main()
  444. {
  445. try
  446. {
  447. // 编辑和运行场景分别验证服务层两条独立职责
  448. testControlEditing();
  449. testHistoryAndAtomicBatchDelete();
  450. testBatchPasteControls();
  451. testRuntimeValidationAndPasteBoundaries();
  452. testAppearanceEditing();
  453. testRuntimeUsesRegisterRepository();
  454. testPageLifecycleAndNavigation();
  455. testPageResizeIsAtomicAndUndoable();
  456. testConfiguredPageDefaultsAndLimits();
  457. }
  458. catch (const std::exception &error)
  459. {
  460. std::cerr << "HMI editor service tests failed: " << error.what() << '\n';
  461. return 1;
  462. }
  463. std::cout << "HMI editor service tests passed\n";
  464. return 0;
  465. }