From 37e5bcc646508aec36fdeca0a747fbe1056b186d Mon Sep 17 00:00:00 2001 From: Martin Hierholzer <martin.hierholzer@desy.de> Date: Fri, 28 May 2021 16:22:30 +0200 Subject: [PATCH] [wip #7982] tests for StatusAggregator --- tests/executables_src/testStatusAggregator.cc | 352 ++++++++++++++++-- 1 file changed, 316 insertions(+), 36 deletions(-) diff --git a/tests/executables_src/testStatusAggregator.cc b/tests/executables_src/testStatusAggregator.cc index 6a2656c8..aa7a3ea8 100644 --- a/tests/executables_src/testStatusAggregator.cc +++ b/tests/executables_src/testStatusAggregator.cc @@ -15,65 +15,345 @@ using namespace boost::unit_test_framework; namespace ctk = ChimeraTK; -struct OuterGroup : public ctk::ModuleGroup { - using ctk::ModuleGroup::ModuleGroup; +/**********************************************************************************************************************/ - ctk::StateMonitor<uint8_t> outerStateMonitor{this, "outerStateMonitor", "", "watch", "status", - ctk::HierarchyModifier::none, {"OUTER_MON_OUTPUT"}, {"OUTER_MON_PARAMS"}, {"OUTER_MON_INPUT"}}; +struct StatusGenerator : ctk::ApplicationModule { + using ctk::ApplicationModule::ApplicationModule; + ctk::StatusOutput status{this, getName(), ""}; + void mainLoop() override {} +}; + +/**********************************************************************************************************************/ + +struct TestApplication : ctk::Application { + TestApplication() : Application("testApp") {} + ~TestApplication() override { shutdown(); } - struct InnerGroup : public ctk::ModuleGroup { + StatusGenerator s{this, "s", "Status", ctk::HierarchyModifier::hideThis}; + + struct OuterGroup : ctk::ModuleGroup { using ctk::ModuleGroup::ModuleGroup; - ctk::StateMonitor<uint8_t> innerStateMonitorNone{this, "innerMinMonitorNone", "", "watch", "status", - ctk::HierarchyModifier::none, {"INNER_MON_OUTPUT"}, {"INNER_MON_PARAMS"}, {"INNER_MON_INPUT"}}; + StatusGenerator s1{this, "s1", "Status 1", ctk::HierarchyModifier::hideThis}; + StatusGenerator s2{this, "s2", "Status 2", ctk::HierarchyModifier::hideThis}; + + struct InnerGroup : ctk::ModuleGroup { + using ctk::ModuleGroup::ModuleGroup; + StatusGenerator s{this, "s", "Status", ctk::HierarchyModifier::hideThis}; + StatusGenerator deep{this, "deep", "Status"}; + }; + InnerGroup innerGroup1{this, "InnerGroup1", ""}; + InnerGroup innerGroup2{this, "InnerGroup2", ""}; + + } outerGroup{this, "OuterGroup", ""}; + + ctk::StatusAggregator aggregator{ + this, "Aggregated/status", "aggregated status description", ctk::StatusAggregator::PriorityMode::fwko}; +}; + +/**********************************************************************************************************************/ + +BOOST_AUTO_TEST_CASE(testSingleNoTags) { + std::cout << "testSingleNoTags" << std::endl; - ctk::StateMonitor<uint8_t> innerStateMonitorHideThis{this, "innerStateMonitorHideThis", "", "watch", "status", - ctk::HierarchyModifier::hideThis, {"INNER_MON_OUTPUT"}, {"INNER_MON_PARAMS"}, {"INNER_MON_INPUT"}}; + TestApplication app; + ctk::TestFacility test; - ctk::StateMonitor<uint8_t> innerStateMonitorOneUp{this, "innerStateMonitorOneUp", "", "watch", "status", - ctk::HierarchyModifier::oneLevelUp, {"INNER_MON_OUTPUT"}, {"INNER_MON_PARAMS"}, {"INNER_MON_INPUT"}}; + auto status = test.getScalar<int>("/Aggregated/status"); - } innerGroup{this, "innerModuleGroup", "", ctk::HierarchyModifier::none}; + // write initial values (all in Status::OFF = 0) + app.s.status.write(); + app.outerGroup.s1.status.write(); + app.outerGroup.s2.status.write(); + app.outerGroup.innerGroup1.s.status.write(); + app.outerGroup.innerGroup1.deep.status.write(); + app.outerGroup.innerGroup2.s.status.write(); + app.outerGroup.innerGroup2.deep.status.write(); + + test.runApplication(); + + // check that statuses on different levels are correctly aggregated + auto check = [&](auto& var) { + var = ctk::StatusOutput::Status::OK; + var.write(); + test.stepApplication(); + BOOST_CHECK(status.readNonBlocking() == true); + BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::OK)); + var = ctk::StatusOutput::Status::OFF; + var.write(); + test.stepApplication(); + BOOST_CHECK(status.readNonBlocking() == true); + BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::OFF)); + }; + check(app.s.status); + check(app.outerGroup.s1.status); + check(app.outerGroup.s2.status); + check(app.outerGroup.innerGroup1.s.status); + check(app.outerGroup.innerGroup1.deep.status); + check(app.outerGroup.innerGroup2.s.status); + check(app.outerGroup.innerGroup2.deep.status); +} - ctk::StatusAggregator outerStatusAggregator{this, "outerStatusAggregator", "StatusAggregator of OuterGroup", - "groupStatus", ctk::HierarchyModifier::none, {"STATUS"}}; +/**********************************************************************************************************************/ + +struct TestPrioApplication : ctk::Application { + TestPrioApplication() : Application("testApp") {} + ~TestPrioApplication() override { shutdown(); } + + StatusGenerator s1{this, "s1", "Status 1", ctk::HierarchyModifier::hideThis}; + StatusGenerator s2{this, "s2", "Status 2", ctk::HierarchyModifier::hideThis}; + + ctk::StatusAggregator aggregator; }; -struct TestApplication : public ctk::Application { - TestApplication() : Application("testApp") {} - ~TestApplication() override { shutdown(); } +/**********************************************************************************************************************/ + +BOOST_AUTO_TEST_CASE(testPriorities) { + std::cout << "testPriorities" << std::endl; + + // Define repeated check for a given priority mode + auto check = [&](ctk::StatusAggregator::PriorityMode mode, auto prio0, auto prio1, auto prio2, auto prio3, + bool warnMixed01 = false) { + TestPrioApplication app; + app.aggregator = ctk::StatusAggregator{&app, "Aggregated/status", "aggregated status description", mode}; + + ctk::TestFacility test; + + auto status = test.getScalar<int>("/Aggregated/status"); + + // write initial values (all in lowest prio value) + app.s1.status = prio0; + app.s1.status.write(); + app.s2.status = prio0; + app.s2.status.write(); + + test.runApplication(); + + // check initial value + status.readNonBlocking(); // do not check return value, as it will only be written when changed + BOOST_CHECK_EQUAL(int(status), int(prio0)); + + // define repeated check to test all combinations of two given values with different priority + auto subcheck = [&](auto lower, auto higher, bool warnMixed = false) { + std::cout << int(lower) << " vs. " << int(higher) << std::endl; + app.s1.status = lower; + app.s1.status.write(); + app.s2.status = lower; + app.s2.status.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK_EQUAL(int(status), int(lower)); + app.s1.status = lower; + app.s1.status.write(); + app.s2.status = higher; + app.s2.status.write(); + test.stepApplication(); + status.readLatest(); + if(!warnMixed) { + BOOST_CHECK_EQUAL(int(status), int(higher)); + } + else { + BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::WARNING)); + } + app.s1.status = higher; + app.s1.status.write(); + app.s2.status = lower; + app.s2.status.write(); + test.stepApplication(); + status.readLatest(); + if(!warnMixed) { + BOOST_CHECK_EQUAL(int(status), int(higher)); + } + else { + BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::WARNING)); + } + app.s1.status = higher; + app.s1.status.write(); + app.s2.status = higher; + app.s2.status.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK_EQUAL(int(status), int(higher)); + }; + + // all prios against each other + subcheck(prio0, prio1, warnMixed01); + subcheck(prio0, prio2); + subcheck(prio0, prio3); + subcheck(prio1, prio2); + subcheck(prio1, prio3); + subcheck(prio2, prio3); + }; + + // check all priority modes + std::cout << "PriorityMode::fwko" << std::endl; + check(ctk::StatusAggregator::PriorityMode::fwko, ctk::StatusOutput::Status::OFF, ctk::StatusOutput::Status::OK, + ctk::StatusOutput::Status::WARNING, ctk::StatusOutput::Status::FAULT); + std::cout << "PriorityMode::fwok" << std::endl; + check(ctk::StatusAggregator::PriorityMode::fwok, ctk::StatusOutput::Status::OK, ctk::StatusOutput::Status::OFF, + ctk::StatusOutput::Status::WARNING, ctk::StatusOutput::Status::FAULT); + std::cout << "PriorityMode::ofwk" << std::endl; + check(ctk::StatusAggregator::PriorityMode::ofwk, ctk::StatusOutput::Status::OK, ctk::StatusOutput::Status::WARNING, + ctk::StatusOutput::Status::FAULT, ctk::StatusOutput::Status::OFF); + std::cout << "PriorityMode::fw_warn_mixed" << std::endl; + check(ctk::StatusAggregator::PriorityMode::fw_warn_mixed, ctk::StatusOutput::Status::OFF, + ctk::StatusOutput::Status::OK, ctk::StatusOutput::Status::WARNING, ctk::StatusOutput::Status::FAULT, true); +} + +/**********************************************************************************************************************/ - OuterGroup outerModuleGroup1{this, "outerModuleGroup1", "", ctk::HierarchyModifier::none}; - //OuterGroup outerModuleGroup2{this, "outerModuleGroup2", "", ctk::HierarchyModifier::hideThis}; +struct TestApplication2Levels : ctk::Application { + TestApplication2Levels() : Application("testApp") {} + ~TestApplication2Levels() override { shutdown(); } - ctk::StateMonitor<uint8_t> globalStateMonitor{this, "globalStateMonitor", "", "stateWatch", "stateStatus", - ctk::HierarchyModifier::none, {"GLOBAL_MON_OUTPUT"}, {"GLOBAL_MON_PARAMS"}, {"GLOBAL_MON_INPUT"}}; + StatusGenerator s{this, "s", "Status", ctk::HierarchyModifier::hideThis}; - ctk::StatusAggregator globalStatusAggregator{this, "globalStatusAggregator", "Global StatusAggregator of testApp", - "globalStatus", ctk::HierarchyModifier::none, {"STATUS"}}; + struct OuterGroup : ctk::ModuleGroup { + using ctk::ModuleGroup::ModuleGroup; + + StatusGenerator s1{this, "s1", "Status 1", ctk::HierarchyModifier::hideThis}; + StatusGenerator s2{this, "s2", "Status 2", ctk::HierarchyModifier::hideThis}; + + ctk::StatusAggregator extraAggregator{ + this, "/Aggregated/extraStatus", "aggregated status description", ctk::StatusAggregator::PriorityMode::ofwk}; + + } outerGroup{this, "OuterGroup", ""}; + + ctk::StatusAggregator aggregator{ + this, "Aggregated/status", "aggregated status description", ctk::StatusAggregator::PriorityMode::fwko}; }; -BOOST_AUTO_TEST_CASE(testStatusAggregator) { - std::cout << "testStatusAggregator" << std::endl; +/**********************************************************************************************************************/ - TestApplication app; +BOOST_AUTO_TEST_CASE(testTwoLevels) { + std::cout << "testTwoLevels" << std::endl; + TestApplication2Levels app; + + ctk::TestFacility test; + + auto status = test.getScalar<int>("/Aggregated/status"); + auto extraStatus = test.getScalar<int>("/Aggregated/extraStatus"); + + // write initial values [default/0 value is OFF] + app.s.status.write(); + app.outerGroup.s2.status.write(); + // Set one of the inputs for the extraAggregator to fault, which has no effect, since one other is OFF which is + // prioritised. If the top-level aggregator would wrongly aggregate this input directly, it would go to FAULT. + app.outerGroup.s1.status = ctk::StatusOutput::Status::FAULT; + app.outerGroup.s1.status.write(); + + test.runApplication(); + + // check the initial values + extraStatus.readLatest(); + BOOST_CHECK_EQUAL(int(extraStatus), int(ctk::StatusOutput::Status::OFF)); + status.readLatest(); + BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::OFF)); + + // change status which goes directly into the upper aggregator + app.s.status = ctk::StatusOutput::Status::OK; + app.s.status.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::OK)); + app.s.status = ctk::StatusOutput::Status::OFF; + app.s.status.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::OFF)); + + // change status which goes into the lower aggregator (then the FAULT of s1 will win) + app.outerGroup.s2.status = ctk::StatusOutput::Status::OK; + app.outerGroup.s2.status.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::FAULT)); + app.outerGroup.s2.status = ctk::StatusOutput::Status::OFF; + app.outerGroup.s2.status.write(); + test.stepApplication(); + status.readLatest(); + BOOST_CHECK_EQUAL(int(status), int(ctk::StatusOutput::Status::OFF)); +} + +/**********************************************************************************************************************/ + +struct TestApplicationTags : ctk::Application { + TestApplicationTags() : Application("testApp") {} + ~TestApplicationTags() override { shutdown(); } + + struct OuterGroup : ctk::ModuleGroup { + using ctk::ModuleGroup::ModuleGroup; + + StatusGenerator sA{this, "sA", "Status 1", ctk::HierarchyModifier::hideThis, {"A"}}; + StatusGenerator sAB{this, "sAB", "Status 2", ctk::HierarchyModifier::hideThis, {"A", "B"}}; + + ctk::StatusAggregator aggregateA{ + this, "aggregateA", "aggregated status description", ctk::StatusAggregator::PriorityMode::fwko, {"A"}}; + ctk::StatusAggregator aggregateB{this, "aggregateB", "aggregated status description", + ctk::StatusAggregator::PriorityMode::fwko, {"B"}, {"A"}}; // the "A" tag should be ignored by other aggregators + } group{this, "Group", ""}; + + // Use other priority mode here to make sure only the aggregators are aggregated, not the generators + ctk::StatusAggregator aggregateA{ + this, "aggregateA", "aggregated status description", ctk::StatusAggregator::PriorityMode::ofwk, {"A"}}; + ctk::StatusAggregator aggregateB{ + this, "aggregateB", "aggregated status description", ctk::StatusAggregator::PriorityMode::ofwk, {"B"}}; + ctk::StatusAggregator aggregateAll{ + this, "aggregateAll", "aggregated status description", ctk::StatusAggregator::PriorityMode::fw_warn_mixed}; +}; + +/**********************************************************************************************************************/ + +BOOST_AUTO_TEST_CASE(testTags) { + std::cout << "testTags" << std::endl; + + TestApplicationTags app; ctk::TestFacility test; - auto innerNoneInput = test.getScalar<uint8_t>("/outerModuleGroup1/innerModuleGroup/watch"); - auto innerHideThisInput = test.getScalar<uint8_t>("/outerModuleGroup1/watch"); - auto innerStateMonitorOneUp = test.getScalar<uint8_t>("/outerModuleGroup1/watch"); - auto outerInput = test.getScalar<uint8_t>("/outerModuleGroup1/watch"); - auto outerAggregated = test.getScalar<int32_t>("/outerModuleGroup1/outerStatusAggregator/groupStatus"); + auto aggregateA = test.getScalar<int>("/aggregateA"); + auto aggregateB = test.getScalar<int>("/aggregateB"); + auto aggregateAll = test.getScalar<int>("/aggregateAll"); + auto Group_aggregateA = test.getScalar<int>("/Group/aggregateA"); + auto Group_aggregateB = test.getScalar<int>("/Group/aggregateB"); + + // write initial values + app.group.sA.status = ctk::StatusOutput::Status::WARNING; + app.group.sA.status.write(); + app.group.sAB.status = ctk::StatusOutput::Status::OFF; + app.group.sAB.status.write(); test.runApplication(); - // FIXME test initial value propagation + // check initial values + aggregateA.readLatest(); + aggregateB.readLatest(); + aggregateAll.readLatest(); + Group_aggregateA.readLatest(); + Group_aggregateB.readLatest(); + BOOST_CHECK_EQUAL(int(aggregateA), int(ctk::StatusOutput::Status::WARNING)); + BOOST_CHECK_EQUAL(int(aggregateB), int(ctk::StatusOutput::Status::OFF)); + BOOST_CHECK_EQUAL(int(aggregateAll), int(ctk::StatusOutput::Status::WARNING)); + BOOST_CHECK_EQUAL(int(Group_aggregateA), int(ctk::StatusOutput::Status::WARNING)); + BOOST_CHECK_EQUAL(int(Group_aggregateB), int(ctk::StatusOutput::Status::OFF)); + + app.group.sAB.status = ctk::StatusOutput::Status::FAULT; + app.group.sAB.status.write(); - innerNoneInput = 1; - innerNoneInput.write(); test.stepApplication(); - //BOOST_CHECK(outerAggregated.readLatest() == true); - //BOOST_CHECK_EQUAL(int(outerAggregated), 0); + // change value tagged with 'A' and 'B', affecting all aggregators to highest priority value, so it is visible + // everywhere + aggregateA.readLatest(); + aggregateB.readLatest(); + aggregateAll.readLatest(); + Group_aggregateA.readLatest(); + Group_aggregateB.readLatest(); + BOOST_CHECK_EQUAL(int(aggregateA), int(ctk::StatusOutput::Status::FAULT)); + BOOST_CHECK_EQUAL(int(aggregateB), int(ctk::StatusOutput::Status::FAULT)); + BOOST_CHECK_EQUAL(int(aggregateAll), int(ctk::StatusOutput::Status::FAULT)); + BOOST_CHECK_EQUAL(int(Group_aggregateA), int(ctk::StatusOutput::Status::FAULT)); + BOOST_CHECK_EQUAL(int(Group_aggregateB), int(ctk::StatusOutput::Status::FAULT)); } + +/**********************************************************************************************************************/ -- GitLab