Commit | Line | Data |
---|---|---|
faf0a8c2 SM |
1 | /* |
2 | * SPDX-License-Identifier: GPL-2.0-only | |
3 | * | |
4 | * Copyright (C) 2024 EfficiOS Inc. | |
5 | */ | |
6 | ||
7 | #include "cpp-common/bt2/component-class-dev.hpp" | |
8 | #include "cpp-common/bt2/component-class.hpp" | |
9 | #include "cpp-common/bt2/error.hpp" | |
10 | #include "cpp-common/bt2/graph.hpp" | |
11 | #include "cpp-common/bt2/plugin-load.hpp" | |
12 | #include "cpp-common/bt2c/call.hpp" | |
13 | #include "cpp-common/vendor/fmt/format.h" /* IWYU pragma: keep */ | |
14 | ||
15 | #include "tap/tap.h" | |
16 | ||
17 | namespace { | |
18 | ||
19 | /* The types of messages a `TestSourceIter` is instructed to send. */ | |
20 | enum class MsgType | |
21 | { | |
22 | /* Send stream beginning and stream end messages. */ | |
23 | STREAM, | |
24 | ||
25 | /* Send a message iterator inactivity message. */ | |
26 | MSG_ITER_INACTIVITY, | |
27 | }; | |
28 | ||
29 | __attribute__((used)) const char *format_as(MsgType msgType) noexcept | |
30 | { | |
31 | switch (msgType) { | |
32 | case MsgType::STREAM: | |
33 | return "stream beginning/end"; | |
34 | ||
35 | case MsgType::MSG_ITER_INACTIVITY: | |
36 | return "message iterator inactivity"; | |
37 | } | |
38 | ||
39 | bt_common_abort(); | |
40 | } | |
41 | ||
42 | using CreateClockClass = bt2::ClockClass::Shared (*)(bt2::SelfComponent); | |
43 | ||
44 | struct TestSourceData final | |
45 | { | |
46 | /* The function to call to obtain a clock class. */ | |
47 | CreateClockClass createClockClass; | |
48 | ||
49 | /* The type of messages to send. */ | |
50 | MsgType msgType; | |
51 | ||
52 | /* If not empty, the clock snapshot to set on the message. */ | |
53 | bt2s::optional<std::uint64_t> clockSnapshot; | |
54 | }; | |
55 | ||
56 | class TestSource; | |
57 | ||
58 | class TestSourceIter final : public bt2::UserMessageIterator<TestSourceIter, TestSource> | |
59 | { | |
60 | friend bt2::UserMessageIterator<TestSourceIter, TestSource>; | |
61 | ||
62 | public: | |
63 | explicit TestSourceIter(const bt2::SelfMessageIterator self, | |
64 | bt2::SelfMessageIteratorConfiguration, | |
65 | const bt2::SelfComponentOutputPort port) : | |
66 | bt2::UserMessageIterator<TestSourceIter, TestSource> {self, "TEST-SRC-MSG-ITER"}, | |
67 | _mData {&port.data<const TestSourceData>()}, _mSelf {self} | |
68 | { | |
69 | } | |
70 | ||
71 | private: | |
72 | void _next(bt2::ConstMessageArray& msgs) | |
73 | { | |
74 | if (_mDone) { | |
75 | return; | |
76 | } | |
77 | ||
78 | const auto clockCls = _mData->createClockClass(_mSelf.component()); | |
79 | ||
80 | switch (_mData->msgType) { | |
81 | case MsgType::STREAM: | |
82 | { | |
83 | const auto traceCls = _mSelf.component().createTraceClass(); | |
84 | const auto streamCls = traceCls->createStreamClass(); | |
85 | ||
86 | if (clockCls) { | |
87 | streamCls->defaultClockClass(*clockCls); | |
88 | } | |
89 | ||
90 | const auto stream = streamCls->instantiate(*traceCls->instantiate()); | |
91 | ||
92 | /* Create stream beginning message. */ | |
93 | msgs.append(bt2c::call([&] { | |
94 | const auto streamBeginningMsg = this->_createStreamBeginningMessage(*stream); | |
95 | ||
96 | /* Set clock snapshot if instructed to. */ | |
97 | if (_mData->clockSnapshot) { | |
98 | streamBeginningMsg->defaultClockSnapshot(*_mData->clockSnapshot); | |
99 | } | |
100 | ||
101 | return streamBeginningMsg; | |
102 | })); | |
103 | ||
104 | /* | |
105 | * The iterator needs to send a stream end message to avoid | |
106 | * a postcondition assertion failure, where it's ended but | |
107 | * didn't end all streams. | |
108 | * | |
109 | * The stream end messages don't play a role in the test | |
110 | * otherwise. | |
111 | */ | |
112 | msgs.append(this->_createStreamEndMessage(*stream)); | |
113 | break; | |
114 | } | |
115 | ||
116 | case MsgType::MSG_ITER_INACTIVITY: | |
117 | msgs.append( | |
118 | this->_createMessageIteratorInactivityMessage(*clockCls, *_mData->clockSnapshot)); | |
119 | break; | |
120 | } | |
121 | ||
122 | _mDone = true; | |
123 | } | |
124 | ||
125 | bool _mDone = false; | |
126 | const TestSourceData *_mData; | |
127 | bt2::SelfMessageIterator _mSelf; | |
128 | }; | |
129 | ||
130 | class TestSource final : public bt2::UserSourceComponent<TestSource, TestSourceIter, TestSourceData> | |
131 | { | |
132 | friend class TestSourceIter; | |
133 | ||
134 | using _ThisUserSourceComponent = | |
135 | bt2::UserSourceComponent<TestSource, TestSourceIter, TestSourceData>; | |
136 | ||
137 | public: | |
138 | static constexpr auto name = "test-source"; | |
139 | ||
140 | explicit TestSource(const bt2::SelfSourceComponent self, bt2::ConstMapValue, | |
141 | TestSourceData * const data) : | |
142 | _ThisUserSourceComponent {self, "TEST-SRC"}, | |
143 | _mData {*data} | |
144 | { | |
145 | this->_addOutputPort("out", _mData); | |
146 | } | |
147 | ||
148 | private: | |
149 | TestSourceData _mData; | |
150 | }; | |
151 | ||
152 | class ErrorTestCase final | |
153 | { | |
154 | public: | |
155 | /* Intentionally not explicit */ | |
156 | ErrorTestCase(CreateClockClass createClockClass1Param, CreateClockClass createClockClass2Param, | |
157 | const char * const testName, const char * const expectedCauseMsg) : | |
158 | _mCreateClockClass1 {createClockClass1Param}, | |
159 | _mCreateClockClass2 {createClockClass2Param}, _mTestName {testName}, _mExpectedCauseMsg { | |
160 | expectedCauseMsg} | |
161 | { | |
162 | } | |
163 | ||
164 | void run() const noexcept; | |
165 | ||
166 | private: | |
167 | void _runOne(MsgType msgType1, MsgType msgType2) const noexcept; | |
168 | ||
169 | CreateClockClass _mCreateClockClass1; | |
170 | CreateClockClass _mCreateClockClass2; | |
171 | const char *_mTestName; | |
172 | const char *_mExpectedCauseMsg; | |
173 | }; | |
174 | ||
175 | bt2::ClockClass::Shared noClockClass(bt2::SelfComponent) noexcept | |
176 | { | |
177 | return bt2::ClockClass::Shared {}; | |
178 | } | |
179 | ||
180 | void ErrorTestCase::run() const noexcept | |
181 | { | |
182 | static constexpr std::array<MsgType, 2> msgTypes { | |
183 | MsgType::STREAM, | |
184 | MsgType::MSG_ITER_INACTIVITY, | |
185 | }; | |
186 | ||
187 | for (const auto msgType1 : msgTypes) { | |
188 | for (const auto msgType2 : msgTypes) { | |
189 | /* | |
190 | * It's not possible to create message iterator inactivity | |
191 | * messages without a clock class. Skip those cases. | |
192 | */ | |
193 | if ((msgType1 == MsgType::MSG_ITER_INACTIVITY && _mCreateClockClass1 == noClockClass) || | |
194 | (msgType2 == MsgType::MSG_ITER_INACTIVITY && _mCreateClockClass2 == noClockClass)) { | |
195 | continue; | |
196 | } | |
197 | ||
198 | /* | |
199 | * The test scenarios depend on the message with the first | |
200 | * clock class going through the muxer first. | |
201 | * | |
202 | * Between a message with a clock snapshot and a message | |
203 | * without a clock snapshot, the muxer always picks the | |
204 | * message without a clock snapshot first. | |
205 | * | |
206 | * Message iterator inactivity messages always have a clock | |
207 | * snapshot. Therefore, if: | |
208 | * | |
209 | * First message: | |
210 | * A message iterator inactivity message (always has a | |
211 | * clock snapshot). | |
212 | * | |
213 | * Second message: | |
214 | * Doesn't have a clock class (never has a clock | |
215 | * snapshot). | |
216 | * | |
217 | * Then there's no way for the first message to go through | |
218 | * first. | |
219 | * | |
220 | * Skip those cases. | |
221 | */ | |
222 | if (msgType1 == MsgType::MSG_ITER_INACTIVITY && _mCreateClockClass2 == noClockClass) { | |
223 | continue; | |
224 | } | |
225 | ||
226 | this->_runOne(msgType1, msgType2); | |
227 | } | |
228 | } | |
229 | } | |
230 | ||
231 | std::string makeSpecTestName(const char * const testName, const MsgType msgType1, | |
232 | const MsgType msgType2) | |
233 | { | |
234 | return fmt::format("{} ({}, {})", testName, msgType1, msgType2); | |
235 | } | |
236 | ||
237 | void ErrorTestCase::_runOne(const MsgType msgType1, const MsgType msgType2) const noexcept | |
238 | { | |
239 | const auto specTestName = makeSpecTestName(_mTestName, msgType1, msgType2); | |
240 | const auto srcCompCls = bt2::SourceComponentClass::create<TestSource>(); | |
241 | const auto graph = bt2::Graph::create(0); | |
242 | ||
243 | { | |
244 | /* | |
245 | * The test scenarios depend on the message with the first clock class going through the | |
246 | * muxer first. Between a message with a clock snapshot and a message without a clock | |
247 | * snapshot, the muxer always picks the message without a clock snapshot first. | |
248 | * | |
249 | * Therefore, for the first message, only set a clock snapshot when absolutely necessary, | |
250 | * that is when the message type is "message iterator inactivity". | |
251 | * | |
252 | * For the second message, always set a clock snapshot when possible, that is when a clock | |
253 | * class is defined for that message. | |
254 | */ | |
255 | const auto srcComp1 = | |
256 | graph->addComponent(*srcCompCls, "source-1", | |
257 | TestSourceData {_mCreateClockClass1, msgType1, | |
258 | msgType1 == MsgType::MSG_ITER_INACTIVITY ? | |
259 | bt2s::optional<std::uint64_t> {10} : | |
260 | bt2s::nullopt}); | |
261 | const auto srcComp2 = | |
262 | graph->addComponent(*srcCompCls, "source-2", | |
263 | TestSourceData {_mCreateClockClass2, msgType2, | |
264 | _mCreateClockClass2 != noClockClass ? | |
265 | bt2s::optional<std::uint64_t> {20} : | |
266 | bt2s::nullopt}); | |
267 | ||
268 | const auto utilsPlugin = bt2::findPlugin("utils"); | |
269 | ||
270 | BT_ASSERT(utilsPlugin); | |
271 | ||
272 | /* Add muxer component */ | |
273 | const auto muxerComp = bt2c::call([&] { | |
274 | const auto muxerCompCls = utilsPlugin->filterComponentClasses()["muxer"]; | |
275 | ||
276 | BT_ASSERT(muxerCompCls); | |
277 | ||
278 | return graph->addComponent(*muxerCompCls, "the-muxer"); | |
279 | }); | |
280 | ||
281 | /* Add dummy sink component */ | |
282 | const auto sinkComp = bt2c::call([&] { | |
283 | const auto dummySinkCompCls = utilsPlugin->sinkComponentClasses()["dummy"]; | |
284 | ||
285 | BT_ASSERT(dummySinkCompCls); | |
286 | ||
287 | return graph->addComponent(*dummySinkCompCls, "the-sink"); | |
288 | }); | |
289 | ||
290 | /* Connect ports */ | |
291 | graph->connectPorts(*srcComp1.outputPorts()["out"], *muxerComp.inputPorts()["in0"]); | |
292 | graph->connectPorts(*srcComp2.outputPorts()["out"], *muxerComp.inputPorts()["in1"]); | |
293 | graph->connectPorts(*muxerComp.outputPorts()["out"], *sinkComp.inputPorts()["in"]); | |
294 | } | |
295 | ||
296 | const auto thrown = bt2c::call([&graph] { | |
297 | try { | |
298 | graph->run(); | |
299 | } catch (const bt2::Error&) { | |
300 | return true; | |
301 | } | |
302 | ||
303 | return false; | |
304 | }); | |
305 | ||
306 | ok(thrown, "%s - `bt2::Error` thrown", specTestName.c_str()); | |
307 | ||
308 | const auto error = bt2::takeCurrentThreadError(); | |
309 | ||
310 | ok(error, "%s - current thread has an error", specTestName.c_str()); | |
311 | ok(error.length() > 0, "%s - error has at least one cause", specTestName.c_str()); | |
312 | ||
313 | const auto cause = error[0]; | |
314 | ||
315 | if (!ok(cause.message().startsWith(_mExpectedCauseMsg), "%s - cause's message is expected", | |
316 | specTestName.c_str())) { | |
317 | diag("expected: %s", _mExpectedCauseMsg); | |
318 | diag("actual: %s", cause.message().data()); | |
319 | } | |
320 | ||
321 | ok(cause.actorTypeIsMessageIterator(), "%s - cause's actor type is message iterator", | |
322 | specTestName.c_str()); | |
323 | ok(cause.asMessageIterator().componentName() == "the-muxer", | |
324 | "%s - causes's component name is `the-muxer`", specTestName.c_str()); | |
325 | } | |
326 | ||
327 | const bt2c::Uuid uuidA {"f00aaf65-ebec-4eeb-85b2-fc255cf1aa8a"}; | |
328 | const bt2c::Uuid uuidB {"03482981-a77b-4d7b-94c4-592bf9e91785"}; | |
329 | ||
330 | const ErrorTestCase errorTestCases[] = { | |
331 | {noClockClass, | |
332 | [](const bt2::SelfComponent self) { | |
333 | return self.createClockClass(); | |
334 | }, | |
335 | "no clock class followed by clock class", "Expecting no clock class, but got one"}, | |
336 | ||
337 | {[](const bt2::SelfComponent self) { | |
338 | return self.createClockClass(); | |
339 | }, | |
340 | noClockClass, "clock class with Unix epoch origin followed by no clock class", | |
341 | "Expecting a clock class, but got none"}, | |
342 | ||
343 | {[](const bt2::SelfComponent self) { | |
344 | return self.createClockClass(); | |
345 | }, | |
346 | [](const bt2::SelfComponent self) { | |
347 | const auto clockCls = self.createClockClass(); | |
348 | ||
349 | clockCls->originIsUnixEpoch(false); | |
350 | return clockCls; | |
351 | }, | |
352 | "clock class with Unix epoch origin followed by clock class with other origin", | |
353 | "Expecting a clock class having a Unix epoch origin, but got one not having a Unix epoch origin"}, | |
354 | ||
355 | {[](const bt2::SelfComponent self) { | |
356 | const auto clockCls = self.createClockClass(); | |
357 | ||
358 | clockCls->originIsUnixEpoch(false).uuid(uuidA); | |
359 | return clockCls; | |
360 | }, | |
361 | noClockClass, "clock class with other origin and a UUID followed by no clock class", | |
362 | "Expecting a clock class, but got none"}, | |
363 | ||
364 | {[](const bt2::SelfComponent self) { | |
365 | const auto clockCls = self.createClockClass(); | |
366 | ||
367 | clockCls->originIsUnixEpoch(false).uuid(uuidA); | |
368 | return clockCls; | |
369 | }, | |
370 | [](const bt2::SelfComponent self) { | |
371 | return self.createClockClass(); | |
372 | }, | |
373 | "clock class with other origin and a UUID followed by clock class with Unix epoch origin", | |
374 | "Expecting a clock class not having a Unix epoch origin, but got one having a Unix epoch origin"}, | |
375 | ||
376 | {[](const bt2::SelfComponent self) { | |
377 | const auto clockCls = self.createClockClass(); | |
378 | ||
379 | clockCls->originIsUnixEpoch(false).uuid(uuidA); | |
380 | return clockCls; | |
381 | }, | |
382 | [](const bt2::SelfComponent self) { | |
383 | const auto clockCls = self.createClockClass(); | |
384 | ||
385 | clockCls->originIsUnixEpoch(false); | |
386 | return clockCls; | |
387 | }, | |
388 | "clock class with other origin and a UUID followed by clock class with other origin and no UUID", | |
389 | "Expecting a clock class with a UUID, but got one without a UUID"}, | |
390 | ||
391 | {[](const bt2::SelfComponent self) { | |
392 | const auto clockCls = self.createClockClass(); | |
393 | ||
394 | clockCls->originIsUnixEpoch(false).uuid(uuidA); | |
395 | return clockCls; | |
396 | }, | |
397 | [](const bt2::SelfComponent self) { | |
398 | const auto clockCls = self.createClockClass(); | |
399 | ||
400 | clockCls->originIsUnixEpoch(false).uuid(uuidB); | |
401 | return clockCls; | |
402 | }, | |
403 | "clock class with other origin and a UUID followed by clock class with other origin and another UUID", | |
404 | "Expecting a clock class with a specific UUID, but got one with a different UUID"}, | |
405 | ||
406 | {[](const bt2::SelfComponent self) { | |
407 | const auto clockCls = self.createClockClass(); | |
408 | ||
409 | clockCls->originIsUnixEpoch(false); | |
410 | return clockCls; | |
411 | }, | |
412 | noClockClass, "clock class with other origin and no UUID followed by no clock class", | |
413 | "Expecting a clock class, but got none"}, | |
414 | ||
415 | {[](const bt2::SelfComponent self) { | |
416 | const auto clockCls = self.createClockClass(); | |
417 | ||
418 | clockCls->originIsUnixEpoch(false); | |
419 | return clockCls; | |
420 | }, | |
421 | ||
422 | [](const bt2::SelfComponent self) { | |
423 | const auto clockCls = self.createClockClass(); | |
424 | ||
425 | clockCls->originIsUnixEpoch(false); | |
426 | return clockCls; | |
427 | }, | |
428 | "clock class with other origin and no UUID followed by different clock class", | |
429 | "Unexpected clock class"}, | |
430 | }; | |
431 | ||
432 | } /* namespace */ | |
433 | ||
434 | int main() | |
435 | { | |
436 | plan_tests(150); | |
437 | ||
438 | for (auto& errorTestCase : errorTestCases) { | |
439 | errorTestCase.run(); | |
440 | } | |
441 | ||
442 | return exit_status(); | |
443 | } |