diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 69d71cb8c..4271498bb 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@v6 - 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 @@ -95,11 +95,11 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - python-version: [3.14] + python-version: ["3.14"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - 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 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/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/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 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..e01ee928e 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -1,12 +1,8 @@ -r base.txt pycodestyle -coveralls +coverage pytest -pytest-mccabe -pytest-random mock -pytest-cov pytest-asyncio flake8 -pydocstyle pylint 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 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():