From da1e97c944bb51fa96e89235240bc8bc8b02eeb4 Mon Sep 17 00:00:00 2001 From: Mathieu Desnoyers Date: Fri, 17 Feb 2023 15:32:25 -0500 Subject: [PATCH] Tests: Introduce test_ust_constructor MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Test instrumentation coverage of C/C++ constructors and destructors by LTTng-UST tracepoints. Signed-off-by: Mathieu Desnoyers Signed-off-by: Jérémie Galarneau Change-Id: Ia9e5a5a57bfa7fd4316f8a914ef97effd020262e --- configure.ac | 1 + tests/regression/Makefile.am | 1 + tests/regression/ust/Makefile.am | 3 +- .../ust/ust-constructor/Makefile.am | 18 ++ .../ust-constructor/test_ust_constructor.py | 252 ++++++++++++++++++ tests/utils/lttngtest/environment.py | 54 ++++ 6 files changed, 328 insertions(+), 1 deletion(-) create mode 100644 tests/regression/ust/ust-constructor/Makefile.am create mode 100755 tests/regression/ust/ust-constructor/test_ust_constructor.py diff --git a/configure.ac b/configure.ac index 9af597923..b085af884 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/tests/regression/Makefile.am b/tests/regression/Makefile.am index c1fc91342..a95feee98 100644 --- a/tests/regression/Makefile.am +++ b/tests/regression/Makefile.am @@ -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 diff --git a/tests/regression/ust/Makefile.am b/tests/regression/ust/Makefile.am index 5af414966..dd8186f9a 100644 --- a/tests/regression/ust/Makefile.am +++ b/tests/regression/ust/Makefile.am @@ -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 index 000000000..4201fdfe8 --- /dev/null +++ b/tests/regression/ust/ust-constructor/Makefile.am @@ -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 index 000000000..5e6523c76 --- /dev/null +++ b/tests/regression/ust/ust-constructor/test_ust_constructor.py @@ -0,0 +1,252 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 Jérémie Galarneau +# Copyright (C) 2023 Mathieu Desnoyers +# +# 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) diff --git a/tests/utils/lttngtest/environment.py b/tests/utils/lttngtest/environment.py index a119669ed..282e95643 100644 --- a/tests/utils/lttngtest/environment.py +++ b/tests/utils/lttngtest/environment.py @@ -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 -- 2.34.1