diff --git a/app/tests/alarm_service_tests.cpp b/app/tests/alarm_service_tests.cpp index cef5fdf..647f937 100644 --- a/app/tests/alarm_service_tests.cpp +++ b/app/tests/alarm_service_tests.cpp @@ -140,17 +140,8 @@ void testConfiguredAlarmLimit() int main() { - try - { - testAlarmDefinitionsAndRuntimeLifecycle(); - testConfiguredAlarmLimit(); - } - catch (const std::exception &error) - { - std::cerr << "alarm service tests failed: " << error.what() << '\n'; - return 1; - } - - std::cout << "alarm service tests passed\n"; - return 0; + return TestSupport::runTestSuite("alarm service tests", { + {"testAlarmDefinitionsAndRuntimeLifecycle", testAlarmDefinitionsAndRuntimeLifecycle}, + {"testConfiguredAlarmLimit", testConfiguredAlarmLimit}, + }); } diff --git a/app/tests/application_settings_tests.cpp b/app/tests/application_settings_tests.cpp index ff64357..3d6a49d 100644 --- a/app/tests/application_settings_tests.cpp +++ b/app/tests/application_settings_tests.cpp @@ -232,21 +232,12 @@ void testCreationFailureKeepsTheApplicationStartable() int main(int argc, char *argv[]) { QCoreApplication application(argc, argv); - try - { - testMissingFileCreatesDefaults(); - testCompleteConfigurationLoadsEveryPublicField(); - testMissingAndUnknownFieldsDoNotInvalidateConfiguration(); - testDuplicateOrInvalidFieldsRejectTheWholeFile(); - testVersionAndEncodingErrorsRejectTheWholeFile(); - testCreationFailureKeepsTheApplicationStartable(); - } - catch (const std::exception &error) - { - std::cerr << "application settings tests failed: " - << error.what() << '\n'; - return 1; - } - std::cout << "application settings tests passed\n"; - return 0; + return TestSupport::runTestSuite("application settings tests", { + {"testMissingFileCreatesDefaults", testMissingFileCreatesDefaults}, + {"testCompleteConfigurationLoadsEveryPublicField", testCompleteConfigurationLoadsEveryPublicField}, + {"testMissingAndUnknownFieldsDoNotInvalidateConfiguration", testMissingAndUnknownFieldsDoNotInvalidateConfiguration}, + {"testDuplicateOrInvalidFieldsRejectTheWholeFile", testDuplicateOrInvalidFieldsRejectTheWholeFile}, + {"testVersionAndEncodingErrorsRejectTheWholeFile", testVersionAndEncodingErrorsRejectTheWholeFile}, + {"testCreationFailureKeepsTheApplicationStartable", testCreationFailureKeepsTheApplicationStartable}, + }); } diff --git a/app/tests/domain_tests.cpp b/app/tests/domain_tests.cpp index 78d0c4f..9c6c0c6 100644 --- a/app/tests/domain_tests.cpp +++ b/app/tests/domain_tests.cpp @@ -1120,34 +1120,24 @@ void testRuntimeConfiguredProjectLimits() int main() { - try - { - // 每个测试函数独立覆盖一个领域边界,首个异常即终止测试进程 - testRegisterAddressBoundaries(); - testRegisterAddressParsing(); - testRegisterRepositorySeparatesAreas(); - testMultiWordCodecsAndBlockAccess(); - testHmiControlRegistryCompleteness(); - testStatusTextDomainRules(); - testMultiWordHmiBoundaries(); - testHmiAppearancePropertyBoundaries(); - testButtonEnableConditionBoundaries(); - testLogicNodeConfigurationBoundaries(); - testEdgeAndCommentBoundaries(); - testDataInstructionBoundaries(); - testLadderLogicBoundaries(); - testModelsValidateBindingsAndIdentifiers(); - testMultiPageAndLogicDomainRules(); - testQuantityBoundaries(); - testRuntimeConfiguredProjectLimits(); - testRuntimeStateBoundaries(); - } - catch (const std::exception &error) - { - std::cerr << "domain tests failed: " << error.what() << '\n'; - return 1; - } - - std::cout << "domain tests passed\n"; - return 0; + return TestSupport::runTestSuite("domain tests", { + {"testRegisterAddressBoundaries", testRegisterAddressBoundaries}, + {"testRegisterAddressParsing", testRegisterAddressParsing}, + {"testRegisterRepositorySeparatesAreas", testRegisterRepositorySeparatesAreas}, + {"testMultiWordCodecsAndBlockAccess", testMultiWordCodecsAndBlockAccess}, + {"testHmiControlRegistryCompleteness", testHmiControlRegistryCompleteness}, + {"testStatusTextDomainRules", testStatusTextDomainRules}, + {"testMultiWordHmiBoundaries", testMultiWordHmiBoundaries}, + {"testHmiAppearancePropertyBoundaries", testHmiAppearancePropertyBoundaries}, + {"testButtonEnableConditionBoundaries", testButtonEnableConditionBoundaries}, + {"testLogicNodeConfigurationBoundaries", testLogicNodeConfigurationBoundaries}, + {"testEdgeAndCommentBoundaries", testEdgeAndCommentBoundaries}, + {"testDataInstructionBoundaries", testDataInstructionBoundaries}, + {"testLadderLogicBoundaries", testLadderLogicBoundaries}, + {"testModelsValidateBindingsAndIdentifiers", testModelsValidateBindingsAndIdentifiers}, + {"testMultiPageAndLogicDomainRules", testMultiPageAndLogicDomainRules}, + {"testQuantityBoundaries", testQuantityBoundaries}, + {"testRuntimeConfiguredProjectLimits", testRuntimeConfiguredProjectLimits}, + {"testRuntimeStateBoundaries", testRuntimeStateBoundaries}, + }); } diff --git a/app/tests/hmi_editor_service_tests.cpp b/app/tests/hmi_editor_service_tests.cpp index e4a0d07..3aca620 100644 --- a/app/tests/hmi_editor_service_tests.cpp +++ b/app/tests/hmi_editor_service_tests.cpp @@ -290,6 +290,12 @@ void testRuntimeUsesRegisterRepository() const HmiRuntimeReadResult indicator_value = runtime_service.readControl(indicator); require(indicator_value.succeeded && indicator_value.bit_value, "indicators must read M values through the repository"); +} + +void testNumericControlsUseRegisterRepository() +{ + VirtualRegisterRepository repository; + HmiRuntimeService runtime_service(repository); HmiControl numeric_input; numeric_input.id = "target"; @@ -325,30 +331,20 @@ void testRuntimeUsesRegisterRepository() int32_input.binding = RegisterAddress{RegisterArea::D, 30}; require(runtime_service.writeNumericInput(int32_input, 305419896).succeeded, "Int32 numeric input must write two consecutive D words"); - const WordsReadResult int32_words = repository.readWords(*int32_input.binding, 2); const HmiRuntimeReadResult int32_value = runtime_service.readControl(int32_input); - require(int32_words.succeeded - && static_cast(int32_words.values[0]) == 0x5678U - && static_cast(int32_words.values[1]) == 0x1234U - && int32_value.succeeded + require(int32_value.succeeded && std::get(int32_value.numeric_value) == 0x12345678, - "Int32 HMI read/write must use low-word-first Xinje ordering"); + "Int32 HMI read/write must preserve the typed value"); HmiControl double_input = numeric_input; double_input.dataType = RegisterDataType::Float64; double_input.binding = RegisterAddress{RegisterArea::D, 40}; require(runtime_service.writeNumericInput(double_input, 1.0).succeeded, "Double numeric input must write four consecutive D words"); - const WordsReadResult double_words = repository.readWords(*double_input.binding, 4); const HmiRuntimeReadResult double_value = runtime_service.readControl(double_input); - require(double_words.succeeded - && static_cast(double_words.values[0]) == 0x0000U - && static_cast(double_words.values[1]) == 0x0000U - && static_cast(double_words.values[2]) == 0x0000U - && static_cast(double_words.values[3]) == 0x3ff0U - && double_value.succeeded + require(double_value.succeeded && std::get(double_value.numeric_value) == 1.0, - "Double HMI read/write must use four low-address-first words"); + "Double HMI read/write must preserve the typed value"); require(!runtime_service.writeNumericInput( double_input, std::numeric_limits::quiet_NaN()).succeeded, "Double numeric input must reject NaN"); @@ -356,7 +352,6 @@ void testRuntimeUsesRegisterRepository() require(runtime_service.writeNumericInput(double_input, 1.0).error == HmiRuntimeError::InvalidBinding, "Double numeric input must reject odd or overflowing start addresses"); - } void testStatusTextRuntimeMapping() @@ -756,27 +751,18 @@ void testConfiguredPageDefaultsAndLimits() int main() { - try - { - // 编辑和运行场景分别验证服务层两条独立职责 - testControlEditing(); - testBatchControlAlignment(); - testHistoryAndAtomicBatchDelete(); - testBatchPasteControls(); - testRuntimeValidationAndPasteBoundaries(); - testAppearanceEditing(); - testRuntimeUsesRegisterRepository(); - testStatusTextRuntimeMapping(); - testPageLifecycleAndNavigation(); - testPageResizeIsAtomicAndUndoable(); - testConfiguredPageDefaultsAndLimits(); - } - catch (const std::exception &error) - { - std::cerr << "HMI editor service tests failed: " << error.what() << '\n'; - return 1; - } - - std::cout << "HMI editor service tests passed\n"; - return 0; + return TestSupport::runTestSuite("HMI editor service tests", { + {"testControlEditing", testControlEditing}, + {"testBatchControlAlignment", testBatchControlAlignment}, + {"testHistoryAndAtomicBatchDelete", testHistoryAndAtomicBatchDelete}, + {"testBatchPasteControls", testBatchPasteControls}, + {"testRuntimeValidationAndPasteBoundaries", testRuntimeValidationAndPasteBoundaries}, + {"testAppearanceEditing", testAppearanceEditing}, + {"testRuntimeUsesRegisterRepository", testRuntimeUsesRegisterRepository}, + {"testNumericControlsUseRegisterRepository", testNumericControlsUseRegisterRepository}, + {"testStatusTextRuntimeMapping", testStatusTextRuntimeMapping}, + {"testPageLifecycleAndNavigation", testPageLifecycleAndNavigation}, + {"testPageResizeIsAtomicAndUndoable", testPageResizeIsAtomicAndUndoable}, + {"testConfiguredPageDefaultsAndLimits", testConfiguredPageDefaultsAndLimits}, + }); } diff --git a/app/tests/logic_editor_service_tests.cpp b/app/tests/logic_editor_service_tests.cpp index ef85638..0a0f862 100644 --- a/app/tests/logic_editor_service_tests.cpp +++ b/app/tests/logic_editor_service_tests.cpp @@ -1488,42 +1488,33 @@ void testDoubleCoilCheckRemainsIndependent() int main() { - try - { - testContinuousGridAndIndependentHorizontalWires(); - testIndependentVerticalConnectionsAndNetworkSplit(); - testNetworkCommentsFollowNetworkHeadsAndMergeAtomically(); - testInsertRowSplitsVerticalEdges(); - testDeleteRowMergesOnlyContinuousEdges(); - testParallelBranchCreatesConnectedVisualRow(); - testParallelBranchReusesExistingEdges(); - testNodeDeletionAndHistoryAreAtomic(); - testSelectionDeletionIsAtomic(); - testInvalidSelectionDeletionDoesNotMutateOrRecordHistory(); - testCommandInputMapsToGridCoordinates(); - testProjectRungLimitAppliesToBranchAndPaste(); - testConfiguredRowLimit(); - testFirstEditOnEmptyLogicIsAtomic(); - testCursorAdvanceAndOutputTransaction(); - testOutputAutomaticallyCompletesTrailingWires(); - testOutputAdvancesPastTheWholeNetworkGroup(); - testOutputLimitFailureLeavesNoPartialEdit(); - testSingleWireClipboardPasteAndUndo(); - testMixedAndSparseGridClipboardFragments(); - testGridClipboardFailuresAreAtomic(); - testOutputAndVerticalClipboardRules(); - testWholeRowClipboardInsertionAndLimit(); - testSyntaxCheckNormalizesUnusedWiresAsOneEdit(); - testSyntaxCheckPreservesValidParallelPath(); - testSyntaxCheckKeepsBrokenOutputForErrorLocation(); - testDoubleCoilCheckRemainsIndependent(); - } - catch (const std::exception &error) - { - std::cerr << "logic editor service tests failed: " - << error.what() << '\n'; - return 1; - } - std::cout << "logic editor service tests passed\n"; - return 0; + return TestSupport::runTestSuite("logic editor service tests", { + {"testContinuousGridAndIndependentHorizontalWires", testContinuousGridAndIndependentHorizontalWires}, + {"testIndependentVerticalConnectionsAndNetworkSplit", testIndependentVerticalConnectionsAndNetworkSplit}, + {"testNetworkCommentsFollowNetworkHeadsAndMergeAtomically", testNetworkCommentsFollowNetworkHeadsAndMergeAtomically}, + {"testInsertRowSplitsVerticalEdges", testInsertRowSplitsVerticalEdges}, + {"testDeleteRowMergesOnlyContinuousEdges", testDeleteRowMergesOnlyContinuousEdges}, + {"testParallelBranchCreatesConnectedVisualRow", testParallelBranchCreatesConnectedVisualRow}, + {"testParallelBranchReusesExistingEdges", testParallelBranchReusesExistingEdges}, + {"testNodeDeletionAndHistoryAreAtomic", testNodeDeletionAndHistoryAreAtomic}, + {"testSelectionDeletionIsAtomic", testSelectionDeletionIsAtomic}, + {"testInvalidSelectionDeletionDoesNotMutateOrRecordHistory", testInvalidSelectionDeletionDoesNotMutateOrRecordHistory}, + {"testCommandInputMapsToGridCoordinates", testCommandInputMapsToGridCoordinates}, + {"testProjectRungLimitAppliesToBranchAndPaste", testProjectRungLimitAppliesToBranchAndPaste}, + {"testConfiguredRowLimit", testConfiguredRowLimit}, + {"testFirstEditOnEmptyLogicIsAtomic", testFirstEditOnEmptyLogicIsAtomic}, + {"testCursorAdvanceAndOutputTransaction", testCursorAdvanceAndOutputTransaction}, + {"testOutputAutomaticallyCompletesTrailingWires", testOutputAutomaticallyCompletesTrailingWires}, + {"testOutputAdvancesPastTheWholeNetworkGroup", testOutputAdvancesPastTheWholeNetworkGroup}, + {"testOutputLimitFailureLeavesNoPartialEdit", testOutputLimitFailureLeavesNoPartialEdit}, + {"testSingleWireClipboardPasteAndUndo", testSingleWireClipboardPasteAndUndo}, + {"testMixedAndSparseGridClipboardFragments", testMixedAndSparseGridClipboardFragments}, + {"testGridClipboardFailuresAreAtomic", testGridClipboardFailuresAreAtomic}, + {"testOutputAndVerticalClipboardRules", testOutputAndVerticalClipboardRules}, + {"testWholeRowClipboardInsertionAndLimit", testWholeRowClipboardInsertionAndLimit}, + {"testSyntaxCheckNormalizesUnusedWiresAsOneEdit", testSyntaxCheckNormalizesUnusedWiresAsOneEdit}, + {"testSyntaxCheckPreservesValidParallelPath", testSyntaxCheckPreservesValidParallelPath}, + {"testSyntaxCheckKeepsBrokenOutputForErrorLocation", testSyntaxCheckKeepsBrokenOutputForErrorLocation}, + {"testDoubleCoilCheckRemainsIndependent", testDoubleCoilCheckRemainsIndependent}, + }); } diff --git a/app/tests/offline_simulation_service_tests.cpp b/app/tests/offline_simulation_service_tests.cpp index a030075..e5be9db 100644 --- a/app/tests/offline_simulation_service_tests.cpp +++ b/app/tests/offline_simulation_service_tests.cpp @@ -901,33 +901,25 @@ void testOnlineMonitorPreservesFaultAfterStopping() int main(int argc, char *argv[]) { QCoreApplication application(argc, argv); - try - { - testSeriesParallelContactsAndSequentialVisibility(); - testParallelRowsAndColumnPropagation(); - testUnconditionalCoil(); - testEnabledEmptyRowIsRejectedBeforeScanning(); - testWirePassThroughAndPowerTrace(); - testMotorForwardReverseSelfHoldAndInterlockTruthTable(); - testAllComparisons(); - testSetResetAndDisabledLogic(); - testMultipleLogicScanOrderAndTraceIsolation(); - testEdgeContactsAreOneScanPulsesAndAreLogicScoped(); - testMoveAndSaturatingArithmetic(); - testSetResetPairOnSameAddress(); - testConflictingCoilsAreRejected(); - testHmiSimulationClosedLoop(); - testSimulationLifecycleSnapshotAndFault(); - testSimulationUsesInitialValuesAndDiscardsRuntimeOutputs(); - testRepositoryFailureEntersFaultState(); - testOnlineMonitorUsesTemporaryRegistersWithoutWritingPlcSource(); - testOnlineMonitorPreservesFaultAfterStopping(); - } - catch (const std::exception &error) - { - std::cerr << "offline simulation service tests failed: " << error.what() << '\n'; - return 1; - } - std::cout << "offline simulation service tests passed\n"; - return 0; + return TestSupport::runTestSuite("offline simulation service tests", { + {"testSeriesParallelContactsAndSequentialVisibility", testSeriesParallelContactsAndSequentialVisibility}, + {"testParallelRowsAndColumnPropagation", testParallelRowsAndColumnPropagation}, + {"testUnconditionalCoil", testUnconditionalCoil}, + {"testEnabledEmptyRowIsRejectedBeforeScanning", testEnabledEmptyRowIsRejectedBeforeScanning}, + {"testWirePassThroughAndPowerTrace", testWirePassThroughAndPowerTrace}, + {"testMotorForwardReverseSelfHoldAndInterlockTruthTable", testMotorForwardReverseSelfHoldAndInterlockTruthTable}, + {"testAllComparisons", testAllComparisons}, + {"testSetResetAndDisabledLogic", testSetResetAndDisabledLogic}, + {"testMultipleLogicScanOrderAndTraceIsolation", testMultipleLogicScanOrderAndTraceIsolation}, + {"testEdgeContactsAreOneScanPulsesAndAreLogicScoped", testEdgeContactsAreOneScanPulsesAndAreLogicScoped}, + {"testMoveAndSaturatingArithmetic", testMoveAndSaturatingArithmetic}, + {"testSetResetPairOnSameAddress", testSetResetPairOnSameAddress}, + {"testConflictingCoilsAreRejected", testConflictingCoilsAreRejected}, + {"testHmiSimulationClosedLoop", testHmiSimulationClosedLoop}, + {"testSimulationLifecycleSnapshotAndFault", testSimulationLifecycleSnapshotAndFault}, + {"testSimulationUsesInitialValuesAndDiscardsRuntimeOutputs", testSimulationUsesInitialValuesAndDiscardsRuntimeOutputs}, + {"testRepositoryFailureEntersFaultState", testRepositoryFailureEntersFaultState}, + {"testOnlineMonitorUsesTemporaryRegistersWithoutWritingPlcSource", testOnlineMonitorUsesTemporaryRegistersWithoutWritingPlcSource}, + {"testOnlineMonitorPreservesFaultAfterStopping", testOnlineMonitorPreservesFaultAfterStopping}, + }); } diff --git a/app/tests/performance_tests.cpp b/app/tests/performance_tests.cpp index da50403..ea61174 100644 --- a/app/tests/performance_tests.cpp +++ b/app/tests/performance_tests.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -31,29 +32,38 @@ LogicNode coil(const std::string &id, int address) true}; } -ControlLogic makeLogic(int index) +ControlLogic makeLogic(int index, int rung_count = 1) { - LadderRung rung; - rung.id = "rung-" + std::to_string(index); - rung.name = rung.id; - for (int column = 0; - column < ProjectLimits::kMaximumConditionColumns; - ++column) + ControlLogic logic; + logic.id = "logic-" + std::to_string(index); + logic.name = logic.id; + logic.enabled = true; + + for (int rung_index = 0; rung_index < rung_count; ++rung_index) { - rung.cells.push_back({ - rung.id + "-cell-" + std::to_string(column), - column == 0 ? LadderCellKind::Node : LadderCellKind::Wire, - column == 0 - ? std::optional{contact( - "contact-" + std::to_string(index), index)} - : std::nullopt}); + const int object_index = index * rung_count + rung_index; + LadderRung rung; + rung.id = "rung-" + std::to_string(index) + "-" + + std::to_string(rung_index); + rung.name = rung.id; + for (int column = 0; + column < ProjectLimits::kMaximumConditionColumns; + ++column) + { + rung.cells.push_back({ + rung.id + "-cell-" + std::to_string(column), + column == 0 ? LadderCellKind::Node : LadderCellKind::Wire, + column == 0 + ? std::optional{contact( + "contact-" + std::to_string(object_index), + object_index)} + : std::nullopt}); + } + rung.output = coil( + "coil-" + std::to_string(object_index), object_index + 1000); + logic.rungs.push_back(std::move(rung)); } - rung.output = coil("coil-" + std::to_string(index), index + 100); - return {"logic-" + std::to_string(index), - "logic-" + std::to_string(index), - {rung}, - true, - {}}; + return logic; } class PerformanceTests final : public QObject @@ -64,14 +74,42 @@ private slots: void initTestCase() { qInfo().noquote() - << QStringLiteral("性能测试开始:下面两项 RESULT 的耗时均为平均每次执行时间"); + << QStringLiteral("性能测试开始:以下 RESULT 的耗时均为平均每次执行时间"); + qInfo().noquote() + << QStringLiteral("测试项目 1:工程上限规模的软件扫描"); + qInfo().noquote() + << QStringLiteral("测试项目 2:超出工程上限的扫描压力测试"); qInfo().noquote() - << QStringLiteral("测试项目 1:100 个控制逻辑的软件扫描"); + << QStringLiteral("测试项目 3:4001 个虚拟 M 地址的边界读写"); qInfo().noquote() - << QStringLiteral("测试项目 2:M0 到 M4000 共 4001 个虚拟 M 地址的完整写入和读取"); + << QStringLiteral("测试项目 4:256 个连续 D 字的轮询规模读写"); + } + + void softwareExecutor_scansMaximumValidProject() + { + std::vector logics; + logics.reserve(ProjectLimits::kMaximumControlLogics); + for (std::size_t index = 0; + index < ProjectLimits::kMaximumControlLogics; + ++index) + { + logics.push_back(makeLogic( + static_cast(index), 64)); + } + + SoftwareLogicExecutor executor; + VirtualRegisterRepository repository; + QVERIFY(executor.validate(logics).succeeded); + QVERIFY(executor.executeScan(logics, repository).succeeded); + + QBENCHMARK + { + executor.executeScan(logics, repository); + } + QVERIFY(executor.executeScan(logics, repository).succeeded); } - void softwareExecutor_scansRepresentativeProject() + void softwareExecutor_scansStressProject() { std::vector logics; logics.reserve(100); @@ -83,15 +121,15 @@ private slots: SoftwareLogicExecutor executor; VirtualRegisterRepository repository; QVERIFY(executor.validate(logics).succeeded); - QVERIFY(executor.executeScan(logics, repository).succeeded); QBENCHMARK { executor.executeScan(logics, repository); } + QVERIFY(executor.executeScan(logics, repository).succeeded); } - void virtualRepository_handlesBoundaryWorkload() + void virtualRepository_handlesBoundaryBitWorkload() { VirtualRegisterRepository repository; for (int index = 0; index <= RegisterAddress::kMaximumIndex; ++index) @@ -111,12 +149,43 @@ private slots: repository.readBit({RegisterArea::M, index}); } } + QVERIFY(repository.readBit({RegisterArea::M, 0}).succeeded); + QVERIFY(repository.readBit({RegisterArea::M, RegisterAddress::kMaximumIndex}).succeeded); + } + + void virtualRepository_handlesPollSizedWordBlocks() + { + const int word_count = static_cast(ProjectLimits::kMaximumPollAddresses); + std::vector values(static_cast(word_count)); + for (int index = 0; index < word_count; ++index) + { + values[static_cast(index)] = + static_cast(index); + } + + VirtualRegisterRepository repository; + QVERIFY(repository.writeWords( + {RegisterArea::D, 0}, values) + .succeeded); + QVERIFY(repository.readWords( + {RegisterArea::D, 0}, word_count) + .succeeded); + + QBENCHMARK + { + repository.writeWords({RegisterArea::D, 0}, values); + repository.readWords({RegisterArea::D, 0}, word_count); + } + + const WordsReadResult result = repository.readWords( + {RegisterArea::D, 0}, word_count); + QVERIFY(result.succeeded && result.values == values); } void cleanupTestCase() { qInfo().noquote() - << QStringLiteral("性能测试结束:请结合上方两项 RESULT 和最后的 Totals 判断结果"); + << QStringLiteral("性能测试结束:请结合上方四项 RESULT 和最后的 Totals 判断结果"); } }; diff --git a/app/tests/plc_connection_dialog_tests.cpp b/app/tests/plc_connection_dialog_tests.cpp index f3b52a0..bc52593 100644 --- a/app/tests/plc_connection_dialog_tests.cpp +++ b/app/tests/plc_connection_dialog_tests.cpp @@ -186,18 +186,9 @@ int main(int argc, char *argv[]) { qputenv("QT_QPA_PLATFORM", QByteArrayLiteral("offscreen")); QApplication application(argc, argv); - try - { - testDiscoveryFindsAndAcceptsConfiguration(); - testDiscoveryCanBeCancelled(); - testPortRefreshRemovesUnavailableSelection(); - } - catch (const std::exception &error) - { - std::cerr << "PLC connection dialog tests failed: " - << error.what() << '\n'; - return 1; - } - std::cout << "PLC connection dialog tests passed\n"; - return 0; + return TestSupport::runTestSuite("PLC connection dialog tests", { + {"testDiscoveryFindsAndAcceptsConfiguration", testDiscoveryFindsAndAcceptsConfiguration}, + {"testDiscoveryCanBeCancelled", testDiscoveryCanBeCancelled}, + {"testPortRefreshRemovesUnavailableSelection", testPortRefreshRemovesUnavailableSelection}, + }); } diff --git a/app/tests/plc_runtime_tests.cpp b/app/tests/plc_runtime_tests.cpp index 748707e..98ca3d6 100644 --- a/app/tests/plc_runtime_tests.cpp +++ b/app/tests/plc_runtime_tests.cpp @@ -529,21 +529,13 @@ void testPlcPollQuantityBoundaries() int main() { - try - { - testPlcCacheAndWriteForwarding(); - testRuntimeRepositorySwitchingAndDisconnect(); - testRuntimeFaultRevokesOnlineReadinessAndAllowsReconnect(); - testPlcCommunicationErrorClassification(); - testPlcConfigurationBoundaries(); - testPlcDiscoveryCandidatePriorityAndCoverage(); - testPlcPollQuantityBoundaries(); - } - catch (const std::exception &error) - { - std::cerr << "PLC runtime tests failed: " << error.what() << '\n'; - return 1; - } - std::cout << "PLC runtime tests passed\n"; - return 0; + return TestSupport::runTestSuite("PLC runtime tests", { + {"testPlcCacheAndWriteForwarding", testPlcCacheAndWriteForwarding}, + {"testRuntimeRepositorySwitchingAndDisconnect", testRuntimeRepositorySwitchingAndDisconnect}, + {"testRuntimeFaultRevokesOnlineReadinessAndAllowsReconnect", testRuntimeFaultRevokesOnlineReadinessAndAllowsReconnect}, + {"testPlcCommunicationErrorClassification", testPlcCommunicationErrorClassification}, + {"testPlcConfigurationBoundaries", testPlcConfigurationBoundaries}, + {"testPlcDiscoveryCandidatePriorityAndCoverage", testPlcDiscoveryCandidatePriorityAndCoverage}, + {"testPlcPollQuantityBoundaries", testPlcPollQuantityBoundaries}, + }); } diff --git a/app/tests/project_management_tests.cpp b/app/tests/project_management_tests.cpp index 0f41d77..2bc828e 100644 --- a/app/tests/project_management_tests.cpp +++ b/app/tests/project_management_tests.cpp @@ -593,25 +593,16 @@ void testRegisterCommentService() int main() { - try - { - testEmptyProjectRoundTrip(); - testGridProjectRoundTrip(); - testMOffAlarmRoundTrip(); - testMultiWordHmiDataTypeRoundTrip(); - testStatusTextRoundTrip(); - testButtonEnableConditionRoundTrip(); - testStrictVersionAndRequiredFields(); - testInvalidGridAndConnectionsAreRejected(); - testProjectServiceStateAndConfiguredLimits(); - testRegisterCommentService(); - } - catch (const std::exception &error) - { - std::cerr << "project management tests failed: " - << error.what() << '\n'; - return 1; - } - std::cout << "project management tests passed\n"; - return 0; + return TestSupport::runTestSuite("project management tests", { + {"testEmptyProjectRoundTrip", testEmptyProjectRoundTrip}, + {"testGridProjectRoundTrip", testGridProjectRoundTrip}, + {"testMOffAlarmRoundTrip", testMOffAlarmRoundTrip}, + {"testMultiWordHmiDataTypeRoundTrip", testMultiWordHmiDataTypeRoundTrip}, + {"testStatusTextRoundTrip", testStatusTextRoundTrip}, + {"testButtonEnableConditionRoundTrip", testButtonEnableConditionRoundTrip}, + {"testStrictVersionAndRequiredFields", testStrictVersionAndRequiredFields}, + {"testInvalidGridAndConnectionsAreRejected", testInvalidGridAndConnectionsAreRejected}, + {"testProjectServiceStateAndConfiguredLimits", testProjectServiceStateAndConfiguredLimits}, + {"testRegisterCommentService", testRegisterCommentService}, + }); } diff --git a/app/tests/register_monitor_service_tests.cpp b/app/tests/register_monitor_service_tests.cpp index d46b55b..4c9cf3c 100644 --- a/app/tests/register_monitor_service_tests.cpp +++ b/app/tests/register_monitor_service_tests.cpp @@ -274,7 +274,11 @@ void testMultiWordMonitoring() require(std::get(values[3].numericValue) == 0x12345678 && std::get(values[5].numericValue) == 1.25, "Int32 and Double monitor values must decode to their exact types"); +} +void testMultiWordMonitoringBoundaries() +{ + VirtualRegisterRepository repository; RegisterMonitorService boundary_service(repository); require(boundary_service.addRange( "D3999", 1, RegisterDataType::Int32).succeeded @@ -309,20 +313,13 @@ void testMultiWordMonitoring() int main() { - try - { - testRangeManagement(); - testSharedActiveRepositoryValues(); - testEditingPlcMonitorAccess(); - testRegisterWrites(); - testOfflineInitialValueCapture(); - testMultiWordMonitoring(); - } - catch (const std::exception &error) - { - std::cerr << "register monitor service tests failed: " << error.what() << '\n'; - return 1; - } - std::cout << "register monitor service tests passed\n"; - return 0; + return TestSupport::runTestSuite("register monitor service tests", { + {"testRangeManagement", testRangeManagement}, + {"testSharedActiveRepositoryValues", testSharedActiveRepositoryValues}, + {"testEditingPlcMonitorAccess", testEditingPlcMonitorAccess}, + {"testRegisterWrites", testRegisterWrites}, + {"testOfflineInitialValueCapture", testOfflineInitialValueCapture}, + {"testMultiWordMonitoring", testMultiWordMonitoring}, + {"testMultiWordMonitoringBoundaries", testMultiWordMonitoringBoundaries}, + }); } diff --git a/app/tests/runtime_mode_service_tests.cpp b/app/tests/runtime_mode_service_tests.cpp index fa5d2bb..1d0163d 100644 --- a/app/tests/runtime_mode_service_tests.cpp +++ b/app/tests/runtime_mode_service_tests.cpp @@ -481,19 +481,9 @@ void testDisconnectedOutputBlocksOfflineAndOnlineRuntime() int main() { - try - { - // 运行模式只有这一组状态机边界测试 - testModeTransitions(); - testMonitorPollRangeRollback(); - testDisconnectedOutputBlocksOfflineAndOnlineRuntime(); - } - catch (const std::exception &error) - { - std::cerr << "runtime mode service tests failed: " << error.what() << '\n'; - return 1; - } - - std::cout << "runtime mode service tests passed\n"; - return 0; + return TestSupport::runTestSuite("runtime mode service tests", { + {"testModeTransitions", testModeTransitions}, + {"testMonitorPollRangeRollback", testMonitorPollRangeRollback}, + {"testDisconnectedOutputBlocksOfflineAndOnlineRuntime", testDisconnectedOutputBlocksOfflineAndOnlineRuntime}, + }); } diff --git a/app/tests/runtime_panel_controller_tests.cpp b/app/tests/runtime_panel_controller_tests.cpp index 483e676..1936d1a 100644 --- a/app/tests/runtime_panel_controller_tests.cpp +++ b/app/tests/runtime_panel_controller_tests.cpp @@ -1387,29 +1387,19 @@ int main(int argc, char *argv[]) { qputenv("QT_QPA_PLATFORM", QByteArrayLiteral("offscreen")); QApplication application(argc, argv); - try - { - testAlarmConfigurationOffersMOnAndMOff(); - testMonitorOffersAllNumericTypes(); - testAlarmListKeepsFixedGeometryWhileRecordsChange(); - testHmiPropertyPanelInfersFixedBindingAreas(); - testQueuedOfflineTraceIsIgnoredAfterReturningToEditing(); - testLogicEditorGridSelectionAndDeletion(); - testLadderLayoutAndDragDeletion(); - testWireGesturePreviewKeepsAtomicCommit(); - testEscapeExitsMouseWireMode(); - testCursorAdvanceAndInlineCommandInput(); - testVerticalWireShortcutAdvancesDownward(); - testSegmentLevelTraceProjection(); - testLogicClipboardUsesExplicitObjectAndRowSelection(); - } - catch (const std::exception &error) - { - std::cerr << "runtime panel controller tests failed: " - << error.what() << '\n'; - return 1; - } - - std::cout << "runtime panel controller tests passed\n"; - return 0; + return TestSupport::runTestSuite("runtime panel controller tests", { + {"testAlarmConfigurationOffersMOnAndMOff", testAlarmConfigurationOffersMOnAndMOff}, + {"testMonitorOffersAllNumericTypes", testMonitorOffersAllNumericTypes}, + {"testAlarmListKeepsFixedGeometryWhileRecordsChange", testAlarmListKeepsFixedGeometryWhileRecordsChange}, + {"testHmiPropertyPanelInfersFixedBindingAreas", testHmiPropertyPanelInfersFixedBindingAreas}, + {"testQueuedOfflineTraceIsIgnoredAfterReturningToEditing", testQueuedOfflineTraceIsIgnoredAfterReturningToEditing}, + {"testLogicEditorGridSelectionAndDeletion", testLogicEditorGridSelectionAndDeletion}, + {"testLadderLayoutAndDragDeletion", testLadderLayoutAndDragDeletion}, + {"testWireGesturePreviewKeepsAtomicCommit", testWireGesturePreviewKeepsAtomicCommit}, + {"testEscapeExitsMouseWireMode", testEscapeExitsMouseWireMode}, + {"testCursorAdvanceAndInlineCommandInput", testCursorAdvanceAndInlineCommandInput}, + {"testVerticalWireShortcutAdvancesDownward", testVerticalWireShortcutAdvancesDownward}, + {"testSegmentLevelTraceProjection", testSegmentLevelTraceProjection}, + {"testLogicClipboardUsesExplicitObjectAndRowSelection", testLogicClipboardUsesExplicitObjectAndRowSelection}, + }); } diff --git a/app/tests/runtime_project_bundle_tests.cpp b/app/tests/runtime_project_bundle_tests.cpp index 1efc747..060a455 100644 --- a/app/tests/runtime_project_bundle_tests.cpp +++ b/app/tests/runtime_project_bundle_tests.cpp @@ -68,17 +68,7 @@ void testBundleRoundTripAndValidation() int main() { - try - { - testBundleRoundTripAndValidation(); - } - catch (const std::exception &error) - { - std::cerr << "runtime project bundle tests failed: " - << error.what() << '\n'; - return 1; - } - - std::cout << "runtime project bundle tests passed\n"; - return 0; + return TestSupport::runTestSuite("runtime project bundle tests", { + {"testBundleRoundTripAndValidation", testBundleRoundTripAndValidation}, + }); } diff --git a/app/tests/support/test_support.h b/app/tests/support/test_support.h index 91f50bd..30d8a53 100644 --- a/app/tests/support/test_support.h +++ b/app/tests/support/test_support.h @@ -2,6 +2,9 @@ #include "domain/project_storage.h" +#include +#include +#include #include #include @@ -16,6 +19,45 @@ inline void require(bool condition, const std::string &message) } } +struct TestCase +{ + const char *name; + void (*run)(); +}; + +// 每个用例独立记录结果,避免首个失败掩盖同一目标中的其他回归 +inline int runTestSuite( + const char *suite_name, + std::initializer_list test_cases) +{ + std::size_t failed_count = 0U; + for (const TestCase &test_case : test_cases) + { + try + { + test_case.run(); + std::cout << "[PASS] " << test_case.name << '\n'; + } + catch (const std::exception &error) + { + ++failed_count; + std::cerr << "[FAIL] " << test_case.name << ": " + << error.what() << '\n'; + } + catch (...) + { + ++failed_count; + std::cerr << "[FAIL] " << test_case.name + << ": unknown exception\n"; + } + } + + const std::size_t passed_count = test_cases.size() - failed_count; + std::cout << suite_name << ": " << passed_count << "/" + << test_cases.size() << " test cases passed\n"; + return failed_count == 0U ? 0 : 1; +} + // 编辑服务测试只验证内存模型和服务契约,不应依赖真实文件系统 class InMemoryProjectStorage final : public ProjectStorage {