import logging import time import types import typing import tempfile import pytest import rnsh.rnsh import asyncio import rnsh.process import contextlib import threading import os import pathlib import tests import shutil import random module_logger = logging.getLogger(__name__) module_abs_filename = os.path.abspath(tests.__file__) module_dir = os.path.dirname(module_abs_filename) class SubprocessReader(contextlib.AbstractContextManager): def __init__(self, argv: [str], env: dict = None, name: str = None, stdin_is_pipe: bool = False, stdout_is_pipe: bool = False, stderr_is_pipe: bool = False): self._log = module_logger.getChild(self.__class__.__name__ + ("" if name is None else f"({name})")) self.name = name or "subproc" self.process: rnsh.process.CallbackSubprocess self.loop = asyncio.get_running_loop() self.env = env or os.environ.copy() self.argv = argv self._lock = threading.RLock() self._stdout = bytearray() self._stderr = bytearray() self.return_code: int = None self.process = rnsh.process.CallbackSubprocess(argv=self.argv, env=self.env, loop=self.loop, stdout_callback=self._stdout_cb, terminated_callback=self._terminated_cb, stderr_callback=self._stderr_cb, stdin_is_pipe=stdin_is_pipe, stdout_is_pipe=stdout_is_pipe, stderr_is_pipe=stderr_is_pipe) def _stdout_cb(self, data): self._log.debug(f"_stdout_cb({data})") with self._lock: self._stdout.extend(data) def read(self): self._log.debug(f"read()") with self._lock: data = self._stdout.copy() self._stdout.clear() self._log.debug(f"read() returns {data}") return data def _stderr_cb(self, data): self._log.debug(f"_stderr_cb({data})") with self._lock: self._stderr.extend(data) def read_err(self): self._log.debug(f"read_err()") with self._lock: data = self._stderr.copy() self._stderr.clear() self._log.debug(f"read_err() returns {data}") return data def _terminated_cb(self, rc): self._log.debug(f"_terminated_cb({rc})") self.return_code = rc def start(self): self._log.debug(f"start()") self.process.start() def cleanup(self): self._log.debug(f"cleanup()") if self.process and self.process.running: self.process.terminate(kill_delay=0.1) time.sleep(0.5) def __exit__(self, __exc_type: typing.Type[BaseException], __exc_value: BaseException, __traceback: types.TracebackType) -> bool: self._log.debug(f"__exit__({__exc_type}, {__exc_value}, {__traceback})") self.cleanup() return False def replace_text_in_file(filename: str, text: str, replacement: str): # Read in the file with open(filename, 'r') as file: filedata = file.read() # Replace the target string filedata = filedata.replace(text, replacement) # Write the file out again with open(filename, 'w') as file: file.write(filedata) class tempdir(object): """Sets the cwd within the context Args: path (Path): The path to the cwd """ def __init__(self, cd: bool = False): self.cd = cd self.tempdir = tempfile.TemporaryDirectory() self.path = self.tempdir.name self.origin = pathlib.Path().absolute() self.configfile = os.path.join(self.path, "config") def setup_files(self): shutil.copy(os.path.join(module_dir, "reticulum_test_config"), self.configfile) port1 = random.randint(30000, 65000) port2 = port1 + 1 replace_text_in_file(self.configfile, "22222", str(port1)) replace_text_in_file(self.configfile, "22223", str(port2)) def __enter__(self): self.setup_files() if self.cd: os.chdir(self.path) return self.path def __exit__(self, exc, value, tb): if self.cd: os.chdir(self.origin) self.tempdir.__exit__(exc, value, tb) def test_config_and_cleanup(): td = None with tests.helpers.tempdir() as td: assert os.path.isfile(os.path.join(td, "config")) with open(os.path.join(td, "config"), 'r') as file: filedata = file.read() assert filedata.index("acehoss test config") > 0 with pytest.raises(ValueError): filedata.index("22222") assert not os.path.exists(os.path.join(td, "config")) def wait_for_condition(condition: callable, timeout: float): tm = time.time() + timeout while tm > time.time() and not condition(): time.sleep(0.01) async def wait_for_condition_async(condition: callable, timeout: float): tm = time.time() + timeout while tm > time.time() and not condition(): await asyncio.sleep(0.01)