tests/plugins/flt.utils.muxer: add test for clock (in)compatibility cases
authorSimon Marchi <simon.marchi@efficios.com>
Tue, 12 Mar 2024 21:23:02 +0000 (17:23 -0400)
committerSimon Marchi <simon.marchi@efficios.com>
Tue, 26 Mar 2024 18:56:36 +0000 (14:56 -0400)
Add a test for the various clock class incompatibility cases checked by
the `MsgIter::_makeSureClkClsIsExpected` method in the `flt.utils.muxer`
component class.

The scenarios are equivalent to the triggers in
`tests/lib/conds/conds-trigger.cpp`, used to test the preconditions in
the library.  Since the scenarios are equivalent, it would probably be
possible to share some code with that file, but I haven't tried it.

In essence, for each scenario, the test creates a graph where a muxer
component receives two messages with incompatible clock
configurations.  The test expects that an exception is thrown, and
checks whether the root cause contains an expected string.

Change-Id: I8bd98fa52719ab2cedb7cbb09310a560b437eb28
Signed-off-by: Simon Marchi <simon.marchi@efficios.com>
Reviewed-on: https://review.lttng.org/c/babeltrace/+/12005
Reviewed-by: Philippe Proulx <eeppeliteloop@gmail.com>
Tested-by: jenkins <jenkins@lttng.org>
tests/Makefile.am
tests/plugins/flt.utils.muxer/test-clock-compatibility.cpp [new file with mode: 0644]
tests/plugins/flt.utils.muxer/test-clock-compatibility.sh [new file with mode: 0755]

index a9200a0c687c53b726ec0f73edce8db68555839c..88c385e00349d3d7d6bf121a0dcbc929f86ee177 100644 (file)
@@ -1,5 +1,7 @@
 # SPDX-License-Identifier: MIT
 
+include $(top_srcdir)/src/Makefile.common.inc
+
 SUBDIRS = \
        utils \
        lib \
@@ -120,12 +122,33 @@ if !ENABLE_BUILT_IN_PLUGINS
 TESTS_LIB += lib/test-plugins.sh
 endif
 
+# plugins/flt.utils.muxer
+
+noinst_PROGRAMS += plugins/flt.utils.muxer/test-clock-compatibility
+
+plugins_flt_utils_muxer_test_clock_compatibility_SOURCES = \
+       plugins/flt.utils.muxer/test-clock-compatibility.cpp
+
+plugins_flt_utils_muxer_test_clock_compatibility_LDADD = \
+       $(COMMON_TEST_LDADD) \
+       $(top_builddir)/src/lib/libbabeltrace2.la \
+       $(top_builddir)/src/cpp-common/vendor/fmt/libfmt.la
+
+dist_check_SCRIPTS += plugins/flt.utils.muxer/test-clock-compatibility.sh
+
+if ENABLE_BUILT_IN_PLUGINS
+plugins_flt_utils_muxer_test_clock_compatibility_LDFLAGS = $(call pluginarchive,utils)
+plugins_flt_utils_muxer_test_clock_compatibility_LDADD += \
+       $(top_builddir)/src/plugins/common/param-validation/libparam-validation.la
+endif # ENABLE_BUILT_IN_PLUGINS
+
 TESTS_PLUGINS = \
        plugins/src.ctf.fs/fail/test-fail.sh \
        plugins/src.ctf.fs/succeed/test-succeed.sh \
        plugins/src.ctf.fs/test-deterministic-ordering.sh \
        plugins/sink.ctf.fs/succeed/test-succeed.sh \
-       plugins/sink.text.details/succeed/test-succeed.sh
+       plugins/sink.text.details/succeed/test-succeed.sh \
+       plugins/flt.utils.muxer/test-clock-compatibility.sh
 
 if !ENABLE_BUILT_IN_PLUGINS
 if ENABLE_PYTHON_BINDINGS
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 (file)
index 0000000..c922d8c
--- /dev/null
@@ -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<std::uint64_t> clockSnapshot;
+};
+
+class TestSource;
+
+class TestSourceIter final : public bt2::UserMessageIterator<TestSourceIter, TestSource>
+{
+    friend bt2::UserMessageIterator<TestSourceIter, TestSource>;
+
+public:
+    explicit TestSourceIter(const bt2::SelfMessageIterator self,
+                            bt2::SelfMessageIteratorConfiguration,
+                            const bt2::SelfComponentOutputPort port) :
+        bt2::UserMessageIterator<TestSourceIter, TestSource> {self, "TEST-SRC-MSG-ITER"},
+        _mData {&port.data<const TestSourceData>()}, _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<TestSource, TestSourceIter, TestSourceData>
+{
+    friend class TestSourceIter;
+
+    using _ThisUserSourceComponent =
+        bt2::UserSourceComponent<TestSource, TestSourceIter, TestSourceData>;
+
+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<MsgType, 2> 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<TestSource>();
+    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<std::uint64_t> {10} :
+                                                    bt2s::nullopt});
+        const auto srcComp2 =
+            graph->addComponent(*srcCompCls, "source-2",
+                                TestSourceData {_mCreateClockClass2, msgType2,
+                                                _mCreateClockClass2 != noClockClass ?
+                                                    bt2s::optional<std::uint64_t> {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();
+}
diff --git a/tests/plugins/flt.utils.muxer/test-clock-compatibility.sh b/tests/plugins/flt.utils.muxer/test-clock-compatibility.sh
new file mode 100755 (executable)
index 0000000..e3e2cca
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Copyright (C) 2024 EfficiOS Inc.
+#
+
+if [[ -n "${BT_TESTS_SRCDIR:-}" ]]; then
+       UTILSSH="$BT_TESTS_SRCDIR/utils/utils.sh"
+else
+       UTILSSH="$(dirname "$0")/../../utils/utils.sh"
+fi
+
+# shellcheck source=../../utils/utils.sh
+source "$UTILSSH"
+
+bt_run_in_py_env "${BT_TESTS_BUILDDIR}/plugins/flt.utils.muxer/test-clock-compatibility"
This page took 0.029309 seconds and 4 git commands to generate.