From 33f27f44dc1cf656d1343fd63c6f6542c98fb8d8 Mon Sep 17 00:00:00 2001 From: Marcin Usielski Date: Fri, 12 Jun 2026 09:17:09 +0200 Subject: [PATCH 01/12] Terminal dimensions and buffer --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 608492ef6..317120ec8 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name='moler', - version='4.10.1', + version='4.10.50', description='Moler is a library for working with terminals, mainly for automated tests', # Required long_description=long_description, long_description_content_type='text/markdown', From a2a9d2e11a042e283492de70dd5bfba21462e25d Mon Sep 17 00:00:00 2001 From: Marcin Usielski Date: Fri, 12 Jun 2026 09:18:56 +0200 Subject: [PATCH 02/12] terminal --- moler/io/raw/terminal_no_fork.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/moler/io/raw/terminal_no_fork.py b/moler/io/raw/terminal_no_fork.py index 5f3a7eff1..36334a467 100644 --- a/moler/io/raw/terminal_no_fork.py +++ b/moler/io/raw/terminal_no_fork.py @@ -36,11 +36,11 @@ def __init__( moler_connection: Connection, cmd: str = "/bin/bash", select_timeout: float = 0.002, - read_buffer_size: int = 4096, + read_buffer_size: int = 4096000, first_prompt: str = r"[%$#\]]+", target_prompt: str = r"moler_bash#", set_prompt_cmd: str = 'unset PROMPT_COMMAND; export PS1="moler_bash# "\n', - dimensions: Tuple[int, int] = (100, 300), + dimensions: Tuple[int, int] = (1000, 3000), terminal_delayafterclose: float = 0.2, ): """ From 674dc6e51b0c6ae2b05bc898821b1176a9fd07fe Mon Sep 17 00:00:00 2001 From: Marcin Usielski Date: Fri, 19 Jun 2026 11:12:04 +0200 Subject: [PATCH 03/12] dar --- moler/asyncio_runner.py | 17 ++++++++--------- test/conftest.py | 8 +++++--- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/moler/asyncio_runner.py b/moler/asyncio_runner.py index 87710a4cb..49cce705d 100644 --- a/moler/asyncio_runner.py +++ b/moler/asyncio_runner.py @@ -35,23 +35,22 @@ current_process = psutil.Process() -if platform.system() == 'Linux': - - # Check if RLIMIT_NOFILE is available in your psutil - # noinspection PyUnresolvedReferences - (max_open_files_limit_soft, max_open_files_limit_hard) = current_process.rlimit(psutil.RLIMIT_NOFILE) -else: +if platform.system() == 'Windows': # https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setmaxstdio?view=vs-2019 (max_open_files_limit_soft, max_open_files_limit_hard) = (510, 512) # TODO: any way on Win? +else: + # Unix (Linux, Darwin, BSD, ...): read the real RLIMIT_NOFILE via stdlib resource module. + import resource + (max_open_files_limit_soft, max_open_files_limit_hard) = resource.getrlimit(resource.RLIMIT_NOFILE) def system_resources_usage(): - if platform.system() == 'Linux': - curr_fds_open = current_process.num_fds() - else: + if platform.system() == 'Windows': ofiles = current_process.open_files() osockets = current_process.connections(kind="all") curr_fds_open = len(ofiles) + len(osockets) # TODO: any better way on Win? + else: + curr_fds_open = current_process.num_fds() curr_threads_nb = threading.active_count() return curr_fds_open, curr_threads_nb diff --git a/test/conftest.py b/test/conftest.py index 8a23550c0..c1128c4c5 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -21,11 +21,13 @@ current_process = psutil.Process() -if platform.system() == 'Linux': - (max_open_files_limit_soft, max_open_files_limit_hard) = current_process.rlimit(psutil.RLIMIT_NOFILE) -else: +if platform.system() == 'Windows': # https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setmaxstdio?view=vs-2019 (max_open_files_limit_soft, max_open_files_limit_hard) = (510, 512) # TODO: any way on Win? +else: + # Unix (Linux, Darwin, BSD, ...): read the real RLIMIT_NOFILE via stdlib resource module. + import resource + (max_open_files_limit_soft, max_open_files_limit_hard) = resource.getrlimit(resource.RLIMIT_NOFILE) def system_resources_usage(): From 4355a84c928ffc38c341e7ede0ccbe4ecd83ec5e Mon Sep 17 00:00:00 2001 From: Marcin Usielski Date: Fri, 19 Jun 2026 11:18:27 +0200 Subject: [PATCH 04/12] --mccabe --- py3pytest.ini | 2 -- requirements/test.txt | 1 - setup.cfg | 4 ---- 3 files changed, 7 deletions(-) diff --git a/py3pytest.ini b/py3pytest.ini index 35e87da29..f58aaf0a9 100644 --- a/py3pytest.ini +++ b/py3pytest.ini @@ -1,4 +1,2 @@ [pytest] python_files = test_*.py py3test_*.py # testfiles running only under python3 start with py3test_ -mccabe-complexity = - *.py 10 diff --git a/requirements/test.txt b/requirements/test.txt index 285c14b67..c196b22ce 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -2,7 +2,6 @@ pycodestyle coveralls pytest -pytest-mccabe pytest-random mock pytest-cov diff --git a/setup.cfg b/setup.cfg index f84d83a91..81d63ed3e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,3 @@ -[tool:pytest] -mccabe-complexity = - *.py 10 - [pycodestyle] count = True ignore = E501 From a922dc42c5518e1cd6611396686735d01cfe5b82 Mon Sep 17 00:00:00 2001 From: Marcin Usielski Date: Fri, 19 Jun 2026 13:22:14 +0200 Subject: [PATCH 05/12] f --- moler/config/loggers.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/moler/config/loggers.py b/moler/config/loggers.py index a199d8626..4a18b2a00 100644 --- a/moler/config/loggers.py +++ b/moler/config/loggers.py @@ -290,6 +290,25 @@ def change_logging_suffix(suffix=None, logger_name=None): ) +def _is_foreign_file_handler(handler): + """ + Tell whether a FileHandler was not created by Moler and must not be reopened. + + Other libraries (e.g. pytest, since its 9.0 logging change) attach their own + FileHandlers to Moler's non-propagating loggers. pytest's default handler points + to os.devnull ('/dev/null'); reopening it with a Moler suffix/path would yield an + unwritable path like '/dev/null.suffix' and raise PermissionError. Such handlers + are owned by the other library, so Moler should leave them untouched. + + :param handler: logging.FileHandler to inspect. + :return: True if the handler points to os.devnull and should be skipped. + """ + try: + return os.path.abspath(handler.baseFilename) == os.path.abspath(os.devnull) + except (AttributeError, TypeError): + return False + + def _reopen_all_logfiles_with_new_suffix(logger_suffixes, new_suffix, logger_name): """ Reopen all log files with new suffix. @@ -308,6 +327,8 @@ def _reopen_all_logfiles_with_new_suffix(logger_suffixes, new_suffix, logger_nam written_to_log = False for handler in logger_handlers: if isinstance(handler, logging.FileHandler): + if _is_foreign_file_handler(handler): + continue new_log_full_path = _get_new_filepath_with_suffix( old_path=handler.baseFilename, old_suffix=old_suffix, @@ -367,6 +388,8 @@ def _reopen_all_logfiles_in_new_path(old_logging_path, new_logging_path): for handler in logger_handlers: if isinstance(handler, logging.FileHandler): + if _is_foreign_file_handler(handler): + continue handler.close() handler.baseFilename = handler.baseFilename.replace( old_logging_path, new_logging_path From 98a798716f23637d2fa4afddce25da2fc22627ff Mon Sep 17 00:00:00 2001 From: Marcin Usielski Date: Fri, 19 Jun 2026 13:49:02 +0200 Subject: [PATCH 06/12] redo terminal_no_fork.py --- moler/io/raw/terminal_no_fork.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/moler/io/raw/terminal_no_fork.py b/moler/io/raw/terminal_no_fork.py index 36334a467..6ab99d3bb 100644 --- a/moler/io/raw/terminal_no_fork.py +++ b/moler/io/raw/terminal_no_fork.py @@ -36,11 +36,11 @@ def __init__( moler_connection: Connection, cmd: str = "/bin/bash", select_timeout: float = 0.002, - read_buffer_size: int = 4096000, + read_buffer_size: int = 40960, first_prompt: str = r"[%$#\]]+", target_prompt: str = r"moler_bash#", set_prompt_cmd: str = 'unset PROMPT_COMMAND; export PS1="moler_bash# "\n', - dimensions: Tuple[int, int] = (1000, 3000), + dimensions: Tuple[int, int] = (100, 300), terminal_delayafterclose: float = 0.2, ): """ From cd565968ee7fb68a596d94fdc1a7e3d6b5df051c Mon Sep 17 00:00:00 2001 From: Marcin Usielski Date: Fri, 19 Jun 2026 14:14:21 +0200 Subject: [PATCH 07/12] - .travis.yml --- .travis.yml | 54 ------------------------------------------- requirements/test.txt | 1 - 2 files changed, 55 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7a5bb9fc9..000000000 --- a/.travis.yml +++ /dev/null @@ -1,54 +0,0 @@ -language: python -dist: bionic -matrix: - include: - - python: 2.7.17 - - python: 3.6.10 - - python: 3.7.3 - - python: 3.8.1 - -before_install: - - sudo apt-get -y install sshpass - -install: - - pip install -U pip - - pip install -U pycodestyle coveralls pytest pytest-mccabe pytest-random - - if [[ $TRAVIS_PYTHON_VERSION == *"3."* ]]; then pip install -U pytest-asyncio; fi - - pip install -Ur requirements.txt - -before_script: - - python -V - - cat /etc/passwd | grep home - - sudo useradd molerssh -c MolerSshTest -s /bin/bash -l -m -p `python3 -c 'import crypt; print(crypt.crypt("moler_password", "mr"))'` - - sudo useradd sshproxy -c Proxy4SshTest -s /bin/bash -l -m -p `python3 -c 'import crypt; print(crypt.crypt("proxy_password", "mr"))'` - - sudo useradd adbshell -c AdbShell4SshTest -s /bin/bash -l -m -p `python3 -c 'import crypt; print(crypt.crypt("adb_password", "mr"))'` - - cat /etc/passwd | grep home - - which scp - - sshpass -p moler_password ssh -oStrictHostKeyChecking=no molerssh@localhost pwd - - sshpass -p proxy_password ssh -oStrictHostKeyChecking=no sshproxy@localhost pwd - - sshpass -p adb_password ssh -oStrictHostKeyChecking=no adbshell@localhost pwd - - sshpass -p moler_password scp adb_simulation.sh molerssh@localhost:/home/molerssh/adb_simulation.sh - - sshpass -p moler_password ssh molerssh@localhost 'chmod +x ~/adb_simulation.sh' - - sshpass -p moler_password ssh molerssh@localhost 'echo "alias adb=~/adb_simulation.sh" >> ~/.profile' - - -script: - - python -m pycodestyle --statistics --count moler - - if [[ $TRAVIS_PYTHON_VERSION == *"2.7"* ]]; then python -m pytest --random -s -vv test; fi - - if [[ $TRAVIS_PYTHON_VERSION == *"3.6"* ]]; then python -m pytest -c py3pytest.ini --random --mccabe -s -vv; fi - - if [[ $TRAVIS_PYTHON_VERSION == *"3.7"* ]]; then coverage run -m pytest -c py3pytest.ini --random --mccabe -s -vv; fi - - if [[ $TRAVIS_PYTHON_VERSION == *"3.8"* ]]; then python -m pytest --mccabe -s -vv moler; fi - - -after_success: - - if [[ $TRAVIS_PYTHON_VERSION == *"3.7"* ]]; then coveralls; fi - - sudo userdel --remove --force molerssh - - cat /etc/passwd | grep home - -after_failure: - - echo "----------------------- LOGS -----------------------" - - whoami - - pwd - - ls -lah *.log - - export LINE='------------------------------------------------------------------------------------------------' - - for log in `ls -1 *.log`; do echo $LINE; echo ---; echo --- $log; echo ---; echo $LINE; cat $log; done diff --git a/requirements/test.txt b/requirements/test.txt index c196b22ce..7ad0c84cc 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -2,7 +2,6 @@ pycodestyle coveralls pytest -pytest-random mock pytest-cov pytest-asyncio From 6714072b1a5243e688726ab768789a3c2cea11d0 Mon Sep 17 00:00:00 2001 From: Marcin Usielski Date: Fri, 19 Jun 2026 14:22:54 +0200 Subject: [PATCH 08/12] f --- requirements/test.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/requirements/test.txt b/requirements/test.txt index 7ad0c84cc..e01ee928e 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1,10 +1,8 @@ -r base.txt pycodestyle -coveralls +coverage pytest mock -pytest-cov pytest-asyncio flake8 -pydocstyle pylint From 6efb3ff4aa1eb503b452a8da14ad98d80bc0431f Mon Sep 17 00:00:00 2001 From: Marcin Usielski Date: Fri, 19 Jun 2026 15:04:25 +0200 Subject: [PATCH 09/12] new versions of actions --- .github/workflows/ci.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 69d71cb8c..0ba5cf286 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -20,9 +20,9 @@ jobs: MOLER_DEBUG_THREADS: True PYTHON_COVERAGE: '3.14' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.os }} ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -97,9 +97,9 @@ jobs: os: [ubuntu-latest, macos-latest] python-version: [3.14] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.os }} ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies From ce6014aea401689d6d9d3bfa14eccd6c7945a79b Mon Sep 17 00:00:00 2001 From: Marcin Usielski Date: Fri, 19 Jun 2026 15:23:56 +0200 Subject: [PATCH 10/12] actions --- .github/workflows/ci.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0ba5cf286..4271498bb 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -20,7 +20,7 @@ jobs: MOLER_DEBUG_THREADS: True PYTHON_COVERAGE: '3.14' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.os }} ${{ matrix.python-version }} uses: actions/setup-python@v5 with: @@ -95,9 +95,9 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - python-version: [3.14] + python-version: ["3.14"] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.os }} ${{ matrix.python-version }} uses: actions/setup-python@v5 with: From fca70b6af3f34366aca5aca36278561092727124 Mon Sep 17 00:00:00 2001 From: Marcin Usielski Date: Mon, 22 Jun 2026 08:36:22 +0200 Subject: [PATCH 11/12] term --- moler/io/raw/terminal_no_fork.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moler/io/raw/terminal_no_fork.py b/moler/io/raw/terminal_no_fork.py index 6ab99d3bb..5f3a7eff1 100644 --- a/moler/io/raw/terminal_no_fork.py +++ b/moler/io/raw/terminal_no_fork.py @@ -36,7 +36,7 @@ def __init__( moler_connection: Connection, cmd: str = "/bin/bash", select_timeout: float = 0.002, - read_buffer_size: int = 40960, + read_buffer_size: int = 4096, first_prompt: str = r"[%$#\]]+", target_prompt: str = r"moler_bash#", set_prompt_cmd: str = 'unset PROMPT_COMMAND; export PS1="moler_bash# "\n', From 5d5e2301fc9446c1854288bbe94257beba6cac1f Mon Sep 17 00:00:00 2001 From: Marcin Usielski Date: Mon, 22 Jun 2026 09:03:30 +0200 Subject: [PATCH 12/12] 4.10.1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 317120ec8..608492ef6 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name='moler', - version='4.10.50', + version='4.10.1', description='Moler is a library for working with terminals, mainly for automated tests', # Required long_description=long_description, long_description_content_type='text/markdown',