From 8164244deb8d3419b111b9899bac37cc6a4257fc Mon Sep 17 00:00:00 2001 From: thomas-bc Date: Wed, 1 Apr 2026 16:47:31 -0700 Subject: [PATCH 01/10] First pass at enhanced system --- Svc/Ccsds/ApidManager/CMakeLists.txt | 3 + .../test/ut/ApidManagerTestMain.cpp | 79 +++++---- .../ApidManager/test/ut/ApidManagerTester.cpp | 155 +----------------- .../ApidManager/test/ut/ApidManagerTester.hpp | 66 ++------ .../ApidManager/test/ut/Rules/GetSeqCount.cpp | 104 ++++++++++++ Svc/Ccsds/ApidManager/test/ut/Rules/Rules.hpp | 60 +++++++ .../test/ut/Rules/ValidateSeqCount.cpp | 72 ++++++++ .../test/ut/TestState/TestState.cpp | 60 +++++++ .../test/ut/TestState/TestState.hpp | 68 ++++++++ TestUtils/RuleBasedTesting.hpp | 99 +++++++++++ 10 files changed, 532 insertions(+), 234 deletions(-) create mode 100644 Svc/Ccsds/ApidManager/test/ut/Rules/GetSeqCount.cpp create mode 100644 Svc/Ccsds/ApidManager/test/ut/Rules/Rules.hpp create mode 100644 Svc/Ccsds/ApidManager/test/ut/Rules/ValidateSeqCount.cpp create mode 100644 Svc/Ccsds/ApidManager/test/ut/TestState/TestState.cpp create mode 100644 Svc/Ccsds/ApidManager/test/ut/TestState/TestState.hpp create mode 100644 TestUtils/RuleBasedTesting.hpp 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..09a4dc14ae1 100644 --- a/Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp +++ b/Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp @@ -4,51 +4,66 @@ // \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" +#include "Svc/Ccsds/ApidManager/test/ut/Rules/Rules.hpp" +namespace Svc { + +namespace Ccsds { + +// ---------------------------------------------------------------------- +// 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 state; + Rules::GetSeqCount::NewOk ruleNewOk; + Rules::GetSeqCount::Existing ruleExisting; + ruleNewOk.apply(state); // register a new APID; expect count 0 + ruleExisting.apply(state); // 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 state; + Rules::GetSeqCount::NewOk ruleNewOk; + Rules::ValidateSeqCount::Ok ruleValidateOk; + Rules::ValidateSeqCount::Failure ruleValidateFailure; + ruleNewOk.apply(state); // register an APID so validate rules can fire + ruleValidateOk.apply(state); // validate correct count; no event expected + ruleValidateFailure.apply(state); // validate wrong count; event expected } -// Randomized testing +// Randomized test: apply rules in a bounded random sequence to exercise +// all state transitions across the APID sequence-count lifecycle. 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}; - - // Take the rules and place them into a random scenario - STest::RandomScenario random("Random Rules", rules, FW_NUM_ARRAY_ELEMENTS(rules)); - - // Create a bounded scenario wrapping the random scenario - STest::BoundedScenario bounded("Bounded Random Rules Scenario", random, 10000); - // Run! - const U32 numSteps = bounded.run(tester); + ApidManagerTester state; + Rules::GetSeqCount::Existing ruleGetExisting; + Rules::GetSeqCount::NewOk ruleGetNewOk; + Rules::GetSeqCount::NewTableFull ruleGetNewTableFull; + Rules::ValidateSeqCount::Ok ruleValidateOk; + Rules::ValidateSeqCount::Failure ruleValidateFailure; + + STest::Rule* rules[] = { + &ruleGetExisting, &ruleGetNewOk, &ruleGetNewTableFull, &ruleValidateOk, &ruleValidateFailure, + }; + + STest::RandomScenario random("Random Rules", rules, FW_NUM_ARRAY_ELEMENTS(rules)); + STest::BoundedScenario bounded("Bounded Random Rules Scenario", random, 10000); + const U32 numSteps = bounded.run(state); printf("Ran %u steps.\n", numSteps); } +} // namespace Ccsds + +} // namespace Svc + int main(int argc, char** argv) { STest::Random::seed(); ::testing::InitGoogleTest(&argc, argv); 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..acbe7e28f30 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; - + public: // ---------------------------------------------------------------------- - // Helpers for tracking the shadow test state + // Shadow state and rule method declarations // ---------------------------------------------------------------------- - U16 shadow_getAndIncrementSeqCount(ComCfg::Apid::T apid); - - void shadow_validateApidSeqCount(ComCfg::Apid::T apid, U16 expectedSeqCount); - - ComCfg::Apid::T shadow_getRandomTrackedApid(); + //! Shadow model used by precondition/action rule methods + TestState shadow; - ComCfg::Apid::T shadow_getRandomUntrackedApid(); + //! Rules for the getApidSeqCountIn port + FW_TEST_STATE_DEF_RULE(GetSeqCount, Existing) + FW_TEST_STATE_DEF_RULE(GetSeqCount, NewOk) + FW_TEST_STATE_DEF_RULE(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_TEST_STATE_DEF_RULE(ValidateSeqCount, Ok) + FW_TEST_STATE_DEF_RULE(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..64206943b0d --- /dev/null +++ b/Svc/Ccsds/ApidManager/test/ut/Rules/GetSeqCount.cpp @@ -0,0 +1,104 @@ +// ====================================================================== +// \title GetSeqCount.cpp +// \author thomas-bc +// \brief Rule implementations for the GetSeqCount rule group +// +// These rules exercise the getApidSeqCountIn port. +// +// GetSeqCount.Existing +// Precondition: at least one APID is tracked in the shadow. +// Action: invoke getApidSeqCountIn for a random tracked APID +// and verify the returned count matches the shadow. +// +// GetSeqCount.NewOk +// Precondition: the APID table is not full. +// Action: invoke getApidSeqCountIn for an untracked APID; +// expect it to be registered at sequence count 0 +// with no events fired. +// +// GetSeqCount.NewTableFull +// Precondition: the APID table is full. +// Action: invoke getApidSeqCountIn for an untracked APID; +// expect SEQUENCE_COUNT_ERROR and an ApidTableFull event. +// ====================================================================== + +#include "Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.hpp" +#include "Svc/Ccsds/Types/FppConstantsAc.hpp" + +namespace Svc { + +namespace Ccsds { + +// ---------------------------------------------------------------------- +// GetSeqCount.Existing +// ---------------------------------------------------------------------- + +bool ApidManagerTester::precondition__GetSeqCount__Existing() const { + return !this->shadow.shadow_seqCounts.empty(); +} + +void ApidManagerTester::action__GetSeqCount__Existing() { + 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::precondition__GetSeqCount__NewOk() const { + return !this->shadow.shadow_isTableFull; +} + +void ApidManagerTester::action__GetSeqCount__NewOk() { + 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::precondition__GetSeqCount__NewTableFull() const { + return this->shadow.shadow_isTableFull; +} + +void ApidManagerTester::action__GetSeqCount__NewTableFull() { + 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/Rules.hpp b/Svc/Ccsds/ApidManager/test/ut/Rules/Rules.hpp new file mode 100644 index 00000000000..6e1d5141ce8 --- /dev/null +++ b/Svc/Ccsds/ApidManager/test/ut/Rules/Rules.hpp @@ -0,0 +1,60 @@ +// ====================================================================== +// \title Rules.hpp +// \author thomas-bc +// \brief Rule definitions for ApidManager rule-based testing +// +// FW_RULES_DEF_RULE(ApidManagerTester, GROUP, RULE) creates a +// STest::Rule subclass in namespace GROUP named RULE. +// Its precondition() and action() delegate to the matching methods on the +// TestState object. Those methods are declared in TestState.hpp via +// FW_TEST_STATE_DEF_RULE and implemented in the per-group .cpp files +// listed below. +// +// Rule groups +// ----------- +// GetSeqCount — exercises getApidSeqCountIn (GetSeqCount.cpp) +// ValidateSeqCount — exercises validateApidSeqCountIn (ValidateSeqCount.cpp) +// ====================================================================== + +#ifndef Svc_Ccsds_ApidManager_Rules_HPP +#define Svc_Ccsds_ApidManager_Rules_HPP + +#include "Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.hpp" +#include "TestUtils/RuleBasedTesting.hpp" + +namespace Svc { + +namespace Ccsds { + +namespace Rules { + +// ---------------------------------------------------------------------- +// GetSeqCount rules +// ---------------------------------------------------------------------- + +//! Get the sequence count for an APID that is already tracked +FW_RULES_DEF_RULE(ApidManagerTester, GetSeqCount, Existing) + +//! Register and get the sequence count for a new APID (table not full) +FW_RULES_DEF_RULE(ApidManagerTester, GetSeqCount, NewOk) + +//! Attempt to get the sequence count for a new APID when the table is full +FW_RULES_DEF_RULE(ApidManagerTester, GetSeqCount, NewTableFull) + +// ---------------------------------------------------------------------- +// ValidateSeqCount rules +// ---------------------------------------------------------------------- + +//! Validate the correct (expected) sequence count — no event expected +FW_RULES_DEF_RULE(ApidManagerTester, ValidateSeqCount, Ok) + +//! Validate a wrong sequence count — UnexpectedSequenceCount event expected +FW_RULES_DEF_RULE(ApidManagerTester, ValidateSeqCount, Failure) + +} // namespace Rules + +} // namespace Ccsds + +} // namespace Svc + +#endif 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..b635298d8bd --- /dev/null +++ b/Svc/Ccsds/ApidManager/test/ut/Rules/ValidateSeqCount.cpp @@ -0,0 +1,72 @@ +// ====================================================================== +// \title ValidateSeqCount.cpp +// \author thomas-bc +// \brief Rule implementations for the ValidateSeqCount rule group +// +// These rules exercise the validateApidSeqCountIn port. +// +// ValidateSeqCount.Ok +// Precondition: at least one APID is tracked in the shadow. +// Action: invoke validateApidSeqCountIn with the correct +// (shadow-expected) count; verify no event is fired. +// +// ValidateSeqCount.Failure +// Precondition: at least one APID is tracked in the shadow. +// Action: invoke validateApidSeqCountIn with a deliberately +// wrong count; verify UnexpectedSequenceCount fires +// with the transmitted and expected values. +// ====================================================================== + +#include "Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.hpp" +#include "Svc/Ccsds/Types/FppConstantsAc.hpp" + +namespace Svc { + +namespace Ccsds { + +// ---------------------------------------------------------------------- +// ValidateSeqCount.Ok +// ---------------------------------------------------------------------- + +bool ApidManagerTester::precondition__ValidateSeqCount__Ok() const { + return !this->shadow.shadow_seqCounts.empty(); +} + +void ApidManagerTester::action__ValidateSeqCount__Ok() { + 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::precondition__ValidateSeqCount__Failure() const { + return !this->shadow.shadow_seqCounts.empty(); +} + +void ApidManagerTester::action__ValidateSeqCount__Failure() { + 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..c520fb3e2b9 --- /dev/null +++ b/Svc/Ccsds/ApidManager/test/ut/TestState/TestState.cpp @@ -0,0 +1,60 @@ +// ====================================================================== +// \title TestState.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 TestState::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 TestState::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 TestState::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 TestState::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..b5ec528b4e1 --- /dev/null +++ b/Svc/Ccsds/ApidManager/test/ut/TestState/TestState.hpp @@ -0,0 +1,68 @@ +// ====================================================================== +// \title TestState.hpp +// \author thomas-bc +// \brief Shadow state model for ApidManager rule-based testing +// +// TestState 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_TestState_HPP +#define Svc_Ccsds_ApidManager_TestState_HPP + +#include + +#include "Svc/Ccsds/ApidManager/ApidManager.hpp" + +namespace Svc { + +namespace Ccsds { + +class TestState { + public: + // ---------------------------------------------------------------------- + // Shadow state + // + // These mirror the component's internal APID-to-sequence-count map. + // Preconditions check them; actions update them alongside the component. + // ---------------------------------------------------------------------- + + //! 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: + // ---------------------------------------------------------------------- + // Shadow helpers (implemented in TestState/TestState.cpp) + // ---------------------------------------------------------------------- + + //! 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..0a74cb2b7e8 --- /dev/null +++ b/TestUtils/RuleBasedTesting.hpp @@ -0,0 +1,99 @@ +// ====================================================================== +// \title RuleBasedTesting.hpp +// \brief Shared macros for Rule-Based Testing (RBT) in F Prime +// +// Background +// ---------- +// F Prime rule-based testing uses STest::Rule, where State is a +// concrete TestState class. The STest infrastructure selects rules +// whose precondition() returns true and then calls action(). +// +// The generated ASSERT_EVENTS_*, ASSERT_TLM_*, invoke_to_*, etc. macros +// all expand to `this->someMethod(...)`. They only compile in a context +// where `this` is the component tester (or a subclass of it). +// +// Common patterns +// --------------- +// 1) Composition-first (recommended for readability) +// class TestState { MyComponentTester tester; ... }; +// Rule methods call tester APIs explicitly and use explicit forwarding +// helpers for event/tlm assertions. +// +// 2) Inheritance-first (macro convenience) +// class TestState : public MyComponentTester { ... }; +// Rule methods can call generated ASSERT_* macros directly. +// +// This header is intentionally neutral and supports both patterns. +// +// Usage +// ----- +// 1. Create a TestState class (composition or inheritance). +// +// 2. Declare one method pair per rule in TestState using FW_TEST_STATE_DEF_RULE: +// +// FW_TEST_STATE_DEF_RULE(GroupName, RuleName) +// +// This expands to: +// bool precondition__GroupName__RuleName() const; +// void action__GroupName__RuleName(); +// +// 3. In Rules.hpp, define the STest::Rule subclass for each rule: +// +// FW_RULES_DEF_RULE(TestState, GroupName, RuleName) +// +// This creates: +// namespace GroupName { +// struct RuleName : STest::Rule { ... }; +// } +// +// 4. Implement the method bodies in per-group .cpp files. Inside each +// body, `this` is a TestState pointer, so ASSERT_* macros are direct. +// +// ====================================================================== + +#ifndef TestUtils_RuleBasedTesting_HPP +#define TestUtils_RuleBasedTesting_HPP + +#include "STest/Rule/Rule.hpp" + +// ----------------------------------------------------------------------- +//! \def FW_RULES_DEF_RULE +//! +//! Defines an STest::Rule subclass named RULE_NAME inside +//! namespace GROUP_NAME. The subclass forwards precondition() and +//! action() calls to the matching methods on the STATE_TYPE object, +//! which must be declared with FW_TEST_STATE_DEF_RULE and implemented +//! in a per-group .cpp file. +//! +//! \param STATE_TYPE The TestState type used by STest::Rule +//! \param GROUP_NAME Rule group: becomes a C++ namespace and name prefix +//! \param RULE_NAME Rule variant: becomes the struct name and name suffix +// ----------------------------------------------------------------------- +#define FW_RULES_DEF_RULE(STATE_TYPE, GROUP_NAME, RULE_NAME) \ + namespace GROUP_NAME { \ + \ + struct RULE_NAME : public STest::Rule { \ + RULE_NAME() : Rule(#GROUP_NAME "." #RULE_NAME) {} \ + \ + bool precondition(const STATE_TYPE& state) { return state.precondition__##GROUP_NAME##__##RULE_NAME(); } \ + \ + void action(STATE_TYPE& state) { state.action__##GROUP_NAME##__##RULE_NAME(); } \ + }; \ + } + +// ----------------------------------------------------------------------- +//! \def FW_TEST_STATE_DEF_RULE +//! +//! Declares a precondition/action method pair in a TestState class. +//! The precondition is const; the action is non-const. +//! Use this inside the TestState class body; implement both bodies in a +//! per-group .cpp file. +//! +//! \param GROUP_NAME Must match the GROUP_NAME in FW_RULES_DEF_RULE +//! \param RULE_NAME Must match the RULE_NAME in FW_RULES_DEF_RULE +// ----------------------------------------------------------------------- +#define FW_TEST_STATE_DEF_RULE(GROUP_NAME, RULE_NAME) \ + bool precondition__##GROUP_NAME##__##RULE_NAME() const; \ + void action__##GROUP_NAME##__##RULE_NAME(); + +#endif From f4fecbe9e32f51a998acaca1cd06d524d68396ee Mon Sep 17 00:00:00 2001 From: thomas-bc Date: Wed, 8 Apr 2026 13:31:13 -0700 Subject: [PATCH 02/10] change macro names --- .../ApidManager/test/ut/ApidManagerTester.hpp | 10 +++++----- Svc/Ccsds/ApidManager/test/ut/Rules/Rules.hpp | 14 ++++++------- .../test/ut/TestState/TestState.cpp | 2 +- TestUtils/RuleBasedTesting.hpp | 20 +++++++++---------- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.hpp b/Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.hpp index acbe7e28f30..26370f4dd49 100644 --- a/Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.hpp +++ b/Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.hpp @@ -67,13 +67,13 @@ class ApidManagerTester : public ApidManagerGTestBase { TestState shadow; //! Rules for the getApidSeqCountIn port - FW_TEST_STATE_DEF_RULE(GetSeqCount, Existing) - FW_TEST_STATE_DEF_RULE(GetSeqCount, NewOk) - FW_TEST_STATE_DEF_RULE(GetSeqCount, NewTableFull) + FW_RBT_DECLARE_RULE(GetSeqCount, Existing) + FW_RBT_DECLARE_RULE(GetSeqCount, NewOk) + FW_RBT_DECLARE_RULE(GetSeqCount, NewTableFull) //! Rules for the validateApidSeqCountIn port - FW_TEST_STATE_DEF_RULE(ValidateSeqCount, Ok) - FW_TEST_STATE_DEF_RULE(ValidateSeqCount, Failure) + FW_RBT_DECLARE_RULE(ValidateSeqCount, Ok) + FW_RBT_DECLARE_RULE(ValidateSeqCount, Failure) }; } // namespace Ccsds diff --git a/Svc/Ccsds/ApidManager/test/ut/Rules/Rules.hpp b/Svc/Ccsds/ApidManager/test/ut/Rules/Rules.hpp index 6e1d5141ce8..eca7bdb059a 100644 --- a/Svc/Ccsds/ApidManager/test/ut/Rules/Rules.hpp +++ b/Svc/Ccsds/ApidManager/test/ut/Rules/Rules.hpp @@ -3,11 +3,11 @@ // \author thomas-bc // \brief Rule definitions for ApidManager rule-based testing // -// FW_RULES_DEF_RULE(ApidManagerTester, GROUP, RULE) creates a +// FW_RBT_IMPLEMENT_RULE(ApidManagerTester, GROUP, RULE) creates a // STest::Rule subclass in namespace GROUP named RULE. // Its precondition() and action() delegate to the matching methods on the // TestState object. Those methods are declared in TestState.hpp via -// FW_TEST_STATE_DEF_RULE and implemented in the per-group .cpp files +// FW_RBT_DECLARE_RULE and implemented in the per-group .cpp files // listed below. // // Rule groups @@ -33,23 +33,23 @@ namespace Rules { // ---------------------------------------------------------------------- //! Get the sequence count for an APID that is already tracked -FW_RULES_DEF_RULE(ApidManagerTester, GetSeqCount, Existing) +FW_RBT_IMPLEMENT_RULE(ApidManagerTester, GetSeqCount, Existing) //! Register and get the sequence count for a new APID (table not full) -FW_RULES_DEF_RULE(ApidManagerTester, GetSeqCount, NewOk) +FW_RBT_IMPLEMENT_RULE(ApidManagerTester, GetSeqCount, NewOk) //! Attempt to get the sequence count for a new APID when the table is full -FW_RULES_DEF_RULE(ApidManagerTester, GetSeqCount, NewTableFull) +FW_RBT_IMPLEMENT_RULE(ApidManagerTester, GetSeqCount, NewTableFull) // ---------------------------------------------------------------------- // ValidateSeqCount rules // ---------------------------------------------------------------------- //! Validate the correct (expected) sequence count — no event expected -FW_RULES_DEF_RULE(ApidManagerTester, ValidateSeqCount, Ok) +FW_RBT_IMPLEMENT_RULE(ApidManagerTester, ValidateSeqCount, Ok) //! Validate a wrong sequence count — UnexpectedSequenceCount event expected -FW_RULES_DEF_RULE(ApidManagerTester, ValidateSeqCount, Failure) +FW_RBT_IMPLEMENT_RULE(ApidManagerTester, ValidateSeqCount, Failure) } // namespace Rules diff --git a/Svc/Ccsds/ApidManager/test/ut/TestState/TestState.cpp b/Svc/Ccsds/ApidManager/test/ut/TestState/TestState.cpp index c520fb3e2b9..8cc90425146 100644 --- a/Svc/Ccsds/ApidManager/test/ut/TestState/TestState.cpp +++ b/Svc/Ccsds/ApidManager/test/ut/TestState/TestState.cpp @@ -29,7 +29,7 @@ U16 TestState::shadow_getAndIncrementSeqCount(ComCfg::Apid::T apid) { // 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 0; // first count returned is 0 } return ApidManager::SEQUENCE_COUNT_ERROR; } diff --git a/TestUtils/RuleBasedTesting.hpp b/TestUtils/RuleBasedTesting.hpp index 0a74cb2b7e8..9a578502172 100644 --- a/TestUtils/RuleBasedTesting.hpp +++ b/TestUtils/RuleBasedTesting.hpp @@ -29,9 +29,9 @@ // ----- // 1. Create a TestState class (composition or inheritance). // -// 2. Declare one method pair per rule in TestState using FW_TEST_STATE_DEF_RULE: +// 2. Declare one method pair per rule in TestState using FW_RBT_DECLARE_RULE: // -// FW_TEST_STATE_DEF_RULE(GroupName, RuleName) +// FW_RBT_DECLARE_RULE(GroupName, RuleName) // // This expands to: // bool precondition__GroupName__RuleName() const; @@ -39,7 +39,7 @@ // // 3. In Rules.hpp, define the STest::Rule subclass for each rule: // -// FW_RULES_DEF_RULE(TestState, GroupName, RuleName) +// FW_RBT_IMPLEMENT_RULE(TestState, GroupName, RuleName) // // This creates: // namespace GroupName { @@ -57,19 +57,19 @@ #include "STest/Rule/Rule.hpp" // ----------------------------------------------------------------------- -//! \def FW_RULES_DEF_RULE +//! \def FW_RBT_IMPLEMENT_RULE //! //! Defines an STest::Rule subclass named RULE_NAME inside //! namespace GROUP_NAME. The subclass forwards precondition() and //! action() calls to the matching methods on the STATE_TYPE object, -//! which must be declared with FW_TEST_STATE_DEF_RULE and implemented +//! which must be declared with FW_RBT_DECLARE_RULE and implemented //! in a per-group .cpp file. //! //! \param STATE_TYPE The TestState type used by STest::Rule //! \param GROUP_NAME Rule group: becomes a C++ namespace and name prefix //! \param RULE_NAME Rule variant: becomes the struct name and name suffix // ----------------------------------------------------------------------- -#define FW_RULES_DEF_RULE(STATE_TYPE, GROUP_NAME, RULE_NAME) \ +#define FW_RBT_IMPLEMENT_RULE(STATE_TYPE, GROUP_NAME, RULE_NAME) \ namespace GROUP_NAME { \ \ struct RULE_NAME : public STest::Rule { \ @@ -82,17 +82,17 @@ } // ----------------------------------------------------------------------- -//! \def FW_TEST_STATE_DEF_RULE +//! \def FW_RBT_DECLARE_RULE //! //! Declares a precondition/action method pair in a TestState class. //! The precondition is const; the action is non-const. //! Use this inside the TestState class body; implement both bodies in a //! per-group .cpp file. //! -//! \param GROUP_NAME Must match the GROUP_NAME in FW_RULES_DEF_RULE -//! \param RULE_NAME Must match the RULE_NAME in FW_RULES_DEF_RULE +//! \param GROUP_NAME Must match the GROUP_NAME in FW_RBT_IMPLEMENT_RULE +//! \param RULE_NAME Must match the RULE_NAME in FW_RBT_IMPLEMENT_RULE // ----------------------------------------------------------------------- -#define FW_TEST_STATE_DEF_RULE(GROUP_NAME, RULE_NAME) \ +#define FW_RBT_DECLARE_RULE(GROUP_NAME, RULE_NAME) \ bool precondition__##GROUP_NAME##__##RULE_NAME() const; \ void action__##GROUP_NAME##__##RULE_NAME(); From 294598f919b20d10e48f814ad013ca8836274469 Mon Sep 17 00:00:00 2001 From: thomas-bc Date: Wed, 8 Apr 2026 13:42:49 -0700 Subject: [PATCH 03/10] cosmetic updates --- .../ApidManager/test/ut/Rules/GetSeqCount.cpp | 19 +++++++++---------- .../test/ut/Rules/ValidateSeqCount.cpp | 8 ++++---- TestUtils/RuleBasedTesting.hpp | 12 ++++++------ 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/Svc/Ccsds/ApidManager/test/ut/Rules/GetSeqCount.cpp b/Svc/Ccsds/ApidManager/test/ut/Rules/GetSeqCount.cpp index 64206943b0d..3ddb151d85b 100644 --- a/Svc/Ccsds/ApidManager/test/ut/Rules/GetSeqCount.cpp +++ b/Svc/Ccsds/ApidManager/test/ut/Rules/GetSeqCount.cpp @@ -33,11 +33,11 @@ namespace Ccsds { // GetSeqCount.Existing // ---------------------------------------------------------------------- -bool ApidManagerTester::precondition__GetSeqCount__Existing() const { +bool ApidManagerTester::GetSeqCount__Existing__precondition() const { return !this->shadow.shadow_seqCounts.empty(); } -void ApidManagerTester::action__GetSeqCount__Existing() { +void ApidManagerTester::GetSeqCount__Existing__action() { this->clearHistory(); ComCfg::Apid::T apid = this->shadow.shadow_getRandomTrackedApid(); @@ -52,11 +52,11 @@ void ApidManagerTester::action__GetSeqCount__Existing() { // GetSeqCount.NewOk // ---------------------------------------------------------------------- -bool ApidManagerTester::precondition__GetSeqCount__NewOk() const { +bool ApidManagerTester::GetSeqCount__NewOk__precondition() const { return !this->shadow.shadow_isTableFull; } -void ApidManagerTester::action__GetSeqCount__NewOk() { +void ApidManagerTester::GetSeqCount__NewOk__action() { this->clearHistory(); // Use constexpr local to avoid ODR-use of the static constexpr member @@ -71,8 +71,7 @@ void ApidManagerTester::action__GetSeqCount__NewOk() { 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_EQ(returned, expected) << "First sequence count for new APID " << static_cast(apid) << " must be 0"; ASSERT_EVENTS_SIZE(0); } @@ -80,11 +79,11 @@ void ApidManagerTester::action__GetSeqCount__NewOk() { // GetSeqCount.NewTableFull // ---------------------------------------------------------------------- -bool ApidManagerTester::precondition__GetSeqCount__NewTableFull() const { +bool ApidManagerTester::GetSeqCount__NewTableFull__precondition() const { return this->shadow.shadow_isTableFull; } -void ApidManagerTester::action__GetSeqCount__NewTableFull() { +void ApidManagerTester::GetSeqCount__NewTableFull__action() { this->clearHistory(); ComCfg::Apid::T apid = this->shadow.shadow_getRandomUntrackedApid(); @@ -92,8 +91,8 @@ void ApidManagerTester::action__GetSeqCount__NewTableFull() { // 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_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)); diff --git a/Svc/Ccsds/ApidManager/test/ut/Rules/ValidateSeqCount.cpp b/Svc/Ccsds/ApidManager/test/ut/Rules/ValidateSeqCount.cpp index b635298d8bd..6d7719e28d6 100644 --- a/Svc/Ccsds/ApidManager/test/ut/Rules/ValidateSeqCount.cpp +++ b/Svc/Ccsds/ApidManager/test/ut/Rules/ValidateSeqCount.cpp @@ -28,11 +28,11 @@ namespace Ccsds { // ValidateSeqCount.Ok // ---------------------------------------------------------------------- -bool ApidManagerTester::precondition__ValidateSeqCount__Ok() const { +bool ApidManagerTester::ValidateSeqCount__Ok__precondition() const { return !this->shadow.shadow_seqCounts.empty(); } -void ApidManagerTester::action__ValidateSeqCount__Ok() { +void ApidManagerTester::ValidateSeqCount__Ok__action() { this->clearHistory(); ComCfg::Apid::T apid = this->shadow.shadow_getRandomTrackedApid(); @@ -47,11 +47,11 @@ void ApidManagerTester::action__ValidateSeqCount__Ok() { // ValidateSeqCount.Failure // ---------------------------------------------------------------------- -bool ApidManagerTester::precondition__ValidateSeqCount__Failure() const { +bool ApidManagerTester::ValidateSeqCount__Failure__precondition() const { return !this->shadow.shadow_seqCounts.empty(); } -void ApidManagerTester::action__ValidateSeqCount__Failure() { +void ApidManagerTester::ValidateSeqCount__Failure__action() { this->clearHistory(); ComCfg::Apid::T apid = this->shadow.shadow_getRandomTrackedApid(); diff --git a/TestUtils/RuleBasedTesting.hpp b/TestUtils/RuleBasedTesting.hpp index 9a578502172..6f7c509c417 100644 --- a/TestUtils/RuleBasedTesting.hpp +++ b/TestUtils/RuleBasedTesting.hpp @@ -34,8 +34,8 @@ // FW_RBT_DECLARE_RULE(GroupName, RuleName) // // This expands to: -// bool precondition__GroupName__RuleName() const; -// void action__GroupName__RuleName(); +// bool GroupName__RuleName__precondition() const; +// void GroupName__RuleName__action(); // // 3. In Rules.hpp, define the STest::Rule subclass for each rule: // @@ -75,9 +75,9 @@ struct RULE_NAME : public STest::Rule { \ RULE_NAME() : Rule(#GROUP_NAME "." #RULE_NAME) {} \ \ - bool precondition(const STATE_TYPE& state) { return state.precondition__##GROUP_NAME##__##RULE_NAME(); } \ + bool precondition(const STATE_TYPE& state) { return state.GROUP_NAME##__##RULE_NAME##__precondition(); } \ \ - void action(STATE_TYPE& state) { state.action__##GROUP_NAME##__##RULE_NAME(); } \ + void action(STATE_TYPE& state) { state.GROUP_NAME##__##RULE_NAME##__action(); } \ }; \ } @@ -93,7 +93,7 @@ //! \param RULE_NAME Must match the RULE_NAME in FW_RBT_IMPLEMENT_RULE // ----------------------------------------------------------------------- #define FW_RBT_DECLARE_RULE(GROUP_NAME, RULE_NAME) \ - bool precondition__##GROUP_NAME##__##RULE_NAME() const; \ - void action__##GROUP_NAME##__##RULE_NAME(); + bool GROUP_NAME##__##RULE_NAME##__precondition() const; \ + void GROUP_NAME##__##RULE_NAME##__action(); #endif From d74e1e4f616bd76ca680446ac5e60692191d2de9 Mon Sep 17 00:00:00 2001 From: thomas-bc Date: Wed, 8 Apr 2026 15:21:34 -0700 Subject: [PATCH 04/10] Mono-macro implementation --- .../test/ut/ApidManagerTestMain.cpp | 21 ++++--- .../ApidManager/test/ut/ApidManagerTester.hpp | 18 +++--- Svc/Ccsds/ApidManager/test/ut/Rules/Rules.hpp | 60 ------------------- .../test/ut/TestState/TestState.cpp | 10 ++-- .../test/ut/TestState/TestState.hpp | 12 ++-- TestUtils/RuleBasedTesting.hpp | 46 ++++++++++---- 6 files changed, 63 insertions(+), 104 deletions(-) delete mode 100644 Svc/Ccsds/ApidManager/test/ut/Rules/Rules.hpp diff --git a/Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp b/Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp index 09a4dc14ae1..612d9345e67 100644 --- a/Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp +++ b/Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp @@ -8,7 +8,6 @@ #include "STest/Scenario/BoundedScenario.hpp" #include "STest/Scenario/RandomScenario.hpp" #include "Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.hpp" -#include "Svc/Ccsds/ApidManager/test/ut/Rules/Rules.hpp" namespace Svc { @@ -22,8 +21,8 @@ namespace Ccsds { // incrementing counts on subsequent calls. TEST(ApidManager, GetSequenceCounts) { ApidManagerTester state; - Rules::GetSeqCount::NewOk ruleNewOk; - Rules::GetSeqCount::Existing ruleExisting; + ApidManagerTester::GetSeqCount__NewOk ruleNewOk; + ApidManagerTester::GetSeqCount__Existing ruleExisting; ruleNewOk.apply(state); // register a new APID; expect count 0 ruleExisting.apply(state); // retrieve count for the same APID; expect count 1 } @@ -32,9 +31,9 @@ TEST(ApidManager, GetSequenceCounts) { // and fires UnexpectedSequenceCount on a mismatch. TEST(ApidManager, ValidateSequenceCounts) { ApidManagerTester state; - Rules::GetSeqCount::NewOk ruleNewOk; - Rules::ValidateSeqCount::Ok ruleValidateOk; - Rules::ValidateSeqCount::Failure ruleValidateFailure; + ApidManagerTester::GetSeqCount__NewOk ruleNewOk; + ApidManagerTester::ValidateSeqCount__Ok ruleValidateOk; + ApidManagerTester::ValidateSeqCount__Failure ruleValidateFailure; ruleNewOk.apply(state); // register an APID so validate rules can fire ruleValidateOk.apply(state); // validate correct count; no event expected ruleValidateFailure.apply(state); // validate wrong count; event expected @@ -44,11 +43,11 @@ TEST(ApidManager, ValidateSequenceCounts) { // all state transitions across the APID sequence-count lifecycle. TEST(ApidManager, RandomizedTesting) { ApidManagerTester state; - Rules::GetSeqCount::Existing ruleGetExisting; - Rules::GetSeqCount::NewOk ruleGetNewOk; - Rules::GetSeqCount::NewTableFull ruleGetNewTableFull; - Rules::ValidateSeqCount::Ok ruleValidateOk; - Rules::ValidateSeqCount::Failure ruleValidateFailure; + ApidManagerTester::GetSeqCount__Existing ruleGetExisting; + ApidManagerTester::GetSeqCount__NewOk ruleGetNewOk; + ApidManagerTester::GetSeqCount__NewTableFull ruleGetNewTableFull; + ApidManagerTester::ValidateSeqCount__Ok ruleValidateOk; + ApidManagerTester::ValidateSeqCount__Failure ruleValidateFailure; STest::Rule* rules[] = { &ruleGetExisting, &ruleGetNewOk, &ruleGetNewTableFull, &ruleValidateOk, &ruleValidateFailure, diff --git a/Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.hpp b/Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.hpp index 26370f4dd49..a047b56a624 100644 --- a/Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.hpp +++ b/Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.hpp @@ -58,22 +58,22 @@ class ApidManagerTester : public ApidManagerGTestBase { //! The component under test ApidManager component; + //! Shadow state for rule-based testing + ApidManagerTestState shadow; + public: // ---------------------------------------------------------------------- - // Shadow state and rule method declarations + // Rule Based Testing // ---------------------------------------------------------------------- - //! Shadow model used by precondition/action rule methods - TestState shadow; - //! Rules for the getApidSeqCountIn port - FW_RBT_DECLARE_RULE(GetSeqCount, Existing) - FW_RBT_DECLARE_RULE(GetSeqCount, NewOk) - FW_RBT_DECLARE_RULE(GetSeqCount, NewTableFull) + FW_RBT_DEFINE_RULE(ApidManagerTester, GetSeqCount, Existing); + FW_RBT_DEFINE_RULE(ApidManagerTester, GetSeqCount, NewOk); + FW_RBT_DEFINE_RULE(ApidManagerTester, GetSeqCount, NewTableFull); //! Rules for the validateApidSeqCountIn port - FW_RBT_DECLARE_RULE(ValidateSeqCount, Ok) - FW_RBT_DECLARE_RULE(ValidateSeqCount, Failure) + 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/Rules.hpp b/Svc/Ccsds/ApidManager/test/ut/Rules/Rules.hpp deleted file mode 100644 index eca7bdb059a..00000000000 --- a/Svc/Ccsds/ApidManager/test/ut/Rules/Rules.hpp +++ /dev/null @@ -1,60 +0,0 @@ -// ====================================================================== -// \title Rules.hpp -// \author thomas-bc -// \brief Rule definitions for ApidManager rule-based testing -// -// FW_RBT_IMPLEMENT_RULE(ApidManagerTester, GROUP, RULE) creates a -// STest::Rule subclass in namespace GROUP named RULE. -// Its precondition() and action() delegate to the matching methods on the -// TestState object. Those methods are declared in TestState.hpp via -// FW_RBT_DECLARE_RULE and implemented in the per-group .cpp files -// listed below. -// -// Rule groups -// ----------- -// GetSeqCount — exercises getApidSeqCountIn (GetSeqCount.cpp) -// ValidateSeqCount — exercises validateApidSeqCountIn (ValidateSeqCount.cpp) -// ====================================================================== - -#ifndef Svc_Ccsds_ApidManager_Rules_HPP -#define Svc_Ccsds_ApidManager_Rules_HPP - -#include "Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.hpp" -#include "TestUtils/RuleBasedTesting.hpp" - -namespace Svc { - -namespace Ccsds { - -namespace Rules { - -// ---------------------------------------------------------------------- -// GetSeqCount rules -// ---------------------------------------------------------------------- - -//! Get the sequence count for an APID that is already tracked -FW_RBT_IMPLEMENT_RULE(ApidManagerTester, GetSeqCount, Existing) - -//! Register and get the sequence count for a new APID (table not full) -FW_RBT_IMPLEMENT_RULE(ApidManagerTester, GetSeqCount, NewOk) - -//! Attempt to get the sequence count for a new APID when the table is full -FW_RBT_IMPLEMENT_RULE(ApidManagerTester, GetSeqCount, NewTableFull) - -// ---------------------------------------------------------------------- -// ValidateSeqCount rules -// ---------------------------------------------------------------------- - -//! Validate the correct (expected) sequence count — no event expected -FW_RBT_IMPLEMENT_RULE(ApidManagerTester, ValidateSeqCount, Ok) - -//! Validate a wrong sequence count — UnexpectedSequenceCount event expected -FW_RBT_IMPLEMENT_RULE(ApidManagerTester, ValidateSeqCount, Failure) - -} // namespace Rules - -} // namespace Ccsds - -} // namespace Svc - -#endif diff --git a/Svc/Ccsds/ApidManager/test/ut/TestState/TestState.cpp b/Svc/Ccsds/ApidManager/test/ut/TestState/TestState.cpp index 8cc90425146..2e6c65ae624 100644 --- a/Svc/Ccsds/ApidManager/test/ut/TestState/TestState.cpp +++ b/Svc/Ccsds/ApidManager/test/ut/TestState/TestState.cpp @@ -1,5 +1,5 @@ // ====================================================================== -// \title TestState.cpp +// \title ApidManagerTestState.cpp // \author thomas-bc // \brief Shadow helper implementations for ApidManager test state // ====================================================================== @@ -18,7 +18,7 @@ namespace Ccsds { // Shadow helpers // ---------------------------------------------------------------------- -U16 TestState::shadow_getAndIncrementSeqCount(ComCfg::Apid::T apid) { +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 @@ -34,20 +34,20 @@ U16 TestState::shadow_getAndIncrementSeqCount(ComCfg::Apid::T apid) { return ApidManager::SEQUENCE_COUNT_ERROR; } -void TestState::shadow_validateApidSeqCount(ComCfg::Apid::T apid, U16 expectedSeqCount) { +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 TestState::shadow_getRandomTrackedApid() const { +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 TestState::shadow_getRandomUntrackedApid() const { +ComCfg::Apid::T ApidManagerTestState::shadow_getRandomUntrackedApid() const { ComCfg::Apid::T apid; do { apid = static_cast(STest::Random::lowerUpper(10, ComCfg::Apid::SPP_IDLE_PACKET)); diff --git a/Svc/Ccsds/ApidManager/test/ut/TestState/TestState.hpp b/Svc/Ccsds/ApidManager/test/ut/TestState/TestState.hpp index b5ec528b4e1..9a9d907a094 100644 --- a/Svc/Ccsds/ApidManager/test/ut/TestState/TestState.hpp +++ b/Svc/Ccsds/ApidManager/test/ut/TestState/TestState.hpp @@ -1,9 +1,9 @@ // ====================================================================== -// \title TestState.hpp +// \title ApidManagerTestState.hpp // \author thomas-bc // \brief Shadow state model for ApidManager rule-based testing // -// TestState contains only shadow-model data and state-only helpers. +// ApidManagerTestState contains only shadow-model data and state-only helpers. // Rule preconditions/actions are implemented on ApidManagerTester. // // Shadow state @@ -14,8 +14,8 @@ // component. After every action the shadow and the component must agree. // ====================================================================== -#ifndef Svc_Ccsds_ApidManager_TestState_HPP -#define Svc_Ccsds_ApidManager_TestState_HPP +#ifndef Svc_Ccsds_ApidManager_ApidManagerTestState_HPP +#define Svc_Ccsds_ApidManager_ApidManagerTestState_HPP #include @@ -25,7 +25,7 @@ namespace Svc { namespace Ccsds { -class TestState { +class ApidManagerTestState { public: // ---------------------------------------------------------------------- // Shadow state @@ -43,7 +43,7 @@ class TestState { public: // ---------------------------------------------------------------------- - // Shadow helpers (implemented in TestState/TestState.cpp) + // Shadow helpers (implemented in ApidManagerTestState/ApidManagerTestState.cpp) // ---------------------------------------------------------------------- //! Return the current expected sequence count for apid and advance the diff --git a/TestUtils/RuleBasedTesting.hpp b/TestUtils/RuleBasedTesting.hpp index 6f7c509c417..a60e506ab6e 100644 --- a/TestUtils/RuleBasedTesting.hpp +++ b/TestUtils/RuleBasedTesting.hpp @@ -29,24 +29,16 @@ // ----- // 1. Create a TestState class (composition or inheritance). // -// 2. Declare one method pair per rule in TestState using FW_RBT_DECLARE_RULE: +// 2. Define each rule inside TestState with FW_RBT_DEFINE_RULE: // -// FW_RBT_DECLARE_RULE(GroupName, RuleName) +// FW_RBT_DEFINE_RULE(TestState, GroupName, RuleName) // -// This expands to: +// This creates: // bool GroupName__RuleName__precondition() const; // void GroupName__RuleName__action(); +// struct GroupName__RuleName : STest::Rule { ... }; // -// 3. In Rules.hpp, define the STest::Rule subclass for each rule: -// -// FW_RBT_IMPLEMENT_RULE(TestState, GroupName, RuleName) -// -// This creates: -// namespace GroupName { -// struct RuleName : STest::Rule { ... }; -// } -// -// 4. Implement the method bodies in per-group .cpp files. Inside each +// 3. Implement the method bodies in per-group .cpp files. Inside each // body, `this` is a TestState pointer, so ASSERT_* macros are direct. // // ====================================================================== @@ -56,6 +48,34 @@ #include "STest/Rule/Rule.hpp" +// ----------------------------------------------------------------------- +//! \def FW_RBT_DEFINE_RULE +//! +//! Defines everything needed for one rule inside a TestState class: +//! 1) precondition declaration +//! 2) action declaration +//! 3) a nested STest::Rule subclass named GROUP_NAME__RULE_NAME +//! +//! This allows users to keep all rule declarations and rule types in one +//! place (the tester header) without creating a separate Rules.hpp file. +//! +//! \param STATE_TYPE The TestState type used by STest::Rule +//! \param GROUP_NAME Rule group: used in method/rule names and rule label +//! \param RULE_NAME Rule variant: used in method/rule names and rule label +// ----------------------------------------------------------------------- +#define FW_RBT_DEFINE_RULE(STATE_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 STATE_TYPE& state) override { \ + return state.GROUP_NAME##__##RULE_NAME##__precondition(); \ + } \ + \ + void action(STATE_TYPE& state) override { state.GROUP_NAME##__##RULE_NAME##__action(); } \ + } + // ----------------------------------------------------------------------- //! \def FW_RBT_IMPLEMENT_RULE //! From 473ebf7b9cc529e15368730c61dac985b77cffe9 Mon Sep 17 00:00:00 2001 From: thomas-bc Date: Wed, 8 Apr 2026 16:06:54 -0700 Subject: [PATCH 05/10] nits --- .../test/ut/ApidManagerTestMain.cpp | 18 +++++++++--------- TestUtils/RuleBasedTesting.hpp | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp b/Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp index 612d9345e67..999a2048354 100644 --- a/Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp +++ b/Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp @@ -20,29 +20,29 @@ namespace Ccsds { // Verify that getApidSeqCountIn registers a new APID and returns // incrementing counts on subsequent calls. TEST(ApidManager, GetSequenceCounts) { - ApidManagerTester state; + ApidManagerTester tester; ApidManagerTester::GetSeqCount__NewOk ruleNewOk; ApidManagerTester::GetSeqCount__Existing ruleExisting; - ruleNewOk.apply(state); // register a new APID; expect count 0 - ruleExisting.apply(state); // retrieve count for the same APID; expect count 1 + 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) { - ApidManagerTester state; + ApidManagerTester tester; ApidManagerTester::GetSeqCount__NewOk ruleNewOk; ApidManagerTester::ValidateSeqCount__Ok ruleValidateOk; ApidManagerTester::ValidateSeqCount__Failure ruleValidateFailure; - ruleNewOk.apply(state); // register an APID so validate rules can fire - ruleValidateOk.apply(state); // validate correct count; no event expected - ruleValidateFailure.apply(state); // validate wrong count; event expected + 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 test: apply rules in a bounded random sequence to exercise // all state transitions across the APID sequence-count lifecycle. TEST(ApidManager, RandomizedTesting) { - ApidManagerTester state; + ApidManagerTester tester; ApidManagerTester::GetSeqCount__Existing ruleGetExisting; ApidManagerTester::GetSeqCount__NewOk ruleGetNewOk; ApidManagerTester::GetSeqCount__NewTableFull ruleGetNewTableFull; @@ -55,7 +55,7 @@ TEST(ApidManager, RandomizedTesting) { STest::RandomScenario random("Random Rules", rules, FW_NUM_ARRAY_ELEMENTS(rules)); STest::BoundedScenario bounded("Bounded Random Rules Scenario", random, 10000); - const U32 numSteps = bounded.run(state); + const U32 numSteps = bounded.run(tester); printf("Ran %u steps.\n", numSteps); } diff --git a/TestUtils/RuleBasedTesting.hpp b/TestUtils/RuleBasedTesting.hpp index a60e506ab6e..c34b51dab22 100644 --- a/TestUtils/RuleBasedTesting.hpp +++ b/TestUtils/RuleBasedTesting.hpp @@ -29,9 +29,9 @@ // ----- // 1. Create a TestState class (composition or inheritance). // -// 2. Define each rule inside TestState with FW_RBT_DEFINE_RULE: +// 2. Define each rule inside a ComponentTester with FW_RBT_DEFINE_RULE: // -// FW_RBT_DEFINE_RULE(TestState, GroupName, RuleName) +// FW_RBT_DEFINE_RULE(MyComponentTester, GroupName, RuleName) // // This creates: // bool GroupName__RuleName__precondition() const; @@ -59,7 +59,7 @@ //! This allows users to keep all rule declarations and rule types in one //! place (the tester header) without creating a separate Rules.hpp file. //! -//! \param STATE_TYPE The TestState type used by STest::Rule +//! \param STATE_TYPE The TestState type used by STest::Rule (usually a ComponentTester class) //! \param GROUP_NAME Rule group: used in method/rule names and rule label //! \param RULE_NAME Rule variant: used in method/rule names and rule label // ----------------------------------------------------------------------- From dbe79ef9c868f4d0ddab11a951660066d015cda4 Mon Sep 17 00:00:00 2001 From: thomas-bc Date: Wed, 8 Apr 2026 16:32:57 -0700 Subject: [PATCH 06/10] Update docstrings --- .../test/ut/TestState/TestState.hpp | 12 +- TestUtils/RuleBasedTesting.hpp | 129 ++++++------------ 2 files changed, 48 insertions(+), 93 deletions(-) diff --git a/Svc/Ccsds/ApidManager/test/ut/TestState/TestState.hpp b/Svc/Ccsds/ApidManager/test/ut/TestState/TestState.hpp index 9a9d907a094..f92a6a42f32 100644 --- a/Svc/Ccsds/ApidManager/test/ut/TestState/TestState.hpp +++ b/Svc/Ccsds/ApidManager/test/ut/TestState/TestState.hpp @@ -28,10 +28,9 @@ namespace Ccsds { class ApidManagerTestState { public: // ---------------------------------------------------------------------- - // Shadow state + // Component "shadow" test state // - // These mirror the component's internal APID-to-sequence-count map. - // Preconditions check them; actions update them alongside the component. + // These state variables mirror the component's internal state (APID map, etc). // ---------------------------------------------------------------------- //! Expected next sequence count for every tracked APID. @@ -42,9 +41,10 @@ class ApidManagerTestState { bool shadow_isTableFull = false; public: - // ---------------------------------------------------------------------- - // Shadow helpers (implemented in ApidManagerTestState/ApidManagerTestState.cpp) - // ---------------------------------------------------------------------- + // ----------------------------------------------------------------------------- + // 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. diff --git a/TestUtils/RuleBasedTesting.hpp b/TestUtils/RuleBasedTesting.hpp index c34b51dab22..bfbbcdb0490 100644 --- a/TestUtils/RuleBasedTesting.hpp +++ b/TestUtils/RuleBasedTesting.hpp @@ -4,42 +4,37 @@ // // Background // ---------- -// F Prime rule-based testing uses STest::Rule, where State is a -// concrete TestState class. The STest infrastructure selects rules -// whose precondition() returns true and then calls action(). +// 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(). // -// The generated ASSERT_EVENTS_*, ASSERT_TLM_*, invoke_to_*, etc. macros -// all expand to `this->someMethod(...)`. They only compile in a context -// where `this` is the component tester (or a subclass of it). -// -// Common patterns -// --------------- -// 1) Composition-first (recommended for readability) -// class TestState { MyComponentTester tester; ... }; -// Rule methods call tester APIs explicitly and use explicit forwarding -// helpers for event/tlm assertions. -// -// 2) Inheritance-first (macro convenience) -// class TestState : public MyComponentTester { ... }; -// Rule methods can call generated ASSERT_* macros directly. -// -// This header is intentionally neutral and supports both patterns. +// 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. Create a TestState class (composition or inheritance). +// 1. Inside a ComponentTester class, declare each rule with FW_RBT_DEFINE_RULE: // -// 2. Define each rule inside a ComponentTester with FW_RBT_DEFINE_RULE: +// FW_RBT_DEFINE_RULE(MyComponentTester, GroupName, RuleName); // -// FW_RBT_DEFINE_RULE(MyComponentTester, GroupName, RuleName) -// -// This creates: +// This creates inside the tester class: // bool GroupName__RuleName__precondition() const; // void GroupName__RuleName__action(); -// struct GroupName__RuleName : STest::Rule { ... }; +// struct GroupName__RuleName : STest::Rule { ... }; +// +// 2. Implement the method bodies in .cpp files (and add *.cpp to CMakeLists.txt !!) // -// 3. Implement the method bodies in per-group .cpp files. Inside each -// body, `this` is a TestState pointer, so ASSERT_* macros are direct. +// 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); // // ====================================================================== @@ -51,69 +46,29 @@ // ----------------------------------------------------------------------- //! \def FW_RBT_DEFINE_RULE //! -//! Defines everything needed for one rule inside a TestState class: -//! 1) precondition declaration -//! 2) action declaration -//! 3) a nested STest::Rule subclass named GROUP_NAME__RULE_NAME -//! -//! This allows users to keep all rule declarations and rule types in one -//! place (the tester header) without creating a separate Rules.hpp file. +//! 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 //! -//! \param STATE_TYPE The TestState type used by STest::Rule (usually a ComponentTester class) -//! \param GROUP_NAME Rule group: used in method/rule names and rule label -//! \param RULE_NAME Rule variant: used in method/rule names and rule label -// ----------------------------------------------------------------------- -#define FW_RBT_DEFINE_RULE(STATE_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 STATE_TYPE& state) override { \ - return state.GROUP_NAME##__##RULE_NAME##__precondition(); \ - } \ - \ - void action(STATE_TYPE& state) override { state.GROUP_NAME##__##RULE_NAME##__action(); } \ - } - -// ----------------------------------------------------------------------- -//! \def FW_RBT_IMPLEMENT_RULE -//! -//! Defines an STest::Rule subclass named RULE_NAME inside -//! namespace GROUP_NAME. The subclass forwards precondition() and -//! action() calls to the matching methods on the STATE_TYPE object, -//! which must be declared with FW_RBT_DECLARE_RULE and implemented -//! in a per-group .cpp file. -//! -//! \param STATE_TYPE The TestState type used by STest::Rule -//! \param GROUP_NAME Rule group: becomes a C++ namespace and name prefix -//! \param RULE_NAME Rule variant: becomes the struct name and name suffix -// ----------------------------------------------------------------------- -#define FW_RBT_IMPLEMENT_RULE(STATE_TYPE, GROUP_NAME, RULE_NAME) \ - namespace GROUP_NAME { \ - \ - struct RULE_NAME : public STest::Rule { \ - RULE_NAME() : Rule(#GROUP_NAME "." #RULE_NAME) {} \ - \ - bool precondition(const STATE_TYPE& state) { return state.GROUP_NAME##__##RULE_NAME##__precondition(); } \ - \ - void action(STATE_TYPE& state) { state.GROUP_NAME##__##RULE_NAME##__action(); } \ - }; \ - } - -// ----------------------------------------------------------------------- -//! \def FW_RBT_DECLARE_RULE -//! -//! Declares a precondition/action method pair in a TestState class. -//! The precondition is const; the action is non-const. -//! Use this inside the TestState class body; implement both bodies in a +//! Place inside the tester class body; implement both method bodies in a //! per-group .cpp file. //! -//! \param GROUP_NAME Must match the GROUP_NAME in FW_RBT_IMPLEMENT_RULE -//! \param RULE_NAME Must match the RULE_NAME in FW_RBT_IMPLEMENT_RULE +//! \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_DECLARE_RULE(GROUP_NAME, RULE_NAME) \ - bool GROUP_NAME##__##RULE_NAME##__precondition() const; \ - void GROUP_NAME##__##RULE_NAME##__action(); +#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 From ed4e7c7434b34a3bd63c07d5363b25a3fd6d6328 Mon Sep 17 00:00:00 2001 From: thomas-bc Date: Wed, 8 Apr 2026 16:41:32 -0700 Subject: [PATCH 07/10] Remove extra comments --- .../ApidManager/test/ut/ApidManagerTestMain.cpp | 7 +++---- .../ApidManager/test/ut/Rules/GetSeqCount.cpp | 16 ---------------- .../test/ut/Rules/ValidateSeqCount.cpp | 11 ----------- 3 files changed, 3 insertions(+), 31 deletions(-) diff --git a/Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp b/Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp index 999a2048354..c857184381a 100644 --- a/Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp +++ b/Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp @@ -39,9 +39,9 @@ TEST(ApidManager, ValidateSequenceCounts) { ruleValidateFailure.apply(tester); // validate wrong count; event expected } -// Randomized test: apply rules in a bounded random sequence to exercise -// all state transitions across the APID sequence-count lifecycle. +// Randomized test: apply rules in a random sequence for a large number of iterations TEST(ApidManager, RandomizedTesting) { + FwSizeType numRulesToApply = 10000; ApidManagerTester tester; ApidManagerTester::GetSeqCount__Existing ruleGetExisting; ApidManagerTester::GetSeqCount__NewOk ruleGetNewOk; @@ -54,13 +54,12 @@ TEST(ApidManager, RandomizedTesting) { }; STest::RandomScenario random("Random Rules", rules, FW_NUM_ARRAY_ELEMENTS(rules)); - STest::BoundedScenario bounded("Bounded Random Rules Scenario", random, 10000); + STest::BoundedScenario bounded("Bounded Random Rules Scenario", random, numRulesToApply); const U32 numSteps = bounded.run(tester); printf("Ran %u steps.\n", numSteps); } } // namespace Ccsds - } // namespace Svc int main(int argc, char** argv) { diff --git a/Svc/Ccsds/ApidManager/test/ut/Rules/GetSeqCount.cpp b/Svc/Ccsds/ApidManager/test/ut/Rules/GetSeqCount.cpp index 3ddb151d85b..57d12e65a79 100644 --- a/Svc/Ccsds/ApidManager/test/ut/Rules/GetSeqCount.cpp +++ b/Svc/Ccsds/ApidManager/test/ut/Rules/GetSeqCount.cpp @@ -4,22 +4,6 @@ // \brief Rule implementations for the GetSeqCount rule group // // These rules exercise the getApidSeqCountIn port. -// -// GetSeqCount.Existing -// Precondition: at least one APID is tracked in the shadow. -// Action: invoke getApidSeqCountIn for a random tracked APID -// and verify the returned count matches the shadow. -// -// GetSeqCount.NewOk -// Precondition: the APID table is not full. -// Action: invoke getApidSeqCountIn for an untracked APID; -// expect it to be registered at sequence count 0 -// with no events fired. -// -// GetSeqCount.NewTableFull -// Precondition: the APID table is full. -// Action: invoke getApidSeqCountIn for an untracked APID; -// expect SEQUENCE_COUNT_ERROR and an ApidTableFull event. // ====================================================================== #include "Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.hpp" diff --git a/Svc/Ccsds/ApidManager/test/ut/Rules/ValidateSeqCount.cpp b/Svc/Ccsds/ApidManager/test/ut/Rules/ValidateSeqCount.cpp index 6d7719e28d6..8728847ebca 100644 --- a/Svc/Ccsds/ApidManager/test/ut/Rules/ValidateSeqCount.cpp +++ b/Svc/Ccsds/ApidManager/test/ut/Rules/ValidateSeqCount.cpp @@ -4,17 +4,6 @@ // \brief Rule implementations for the ValidateSeqCount rule group // // These rules exercise the validateApidSeqCountIn port. -// -// ValidateSeqCount.Ok -// Precondition: at least one APID is tracked in the shadow. -// Action: invoke validateApidSeqCountIn with the correct -// (shadow-expected) count; verify no event is fired. -// -// ValidateSeqCount.Failure -// Precondition: at least one APID is tracked in the shadow. -// Action: invoke validateApidSeqCountIn with a deliberately -// wrong count; verify UnexpectedSequenceCount fires -// with the transmitted and expected values. // ====================================================================== #include "Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.hpp" From 16d6e3a6b0ee4ab5a24493c8c10a5ca01c10aca9 Mon Sep 17 00:00:00 2001 From: thomas-bc Date: Wed, 8 Apr 2026 16:57:25 -0700 Subject: [PATCH 08/10] switch numRulesToApply to U32 --- Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp b/Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp index c857184381a..66d253ea50b 100644 --- a/Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp +++ b/Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp @@ -41,7 +41,7 @@ TEST(ApidManager, ValidateSequenceCounts) { // Randomized test: apply rules in a random sequence for a large number of iterations TEST(ApidManager, RandomizedTesting) { - FwSizeType numRulesToApply = 10000; + U32 numRulesToApply = 10000; ApidManagerTester tester; ApidManagerTester::GetSeqCount__Existing ruleGetExisting; ApidManagerTester::GetSeqCount__NewOk ruleGetNewOk; From d1229d5519b9775ccd44c4f513d41f054c1c3d59 Mon Sep 17 00:00:00 2001 From: thomas-bc Date: Thu, 9 Apr 2026 10:39:27 -0700 Subject: [PATCH 09/10] fix formatting --- TestUtils/RuleBasedTesting.hpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/TestUtils/RuleBasedTesting.hpp b/TestUtils/RuleBasedTesting.hpp index bfbbcdb0490..78be76d98b3 100644 --- a/TestUtils/RuleBasedTesting.hpp +++ b/TestUtils/RuleBasedTesting.hpp @@ -58,17 +58,17 @@ //! \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(); } \ +#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 From 0aa38c2ce44686024af7fe1312b6a82a38d1ab56 Mon Sep 17 00:00:00 2001 From: thomas-bc Date: Thu, 9 Apr 2026 10:54:46 -0700 Subject: [PATCH 10/10] Bring tester into scope with using --- Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp b/Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp index 66d253ea50b..5b3dc4fb0ad 100644 --- a/Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp +++ b/Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp @@ -9,9 +9,7 @@ #include "STest/Scenario/RandomScenario.hpp" #include "Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.hpp" -namespace Svc { - -namespace Ccsds { +using Svc::Ccsds::ApidManagerTester; // ---------------------------------------------------------------------- // Tests @@ -59,9 +57,6 @@ TEST(ApidManager, RandomizedTesting) { printf("Ran %u steps.\n", numSteps); } -} // namespace Ccsds -} // namespace Svc - int main(int argc, char** argv) { STest::Random::seed(); ::testing::InitGoogleTest(&argc, argv);