tests/plugins/flt.utils.muxer: add test for clock (in)compatibility cases
[babeltrace.git] / tests / plugins / flt.utils.muxer / test-clock-compatibility.cpp
CommitLineData
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
17namespace {
18
19/* The types of messages a `TestSourceIter` is instructed to send. */
20enum 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
42using CreateClockClass = bt2::ClockClass::Shared (*)(bt2::SelfComponent);
43
44struct 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
56class TestSource;
57
58class TestSourceIter final : public bt2::UserMessageIterator<TestSourceIter, TestSource>
59{
60 friend bt2::UserMessageIterator<TestSourceIter, TestSource>;
61
62public:
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
71private:
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
130class TestSource final : public bt2::UserSourceComponent<TestSource, TestSourceIter, TestSourceData>
131{
132 friend class TestSourceIter;
133
134 using _ThisUserSourceComponent =
135 bt2::UserSourceComponent<TestSource, TestSourceIter, TestSourceData>;
136
137public:
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
148private:
149 TestSourceData _mData;
150};
151
152class ErrorTestCase final
153{
154public:
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
166private:
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
175bt2::ClockClass::Shared noClockClass(bt2::SelfComponent) noexcept
176{
177 return bt2::ClockClass::Shared {};
178}
179
180void 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
231std::string makeSpecTestName(const char * const testName, const MsgType msgType1,
232 const MsgType msgType2)
233{
234 return fmt::format("{} ({}, {})", testName, msgType1, msgType2);
235}
236
237void 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
327const bt2c::Uuid uuidA {"f00aaf65-ebec-4eeb-85b2-fc255cf1aa8a"};
328const bt2c::Uuid uuidB {"03482981-a77b-4d7b-94c4-592bf9e91785"};
329
330const 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
434int main()
435{
436 plan_tests(150);
437
438 for (auto& errorTestCase : errorTestCases) {
439 errorTestCase.run();
440 }
441
442 return exit_status();
443}
This page took 0.038085 seconds and 4 git commands to generate.