From 708e374b93a9eff80053d5518b93f131032c79fb Mon Sep 17 00:00:00 2001 From: Justin Blagden Date: Thu, 21 Aug 2025 14:50:39 -0500 Subject: [PATCH 1/3] feat: Adds a more user friendly message when we fail to find the adaptor's executable on the system PATH. Signed-off-by: Justin Blagden --- .../process/_logging_subprocess.py | 29 +++++++++++++++++-- .../test_integration_logging_subprocess.py | 11 +++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/openjd/adaptor_runtime/process/_logging_subprocess.py b/src/openjd/adaptor_runtime/process/_logging_subprocess.py index 5cfd2294..cb74c290 100644 --- a/src/openjd/adaptor_runtime/process/_logging_subprocess.py +++ b/src/openjd/adaptor_runtime/process/_logging_subprocess.py @@ -4,6 +4,7 @@ from __future__ import annotations import logging +import shutil import signal import subprocess import uuid @@ -21,7 +22,11 @@ class LoggingSubprocess(object): - """A process whose stdout/stderr lines are sent to a configurable logger""" + """A process whose stdout/stderr lines are sent to a configurable logger + + Raises: + FileNotFoundError: When the executable the adaptor is set to run cannot be found. + """ _logger: logging.Logger _process: subprocess.Popen @@ -64,7 +69,27 @@ def __init__( # In Windows, this is required for signal. SIGBREAK will be sent to the entire process group. # Without this one, current process will also get the SIGBREAK and may react incorrectly. popen_params.update(creationflags=subprocess.CREATE_NEW_PROCESS_GROUP) # type: ignore[attr-defined] - self._process = subprocess.Popen(**popen_params) + + try: + self._process = subprocess.Popen(**popen_params) + + except FileNotFoundError as fnf_error: + # In ManagedProcess we prepend the executable to the list of arguments before creating a LoggingSubprocess + executable = args[0] + exe_path = shutil.which(executable) + + # If we didn't find the executable found by which + if exe_path is not None: + raise FileNotFoundError( + f"Could not find adaptor executable at: {exe_path} using alias {executable}\n" + f"Error:{fnf_error}" + ) + + raise FileNotFoundError( + f"Could not find the executable associated with the adaptor: {executable}\n" + f"Is the executable on the PATH or in the startup directory?\n" + f"Error:{fnf_error}" + ) if not self._process.stdout: # pragma: no cover raise RuntimeError("process stdout not set") diff --git a/test/openjd/adaptor_runtime/integ/process/test_integration_logging_subprocess.py b/test/openjd/adaptor_runtime/integ/process/test_integration_logging_subprocess.py index c524104a..474ccabb 100644 --- a/test/openjd/adaptor_runtime/integ/process/test_integration_logging_subprocess.py +++ b/test/openjd/adaptor_runtime/integ/process/test_integration_logging_subprocess.py @@ -113,9 +113,9 @@ def test_startup_directory(self, startup_dir: str | None, caplog): def test_startup_directory_empty_posix(self): """When calling LoggingSubprocess with an empty cwd, FileNotFoundError will be raised.""" args = ["pwd"] - with pytest.raises(FileNotFoundError) as excinfo: + with pytest.raises(FileNotFoundError) as exc_info: LoggingSubprocess(args=args, startup_directory="") - assert "[Errno 2] No such file or directory: ''" in str(excinfo.value) + assert "[Errno 2] No such file or directory: ''" in str(exc_info.value) @pytest.mark.skipif(not OSName.is_windows(), reason="Only run this test in Windows.") def test_startup_directory_empty_windows(self): @@ -151,6 +151,13 @@ def test_log_levels(self, log_level: int, caplog): assert any(r.message == message and r.levelno == _STDERR_LEVEL for r in records) + def test_executable_not_found(self): + """When calling LoggingSubprocess with a missing executable, FileNotFoundError will be raised""" + args = ["missing_executable"] + with pytest.raises(FileNotFoundError) as exc_info: + LoggingSubprocess(args=args) + assert "Could not find the executable associated with the adaptor" in str(exc_info.value) + class TestIntegrationRegexHandler(object): """Integration tests for LoggingSubprocess""" From f5cfd1ecbdb8b3d81a1186037c916c245ba8e3cb Mon Sep 17 00:00:00 2001 From: Justin Blagden Date: Fri, 29 Aug 2025 14:16:49 -0500 Subject: [PATCH 2/3] fix: Removes mentioning an adaptor in the command not found error message. Signed-off-by: Justin Blagden --- src/openjd/adaptor_runtime/process/_logging_subprocess.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/openjd/adaptor_runtime/process/_logging_subprocess.py b/src/openjd/adaptor_runtime/process/_logging_subprocess.py index cb74c290..650a144c 100644 --- a/src/openjd/adaptor_runtime/process/_logging_subprocess.py +++ b/src/openjd/adaptor_runtime/process/_logging_subprocess.py @@ -86,8 +86,8 @@ def __init__( ) raise FileNotFoundError( - f"Could not find the executable associated with the adaptor: {executable}\n" - f"Is the executable on the PATH or in the startup directory?\n" + f"Could not find the specified command: {executable}\n" + f"Ensure the command is available from the PATH environment variable.\n" f"Error:{fnf_error}" ) From 3d78c02e4cc57d3914c0576d58e81cb90ecfc743 Mon Sep 17 00:00:00 2001 From: Justin Blagden Date: Wed, 3 Sep 2025 13:34:32 -0500 Subject: [PATCH 3/3] test: Updates the unit test to check for the updated error message Signed-off-by: Justin Blagden --- src/openjd/adaptor_runtime/process/_logging_subprocess.py | 2 +- .../integ/process/test_integration_logging_subprocess.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/openjd/adaptor_runtime/process/_logging_subprocess.py b/src/openjd/adaptor_runtime/process/_logging_subprocess.py index 650a144c..efabd5ab 100644 --- a/src/openjd/adaptor_runtime/process/_logging_subprocess.py +++ b/src/openjd/adaptor_runtime/process/_logging_subprocess.py @@ -81,7 +81,7 @@ def __init__( # If we didn't find the executable found by which if exe_path is not None: raise FileNotFoundError( - f"Could not find adaptor executable at: {exe_path} using alias {executable}\n" + f"Could not find executable at: {exe_path} using alias {executable}\n" f"Error:{fnf_error}" ) diff --git a/test/openjd/adaptor_runtime/integ/process/test_integration_logging_subprocess.py b/test/openjd/adaptor_runtime/integ/process/test_integration_logging_subprocess.py index 474ccabb..87d20e4c 100644 --- a/test/openjd/adaptor_runtime/integ/process/test_integration_logging_subprocess.py +++ b/test/openjd/adaptor_runtime/integ/process/test_integration_logging_subprocess.py @@ -156,7 +156,7 @@ def test_executable_not_found(self): args = ["missing_executable"] with pytest.raises(FileNotFoundError) as exc_info: LoggingSubprocess(args=args) - assert "Could not find the executable associated with the adaptor" in str(exc_info.value) + assert "Could not find the specified command" in str(exc_info.value) class TestIntegrationRegexHandler(object):