X-Git-Url: http://git.efficios.com/?p=babeltrace.git;a=blobdiff_plain;f=tests%2Fplugins%2Fflt.utils.muxer%2Ftest-clock-compatibility.cpp;fp=tests%2Fplugins%2Fflt.utils.muxer%2Ftest-clock-compatibility.cpp;h=c922d8ccbfd2bb9a0e8842a5855e7e6a3a5278ff;hp=0000000000000000000000000000000000000000;hb=faf0a8c2c7051f8e013308838fcf52856486bb9a;hpb=0ea4aa9f8de0535273540aeaeacec74421cfb6b8 diff --git a/tests/plugins/flt.utils.muxer/test-clock-compatibility.cpp b/tests/plugins/flt.utils.muxer/test-clock-compatibility.cpp new file mode 100644 index 00000000..c922d8cc --- /dev/null +++ b/tests/plugins/flt.utils.muxer/test-clock-compatibility.cpp @@ -0,0 +1,443 @@ +/* + * SPDX-License-Identifier: GPL-2.0-only + * + * Copyright (C) 2024 EfficiOS Inc. + */ + +#include "cpp-common/bt2/component-class-dev.hpp" +#include "cpp-common/bt2/component-class.hpp" +#include "cpp-common/bt2/error.hpp" +#include "cpp-common/bt2/graph.hpp" +#include "cpp-common/bt2/plugin-load.hpp" +#include "cpp-common/bt2c/call.hpp" +#include "cpp-common/vendor/fmt/format.h" /* IWYU pragma: keep */ + +#include "tap/tap.h" + +namespace { + +/* The types of messages a `TestSourceIter` is instructed to send. */ +enum class MsgType +{ + /* Send stream beginning and stream end messages. */ + STREAM, + + /* Send a message iterator inactivity message. */ + MSG_ITER_INACTIVITY, +}; + +__attribute__((used)) const char *format_as(MsgType msgType) noexcept +{ + switch (msgType) { + case MsgType::STREAM: + return "stream beginning/end"; + + case MsgType::MSG_ITER_INACTIVITY: + return "message iterator inactivity"; + } + + bt_common_abort(); +} + +using CreateClockClass = bt2::ClockClass::Shared (*)(bt2::SelfComponent); + +struct TestSourceData final +{ + /* The function to call to obtain a clock class. */ + CreateClockClass createClockClass; + + /* The type of messages to send. */ + MsgType msgType; + + /* If not empty, the clock snapshot to set on the message. */ + bt2s::optional clockSnapshot; +}; + +class TestSource; + +class TestSourceIter final : public bt2::UserMessageIterator +{ + friend bt2::UserMessageIterator; + +public: + explicit TestSourceIter(const bt2::SelfMessageIterator self, + bt2::SelfMessageIteratorConfiguration, + const bt2::SelfComponentOutputPort port) : + bt2::UserMessageIterator {self, "TEST-SRC-MSG-ITER"}, + _mData {&port.data()}, _mSelf {self} + { + } + +private: + void _next(bt2::ConstMessageArray& msgs) + { + if (_mDone) { + return; + } + + const auto clockCls = _mData->createClockClass(_mSelf.component()); + + switch (_mData->msgType) { + case MsgType::STREAM: + { + const auto traceCls = _mSelf.component().createTraceClass(); + const auto streamCls = traceCls->createStreamClass(); + + if (clockCls) { + streamCls->defaultClockClass(*clockCls); + } + + const auto stream = streamCls->instantiate(*traceCls->instantiate()); + + /* Create stream beginning message. */ + msgs.append(bt2c::call([&] { + const auto streamBeginningMsg = this->_createStreamBeginningMessage(*stream); + + /* Set clock snapshot if instructed to. */ + if (_mData->clockSnapshot) { + streamBeginningMsg->defaultClockSnapshot(*_mData->clockSnapshot); + } + + return streamBeginningMsg; + })); + + /* + * The iterator needs to send a stream end message to avoid + * a postcondition assertion failure, where it's ended but + * didn't end all streams. + * + * The stream end messages don't play a role in the test + * otherwise. + */ + msgs.append(this->_createStreamEndMessage(*stream)); + break; + } + + case MsgType::MSG_ITER_INACTIVITY: + msgs.append( + this->_createMessageIteratorInactivityMessage(*clockCls, *_mData->clockSnapshot)); + break; + } + + _mDone = true; + } + + bool _mDone = false; + const TestSourceData *_mData; + bt2::SelfMessageIterator _mSelf; +}; + +class TestSource final : public bt2::UserSourceComponent +{ + friend class TestSourceIter; + + using _ThisUserSourceComponent = + bt2::UserSourceComponent; + +public: + static constexpr auto name = "test-source"; + + explicit TestSource(const bt2::SelfSourceComponent self, bt2::ConstMapValue, + TestSourceData * const data) : + _ThisUserSourceComponent {self, "TEST-SRC"}, + _mData {*data} + { + this->_addOutputPort("out", _mData); + } + +private: + TestSourceData _mData; +}; + +class ErrorTestCase final +{ +public: + /* Intentionally not explicit */ + ErrorTestCase(CreateClockClass createClockClass1Param, CreateClockClass createClockClass2Param, + const char * const testName, const char * const expectedCauseMsg) : + _mCreateClockClass1 {createClockClass1Param}, + _mCreateClockClass2 {createClockClass2Param}, _mTestName {testName}, _mExpectedCauseMsg { + expectedCauseMsg} + { + } + + void run() const noexcept; + +private: + void _runOne(MsgType msgType1, MsgType msgType2) const noexcept; + + CreateClockClass _mCreateClockClass1; + CreateClockClass _mCreateClockClass2; + const char *_mTestName; + const char *_mExpectedCauseMsg; +}; + +bt2::ClockClass::Shared noClockClass(bt2::SelfComponent) noexcept +{ + return bt2::ClockClass::Shared {}; +} + +void ErrorTestCase::run() const noexcept +{ + static constexpr std::array msgTypes { + MsgType::STREAM, + MsgType::MSG_ITER_INACTIVITY, + }; + + for (const auto msgType1 : msgTypes) { + for (const auto msgType2 : msgTypes) { + /* + * It's not possible to create message iterator inactivity + * messages without a clock class. Skip those cases. + */ + if ((msgType1 == MsgType::MSG_ITER_INACTIVITY && _mCreateClockClass1 == noClockClass) || + (msgType2 == MsgType::MSG_ITER_INACTIVITY && _mCreateClockClass2 == noClockClass)) { + continue; + } + + /* + * The test scenarios depend on the message with the first + * clock class going through the muxer first. + * + * Between a message with a clock snapshot and a message + * without a clock snapshot, the muxer always picks the + * message without a clock snapshot first. + * + * Message iterator inactivity messages always have a clock + * snapshot. Therefore, if: + * + * First message: + * A message iterator inactivity message (always has a + * clock snapshot). + * + * Second message: + * Doesn't have a clock class (never has a clock + * snapshot). + * + * Then there's no way for the first message to go through + * first. + * + * Skip those cases. + */ + if (msgType1 == MsgType::MSG_ITER_INACTIVITY && _mCreateClockClass2 == noClockClass) { + continue; + } + + this->_runOne(msgType1, msgType2); + } + } +} + +std::string makeSpecTestName(const char * const testName, const MsgType msgType1, + const MsgType msgType2) +{ + return fmt::format("{} ({}, {})", testName, msgType1, msgType2); +} + +void ErrorTestCase::_runOne(const MsgType msgType1, const MsgType msgType2) const noexcept +{ + const auto specTestName = makeSpecTestName(_mTestName, msgType1, msgType2); + const auto srcCompCls = bt2::SourceComponentClass::create(); + const auto graph = bt2::Graph::create(0); + + { + /* + * The test scenarios depend on the message with the first clock class going through the + * muxer first. Between a message with a clock snapshot and a message without a clock + * snapshot, the muxer always picks the message without a clock snapshot first. + * + * Therefore, for the first message, only set a clock snapshot when absolutely necessary, + * that is when the message type is "message iterator inactivity". + * + * For the second message, always set a clock snapshot when possible, that is when a clock + * class is defined for that message. + */ + const auto srcComp1 = + graph->addComponent(*srcCompCls, "source-1", + TestSourceData {_mCreateClockClass1, msgType1, + msgType1 == MsgType::MSG_ITER_INACTIVITY ? + bt2s::optional {10} : + bt2s::nullopt}); + const auto srcComp2 = + graph->addComponent(*srcCompCls, "source-2", + TestSourceData {_mCreateClockClass2, msgType2, + _mCreateClockClass2 != noClockClass ? + bt2s::optional {20} : + bt2s::nullopt}); + + const auto utilsPlugin = bt2::findPlugin("utils"); + + BT_ASSERT(utilsPlugin); + + /* Add muxer component */ + const auto muxerComp = bt2c::call([&] { + const auto muxerCompCls = utilsPlugin->filterComponentClasses()["muxer"]; + + BT_ASSERT(muxerCompCls); + + return graph->addComponent(*muxerCompCls, "the-muxer"); + }); + + /* Add dummy sink component */ + const auto sinkComp = bt2c::call([&] { + const auto dummySinkCompCls = utilsPlugin->sinkComponentClasses()["dummy"]; + + BT_ASSERT(dummySinkCompCls); + + return graph->addComponent(*dummySinkCompCls, "the-sink"); + }); + + /* Connect ports */ + graph->connectPorts(*srcComp1.outputPorts()["out"], *muxerComp.inputPorts()["in0"]); + graph->connectPorts(*srcComp2.outputPorts()["out"], *muxerComp.inputPorts()["in1"]); + graph->connectPorts(*muxerComp.outputPorts()["out"], *sinkComp.inputPorts()["in"]); + } + + const auto thrown = bt2c::call([&graph] { + try { + graph->run(); + } catch (const bt2::Error&) { + return true; + } + + return false; + }); + + ok(thrown, "%s - `bt2::Error` thrown", specTestName.c_str()); + + const auto error = bt2::takeCurrentThreadError(); + + ok(error, "%s - current thread has an error", specTestName.c_str()); + ok(error.length() > 0, "%s - error has at least one cause", specTestName.c_str()); + + const auto cause = error[0]; + + if (!ok(cause.message().startsWith(_mExpectedCauseMsg), "%s - cause's message is expected", + specTestName.c_str())) { + diag("expected: %s", _mExpectedCauseMsg); + diag("actual: %s", cause.message().data()); + } + + ok(cause.actorTypeIsMessageIterator(), "%s - cause's actor type is message iterator", + specTestName.c_str()); + ok(cause.asMessageIterator().componentName() == "the-muxer", + "%s - causes's component name is `the-muxer`", specTestName.c_str()); +} + +const bt2c::Uuid uuidA {"f00aaf65-ebec-4eeb-85b2-fc255cf1aa8a"}; +const bt2c::Uuid uuidB {"03482981-a77b-4d7b-94c4-592bf9e91785"}; + +const ErrorTestCase errorTestCases[] = { + {noClockClass, + [](const bt2::SelfComponent self) { + return self.createClockClass(); + }, + "no clock class followed by clock class", "Expecting no clock class, but got one"}, + + {[](const bt2::SelfComponent self) { + return self.createClockClass(); + }, + noClockClass, "clock class with Unix epoch origin followed by no clock class", + "Expecting a clock class, but got none"}, + + {[](const bt2::SelfComponent self) { + return self.createClockClass(); + }, + [](const bt2::SelfComponent self) { + const auto clockCls = self.createClockClass(); + + clockCls->originIsUnixEpoch(false); + return clockCls; + }, + "clock class with Unix epoch origin followed by clock class with other origin", + "Expecting a clock class having a Unix epoch origin, but got one not having a Unix epoch origin"}, + + {[](const bt2::SelfComponent self) { + const auto clockCls = self.createClockClass(); + + clockCls->originIsUnixEpoch(false).uuid(uuidA); + return clockCls; + }, + noClockClass, "clock class with other origin and a UUID followed by no clock class", + "Expecting a clock class, but got none"}, + + {[](const bt2::SelfComponent self) { + const auto clockCls = self.createClockClass(); + + clockCls->originIsUnixEpoch(false).uuid(uuidA); + return clockCls; + }, + [](const bt2::SelfComponent self) { + return self.createClockClass(); + }, + "clock class with other origin and a UUID followed by clock class with Unix epoch origin", + "Expecting a clock class not having a Unix epoch origin, but got one having a Unix epoch origin"}, + + {[](const bt2::SelfComponent self) { + const auto clockCls = self.createClockClass(); + + clockCls->originIsUnixEpoch(false).uuid(uuidA); + return clockCls; + }, + [](const bt2::SelfComponent self) { + const auto clockCls = self.createClockClass(); + + clockCls->originIsUnixEpoch(false); + return clockCls; + }, + "clock class with other origin and a UUID followed by clock class with other origin and no UUID", + "Expecting a clock class with a UUID, but got one without a UUID"}, + + {[](const bt2::SelfComponent self) { + const auto clockCls = self.createClockClass(); + + clockCls->originIsUnixEpoch(false).uuid(uuidA); + return clockCls; + }, + [](const bt2::SelfComponent self) { + const auto clockCls = self.createClockClass(); + + clockCls->originIsUnixEpoch(false).uuid(uuidB); + return clockCls; + }, + "clock class with other origin and a UUID followed by clock class with other origin and another UUID", + "Expecting a clock class with a specific UUID, but got one with a different UUID"}, + + {[](const bt2::SelfComponent self) { + const auto clockCls = self.createClockClass(); + + clockCls->originIsUnixEpoch(false); + return clockCls; + }, + noClockClass, "clock class with other origin and no UUID followed by no clock class", + "Expecting a clock class, but got none"}, + + {[](const bt2::SelfComponent self) { + const auto clockCls = self.createClockClass(); + + clockCls->originIsUnixEpoch(false); + return clockCls; + }, + + [](const bt2::SelfComponent self) { + const auto clockCls = self.createClockClass(); + + clockCls->originIsUnixEpoch(false); + return clockCls; + }, + "clock class with other origin and no UUID followed by different clock class", + "Unexpected clock class"}, +}; + +} /* namespace */ + +int main() +{ + plan_tests(150); + + for (auto& errorTestCase : errorTestCases) { + errorTestCase.run(); + } + + return exit_status(); +}