diff --git a/Svc/Ccsds/ApidManager/CMakeLists.txt b/Svc/Ccsds/ApidManager/CMakeLists.txt index ab221d3a357..8213c09acc8 100644 --- a/Svc/Ccsds/ApidManager/CMakeLists.txt +++ b/Svc/Ccsds/ApidManager/CMakeLists.txt @@ -24,6 +24,9 @@ register_fprime_ut( SOURCES "${CMAKE_CURRENT_LIST_DIR}/test/ut/ApidManagerTestMain.cpp" "${CMAKE_CURRENT_LIST_DIR}/test/ut/ApidManagerTester.cpp" + "${CMAKE_CURRENT_LIST_DIR}/test/ut/TestState/TestState.cpp" + "${CMAKE_CURRENT_LIST_DIR}/test/ut/Rules/GetSeqCount.cpp" + "${CMAKE_CURRENT_LIST_DIR}/test/ut/Rules/ValidateSeqCount.cpp" AUTOCODER_INPUTS "${CMAKE_CURRENT_LIST_DIR}/ApidManager.fpp" DEPENDS diff --git a/Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp b/Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp index 454618d1084..5b3dc4fb0ad 100644 --- a/Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp +++ b/Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp @@ -4,47 +4,55 @@ // \brief cpp file for ApidManager component test main function // ====================================================================== -#include "ApidManagerTester.hpp" +#include "STest/Random/Random.hpp" #include "STest/Scenario/BoundedScenario.hpp" #include "STest/Scenario/RandomScenario.hpp" +#include "Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.hpp" +using Svc::Ccsds::ApidManagerTester; + +// ---------------------------------------------------------------------- +// Tests +// ---------------------------------------------------------------------- + +// Verify that getApidSeqCountIn registers a new APID and returns +// incrementing counts on subsequent calls. TEST(ApidManager, GetSequenceCounts) { - Svc::Ccsds::ApidManagerTester tester; - Svc::Ccsds::ApidManagerTester::GetExistingSeqCount getExistingSeqCount; - Svc::Ccsds::ApidManagerTester::GetNewSeqCountOk getNewSeqCountOk; - getExistingSeqCount.apply(tester); - getNewSeqCountOk.apply(tester); + ApidManagerTester tester; + ApidManagerTester::GetSeqCount__NewOk ruleNewOk; + ApidManagerTester::GetSeqCount__Existing ruleExisting; + ruleNewOk.apply(tester); // register a new APID; expect count 0 + ruleExisting.apply(tester); // retrieve count for the same APID; expect count 1 } +// Verify that validateApidSeqCountIn fires no event on a matching count +// and fires UnexpectedSequenceCount on a mismatch. TEST(ApidManager, ValidateSequenceCounts) { - Svc::Ccsds::ApidManagerTester tester; - Svc::Ccsds::ApidManagerTester::ValidateSeqCountOk validateSeqCountOkRule; - Svc::Ccsds::ApidManagerTester::ValidateSeqCountFailure validateSeqCountFailureRule; - validateSeqCountOkRule.apply(tester); - validateSeqCountFailureRule.apply(tester); + ApidManagerTester tester; + ApidManagerTester::GetSeqCount__NewOk ruleNewOk; + ApidManagerTester::ValidateSeqCount__Ok ruleValidateOk; + ApidManagerTester::ValidateSeqCount__Failure ruleValidateFailure; + ruleNewOk.apply(tester); // register an APID so validate rules can fire + ruleValidateOk.apply(tester); // validate correct count; no event expected + ruleValidateFailure.apply(tester); // validate wrong count; event expected } -// Randomized testing +// Randomized test: apply rules in a random sequence for a large number of iterations TEST(ApidManager, RandomizedTesting) { - Svc::Ccsds::ApidManagerTester tester; - - Svc::Ccsds::ApidManagerTester::GetExistingSeqCount getExistingSeqCountRule; - Svc::Ccsds::ApidManagerTester::GetNewSeqCountOk getNewSeqCountOkRule; - Svc::Ccsds::ApidManagerTester::GetNewSeqCountTableFull getNewSeqCountTableFullRule; - Svc::Ccsds::ApidManagerTester::ValidateSeqCountOk validateSeqCountOkRule; - Svc::Ccsds::ApidManagerTester::ValidateSeqCountFailure validateSeqCountFailureRule; - - // Place these rules into a list of rules - STest::Rule* rules[] = {&getExistingSeqCountRule, &getNewSeqCountOkRule, - &getNewSeqCountTableFullRule, &validateSeqCountOkRule, - &validateSeqCountFailureRule}; + U32 numRulesToApply = 10000; + ApidManagerTester tester; + ApidManagerTester::GetSeqCount__Existing ruleGetExisting; + ApidManagerTester::GetSeqCount__NewOk ruleGetNewOk; + ApidManagerTester::GetSeqCount__NewTableFull ruleGetNewTableFull; + ApidManagerTester::ValidateSeqCount__Ok ruleValidateOk; + ApidManagerTester::ValidateSeqCount__Failure ruleValidateFailure; - // Take the rules and place them into a random scenario - STest::RandomScenario random("Random Rules", rules, FW_NUM_ARRAY_ELEMENTS(rules)); + STest::Rule* rules[] = { + &ruleGetExisting, &ruleGetNewOk, &ruleGetNewTableFull, &ruleValidateOk, &ruleValidateFailure, + }; - // Create a bounded scenario wrapping the random scenario - STest::BoundedScenario bounded("Bounded Random Rules Scenario", random, 10000); - // Run! + STest::RandomScenario random("Random Rules", rules, FW_NUM_ARRAY_ELEMENTS(rules)); + STest::BoundedScenario bounded("Bounded Random Rules Scenario", random, numRulesToApply); const U32 numSteps = bounded.run(tester); printf("Ran %u steps.\n", numSteps); } diff --git a/Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.cpp b/Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.cpp index dec6cc80788..a284c353326 100644 --- a/Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.cpp +++ b/Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.cpp @@ -5,172 +5,23 @@ // ====================================================================== #include "ApidManagerTester.hpp" -#include "STest/Random/Random.hpp" -#include "Svc/Ccsds/Types/FppConstantsAc.hpp" namespace Svc { namespace Ccsds { -static constexpr ComCfg::Apid::T TEST_REGISTERED_APIDS[] = {ComCfg::Apid::FW_PACKET_COMMAND, - ComCfg::Apid::FW_PACKET_TELEM, ComCfg::Apid::FW_PACKET_LOG, - ComCfg::Apid::FW_PACKET_FILE}; - // ---------------------------------------------------------------------- // Construction and destruction // ---------------------------------------------------------------------- -ApidManagerTester ::ApidManagerTester() +ApidManagerTester::ApidManagerTester() : ApidManagerGTestBase("ApidManagerTester", ApidManagerTester::MAX_HISTORY_SIZE), component("ApidManager") { this->initComponents(); this->connectPorts(); - // Initialize existing sequence counts for common APIDs - for (FwIndexType i = 0; i < static_cast(FW_NUM_ARRAY_ELEMENTS(TEST_REGISTERED_APIDS)); i++) { - this->component.m_apidSequences.insert(TEST_REGISTERED_APIDS[i], static_cast(0)); - this->shadow_seqCounts[TEST_REGISTERED_APIDS[i]] = 0; // Initialize shadow sequence counts to 0 - } -} - -ApidManagerTester ::~ApidManagerTester() {} - -// ---------------------------------------------------------------------- -// Tests -// ---------------------------------------------------------------------- - -bool ApidManagerTester::GetExistingSeqCount::precondition(const ApidManagerTester& testerState) { - return true; // Can always get existing sequence count -} - -void ApidManagerTester::GetExistingSeqCount::action(ApidManagerTester& testerState) { - testerState.clearHistory(); - ComCfg::Apid::T apid = testerState.shadow_getRandomTrackedApid(); - U16 seqCount = testerState.invoke_to_getApidSeqCountIn(0, apid, 0); - U16 shadowSeqCount = testerState.shadow_getAndIncrementSeqCount(apid); - ASSERT_EQ(seqCount, shadowSeqCount) << "Sequence count for APID " << static_cast(apid) - << " does not match shadow value." - << " Shadow: " << shadowSeqCount << ", Actual: " << seqCount; -} - -bool ApidManagerTester::GetNewSeqCountOk::precondition(const ApidManagerTester& testerState) { - return testerState.shadow_isTableFull == false; -} - -void ApidManagerTester::GetNewSeqCountOk::action(ApidManagerTester& testerState) { - testerState.clearHistory(); - // Use local constexpr to potentially avoid ODR-use of ApidManager::MAX_TRACKED_APIDS - constexpr U8 maxTrackedApidsVal = ApidManager::MAX_TRACKED_APIDS; - bool isTableFull = !(testerState.shadow_seqCounts.size() < maxTrackedApidsVal); - if (isTableFull) { - testerState.shadow_isTableFull = true; - return; // Cannot get new sequence count if table is full - skip action - } - - ComCfg::Apid::T apid = testerState.shadow_getRandomUntrackedApid(); - U16 seqCount = testerState.invoke_to_getApidSeqCountIn(0, apid, 0); - U16 shadowSeqCount = testerState.shadow_getAndIncrementSeqCount(apid); - ASSERT_EQ(seqCount, shadowSeqCount) << "Sequence count for APID " << static_cast(apid) - << " does not match shadow value." - << " Shadow: " << shadowSeqCount << ", Actual: " << seqCount; -} - -bool ApidManagerTester::GetNewSeqCountTableFull::precondition(const ApidManagerTester& testerState) { - return testerState.shadow_isTableFull == true; -} - -void ApidManagerTester::GetNewSeqCountTableFull::action(ApidManagerTester& testerState) { - testerState.clearHistory(); - ComCfg::Apid::T apid = testerState.shadow_getRandomUntrackedApid(); - U16 seqCount = testerState.invoke_to_getApidSeqCountIn(0, apid, 0); - // Use local constexpr to potentially avoid ODR-use of ApidManager::SEQUENCE_COUNT_ERROR - constexpr U16 sequenceCountErrorVal = ApidManager::SEQUENCE_COUNT_ERROR; - ASSERT_EQ(seqCount, sequenceCountErrorVal) - << "Expected SEQUENCE_COUNT_ERROR for untracked APID " << static_cast(apid) << ", but got " << seqCount; - testerState.assertEvents_ApidTableFull_size(__FILE__, __LINE__, 1); - testerState.assertEvents_ApidTableFull(__FILE__, __LINE__, 0, static_cast(apid)); -} - -bool ApidManagerTester::ValidateSeqCountOk::precondition(const ApidManagerTester& testerState) { - return true; } -void ApidManagerTester::ValidateSeqCountOk::action(ApidManagerTester& testerState) { - testerState.clearHistory(); - ComCfg::Apid::T apid = testerState.shadow_getRandomTrackedApid(); - U16 shadow_expectedSeqCount = testerState.shadow_seqCounts[apid]; - testerState.invoke_to_validateApidSeqCountIn(0, apid, shadow_expectedSeqCount); - testerState.shadow_validateApidSeqCount(apid, shadow_expectedSeqCount); // keep shadow state in sync - - testerState.assertEvents_UnexpectedSequenceCount_size(__FILE__, __LINE__, 0); -} - -bool ApidManagerTester::ValidateSeqCountFailure::precondition(const ApidManagerTester& testerState) { - return true; -} - -void ApidManagerTester::ValidateSeqCountFailure::action(ApidManagerTester& testerState) { - testerState.clearHistory(); - ComCfg::Apid::T apid = testerState.shadow_getRandomTrackedApid(); - U16 shadow_expectedSeqCount = testerState.shadow_seqCounts.at(apid); - U16 invalidSeqCount = static_cast( - (shadow_expectedSeqCount + 1) % - (1 << SpacePacketSubfields::SeqCountWidth)); // Or any other value that's different, ensure wrap around - - // Invoke the port with the deliberately incorrect sequence count - testerState.invoke_to_validateApidSeqCountIn(0, apid, invalidSeqCount); - testerState.shadow_validateApidSeqCount(apid, invalidSeqCount); // keep shadow state in sync - - // Now, the event should be logged - testerState.assertEvents_UnexpectedSequenceCount_size(__FILE__, __LINE__, 1); - testerState.assertEvents_UnexpectedSequenceCount(__FILE__, __LINE__, 0, invalidSeqCount, shadow_expectedSeqCount); -} - -// ---------------------------------------------------------------------- -// Helpers -// ---------------------------------------------------------------------- - -U16 ApidManagerTester::shadow_getAndIncrementSeqCount(ComCfg::Apid::T apid) { - // This is a shadow function to simulate the getAndIncrementSeqCount behavior - // without modifying the actual component state. - auto found = this->shadow_seqCounts.find(apid); - if (found != this->shadow_seqCounts.end()) { - U16 seqCount = found->second; - found->second = - static_cast((seqCount + 1) % (1 << SpacePacketSubfields::SeqCountWidth)); // Increment for next call - return seqCount; // Return the current sequence count - } - // If APID not found, initialize a new entry - if (this->shadow_seqCounts.size() < this->component.MAX_TRACKED_APIDS) { - U16 seqCount = 0; - this->shadow_seqCounts[apid] = static_cast(seqCount + 1); // increment for next call - return seqCount; // Return the initialized sequence count - } - return this->component.SEQUENCE_COUNT_ERROR; // Return error if APID not found -} - -void ApidManagerTester::shadow_validateApidSeqCount(ComCfg::Apid::T apid, U16 expectedSeqCount) { - // This simply updates the shadow state to the next expected sequence count - auto found = this->shadow_seqCounts.find(apid); - if (found != this->shadow_seqCounts.end()) { - found->second = static_cast((expectedSeqCount + 1) % (1 << SpacePacketSubfields::SeqCountWidth)); - } -} - -ComCfg::Apid::T ApidManagerTester::shadow_getRandomTrackedApid() { - // Select a random APID from the sequence counts map - U32 mapSize = static_cast(this->shadow_seqCounts.size()); - U32 randomIndex = STest::Random::lowerUpper(0, mapSize - 1); - ComCfg::Apid apid = std::next(this->shadow_seqCounts.begin(), randomIndex)->first; - return apid; -} - -ComCfg::Apid::T ApidManagerTester::shadow_getRandomUntrackedApid() { - // Select a random APID that is not currently tracked - ComCfg::Apid::T apid; - do { - apid = static_cast(STest::Random::lowerUpper(10, ComCfg::Apid::SPP_IDLE_PACKET)); - } while (this->shadow_seqCounts.find(apid) != this->shadow_seqCounts.end()); - return apid; -} +ApidManagerTester::~ApidManagerTester() {} } // namespace Ccsds + } // namespace Svc diff --git a/Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.hpp b/Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.hpp index 579f2f9bbc5..a047b56a624 100644 --- a/Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.hpp +++ b/Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.hpp @@ -7,10 +7,10 @@ #ifndef Svc_Ccsds_ApidManagerTester_HPP #define Svc_Ccsds_ApidManagerTester_HPP -#include "STest/Random/Random.hpp" -#include "STest/Rule/Rule.hpp" #include "Svc/Ccsds/ApidManager/ApidManager.hpp" #include "Svc/Ccsds/ApidManager/ApidManagerGTestBase.hpp" +#include "Svc/Ccsds/ApidManager/test/ut/TestState/TestState.hpp" +#include "TestUtils/RuleBasedTesting.hpp" namespace Svc { @@ -22,10 +22,10 @@ class ApidManagerTester : public ApidManagerGTestBase { // Constants // ---------------------------------------------------------------------- - // Maximum size of histories storing events, telemetry, and port outputs + //! Maximum size of histories storing events, telemetry, and port outputs static const FwSizeType MAX_HISTORY_SIZE = 10; - // Instance ID supplied to the component instance under test + //! Instance ID supplied to the component instance under test static const FwEnumStoreType TEST_INSTANCE_ID = 0; public: @@ -41,7 +41,7 @@ class ApidManagerTester : public ApidManagerGTestBase { private: // ---------------------------------------------------------------------- - // Helper functions + // Helper functions (auto-generated via UT_AUTO_HELPERS) // ---------------------------------------------------------------------- //! Connect ports @@ -58,56 +58,22 @@ class ApidManagerTester : public ApidManagerGTestBase { //! The component under test ApidManager component; - // Shadow test state - std::map shadow_seqCounts; //!< Map to hold expected sequence counts for APIDs - bool shadow_isTableFull = false; + //! Shadow state for rule-based testing + ApidManagerTestState shadow; + public: // ---------------------------------------------------------------------- - // Helpers for tracking the shadow test state + // Rule Based Testing // ---------------------------------------------------------------------- - U16 shadow_getAndIncrementSeqCount(ComCfg::Apid::T apid); - - void shadow_validateApidSeqCount(ComCfg::Apid::T apid, U16 expectedSeqCount); - - ComCfg::Apid::T shadow_getRandomTrackedApid(); - - ComCfg::Apid::T shadow_getRandomUntrackedApid(); + //! Rules for the getApidSeqCountIn port + FW_RBT_DEFINE_RULE(ApidManagerTester, GetSeqCount, Existing); + FW_RBT_DEFINE_RULE(ApidManagerTester, GetSeqCount, NewOk); + FW_RBT_DEFINE_RULE(ApidManagerTester, GetSeqCount, NewTableFull); - // ---------------------------------------------------------------------- - // Tests: Rule Based Testing - // ---------------------------------------------------------------------- - - public: - struct GetExistingSeqCount : public STest::Rule { - GetExistingSeqCount() : STest::Rule("GetExistingSeqCount") {}; - bool precondition(const ApidManagerTester& state); - void action(ApidManagerTester& state); - }; - - struct GetNewSeqCountOk : public STest::Rule { - GetNewSeqCountOk() : STest::Rule("GetNewSeqCountOk") {}; - bool precondition(const ApidManagerTester& state); - void action(ApidManagerTester& state); - }; - - struct GetNewSeqCountTableFull : public STest::Rule { - GetNewSeqCountTableFull() : STest::Rule("GetNewSeqCountTableFull") {}; - bool precondition(const ApidManagerTester& state); - void action(ApidManagerTester& state); - }; - - struct ValidateSeqCountOk : public STest::Rule { - ValidateSeqCountOk() : STest::Rule("ValidateSeqCountOk") {}; - bool precondition(const ApidManagerTester& state); - void action(ApidManagerTester& state); - }; - - struct ValidateSeqCountFailure : public STest::Rule { - ValidateSeqCountFailure() : STest::Rule("ValidateSeqCountFailure") {}; - bool precondition(const ApidManagerTester& state); - void action(ApidManagerTester& state); - }; + //! Rules for the validateApidSeqCountIn port + FW_RBT_DEFINE_RULE(ApidManagerTester, ValidateSeqCount, Ok); + FW_RBT_DEFINE_RULE(ApidManagerTester, ValidateSeqCount, Failure); }; } // namespace Ccsds diff --git a/Svc/Ccsds/ApidManager/test/ut/Rules/GetSeqCount.cpp b/Svc/Ccsds/ApidManager/test/ut/Rules/GetSeqCount.cpp new file mode 100644 index 00000000000..57d12e65a79 --- /dev/null +++ b/Svc/Ccsds/ApidManager/test/ut/Rules/GetSeqCount.cpp @@ -0,0 +1,87 @@ +// ====================================================================== +// \title GetSeqCount.cpp +// \author thomas-bc +// \brief Rule implementations for the GetSeqCount rule group +// +// These rules exercise the getApidSeqCountIn port. +// ====================================================================== + +#include "Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.hpp" +#include "Svc/Ccsds/Types/FppConstantsAc.hpp" + +namespace Svc { + +namespace Ccsds { + +// ---------------------------------------------------------------------- +// GetSeqCount.Existing +// ---------------------------------------------------------------------- + +bool ApidManagerTester::GetSeqCount__Existing__precondition() const { + return !this->shadow.shadow_seqCounts.empty(); +} + +void ApidManagerTester::GetSeqCount__Existing__action() { + this->clearHistory(); + + ComCfg::Apid::T apid = this->shadow.shadow_getRandomTrackedApid(); + U16 returned = this->invoke_to_getApidSeqCountIn(0, apid, 0); + U16 expected = this->shadow.shadow_getAndIncrementSeqCount(apid); + + ASSERT_EQ(returned, expected) << "Sequence count mismatch for APID " << static_cast(apid); + ASSERT_EVENTS_SIZE(0); +} + +// ---------------------------------------------------------------------- +// GetSeqCount.NewOk +// ---------------------------------------------------------------------- + +bool ApidManagerTester::GetSeqCount__NewOk__precondition() const { + return !this->shadow.shadow_isTableFull; +} + +void ApidManagerTester::GetSeqCount__NewOk__action() { + this->clearHistory(); + + // Use constexpr local to avoid ODR-use of the static constexpr member + constexpr U8 maxTrackedApids = ApidManager::MAX_TRACKED_APIDS; + if (this->shadow.shadow_seqCounts.size() >= maxTrackedApids) { + // Table is now full; update shadow flag so the precondition flips + this->shadow.shadow_isTableFull = true; + return; + } + + ComCfg::Apid::T apid = this->shadow.shadow_getRandomUntrackedApid(); + U16 returned = this->invoke_to_getApidSeqCountIn(0, apid, 0); + U16 expected = this->shadow.shadow_getAndIncrementSeqCount(apid); + + ASSERT_EQ(returned, expected) << "First sequence count for new APID " << static_cast(apid) << " must be 0"; + ASSERT_EVENTS_SIZE(0); +} + +// ---------------------------------------------------------------------- +// GetSeqCount.NewTableFull +// ---------------------------------------------------------------------- + +bool ApidManagerTester::GetSeqCount__NewTableFull__precondition() const { + return this->shadow.shadow_isTableFull; +} + +void ApidManagerTester::GetSeqCount__NewTableFull__action() { + this->clearHistory(); + + ComCfg::Apid::T apid = this->shadow.shadow_getRandomUntrackedApid(); + U16 returned = this->invoke_to_getApidSeqCountIn(0, apid, 0); + + // Use constexpr local to avoid ODR-use of the static constexpr member + constexpr U16 errorValue = ApidManager::SEQUENCE_COUNT_ERROR; + ASSERT_EQ(returned, errorValue) << "Expected SEQUENCE_COUNT_ERROR for untracked APID " << static_cast(apid) + << " when the table is full"; + ASSERT_EVENTS_SIZE(1); + ASSERT_EVENTS_ApidTableFull_SIZE(1); + ASSERT_EVENTS_ApidTableFull(0, static_cast(apid)); +} + +} // namespace Ccsds + +} // namespace Svc diff --git a/Svc/Ccsds/ApidManager/test/ut/Rules/ValidateSeqCount.cpp b/Svc/Ccsds/ApidManager/test/ut/Rules/ValidateSeqCount.cpp new file mode 100644 index 00000000000..8728847ebca --- /dev/null +++ b/Svc/Ccsds/ApidManager/test/ut/Rules/ValidateSeqCount.cpp @@ -0,0 +1,61 @@ +// ====================================================================== +// \title ValidateSeqCount.cpp +// \author thomas-bc +// \brief Rule implementations for the ValidateSeqCount rule group +// +// These rules exercise the validateApidSeqCountIn port. +// ====================================================================== + +#include "Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.hpp" +#include "Svc/Ccsds/Types/FppConstantsAc.hpp" + +namespace Svc { + +namespace Ccsds { + +// ---------------------------------------------------------------------- +// ValidateSeqCount.Ok +// ---------------------------------------------------------------------- + +bool ApidManagerTester::ValidateSeqCount__Ok__precondition() const { + return !this->shadow.shadow_seqCounts.empty(); +} + +void ApidManagerTester::ValidateSeqCount__Ok__action() { + this->clearHistory(); + + ComCfg::Apid::T apid = this->shadow.shadow_getRandomTrackedApid(); + U16 expected = this->shadow.shadow_seqCounts.at(apid); + this->invoke_to_validateApidSeqCountIn(0, apid, expected); + this->shadow.shadow_validateApidSeqCount(apid, expected); + + ASSERT_EVENTS_UnexpectedSequenceCount_SIZE(0); +} + +// ---------------------------------------------------------------------- +// ValidateSeqCount.Failure +// ---------------------------------------------------------------------- + +bool ApidManagerTester::ValidateSeqCount__Failure__precondition() const { + return !this->shadow.shadow_seqCounts.empty(); +} + +void ApidManagerTester::ValidateSeqCount__Failure__action() { + this->clearHistory(); + + ComCfg::Apid::T apid = this->shadow.shadow_getRandomTrackedApid(); + U16 correctCount = this->shadow.shadow_seqCounts.at(apid); + + // Increment by 1 (mod 14-bit counter width) to produce a provably wrong count + U16 wrongCount = static_cast((correctCount + 1) % (1 << SpacePacketSubfields::SeqCountWidth)); + + this->invoke_to_validateApidSeqCountIn(0, apid, wrongCount); + this->shadow.shadow_validateApidSeqCount(apid, wrongCount); + + ASSERT_EVENTS_UnexpectedSequenceCount_SIZE(1); + ASSERT_EVENTS_UnexpectedSequenceCount(0, wrongCount, correctCount); +} + +} // namespace Ccsds + +} // namespace Svc diff --git a/Svc/Ccsds/ApidManager/test/ut/TestState/TestState.cpp b/Svc/Ccsds/ApidManager/test/ut/TestState/TestState.cpp new file mode 100644 index 00000000000..2e6c65ae624 --- /dev/null +++ b/Svc/Ccsds/ApidManager/test/ut/TestState/TestState.cpp @@ -0,0 +1,60 @@ +// ====================================================================== +// \title ApidManagerTestState.cpp +// \author thomas-bc +// \brief Shadow helper implementations for ApidManager test state +// ====================================================================== + +#include "Svc/Ccsds/ApidManager/test/ut/TestState/TestState.hpp" + +#include "STest/Random/Random.hpp" +#include "Svc/Ccsds/ApidManager/ApidManager.hpp" +#include "Svc/Ccsds/Types/FppConstantsAc.hpp" + +namespace Svc { + +namespace Ccsds { + +// ---------------------------------------------------------------------- +// Shadow helpers +// ---------------------------------------------------------------------- + +U16 ApidManagerTestState::shadow_getAndIncrementSeqCount(ComCfg::Apid::T apid) { + auto it = this->shadow_seqCounts.find(apid); + if (it != this->shadow_seqCounts.end()) { + // APID already tracked: return current count and advance to the next + U16 current = it->second; + it->second = static_cast((current + 1) % (1 << SpacePacketSubfields::SeqCountWidth)); + return current; + } + // APID not yet tracked: register it, starting at count 0 + if (this->shadow_seqCounts.size() < ApidManager::MAX_TRACKED_APIDS) { + this->shadow_seqCounts[apid] = static_cast(1); // next expected is 1 + return 0; // first count returned is 0 + } + return ApidManager::SEQUENCE_COUNT_ERROR; +} + +void ApidManagerTestState::shadow_validateApidSeqCount(ComCfg::Apid::T apid, U16 expectedSeqCount) { + auto it = this->shadow_seqCounts.find(apid); + if (it != this->shadow_seqCounts.end()) { + it->second = static_cast((expectedSeqCount + 1) % (1 << SpacePacketSubfields::SeqCountWidth)); + } +} + +ComCfg::Apid::T ApidManagerTestState::shadow_getRandomTrackedApid() const { + FW_ASSERT(!this->shadow_seqCounts.empty()); + U32 idx = STest::Random::lowerUpper(0, static_cast(this->shadow_seqCounts.size()) - 1); + return std::next(this->shadow_seqCounts.begin(), idx)->first; +} + +ComCfg::Apid::T ApidManagerTestState::shadow_getRandomUntrackedApid() const { + ComCfg::Apid::T apid; + do { + apid = static_cast(STest::Random::lowerUpper(10, ComCfg::Apid::SPP_IDLE_PACKET)); + } while (this->shadow_seqCounts.find(apid) != this->shadow_seqCounts.end()); + return apid; +} + +} // namespace Ccsds + +} // namespace Svc diff --git a/Svc/Ccsds/ApidManager/test/ut/TestState/TestState.hpp b/Svc/Ccsds/ApidManager/test/ut/TestState/TestState.hpp new file mode 100644 index 00000000000..f92a6a42f32 --- /dev/null +++ b/Svc/Ccsds/ApidManager/test/ut/TestState/TestState.hpp @@ -0,0 +1,68 @@ +// ====================================================================== +// \title ApidManagerTestState.hpp +// \author thomas-bc +// \brief Shadow state model for ApidManager rule-based testing +// +// ApidManagerTestState contains only shadow-model data and state-only helpers. +// Rule preconditions/actions are implemented on ApidManagerTester. +// +// Shadow state +// ------------ +// The shadow is a simple software model of the component's internal APID +// sequence-count table. Precondition methods read from it to decide +// whether a rule may fire; action methods update it in lockstep with the +// component. After every action the shadow and the component must agree. +// ====================================================================== + +#ifndef Svc_Ccsds_ApidManager_ApidManagerTestState_HPP +#define Svc_Ccsds_ApidManager_ApidManagerTestState_HPP + +#include + +#include "Svc/Ccsds/ApidManager/ApidManager.hpp" + +namespace Svc { + +namespace Ccsds { + +class ApidManagerTestState { + public: + // ---------------------------------------------------------------------- + // Component "shadow" test state + // + // These state variables mirror the component's internal state (APID map, etc). + // ---------------------------------------------------------------------- + + //! Expected next sequence count for every tracked APID. + //! Mirrors the component's m_apidSequences map. + std::map shadow_seqCounts; + + //! True once shadow_seqCounts has reached MAX_TRACKED_APIDS entries. + bool shadow_isTableFull = false; + + public: + // ----------------------------------------------------------------------------- + // Helpers maintaining the shadow state in line with component under test, + // or simple test helpers querying the state + // ----------------------------------------------------------------------------- + + //! Return the current expected sequence count for apid and advance the + //! shadow to the next value. + U16 shadow_getAndIncrementSeqCount(ComCfg::Apid::T apid); + + //! Advance the shadow's expected count as if validateApidSeqCountIn was + //! called with expectedSeqCount. Mirrors the component's setNextSeqCount. + void shadow_validateApidSeqCount(ComCfg::Apid::T apid, U16 expectedSeqCount); + + //! Return a uniformly random APID from the set currently tracked in the shadow. + ComCfg::Apid::T shadow_getRandomTrackedApid() const; + + //! Return a random APID that is not currently tracked in the shadow. + ComCfg::Apid::T shadow_getRandomUntrackedApid() const; +}; + +} // namespace Ccsds + +} // namespace Svc + +#endif diff --git a/TestUtils/RuleBasedTesting.hpp b/TestUtils/RuleBasedTesting.hpp new file mode 100644 index 00000000000..78be76d98b3 --- /dev/null +++ b/TestUtils/RuleBasedTesting.hpp @@ -0,0 +1,74 @@ +// ====================================================================== +// \title RuleBasedTesting.hpp +// \brief Shared macros for Rule-Based Testing (RBT) in F Prime +// +// Background +// ---------- +// F Prime rule-based testing (RBT) uses STest::Rule, where Tester is +// usually the component tester class. The STest infrastructure selects +// rules whose precondition() returns true and then calls action(). +// +// While rules can be declared manually, the macro has several benefits: +// - It standardizes the naming convention for rule methods and labels. +// - It reduces boilerplate +// - It makes the F Prime test asserts (e.g. ASSERT_EVENTS_*, ASSERT_TLM_* etc.) +// available inside rule method bodies, which is not straightforward manually. +// This is because those asserts expand to `this->...`, where `this` needs +// to be the tester instance, which is not the case in a classical STest::Rule +// +// Usage +// ----- +// 1. Inside a ComponentTester class, declare each rule with FW_RBT_DEFINE_RULE: +// +// FW_RBT_DEFINE_RULE(MyComponentTester, GroupName, RuleName); +// +// This creates inside the tester class: +// bool GroupName__RuleName__precondition() const; +// void GroupName__RuleName__action(); +// struct GroupName__RuleName : STest::Rule { ... }; +// +// 2. Implement the method bodies in .cpp files (and add *.cpp to CMakeLists.txt !!) +// +// 3. In the test main, instantiate and use rule types as nested types of +// the tester class: +// +// MyComponentTester tester; +// MyComponentTester::GroupName__RuleName rule; +// rule.apply(tester); +// +// ====================================================================== + +#ifndef TestUtils_RuleBasedTesting_HPP +#define TestUtils_RuleBasedTesting_HPP + +#include "STest/Rule/Rule.hpp" + +// ----------------------------------------------------------------------- +//! \def FW_RBT_DEFINE_RULE +//! +//! Defines everything needed for one rule inside a ComponentTester class: +//! 1) GroupName__RuleName__precondition() const — declaration +//! 2) GroupName__RuleName__action() — declaration +//! 3) struct GroupName__RuleName : STest::Rule — definition +//! +//! Place inside the tester class body; implement both method bodies in a +//! per-group .cpp file. +//! +//! \param TESTER_TYPE The ComponentTester class (state passed to STest::Rule) +//! \param GROUP_NAME Rule group: appears in method names and the rule label +//! \param RULE_NAME Rule variant: appears in method names and the rule label +// ----------------------------------------------------------------------- +#define FW_RBT_DEFINE_RULE(TESTER_TYPE, GROUP_NAME, RULE_NAME) \ + bool GROUP_NAME##__##RULE_NAME##__precondition() const; \ + void GROUP_NAME##__##RULE_NAME##__action(); \ + struct GROUP_NAME##__##RULE_NAME : public STest::Rule { \ + GROUP_NAME##__##RULE_NAME() : STest::Rule(#GROUP_NAME "." #RULE_NAME) {} \ + bool precondition(const TESTER_TYPE& tester) override { \ + return tester.GROUP_NAME##__##RULE_NAME##__precondition(); \ + } \ + void action(TESTER_TYPE& tester) override { \ + tester.GROUP_NAME##__##RULE_NAME##__action(); \ + } \ + } + +#endif