Skip to content

Commit ac578ae

Browse files
feat: pure stateguard interface for BLE (#1305)
* pure stateguard interface * stateguard owner template * remove StateGuardWithOwner * graceful func and better reporting * add unit tests * Reorder preprocessor directive for clarity
1 parent 944fbf4 commit ac578ae

4 files changed

Lines changed: 141 additions & 0 deletions

File tree

services/ble/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ target_sources(services.ble PRIVATE
4141
GattServerCharacteristicImpl.hpp
4242
RetryGattClientCharacteristicsOperations.cpp
4343
RetryGattClientCharacteristicsOperations.hpp
44+
StateGuard.hpp
4445
)
4546

4647
add_subdirectory(test)

services/ble/StateGuard.hpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#ifndef SERVICES_BLE_STATE_GUARD_HPP
2+
#define SERVICES_BLE_STATE_GUARD_HPP
3+
4+
#include "infra/stream/StringOutputStream.hpp"
5+
#include "infra/util/ReallyAssert.hpp"
6+
#include "services/ble/Gap.hpp"
7+
#include <algorithm>
8+
#include <initializer_list>
9+
10+
namespace services
11+
{
12+
13+
class StateGuard
14+
{
15+
public:
16+
StateGuard() = default;
17+
StateGuard(const StateGuard& other) = delete;
18+
StateGuard& operator=(const StateGuard& other) = delete;
19+
virtual ~StateGuard() = default;
20+
21+
bool StateIs(std::initializer_list<GapState> states) const
22+
{
23+
auto currentState = DetermineCurrentState();
24+
return std::any_of(states.begin(), states.end(), [currentState](auto state)
25+
{
26+
return state == currentState;
27+
});
28+
}
29+
30+
void AssertStateIs(std::initializer_list<GapState> states) const
31+
{
32+
if (!StateIs(states))
33+
{
34+
infra::StringOutputStream::WithStorage<16> stream;
35+
for (auto it = states.begin(); it != states.end(); ++it)
36+
{
37+
if (it != states.begin())
38+
stream << ",";
39+
stream << static_cast<int>(*it);
40+
}
41+
42+
really_assert_with_msg(false,
43+
"Unexpected state found: %d, expected: [%.*s]",
44+
static_cast<int>(DetermineCurrentState()),
45+
static_cast<int>(stream.Storage().size()),
46+
stream.Storage().data());
47+
}
48+
}
49+
50+
protected:
51+
virtual GapState DetermineCurrentState() const = 0;
52+
};
53+
} // namespace services
54+
55+
#endif // SERVICES_BLE_STATE_GUARD_HPP

services/ble/test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ target_sources(services.ble_test PRIVATE
2222
TestGapAdvertisementFormatter.cpp
2323
TestGattServer.cpp
2424
TestRetryGattClientAdapter.cpp
25+
TestStateGuard.cpp
2526
)
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#include "infra/util/LogAndAbort.hpp"
2+
#include "services/ble/StateGuard.hpp"
3+
#include "gmock/gmock.h"
4+
#include <cstdarg>
5+
#include <cstdio>
6+
7+
namespace services
8+
{
9+
namespace
10+
{
11+
class StateGuardMock
12+
: public StateGuard
13+
{
14+
public:
15+
MOCK_METHOD(GapState, DetermineCurrentState, (), (const, override));
16+
};
17+
18+
class TestStateGuard
19+
: public testing::Test
20+
{
21+
public:
22+
~TestStateGuard()
23+
{
24+
infra::RegisterLogAndAbortHook(nullptr);
25+
}
26+
27+
void RegisterStderrHook()
28+
{
29+
infra::RegisterLogAndAbortHook([]([[maybe_unused]] const char* reason, [[maybe_unused]] const char* file, [[maybe_unused]] int line, const char* format, va_list* args)
30+
{
31+
std::vfprintf(stderr, format, *args);
32+
});
33+
}
34+
35+
testing::NiceMock<StateGuardMock> stateGuard;
36+
};
37+
}
38+
39+
TEST_F(TestStateGuard, state_is_returns_true_when_current_state_matches)
40+
{
41+
ON_CALL(stateGuard, DetermineCurrentState()).WillByDefault(testing::Return(GapState::standby));
42+
43+
EXPECT_THAT(stateGuard.StateIs({ GapState::standby }), testing::IsTrue());
44+
}
45+
46+
TEST_F(TestStateGuard, state_is_returns_true_when_current_state_matches_one_of_many)
47+
{
48+
ON_CALL(stateGuard, DetermineCurrentState()).WillByDefault(testing::Return(GapState::connected));
49+
50+
EXPECT_THAT(stateGuard.StateIs({ GapState::standby, GapState::connected }), testing::IsTrue());
51+
}
52+
53+
TEST_F(TestStateGuard, state_is_returns_false_when_current_state_does_not_match)
54+
{
55+
ON_CALL(stateGuard, DetermineCurrentState()).WillByDefault(testing::Return(GapState::advertising));
56+
57+
EXPECT_THAT(stateGuard.StateIs({ GapState::standby, GapState::connected }), testing::IsFalse());
58+
}
59+
60+
TEST_F(TestStateGuard, assert_state_is_does_not_abort_when_state_matches)
61+
{
62+
ON_CALL(stateGuard, DetermineCurrentState()).WillByDefault(testing::Return(GapState::standby));
63+
64+
stateGuard.AssertStateIs({ GapState::standby, GapState::connected });
65+
}
66+
67+
#ifndef EMIL_MUTATION_TESTING
68+
TEST_F(TestStateGuard, assert_state_is_aborts_with_message_for_single_expected_state)
69+
{
70+
ON_CALL(stateGuard, DetermineCurrentState()).WillByDefault(testing::Return(GapState::advertising));
71+
RegisterStderrHook();
72+
73+
EXPECT_DEATH(stateGuard.AssertStateIs({ GapState::standby }), "Unexpected state found: 3, expected: \\[0\\]");
74+
}
75+
76+
TEST_F(TestStateGuard, assert_state_is_aborts_with_message_for_multiple_expected_states)
77+
{
78+
ON_CALL(stateGuard, DetermineCurrentState()).WillByDefault(testing::Return(GapState::initiating));
79+
RegisterStderrHook();
80+
81+
EXPECT_DEATH(stateGuard.AssertStateIs({ GapState::standby, GapState::connected }), "Unexpected state found: 4, expected: \\[0,2\\]");
82+
}
83+
#endif
84+
}

0 commit comments

Comments
 (0)