综合平台编程器项目的远程存储
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 
 

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