+ 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()
+ self._process.wait()
+
+
+class WaitTraceTestApplicationGroup:
+ def __init__(
+ self,
+ environment, # type: Environment
+ application_count, # type: int
+ event_count, # type: int
+ wait_time_between_events_us=0, # type: int
+ wait_before_exit=False, # type: bool
+ ):
+ self._wait_before_exit_file_path = (
+ pathlib.Path(
+ tempfile.mktemp(
+ prefix="app_group_",
+ suffix="_exit",
+ dir=_WaitTraceTestApplication._compat_pathlike(
+ environment.lttng_home_location
+ ),
+ )
+ )
+ if wait_before_exit
+ else None
+ )
+
+ self._apps = []
+ self._consumers = []
+ for i in range(application_count):
+ new_app = environment.launch_wait_trace_test_application(
+ event_count,
+ wait_time_between_events_us,
+ wait_before_exit,
+ self._wait_before_exit_file_path,
+ )
+
+ # Attach an output consumer to log the application's error output (if any).
+ if environment._logging_function:
+ app_output_consumer = ProcessOutputConsumer(
+ new_app._process,
+ "app-{}".format(str(new_app.vpid)),
+ environment._logging_function,
+ ) # type: Optional[ProcessOutputConsumer]
+ app_output_consumer.daemon = True
+ app_output_consumer.start()
+ self._consumers.append(app_output_consumer)
+
+ self._apps.append(new_app)
+
+ def trace(self):
+ # type: () -> None
+ for app in self._apps:
+ app.trace()
+
+ def exit(
+ self, wait_for_apps=False # type: bool
+ ):
+ if self._wait_before_exit_file_path is None:
+ raise RuntimeError(
+ "Can't call exit on an application group created with `wait_before_exit=False`"
+ )
+
+ # Wait for apps to have produced all of their events so that we can
+ # cause the death of all apps to happen within a short time span.
+ for app in self._apps:
+ app.wait_for_tracing_done()
+
+ open(
+ _WaitTraceTestApplication._compat_pathlike(
+ self._wait_before_exit_file_path
+ ),
+ mode="x",
+ )
+ # Performed in two passes to allow tests to stress the unregistration of many applications.
+ # Waiting for each app to exit turn-by-turn would defeat the purpose here.
+ if wait_for_apps:
+ for app in self._apps:
+ app.wait_for_exit()
+
+
+class _TraceTestApplication:
+ """
+ Create an application that emits events as soon as it is launched. In most
+ scenarios, it is preferable to use a WaitTraceTestApplication.
+ """
+
+ def __init__(self, binary_path, environment):
+ # type: (pathlib.Path, Environment)
+ self._process = None
+ self._environment = environment # type: 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(
+ test_app_args, env=test_app_env
+ ) # type: subprocess.Popen
+
+ def wait_for_exit(self):
+ # type: () -> 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 self._process is not None and not self._has_returned: