|
- #include "domain/control_logic_model.h"
- #include "domain/hmi_control_registry.h"
- #include "domain/hmi_model.h"
- #include "domain/project_model.h"
- #include "domain/register_address.h"
- #include "domain/register_repository.h"
- #include "domain/runtime_state.h"
-
- #include <cstdint>
- #include <exception>
- #include <iostream>
- #include <set>
- #include <stdexcept>
- #include <string>
-
- namespace {
-
- void require(bool condition, const std::string &message)
- {
- if (!condition)
- {
- throw std::runtime_error(message);
- }
- }
-
- void testRegisterAddressBoundaries()
- {
- // 覆盖 M/D 地址允许范围及未知枚举值的拒绝路径
- require(RegisterAddress{RegisterArea::M, 0}.isValid(),
- "M0 must be valid");
- require(RegisterAddress{RegisterArea::D, 4000}.isValid(),
- "D4000 must be valid");
- require(!(RegisterAddress{RegisterArea::M, -1}.isValid()),
- "negative register index must be rejected");
- require(!(RegisterAddress{RegisterArea::D, 4001}.isValid()),
- "register index above 4000 must be rejected");
- require(!(RegisterAddress{static_cast<RegisterArea>(99), 0}.isValid()),
- "unknown register area must be rejected");
- }
-
- void testRegisterAddressParsing()
- {
- const RegisterAddressParseResult m0 = parseRegisterAddress(" m0 ");
- require(m0.succeeded && m0.address == RegisterAddress{RegisterArea::M, 0},
- "register parser must trim and normalize lowercase M addresses");
- const RegisterAddressParseResult d4000 = parseRegisterAddress("D4000");
- require(d4000.succeeded
- && d4000.address == RegisterAddress{RegisterArea::D, 4000},
- "register parser must accept the maximum D address");
- require(parseRegisterAddress("").error == RegisterAddressParseError::Empty,
- "register parser must distinguish empty input");
- require(parseRegisterAddress("X0").error
- == RegisterAddressParseError::UnsupportedArea,
- "register parser must reject unsupported areas");
- require(parseRegisterAddress("M1.0").error
- == RegisterAddressParseError::InvalidFormat,
- "register parser must reject non-decimal indices");
- require(parseRegisterAddress("D4001").error
- == RegisterAddressParseError::OutOfRange,
- "register parser must reject addresses above the project range");
- }
-
- void testRegisterRepositorySeparatesAreas()
- {
- // 验证离线仓库不会把 M 位和 D 字交叉解释
- VirtualRegisterRepository repository;
- const RegisterAddress m0{RegisterArea::M, 0};
- const RegisterAddress d0{RegisterArea::D, 0};
-
- require(repository.writeBit(m0, true).succeeded, "M bit write must succeed");
- require(repository.readBit(m0).value, "M bit read must return written value");
- require(repository.writeWord(d0, static_cast<std::int16_t>(-123)).succeeded,
- "D word write must succeed");
- require(repository.readWord(d0).value == -123, "D word read must return written value");
- require(repository.readBit(d0).error == RegisterError::AreaMismatch,
- "D address must not be read as a bit");
- require(repository.readWord(m0).error == RegisterError::AreaMismatch,
- "M address must not be read as a word");
- }
-
- void testHmiControlRegistryCompleteness()
- {
- std::set<const HmiControlDescriptor *> descriptors;
- std::set<std::string> storage_names;
- std::set<std::string> id_prefixes;
- const std::size_t control_type_count =
- static_cast<std::size_t>(HmiControlType::Count);
- for (std::size_t index = 0; index < control_type_count; ++index)
- {
- const HmiControlType type = static_cast<HmiControlType>(index);
- const HmiControlDescriptor *descriptor =
- findHmiControlDescriptor(type);
- require(descriptor != nullptr,
- "every HMI control type must have one descriptor");
- require(descriptor->type == type,
- "HMI type lookup must return the requested descriptor");
- require(descriptors.emplace(descriptor).second,
- "each HMI control type must resolve to a different descriptor");
- require(descriptor->storageName != nullptr
- && descriptor->storageName[0] != '\0',
- "HMI storage names must not be empty");
- require(descriptor->displayName != nullptr
- && descriptor->displayName[0] != '\0',
- "HMI display names must not be empty");
- require(descriptor->idPrefix != nullptr
- && descriptor->idPrefix[0] != '\0',
- "HMI id prefixes must not be empty");
- require(descriptor->defaultText != nullptr,
- "HMI default text must not be null");
- require(descriptor->defaultBounds.width > 0
- && descriptor->defaultBounds.height > 0,
- "HMI default bounds must have a positive size");
- require(findHmiControlDescriptor(descriptor->storageName) == descriptor,
- "HMI storage name lookup must return its registered descriptor");
- require(storage_names.emplace(descriptor->storageName).second,
- "HMI storage names must be unique");
- require(id_prefixes.emplace(descriptor->idPrefix).second,
- "HMI id prefixes must be unique");
-
- const std::optional<RegisterArea> binding_area =
- hmiBindingArea(descriptor->bindingKind);
- switch (descriptor->runtimeValueKind)
- {
- case HmiRuntimeValueKind::Bit:
- {
- require(descriptor->bindingKind == HmiBindingKind::Bit
- && binding_area == RegisterArea::M,
- "bit runtime controls must bind to the M area");
- break;
- }
- case HmiRuntimeValueKind::Word:
- {
- require(descriptor->bindingKind == HmiBindingKind::Word
- && binding_area == RegisterArea::D,
- "word runtime controls must bind to the D area");
- break;
- }
- case HmiRuntimeValueKind::None:
- {
- require(descriptor->bindingKind == HmiBindingKind::None
- && !binding_area.has_value(),
- "static controls must not declare a register binding");
- break;
- }
- }
-
- require(descriptor->requiresBindingForRunning
- == (descriptor->runtimeValueKind
- != HmiRuntimeValueKind::None),
- "runtime value controls must require a configured binding");
- }
-
- require(findHmiControlDescriptor(HmiControlType::Count) == nullptr,
- "the HMI control count marker must not be registered");
- require(findHmiControlDescriptor(static_cast<HmiControlType>(99)) == nullptr,
- "unknown HMI control types must not resolve");
- require(findHmiControlDescriptor("unknown") == nullptr,
- "unknown HMI storage names must not resolve");
- require(hmiBindingArea(HmiBindingKind::Bit) == RegisterArea::M,
- "bit bindings must use the M area");
- require(hmiBindingArea(HmiBindingKind::Word) == RegisterArea::D,
- "word bindings must use the D area");
- require(!hmiBindingArea(HmiBindingKind::None).has_value(),
- "controls without bindings must not resolve a register area");
- }
-
- Project makeValidProject()
- {
- // 构造包含 HMI 绑定和完整梯形图网络的最小合法工程作为测试基线
- HmiControl start_button;
- start_button.id = "start-button";
- start_button.type = HmiControlType::Button;
- start_button.text = "Start";
- start_button.binding = {RegisterArea::M, 0};
-
- HmiPage page;
- page.id = "main-page";
- page.name = "Main";
- page.controls.push_back(start_button);
-
- LogicNode contact;
- contact.id = "start-contact";
- contact.config = ContactNodeConfig{
- RegisterAddress{RegisterArea::M, 0},
- ContactMode::NormallyOpen};
-
- LogicNode coil;
- coil.id = "run-coil";
- coil.config = CoilNodeConfig{
- RegisterAddress{RegisterArea::M, 1},
- CoilMode::Normal};
-
- ControlLogic logic;
- logic.id = "start-logic";
- logic.name = "Start logic";
- LadderRung rung;
- rung.id = "rung-1";
- rung.name = "Network 1";
- rung.condition = ConditionExpression::fromNode(contact);
- rung.output = coil;
- logic.rungs.push_back(rung);
-
- Project project;
- project.metadata = {"sample-project", "Sample project", "1.0"};
- project.hmiPages.push_back(page);
- project.initialHmiPageId = page.id;
- project.controlLogics.push_back(logic);
- return project;
- }
-
- void testMultiPageAndLogicDomainRules()
- {
- Project project = makeValidProject();
- HmiPage settings;
- settings.id = "settings-page";
- settings.name = "Settings";
- project.hmiPages.push_back(settings);
-
- HmiControl label;
- label.id = "title";
- label.type = HmiControlType::Label;
- label.text = "Machine";
- project.hmiPages.front().controls.push_back(label);
- require(project.validate(), "an unbound label must be a valid static control");
-
- project.hmiPages.front().controls.back().binding =
- RegisterAddress{RegisterArea::M, 10};
- require(!project.validate(), "labels must reject register bindings");
-
- project = makeValidProject();
- project.hmiPages.push_back(settings);
- HmiControl jump;
- jump.id = "settings-jump";
- jump.type = HmiControlType::PageJump;
- jump.text = "Settings";
- jump.pageJump = HmiPageJumpConfig{settings.id};
- project.hmiPages.front().controls.push_back(jump);
- require(project.validate() && project.validateForRunning(),
- "a page jump must resolve its target by stable page id");
-
- project.hmiPages.front().controls.back().pageJump->targetPageId = "missing";
- require(!project.validate(), "a page jump must reject a missing target page");
-
- project = makeValidProject();
- project.initialHmiPageId = "missing";
- require(!project.validate(), "the initial HMI page id must resolve to a page");
-
- project = makeValidProject();
- HmiPage duplicate_name = settings;
- duplicate_name.name = project.hmiPages.front().name;
- project.hmiPages.push_back(duplicate_name);
- require(!project.validate(), "HMI page names must be unique");
-
- project = makeValidProject();
- ControlLogic disabled_draft;
- disabled_draft.id = "draft-logic";
- disabled_draft.name = "Draft logic";
- disabled_draft.enabled = false;
- disabled_draft.rungs.push_back(
- {"rung-1", "Draft network", {}, std::nullopt, std::nullopt});
- project.controlLogics.push_back(disabled_draft);
- require(project.validateForRunning(),
- "a disabled draft logic must not block offline running");
-
- project.controlLogics.back().name = project.controlLogics.front().name;
- require(!project.validate(), "control logic names must be unique");
-
- project = makeValidProject();
- project.hmiPages.front().controls.front().type =
- static_cast<HmiControlType>(99);
- project.hmiPages.front().controls.front().binding.reset();
- require(!project.validate(), "unknown HMI control types must be rejected");
- }
-
- void testLogicNodeConfigurationBoundaries()
- {
- // 触点只能绑定 M 区,数值比较只能绑定 D 区
- LogicNode contact;
- contact.id = "contact";
- contact.config = ContactNodeConfig{
- RegisterAddress{RegisterArea::M, 0},
- ContactMode::NormallyOpen};
- require(contact.validate(), "contact node bound to M address must be valid");
-
- contact.config = ContactNodeConfig{
- RegisterAddress{RegisterArea::D, 0},
- ContactMode::NormallyOpen};
- require(!contact.validate(), "contact node bound to D address must be rejected");
-
- LogicNode comparison;
- comparison.id = "comparison";
- comparison.config = CompareNodeConfig{
- RegisterAddress{RegisterArea::D, 0},
- ComparisonOperator::GreaterThan,
- static_cast<std::int16_t>(100)};
- require(comparison.validate(), "comparison node bound to D address must be valid");
- }
-
- void testTimerAndCommentBoundaries()
- {
- LogicNode edge;
- edge.id = "edge";
- edge.config = EdgeContactNodeConfig{
- RegisterAddress{RegisterArea::M, 0}, EdgeMode::Rising};
- require(edge.validate(), "a valid rising edge contact must pass validation");
-
- LogicNode timer_contact;
- timer_contact.id = "timer-contact";
- timer_contact.config = TimerContactNodeConfig{
- TimerAddress{4000}, ContactMode::NormallyOpen};
- require(timer_contact.validate(), "T4000 must be a valid timer contact");
- timer_contact.config = TimerContactNodeConfig{
- TimerAddress{-1}, ContactMode::NormallyOpen};
- require(!timer_contact.validate(), "a negative T address must be rejected");
-
- LogicNode ton;
- ton.id = "ton";
- ton.config = TonNodeConfig{TimerAddress{0}, TonNodeConfig::kMinimumPresetMs};
- require(ton.validate(), "the minimum TON preset must be valid");
- ton.config = TonNodeConfig{TimerAddress{0}, 0};
- require(!ton.validate(), "a zero TON preset must be rejected");
- ton.config = TonNodeConfig{
- TimerAddress{0}, TonNodeConfig::kMaximumPresetMs + 1};
- require(!ton.validate(), "an oversized TON preset must be rejected");
-
- RegisterComment comment{RegisterAddress{RegisterArea::M, 0}, "启动按钮"};
- require(comment.validate(), "a nonblank register comment must be valid");
- comment.text = " \t";
- require(!comment.validate(), "a blank register comment must be rejected");
-
- Project project = makeValidProject();
- project.registerComments = {
- {RegisterAddress{RegisterArea::M, 0}, "启动按钮"},
- {RegisterAddress{RegisterArea::M, 0}, "重复地址"}};
- require(!project.validate(), "duplicate register comments must be rejected");
- }
-
- LadderRung makeTimerRung(
- const std::string &rung_id,
- const std::string &condition_id,
- const std::string &output_id,
- int timer_index,
- int preset_ms)
- {
- LogicNode condition;
- condition.id = condition_id;
- condition.config = ContactNodeConfig{
- RegisterAddress{RegisterArea::M, timer_index}, ContactMode::NormallyOpen};
- LogicNode output;
- output.id = output_id;
- output.config = TonNodeConfig{TimerAddress{timer_index}, preset_ms};
- LadderRung rung;
- rung.id = rung_id;
- rung.name = rung_id;
- rung.condition = ConditionExpression::fromNode(condition);
- rung.output = output;
- return rung;
- }
-
- void testTimerReferencesForRunning()
- {
- ControlLogic valid;
- valid.id = "timer-valid";
- valid.name = "Timer valid";
- valid.rungs.push_back(makeTimerRung("rung-0", "input-0", "ton-0", 0, 100));
- require(validateTimerReferencesForRunning({valid}),
- "a timer contact-free TON network must pass timer reference validation");
-
- ControlLogic duplicate = valid;
- duplicate.id = "timer-duplicate";
- duplicate.name = "Timer duplicate";
- duplicate.rungs.front().output->id = "ton-duplicate";
- require(!validateTimerReferencesForRunning({valid, duplicate}),
- "the same T must not have two enabled TON drivers");
-
- ControlLogic missing;
- missing.id = "timer-missing";
- missing.name = "Timer missing";
- LogicNode contact;
- contact.id = "missing-contact";
- contact.config = TimerContactNodeConfig{
- TimerAddress{7}, ContactMode::NormallyOpen};
- LogicNode output;
- output.id = "missing-coil";
- output.config = CoilNodeConfig{
- RegisterAddress{RegisterArea::M, 7}, CoilMode::Normal};
- LadderRung missing_rung;
- missing_rung.id = "missing-rung";
- missing_rung.name = "Missing rung";
- missing_rung.condition = ConditionExpression::fromNode(contact);
- missing_rung.output = output;
- missing.rungs.push_back(missing_rung);
- require(!validateTimerReferencesForRunning({missing}),
- "a T contact without an enabled TON driver must be rejected");
-
- missing.enabled = false;
- require(validateTimerReferencesForRunning({missing}),
- "disabled timer drafts must not block runtime timer validation");
- }
-
- LadderRung makeCounterRung(
- const std::string &rung_id,
- const std::string &output_id,
- int counter_index)
- {
- LogicNode condition;
- condition.id = rung_id + "-input";
- condition.config = ContactNodeConfig{
- RegisterAddress{RegisterArea::M, counter_index},
- ContactMode::NormallyOpen};
- LogicNode output;
- output.id = output_id;
- output.config = CounterNodeConfig{
- CounterAddress{counter_index},
- CounterMode::Up,
- RegisterAddress{RegisterArea::D, counter_index},
- WordOperand{
- WordOperandKind::Constant,
- RegisterAddress{RegisterArea::D, 0},
- 10},
- RegisterAddress{RegisterArea::M, counter_index + 1}};
- LadderRung rung;
- rung.id = rung_id;
- rung.name = rung_id;
- rung.condition = ConditionExpression::fromNode(condition);
- rung.output = output;
- return rung;
- }
-
- void testCounterAndDataInstructionBoundaries()
- {
- require(CounterAddress{0}.isValid() && CounterAddress{4000}.isValid(),
- "C0 and C4000 must be valid counter resources");
- require(!CounterAddress{-1}.isValid() && !CounterAddress{4001}.isValid(),
- "counter resources outside 0 through 4000 must be rejected");
-
- LogicNode counter;
- counter.id = "counter";
- counter.config = CounterNodeConfig{
- CounterAddress{0},
- CounterMode::Up,
- RegisterAddress{RegisterArea::D, 10},
- WordOperand{
- WordOperandKind::Register,
- RegisterAddress{RegisterArea::D, 11},
- 0},
- RegisterAddress{RegisterArea::M, 12}};
- require(counter.validate(),
- "a counter with C identity and external M/D addresses must be valid");
-
- CounterNodeConfig invalid_counter = std::get<CounterNodeConfig>(counter.config);
- invalid_counter.currentValueAddress = RegisterAddress{RegisterArea::M, 10};
- counter.config = invalid_counter;
- require(!counter.validate(), "counter CV must reject M addresses");
-
- LogicNode move;
- move.id = "move";
- move.config = MoveNodeConfig{
- WordOperand{
- WordOperandKind::Constant,
- RegisterAddress{RegisterArea::D, 0},
- -100},
- RegisterAddress{RegisterArea::D, 20}};
- require(move.validate() && move.isOutput(),
- "MOVE with a constant source and D destination must be a valid output");
-
- LogicNode add;
- add.id = "add";
- add.config = ArithmeticNodeConfig{
- ArithmeticOperation::Add,
- WordOperand{
- WordOperandKind::Register,
- RegisterAddress{RegisterArea::D, 20},
- 0},
- WordOperand{
- WordOperandKind::Constant,
- RegisterAddress{RegisterArea::D, 0},
- 1},
- RegisterAddress{RegisterArea::D, 20}};
- require(add.validate() && add.isOutput(),
- "ADD must allow the same D register as source and destination");
-
- ControlLogic valid;
- valid.id = "counter-valid";
- valid.name = "Counter valid";
- valid.rungs.push_back(makeCounterRung("counter-rung", "ctu-0", 0));
- require(validateCounterReferencesForRunning({valid}),
- "a counter output without contacts must pass reference validation");
-
- ControlLogic duplicate = valid;
- duplicate.id = "counter-duplicate";
- duplicate.name = "Counter duplicate";
- duplicate.rungs.front().output->id = "ctu-duplicate";
- require(!validateCounterReferencesForRunning({valid, duplicate}),
- "the same C resource must not have multiple enabled drivers");
-
- ControlLogic missing;
- missing.id = "counter-missing";
- missing.name = "Counter missing";
- LogicNode missing_contact;
- missing_contact.id = "missing-counter-contact";
- missing_contact.config = CounterContactNodeConfig{
- CounterAddress{7}, ContactMode::NormallyOpen};
- LogicNode output;
- output.id = "missing-counter-coil";
- output.config = CoilNodeConfig{
- RegisterAddress{RegisterArea::M, 7}, CoilMode::Normal};
- LadderRung missing_rung;
- missing_rung.id = "missing-counter-rung";
- missing_rung.name = "Missing counter rung";
- missing_rung.condition = ConditionExpression::fromNode(missing_contact);
- missing_rung.output = output;
- missing.rungs.push_back(missing_rung);
- require(!validateCounterReferencesForRunning({missing}),
- "a C contact without an enabled counter driver must be rejected");
- }
-
- void testLadderLogicBoundaries()
- {
- LogicNode stop;
- stop.id = "stop";
- stop.config = ContactNodeConfig{
- RegisterAddress{RegisterArea::M, 1},
- ContactMode::NormallyClosed};
-
- LogicNode start;
- start.id = "start";
- start.config = ContactNodeConfig{
- RegisterAddress{RegisterArea::M, 0},
- ContactMode::NormallyOpen};
-
- LogicNode run_contact;
- run_contact.id = "run-contact";
- run_contact.config = ContactNodeConfig{
- RegisterAddress{RegisterArea::M, 1},
- ContactMode::NormallyOpen};
-
- LogicNode coil;
- coil.id = "run-coil";
- coil.config = CoilNodeConfig{
- RegisterAddress{RegisterArea::M, 1},
- CoilMode::Normal};
-
- ControlLogic logic;
- logic.id = "hold-logic";
- logic.name = "Hold logic";
- ConditionExpression start_parallel;
- start_parallel.id = "parallel-start";
- start_parallel.kind = ConditionExpressionKind::Parallel;
- start_parallel.children = {
- ConditionExpression::fromNode(start),
- ConditionExpression::fromNode(run_contact)};
- ConditionExpression root;
- root.id = "series-root";
- root.kind = ConditionExpressionKind::Series;
- root.children = {
- ConditionExpression::fromNode(stop),
- start_parallel};
- LadderRung rung;
- rung.id = "rung-1";
- rung.name = "Self hold";
- rung.condition = root;
- rung.output = coil;
- logic.rungs.push_back(rung);
- require(logic.validate(), "stop AND (start OR run) self-hold ladder must be valid");
-
- logic.rungs.front().condition->children.front() =
- ConditionExpression::fromNode(coil);
- require(!logic.validate(), "a ladder condition expression must reject coils");
- logic.rungs.front().condition = root;
- logic.rungs.front().output = start;
- require(!logic.validate(), "a ladder output must be a coil");
-
- logic.rungs.front().output.reset();
- require(logic.validate(), "incomplete ladder may remain in an editable draft");
- require(!logic.validateForRunning(),
- "conditions without an output must block runtime validation");
-
- LadderRung empty_rung{
- "rung-empty", "Empty network", {}, std::nullopt, std::nullopt};
- require(empty_rung.validate(), "an empty editing network must be valid");
-
- empty_rung.output = coil;
- require(empty_rung.validate(), "output-only network may remain in an editable draft");
- require(!empty_rung.validateForRunning(),
- "an output without conditions must block runtime validation");
-
- logic.rungs.front().output = coil;
- logic.rungs.front().condition = root;
- logic.rungs.front().condition->children.at(1).children.at(1).node->id = start.id;
- require(!logic.validate(), "logic node ids must be unique");
-
- ConditionExpression nested_parallel;
- nested_parallel.id = "parallel-nested";
- nested_parallel.kind = ConditionExpressionKind::Parallel;
- nested_parallel.children = {
- ConditionExpression::fromNode(start),
- root};
- require(nested_parallel.validate(),
- "nested series and parallel expressions must be valid");
- }
-
- void testModelsValidateBindingsAndIdentifiers()
- {
- // 聚合验证必须拒绝错误绑定、重复标识和越界控件
- Project project = makeValidProject();
- require(project.validate(), "valid project model must pass validation");
-
- project.hmiPages.front().controls.front().binding =
- RegisterAddress{RegisterArea::D, 0};
- require(!project.validate(), "button bound to D area must be rejected");
-
- project = makeValidProject();
- project.hmiPages.push_back(project.hmiPages.front());
- require(!project.validate(), "duplicate HMI page id must be rejected");
-
- project = makeValidProject();
- project.hmiPages.front().controls.front().bounds.x = -1;
- require(!project.validate(), "controls outside the page must be rejected");
-
- project = makeValidProject();
- project.hmiPages.front().controls.front().bounds.width = 801;
- require(!project.validate(), "controls wider than the page must be rejected");
-
- project = makeValidProject();
- project.hmiPages.front().controls.front().properties.emplace("", "value");
- require(!project.validate(), "empty HMI property names must be rejected");
-
- project = makeValidProject();
- project.hmiPages.front().controls.front().binding.reset();
- require(project.validate(), "unbound HMI control must be accepted in a draft");
- require(!project.validateForRunning(),
- "unbound HMI control must block runtime validation");
-
- project = makeValidProject();
- project.controlLogics.front().rungs.front().output->configured = false;
- require(project.validate(), "unconfigured ladder node must be accepted in a draft");
- require(!project.validateForRunning(),
- "unconfigured ladder node must block runtime validation");
- }
-
- void testRuntimeStateBoundaries()
- {
- // 运行模式测试覆盖离线和真机的互斥及 PLC 首读前置条件
- RuntimeState state;
- require(state.policy().allowsProjectEditing, "editing mode must allow project editing");
- require(state.enterOfflineRunning().succeeded, "editing may enter offline running");
- require(state.policy().usesVirtualRegisters, "offline mode must use virtual registers");
- require(state.policy().runsLogicExecutor, "offline mode must run logic executor");
- require(state.enterOnlineRunning(true).error
- == ModeTransitionError::MustReturnToEditing,
- "offline mode must not directly enter online mode");
-
- require(state.enterEditing().succeeded, "offline mode may return to editing");
- require(state.enterOnlineRunning(false).error
- == ModeTransitionError::InitialPlcReadRequired,
- "online mode must require an initial PLC read");
- require(state.enterOnlineRunning(true).succeeded,
- "editing may enter online mode after initial PLC read");
- require(!state.policy().runsLogicExecutor,
- "online mode must keep the software logic executor stopped");
- require(state.policy().usesPlcRegisters, "online mode must use PLC registers");
- }
-
- } // namespace
-
- int main()
- {
- try
- {
- // 每个测试函数独立覆盖一个领域边界,首个异常即终止测试进程
- testRegisterAddressBoundaries();
- testRegisterAddressParsing();
- testRegisterRepositorySeparatesAreas();
- testHmiControlRegistryCompleteness();
- testLogicNodeConfigurationBoundaries();
- testTimerAndCommentBoundaries();
- testTimerReferencesForRunning();
- testCounterAndDataInstructionBoundaries();
- testLadderLogicBoundaries();
- testModelsValidateBindingsAndIdentifiers();
- testMultiPageAndLogicDomainRules();
- testRuntimeStateBoundaries();
- }
- catch (const std::exception &error)
- {
- std::cerr << "domain tests failed: " << error.what() << '\n';
- return 1;
- }
-
- std::cout << "domain tests passed\n";
- return 0;
- }
|