PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
test_alarmListener.cpp
Go to the documentation of this file.
1
18#include <gtest/gtest.h>
19
20#include <cstdint>
21#include <functional>
22#include <memory>
23
27
28namespace profinet
29{
30
31namespace
32{
33
36void TestAlarmCallback(const AlarmNotification& /*alarm*/)
37{
38}
39
41AlarmEndpoint MakeEndpoint()
42{
43 return AlarmEndpoint{
44 .interface = "lo",
45 .controllerRef = 1,
46 .deviceRef = 42,
47 .deviceMac = MacAddress{0, 0, 0, 0, 0, 0},
48 };
49}
50
51} // namespace
52
53// -----------------------------------------------------------------------------
54// AlarmEndpoint
55// -----------------------------------------------------------------------------
56
58TEST(AlarmEndpointTests, DefaultTransportIsLayer2)
59{
60 auto expectedMac = MacAddress{0x02, 0x00, 0x00, 0x00, 0x00, 0x01};
61 const AlarmEndpoint endpoint{
62 .interface = "eth0",
63 .controllerRef = 1,
64 .deviceRef = 42,
65 .deviceMac = expectedMac,
66 };
67 EXPECT_EQ(endpoint.interface, "eth0");
68 EXPECT_EQ(endpoint.controllerRef, 1);
69 EXPECT_EQ(endpoint.deviceRef, 42);
70 EXPECT_EQ(endpoint.deviceMac, expectedMac);
71 EXPECT_EQ(endpoint.transport, 0);
72}
73
75TEST(AlarmEndpointTests, AllFieldsCanBeConfigured)
76{
77 const MacAddress deviceMac{0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
78
79 const AlarmEndpoint endpoint{
80 .interface = "eth0",
81 .controllerRef = 100,
82 .deviceRef = 200,
83 .deviceMac = deviceMac,
84 .transport = 1,
85 };
86
87 EXPECT_EQ(endpoint.interface, "eth0");
88 EXPECT_EQ(endpoint.controllerRef, 100);
89 EXPECT_EQ(endpoint.deviceRef, 200);
90 EXPECT_EQ(endpoint.deviceMac, deviceMac);
91 EXPECT_EQ(endpoint.transport, 1);
92}
93
94// -----------------------------------------------------------------------------
95// AlarmListener
96// -----------------------------------------------------------------------------
97class AlarmListenerTests : public ::testing::Test
98{
99 protected:
100 void SetUp() override
101 {
102 endpoint = MakeEndpoint();
103 ioContext = std::make_unique<asio::io_context>();
104 listener = std::make_unique<AlarmListener>(*ioContext, endpoint);
105 }
106
107 std::unique_ptr<asio::io_context> ioContext;
109 std::unique_ptr<AlarmListener> listener;
110};
113TEST_F(AlarmListenerTests, InitializesWithEndpoint)
114{
115 EXPECT_EQ(listener->IsRunning(), false);
116}
117
120TEST_F(AlarmListenerTests, InitializesWithoutCallbacks)
121{
122 // There is intentionally no public callback-count API. Registration is
123 // tested through callback invocation in the callback-specific tests.
124 EXPECT_FALSE(listener->IsRunning());
125 // Verify that no callbacks are registered initially.
126 EXPECT_EQ(AlarmListenerTestAccess::CallbackCount(*listener), 0U);
127}
128
130TEST_F(AlarmListenerTests, AddLambaCallbackRegistersCallback)
131{
132 bool callbackRegistered = false;
133
134 listener->AddAlarmNotificationCallback([&callbackRegistered](const AlarmNotification&)
135 {
136 callbackRegistered = true;
137 });
138
139 // AddCallback() itself does not invoke the callback. The registered
140 // callback is exercised by the alarm-processing tests.
141 EXPECT_FALSE(callbackRegistered);
142 ASSERT_EQ(AlarmListenerTestAccess::CallbackCount(*listener), 1U);
143}
144
146TEST_F(AlarmListenerTests, AddCallbackRegistersCallback)
147{
148 bool callbackRegistered = false;
149
150 listener->AddAlarmNotificationCallback(TestAlarmCallback);
151
152 // AddCallback() itself does not invoke the callback. The registered
153 // callback is exercised by the alarm-processing tests.
154 ASSERT_EQ(AlarmListenerTestAccess::CallbackCount(*listener), 1U);
155}
158TEST_F(AlarmListenerTests, AddEmptyCallbackDoesNothing)
159{
160 // Attempt to register an empty callback.
161 listener->AddAlarmNotificationCallback({});
162 // Verify that no callback was registered.
163 EXPECT_EQ(AlarmListenerTestAccess::CallbackCount(*listener), 0U);
164}
165
169{ // Register the callback that will subsequently be removed.
170 listener->AddAlarmNotificationCallback(TestAlarmCallback);
171
172 // Verify that registration succeeded before removal.
173 ASSERT_EQ(AlarmListenerTestAccess::CallbackCount(*listener), 1U);
174
175 // Remove the previously registered callback.
176 listener->RemoveAlarmNotificationCallback(TestAlarmCallback);
177
178 // Verify that the callback collection is now empty.
179 EXPECT_EQ(AlarmListenerTestAccess::CallbackCount(*listener), 0U);
180}
181
184TEST_F(AlarmListenerTests, RemoveNonexistentCallbackDoesNotThrow)
185{
186 // Verify that removing an unregistered callback does not throw.
187 EXPECT_NO_THROW(listener->RemoveAlarmNotificationCallback(TestAlarmCallback));
188
189 // Verify that the callback collection remains empty.
190 EXPECT_EQ(AlarmListenerTestAccess::CallbackCount(*listener), 0U);
191}
192
196TEST_F(AlarmListenerTests, RemoveEmptyCallbackDoesNothing)
197{
198 // Register a valid callback first.
199 listener->AddAlarmNotificationCallback(TestAlarmCallback);
200
201 // Verify that the callback was registered.
202 ASSERT_EQ(AlarmListenerTestAccess::CallbackCount(*listener), 1U);
203
204 // Attempt to remove an empty callback.
205 listener->RemoveAlarmNotificationCallback({});
206
207 // Verify that the existing callback remains registered.
208 EXPECT_EQ(AlarmListenerTestAccess::CallbackCount(*listener), 1U);
209}
210
213TEST_F(AlarmListenerTests, IsRunningInitiallyFalse)
214{
215 // Verify the initial running state.
216 EXPECT_FALSE(listener->IsRunning());
217}
218
226TEST_F(AlarmListenerTests, IsRunningReflectsState)
227{
228 auto socket = std::make_unique<::testing::NiceMock<MockRawEthernetSocket>>();
229
230 ON_CALL(*socket, Recv())
231 .WillByDefault(::testing::Return(std::vector<std::uint8_t>{}));
232
233 ON_CALL(*socket, SetTimeout(::testing::_))
234 .WillByDefault(::testing::Return());
235 auto ioContext = std::make_unique<asio::io_context>();
236 listener = std::make_unique<AlarmListener>(*ioContext, endpoint, std::nullopt);
237
238 EXPECT_FALSE(listener->IsRunning());
239
240 listener->Start(std::move(socket));
241
242 EXPECT_TRUE(listener->IsRunning());
243
244 listener->Stop();
245
246 EXPECT_FALSE(listener->IsRunning());
247}
248
250TEST_F(AlarmListenerTests, ControllerMacDefaultsToZero)
251{
252 const MacAddress defaultMac{0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
253
254 EXPECT_EQ(AlarmListenerTestAccess::ControllerMac(*listener), defaultMac);
255}
256
258TEST_F(AlarmListenerTests, AcceptsCustomControllerMac)
259{
260 const MacAddress customMac{
261 0x11, 0x22, 0x33, 0x44, 0x55, 0x66};
262 auto ioContext = std::make_unique<asio::io_context>();
263 listener = std::make_unique<AlarmListener>(*ioContext, endpoint, customMac);
264
265 EXPECT_EQ(AlarmListenerTestAccess::ControllerMac(*listener), customMac);
266}
267
268// -----------------------------------------------------------------------------
269// Frame IDs
270// -----------------------------------------------------------------------------
271
273TEST(AlarmFrameIdTests, HighPriorityAlarmFrameId)
274{
275 EXPECT_EQ(FRAME_ID_ALARM_HIGH, 0xFC01);
276}
277
279TEST(AlarmFrameIdTests, LowPriorityAlarmFrameId)
280{
281 EXPECT_EQ(FRAME_ID_ALARM_LOW, 0xFE01);
282}
283TEST_F(AlarmListenerTests, FailedStartLeavesListenerStopped)
284{
285 auto socket = std::make_unique<::testing::NiceMock<MockRawEthernetSocket>>();
286 auto* s = socket.get();
287
288 EXPECT_CALL(*s, SetTimeout(testing::_))
289 .WillOnce(testing::Throw(std::runtime_error("timeout")));
290
291 EXPECT_THROW(listener->Start(std::move(socket)), std::runtime_error);
292 EXPECT_FALSE(listener->IsRunning());
293}
294
295TEST_F(AlarmListenerTests, StartAfterFailedStartSucceeds)
296{
297 auto socket = std::make_unique<::testing::NiceMock<MockRawEthernetSocket>>();
298
299 auto* s = socket.get();
300
301 EXPECT_CALL(*s, SetTimeout(testing::_))
302 .WillOnce(testing::Throw(std::runtime_error("timeout")));
303
304 EXPECT_THROW(listener->Start(std::move(socket)), std::runtime_error);
305
306 // clean socket:
307 socket = std::make_unique<::testing::NiceMock<MockRawEthernetSocket>>();
308 EXPECT_NO_THROW(listener->Start(std::move(socket)));
309 EXPECT_TRUE(listener->IsRunning());
310 listener->Stop();
311}
312
313TEST_F(AlarmListenerTests, StopIsIdempotent)
314{
315 auto socket = std::make_unique<::testing::NiceMock<MockRawEthernetSocket>>();
316
317 listener->Start(std::move(socket));
318 listener->Stop();
319 EXPECT_NO_THROW(listener->Stop());
320}
321} // namespace profinet
Private-access adapter used by AlarmListener tests.
Declares the PROFINET RTA alarm listener and AlarmAck transmission support.
static const MacAddress & ControllerMac(const AlarmListener &listener)
Return the listener's configured controller MAC address.
static std::size_t CallbackCount(const AlarmListener &listener)
Return the number of callbacks registered with the listener.
std::unique_ptr< asio::io_context > ioContext
std::unique_ptr< AlarmListener > listener
GoogleMock implementation of IRawEthernetSocket.
TEST_F(AlarmListenerTests, InitializesWithEndpoint)
TEST(AlarmEndpointTests, DefaultTransportIsLayer2)
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
Definition util.h:48
constexpr std::uint16_t FRAME_ID_ALARM_HIGH
Frame ID used for high-priority alarms.
constexpr std::uint16_t FRAME_ID_ALARM_LOW
Frame ID used for low-priority alarms.
Alarm endpoint configuration.
std::string interface
Network interface name to bind to, e.g. "eth0".
Complete parsed AlarmNotification PDU: header fields + parsed items.
Definition alarms.h:356
std::unique_ptr< asio::io_context > ioContext
Shared ASIO context.