tests: Ensure `_process` is set in _TraceTestApplications
[lttng-tools.git] / tests / utils / lttngtest / environment.py
index 4be5fb4faa356add20c59709800d6d1df6ef2541..f0e894a69e14c470c7ac56a0f98b8fc3dab3dad4 100644 (file)
@@ -93,6 +93,7 @@ class _WaitTraceTestApplication:
         wait_before_exit=False,  # type: bool
         wait_before_exit_file_path=None,  # type: Optional[pathlib.Path]
     ):
+        self._process = None
         self._environment = environment  # type: Environment
         self._iteration_count = event_count
         # File that the application will wait to see before tracing its events.
@@ -100,7 +101,7 @@ class _WaitTraceTestApplication:
             tempfile.mktemp(
                 prefix="app_",
                 suffix="_start_tracing",
-                dir=self._compat_open_path(environment.lttng_home_location),
+                dir=self._compat_pathlike(environment.lttng_home_location),
             )
         )
         # File that the application will create when all events have been emitted.
@@ -108,7 +109,7 @@ class _WaitTraceTestApplication:
             tempfile.mktemp(
                 prefix="app_",
                 suffix="_done_tracing",
-                dir=self._compat_open_path(environment.lttng_home_location),
+                dir=self._compat_pathlike(environment.lttng_home_location),
             )
         )
 
@@ -117,7 +118,7 @@ class _WaitTraceTestApplication:
                 tempfile.mktemp(
                     prefix="app_",
                     suffix="_exit",
-                    dir=self._compat_open_path(environment.lttng_home_location),
+                    dir=self._compat_pathlike(environment.lttng_home_location),
                 )
             )
 
@@ -133,7 +134,7 @@ class _WaitTraceTestApplication:
         app_ready_file_path = tempfile.mktemp(
             prefix="app_",
             suffix="_ready",
-            dir=self._compat_open_path(environment.lttng_home_location),
+            dir=self._compat_pathlike(environment.lttng_home_location),
         )  # type: str
 
         test_app_args = [str(binary_path)]
@@ -165,7 +166,7 @@ class _WaitTraceTestApplication:
     def _wait_for_file_to_be_created(self, sync_file_path):
         # type: (pathlib.Path) -> None
         while True:
-            if os.path.exists(sync_file_path):
+            if os.path.exists(self._compat_pathlike(sync_file_path)):
                 break
 
             if self._process.poll() is not None:
@@ -187,7 +188,7 @@ class _WaitTraceTestApplication:
                     return_code=self._process.returncode
                 )
             )
-        open(self._compat_open_path(self._app_start_tracing_file_path), mode="x")
+        open(self._compat_pathlike(self._app_start_tracing_file_path), mode="x")
 
     def wait_for_tracing_done(self):
         # type: () -> None
@@ -209,12 +210,13 @@ class _WaitTraceTestApplication:
         return self._process.pid
 
     @staticmethod
-    def _compat_open_path(path):
+    def _compat_pathlike(path):
         # type: (pathlib.Path) -> pathlib.Path | str
         """
-        The builtin open() in python >= 3.6 expects a path-like object while
-        prior versions expect a string or bytes object. Return the correct type
-        based on the presence of the "__fspath__" attribute specified in PEP-519.
+        The builtin open() and many methods of the 'os' library in Python >= 3.6
+        expect a path-like object while prior versions expect a string or
+        bytes object. Return the correct type based on the presence of the
+        "__fspath__" attribute specified in PEP-519.
         """
         if hasattr(path, "__fspath__"):
             return path
@@ -222,7 +224,7 @@ class _WaitTraceTestApplication:
             return str(path)
 
     def __del__(self):
