Tests: Introduce test_ust_constructor
authorMathieu Desnoyers <mathieu.desnoyers@efficios.com>
Fri, 17 Feb 2023 20:32:25 +0000 (15:32 -0500)
committerJérémie Galarneau <jeremie.galarneau@efficios.com>
Mon, 1 May 2023 15:43:49 +0000 (11:43 -0400)
Test instrumentation coverage of C/C++ constructors and destructors by
LTTng-UST tracepoints.

Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
Signed-off-by: Jérémie Galarneau <jeremie.galarneau@efficios.com>
Change-Id: Ia9e5a5a57bfa7fd4316f8a914ef97effd020262e

configure.ac
tests/regression/Makefile.am
tests/regression/ust/Makefile.am
tests/regression/ust/ust-constructor/Makefile.am [new file with mode: 0644]
tests/regression/ust/ust-constructor/test_ust_constructor.py [new file with mode: 0755]
tests/utils/lttngtest/environment.py

index 9af597923a3a611464da1004650f5b0b45696c92..b085af884047557f49dc0000814978ccefe9e83b 100644 (file)
@@ -1269,6 +1269,7 @@ AC_CONFIG_FILES([
        tests/regression/ust/rotation-destroy-flush/Makefile
        tests/regression/ust/blocking/Makefile
        tests/regression/ust/namespaces/Makefile
+       tests/regression/ust/ust-constructor/Makefile
        tests/stress/Makefile
        tests/unit/Makefile
        tests/unit/ini_config/Makefile
index c1fc9134268f8aaee3a1e8b76f260b934af7924e..a95feee9893c6429364cd83f2733ddd6e3a69a24 100644 (file)
@@ -88,6 +88,7 @@ TESTS += ust/before-after/test_before_after \
        ust/blocking/test_blocking \
        ust/multi-lib/test_multi_lib \
        ust/rotation-destroy-flush/test_rotation_destroy_flush \
+       ust/ust-constructor/test_ust_constructor.py \
        tools/metadata/test_ust \
        tools/relayd-grouping/test_ust \
        tools/trigger/rate-policy/test_ust_rate_policy
index 5af414966d80fc5cf07583f95c43519e74a1f568..dd8186f9a652953aa62fbf88a25a1b4ca56583cf 100644 (file)
@@ -24,7 +24,8 @@ SUBDIRS = \
        overlap \
        periodical-metadata-flush \
        rotation-destroy-flush \
-       type-declarations
+       type-declarations \
+       ust-constructor
 
 if HAVE_OBJCOPY
 SUBDIRS += \
diff --git a/tests/regression/ust/ust-constructor/Makefile.am b/tests/regression/ust/ust-constructor/Makefile.am
new file mode 100644 (file)
index 0000000..4201fdf
--- /dev/null
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+noinst_SCRIPTS = test_ust_constructor.py
+EXTRA_DIST = test_ust_constructor.py
+
+all-local:
+       @if [ x"$(srcdir)" != x"$(builddir)" ]; then \
+               for script in $(EXTRA_DIST); do \
+                       cp -f $(srcdir)/$$script $(builddir); \
+               done; \
+       fi
+
+clean-local:
+       @if [ x"$(srcdir)" != x"$(builddir)" ]; then \
+               for script in $(EXTRA_DIST); do \
+                       rm -f $(builddir)/$$script; \
+               done; \
+       fi
diff --git a/tests/regression/ust/ust-constructor/test_ust_constructor.py b/tests/regression/ust/ust-constructor/test_ust_constructor.py
new file mode 100755 (executable)
index 0000000..5e6523c
--- /dev/null
@@ -0,0 +1,252 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 Jérémie Galarneau <jeremie.galarneau@efficios.com>
+# Copyright (C) 2023 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+#
+# SPDX-License-Identifier: GPL-2.0-only
+
+from cgi import test
+import pathlib
+import sys
+import os
+from typing import Any, Callable, Type
+
+"""
+Test instrumentation coverage of C/C++ constructors and destructors by LTTng-UST
+tracepoints.
+
+This test successively sets up a session, traces a test application, and then
+reads the resulting trace to determine if all the expected events are present.
+"""
+
+# Import in-tree test utils
+test_utils_import_path = pathlib.Path(__file__).absolute().parents[3] / "utils"
+sys.path.append(str(test_utils_import_path))
+
+import lttngtest
+import bt2
+
+num_tests = 3
+
+expected_events = [
+    {"name": "tp_so:constructor_c_provider_shared_library", "msg": None, "count": 0},
+    {"name": "tp_a:constructor_c_provider_static_archive", "msg": None, "count": 0},
+    {
+        "name": "tp_so:constructor_cplusplus_provider_shared_library",
+        "msg": "global - shared library define and provider",
+        "count": 0,
+    },
+    {
+        "name": "tp_a:constructor_cplusplus_provider_static_archive",
+        "msg": "global - static archive define and provider",
+        "count": 0,
+    },
+    {"name": "tp:constructor_c_across_units_before_define", "msg": None, "count": 0},
+    {
+        "name": "tp:constructor_cplusplus",
+        "msg": "global - across units before define",
+        "count": 0,
+    },
+    {"name": "tp:constructor_c_same_unit_before_define", "msg": None, "count": 0},
+    {"name": "tp:constructor_c_same_unit_after_define", "msg": None, "count": 0},
+    {
+        "name": "tp:constructor_cplusplus",
+        "msg": "global - same unit before define",
+        "count": 0,
+    },
+    {
+        "name": "tp:constructor_cplusplus",
+        "msg": "global - same unit after define",
+        "count": 0,
+    },
+    {"name": "tp:constructor_c_across_units_after_define", "msg": None, "count": 0},
+    {
+        "name": "tp:constructor_cplusplus",
+        "msg": "global - across units after define",
+        "count": 0,
+    },
+    {"name": "tp:constructor_c_same_unit_before_provider", "msg": None, "count": 0},
+    {"name": "tp:constructor_c_same_unit_after_provider", "msg": None, "count": 0},
+    {
+        "name": "tp:constructor_cplusplus",
+        "msg": "global - same unit before provider",
+        "count": 0,
+    },
+    {
+        "name": "tp:constructor_cplusplus",
+        "msg": "global - same unit after provider",
+        "count": 0,
+    },
+    {"name": "tp:constructor_c_across_units_after_provider", "msg": None, "count": 0},
+    {
+        "name": "tp:constructor_cplusplus",
+        "msg": "global - across units after provider",
+        "count": 0,
+    },
+    {"name": "tp:constructor_cplusplus", "msg": "main() local", "count": 0},
+    {
+        "name": "tp_so:constructor_cplusplus_provider_shared_library",
+        "msg": "main() local - shared library define and provider",
+        "count": 0,
+    },
+    {
+        "name": "tp_a:constructor_cplusplus_provider_static_archive",
+        "msg": "main() local - static archive define and provider",
+        "count": 0,
+    },
+    {"name": "tp:main", "msg": None, "count": 0},
+    {
+        "name": "tp_a:destructor_cplusplus_provider_static_archive",
+        "msg": "main() local - static archive define and provider",
+        "count": 0,
+    },
+    {
+        "name": "tp_so:destructor_cplusplus_provider_shared_library",
+        "msg": "main() local - shared library define and provider",
+        "count": 0,
+    },
+    {"name": "tp:destructor_cplusplus", "msg": "main() local", "count": 0},
+    {
+        "name": "tp:destructor_cplusplus",
+        "msg": "global - across units after provider",
+        "count": 0,
+    },
+    {
+        "name": "tp:destructor_cplusplus",
+        "msg": "global - same unit after provider",
+        "count": 0,
+    },
+    {
+        "name": "tp:destructor_cplusplus",
+        "msg": "global - same unit before provider",
+        "count": 0,
+    },
+    {
+        "name": "tp:destructor_cplusplus",
+        "msg": "global - across units after define",
+        "count": 0,
+    },
+    {
+        "name": "tp:destructor_cplusplus",
+        "msg": "global - same unit after define",
+        "count": 0,
+    },
+    {
+        "name": "tp:destructor_cplusplus",
+        "msg": "global - same unit before define",
+        "count": 0,
+    },
+    {
+        "name": "tp:destructor_cplusplus",
+        "msg": "global - across units before define",
+        "count": 0,
+    },
+    {
+        "name": "tp_a:destructor_cplusplus_provider_static_archive",
+        "msg": "global - static archive define and provider",
+        "count": 0,
+    },
+    {
+        "name": "tp_so:destructor_cplusplus_provider_shared_library",
+        "msg": "global - shared library define and provider",
+        "count": 0,
+    },
+    {"name": "tp:destructor_c_across_units_after_provider", "msg": None, "count": 0},
+    {"name": "tp:destructor_c_same_unit_after_provider", "msg": None, "count": 0},
+    {"name": "tp:destructor_c_same_unit_before_provider", "msg": None, "count": 0},
+    {"name": "tp:destructor_c_across_units_after_define", "msg": None, "count": 0},
+    {"name": "tp:destructor_c_same_unit_after_define", "msg": None, "count": 0},
+    {"name": "tp:destructor_c_same_unit_before_define", "msg": None, "count": 0},
+    {"name": "tp:destructor_c_across_units_before_define", "msg": None, "count": 0},
+    {"name": "tp_a:destructor_c_provider_static_archive", "msg": None, "count": 0},
+    {"name": "tp_so:destructor_c_provider_shared_library", "msg": None, "count": 0},
+]
+
+
+def capture_trace(
+    tap: lttngtest.TapGenerator, test_env: lttngtest._Environment
+) -> lttngtest.LocalSessionOutputLocation:
+    tap.diagnostic(
+        "Capture trace from application with instrumented C/C++ constructors/destructors"
+    )
+
+    session_output_location = lttngtest.LocalSessionOutputLocation(
+        test_env.create_temporary_directory("trace")
+    )
+
+    client: lttngtest.Controller = lttngtest.LTTngClient(test_env, log=tap.diagnostic)
+
+    with tap.case("Create a session") as test_case:
+        session = client.create_session(output=session_output_location)
+    tap.diagnostic("Created session `{session_name}`".format(session_name=session.name))
+
+    with tap.case(
+        "Add a channel to session `{session_name}`".format(session_name=session.name)
+    ) as test_case:
+        channel = session.add_channel(lttngtest.TracingDomain.User)
+    tap.diagnostic("Created channel `{channel_name}`".format(channel_name=channel.name))
+
+    # Enable all user space events, the default for a user tracepoint event rule.
+    channel.add_recording_rule(lttngtest.UserTracepointEventRule("tp*"))
+
+    session.start()
+    test_app = test_env.launch_trace_test_constructor_application()
+    test_app.wait_for_exit()
+    session.stop()
+    session.destroy()
+    return session_output_location
+
+
+def validate_trace(trace_location: pathlib.Path, tap: lttngtest.TapGenerator) -> bool:
+    success = True
+    unknown_event_count = 0
+
+    for msg in bt2.TraceCollectionMessageIterator(str(trace_location)):
+        if type(msg) is not bt2._EventMessageConst:
+            continue
+
+        found = False
+        for event in expected_events:
+            if event["name"] == msg.event.name and event["msg"] is None:
+                found = True
+                event["count"] = event["count"] + 1
+                break
+            elif (
+                event["name"] == msg.event.name
+                and event["msg"] is not None
+                and event["msg"] == msg.event["msg"]
+            ):
+                found = True
+                event["count"] = event["count"] + 1
+                break
+        if found == False:
+            unknown_event_count = unknown_event_count + 1
+            printmsg = None
+            if "msg" in msg.event:
+                printmsg = msg.event["msg"]
+            tap.diagnostic(
+                'Unexpected event name="{}" msg="{}" encountered'.format(
+                    msg.event.name, str(printmsg)
+                )
+            )
+
+    for event in expected_events:
+        if event["count"] != 1:
+            success = False
+            tap.diagnostic("Expected event {} not found".format(event["name"]))
+    if unknown_event_count != 0:
+        success = False
+    return success
+
+
+tap = lttngtest.TapGenerator(num_tests)
+tap.diagnostic("Test user space constructor/destructor instrumentation coverage")
+
+with lttngtest.test_environment(with_sessiond=True, log=tap.diagnostic) as test_env:
+    outputlocation = capture_trace(tap, test_env)
+    tap.test(
+        validate_trace(outputlocation.path, tap),
+        "Validate that trace constains expected events",
+    )
+
+sys.exit(0 if tap.is_successful else 1)
index a119669ed99f1718d52f738382a44dc8b874ef7b..282e95643c33a6ece9cd2f815bc1d28f5f6f69f0 100644 (file)
@@ -187,6 +187,44 @@ class WaitTraceTestApplication:
             self._process.wait()
 
 
+class TraceTestApplication:
+    """
+    Create an application to trace.
+    """
+
+    def __init__(self, binary_path: pathlib.Path, environment: "Environment"):
+        self._environment: Environment = environment
+        self._has_returned = False
+
+        test_app_env = os.environ.copy()
+        test_app_env["LTTNG_HOME"] = str(environment.lttng_home_location)
+        # Make sure the app is blocked until it is properly registered to
+        # the session daemon.
+        test_app_env["LTTNG_UST_REGISTER_TIMEOUT"] = "-1"
+
+        test_app_args = [str(binary_path)]
+
+        self._process: subprocess.Popen = subprocess.Popen(
+            test_app_args, env=test_app_env
+        )
+
+    def wait_for_exit(self) -> None:
+        if self._process.wait() != 0:
+            raise RuntimeError(
+                "Test application has exit with return code `{return_code}`".format(
+                    return_code=self._process.returncode
+                )
+            )
+        self._has_returned = True
+
+    def __del__(self):
+        if not self._has_returned:
+            # This is potentially racy if the pid has been recycled. However,
+            # we can't use pidfd_open since it is only available in python >= 3.9.
+            self._process.kill()
+            self._process.wait()
+
+
 class ProcessOutputConsumer(threading.Thread, logger._Logger):
     def __init__(
         self,
@@ -374,6 +412,22 @@ class _Environment(logger._Logger):
             self,
         )
 
+    def launch_trace_test_constructor_application(
+        self
+    ) -> TraceTestApplication:
+        """
+        Launch an application that will trace from within constructors.
+        """
+        return TraceTestApplication(
+            self._project_root
+            / "tests"
+            / "utils"
+            / "testapp"
+            / "gen-ust-events-constructor"
+            / "gen-ust-events-constructor",
+            self,
+        )
+
     # Clean-up managed processes
     def _cleanup(self):
         # type: () -> None
This page took 0.030495 seconds and 4 git commands to generate.