Skip to content

Commit b40cdc0

Browse files
thomas-bcLeStarchCopilot
authored
Add common infrastructure for Rule-Based Testing (#4977)
* First pass at enhanced system * change macro names * cosmetic updates * Mono-macro implementation * nits * Update docstrings * Remove extra comments * switch numRulesToApply to U32 * fix formatting * Bring tester into scope with using * Update Svc/Ccsds/ApidManager/test/ut/TestState/TestState.hpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Svc/Ccsds/ApidManager/test/ut/TestState/TestState.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Address review comments * nits * formatting --------- Co-authored-by: M Starch <LeStarch@googlemail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 5c15d7a commit b40cdc0

9 files changed

Lines changed: 412 additions & 231 deletions

File tree

Svc/Ccsds/ApidManager/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ register_fprime_ut(
2424
SOURCES
2525
"${CMAKE_CURRENT_LIST_DIR}/test/ut/ApidManagerTestMain.cpp"
2626
"${CMAKE_CURRENT_LIST_DIR}/test/ut/ApidManagerTester.cpp"
27+
"${CMAKE_CURRENT_LIST_DIR}/test/ut/TestState/TestState.cpp"
28+
"${CMAKE_CURRENT_LIST_DIR}/test/ut/Rules/GetSeqCount.cpp"
29+
"${CMAKE_CURRENT_LIST_DIR}/test/ut/Rules/ValidateSeqCount.cpp"
2730
AUTOCODER_INPUTS
2831
"${CMAKE_CURRENT_LIST_DIR}/ApidManager.fpp"
2932
DEPENDS

Svc/Ccsds/ApidManager/test/ut/ApidManagerTestMain.cpp

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,47 +4,55 @@
44
// \brief cpp file for ApidManager component test main function
55
// ======================================================================
66

7-
#include "ApidManagerTester.hpp"
7+
#include "STest/Random/Random.hpp"
88
#include "STest/Scenario/BoundedScenario.hpp"
99
#include "STest/Scenario/RandomScenario.hpp"
10+
#include "Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.hpp"
1011

12+
using Svc::Ccsds::ApidManagerTester;
13+
14+
// ----------------------------------------------------------------------
15+
// Tests
16+
// ----------------------------------------------------------------------
17+
18+
// Verify that getApidSeqCountIn registers a new APID and returns
19+
// incrementing counts on subsequent calls.
1120
TEST(ApidManager, GetSequenceCounts) {
12-
Svc::Ccsds::ApidManagerTester tester;
13-
Svc::Ccsds::ApidManagerTester::GetExistingSeqCount getExistingSeqCount;
14-
Svc::Ccsds::ApidManagerTester::GetNewSeqCountOk getNewSeqCountOk;
15-
getExistingSeqCount.apply(tester);
16-
getNewSeqCountOk.apply(tester);
21+
ApidManagerTester tester;
22+
ApidManagerTester::GetSeqCount__NewOk ruleNewOk;
23+
ApidManagerTester::GetSeqCount__Existing ruleExisting;
24+
ruleNewOk.apply(tester); // register a new APID; expect count 0
25+
ruleExisting.apply(tester); // retrieve count for the same APID; expect count 1
1726
}
1827

28+
// Verify that validateApidSeqCountIn fires no event on a matching count
29+
// and fires UnexpectedSequenceCount on a mismatch.
1930
TEST(ApidManager, ValidateSequenceCounts) {
20-
Svc::Ccsds::ApidManagerTester tester;
21-
Svc::Ccsds::ApidManagerTester::ValidateSeqCountOk validateSeqCountOkRule;
22-
Svc::Ccsds::ApidManagerTester::ValidateSeqCountFailure validateSeqCountFailureRule;
23-
validateSeqCountOkRule.apply(tester);
24-
validateSeqCountFailureRule.apply(tester);
31+
ApidManagerTester tester;
32+
ApidManagerTester::GetSeqCount__NewOk ruleNewOk;
33+
ApidManagerTester::ValidateSeqCount__Ok ruleValidateOk;
34+
ApidManagerTester::ValidateSeqCount__Failure ruleValidateFailure;
35+
ruleNewOk.apply(tester); // register an APID so validate rules can fire
36+
ruleValidateOk.apply(tester); // validate correct count; no event expected
37+
ruleValidateFailure.apply(tester); // validate wrong count; event expected
2538
}
2639

27-
// Randomized testing
40+
// Randomized test: apply rules in a random sequence for a large number of iterations
2841
TEST(ApidManager, RandomizedTesting) {
29-
Svc::Ccsds::ApidManagerTester tester;
30-
31-
Svc::Ccsds::ApidManagerTester::GetExistingSeqCount getExistingSeqCountRule;
32-
Svc::Ccsds::ApidManagerTester::GetNewSeqCountOk getNewSeqCountOkRule;
33-
Svc::Ccsds::ApidManagerTester::GetNewSeqCountTableFull getNewSeqCountTableFullRule;
34-
Svc::Ccsds::ApidManagerTester::ValidateSeqCountOk validateSeqCountOkRule;
35-
Svc::Ccsds::ApidManagerTester::ValidateSeqCountFailure validateSeqCountFailureRule;
36-
37-
// Place these rules into a list of rules
38-
STest::Rule<Svc::Ccsds::ApidManagerTester>* rules[] = {&getExistingSeqCountRule, &getNewSeqCountOkRule,
39-
&getNewSeqCountTableFullRule, &validateSeqCountOkRule,
40-
&validateSeqCountFailureRule};
42+
U32 numRulesToApply = 10000;
43+
ApidManagerTester tester;
44+
ApidManagerTester::GetSeqCount__Existing ruleGetExisting;
45+
ApidManagerTester::GetSeqCount__NewOk ruleGetNewOk;
46+
ApidManagerTester::GetSeqCount__NewTableFull ruleGetNewTableFull;
47+
ApidManagerTester::ValidateSeqCount__Ok ruleValidateOk;
48+
ApidManagerTester::ValidateSeqCount__Failure ruleValidateFailure;
4149

42-
// Take the rules and place them into a random scenario
43-
STest::RandomScenario<Svc::Ccsds::ApidManagerTester> random("Random Rules", rules, FW_NUM_ARRAY_ELEMENTS(rules));
50+
STest::Rule<ApidManagerTester>* rules[] = {
51+
&ruleGetExisting, &ruleGetNewOk, &ruleGetNewTableFull, &ruleValidateOk, &ruleValidateFailure,
52+
};
4453

45-
// Create a bounded scenario wrapping the random scenario
46-
STest::BoundedScenario<Svc::Ccsds::ApidManagerTester> bounded("Bounded Random Rules Scenario", random, 10000);
47-
// Run!
54+
STest::RandomScenario<ApidManagerTester> random("Random Rules", rules, FW_NUM_ARRAY_ELEMENTS(rules));
55+
STest::BoundedScenario<ApidManagerTester> bounded("Bounded Random Rules Scenario", random, numRulesToApply);
4856
const U32 numSteps = bounded.run(tester);
4957
printf("Ran %u steps.\n", numSteps);
5058
}

Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.cpp

Lines changed: 3 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -5,172 +5,23 @@
55
// ======================================================================
66

77
#include "ApidManagerTester.hpp"
8-
#include "STest/Random/Random.hpp"
9-
#include "Svc/Ccsds/Types/FppConstantsAc.hpp"
108

119
namespace Svc {
1210

1311
namespace Ccsds {
1412

15-
static constexpr ComCfg::Apid::T TEST_REGISTERED_APIDS[] = {ComCfg::Apid::FW_PACKET_COMMAND,
16-
ComCfg::Apid::FW_PACKET_TELEM, ComCfg::Apid::FW_PACKET_LOG,
17-
ComCfg::Apid::FW_PACKET_FILE};
18-
1913
// ----------------------------------------------------------------------
2014
// Construction and destruction
2115
// ----------------------------------------------------------------------
2216

23-
ApidManagerTester ::ApidManagerTester()
17+
ApidManagerTester::ApidManagerTester()
2418
: ApidManagerGTestBase("ApidManagerTester", ApidManagerTester::MAX_HISTORY_SIZE), component("ApidManager") {
2519
this->initComponents();
2620
this->connectPorts();
27-
// Initialize existing sequence counts for common APIDs
28-
for (FwIndexType i = 0; i < static_cast<FwIndexType>(FW_NUM_ARRAY_ELEMENTS(TEST_REGISTERED_APIDS)); i++) {
29-
this->component.m_apidSequences.insert(TEST_REGISTERED_APIDS[i], static_cast<U16>(0));
30-
this->shadow_seqCounts[TEST_REGISTERED_APIDS[i]] = 0; // Initialize shadow sequence counts to 0
31-
}
32-
}
33-
34-
ApidManagerTester ::~ApidManagerTester() {}
35-
36-
// ----------------------------------------------------------------------
37-
// Tests
38-
// ----------------------------------------------------------------------
39-
40-
bool ApidManagerTester::GetExistingSeqCount::precondition(const ApidManagerTester& testerState) {
41-
return true; // Can always get existing sequence count
42-
}
43-
44-
void ApidManagerTester::GetExistingSeqCount::action(ApidManagerTester& testerState) {
45-
testerState.clearHistory();
46-
ComCfg::Apid::T apid = testerState.shadow_getRandomTrackedApid();
47-
U16 seqCount = testerState.invoke_to_getApidSeqCountIn(0, apid, 0);
48-
U16 shadowSeqCount = testerState.shadow_getAndIncrementSeqCount(apid);
49-
ASSERT_EQ(seqCount, shadowSeqCount) << "Sequence count for APID " << static_cast<U16>(apid)
50-
<< " does not match shadow value."
51-
<< " Shadow: " << shadowSeqCount << ", Actual: " << seqCount;
52-
}
53-
54-
bool ApidManagerTester::GetNewSeqCountOk::precondition(const ApidManagerTester& testerState) {
55-
return testerState.shadow_isTableFull == false;
56-
}
57-
58-
void ApidManagerTester::GetNewSeqCountOk::action(ApidManagerTester& testerState) {
59-
testerState.clearHistory();
60-
// Use local constexpr to potentially avoid ODR-use of ApidManager::MAX_TRACKED_APIDS
61-
constexpr U8 maxTrackedApidsVal = ApidManager::MAX_TRACKED_APIDS;
62-
bool isTableFull = !(testerState.shadow_seqCounts.size() < maxTrackedApidsVal);
63-
if (isTableFull) {
64-
testerState.shadow_isTableFull = true;
65-
return; // Cannot get new sequence count if table is full - skip action
66-
}
67-
68-
ComCfg::Apid::T apid = testerState.shadow_getRandomUntrackedApid();
69-
U16 seqCount = testerState.invoke_to_getApidSeqCountIn(0, apid, 0);
70-
U16 shadowSeqCount = testerState.shadow_getAndIncrementSeqCount(apid);
71-
ASSERT_EQ(seqCount, shadowSeqCount) << "Sequence count for APID " << static_cast<U16>(apid)
72-
<< " does not match shadow value."
73-
<< " Shadow: " << shadowSeqCount << ", Actual: " << seqCount;
74-
}
75-
76-
bool ApidManagerTester::GetNewSeqCountTableFull::precondition(const ApidManagerTester& testerState) {
77-
return testerState.shadow_isTableFull == true;
78-
}
79-
80-
void ApidManagerTester::GetNewSeqCountTableFull::action(ApidManagerTester& testerState) {
81-
testerState.clearHistory();
82-
ComCfg::Apid::T apid = testerState.shadow_getRandomUntrackedApid();
83-
U16 seqCount = testerState.invoke_to_getApidSeqCountIn(0, apid, 0);
84-
// Use local constexpr to potentially avoid ODR-use of ApidManager::SEQUENCE_COUNT_ERROR
85-
constexpr U16 sequenceCountErrorVal = ApidManager::SEQUENCE_COUNT_ERROR;
86-
ASSERT_EQ(seqCount, sequenceCountErrorVal)
87-
<< "Expected SEQUENCE_COUNT_ERROR for untracked APID " << static_cast<U16>(apid) << ", but got " << seqCount;
88-
testerState.assertEvents_ApidTableFull_size(__FILE__, __LINE__, 1);
89-
testerState.assertEvents_ApidTableFull(__FILE__, __LINE__, 0, static_cast<U16>(apid));
90-
}
91-
92-
bool ApidManagerTester::ValidateSeqCountOk::precondition(const ApidManagerTester& testerState) {
93-
return true;
9421
}
9522

96-
void ApidManagerTester::ValidateSeqCountOk::action(ApidManagerTester& testerState) {
97-
testerState.clearHistory();
98-
ComCfg::Apid::T apid = testerState.shadow_getRandomTrackedApid();
99-
U16 shadow_expectedSeqCount = testerState.shadow_seqCounts[apid];
100-
testerState.invoke_to_validateApidSeqCountIn(0, apid, shadow_expectedSeqCount);
101-
testerState.shadow_validateApidSeqCount(apid, shadow_expectedSeqCount); // keep shadow state in sync
102-
103-
testerState.assertEvents_UnexpectedSequenceCount_size(__FILE__, __LINE__, 0);
104-
}
105-
106-
bool ApidManagerTester::ValidateSeqCountFailure::precondition(const ApidManagerTester& testerState) {
107-
return true;
108-
}
109-
110-
void ApidManagerTester::ValidateSeqCountFailure::action(ApidManagerTester& testerState) {
111-
testerState.clearHistory();
112-
ComCfg::Apid::T apid = testerState.shadow_getRandomTrackedApid();
113-
U16 shadow_expectedSeqCount = testerState.shadow_seqCounts.at(apid);
114-
U16 invalidSeqCount = static_cast<U16>(
115-
(shadow_expectedSeqCount + 1) %
116-
(1 << SpacePacketSubfields::SeqCountWidth)); // Or any other value that's different, ensure wrap around
117-
118-
// Invoke the port with the deliberately incorrect sequence count
119-
testerState.invoke_to_validateApidSeqCountIn(0, apid, invalidSeqCount);
120-
testerState.shadow_validateApidSeqCount(apid, invalidSeqCount); // keep shadow state in sync
121-
122-
// Now, the event should be logged
123-
testerState.assertEvents_UnexpectedSequenceCount_size(__FILE__, __LINE__, 1);
124-
testerState.assertEvents_UnexpectedSequenceCount(__FILE__, __LINE__, 0, invalidSeqCount, shadow_expectedSeqCount);
125-
}
126-
127-
// ----------------------------------------------------------------------
128-
// Helpers
129-
// ----------------------------------------------------------------------
130-
131-
U16 ApidManagerTester::shadow_getAndIncrementSeqCount(ComCfg::Apid::T apid) {
132-
// This is a shadow function to simulate the getAndIncrementSeqCount behavior
133-
// without modifying the actual component state.
134-
auto found = this->shadow_seqCounts.find(apid);
135-
if (found != this->shadow_seqCounts.end()) {
136-
U16 seqCount = found->second;
137-
found->second =
138-
static_cast<U16>((seqCount + 1) % (1 << SpacePacketSubfields::SeqCountWidth)); // Increment for next call
139-
return seqCount; // Return the current sequence count
140-
}
141-
// If APID not found, initialize a new entry
142-
if (this->shadow_seqCounts.size() < this->component.MAX_TRACKED_APIDS) {
143-
U16 seqCount = 0;
144-
this->shadow_seqCounts[apid] = static_cast<U16>(seqCount + 1); // increment for next call
145-
return seqCount; // Return the initialized sequence count
146-
}
147-
return this->component.SEQUENCE_COUNT_ERROR; // Return error if APID not found
148-
}
149-
150-
void ApidManagerTester::shadow_validateApidSeqCount(ComCfg::Apid::T apid, U16 expectedSeqCount) {
151-
// This simply updates the shadow state to the next expected sequence count
152-
auto found = this->shadow_seqCounts.find(apid);
153-
if (found != this->shadow_seqCounts.end()) {
154-
found->second = static_cast<U16>((expectedSeqCount + 1) % (1 << SpacePacketSubfields::SeqCountWidth));
155-
}
156-
}
157-
158-
ComCfg::Apid::T ApidManagerTester::shadow_getRandomTrackedApid() {
159-
// Select a random APID from the sequence counts map
160-
U32 mapSize = static_cast<U32>(this->shadow_seqCounts.size());
161-
U32 randomIndex = STest::Random::lowerUpper(0, mapSize - 1);
162-
ComCfg::Apid apid = std::next(this->shadow_seqCounts.begin(), randomIndex)->first;
163-
return apid;
164-
}
165-
166-
ComCfg::Apid::T ApidManagerTester::shadow_getRandomUntrackedApid() {
167-
// Select a random APID that is not currently tracked
168-
ComCfg::Apid::T apid;
169-
do {
170-
apid = static_cast<ComCfg::Apid::T>(STest::Random::lowerUpper(10, ComCfg::Apid::SPP_IDLE_PACKET));
171-
} while (this->shadow_seqCounts.find(apid) != this->shadow_seqCounts.end());
172-
return apid;
173-
}
23+
ApidManagerTester::~ApidManagerTester() {}
17424

17525
} // namespace Ccsds
26+
17627
} // namespace Svc

Svc/Ccsds/ApidManager/test/ut/ApidManagerTester.hpp

Lines changed: 16 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
#ifndef Svc_Ccsds_ApidManagerTester_HPP
88
#define Svc_Ccsds_ApidManagerTester_HPP
99

10-
#include "STest/Random/Random.hpp"
11-
#include "STest/Rule/Rule.hpp"
1210
#include "Svc/Ccsds/ApidManager/ApidManager.hpp"
1311
#include "Svc/Ccsds/ApidManager/ApidManagerGTestBase.hpp"
12+
#include "Svc/Ccsds/ApidManager/test/ut/TestState/TestState.hpp"
13+
#include "TestUtils/RuleBasedTesting.hpp"
1414

1515
namespace Svc {
1616

@@ -22,10 +22,10 @@ class ApidManagerTester : public ApidManagerGTestBase {
2222
// Constants
2323
// ----------------------------------------------------------------------
2424

25-
// Maximum size of histories storing events, telemetry, and port outputs
25+
//! Maximum size of histories storing events, telemetry, and port outputs
2626
static const FwSizeType MAX_HISTORY_SIZE = 10;
2727

28-
// Instance ID supplied to the component instance under test
28+
//! Instance ID supplied to the component instance under test
2929
static const FwEnumStoreType TEST_INSTANCE_ID = 0;
3030

3131
public:
@@ -41,7 +41,7 @@ class ApidManagerTester : public ApidManagerGTestBase {
4141

4242
private:
4343
// ----------------------------------------------------------------------
44-
// Helper functions
44+
// Helper functions (auto-generated via UT_AUTO_HELPERS)
4545
// ----------------------------------------------------------------------
4646

4747
//! Connect ports
@@ -58,56 +58,22 @@ class ApidManagerTester : public ApidManagerGTestBase {
5858
//! The component under test
5959
ApidManager component;
6060

61-
// Shadow test state
62-
std::map<ComCfg::Apid::T, U16> shadow_seqCounts; //!< Map to hold expected sequence counts for APIDs
63-
bool shadow_isTableFull = false;
61+
//! Shadow state for rule-based testing
62+
ApidManagerTestState shadow;
6463

64+
public:
6565
// ----------------------------------------------------------------------
66-
// Helpers for tracking the shadow test state
66+
// Rule Based Testing
6767
// ----------------------------------------------------------------------
6868

69-
U16 shadow_getAndIncrementSeqCount(ComCfg::Apid::T apid);
70-
71-
void shadow_validateApidSeqCount(ComCfg::Apid::T apid, U16 expectedSeqCount);
72-
73-
ComCfg::Apid::T shadow_getRandomTrackedApid();
74-
75-
ComCfg::Apid::T shadow_getRandomUntrackedApid();
69+
//! Rules for the getApidSeqCountIn port
70+
FW_RBT_DEFINE_RULE(ApidManagerTester, GetSeqCount, Existing);
71+
FW_RBT_DEFINE_RULE(ApidManagerTester, GetSeqCount, NewOk);
72+
FW_RBT_DEFINE_RULE(ApidManagerTester, GetSeqCount, NewTableFull);
7673

77-
// ----------------------------------------------------------------------
78-
// Tests: Rule Based Testing
79-
// ----------------------------------------------------------------------
80-
81-
public:
82-
struct GetExistingSeqCount : public STest::Rule<ApidManagerTester> {
83-
GetExistingSeqCount() : STest::Rule<ApidManagerTester>("GetExistingSeqCount") {};
84-
bool precondition(const ApidManagerTester& state);
85-
void action(ApidManagerTester& state);
86-
};
87-
88-
struct GetNewSeqCountOk : public STest::Rule<ApidManagerTester> {
89-
GetNewSeqCountOk() : STest::Rule<ApidManagerTester>("GetNewSeqCountOk") {};
90-
bool precondition(const ApidManagerTester& state);
91-
void action(ApidManagerTester& state);
92-
};
93-
94-
struct GetNewSeqCountTableFull : public STest::Rule<ApidManagerTester> {
95-
GetNewSeqCountTableFull() : STest::Rule<ApidManagerTester>("GetNewSeqCountTableFull") {};
96-
bool precondition(const ApidManagerTester& state);
97-
void action(ApidManagerTester& state);
98-
};
99-
100-
struct ValidateSeqCountOk : public STest::Rule<ApidManagerTester> {
101-
ValidateSeqCountOk() : STest::Rule<ApidManagerTester>("ValidateSeqCountOk") {};
102-
bool precondition(const ApidManagerTester& state);
103-
void action(ApidManagerTester& state);
104-
};
105-
106-
struct ValidateSeqCountFailure : public STest::Rule<ApidManagerTester> {
107-
ValidateSeqCountFailure() : STest::Rule<ApidManagerTester>("ValidateSeqCountFailure") {};
108-
bool precondition(const ApidManagerTester& state);
109-
void action(ApidManagerTester& state);
110-
};
74+
//! Rules for the validateApidSeqCountIn port
75+
FW_RBT_DEFINE_RULE(ApidManagerTester, ValidateSeqCount, Ok);
76+
FW_RBT_DEFINE_RULE(ApidManagerTester, ValidateSeqCount, Failure);
11177
};
11278

11379
} // namespace Ccsds

0 commit comments

Comments
 (0)