PROFINET IO Controller Stack 1.0.0
Modern C++ implementation of a PROFINET IO Controller stack
Loading...
Searching...
No Matches
test_deviceStartCyclic.cpp
Go to the documentation of this file.
1
6#include <gmock/gmock.h>
7#include <gtest/gtest.h>
8
9#include <memory>
10#include <stdexcept>
11#include <string>
12#include <vector>
13
15#include "mocks/mockRPCCon.h"
16#include "profinet/device.h"
17
18using ::testing::_;
19using ::testing::InSequence;
20using ::testing::NiceMock;
21using ::testing::Return;
22using ::testing::StrictMock;
23using ::testing::Throw;
24
25namespace profinet::device
26{
27
28namespace
29{
30
32 0x00,
33 0x11,
34 0x22,
35 0x33,
36 0x44,
37 0x55,
38};
39
40dcp::DCPDeviceDescription MakeDeviceDescription()
41{
42 // Adapt this construction to the actual DCPDeviceDescription constructor
43 // used by the project.
44 dcp::DCPDeviceDescription info(String2Mac("02:00:00:00:00:01"), {});
45 info.name = "dev";
46 info.ip = "192.168.0.10";
47 return info;
48}
49
50std::optional<rpc::IOCRSetup> MakeIocrSetup()
51{
52 std::optional<rpc::IOCRSetup> setup = rpc::IOCRSetup{};
53 setup->sendClockFactor = 32;
54 setup->reductionRatio = 32;
55 setup->watchdogFactor = 3;
56 return setup;
57}
58
59} // namespace
60
61class StartCyclicTests : public ::testing::Test
62{
63 protected:
64 void SetUp() override
65 {
66 auto mockRpc = std::make_unique<testing::NiceMock<rpc::MockRPCCon>>();
67 rpc = mockRpc.get();
68
69 device = std::make_unique<ProfinetDevice>(
70 MakeDeviceDescription(),
71 "eth0",
72 kControllerMac,
73 1.0,
74 std::move(mockRpc));
75 }
76
77 std::unique_ptr<ProfinetDevice> device;
78 rpc::MockRPCCon* rpc = nullptr;
79};
80
82TEST_F(StartCyclicTests, ConnectsWithAlarmCrAndIocrSetup)
83{
84 EXPECT_CALL(*rpc, Connect(_, false, _))
85 .WillOnce(Return(std::nullopt));
86
87 auto iocrSetup = MakeIocrSetup();
88 std::optional<rpc::ConnectResult> result;
89 result = rpc::ConnectResult{};
90
91 EXPECT_CALL(*rpc, Connect(_, true, iocrSetup))
92 .WillOnce(Return(result));
93
94 const auto& setup = iocrSetup.value();
95 EXPECT_THROW(device->StartCyclic(setup),
96 std::runtime_error);
97}
99TEST_F(StartCyclicTests, StartsRtAfterPrmEndAndApplicationReady)
100{
101 rpc::ConnectResult connectResult{};
102 // connectResult.hasCyclic = true;
103 connectResult.inputFrameId = 0xC001;
104 connectResult.outputFrameId = 0x8002;
105
106 auto mockController = std::make_unique<testing::NiceMock<cyclic::MockCyclicController>>();
107 auto* controller = mockController.get();
108
109 testing::InSequence sequence;
110
111 EXPECT_CALL(*rpc, Connect(_, false, _))
112 .WillOnce(Return(std::nullopt));
113
114 EXPECT_CALL(*rpc, Connect(_, true, _))
115 .WillOnce(Return(connectResult));
116
117 EXPECT_CALL(*rpc, PrmEnd())
118 .WillOnce(Return(Bytes{}));
119
120 EXPECT_CALL(*rpc, ApplicationReady(30.0))
121 .WillOnce(Return(Bytes{}));
122
123 EXPECT_CALL(*controller, Start(_, _));
124
125 auto result = device->StartCyclic(MakeIocrSetup().value(), 3, std::move(mockController));
126
127 EXPECT_EQ(result.get(), controller);
128}
129
131TEST_F(StartCyclicTests, MissingCyclicResultFailsStartup)
132{
133 rpc::ConnectResult connectResult{};
134 // connectResult.hasCyclic = false;
135
136 EXPECT_CALL(*rpc, Connect(_, false, _))
137 .WillOnce(Return(std::nullopt));
138
139 EXPECT_CALL(*rpc, Connect(_, true, _))
140 .WillOnce(Return(connectResult));
141
142 EXPECT_THROW(device->StartCyclic(MakeIocrSetup().value()),
143 std::runtime_error);
144}
146TEST_F(StartCyclicTests, ApplicationReadyFailurePropagates)
147{
148 rpc::ConnectResult connectResult{};
149 // connectResult.hasCyclic = true;
150 connectResult.inputFrameId = 0xC001;
151 connectResult.outputFrameId = 0x8002;
152
153 EXPECT_CALL(*rpc, Connect(_, false, _))
154 .WillOnce(Return(std::nullopt));
155
156 EXPECT_CALL(*rpc, Connect(_, true, _))
157 .WillOnce(Return(connectResult));
158
159 EXPECT_CALL(*rpc, PrmEnd())
160 .WillOnce(Return(Bytes{}));
161
162 EXPECT_CALL(*rpc, ApplicationReady(30.0))
163 .WillOnce(Throw(std::runtime_error("timeout")));
164
165 auto mockController =
166 std::make_unique<testing::NiceMock<cyclic::MockCyclicController>>();
167
168 EXPECT_CALL(*mockController, Start(_, _)).Times(0);
169
170 EXPECT_THROW(device->StartCyclic(MakeIocrSetup().value(), 3, std::move(mockController)),
171 std::runtime_error);
172
173 EXPECT_TRUE(device->IsConnected());
174}
175
177TEST_F(StartCyclicTests, ApplicationReadyFailureDoesNotStartRt)
178{
179 rpc::ConnectResult connectResult{};
180 // connectResult.hasCyclic = true;
181 connectResult.inputFrameId = 0xC001;
182 connectResult.outputFrameId = 0x8002;
183
184 EXPECT_CALL(*rpc, Connect(_, false, _))
185 .WillOnce(Return(std::nullopt));
186
187 EXPECT_CALL(*rpc, Connect(_, true, _))
188 .WillOnce(Return(connectResult));
189
190 EXPECT_CALL(*rpc, PrmEnd())
191 .WillOnce(Return(Bytes{}));
192
193 EXPECT_CALL(*rpc, ApplicationReady(30.0))
194 .WillOnce(Throw(std::runtime_error("timeout")));
195
196 auto mockController =
197 std::make_unique<testing::NiceMock<cyclic::MockCyclicController>>();
198
199 EXPECT_CALL(*mockController, Start(_, _)).Times(0);
200
201 EXPECT_THROW(device->StartCyclic(MakeIocrSetup().value(), 3, std::move(mockController)),
202 std::runtime_error);
203}
206TEST(StartCyclicOrderingTests, StartsRtAfterApplicationReadyWhenDisabled)
207{
208 GTEST_SKIP() << "Current StartCyclic API has no start_rt_before_prm_end option.";
209}
210
212TEST(StartCyclicOrderingTests, ApplicationReadyCanBeSkipped)
213{
214 GTEST_SKIP() << "Current StartCyclic API has no confirm_application_ready option.";
215}
216
219TEST_F(StartCyclicTests, CommitsConnectedDeviceState)
220{
221 rpc::ConnectResult connectResult{};
222 // connectResult.hasCyclic = true;
223 connectResult.inputFrameId = 0xC001;
224 connectResult.outputFrameId = 0x8002;
225
226 EXPECT_CALL(*rpc, Connect(_, false, _))
227 .WillOnce(Return(std::nullopt));
228
229 EXPECT_CALL(*rpc, Connect(_, true, _))
230 .WillOnce(Return(connectResult));
231
232 EXPECT_CALL(*rpc, PrmEnd())
233 .WillOnce(Return(Bytes{}));
234
235 EXPECT_CALL(*rpc, ApplicationReady(30.0))
236 .WillOnce(Return(Bytes{}));
237
238 auto mockController =
239 std::make_unique<testing::NiceMock<cyclic::MockCyclicController>>();
240
241 EXPECT_CALL(*mockController, Start(_, _));
242
243 auto result = device->StartCyclic(
244 MakeIocrSetup().value(),
245 3,
246 std::move(mockController));
247
248 EXPECT_NE(result, nullptr);
249 EXPECT_TRUE(device->IsConnected());
250}
251
253TEST_F(StartCyclicTests, RequestsAlarmCrConnection)
254{
255 rpc::ConnectResult connectResult{};
256 // connectResult.hasCyclic = true;
257 connectResult.inputFrameId = 0xC001;
258 connectResult.outputFrameId = 0x8002;
259
260 EXPECT_CALL(*rpc, Connect(_, false, _))
261 .WillOnce(Return(std::nullopt));
262
263 EXPECT_CALL(
264 *rpc,
265 Connect(
266 _,
267 true,
268 testing::Truly(
269 [](const std::optional<rpc::IOCRSetup>& setup)
270 {
271 return setup.has_value() &&
272 setup->sendClockFactor == 32 &&
273 setup->reductionRatio == 32 &&
274 setup->watchdogFactor == 3;
275 })))
276 .WillOnce(Return(connectResult));
277
278 EXPECT_CALL(*rpc, PrmEnd())
279 .WillOnce(Return(Bytes{}));
280
281 EXPECT_CALL(*rpc, ApplicationReady(30.0))
282 .WillOnce(Return(Bytes{}));
283
284 auto mockController =
285 std::make_unique<testing::NiceMock<cyclic::MockCyclicController>>();
286
287 EXPECT_CALL(*mockController, Start(_, _));
288
289 device->StartCyclic(
290 MakeIocrSetup().value(),
291 3,
292 std::move(mockController));
293}
294
296TEST(StartCyclicArHandlingTests, ReleasesExistingArBeforeNewConnect)
297{
298 GTEST_SKIP() << "Current StartCyclic uses EnsureConnected() and reuses an existing AR.";
299}
300
303TEST(StartCyclicArHandlingTests, StopsAlarmListenerBeforeReplacingAr)
304{
305 GTEST_SKIP() << "Current StartCyclic does not replace an existing AR.";
306}
307
310TEST(StartCyclicRollbackTests, PrmEndFailureRollsBack)
311{
312 GTEST_SKIP() << "Current StartCyclic has no rollback path.";
313}
314
317TEST(StartCyclicRollbackTests, MissingCyclicResultRollsBack)
318{
319 GTEST_SKIP() << "Requires injectable RPCCon test seam.";
320}
321
324TEST(StartCyclicRollbackTests, ApplicationReadyFailureStopsStartedRt)
325{
326 GTEST_SKIP() << "Current StartCyclic starts RT only after ApplicationReady.";
327}
328
331TEST(StartCyclicRollbackTests, ReleaseFailureDoesNotMaskOriginalError)
332{
333 GTEST_SKIP() << "Current StartCyclic has no explicit rollback/release path.";
334}
335
336} // namespace profinet::device
std::unique_ptr< ProfinetDevice > device
Google Mock implementation of the IRPCCon RPC connection interface.
Definition mockRPCCon.h:35
High-level PROFINET device interface and device lifecycle management.
Google Mock implementation of the cyclic PROFINET controller interface.
Google Mock implementation of the IRPCCon PROFINET RPC connection interface.
TEST_F(DeviceTest, ParseMacAcceptsColonDashAndDotSeparators)
TEST(DeviceMoveTests, MoveConstructionDisconnectsSource)
constexpr MacAddress kControllerMac
std::array< std::uint8_t, macAddressLength > MacAddress
A 6-byte Ethernet MAC address.
Definition util.h:48
MacAddress String2Mac(const std::string &macStr)
Parse a colon-separated MAC address string.
Definition util.cpp:122
@ ApplicationReady
Application Ready command.
@ PrmEnd
Prm End command.
std::vector< std::uint8_t > Bytes
Generic byte buffer alias used throughout the library for raw wire data.
Definition protocol.h:55
Result of a successful RPCCon::Connect() that also established cyclic IO.
Definition rpcTypes.h:402
std::uint16_t inputFrameId
Frame ID assigned to the input (device -> controller) IOCR.
Definition rpcTypes.h:410
std::unique_ptr< cyclic::CyclicController > controller