-        if not self._has_returned:
+        if self._process is not None and 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()
@@ -243,7 +245,7 @@ class WaitTraceTestApplicationGroup:
                 tempfile.mktemp(
                     prefix="app_group_",
                     suffix="_exit",
-                    dir=_WaitTraceTestApplication._compat_open_path(
+                    dir=_WaitTraceTestApplication._compat_pathlike(
                         environment.lttng_home_location
                     ),
                 )
@@ -294,7 +296,7 @@ class WaitTraceTestApplicationGroup:
             app.wait_for_tracing_done()
 
         open(
-            _WaitTraceTestApplication._compat_open_path(
+            _WaitTraceTestApplication._compat_pathlike(
                 self._wait_before_exit_file_path
             ),
             mode="x",
@@ -314,6 +316,7 @@ class _TraceTestApplication:
 
     def __init__(self, binary_path, environment):
         # type: (pathlib.Path, Environment)
+        self._process = None
         self._environment = environment  # type: Environment
         self._has_returned = False
 
@@ -340,7 +343,7 @@ class _TraceTestApplication:
         self._has_returned = True
 
     def __del__(self):
-        if not self._has_returned:
+        if self._process is not None and 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()
@@ -374,6 +377,7 @@ class _Environment(logger._Logger):
         self,
         with_sessiond,  # type: bool
         log=None,  # type: Optional[Callable[[str], None]]
+        with_relayd=False,  # type: bool
     ):
         super().__init__(log)
         signal.signal(signal.SIGTERM, self._handle_termination_signal)
@@ -388,6 +392,11 @@ class _Environment(logger._Logger):
             "lttng_test_env_home"
         )  # type: Optional[TemporaryDirectory]
 
+        self._relayd = (
+            self._launch_lttng_relayd() if with_relayd else None
+        )  # type: Optional[subprocess.Popen[bytes]]
+        self._relayd_output_consumer = None
+
         self._sessiond = (
             self._launch_lttng_sessiond() if with_sessiond else None
         )  # type: Optional[subprocess.Popen[bytes]]
@@ -404,6 +413,21 @@ class _Environment(logger._Logger):
         # type: () -> pathlib.Path
         return self._project_root / "src" / "bin" / "lttng" / "lttng"
 
+    @property
+    def lttng_relayd_control_port(self):
+        # type: () -> int
+        return 5400
+
+    @property
+    def lttng_relayd_data_port(self):
+        # type: () -> int
+        return 5401
+
+    @property
+    def lttng_relayd_live_port(self):
+        # type: () -> int
+        return 5402
+
     def create_temporary_directory(self, prefix=None):
         # type: (Optional[str]) -> pathlib.Path
         # Simply return a path that is contained within LTTNG_HOME; it will
@@ -439,6 +463,53 @@ class _Environment(logger._Logger):
 
         return unpacked_vars
 
+    def _launch_lttng_relayd(self):
+        # type: () -> Optional[subprocess.Popen]
+        relayd_path = (
+            self._project_root / "src" / "bin" / "lttng-relayd" / "lttng-relayd"
+        )
+        if os.environ.get("LTTNG_TEST_NO_RELAYD", "0") == "1":
+            # Run without a relay daemon; the user may be running one
+            # under gdb, for example.
+            return None
+
+        relayd_env_vars = os.environ.get("LTTNG_RELAYD_ENV_VARS")
+        relayd_env = os.environ.copy()
+        if relayd_env_vars:
+            self._log("Additional lttng-relayd environment variables:")
+            for name, value in self._unpack_env_vars(relayd_env_vars):
+                self._log("{}={}".format(name, value))
+                relayd_env[name] = value
+
+        assert self._lttng_home is not None
+        relayd_env["LTTNG_HOME"] = str(self._lttng_home.path)
+        self._log(
+            "Launching relayd with LTTNG_HOME='${}'".format(str(self._lttng_home.path))
+        )
+        process = subprocess.Popen(
+            [
+                str(relayd_path),
+                "-C",
+                "tcp://0.0.0.0:{}".format(self.lttng_relayd_control_port),
+                "-D",
+                "tcp://0.0.0.0:{}".format(self.lttng_relayd_data_port),
+                "-L",
+                "tcp://localhost:{}".format(self.lttng_relayd_live_port),
+            ],
+            stdout=subprocess.PIPE,
+            stderr=subprocess.STDOUT,
+            env=relayd_env,
+        )
+
+        if self._logging_function:
+            self._relayd_output_consumer = ProcessOutputConsumer(
+                process, "lttng-relayd", self._logging_function
+            )
+            self._relayd_output_consumer.daemon = True
+            self._relayd_output_consumer.start()
+
+        return process
+
     def _launch_lttng_sessiond(self):
         # type: () -> Optional[subprocess.Popen]
         is_64bits_host = sys.maxsize > 2**32
@@ -576,6 +647,15 @@ class _Environment(logger._Logger):
             self._log("Session daemon killed")
             self._sessiond = None
 
+        if self._relayd and self._relayd.poll() is None:
+            self._relayd.terminate()
+            self._relayd.wait()
+            if self._relayd_output_consumer:
+                self._relayd_output_consumer.join()
+                self._relayd_output_consumer = None
+            self._log("Relayd killed")
+            self._relayd = None
+
         self._lttng_home = None
 
     def __del__(self):
@@ -583,9 +663,9 @@ class _Environment(logger._Logger):
 
 
 @contextlib.contextmanager
-def test_environment(with_sessiond, log=None):
-    # type: (bool, Optional[Callable[[str], None]]) -> Iterator[_Environment]
-    env = _Environment(with_sessiond, log)
+def test_environment(with_sessiond, log=None, with_relayd=False):
+    # type: (bool, Optional[Callable[[str], None]], bool) -> Iterator[_Environment]
+    env = _Environment(with_sessiond, log, with_relayd)
     try:
         yield env
     finally:
This page took 0.025914 seconds and 4 git commands to generate.