From 1228eca3d727add5a80a4ace806c246742130273 Mon Sep 17 00:00:00 2001 From: Benjamin Hamon Date: Wed, 1 Oct 2025 18:52:30 +0200 Subject: [PATCH 1/3] Set pip configuration path in develop command --- .../Scripts/automation_scripts/commands/develop_command.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Automation/Scripts/automation_scripts/commands/develop_command.py b/Automation/Scripts/automation_scripts/commands/develop_command.py index 3c792cd..b6564dd 100644 --- a/Automation/Scripts/automation_scripts/commands/develop_command.py +++ b/Automation/Scripts/automation_scripts/commands/develop_command.py @@ -35,7 +35,7 @@ def run(self, arguments: argparse.Namespace, simulate: bool, **kwargs) -> None: package_collection_for_pip.append(package.path_to_sources + "[all,dev]") logger.info("Setting up python virtual environment (Path: %s)", venv_directory) - python_environment.setup_virtual_environment(simulate = simulate) + python_environment.setup_virtual_environment("pip.conf", simulate = simulate) python_environment.install_python_packages_for_development(package_collection_for_pip, simulate = simulate) From ca31dcf1a3865636c3fece35cfb6e1eb73077664 Mon Sep 17 00:00:00 2001 From: Benjamin Hamon Date: Wed, 1 Oct 2025 19:01:46 +0200 Subject: [PATCH 2/3] Add dependency standard extensions --- Sources/toolkit/pyproject.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/toolkit/pyproject.toml b/Sources/toolkit/pyproject.toml index 880d19c..a29d2b5 100644 --- a/Sources/toolkit/pyproject.toml +++ b/Sources/toolkit/pyproject.toml @@ -25,7 +25,9 @@ classifiers = [ "Programming Language :: Python :: 3", ] -dependencies = [] +dependencies = [ + "benjaminhamon_standard_extensions ~= 2.0.1" +] [project.optional-dependencies] all = [ From 26d1d9fcba1f489bc88637f8cc4dfc2d8d2ca30b Mon Sep 17 00:00:00 2001 From: Benjamin Hamon Date: Wed, 1 Oct 2025 19:02:20 +0200 Subject: [PATCH 3/3] Use standard extensions --- .../archives/__init__.py | 0 .../archives/archive_operations.py | 38 -- .../archives/archive_operations_base.py | 91 ----- .../archives/tar_archive_operations.py | 64 ---- .../archives/zip_archive_operations.py | 65 ---- .../asyncio_extensions/__init__.py | 0 .../asyncio_extensions/asyncio_context.py | 69 ---- .../asyncio_extensions/asyncio_helpers.py | 8 - .../automation/automation_helpers.py | 3 +- .../logging/__init__.py | 0 .../logging/logging_helpers.py | 47 --- .../logging/raw_logger.py | 40 --- .../processes/__init__.py | 0 .../processes/exceptions/__init__.py | 0 .../processes/exceptions/process_exception.py | 10 - .../exceptions/process_failure_exception.py | 5 - .../exceptions/process_start_exception.py | 5 - .../exceptions/process_timeout_exception.py | 5 - .../processes/executable_command.py | 39 --- .../processes/process.py | 50 --- .../processes/process_helpers.py | 134 -------- .../processes/process_options.py | 16 - .../processes/process_output_collector.py | 33 -- .../processes/process_output_handler.py | 24 -- .../processes/process_output_logger.py | 30 -- .../processes/process_result.py | 34 -- .../processes/process_runner.py | 69 ---- .../processes/process_spawner.py | 54 --- .../processes/process_status.py | 21 -- .../processes/process_watcher.py | 254 -------------- .../processes/process_wrapper.py | 51 --- .../python/pyinstaller_runner.py | 11 +- .../python/pylint_output_handler.py | 3 +- .../python/pylint_runner.py | 15 +- .../python/pytest_output_handler.py | 3 +- .../python/pytest_runner.py | 15 +- .../python/python_environment.py | 4 +- .../python/python_package_builder.py | 15 +- .../python_twine_distribution_manager.py | 4 +- .../archives/__init__.py | 0 .../archives/test_archive_operations.py | 212 ------------ .../processes/__init__.py | 0 .../processes/fake_process.py | 81 ----- .../processes/test_process_helpers.py | 29 -- .../test_process_helpers_with_integration.py | 138 -------- .../processes/test_process_watcher.py | 325 ------------------ .../test_process_watcher_with_integration.py | 250 -------------- .../python/test_pyinstaller_runner.py | 9 +- ...est_pyinstaller_runner_with_integration.py | 5 +- .../python/test_pylint_runner.py | 9 +- .../test_pylint_runner_with_integration.py | 5 +- .../python/test_pytest_runner.py | 9 +- .../test_pytest_runner_with_integration.py | 5 +- ...est_python_environment_with_integration.py | 3 +- .../python/test_python_package_builder.py | 9 +- ...python_package_builder_with_integration.py | 7 +- 56 files changed, 75 insertions(+), 2350 deletions(-) delete mode 100644 Sources/toolkit/bhamon_development_toolkit/archives/__init__.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/archives/archive_operations.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/archives/archive_operations_base.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/archives/tar_archive_operations.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/archives/zip_archive_operations.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/asyncio_extensions/__init__.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/asyncio_extensions/asyncio_context.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/asyncio_extensions/asyncio_helpers.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/logging/__init__.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/logging/logging_helpers.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/logging/raw_logger.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/processes/__init__.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/processes/exceptions/__init__.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/processes/exceptions/process_exception.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/processes/exceptions/process_failure_exception.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/processes/exceptions/process_start_exception.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/processes/exceptions/process_timeout_exception.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/processes/executable_command.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/processes/process.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/processes/process_helpers.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/processes/process_options.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/processes/process_output_collector.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/processes/process_output_handler.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/processes/process_output_logger.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/processes/process_result.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/processes/process_runner.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/processes/process_spawner.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/processes/process_status.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/processes/process_watcher.py delete mode 100644 Sources/toolkit/bhamon_development_toolkit/processes/process_wrapper.py delete mode 100644 Tests/toolkit/bhamon_development_toolkit_tests/archives/__init__.py delete mode 100644 Tests/toolkit/bhamon_development_toolkit_tests/archives/test_archive_operations.py delete mode 100644 Tests/toolkit/bhamon_development_toolkit_tests/processes/__init__.py delete mode 100644 Tests/toolkit/bhamon_development_toolkit_tests/processes/fake_process.py delete mode 100644 Tests/toolkit/bhamon_development_toolkit_tests/processes/test_process_helpers.py delete mode 100644 Tests/toolkit/bhamon_development_toolkit_tests/processes/test_process_helpers_with_integration.py delete mode 100644 Tests/toolkit/bhamon_development_toolkit_tests/processes/test_process_watcher.py delete mode 100644 Tests/toolkit/bhamon_development_toolkit_tests/processes/test_process_watcher_with_integration.py diff --git a/Sources/toolkit/bhamon_development_toolkit/archives/__init__.py b/Sources/toolkit/bhamon_development_toolkit/archives/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/Sources/toolkit/bhamon_development_toolkit/archives/archive_operations.py b/Sources/toolkit/bhamon_development_toolkit/archives/archive_operations.py deleted file mode 100644 index 3ddcb75..0000000 --- a/Sources/toolkit/bhamon_development_toolkit/archives/archive_operations.py +++ /dev/null @@ -1,38 +0,0 @@ -import abc -import logging -from typing import List, Optional, Tuple - - -logger = logging.getLogger("ArchiveOperations") - - -class ArchiveOperations(abc.ABC): - - - @abc.abstractmethod - def get_file_extension(self) -> str: - """ Get the archive file extension """ - - - @abc.abstractmethod - def create(self, archive_path: str, mapping_collection: List[Tuple[str,str]], *, simulate: bool = False) -> None: - """ Create an archive with mappings of path sources and destinations """ - - - @abc.abstractmethod - def list_files(self, archive_path: str)-> List[str]: - """ List the files from an archive """ - - - @abc.abstractmethod - def verify(self, archive_path: str) -> None: - """ Verify the integrity of an archive """ - - - @abc.abstractmethod - def extract(self, # pylint: disable = too-many-arguments - archive_path: str, output_directory: str, *, - extraction_directory: Optional[str] = None, - file_collection: Optional[List[str]] = None, - replace: bool = False, simulate: bool = False) -> None: - """ Extract an archive to the provided output directory """ diff --git a/Sources/toolkit/bhamon_development_toolkit/archives/archive_operations_base.py b/Sources/toolkit/bhamon_development_toolkit/archives/archive_operations_base.py deleted file mode 100644 index 4798de4..0000000 --- a/Sources/toolkit/bhamon_development_toolkit/archives/archive_operations_base.py +++ /dev/null @@ -1,91 +0,0 @@ -import abc -import logging -import os -import shutil -from typing import List, Optional, Tuple - -from bhamon_development_toolkit.archives.archive_operations import ArchiveOperations - - -logger = logging.getLogger("ArchiveOperations") - - -class ArchiveOperationsBase(ArchiveOperations): - - - def create(self, archive_path: str, mapping_collection: List[Tuple[str,str]], *, simulate: bool = False) -> None: - """ Create an archive with mappings of path sources and destinations """ - - logger.info("Writing archive '%s'", archive_path) - - if not simulate: - if os.path.dirname(archive_path): - os.makedirs(os.path.dirname(archive_path), exist_ok = True) - - if simulate: - for source, destination in mapping_collection: - logger.debug("+ '%s' => '%s'", source, destination) - - else: - self._create_implementation(archive_path, mapping_collection) - - - @abc.abstractmethod - def _create_implementation(self, archive_path: str, mapping_collection: List[Tuple[str,str]]) -> None: - pass - - - def extract(self, # pylint: disable = too-many-arguments - archive_path: str, output_directory: str, *, - extraction_directory: Optional[str] = None, - file_collection: Optional[List[str]] = None, - replace: bool = False, simulate: bool = False) -> None: - """ Extract an archive to the provided output directory """ - - logger.info("Extracting archive '%s'", archive_path) - - if extraction_directory is None: - extraction_directory = archive_path + ".extracting" - - if file_collection is None: - file_collection = self.list_files(archive_path) - - self._extract_implementation(archive_path, extraction_directory, file_collection, simulate = simulate) - self._apply_extraction_changes(output_directory, extraction_directory, file_collection, replace = replace, simulate = simulate) - - - def _apply_extraction_changes(self, # pylint: disable = too-many-arguments - output_directory: str, extraction_directory: str, file_collection: List[str], *, replace: bool = False, simulate: bool = False) -> None: - - if replace and os.path.isdir(output_directory): - logger.debug("Removing existing files from '%s'", output_directory) - if not simulate: - shutil.rmtree(output_directory) - - logger.debug("Moving files to '%s'", output_directory) - move_whole_directory = not os.path.isdir(output_directory) - - for file_path in file_collection: - source = os.path.normpath(os.path.join(extraction_directory, file_path)) - destination = os.path.normpath(os.path.join(output_directory, file_path)) - logger.debug("+ '%s' => '%s'", source, destination) - - if not simulate and not move_whole_directory: - if os.path.dirname(destination): - os.makedirs(os.path.dirname(destination), exist_ok = True) - if os.path.exists(destination): - os.remove(destination) - shutil.move(source, destination) - - if not simulate and move_whole_directory: - if os.path.dirname(output_directory): - os.makedirs(os.path.dirname(output_directory), exist_ok = True) - shutil.move(extraction_directory, output_directory) - - if not simulate and not move_whole_directory: - shutil.rmtree(extraction_directory) - - - @abc.abstractmethod - def _extract_implementation(self, archive_path: str, extraction_directory: str, file_collection: List[str], *, simulate: bool = False) -> None: - pass diff --git a/Sources/toolkit/bhamon_development_toolkit/archives/tar_archive_operations.py b/Sources/toolkit/bhamon_development_toolkit/archives/tar_archive_operations.py deleted file mode 100644 index 569fcbb..0000000 --- a/Sources/toolkit/bhamon_development_toolkit/archives/tar_archive_operations.py +++ /dev/null @@ -1,64 +0,0 @@ -import logging -import os -import tarfile -from typing import List, Optional, Tuple - -from bhamon_development_toolkit.archives.archive_operations_base import ArchiveOperationsBase - - -logger = logging.getLogger("ArchiveOperations") - - -class TarArchiveOperations(ArchiveOperationsBase): - - - def __init__(self, compression: Optional[str] = None) -> None: - self._compression = compression - - - def get_file_extension(self) -> str: - if self._compression is None: - return ".tar" - if self._compression == "bz2": - return ".tar.bz2" - if self._compression == "gz": - return ".tar.gz" - - raise ValueError("Unsupported compression: %s" % self._compression) - - - def _create_implementation(self, archive_path: str, mapping_collection: List[Tuple[str, str]]) -> None: - mode = "w" if self._compression is None else "w:" + self._compression - - # VSCode shows reportCallIssue here, apparently because it doesn't detects all allowed values for mode - with tarfile.open(archive_path, mode = mode, format = tarfile.GNU_FORMAT) as archive_file: # type: ignore - for source, destination in mapping_collection: - destination = os.path.normpath(destination).replace("\\", "/") - logger.debug("+ '%s' => '%s'", source, destination) - archive_file.add(source, destination) - - - def list_files(self, archive_path: str)-> List[str]: - with tarfile.open(archive_path, mode = "r") as archive_file: - return [ x.path for x in archive_file.getmembers() ] - - - def verify(self, archive_path: str) -> None: - logger.info("Verifying archive '%s'", archive_path) - - try: - with tarfile.open(archive_path, mode = "r"): - pass - except tarfile.TarError as exception: - raise RuntimeError("Archive '%s' is corrupted" % archive_path) from exception - - - def _extract_implementation(self, archive_path: str, extraction_directory: str, file_collection: List[str], *, simulate: bool = False) -> None: - logger.debug("Extracting files to '%s'", extraction_directory) - - with tarfile.open(archive_path, mode = "r") as archive_file: - for source in file_collection: - destination = os.path.normpath(os.path.join(extraction_directory, source)) - logger.debug("+ '%s' => '%s'", source, destination) - if not simulate: - archive_file.extract(source, extraction_directory) diff --git a/Sources/toolkit/bhamon_development_toolkit/archives/zip_archive_operations.py b/Sources/toolkit/bhamon_development_toolkit/archives/zip_archive_operations.py deleted file mode 100644 index b9b3fe0..0000000 --- a/Sources/toolkit/bhamon_development_toolkit/archives/zip_archive_operations.py +++ /dev/null @@ -1,65 +0,0 @@ -# cspell:words compresslevel - -import logging -import os -import shutil -from typing import List, Optional, Tuple -import zipfile - -from bhamon_development_toolkit.archives.archive_operations_base import ArchiveOperationsBase - - -logger = logging.getLogger("ArchiveOperations") - - -class ZipArchiveOperations(ArchiveOperationsBase): - - - def __init__(self, compression: int = zipfile.ZIP_STORED, compression_level: Optional[int] = None) -> None: - self._compression = compression - self._compression_level = compression_level - - - def get_file_extension(self) -> str: - return ".zip" - - - def _create_implementation(self, archive_path: str, mapping_collection: List[Tuple[str, str]]) -> None: - with zipfile.ZipFile(archive_path + ".tmp", mode = "w", compression = self._compression, compresslevel = self._compression_level) as archive_file: - for source, destination in mapping_collection: - destination = os.path.normpath(destination).replace("\\", "/") - logger.debug("+ '%s' => '%s'", source, destination) - archive_file.write(source, destination, compress_type = self._compression, compresslevel = self._compression_level) - os.replace(archive_path + ".tmp", archive_path) - - - def list_files(self, archive_path: str)-> List[str]: - with zipfile.ZipFile(archive_path, mode = "r") as archive_file: - return [ x for x in archive_file.namelist() if not x.replace("\\", "/").endswith('/') ] - - - def verify(self, archive_path: str) -> None: - logger.info("Verifying archive '%s'", archive_path) - - with zipfile.ZipFile(archive_path, mode = "r") as archive_file: - if archive_file.testzip(): - raise RuntimeError("Archive '%s' is corrupted" % archive_path) - - - def _extract_implementation(self, archive_path: str, extraction_directory: str, file_collection: List[str], *, simulate: bool = False) -> None: - logger.debug("Extracting files to '%s'", extraction_directory) - - with zipfile.ZipFile(archive_path, mode = "r") as archive_file: - for source in file_collection: - destination = os.path.normpath(os.path.join(extraction_directory, source)) - patched_destination = os.path.normpath(os.path.join(extraction_directory, source.replace("\\", "/"))) - - logger.debug("+ '%s' => '%s'", source, destination) - if not simulate: - archive_file.extract(source, extraction_directory) - - if "\\" in source and destination != patched_destination: - logger.debug(" the source contains a backslash, the file will be moved: '%s' => '%s'", destination, patched_destination) - if not simulate: - os.makedirs(os.path.dirname(patched_destination), exist_ok = True) - shutil.move(destination, patched_destination) diff --git a/Sources/toolkit/bhamon_development_toolkit/asyncio_extensions/__init__.py b/Sources/toolkit/bhamon_development_toolkit/asyncio_extensions/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/Sources/toolkit/bhamon_development_toolkit/asyncio_extensions/asyncio_context.py b/Sources/toolkit/bhamon_development_toolkit/asyncio_extensions/asyncio_context.py deleted file mode 100644 index bb4b409..0000000 --- a/Sources/toolkit/bhamon_development_toolkit/asyncio_extensions/asyncio_context.py +++ /dev/null @@ -1,69 +0,0 @@ -# cspell:words sigbreak - -import asyncio -import platform -import signal -from typing import Any, List, Tuple - - -class AsyncioContext: - """ Wrapper around asyncio.run to offer graceful termination """ - - - def __init__(self) -> None: - self.should_shutdown = False - self.shutdown_request_counter = 0 - self.shutdown_request_counter_limit = 3 - self.shutdown_timeout_seconds = 30 - - - def run(self, coroutine: Any) -> None: - system = platform.system() - - old_signal_handlers: List[Tuple[signal.Signals,signal._HANDLER]] = [] # pylint: disable = no-member - - if system == "Windows": - old_sigbreak_handler = signal.signal(signal.SIGBREAK, lambda signal_number, frame: self.shutdown()) # pylint: disable = no-member - old_signal_handlers.append((signal.SIGBREAK, old_sigbreak_handler)) # pylint: disable = no-member - - old_sigint_handler = signal.signal(signal.SIGINT, lambda signal_number, frame: self.shutdown()) - old_signal_handlers.append((signal.SIGINT, old_sigint_handler)) - old_sigterm_handler = signal.signal(signal.SIGTERM, lambda signal_number, frame: self.shutdown()) - old_signal_handlers.append((signal.SIGTERM, old_sigterm_handler)) - - try: - asyncio.run(self.run_async(coroutine)) - finally: - for signal_value, signal_handler in old_signal_handlers: - signal.signal(signal_value, signal_handler) - - - async def run_async(self, coroutine: Any) -> None: - future = asyncio.ensure_future(coroutine) - - try: - while not self.should_shutdown and not future.done(): - await asyncio.sleep(1) - - if self.should_shutdown: - raise RuntimeError("Async operation was interrupted") - - finally: - if not future.done(): - future.cancel() - - try: - await asyncio.wait_for(future, timeout = self.shutdown_timeout_seconds) - except asyncio.CancelledError: - pass - - if not future.done(): - raise RuntimeError("Future did not complete") - - - def shutdown(self) -> None: - self.should_shutdown = True - - self.shutdown_request_counter += 1 - if self.shutdown_request_counter > self.shutdown_request_counter_limit: - raise RuntimeError("Forcing shutdown (many requests)") diff --git a/Sources/toolkit/bhamon_development_toolkit/asyncio_extensions/asyncio_helpers.py b/Sources/toolkit/bhamon_development_toolkit/asyncio_extensions/asyncio_helpers.py deleted file mode 100644 index b75be5a..0000000 --- a/Sources/toolkit/bhamon_development_toolkit/asyncio_extensions/asyncio_helpers.py +++ /dev/null @@ -1,8 +0,0 @@ -import asyncio -from typing import Any - - -def from_result(value: Any) -> asyncio.Future: - future = asyncio.Future() - future.set_result(value) - return future diff --git a/Sources/toolkit/bhamon_development_toolkit/automation/automation_helpers.py b/Sources/toolkit/bhamon_development_toolkit/automation/automation_helpers.py index 122691d..ac3f81b 100644 --- a/Sources/toolkit/bhamon_development_toolkit/automation/automation_helpers.py +++ b/Sources/toolkit/bhamon_development_toolkit/automation/automation_helpers.py @@ -9,8 +9,9 @@ import sys from typing import Generator, Optional +from benjaminhamon_standard_extensions.logging import logging_helpers + from bhamon_development_toolkit.automation.automation_command import AutomationCommand -from bhamon_development_toolkit.logging import logging_helpers @contextlib.contextmanager diff --git a/Sources/toolkit/bhamon_development_toolkit/logging/__init__.py b/Sources/toolkit/bhamon_development_toolkit/logging/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/Sources/toolkit/bhamon_development_toolkit/logging/logging_helpers.py b/Sources/toolkit/bhamon_development_toolkit/logging/logging_helpers.py deleted file mode 100644 index f7f1c12..0000000 --- a/Sources/toolkit/bhamon_development_toolkit/logging/logging_helpers.py +++ /dev/null @@ -1,47 +0,0 @@ -import logging -import os -from typing import TextIO - - -all_log_levels = [ "debug", "info", "warning", "error", "critical" ] -date_format_iso = "%Y-%m-%dT%H:%M:%S" - - -def get_level_as_integer(level_as_string: str) -> int: - if level_as_string.lower() == "debug": - return logging.DEBUG - if level_as_string.lower() == "info": - return logging.INFO - if level_as_string.lower() == "warning": - return logging.WARNING - if level_as_string.lower() == "error": - return logging.ERROR - if level_as_string.lower() == "critical": - return logging.CRITICAL - - raise ValueError("Unknown logging level '%s'" % level_as_string) - - -def configure_log_stream(logger: logging.Logger, stream: TextIO, level: str, message_format: str, date_format: str) -> None: - formatter = logging.Formatter(fmt = message_format, datefmt = date_format, style = "{") - - stream_handler = logging.StreamHandler(stream) - stream_handler.setLevel(get_level_as_integer(level)) - stream_handler.formatter = formatter - - logger.addHandler(stream_handler) - - -def configure_log_file( # pylint: disable = too-many-arguments, too-many-positional-arguments - logger: logging.Logger, file_path: str, level: str, message_format: str, date_format: str, mode: str, encoding: str) -> None: - - if os.path.dirname(file_path): - os.makedirs(os.path.dirname(file_path), exist_ok = True) - - formatter = logging.Formatter(fmt = message_format, datefmt = date_format, style = "{") - - file_handler = logging.FileHandler(file_path, mode = mode, encoding = encoding) - file_handler.setLevel(get_level_as_integer(level)) - file_handler.formatter = formatter - - logger.addHandler(file_handler) diff --git a/Sources/toolkit/bhamon_development_toolkit/logging/raw_logger.py b/Sources/toolkit/bhamon_development_toolkit/logging/raw_logger.py deleted file mode 100644 index a44b166..0000000 --- a/Sources/toolkit/bhamon_development_toolkit/logging/raw_logger.py +++ /dev/null @@ -1,40 +0,0 @@ -import logging -from typing import Optional, TextIO - -from bhamon_development_toolkit.logging import logging_helpers - - -class RawLogger: - - - def __init__(self, name: Optional[str] = None) -> None: - if name is None: - name = "raw" - - self._logger = logging.Logger(name, logging.DEBUG) - - - def get_actual_logger(self) -> logging.Logger: - return self._logger - - - def __enter__(self) -> "RawLogger": - return self - - - def __exit__(self, exc_type, exc_value, traceback) -> None: - self.dispose() - - - def configure_log_stream(self, stream: TextIO, level: str) -> None: - logging_helpers.configure_log_stream(self._logger, stream, level, "{message}", logging_helpers.date_format_iso) - - - def configure_log_file(self, file_path: str, level: str, mode: str, encoding: str) -> None: - logging_helpers.configure_log_file(self._logger, file_path, level, "{message}", logging_helpers.date_format_iso, mode, encoding) - - - def dispose(self) -> None: - for handler in list(self._logger.handlers): - self._logger.removeHandler(handler) - handler.close() diff --git a/Sources/toolkit/bhamon_development_toolkit/processes/__init__.py b/Sources/toolkit/bhamon_development_toolkit/processes/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/Sources/toolkit/bhamon_development_toolkit/processes/exceptions/__init__.py b/Sources/toolkit/bhamon_development_toolkit/processes/exceptions/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/Sources/toolkit/bhamon_development_toolkit/processes/exceptions/process_exception.py b/Sources/toolkit/bhamon_development_toolkit/processes/exceptions/process_exception.py deleted file mode 100644 index cc5df2d..0000000 --- a/Sources/toolkit/bhamon_development_toolkit/processes/exceptions/process_exception.py +++ /dev/null @@ -1,10 +0,0 @@ -from typing import Optional - - -class ProcessException(Exception): - - - def __init__(self, message: str, executable: str, exit_code: Optional[int]) -> None: - super().__init__(message) - self.executable = executable - self.exit_code = exit_code diff --git a/Sources/toolkit/bhamon_development_toolkit/processes/exceptions/process_failure_exception.py b/Sources/toolkit/bhamon_development_toolkit/processes/exceptions/process_failure_exception.py deleted file mode 100644 index d04e206..0000000 --- a/Sources/toolkit/bhamon_development_toolkit/processes/exceptions/process_failure_exception.py +++ /dev/null @@ -1,5 +0,0 @@ -from bhamon_development_toolkit.processes.exceptions.process_exception import ProcessException - - -class ProcessFailureException(ProcessException): - pass diff --git a/Sources/toolkit/bhamon_development_toolkit/processes/exceptions/process_start_exception.py b/Sources/toolkit/bhamon_development_toolkit/processes/exceptions/process_start_exception.py deleted file mode 100644 index 610cf40..0000000 --- a/Sources/toolkit/bhamon_development_toolkit/processes/exceptions/process_start_exception.py +++ /dev/null @@ -1,5 +0,0 @@ -from bhamon_development_toolkit.processes.exceptions.process_exception import ProcessException - - -class ProcessStartException(ProcessException): - pass diff --git a/Sources/toolkit/bhamon_development_toolkit/processes/exceptions/process_timeout_exception.py b/Sources/toolkit/bhamon_development_toolkit/processes/exceptions/process_timeout_exception.py deleted file mode 100644 index 0c54c08..0000000 --- a/Sources/toolkit/bhamon_development_toolkit/processes/exceptions/process_timeout_exception.py +++ /dev/null @@ -1,5 +0,0 @@ -from bhamon_development_toolkit.processes.exceptions.process_exception import ProcessException - - -class ProcessTimeoutException(ProcessException): - pass diff --git a/Sources/toolkit/bhamon_development_toolkit/processes/executable_command.py b/Sources/toolkit/bhamon_development_toolkit/processes/executable_command.py deleted file mode 100644 index 5305397..0000000 --- a/Sources/toolkit/bhamon_development_toolkit/processes/executable_command.py +++ /dev/null @@ -1,39 +0,0 @@ -import os -from typing import List - - -class ExecutableCommand: - - - def __init__(self, executable: str) -> None: - self._executable = executable - self._arguments: List[str] = [] - self._arguments_for_logging: List[str] = [] - - - def add_arguments(self, arguments: List[str]) -> None: - self._arguments += arguments - self._arguments_for_logging += arguments - - - def add_internal_arguments(self, arguments: List[str], arguments_for_logging: List[str]) -> None: - self._arguments += arguments - self._arguments_for_logging += arguments_for_logging - - - @property - def executable_name(self) -> str: - return os.path.basename(self._executable) - - - @property - def executable_path(self) -> str: - return self._executable - - - def get_command(self) -> List[str]: - return [ self._executable ] + self._arguments - - - def get_command_for_logging(self) -> List[str]: - return [ self._executable ] + self._arguments_for_logging diff --git a/Sources/toolkit/bhamon_development_toolkit/processes/process.py b/Sources/toolkit/bhamon_development_toolkit/processes/process.py deleted file mode 100644 index d61ffbd..0000000 --- a/Sources/toolkit/bhamon_development_toolkit/processes/process.py +++ /dev/null @@ -1,50 +0,0 @@ -import abc -import asyncio -from typing import Optional - - -class Process(abc.ABC): - - - @property - @abc.abstractmethod - def pid(self) -> int: - pass - - - @property - @abc.abstractmethod - def stdout(self) -> Optional[asyncio.StreamReader]: - pass - - - @property - @abc.abstractmethod - def stderr(self) -> Optional[asyncio.StreamReader]: - pass - - - @property - @abc.abstractmethod - def exit_code(self) -> Optional[int]: - """ Return the exit code from the process. If the process is running, returns None. """ - - @property - @abc.abstractmethod - def is_running(self) -> bool: - """ Return True if the process is running, otherwise False. """ - - - @abc.abstractmethod - async def wait(self) -> int: - """ Wait for the process to complete, and return the exit code. """ - - - @abc.abstractmethod - def terminate(self) -> None: - """ Signal the process to terminate. """ - - - @abc.abstractmethod - def kill(self) -> None: - """ Signal the system to force the process to terminate. """ diff --git a/Sources/toolkit/bhamon_development_toolkit/processes/process_helpers.py b/Sources/toolkit/bhamon_development_toolkit/processes/process_helpers.py deleted file mode 100644 index 1285cb6..0000000 --- a/Sources/toolkit/bhamon_development_toolkit/processes/process_helpers.py +++ /dev/null @@ -1,134 +0,0 @@ -import asyncio -import logging -import shlex -import subprocess -from typing import Any, List, Optional, TextIO - -from bhamon_development_toolkit.logging.raw_logger import RawLogger -from bhamon_development_toolkit.processes.exceptions.process_failure_exception import ProcessFailureException -from bhamon_development_toolkit.processes.executable_command import ExecutableCommand -from bhamon_development_toolkit.processes.process_result import ProcessResult - - -def format_executable_command(command: List[str]): - return " ".join(format_executable_command_element(element) for element in command) - - -def format_executable_command_element(element: str) -> str: - return shlex.quote(element) - - -def create_raw_logger(stream: Optional[TextIO] = None, log_file_path: Optional[str] = None) -> RawLogger: - raw_logger = RawLogger() - - if stream is not None: - raw_logger.configure_log_stream(stream, "info") - - if log_file_path is not None: - raw_logger.configure_log_file(log_file_path, "debug", mode = "w", encoding = "utf-8") - - return raw_logger - - -def run_simple(logger: logging.Logger, command: ExecutableCommand, *, - working_directory: Optional[str] = None, check_exit_code: bool = True, simulate: bool = False) -> ProcessResult: - - logger.debug("+ %s", format_executable_command(command.get_command_for_logging())) - - subprocess_options = { - "cwd": working_directory, - "capture_output": True, - "text": True, - "encoding": "utf-8", - "stdin": subprocess.DEVNULL, - } - - if not simulate: - result = subprocess.run(command.get_command(), check = False, **subprocess_options) - for line in result.stdout.splitlines(): - logger.debug(line) - for line in result.stderr.splitlines(): - logger.error(line) - - if check_exit_code and result.returncode != 0: - exception_message = "Subprocess failed (Executable: '%s', ExitCode: %s)" % (command.executable_path, result.returncode) - raise ProcessFailureException(exception_message, command.executable_path, result.returncode) - - return ProcessResult( - executable = command.executable_path, - exit_code = result.returncode, - standard_output = result.stdout, - error_output = result.stderr, - ) - - return ProcessResult( - executable = command.executable_path, - exit_code = 0, - ) - - -async def run_simple_async(logger: logging.Logger, command: ExecutableCommand, *, - working_directory: Optional[str] = None, check_exit_code: bool = True, simulate: bool = False) -> ProcessResult: - - async def watch_output(stream: asyncio.StreamReader, logging_level: int) -> str: - encoding = "utf-8" - - output = "" - - while True: - line_as_bytes = await stream.readline() - if not line_as_bytes: - break - - line = line_as_bytes.decode(encoding).replace("\r\n", "\n") - logger.log(logging_level, line.rstrip()) - - output += line - - return output - - async def check_task(identifier: str, task: asyncio.Task) -> Any: - try: - return await asyncio.wait_for(task, 1) - except asyncio.CancelledError: - pass - except asyncio.TimeoutError: - logger.warning("Task '%s' timed out", identifier) - except Exception: # pylint: disable = broad-except - logger.error("Task '%s' raised an unhandled exception", identifier, exc_info = True) - return None - - logger.debug("+ %s", format_executable_command(command.get_command_for_logging())) - - if not simulate: - process = await asyncio.create_subprocess_exec(*command.get_command(), - stdin = subprocess.DEVNULL, stdout = subprocess.PIPE, stderr = subprocess.PIPE, cwd = working_directory) - - if process.stdout is None: - raise RuntimeError("Process stdout should not be none") - if process.stderr is None: - raise RuntimeError("Process stderr should not be none") - - stdout_task = asyncio.create_task(watch_output(process.stdout, logging.DEBUG)) - stderr_task = asyncio.create_task(watch_output(process.stderr, logging.ERROR)) - - exit_code = await process.wait() - - if check_exit_code and exit_code != 0: - exception_message = "Subprocess failed (Executable: '%s', ExitCode: %s)" % (command.executable_path, exit_code) - raise ProcessFailureException(exception_message, command.executable_path, exit_code) - - stdout_text = await check_task("stdout", stdout_task) - stderr_text = await check_task("stderr", stderr_task) - - return ProcessResult( - executable = command.executable_path, - exit_code = exit_code, - standard_output = stdout_text, - error_output = stderr_text, - ) - - return ProcessResult( - executable = command.executable_path, - exit_code = 0, - ) diff --git a/Sources/toolkit/bhamon_development_toolkit/processes/process_options.py b/Sources/toolkit/bhamon_development_toolkit/processes/process_options.py deleted file mode 100644 index 5820444..0000000 --- a/Sources/toolkit/bhamon_development_toolkit/processes/process_options.py +++ /dev/null @@ -1,16 +0,0 @@ - -import dataclasses -import datetime -from typing import Dict, Optional - - -@dataclasses.dataclass(frozen = True) -class ProcessOptions: - working_directory: Optional[str] = None - environment: Optional[Dict[str,str]] = None - encoding: str = "utf-8" - - run_timeout: Optional[datetime.timedelta] = None - output_timeout: Optional[datetime.timedelta] = None - termination_timeout: datetime.timedelta = datetime.timedelta(seconds = 10) - wait_update_interval: datetime.timedelta = datetime.timedelta(seconds = 1) diff --git a/Sources/toolkit/bhamon_development_toolkit/processes/process_output_collector.py b/Sources/toolkit/bhamon_development_toolkit/processes/process_output_collector.py deleted file mode 100644 index c53372e..0000000 --- a/Sources/toolkit/bhamon_development_toolkit/processes/process_output_collector.py +++ /dev/null @@ -1,33 +0,0 @@ -from bhamon_development_toolkit.processes.process_output_handler import ProcessOutputHandler - - -class ProcessOutputCollector(ProcessOutputHandler): - - - def __init__(self) -> None: - self._stdout: str = "" - self._stderr: str = "" - - - def get_stdout(self) -> str: - return self._stdout - - - def get_stderr(self) -> str: - return self._stderr - - - def process_stdout_line(self, line: str) -> None: - self._stdout += line - - - def process_stderr_line(self, line: str) -> None: - self._stderr += line - - - def process_stdout_end(self) -> None: - pass - - - def process_stderr_end(self) -> None: - pass diff --git a/Sources/toolkit/bhamon_development_toolkit/processes/process_output_handler.py b/Sources/toolkit/bhamon_development_toolkit/processes/process_output_handler.py deleted file mode 100644 index a610e96..0000000 --- a/Sources/toolkit/bhamon_development_toolkit/processes/process_output_handler.py +++ /dev/null @@ -1,24 +0,0 @@ -import abc - - -class ProcessOutputHandler(abc.ABC): - - - @abc.abstractmethod - def process_stdout_line(self, line: str) -> None: - pass - - - @abc.abstractmethod - def process_stderr_line(self, line: str) -> None: - pass - - - @abc.abstractmethod - def process_stdout_end(self) -> None: - pass - - - @abc.abstractmethod - def process_stderr_end(self) -> None: - pass diff --git a/Sources/toolkit/bhamon_development_toolkit/processes/process_output_logger.py b/Sources/toolkit/bhamon_development_toolkit/processes/process_output_logger.py deleted file mode 100644 index 38df474..0000000 --- a/Sources/toolkit/bhamon_development_toolkit/processes/process_output_logger.py +++ /dev/null @@ -1,30 +0,0 @@ -import logging - -from bhamon_development_toolkit.processes.process_output_handler import ProcessOutputHandler - - -class ProcessOutputLogger(ProcessOutputHandler): - - - def __init__(self, logger: logging.Logger, - stdout_logging_level: int = logging.INFO, stderr_logging_level: int = logging.ERROR) -> None: - - self._logger = logger - self._stdout_logging_level = stdout_logging_level - self._stderr_logging_level = stderr_logging_level - - - def process_stdout_line(self, line: str) -> None: - self._logger.log(self._stdout_logging_level, line.rstrip()) - - - def process_stderr_line(self, line: str) -> None: - self._logger.log(self._stderr_logging_level, line.rstrip()) - - - def process_stdout_end(self) -> None: - pass - - - def process_stderr_end(self) -> None: - pass diff --git a/Sources/toolkit/bhamon_development_toolkit/processes/process_result.py b/Sources/toolkit/bhamon_development_toolkit/processes/process_result.py deleted file mode 100644 index f290acf..0000000 --- a/Sources/toolkit/bhamon_development_toolkit/processes/process_result.py +++ /dev/null @@ -1,34 +0,0 @@ -import dataclasses -import os -import subprocess -from typing import Optional - - -@dataclasses.dataclass(frozen = True) -class ProcessResult: - - - @staticmethod - def create_from_completed_process(completed_process: subprocess.CompletedProcess) -> "ProcessResult": - return ProcessResult( - executable = completed_process.args[0], - exit_code = completed_process.returncode, - standard_output = completed_process.stdout, - error_output = completed_process.stderr, - ) - - - executable: str - exit_code: int - standard_output: Optional[str] = None - error_output: Optional[str] = None - - - @property - def executable_name(self) -> str: - return os.path.basename(self.executable) - - - @property - def executable_path(self) -> str: - return self.executable diff --git a/Sources/toolkit/bhamon_development_toolkit/processes/process_runner.py b/Sources/toolkit/bhamon_development_toolkit/processes/process_runner.py deleted file mode 100644 index 8ef4cd8..0000000 --- a/Sources/toolkit/bhamon_development_toolkit/processes/process_runner.py +++ /dev/null @@ -1,69 +0,0 @@ -from typing import List, Optional - -from bhamon_development_toolkit.processes.executable_command import ExecutableCommand -from bhamon_development_toolkit.processes.process_options import ProcessOptions -from bhamon_development_toolkit.processes.process_output_collector import ProcessOutputCollector -from bhamon_development_toolkit.processes.process_output_handler import ProcessOutputHandler -from bhamon_development_toolkit.processes.process_result import ProcessResult -from bhamon_development_toolkit.processes.process_spawner import ProcessSpawner - - -class ProcessRunner: - - - def __init__(self, spawner: ProcessSpawner) -> None: - self._spawner = spawner - - - async def run(self, - command: ExecutableCommand, - options: ProcessOptions, - *, - output_handlers: Optional[List[ProcessOutputHandler]] = None, - check_exit_code: bool = True - ) -> ProcessResult: - - watcher = await self._spawner.spawn_process(command = command, options = options) - - if output_handlers is not None: - for handler in output_handlers: - watcher.add_output_handler(handler) - - try: - await watcher.start() - await watcher.wait() - await watcher.complete(check_exit_code) - - except BaseException as exception: - if watcher.get_status().is_running: - await watcher.terminate(type(exception).__name__) - - raise - - status = watcher.get_status() - if status.exit_code is None: - raise ValueError("Process exit code is not set") - - return ProcessResult( - executable = status.executable, - exit_code = status.exit_code, - ) - - - async def run_with_collector(self, - command: ExecutableCommand, - options: ProcessOptions, - *, - check_exit_code: bool = True, - ) -> ProcessResult: - - output_collector = ProcessOutputCollector() - - result = await self.run(command, options, output_handlers = [ output_collector ], check_exit_code = check_exit_code) - - return ProcessResult( - executable = result.executable, - exit_code = result.exit_code, - standard_output = output_collector.get_stdout(), - error_output = output_collector.get_stderr(), - ) diff --git a/Sources/toolkit/bhamon_development_toolkit/processes/process_spawner.py b/Sources/toolkit/bhamon_development_toolkit/processes/process_spawner.py deleted file mode 100644 index 79eadf3..0000000 --- a/Sources/toolkit/bhamon_development_toolkit/processes/process_spawner.py +++ /dev/null @@ -1,54 +0,0 @@ -# cspell:words creationflags pythonioencoding - -import asyncio -import logging -import os -import platform -import signal -import subprocess - -from bhamon_development_toolkit.processes.executable_command import ExecutableCommand -from bhamon_development_toolkit.processes.exceptions.process_start_exception import ProcessStartException -from bhamon_development_toolkit.processes.process_options import ProcessOptions -from bhamon_development_toolkit.processes.process_watcher import ProcessWatcher -from bhamon_development_toolkit.processes.process_wrapper import ProcessWrapper - - -logger = logging.getLogger("ProcessSpawner") - - -class ProcessSpawner: - - - def __init__(self, *, is_console: bool = False) -> None: - self.termination_signal: signal.Signals = signal.SIGTERM - self.subprocess_flags: int = 0 - - if platform.system() == "Windows": - if is_console: - self.termination_signal = signal.CTRL_BREAK_EVENT # pylint: disable = no-member - self.subprocess_flags = subprocess.CREATE_NEW_PROCESS_GROUP # pylint: disable = no-member - - - async def spawn_process(self, command: ExecutableCommand, options: ProcessOptions) -> ProcessWatcher: - process_environment = os.environ.copy() - process_environment["PYTHONIOENCODING"] = options.encoding # Force encoding instead of the default stdout encoding - - if options.environment is not None: - process_environment.update(options.environment) - - try: - process = await asyncio.create_subprocess_exec(*command.get_command(), - stdin = subprocess.DEVNULL, stdout = subprocess.PIPE, stderr = subprocess.PIPE, - cwd = options.working_directory, env = process_environment, creationflags = self.subprocess_flags) - - except FileNotFoundError as exception: - exception_message = "Executable not found: '%s'" % (command.executable_name) - raise ProcessStartException(exception_message, command.executable_path, None) from exception - - logger.debug("Subprocess spawned (Executable: '%s', PID: %s)", command.executable_name, process.pid) - - process_wrapper = ProcessWrapper(process, self.termination_signal) - process_watcher = ProcessWatcher(process_wrapper, command, options) - - return process_watcher diff --git a/Sources/toolkit/bhamon_development_toolkit/processes/process_status.py b/Sources/toolkit/bhamon_development_toolkit/processes/process_status.py deleted file mode 100644 index 017ece8..0000000 --- a/Sources/toolkit/bhamon_development_toolkit/processes/process_status.py +++ /dev/null @@ -1,21 +0,0 @@ -import dataclasses -import os -from typing import Optional - - -@dataclasses.dataclass(frozen = True) -class ProcessStatus: - executable: str - pid: int - is_running: bool - exit_code: Optional[int] - - - @property - def executable_name(self) -> str: - return os.path.basename(self.executable) - - - @property - def executable_path(self) -> str: - return self.executable diff --git a/Sources/toolkit/bhamon_development_toolkit/processes/process_watcher.py b/Sources/toolkit/bhamon_development_toolkit/processes/process_watcher.py deleted file mode 100644 index 4062973..0000000 --- a/Sources/toolkit/bhamon_development_toolkit/processes/process_watcher.py +++ /dev/null @@ -1,254 +0,0 @@ -import asyncio -import datetime -import logging -import time -from typing import List, Optional - -from bhamon_development_toolkit.processes.exceptions.process_failure_exception import ProcessFailureException -from bhamon_development_toolkit.processes.exceptions.process_timeout_exception import ProcessTimeoutException -from bhamon_development_toolkit.processes.executable_command import ExecutableCommand -from bhamon_development_toolkit.processes.process import Process -from bhamon_development_toolkit.processes.process_options import ProcessOptions -from bhamon_development_toolkit.processes.process_output_handler import ProcessOutputHandler -from bhamon_development_toolkit.processes.process_status import ProcessStatus - - -logger = logging.getLogger("ProcessWatcher") - - -class ProcessWatcher: # pylint: disable = too-many-instance-attributes - - - def __init__(self, process: Process, command: ExecutableCommand, options: ProcessOptions) -> None: - self._process = process - self._command = command - self._options = options - - self._timeout_task: Optional[asyncio.Task] = None - self._start_time: Optional[float] = None - self._completion_time: Optional[float] = None - self._last_output_time: Optional[float] = None - self._custom_exit_code: Optional[int] = None - - self._stdout_task: Optional[asyncio.Task] = None - self._stderr_task: Optional[asyncio.Task] = None - self._output_handlers: List[ProcessOutputHandler] = [] - - self._termination_lock = asyncio.Lock() - - - @property - def executable(self) -> str: - return self._command.executable_name - - - @property - def executable_path(self) -> str: - return self._command.executable_path - - - @property - def pid(self) -> int: - return self._process.pid - - - def get_status(self) -> ProcessStatus: - return ProcessStatus( - executable = self._command.executable_path, - pid = self._process.pid, - is_running = self._process.is_running, - exit_code = self._resolve_exit_code(), - ) - - - def _resolve_exit_code(self) -> Optional[int]: - if self._custom_exit_code is not None: - return self._custom_exit_code - if self._process is not None: - return self._process.exit_code - return None - - - async def start(self) -> None: - logger.debug("Subprocess started (Executable: '%s', PID: %s)", self.executable, self.pid) - - self._start_time = time.time() - self._last_output_time = time.time() - - self._timeout_task = asyncio.create_task(self._watch_timeout()) - if self._process.stdout is not None: - self._stdout_task = asyncio.create_task(self._watch_stdout(self._process.stdout)) - if self._process.stderr is not None: - self._stderr_task = asyncio.create_task(self._watch_stderr(self._process.stderr)) - - - async def wait(self) -> None: - await self._process.wait() - - if self._timeout_task is not None: - self._timeout_task.cancel() - - await self._wait_tasks() - - - async def complete(self, check_exit_code: bool = True) -> None: - if self._process.is_running: - raise RuntimeError("Subprocess is still active") - - exit_code = self._resolve_exit_code() - logger.debug("Subprocess exited (Executable: '%s', PID: %s, ExitCode: %s)", self.executable, self.pid, exit_code) - - self._completion_time = time.time() - - if exit_code != 0: - try: - self._check_timeouts() - except TimeoutError as exception: - raise ProcessTimeoutException(str(exception), self.executable, exit_code) from exception - - if check_exit_code: - exception_message = "Subprocess failed (Executable: '%s', ExitCode: %s)" % (self.executable, exit_code) - raise ProcessFailureException(exception_message, self.executable, exit_code) - - - async def terminate(self, reason: str, exit_code: Optional[int] = None) -> None: - async with self._termination_lock: - await self._terminate_unsafe(reason, exit_code) - - - async def _terminate_unsafe(self, reason: str, exit_code: Optional[int] = None) -> None: - if not self._process.is_running: - logger.debug("Requested subprocess termination but has already exited (Executable: '%s', PID: %s, Reason: '%s')", self.executable, self.pid, reason) - return - - logger.warning("Terminating subprocess (Executable: '%s', PID: %s, Reason: '%s')", self.executable, self.pid, reason) - - if exit_code is not None: - self._custom_exit_code = exit_code - - if self._process.is_running: - logger.warning("Requesting subprocess termination (Executable: '%s', PID: %s)", self.executable, self.pid) - self._process.terminate() - - try: - await asyncio.wait_for(self._process.wait(), self._options.termination_timeout.total_seconds()) - except asyncio.TimeoutError: - pass - - if self._process.is_running: - logger.error("Forcing subprocess termination (Executable: '%s', PID: %s)", self.executable, self.pid) - self._process.kill() - - try: - await asyncio.wait_for(self._process.wait(), self._options.termination_timeout.total_seconds()) - except asyncio.TimeoutError: - pass - - if self._timeout_task is not None: - self._timeout_task.cancel() - - await self._wait_tasks() - - if self._process.is_running: - logger.error("Terminating subprocess failed (Executable: '%s', PID: %s)", self.executable, self.pid) - - if not self._process.is_running: - logger.warning("Terminating subprocess succeeded (Executable: '%s', PID: %s)", self.executable, self.pid) - - - def add_output_handler(self, handler: ProcessOutputHandler) -> None: - self._output_handlers.append(handler) - - - def remove_output_handler(self, handler: ProcessOutputHandler) -> None: - self._output_handlers.remove(handler) - - - async def _watch_stdout(self, stream: asyncio.StreamReader) -> None: - while True: - line_as_bytes = await stream.readline() - if not line_as_bytes: - for handler in self._output_handlers: - handler.process_stdout_end() - break - - self._last_output_time = time.time() - line = line_as_bytes.decode(self._options.encoding).replace("\r\n", "\n") - - for handler in self._output_handlers: - handler.process_stdout_line(line) - - - async def _watch_stderr(self, stream: asyncio.StreamReader) -> None: - while True: - line_as_bytes = await stream.readline() - if not line_as_bytes: - for handler in self._output_handlers: - handler.process_stderr_end() - break - - self._last_output_time = time.time() - line = line_as_bytes.decode(self._options.encoding).replace("\r\n", "\n") - - for handler in self._output_handlers: - handler.process_stderr_line(line) - - - async def _watch_timeout(self) -> None: - while self._process.is_running: - try: - self._check_timeouts() - except TimeoutError: - asyncio.create_task(self.terminate("TimeoutError")) - break - - await asyncio.sleep(self._options.wait_update_interval.total_seconds()) - - - def _check_timeouts(self) -> None: - - def check(start: float, timeout: datetime.timedelta, reason: str) -> None: - elapsed = datetime.timedelta(seconds = time.time() - start) - - if elapsed > timeout: - exception_message = "Subprocess timed out with reason %s" % reason - exception_message += " (Executable: '%s', PID: %s, Timeout: %s > %s)" % (self.executable, self.pid, elapsed, timeout) - raise TimeoutError(exception_message) - - if self._options.run_timeout is not None and self._start_time is not None: - check(self._start_time, self._options.run_timeout, "total runtime") - if self._options.output_timeout is not None and self._last_output_time is not None: - check(self._last_output_time, self._options.output_timeout, "no output") - - - async def _wait_tasks(self) -> None: - - async def _check_task(identifier: str, task: asyncio.Task) -> None: - try: - await asyncio.wait_for(task, 1) - except asyncio.CancelledError: - pass - except asyncio.TimeoutError: - logger.warning("Task '%s' timed out (Executable: '%s', PID: %s)", identifier, self.executable, self.pid) - except Exception: # pylint: disable = broad-except - logger.error("Task '%s' raised an unhandled exception (Executable: '%s', PID: %s)", identifier, self.executable, self.pid, exc_info = True) - - tasks_to_wait = [] - if self._timeout_task is not None: - tasks_to_wait.append(self._timeout_task) - if self._stdout_task is not None: - tasks_to_wait.append(self._stdout_task) - if self._stderr_task is not None: - tasks_to_wait.append(self._stderr_task) - - if len(tasks_to_wait) == 0: - return - - await asyncio.wait(tasks_to_wait, timeout = 10, return_when = asyncio.ALL_COMPLETED) - - if self._timeout_task is not None: - await _check_task("timeout", self._timeout_task) - if self._stdout_task is not None: - await _check_task("stdout", self._stdout_task) - if self._stderr_task is not None: - await _check_task("stderr", self._stderr_task) diff --git a/Sources/toolkit/bhamon_development_toolkit/processes/process_wrapper.py b/Sources/toolkit/bhamon_development_toolkit/processes/process_wrapper.py deleted file mode 100644 index 73841ca..0000000 --- a/Sources/toolkit/bhamon_development_toolkit/processes/process_wrapper.py +++ /dev/null @@ -1,51 +0,0 @@ -import asyncio -from asyncio.subprocess import Process as ProcessImplementation -import signal -from typing import Optional - -from bhamon_development_toolkit.processes.process import Process - - -class ProcessWrapper(Process): - - - def __init__(self, implementation: ProcessImplementation, termination_signal: signal.Signals) -> None: - self._implementation = implementation - self._termination_signal = termination_signal - - - @property - def pid(self) -> int: - return self._implementation.pid - - - @property - def stdout(self) -> Optional[asyncio.StreamReader]: - return self._implementation.stdout - - - @property - def stderr(self) -> Optional[asyncio.StreamReader]: - return self._implementation.stderr - - - @property - def exit_code(self) -> Optional[int]: - return self._implementation.returncode - - - @property - def is_running(self) -> bool: - return self._implementation.returncode is None - - - async def wait(self) -> int: - return await self._implementation.wait() - - - def terminate(self) -> None: - self._implementation.send_signal(self._termination_signal) - - - def kill(self) -> None: - self._implementation.kill() diff --git a/Sources/toolkit/bhamon_development_toolkit/python/pyinstaller_runner.py b/Sources/toolkit/bhamon_development_toolkit/python/pyinstaller_runner.py index 6bd7a58..daafcaa 100644 --- a/Sources/toolkit/bhamon_development_toolkit/python/pyinstaller_runner.py +++ b/Sources/toolkit/bhamon_development_toolkit/python/pyinstaller_runner.py @@ -6,13 +6,14 @@ import sys from typing import List, Optional +from benjaminhamon_standard_extensions.processes import process_helpers +from benjaminhamon_standard_extensions.processes.executable_command import ExecutableCommand +from benjaminhamon_standard_extensions.processes.process_options import ProcessOptions +from benjaminhamon_standard_extensions.processes.process_output_logger import ProcessOutputLogger +from benjaminhamon_standard_extensions.processes.process_runner import ProcessRunner + from bhamon_development_toolkit.applications import application_version_helpers from bhamon_development_toolkit.applications.application_metadata import ApplicationMetadata -from bhamon_development_toolkit.processes import process_helpers -from bhamon_development_toolkit.processes.executable_command import ExecutableCommand -from bhamon_development_toolkit.processes.process_options import ProcessOptions -from bhamon_development_toolkit.processes.process_output_logger import ProcessOutputLogger -from bhamon_development_toolkit.processes.process_runner import ProcessRunner from bhamon_development_toolkit.python.pyinstaller_configuration import PyInstallerConfiguration diff --git a/Sources/toolkit/bhamon_development_toolkit/python/pylint_output_handler.py b/Sources/toolkit/bhamon_development_toolkit/python/pylint_output_handler.py index a68b31b..69a6674 100644 --- a/Sources/toolkit/bhamon_development_toolkit/python/pylint_output_handler.py +++ b/Sources/toolkit/bhamon_development_toolkit/python/pylint_output_handler.py @@ -2,7 +2,8 @@ import os from typing import Optional -from bhamon_development_toolkit.processes.process_output_handler import ProcessOutputHandler +from benjaminhamon_standard_extensions.processes.process_output_handler import ProcessOutputHandler + from bhamon_development_toolkit.python.pylint_issue import PylintIssue diff --git a/Sources/toolkit/bhamon_development_toolkit/python/pylint_runner.py b/Sources/toolkit/bhamon_development_toolkit/python/pylint_runner.py index 6cd18c5..590c1d4 100644 --- a/Sources/toolkit/bhamon_development_toolkit/python/pylint_runner.py +++ b/Sources/toolkit/bhamon_development_toolkit/python/pylint_runner.py @@ -3,13 +3,14 @@ import shutil from typing import List, Optional -from bhamon_development_toolkit.processes import process_helpers -from bhamon_development_toolkit.processes.executable_command import ExecutableCommand -from bhamon_development_toolkit.processes.process_options import ProcessOptions -from bhamon_development_toolkit.processes.process_output_handler import ProcessOutputHandler -from bhamon_development_toolkit.processes.process_output_logger import ProcessOutputLogger -from bhamon_development_toolkit.processes.process_result import ProcessResult -from bhamon_development_toolkit.processes.process_runner import ProcessRunner +from benjaminhamon_standard_extensions.processes import process_helpers +from benjaminhamon_standard_extensions.processes.executable_command import ExecutableCommand +from benjaminhamon_standard_extensions.processes.process_options import ProcessOptions +from benjaminhamon_standard_extensions.processes.process_output_handler import ProcessOutputHandler +from benjaminhamon_standard_extensions.processes.process_output_logger import ProcessOutputLogger +from benjaminhamon_standard_extensions.processes.process_result import ProcessResult +from benjaminhamon_standard_extensions.processes.process_runner import ProcessRunner + from bhamon_development_toolkit.python.pylint_output_handler import PylintOutputHandler from bhamon_development_toolkit.python.pylint_scope import PylintScope diff --git a/Sources/toolkit/bhamon_development_toolkit/python/pytest_output_handler.py b/Sources/toolkit/bhamon_development_toolkit/python/pytest_output_handler.py index 9fd0481..40ccb99 100644 --- a/Sources/toolkit/bhamon_development_toolkit/python/pytest_output_handler.py +++ b/Sources/toolkit/bhamon_development_toolkit/python/pytest_output_handler.py @@ -5,7 +5,8 @@ import re from typing import Optional -from bhamon_development_toolkit.processes.process_output_handler import ProcessOutputHandler +from benjaminhamon_standard_extensions.processes.process_output_handler import ProcessOutputHandler + from bhamon_development_toolkit.python.pytest_result import PytestResult from bhamon_development_toolkit.python.pytest_scope import PytestScope diff --git a/Sources/toolkit/bhamon_development_toolkit/python/pytest_runner.py b/Sources/toolkit/bhamon_development_toolkit/python/pytest_runner.py index 78083a5..54a50d7 100644 --- a/Sources/toolkit/bhamon_development_toolkit/python/pytest_runner.py +++ b/Sources/toolkit/bhamon_development_toolkit/python/pytest_runner.py @@ -6,13 +6,14 @@ import shutil from typing import List, Optional -from bhamon_development_toolkit.processes import process_helpers -from bhamon_development_toolkit.processes.executable_command import ExecutableCommand -from bhamon_development_toolkit.processes.process_options import ProcessOptions -from bhamon_development_toolkit.processes.process_output_handler import ProcessOutputHandler -from bhamon_development_toolkit.processes.process_output_logger import ProcessOutputLogger -from bhamon_development_toolkit.processes.process_result import ProcessResult -from bhamon_development_toolkit.processes.process_runner import ProcessRunner +from benjaminhamon_standard_extensions.processes import process_helpers +from benjaminhamon_standard_extensions.processes.executable_command import ExecutableCommand +from benjaminhamon_standard_extensions.processes.process_options import ProcessOptions +from benjaminhamon_standard_extensions.processes.process_output_handler import ProcessOutputHandler +from benjaminhamon_standard_extensions.processes.process_output_logger import ProcessOutputLogger +from benjaminhamon_standard_extensions.processes.process_result import ProcessResult +from benjaminhamon_standard_extensions.processes.process_runner import ProcessRunner + from bhamon_development_toolkit.python.pytest_output_handler import PytestOutputHandler from bhamon_development_toolkit.python.pytest_scope import PytestScope diff --git a/Sources/toolkit/bhamon_development_toolkit/python/python_environment.py b/Sources/toolkit/bhamon_development_toolkit/python/python_environment.py index cfbec88..3c3e5b9 100644 --- a/Sources/toolkit/bhamon_development_toolkit/python/python_environment.py +++ b/Sources/toolkit/bhamon_development_toolkit/python/python_environment.py @@ -5,8 +5,8 @@ import sys from typing import List, Optional -from bhamon_development_toolkit.processes import process_helpers -from bhamon_development_toolkit.processes.executable_command import ExecutableCommand +from benjaminhamon_standard_extensions.processes import process_helpers +from benjaminhamon_standard_extensions.processes.executable_command import ExecutableCommand logger = logging.getLogger("Python") diff --git a/Sources/toolkit/bhamon_development_toolkit/python/python_package_builder.py b/Sources/toolkit/bhamon_development_toolkit/python/python_package_builder.py index 032247c..0bff377 100644 --- a/Sources/toolkit/bhamon_development_toolkit/python/python_package_builder.py +++ b/Sources/toolkit/bhamon_development_toolkit/python/python_package_builder.py @@ -5,13 +5,14 @@ import sys from typing import Dict, List, Optional -from bhamon_development_toolkit.processes import process_helpers -from bhamon_development_toolkit.processes.executable_command import ExecutableCommand -from bhamon_development_toolkit.processes.process_options import ProcessOptions -from bhamon_development_toolkit.processes.process_output_collector import ProcessOutputCollector -from bhamon_development_toolkit.processes.process_output_handler import ProcessOutputHandler -from bhamon_development_toolkit.processes.process_output_logger import ProcessOutputLogger -from bhamon_development_toolkit.processes.process_runner import ProcessRunner +from benjaminhamon_standard_extensions.processes import process_helpers +from benjaminhamon_standard_extensions.processes.executable_command import ExecutableCommand +from benjaminhamon_standard_extensions.processes.process_options import ProcessOptions +from benjaminhamon_standard_extensions.processes.process_output_collector import ProcessOutputCollector +from benjaminhamon_standard_extensions.processes.process_output_handler import ProcessOutputHandler +from benjaminhamon_standard_extensions.processes.process_output_logger import ProcessOutputLogger +from benjaminhamon_standard_extensions.processes.process_runner import ProcessRunner + from bhamon_development_toolkit.python.python_package import PythonPackage from bhamon_development_toolkit.python.python_package_metadata import PythonPackageMetadata diff --git a/Sources/toolkit/bhamon_development_toolkit/python/python_twine_distribution_manager.py b/Sources/toolkit/bhamon_development_toolkit/python/python_twine_distribution_manager.py index 882c2df..681b37a 100644 --- a/Sources/toolkit/bhamon_development_toolkit/python/python_twine_distribution_manager.py +++ b/Sources/toolkit/bhamon_development_toolkit/python/python_twine_distribution_manager.py @@ -1,8 +1,8 @@ import logging from typing import Optional -from bhamon_development_toolkit.processes import process_helpers -from bhamon_development_toolkit.processes.executable_command import ExecutableCommand +from benjaminhamon_standard_extensions.processes import process_helpers +from benjaminhamon_standard_extensions.processes.executable_command import ExecutableCommand logger = logging.getLogger("Python") diff --git a/Tests/toolkit/bhamon_development_toolkit_tests/archives/__init__.py b/Tests/toolkit/bhamon_development_toolkit_tests/archives/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/Tests/toolkit/bhamon_development_toolkit_tests/archives/test_archive_operations.py b/Tests/toolkit/bhamon_development_toolkit_tests/archives/test_archive_operations.py deleted file mode 100644 index 0bdb8d1..0000000 --- a/Tests/toolkit/bhamon_development_toolkit_tests/archives/test_archive_operations.py +++ /dev/null @@ -1,212 +0,0 @@ -# cspell:words fileset - -import os -import zipfile - -import py -import pytest - -from bhamon_development_toolkit.archives.archive_operations import ArchiveOperations -from bhamon_development_toolkit.archives.tar_archive_operations import TarArchiveOperations -from bhamon_development_toolkit.archives.zip_archive_operations import ZipArchiveOperations - - -def list_implementations(): - return [ - "tar-uncompressed", - "tar-bz2", - "tar-gzip", - "zip-uncompressed", - "zip-compressed", - ] - - -def instantiate_implementation(implementation: str) -> ArchiveOperations: - if implementation == "tar-uncompressed": - return TarArchiveOperations(None) - - if implementation == "tar-bz2": - return TarArchiveOperations("bz2") - - if implementation == "tar-gzip": - return TarArchiveOperations("gz") - - if implementation == "zip-uncompressed": - return ZipArchiveOperations(compression = zipfile.ZIP_STORED) - - if implementation == "zip-compressed": - return ZipArchiveOperations(compression = zipfile.ZIP_DEFLATED) - - raise ValueError("Unsupported implementation '%s'" % implementation) - - -@pytest.mark.parametrize("implementation", list_implementations()) -def test_create(tmpdir: py.path.local, implementation: str): - operations = instantiate_implementation(implementation) - - archive_path = os.path.join(tmpdir, "Archive" + operations.get_file_extension()) - - fileset = [ - (os.path.join(tmpdir, "Source", "Directory_A", "File_A_1"), "Directory_A/File_A_1"), - (os.path.join(tmpdir, "Source", "File_X_1"), "File_X_1"), - (os.path.join(tmpdir, "Source", "File_X_2"), "File_X_2"), - ] - - for source, _ in fileset: - os.makedirs(os.path.dirname(source), exist_ok = True) - with open(source, mode = "w", encoding = "utf-8"): - pass - - operations.create(archive_path, fileset) - - assert os.path.exists(archive_path) - - -@pytest.mark.parametrize("implementation", list_implementations()) -def test_list_files(tmpdir: py.path.local, implementation: str): - operations = instantiate_implementation(implementation) - - archive_path = os.path.join(tmpdir, "Archive" + operations.get_file_extension()) - - fileset = [ - (os.path.join(tmpdir, "Source", "Directory_A", "File_A_1"), "Directory_A/File_A_1"), - (os.path.join(tmpdir, "Source", "File_X_1"), "File_X_1"), - (os.path.join(tmpdir, "Source", "File_X_2"), "File_X_2"), - ] - - for source, _ in fileset: - os.makedirs(os.path.dirname(source), exist_ok = True) - with open(source, mode = "w", encoding = "utf-8"): - pass - - operations.create(archive_path, fileset) - - file_collection = operations.list_files(archive_path) - assert file_collection == [ dst for _, dst in fileset ] - - -@pytest.mark.parametrize("implementation", list_implementations()) -def test_verify(tmpdir: py.path.local, implementation: str): - operations = instantiate_implementation(implementation) - - archive_path = os.path.join(tmpdir, "Archive" + operations.get_file_extension()) - - fileset = [ - (os.path.join(tmpdir, "Source", "File_X_1"), "File_X_1"), - ] - - for source, _ in fileset: - os.makedirs(os.path.dirname(source), exist_ok = True) - with open(source, mode = "w", encoding = "utf-8"): - pass - - operations.create(archive_path, fileset) - operations.verify(archive_path) - - -@pytest.mark.parametrize("implementation", list_implementations()) -def test_verify_corrupted(tmpdir: py.path.local, implementation: str): - operations = instantiate_implementation(implementation) - - archive_path = os.path.join(tmpdir, "Archive" + operations.get_file_extension()) - - fileset = [ - (os.path.join(tmpdir, "Source", "File_X_1"), "File_X_1"), - ] - - for source, _ in fileset: - os.makedirs(os.path.dirname(source), exist_ok = True) - with open(source, mode = "w", encoding = "utf-8"): - pass - - operations.create(archive_path, fileset) - - with open(archive_path, mode = "rb+") as archive_file: - archive_file.write(b"0" * 10) - - with pytest.raises(RuntimeError): - operations.verify(archive_path) - - -@pytest.mark.parametrize("implementation", list_implementations()) -def test_extract(tmpdir: py.path.local, implementation: str): - operations = instantiate_implementation(implementation) - - archive_path = os.path.join(tmpdir, "Archive" + operations.get_file_extension()) - - fileset = [ - (os.path.join(tmpdir, "Source", "Directory_A", "File_A_1"), "Directory_A/File_A_1"), - (os.path.join(tmpdir, "Source", "File_X_1"), "File_X_1"), - (os.path.join(tmpdir, "Source", "File_X_2"), "File_X_2"), - ] - - for source, _ in fileset: - os.makedirs(os.path.dirname(source), exist_ok = True) - with open(source, mode = "w", encoding = "utf-8"): - pass - - operations.create(archive_path, fileset) - operations.extract(archive_path, os.path.join(tmpdir, "Extraction")) - - for _, destination in fileset: - assert os.path.exists(os.path.join(tmpdir, "Extraction", os.path.normpath(destination))) - - -@pytest.mark.parametrize("implementation", list_implementations()) -def test_extract_replace(tmpdir: py.path.local, implementation: str): - operations = instantiate_implementation(implementation) - - archive_path = os.path.join(tmpdir, "Archive" + operations.get_file_extension()) - extra_file_path = os.path.join(tmpdir, "Extraction", "File_Y_1") - - fileset = [ - (os.path.join(tmpdir, "Source", "Directory_A", "File_A_1"), "Directory_A/File_A_1"), - (os.path.join(tmpdir, "Source", "File_X_1"), "File_X_1"), - (os.path.join(tmpdir, "Source", "File_X_2"), "File_X_2"), - ] - - for source, _ in fileset: - os.makedirs(os.path.dirname(source), exist_ok = True) - with open(source, mode = "w", encoding = "utf-8"): - pass - - os.makedirs(os.path.dirname(extra_file_path)) - with open(extra_file_path, mode = "w", encoding = "utf-8"): - pass - - operations.create(archive_path, fileset) - operations.extract(archive_path, os.path.join(tmpdir, "Extraction"), replace = True) - - for _, destination in fileset: - assert os.path.exists(os.path.join(tmpdir, "Extraction", os.path.normpath(destination))) - assert not os.path.exists(extra_file_path) - - -@pytest.mark.parametrize("implementation", list_implementations()) -def test_extract_keep(tmpdir: py.path.local, implementation: str): - operations = instantiate_implementation(implementation) - - archive_path = os.path.join(tmpdir, "Archive" + operations.get_file_extension()) - extra_file_path = os.path.join(tmpdir, "Extraction", "File_Y_1") - - fileset = [ - (os.path.join(tmpdir, "Source", "Directory_A", "File_A_1"), "Directory_A/File_A_1"), - (os.path.join(tmpdir, "Source", "File_X_1"), "File_X_1"), - (os.path.join(tmpdir, "Source", "File_X_2"), "File_X_2"), - ] - - for source, _ in fileset: - os.makedirs(os.path.dirname(source), exist_ok = True) - with open(source, mode = "w", encoding = "utf-8"): - pass - - os.makedirs(os.path.dirname(extra_file_path)) - with open(extra_file_path, mode = "w", encoding = "utf-8"): - pass - - operations.create(archive_path, fileset) - operations.extract(archive_path, os.path.join(tmpdir, "Extraction"), replace = False) - - for _, destination in fileset: - assert os.path.exists(os.path.join(tmpdir, "Extraction", os.path.normpath(destination))) - assert os.path.exists(extra_file_path) diff --git a/Tests/toolkit/bhamon_development_toolkit_tests/processes/__init__.py b/Tests/toolkit/bhamon_development_toolkit_tests/processes/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/Tests/toolkit/bhamon_development_toolkit_tests/processes/fake_process.py b/Tests/toolkit/bhamon_development_toolkit_tests/processes/fake_process.py deleted file mode 100644 index bc89bb1..0000000 --- a/Tests/toolkit/bhamon_development_toolkit_tests/processes/fake_process.py +++ /dev/null @@ -1,81 +0,0 @@ -import asyncio -import datetime -from typing import Optional - -from bhamon_development_toolkit.processes.process import Process - - -class FakeProcess(Process): - - - def __init__(self, # pylint: disable = too-many-arguments - pid: int, - execution_duration: datetime.timedelta, - *, - stdout: Optional[asyncio.StreamReader] = None, - stderr: Optional[asyncio.StreamReader] = None, - exit_code_for_normal_completion: int = 0, - allow_termination: bool = True, - ) -> None: - - self._pid = pid - self._stdout = stdout - self._stderr = stderr - self._execution_duration = execution_duration - self._exit_code_for_normal_completion = exit_code_for_normal_completion - self._allow_termination = allow_termination - - self._exit_code: Optional[int] = None - self._is_alive: bool = True - - - @property - def pid(self) -> int: - return self._pid - - - @property - def stdout(self) -> Optional[asyncio.StreamReader]: - return self._stdout - - - @property - def stderr(self) -> Optional[asyncio.StreamReader]: - return self._stderr - - - @property - def exit_code(self) -> Optional[int]: - return self._exit_code - - - @property - def is_running(self) -> bool: - return self._is_alive - - - async def wait(self) -> int: - if not self._is_alive: - if self._exit_code is None: - raise RuntimeError("Exit code should not be none") - return self._exit_code - - await asyncio.sleep(self._execution_duration.total_seconds()) - - self._is_alive = False - self._exit_code = self._exit_code_for_normal_completion - - return self._exit_code - - - def terminate(self) -> None: - if not self._allow_termination: - return - - self._is_alive = False - self._exit_code = -1 - - - def kill(self) -> None: - self._is_alive = False - self._exit_code = -2 diff --git a/Tests/toolkit/bhamon_development_toolkit_tests/processes/test_process_helpers.py b/Tests/toolkit/bhamon_development_toolkit_tests/processes/test_process_helpers.py deleted file mode 100644 index 4c32d19..0000000 --- a/Tests/toolkit/bhamon_development_toolkit_tests/processes/test_process_helpers.py +++ /dev/null @@ -1,29 +0,0 @@ -""" Unit tests for process_helpers """ - -from bhamon_development_toolkit.processes import process_helpers - - -def test_format_executable_command(): - command = [ "my_executable", "--path", "MyDirectory/My file with spaces", "--option=value" ] - command_formatted = process_helpers.format_executable_command(command) - - assert command_formatted == "my_executable --path 'MyDirectory/My file with spaces' --option=value" - - command = [ "my_executable", "--path", "MyDirectory/My file with spaces", "--option=value with spaces" ] - command_formatted = process_helpers.format_executable_command(command) - - assert command_formatted == "my_executable --path 'MyDirectory/My file with spaces' '--option=value with spaces'" - - command = [ "my_executable", "--path", "MyDirectory/My file with spaces", "--option='value with spaces and quoted'" ] - command_formatted = process_helpers.format_executable_command(command) - - assert command_formatted == "my_executable --path 'MyDirectory/My file with spaces' '--option='\"'\"'value with spaces and quoted'\"'\"''" - - -def test_format_executable_command_element(): - assert process_helpers.format_executable_command_element("something") == "something" - assert process_helpers.format_executable_command_element("something with spaces") == "'something with spaces'" - assert process_helpers.format_executable_command_element("some directory/some file") == "'some directory/some file'" - assert process_helpers.format_executable_command_element("key=value") == "key=value" - assert process_helpers.format_executable_command_element("key=value with spaces") == "'key=value with spaces'" - assert process_helpers.format_executable_command_element("key='value with spaces and quoted'") == "'key='\"'\"'value with spaces and quoted'\"'\"''" diff --git a/Tests/toolkit/bhamon_development_toolkit_tests/processes/test_process_helpers_with_integration.py b/Tests/toolkit/bhamon_development_toolkit_tests/processes/test_process_helpers_with_integration.py deleted file mode 100644 index 647a3dd..0000000 --- a/Tests/toolkit/bhamon_development_toolkit_tests/processes/test_process_helpers_with_integration.py +++ /dev/null @@ -1,138 +0,0 @@ -""" Integration tests for process_helpers """ - -import logging - -import pytest - -from bhamon_development_toolkit.processes import process_helpers -from bhamon_development_toolkit.processes.exceptions.process_failure_exception import ProcessFailureException -from bhamon_development_toolkit.processes.executable_command import ExecutableCommand - - -def test_run_simple_success(): - logger = logging.getLogger("Subprocess") - command = ExecutableCommand("python") - command.add_arguments([ "-c", "pass" ]) - - result = process_helpers.run_simple(logger, command) - - assert result.executable == "python" - assert result.exit_code == 0 - assert result.standard_output == "" - assert result.error_output == "" - - -def test_run_simple_failure(): - logger = logging.getLogger("Subprocess") - command = ExecutableCommand("python") - command.add_arguments([ "-c", "raise RuntimeError" ]) - - with pytest.raises(ProcessFailureException) as exception: - process_helpers.run_simple(logger, command) - assert exception.value.exit_code == 1 - - -def test_run_simple_output(): - logger = logging.getLogger("Subprocess") - command = ExecutableCommand("python") - command.add_arguments([ "-c", "print('hello')" ]) - - result = process_helpers.run_simple(logger, command) - - assert result.executable == "python" - assert result.exit_code == 0 - assert result.standard_output == "hello\n" - assert result.error_output == "" - - -def test_run_simple_output_stderr(): - logger = logging.getLogger("Subprocess") - command = ExecutableCommand("python") - command.add_arguments([ "-c", "import sys; print('hello stderr', file = sys.stderr)" ]) - - result = process_helpers.run_simple(logger, command) - - assert result.executable == "python" - assert result.exit_code == 0 - assert result.standard_output == "" - assert result.error_output == "hello stderr\n" - - -def test_run_simple_output_unicode(): - logger = logging.getLogger("Subprocess") - command = ExecutableCommand("python") - command.add_arguments([ "-c", "print('… é ² √ 👍')" ]) - - result = process_helpers.run_simple(logger, command) - - assert result.executable == "python" - assert result.exit_code == 0 - assert result.standard_output == "… é ² √ 👍\n" - assert result.error_output == "" - - -@pytest.mark.asyncio -async def test_run_simple_async_success(): - logger = logging.getLogger("Subprocess") - command = ExecutableCommand("python") - command.add_arguments([ "-c", "pass" ]) - - result = await process_helpers.run_simple_async(logger, command) - - assert result.executable == "python" - assert result.exit_code == 0 - assert result.standard_output == "" - assert result.error_output == "" - - -@pytest.mark.asyncio -async def test_run_simple_async_failure(): - logger = logging.getLogger("Subprocess") - command = ExecutableCommand("python") - command.add_arguments([ "-c", "raise RuntimeError" ]) - - with pytest.raises(ProcessFailureException) as exception: - await process_helpers.run_simple_async(logger, command) - assert exception.value.exit_code == 1 - - -@pytest.mark.asyncio -async def test_run_simple_async_output(): - logger = logging.getLogger("Subprocess") - command = ExecutableCommand("python") - command.add_arguments([ "-c", "print('hello')" ]) - - result = await process_helpers.run_simple_async(logger, command) - - assert result.executable == "python" - assert result.exit_code == 0 - assert result.standard_output == "hello\n" - assert result.error_output == "" - - -@pytest.mark.asyncio -async def test_run_simple_async_output_stderr(): - logger = logging.getLogger("Subprocess") - command = ExecutableCommand("python") - command.add_arguments([ "-c", "import sys; print('hello stderr', file = sys.stderr)" ]) - - result = await process_helpers.run_simple_async(logger, command) - - assert result.executable == "python" - assert result.exit_code == 0 - assert result.standard_output == "" - assert result.error_output == "hello stderr\n" - - -@pytest.mark.asyncio -async def test_run_simple_async_output_unicode(): - logger = logging.getLogger("Subprocess") - command = ExecutableCommand("python") - command.add_arguments([ "-c", "print('… é ² √ 👍')" ]) - - result = await process_helpers.run_simple_async(logger, command) - - assert result.executable == "python" - assert result.exit_code == 0 - assert result.standard_output == "… é ² √ 👍\n" - assert result.error_output == "" diff --git a/Tests/toolkit/bhamon_development_toolkit_tests/processes/test_process_watcher.py b/Tests/toolkit/bhamon_development_toolkit_tests/processes/test_process_watcher.py deleted file mode 100644 index ec22486..0000000 --- a/Tests/toolkit/bhamon_development_toolkit_tests/processes/test_process_watcher.py +++ /dev/null @@ -1,325 +0,0 @@ -""" Unit tests for ProcessWatcher """ - -import asyncio -import datetime - -import pytest - -from bhamon_development_toolkit.processes.exceptions.process_failure_exception import ProcessFailureException -from bhamon_development_toolkit.processes.exceptions.process_timeout_exception import ProcessTimeoutException -from bhamon_development_toolkit.processes.executable_command import ExecutableCommand -from bhamon_development_toolkit.processes.process_options import ProcessOptions -from bhamon_development_toolkit.processes.process_output_collector import ProcessOutputCollector -from bhamon_development_toolkit.processes.process_watcher import ProcessWatcher - -from .fake_process import FakeProcess - - -@pytest.mark.asyncio -async def test_run_success(): - command = ExecutableCommand("dummy") - - options = ProcessOptions( - wait_update_interval = datetime.timedelta(seconds = 0.1)) - - process = FakeProcess(pid = 1, execution_duration = datetime.timedelta(seconds = 0.5)) - watcher = ProcessWatcher(process, command, options) - - await watcher.start() - - status = watcher.get_status() - assert status.pid > 0 - assert status.is_running - assert status.exit_code is None - - await watcher.wait() - - status = watcher.get_status() - assert status.pid > 0 - assert not status.is_running - assert status.exit_code == 0 - - await watcher.complete() - - status = watcher.get_status() - assert status.pid > 0 - assert not status.is_running - assert status.exit_code == 0 - - -@pytest.mark.asyncio -async def test_run_failure(): - command = ExecutableCommand("dummy") - - options = ProcessOptions( - wait_update_interval = datetime.timedelta(seconds = 0.1)) - - process = FakeProcess(pid = 1, execution_duration = datetime.timedelta(seconds = 0.5), exit_code_for_normal_completion = 1) - watcher = ProcessWatcher(process, command, options) - - await watcher.start() - - status = watcher.get_status() - assert status.pid > 0 - assert status.is_running - assert status.exit_code is None - - await watcher.wait() - - status = watcher.get_status() - assert status.pid > 0 - assert not status.is_running - assert status.exit_code == 1 - - with pytest.raises(ProcessFailureException) as exception: - await watcher.complete() - assert exception.value.exit_code == 1 - - status = watcher.get_status() - assert status.pid > 0 - assert not status.is_running - assert status.exit_code == 1 - - -@pytest.mark.asyncio -async def test_terminate(): - command = ExecutableCommand("dummy") - - options = ProcessOptions( - termination_timeout = datetime.timedelta(seconds = 0.5), - wait_update_interval = datetime.timedelta(seconds = 0.1)) - - process = FakeProcess(pid = 1, execution_duration = datetime.timedelta(seconds = 2)) - watcher = ProcessWatcher(process, command, options) - - await watcher.start() - - status = watcher.get_status() - assert status.pid > 0 - assert status.is_running - assert status.exit_code is None - - await watcher.terminate("Interrupt") - - status = watcher.get_status() - assert status.pid > 0 - assert not status.is_running - assert status.exit_code == -1 - - -@pytest.mark.asyncio -async def test_terminate_with_normal_completion(): - command = ExecutableCommand("dummy") - - options = ProcessOptions( - termination_timeout = datetime.timedelta(seconds = 0.5), - wait_update_interval = datetime.timedelta(seconds = 0.1)) - - process = FakeProcess(pid = 1, execution_duration = datetime.timedelta(seconds = 0.5)) - watcher = ProcessWatcher(process, command, options) - - await watcher.start() - - status = watcher.get_status() - assert status.pid > 0 - assert status.is_running - assert status.exit_code is None - - await process.wait() - - status = watcher.get_status() - assert status.pid > 0 - assert not status.is_running - assert status.exit_code == 0 - - await watcher.terminate("Interrupt") - - status = watcher.get_status() - assert status.pid > 0 - assert not status.is_running - assert status.exit_code == 0 - - -@pytest.mark.asyncio -async def test_terminate_with_custom_exit_code(): - command = ExecutableCommand("dummy") - - options = ProcessOptions( - termination_timeout = datetime.timedelta(seconds = 0.5), - wait_update_interval = datetime.timedelta(seconds = 0.1)) - - process = FakeProcess(pid = 1, execution_duration = datetime.timedelta(seconds = 2)) - watcher = ProcessWatcher(process, command, options) - - await watcher.start() - - status = watcher.get_status() - assert status.pid > 0 - assert status.is_running - assert status.exit_code is None - - await watcher.terminate("Interrupt", exit_code = 5) - - status = watcher.get_status() - assert status.pid > 0 - assert not status.is_running - assert status.exit_code == 5 - - -@pytest.mark.asyncio -async def test_terminate_force(): - command = ExecutableCommand("dummy") - - options = ProcessOptions( - termination_timeout = datetime.timedelta(seconds = 0.5), - wait_update_interval = datetime.timedelta(seconds = 0.1)) - - process = FakeProcess(pid = 1, execution_duration = datetime.timedelta(seconds = 2), allow_termination = False) - watcher = ProcessWatcher(process, command, options) - - await watcher.start() - - status = watcher.get_status() - assert status.pid > 0 - assert status.is_running - assert status.exit_code is None - - await watcher.terminate("Interrupt") - - status = watcher.get_status() - assert status.pid > 0 - assert not status.is_running - assert status.exit_code == -2 - - -@pytest.mark.asyncio -async def test_run_timeout(): - command = ExecutableCommand("dummy") - - options = ProcessOptions( - run_timeout = datetime.timedelta(seconds = 0.5), - termination_timeout = datetime.timedelta(seconds = 0.5), - wait_update_interval = datetime.timedelta(seconds = 0.1)) - - process = FakeProcess(pid = 1, execution_duration = datetime.timedelta(seconds = 2)) - watcher = ProcessWatcher(process, command, options) - - await watcher.start() - - status = watcher.get_status() - assert status.pid > 0 - assert status.is_running - assert status.exit_code is None - - if options.run_timeout is None: - raise RuntimeError("Run timeout should not be none") - - with pytest.raises(asyncio.TimeoutError): - await asyncio.wait_for(watcher.wait(), timeout = (options.run_timeout + datetime.timedelta(seconds = 0.5)).total_seconds()) - - with pytest.raises(ProcessTimeoutException) as exception: - await watcher.complete() - assert exception.value.exit_code == -1 - - status = watcher.get_status() - assert status.pid > 0 - assert not status.is_running - assert status.exit_code == -1 - - -@pytest.mark.asyncio -async def test_output_timeout(): - command = ExecutableCommand("dummy") - - options = ProcessOptions( - output_timeout = datetime.timedelta(seconds = 0.5), - termination_timeout = datetime.timedelta(seconds = 0.5), - wait_update_interval = datetime.timedelta(seconds = 0.1)) - - process = FakeProcess(pid = 1, execution_duration = datetime.timedelta(seconds = 2)) - watcher = ProcessWatcher(process, command, options) - - await watcher.start() - - status = watcher.get_status() - assert status.pid > 0 - assert status.is_running - assert status.exit_code is None - - if options.output_timeout is None: - raise RuntimeError("Output timeout should not be none") - - with pytest.raises(asyncio.TimeoutError): - await asyncio.wait_for(watcher.wait(), timeout = (options.output_timeout + datetime.timedelta(seconds = 0.5)).total_seconds()) - - with pytest.raises(ProcessTimeoutException) as exception: - await watcher.complete() - assert exception.value.exit_code == -1 - - status = watcher.get_status() - assert status.pid > 0 - assert not status.is_running - assert status.exit_code == -1 - - -@pytest.mark.asyncio -async def test_output(): - command = ExecutableCommand("dummy") - - options = ProcessOptions( - termination_timeout = datetime.timedelta(seconds = 0.5), - wait_update_interval = datetime.timedelta(seconds = 0.1)) - - stdout = asyncio.StreamReader() - stderr = asyncio.StreamReader() - - process = FakeProcess(pid = 1, execution_duration = datetime.timedelta(seconds = 0.5), stdout = stdout, stderr = stderr) - watcher = ProcessWatcher(process, command, options) - output_collector = ProcessOutputCollector() - watcher.add_output_handler(output_collector) - - stdout.feed_data("hello stdout\n".encode(options.encoding)) - stderr.feed_data("hello stderr\n".encode(options.encoding)) - - stdout.feed_data("bye stdout\n".encode(options.encoding)) - stderr.feed_data("bye stderr\n".encode(options.encoding)) - - stdout.feed_eof() - stderr.feed_eof() - - await watcher.start() - await watcher.wait() - await watcher.complete() - - assert output_collector.get_stdout() == "hello stdout\nbye stdout\n" - assert output_collector.get_stderr() == "hello stderr\nbye stderr\n" - - -@pytest.mark.asyncio -async def test_output_unicode(): - command = ExecutableCommand("dummy") - - options = ProcessOptions( - termination_timeout = datetime.timedelta(seconds = 0.5), - wait_update_interval = datetime.timedelta(seconds = 0.1)) - - stdout = asyncio.StreamReader() - stderr = asyncio.StreamReader() - - process = FakeProcess(pid = 1, execution_duration = datetime.timedelta(seconds = 0.5), stdout = stdout, stderr = stderr) - watcher = ProcessWatcher(process, command, options) - output_collector = ProcessOutputCollector() - watcher.add_output_handler(output_collector) - - stdout.feed_data("… é ² √ 👍\n".encode(options.encoding)) - stderr.feed_data("… é ² √ 👍\n".encode(options.encoding)) - - stdout.feed_eof() - stderr.feed_eof() - - await watcher.start() - await watcher.wait() - await watcher.complete() - - assert output_collector.get_stdout() == "… é ² √ 👍\n" - assert output_collector.get_stderr() == "… é ² √ 👍\n" diff --git a/Tests/toolkit/bhamon_development_toolkit_tests/processes/test_process_watcher_with_integration.py b/Tests/toolkit/bhamon_development_toolkit_tests/processes/test_process_watcher_with_integration.py deleted file mode 100644 index bfacdb0..0000000 --- a/Tests/toolkit/bhamon_development_toolkit_tests/processes/test_process_watcher_with_integration.py +++ /dev/null @@ -1,250 +0,0 @@ -""" Integration tests for ProcessWatcher """ - -import asyncio -import datetime -import platform -import signal - -import pytest - -from bhamon_development_toolkit.processes.exceptions.process_failure_exception import ProcessFailureException -from bhamon_development_toolkit.processes.exceptions.process_timeout_exception import ProcessTimeoutException -from bhamon_development_toolkit.processes.executable_command import ExecutableCommand -from bhamon_development_toolkit.processes.process_options import ProcessOptions -from bhamon_development_toolkit.processes.process_output_collector import ProcessOutputCollector -from bhamon_development_toolkit.processes.process_spawner import ProcessSpawner - - -def get_expected_termination_exit_code() -> int: - if platform.system() == "Windows": - return 0xC000013A # STATUS_CONTROL_C_EXIT - return - signal.SIGTERM - - -@pytest.mark.asyncio -async def test_run_success(): - spawner = ProcessSpawner(is_console = True) - command = ExecutableCommand("python") - command.add_arguments([ "-c", "pass" ]) - - options = ProcessOptions( - wait_update_interval = datetime.timedelta(seconds = 0.1)) - - watcher = await spawner.spawn_process(command = command, options = options) - - await watcher.start() - await watcher.wait() - await watcher.complete() - - status = watcher.get_status() - - assert status.pid > 0 - assert not status.is_running - assert status.exit_code == 0 - - -@pytest.mark.asyncio -async def test_run_failure(): - spawner = ProcessSpawner(is_console = True) - command = ExecutableCommand("python") - command.add_arguments([ "-c", "raise RuntimeError" ]) - - options = ProcessOptions( - wait_update_interval = datetime.timedelta(seconds = 0.1)) - - watcher = await spawner.spawn_process(command = command, options = options) - - await watcher.start() - await watcher.wait() - - with pytest.raises(ProcessFailureException) as exception: - await watcher.complete() - assert exception.value.exit_code == 1 - - status = watcher.get_status() - - assert status.executable_name == "python" - assert status.executable_path == "python" - assert status.pid > 0 - assert not status.is_running - assert status.exit_code == 1 - - -@pytest.mark.asyncio -async def test_terminate(): - spawner = ProcessSpawner(is_console = True) - command = ExecutableCommand("python") - command.add_arguments([ "-c", "import time; time.sleep(10)" ]) - - options = ProcessOptions( - wait_update_interval = datetime.timedelta(seconds = 0.1)) - - watcher = await spawner.spawn_process(command, options) - - await watcher.start() - - status = watcher.get_status() - assert status.pid > 0 - assert status.is_running - assert status.exit_code is None - - await asyncio.sleep(0.1) - - await watcher.terminate("Interrupt") - - status = watcher.get_status() - assert status.pid > 0 - assert not status.is_running - assert status.exit_code == get_expected_termination_exit_code() - - -@pytest.mark.asyncio -async def test_run_timeout(): - spawner = ProcessSpawner(is_console = True) - command = ExecutableCommand("python") - command.add_arguments([ "-c", "import time; time.sleep(10)" ]) - - options = ProcessOptions( - run_timeout = datetime.timedelta(seconds = 0.5), - wait_update_interval = datetime.timedelta(seconds = 0.1)) - - watcher = await spawner.spawn_process(command, options) - - await watcher.start() - - status = watcher.get_status() - assert status.pid > 0 - assert status.is_running - assert status.exit_code is None - - if options.run_timeout is None: - raise RuntimeError("Run timeout should not be none") - - await asyncio.wait_for(watcher.wait(), timeout = (options.run_timeout + datetime.timedelta(seconds = 0.5)).total_seconds()) - - with pytest.raises(ProcessTimeoutException) as exception: - await watcher.complete() - assert exception.value.exit_code == get_expected_termination_exit_code() - - status = watcher.get_status() - assert status.pid > 0 - assert not status.is_running - assert status.exit_code == get_expected_termination_exit_code() - - -@pytest.mark.asyncio -async def test_output_timeout(): - spawner = ProcessSpawner(is_console = True) - command = ExecutableCommand("python") - command.add_arguments([ "-c", "import time; print('before'); time.sleep(10); print('after')" ]) - - options = ProcessOptions( - output_timeout = datetime.timedelta(seconds = 0.5), - wait_update_interval = datetime.timedelta(seconds = 0.1)) - - watcher = await spawner.spawn_process(command, options) - - await watcher.start() - - status = watcher.get_status() - assert status.pid > 0 - assert status.is_running - assert status.exit_code is None - - if options.output_timeout is None: - raise RuntimeError("Output timeout should not be none") - - await asyncio.wait_for(watcher.wait(), timeout = (options.output_timeout + datetime.timedelta(seconds = 0.5)).total_seconds()) - - with pytest.raises(ProcessTimeoutException) as exception: - await watcher.complete() - assert exception.value.exit_code == get_expected_termination_exit_code() - - status = watcher.get_status() - assert status.pid > 0 - assert not status.is_running - assert status.exit_code == get_expected_termination_exit_code() - - -@pytest.mark.asyncio -async def test_output(): - spawner = ProcessSpawner(is_console = True) - command = ExecutableCommand("python") - command.add_arguments([ "-c", "print('hello')" ]) - - options = ProcessOptions( - wait_update_interval = datetime.timedelta(seconds = 0.1)) - - watcher = await spawner.spawn_process(command = command, options = options) - - output_collector = ProcessOutputCollector() - watcher.add_output_handler(output_collector) - - await watcher.start() - await watcher.wait() - await watcher.complete() - - status = watcher.get_status() - - assert status.pid > 0 - assert not status.is_running - assert status.exit_code == 0 - - assert output_collector.get_stdout() == "hello\n" - assert output_collector.get_stderr() == "" - - -@pytest.mark.asyncio -async def test_output_stderr(): - spawner = ProcessSpawner(is_console = True) - command = ExecutableCommand("python") - command.add_arguments([ "-c", "import sys; print('hello stderr', file = sys.stderr)" ]) - - options = ProcessOptions( - wait_update_interval = datetime.timedelta(seconds = 0.1)) - - watcher = await spawner.spawn_process(command = command, options = options) - - output_collector = ProcessOutputCollector() - watcher.add_output_handler(output_collector) - - await watcher.start() - await watcher.wait() - await watcher.complete() - - status = watcher.get_status() - - assert status.pid > 0 - assert not status.is_running - assert status.exit_code == 0 - - assert output_collector.get_stdout() == "" - assert output_collector.get_stderr() == "hello stderr\n" - - -@pytest.mark.asyncio -async def test_output_unicode(): - spawner = ProcessSpawner(is_console = True) - command = ExecutableCommand("python") - command.add_arguments([ "-c", "print('… é ² √ 👍')" ]) - - options = ProcessOptions( - wait_update_interval = datetime.timedelta(seconds = 0.1)) - - watcher = await spawner.spawn_process(command = command, options = options) - - output_collector = ProcessOutputCollector() - watcher.add_output_handler(output_collector) - - await watcher.start() - await watcher.wait() - await watcher.complete() - - status = watcher.get_status() - - assert status.pid > 0 - assert not status.is_running - assert status.exit_code == 0 - - assert output_collector.get_stdout() == "… é ² √ 👍\n" - assert output_collector.get_stderr() == "" diff --git a/Tests/toolkit/bhamon_development_toolkit_tests/python/test_pyinstaller_runner.py b/Tests/toolkit/bhamon_development_toolkit_tests/python/test_pyinstaller_runner.py index 29cd4be..0af7520 100644 --- a/Tests/toolkit/bhamon_development_toolkit_tests/python/test_pyinstaller_runner.py +++ b/Tests/toolkit/bhamon_development_toolkit_tests/python/test_pyinstaller_runner.py @@ -8,11 +8,12 @@ import mockito import pytest +from benjaminhamon_standard_extensions.processes.executable_command import ExecutableCommand +from benjaminhamon_standard_extensions.processes.process_options import ProcessOptions +from benjaminhamon_standard_extensions.processes.process_result import ProcessResult +from benjaminhamon_standard_extensions.processes.process_runner import ProcessRunner + from bhamon_development_toolkit.applications.application_metadata import ApplicationMetadata -from bhamon_development_toolkit.processes.executable_command import ExecutableCommand -from bhamon_development_toolkit.processes.process_options import ProcessOptions -from bhamon_development_toolkit.processes.process_result import ProcessResult -from bhamon_development_toolkit.processes.process_runner import ProcessRunner from bhamon_development_toolkit.python.pyinstaller_configuration import PyInstallerConfiguration from bhamon_development_toolkit.python.pyinstaller_runner import PyInstallerRunner diff --git a/Tests/toolkit/bhamon_development_toolkit_tests/python/test_pyinstaller_runner_with_integration.py b/Tests/toolkit/bhamon_development_toolkit_tests/python/test_pyinstaller_runner_with_integration.py index 6b5b83d..681e058 100644 --- a/Tests/toolkit/bhamon_development_toolkit_tests/python/test_pyinstaller_runner_with_integration.py +++ b/Tests/toolkit/bhamon_development_toolkit_tests/python/test_pyinstaller_runner_with_integration.py @@ -9,8 +9,9 @@ import pytest -from bhamon_development_toolkit.processes.process_runner import ProcessRunner -from bhamon_development_toolkit.processes.process_spawner import ProcessSpawner +from benjaminhamon_standard_extensions.processes.process_runner import ProcessRunner +from benjaminhamon_standard_extensions.processes.process_spawner import ProcessSpawner + from bhamon_development_toolkit.python.pyinstaller_configuration import PyInstallerConfiguration from bhamon_development_toolkit.python.pyinstaller_runner import PyInstallerRunner diff --git a/Tests/toolkit/bhamon_development_toolkit_tests/python/test_pylint_runner.py b/Tests/toolkit/bhamon_development_toolkit_tests/python/test_pylint_runner.py index c118258..0ad3f0b 100644 --- a/Tests/toolkit/bhamon_development_toolkit_tests/python/test_pylint_runner.py +++ b/Tests/toolkit/bhamon_development_toolkit_tests/python/test_pylint_runner.py @@ -6,10 +6,11 @@ import mockito import pytest -from bhamon_development_toolkit.processes.executable_command import ExecutableCommand -from bhamon_development_toolkit.processes.process_options import ProcessOptions -from bhamon_development_toolkit.processes.process_result import ProcessResult -from bhamon_development_toolkit.processes.process_runner import ProcessRunner +from benjaminhamon_standard_extensions.processes.executable_command import ExecutableCommand +from benjaminhamon_standard_extensions.processes.process_options import ProcessOptions +from benjaminhamon_standard_extensions.processes.process_result import ProcessResult +from benjaminhamon_standard_extensions.processes.process_runner import ProcessRunner + from bhamon_development_toolkit.python.pylint_runner import PylintRunner from bhamon_development_toolkit.python.pylint_scope import PylintScope diff --git a/Tests/toolkit/bhamon_development_toolkit_tests/python/test_pylint_runner_with_integration.py b/Tests/toolkit/bhamon_development_toolkit_tests/python/test_pylint_runner_with_integration.py index 7168917..51f82d4 100644 --- a/Tests/toolkit/bhamon_development_toolkit_tests/python/test_pylint_runner_with_integration.py +++ b/Tests/toolkit/bhamon_development_toolkit_tests/python/test_pylint_runner_with_integration.py @@ -5,8 +5,9 @@ import pytest -from bhamon_development_toolkit.processes.process_runner import ProcessRunner -from bhamon_development_toolkit.processes.process_spawner import ProcessSpawner +from benjaminhamon_standard_extensions.processes.process_runner import ProcessRunner +from benjaminhamon_standard_extensions.processes.process_spawner import ProcessSpawner + from bhamon_development_toolkit.python.pylint_runner import PylintRunner from bhamon_development_toolkit.python.pylint_scope import PylintScope diff --git a/Tests/toolkit/bhamon_development_toolkit_tests/python/test_pytest_runner.py b/Tests/toolkit/bhamon_development_toolkit_tests/python/test_pytest_runner.py index 3ebb6fe..5fe40f8 100644 --- a/Tests/toolkit/bhamon_development_toolkit_tests/python/test_pytest_runner.py +++ b/Tests/toolkit/bhamon_development_toolkit_tests/python/test_pytest_runner.py @@ -6,10 +6,11 @@ import mockito import pytest -from bhamon_development_toolkit.processes.executable_command import ExecutableCommand -from bhamon_development_toolkit.processes.process_options import ProcessOptions -from bhamon_development_toolkit.processes.process_result import ProcessResult -from bhamon_development_toolkit.processes.process_runner import ProcessRunner +from benjaminhamon_standard_extensions.processes.executable_command import ExecutableCommand +from benjaminhamon_standard_extensions.processes.process_options import ProcessOptions +from benjaminhamon_standard_extensions.processes.process_result import ProcessResult +from benjaminhamon_standard_extensions.processes.process_runner import ProcessRunner + from bhamon_development_toolkit.python.pytest_runner import PytestRunner from bhamon_development_toolkit.python.pytest_scope import PytestScope diff --git a/Tests/toolkit/bhamon_development_toolkit_tests/python/test_pytest_runner_with_integration.py b/Tests/toolkit/bhamon_development_toolkit_tests/python/test_pytest_runner_with_integration.py index 7063575..ce010d2 100644 --- a/Tests/toolkit/bhamon_development_toolkit_tests/python/test_pytest_runner_with_integration.py +++ b/Tests/toolkit/bhamon_development_toolkit_tests/python/test_pytest_runner_with_integration.py @@ -5,8 +5,9 @@ import pytest -from bhamon_development_toolkit.processes.process_runner import ProcessRunner -from bhamon_development_toolkit.processes.process_spawner import ProcessSpawner +from benjaminhamon_standard_extensions.processes.process_runner import ProcessRunner +from benjaminhamon_standard_extensions.processes.process_spawner import ProcessSpawner + from bhamon_development_toolkit.python.pytest_runner import PytestRunner from bhamon_development_toolkit.python.pytest_scope import PytestScope diff --git a/Tests/toolkit/bhamon_development_toolkit_tests/python/test_python_environment_with_integration.py b/Tests/toolkit/bhamon_development_toolkit_tests/python/test_python_environment_with_integration.py index e472cbf..c9cc677 100644 --- a/Tests/toolkit/bhamon_development_toolkit_tests/python/test_python_environment_with_integration.py +++ b/Tests/toolkit/bhamon_development_toolkit_tests/python/test_python_environment_with_integration.py @@ -4,7 +4,8 @@ import pytest -from bhamon_development_toolkit.processes.exceptions.process_failure_exception import ProcessFailureException +from benjaminhamon_standard_extensions.processes.exceptions.process_failure_exception import ProcessFailureException + from bhamon_development_toolkit.python import python_helpers from bhamon_development_toolkit.python.python_environment import PythonEnvironment diff --git a/Tests/toolkit/bhamon_development_toolkit_tests/python/test_python_package_builder.py b/Tests/toolkit/bhamon_development_toolkit_tests/python/test_python_package_builder.py index e5ec095..4e12801 100644 --- a/Tests/toolkit/bhamon_development_toolkit_tests/python/test_python_package_builder.py +++ b/Tests/toolkit/bhamon_development_toolkit_tests/python/test_python_package_builder.py @@ -6,10 +6,11 @@ import mockito import pytest -from bhamon_development_toolkit.processes.executable_command import ExecutableCommand -from bhamon_development_toolkit.processes.process_options import ProcessOptions -from bhamon_development_toolkit.processes.process_result import ProcessResult -from bhamon_development_toolkit.processes.process_runner import ProcessRunner +from benjaminhamon_standard_extensions.processes.executable_command import ExecutableCommand +from benjaminhamon_standard_extensions.processes.process_options import ProcessOptions +from benjaminhamon_standard_extensions.processes.process_result import ProcessResult +from benjaminhamon_standard_extensions.processes.process_runner import ProcessRunner + from bhamon_development_toolkit.python.python_package import PythonPackage from bhamon_development_toolkit.python.python_package_builder import PythonPackageBuilder diff --git a/Tests/toolkit/bhamon_development_toolkit_tests/python/test_python_package_builder_with_integration.py b/Tests/toolkit/bhamon_development_toolkit_tests/python/test_python_package_builder_with_integration.py index 4ccb3e6..1c0f79f 100644 --- a/Tests/toolkit/bhamon_development_toolkit_tests/python/test_python_package_builder_with_integration.py +++ b/Tests/toolkit/bhamon_development_toolkit_tests/python/test_python_package_builder_with_integration.py @@ -5,9 +5,10 @@ import pytest -from bhamon_development_toolkit.processes.exceptions.process_failure_exception import ProcessFailureException -from bhamon_development_toolkit.processes.process_runner import ProcessRunner -from bhamon_development_toolkit.processes.process_spawner import ProcessSpawner +from benjaminhamon_standard_extensions.processes.exceptions.process_failure_exception import ProcessFailureException +from benjaminhamon_standard_extensions.processes.process_runner import ProcessRunner +from benjaminhamon_standard_extensions.processes.process_spawner import ProcessSpawner + from bhamon_development_toolkit.python.python_package import PythonPackage from bhamon_development_toolkit.python.python_package_builder import PythonPackageBuilder