diff --git a/.ci/ansible_install.py b/.ci/ansible_install.py index ff2d3b63..1c8682db 100755 --- a/.ci/ansible_install.py +++ b/.ci/ansible_install.py @@ -16,7 +16,7 @@ batches = [ ] batches.extend( - ['docker pull %s' % (ci_lib.image_for_distro(distro),)] + ['docker pull %s' % (ci_lib.image_for_distro(distro),), 'sleep 1'] for distro in ci_lib.DISTROS ) diff --git a/.ci/ansible_tests.py b/.ci/ansible_tests.py index 4df2dc70..665e2c60 100755 --- a/.ci/ansible_tests.py +++ b/.ci/ansible_tests.py @@ -37,9 +37,6 @@ with ci_lib.Fold('docker_setup'): with ci_lib.Fold('job_setup'): - # Don't set -U as that will upgrade Paramiko to a non-2.6 compatible version. - run("pip install -q ansible==%s", ci_lib.ANSIBLE_VERSION) - os.chdir(TESTS_DIR) os.chmod('../data/docker/mitogen__has_sudo_pubkey.key', int('0600', 7)) @@ -69,8 +66,6 @@ with ci_lib.Fold('job_setup'): run("sudo apt-get update") run("sudo apt-get install -y sshpass") - run("bash -c 'sudo ln -vfs /usr/lib/python2.7/plat-x86_64-linux-gnu/_sysconfigdata_nd.py /usr/lib/python2.7 || true'") - run("bash -c 'sudo ln -vfs /usr/lib/python2.7/plat-x86_64-linux-gnu/_sysconfigdata_nd.py $VIRTUAL_ENV/lib/python2.7 || true'") with ci_lib.Fold('ansible'): playbook = os.environ.get('PLAYBOOK', 'all.yml') diff --git a/.ci/azure-pipelines-steps.yml b/.ci/azure-pipelines-steps.yml index 07358c0f..43eda90f 100644 --- a/.ci/azure-pipelines-steps.yml +++ b/.ci/azure-pipelines-steps.yml @@ -14,17 +14,25 @@ steps: # stuff into. The virtualenv can probably be removed again, but this was a # hard-fought battle and for now I am tired of this crap. - script: | - # need wheel before building virtualenv because of bdist_wheel and setuptools deps - # Mac's System Integrity Protection prevents symlinking /usr/bin - # and Azure isn't allowing disabling it apparently: https://developercommunityapi.westus.cloudapp.azure.com/idea/558702/allow-disabling-sip-on-microsoft-hosted-macos-agen.html - # the || will activate when running python3 tests # TODO: get python3 tests passing - (sudo ln -fs /usr/bin/python$(python.version) /usr/bin/python && - /usr/bin/python -m pip install -U pip wheel setuptools && - /usr/bin/python -m pip install -U virtualenv && - /usr/bin/python -m virtualenv /tmp/venv -p /usr/bin/python$(python.version)) || - (sudo /usr/bin/python$(python.version) -m pip install -U pip wheel setuptools && - /usr/bin/python$(python.version) -m venv /tmp/venv) + set -o errexit + set -o nounset + set -o pipefail + set -x + + # Ensure virtualenv is in system-wide $PATH on OSX, for Ansible test tasks + # that use it, e.g. regression/issue_152__virtualenv_python_fails.yml + if [[ -d /Library/Frameworks/Python.framework/Versions/2.7/bin ]]; then + echo $PATH + which python + python -m pip install -U pip virtualenv + ln -s /Library/Frameworks/Python.framework/Versions/2.7/bin/virtualenv /usr/local/bin/virtualenv + virtualenv --version + fi + + # Create virtualenv using desired Python version, to run the testcases + python$(python.version) -m pip install -U pip wheel setuptools virtualenv + python$(python.version) -m virtualenv /tmp/venv -p python$(python.version) echo "##vso[task.prependpath]/tmp/venv/bin" displayName: activate venv @@ -37,3 +45,5 @@ steps: - script: .ci/$(MODE)_tests.py displayName: "Run $(MODE)_tests.py" + env: + NOCOVERAGE: 1 diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml index c23974df..a537b0f5 100644 --- a/.ci/azure-pipelines.yml +++ b/.ci/azure-pipelines.yml @@ -13,10 +13,10 @@ jobs: strategy: matrix: Mito27_27: - python.version: '2.7.18' + python.version: '2.7' MODE: mitogen Ans288_27: - python.version: '2.7.18' + python.version: '2.7' MODE: localhost_ansible VER: 2.8.8 diff --git a/.ci/ci_lib.py b/.ci/ci_lib.py index 84db7a94..8c7716d8 100644 --- a/.ci/ci_lib.py +++ b/.ci/ci_lib.py @@ -80,12 +80,28 @@ if 'TRAVIS_HOME' in os.environ: # ----------------- def _argv(s, *args): + """Interpolate a command line using *args, return an argv style list. + + >>> _argv('git commit -m "Use frobnicate 2.0 (fixes #%d)"', 1234) + ['git', commit', '-m', 'Use frobnicate 2.0 (fixes #1234)'] + """ if args: s %= args return shlex.split(s) def run(s, *args, **kwargs): + """ Run a command, with arguments, and print timing information + + >>> rc = run('echo "%s %s"', 'foo', 'bar') + Running: ['/usr/bin/time', '--', 'echo', 'foo bar'] + foo bar + 0.00user 0.00system 0:00.00elapsed ?%CPU (0avgtext+0avgdata 1964maxresident)k + 0inputs+0outputs (0major+71minor)pagefaults 0swaps + Finished running: ['/usr/bin/time', '--', 'echo', 'foo bar'] + >>> rc + 0 + """ argv = ['/usr/bin/time', '--'] + _argv(s, *args) print('Running: %s' % (argv,)) try: @@ -98,12 +114,36 @@ def run(s, *args, **kwargs): return ret -def run_batches(batches): - combine = lambda batch: 'set -x; ' + (' && '.join( +def combine(batch): + """ + >>> combine(['ls -l', 'echo foo']) + 'set -x; ( ls -l; ) && ( echo foo; )' + """ + return 'set -x; ' + (' && '.join( '( %s; )' % (cmd,) for cmd in batch )) + +def run_batches(batches): + """ Run shell commands grouped into batches, showing an execution trace. + + Raise AssertionError if any command has exits with a non-zero status. + + >>> run_batches([['echo foo', 'true']]) + + echo foo + foo + + true + >>> run_batches([['true', 'echo foo'], ['false']]) + + true + + echo foo + foo + + false + Traceback (most recent call last): + File "...", line ..., in + File "...", line ..., in run_batches + AssertionError + """ procs = [ subprocess.Popen(combine(batch), shell=True) for batch in batches @@ -112,12 +152,28 @@ def run_batches(batches): def get_output(s, *args, **kwargs): + """ + Print and run command line s, %-interopolated using *args. Return stdout. + + >>> s = get_output('echo "%s %s"', 'foo', 'bar') + Running: ['echo', 'foo bar'] + >>> s + 'foo bar\n' + """ argv = _argv(s, *args) print('Running: %s' % (argv,)) return subprocess.check_output(argv, **kwargs) def exists_in_path(progname): + """ + Return True if proganme exists in $PATH. + + >>> exists_in_path('echo') + True + >>> exists_in_path('kwyjibo') # Only found in North American cartoons + False + """ return any(os.path.exists(os.path.join(dirname, progname)) for dirname in os.environ['PATH'].split(os.pathsep)) @@ -132,6 +188,18 @@ class TempDir(object): class Fold(object): + """ + Bracket a section of stdout with travis_fold markers. + + This allows the section to be collapsed or expanded in Travis CI web UI. + + >>> with Fold('stage 1'): + ... print('Frobnicate the frobnitz') + ... + travis_fold:start:stage 1 + Frobnicate the frobnitz + travis_fold:end:stage 1 + """ def __init__(self, name): self.name = name @@ -171,6 +239,8 @@ os.environ['PYTHONPATH'] = '%s:%s' % ( ) def get_docker_hostname(): + """Return the hostname where the docker daemon is running. + """ url = os.environ.get('DOCKER_HOST') if url in (None, 'http+docker://localunixsocket'): return 'localhost' @@ -180,10 +250,34 @@ def get_docker_hostname(): def image_for_distro(distro): - return 'mitogen/%s-test' % (distro.partition('-')[0],) + """Return the container image name or path for a test distro name. + + The returned value is suitable for use with `docker pull`. + + >>> image_for_distro('centos5') + 'public.ecr.aws/n5z0e8q9/centos5-test' + >>> image_for_distro('centos5-something_custom') + 'public.ecr.aws/n5z0e8q9/centos5-test' + """ + return 'public.ecr.aws/n5z0e8q9/%s-test' % (distro.partition('-')[0],) def make_containers(name_prefix='', port_offset=0): + """ + >>> import pprint + >>> BASE_PORT=2200; DISTROS=['debian', 'centos6'] + >>> pprint.pprint(make_containers()) + [{'distro': 'debian', + 'hostname': 'localhost', + 'name': 'target-debian-1', + 'port': 2201, + 'python_path': '/usr/bin/python'}, + {'distro': 'centos6', + 'hostname': 'localhost', + 'name': 'target-centos6-2', + 'port': 2202, + 'python_path': '/usr/bin/python'}] + """ docker_hostname = get_docker_hostname() firstbit = lambda s: (s+'-').split('-')[0] secondbit = lambda s: (s+'-').split('-')[1] @@ -256,6 +350,14 @@ def get_interesting_procs(container_name=None): def start_containers(containers): + """Run docker containers in the background, with sshd on specified ports. + + >>> containers = start_containers([ + ... {'distro': 'debian', 'hostname': 'localhost', + ... 'name': 'target-debian-1', 'port': 2201, + ... 'python_path': '/usr/bin/python'}, + ... ]) + """ if os.environ.get('KEEP'): return diff --git a/.ci/localhost_ansible_tests.py b/.ci/localhost_ansible_tests.py index b4d6a542..6d7bef0d 100755 --- a/.ci/localhost_ansible_tests.py +++ b/.ci/localhost_ansible_tests.py @@ -20,12 +20,15 @@ with ci_lib.Fold('unit_tests'): with ci_lib.Fold('job_setup'): - # Don't set -U as that will upgrade Paramiko to a non-2.6 compatible version. - run("pip install -q virtualenv ansible==%s", ci_lib.ANSIBLE_VERSION) - os.chmod(KEY_PATH, int('0600', 8)) + # NOTE: sshpass v1.06 causes errors so pegging to 1.05 -> "msg": "Error when changing password","out": "passwd: DS error: eDSAuthFailed\n", + # there's a checksum error with "brew install http://git.io/sshpass.rb" though, so installing manually if not ci_lib.exists_in_path('sshpass'): - run("brew install http://git.io/sshpass.rb") + os.system("curl -O -L https://sourceforge.net/projects/sshpass/files/sshpass/1.05/sshpass-1.05.tar.gz && \ + tar xvf sshpass-1.05.tar.gz && \ + cd sshpass-1.05 && \ + ./configure && \ + sudo make install") with ci_lib.Fold('machine_prep'): diff --git a/.ci/prep_azure.py b/.ci/prep_azure.py index 344564e8..40cebe3a 100755 --- a/.ci/prep_azure.py +++ b/.ci/prep_azure.py @@ -43,12 +43,4 @@ if ci_lib.have_apt(): .format(pv=os.environ['PYTHONVERSION']) ]) - -if ci_lib.have_docker(): - batches.extend( - ['docker pull %s' % (ci_lib.image_for_distro(distro),)] - for distro in ci_lib.DISTROS - ) - - ci_lib.run_batches(batches) diff --git a/.travis.yml b/.travis.yml index 877f9ca3..bb50dc51 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,9 @@ cache: - directories: - /home/travis/virtualenv +env: + - NOCOVERAGE=1 + install: - grep -Erl git-lfs\|couchdb /etc/apt | sudo xargs rm -v - .ci/${MODE}_install.py diff --git a/LICENSE b/LICENSE index 70e43a94..62ef15de 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2019, David Wilson +Copyright 2021, the Mitogen authors Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/README.md b/README.md index c7d8b03f..85beacb4 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ ![](https://i.imgur.com/eBM6LhJ.gif) -[![Total alerts](https://img.shields.io/lgtm/alerts/g/dw/mitogen.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/dw/mitogen/alerts/) +[![Total alerts](https://img.shields.io/lgtm/alerts/g/mitogen-hq/mitogen.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/mitogen-hq/mitogen/alerts/) -[![Build Status](https://travis-ci.org/dw/mitogen.svg?branch=master)](https://travis-ci.org/dw/mitogen) +[![Build Status](https://api.travis-ci.com/mitogen-hq/mitogen.svg?branch=master)](https://api.travis-ci.com/mitogen-hq/mitogen) -[![Pipelines Status](https://dev.azure.com/dw-mitogen/Mitogen/_apis/build/status/dw.mitogen?branchName=master)](https://dev.azure.com/dw-mitogen/Mitogen/_build/latest?definitionId=1?branchName=master) +[![Pipelines Status](https://dev.azure.com/mitogen-hq/mitogen/_apis/build/status/mitogen-hq.mitogen?branchName=master)](https://dev.azure.com/mitogen-hq/mitogen/_build/latest?definitionId=1&branchName=master) diff --git a/docs/ansible_detailed.rst b/docs/ansible_detailed.rst index 12d89c9c..c9bbcf51 100644 --- a/docs/ansible_detailed.rst +++ b/docs/ansible_detailed.rst @@ -145,7 +145,7 @@ Testimonials Noteworthy Differences ---------------------- -* Ansible 2.3-2.8 are supported along with Python 2.6, 2.7, 3.6 and 3.7. Verify +* Ansible 2.3-2.9 are supported along with Python 2.6, 2.7, 3.6 and 3.7. Verify your installation is running one of these versions by checking ``ansible --version`` output. diff --git a/docs/changelog.rst b/docs/changelog.rst index 1bc87342..cb7360a2 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -14,18 +14,23 @@ Release Notes } +To avail of fixes in an unreleased version, please download a ZIP file +`directly from GitHub `_. v0.2.10 (unreleased) -------------------- -To avail of fixes in an unreleased version, please download a ZIP file -`directly from GitHub `_. - +* :gh:issue:`597` mitogen does not support Ansible 2.8 Python interpreter detection +* :gh:issue:`655` wait_for_connection gives errors +* :gh:issue:`672` cannot perform relative import error +* :gh:issue:`673` mitogen fails on RHEL8 server with bash /usr/bin/python: No such file or directory +* :gh:issue:`676` mitogen fail to run playbook without “/usr/bin/python” on target host +* :gh:issue:`716` fetch fails with "AttributeError: 'ShellModule' object has no attribute 'tmpdir'" * :gh:issue:`756` ssh connections with `check_host_keys='accept'` would timeout, when using recent OpenSSH client versions. -* :gh:issue:`758` fix initilialisation of callback plugins in test suite, to - to address a `KeyError` in +* :gh:issue:`758` fix initilialisation of callback plugins in test suite, to address a `KeyError` in :method:`ansible.plugins.callback.CallbackBase.v2_runner_on_start` +* :gh:issue:`775` Add msvcrt to the default module deny list v0.2.9 (2019-11-02) diff --git a/docs/conf.py b/docs/conf.py index 1a6a117b..54e3a5c7 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -7,7 +7,7 @@ import mitogen VERSION = '%s.%s.%s' % mitogen.__version__ author = u'Network Genomics' -copyright = u'2019, Network Genomics' +copyright = u'2021, the Mitogen authors' exclude_patterns = ['_build', '.venv'] extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinxcontrib.programoutput', 'domainrefs'] diff --git a/mitogen/__init__.py b/mitogen/__init__.py index f18c5a90..3af0e6a7 100644 --- a/mitogen/__init__.py +++ b/mitogen/__init__.py @@ -35,7 +35,7 @@ be expected. On the slave, it is built dynamically during startup. #: Library version as a tuple. -__version__ = (0, 2, 9) +__version__ = (0, 2, 10, 'rc', 1) #: This is :data:`False` in slave contexts. Previously it was used to prevent diff --git a/mitogen/core.py b/mitogen/core.py index 4dd44925..802ac45e 100644 --- a/mitogen/core.py +++ b/mitogen/core.py @@ -1269,6 +1269,13 @@ class Importer(object): # a negative round-trip. 'builtins', '__builtin__', + + # On some Python releases (e.g. 3.8, 3.9) the subprocess module tries + # to import of this Windows-only builtin module. + 'msvcrt', + + # Python 2.x module that was renamed to _thread in 3.x. + # This entry avoids a roundtrip on 2.x -> 3.x. 'thread', # org.python.core imported by copy, pickle, xml.sax; breaks Jython, but @@ -3860,7 +3867,7 @@ class ExternalContext(object): else: core_src_fd = self.config.get('core_src_fd', 101) if core_src_fd: - fp = os.fdopen(core_src_fd, 'rb', 1) + fp = os.fdopen(core_src_fd, 'rb', 0) try: core_src = fp.read() # Strip "ExternalContext.main()" call from last line. diff --git a/setup.cfg b/setup.cfg index bf012c6b..08919787 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,6 @@ +[bdist_wheel] +universal=1 + [coverage:run] branch = true source = diff --git a/setup.py b/setup.py index c3257996..bd105147 100644 --- a/setup.py +++ b/setup.py @@ -37,29 +37,46 @@ def grep_version(): for line in fp: if line.startswith('__version__'): _, _, s = line.partition('=') - return '.'.join(map(str, eval(s))) + return '%i.%i.%i%s%i' % eval(s) + + +def long_description(): + here = os.path.dirname(__file__) + readme_path = os.path.join(here, 'README.md') + with open(readme_path) as fp: + readme = fp.read() + return readme setup( name = 'mitogen', version = grep_version(), description = 'Library for writing distributed self-replicating programs.', + long_description = long_description(), + long_description_content_type='text/markdown', author = 'David Wilson', license = 'New BSD', - url = 'https://github.com/dw/mitogen/', + url = 'https://github.com/mitogen-hq/mitogen/', packages = find_packages(exclude=['tests', 'examples']), + python_requires='>=2.4, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4', zip_safe = False, classifiers = [ 'Environment :: Console', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: BSD License', + 'Operating System :: MacOS :: MacOS X', 'Operating System :: POSIX', 'Programming Language :: Python', + 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.4', 'Programming Language :: Python :: 2.5', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: Implementation :: CPython', 'Topic :: System :: Distributed Computing', 'Topic :: System :: Systems Administration', diff --git a/tests/ansible/integration/playbook_semantics/delegate_to.yml b/tests/ansible/integration/playbook_semantics/delegate_to.yml index 4d4da028..23b7168c 100644 --- a/tests/ansible/integration/playbook_semantics/delegate_to.yml +++ b/tests/ansible/integration/playbook_semantics/delegate_to.yml @@ -51,10 +51,14 @@ shell: whoami > /tmp/delegate_to.yml.txt delegate_to: localhost become: true + tags: + - requires_local_sudo - name: "delegate_to, sudo" assert: that: "lookup('file', '/tmp/delegate_to.yml.txt') == 'root'" + tags: + - requires_local_sudo - name: "delegate_to, sudo" file: @@ -62,6 +66,8 @@ state: absent delegate_to: localhost become: true + tags: + - requires_local_sudo # @@ -71,10 +77,14 @@ shell: whoami > /tmp/delegate_to.yml.txt connection: local become: true + tags: + - requires_local_sudo - name: "connection:local, sudo" assert: that: "lookup('file', '/tmp/delegate_to.yml.txt') == 'root'" + tags: + - requires_local_sudo - name: "connection:local, sudo" file: @@ -82,3 +92,5 @@ state: absent connection: local become: true + tags: + - requires_local_sudo diff --git a/tests/ansible/regression/issue_152__local_action_wrong_interpreter.yml b/tests/ansible/regression/issue_152__local_action_wrong_interpreter.yml index 5de67ab9..8a8f0068 100644 --- a/tests/ansible/regression/issue_152__local_action_wrong_interpreter.yml +++ b/tests/ansible/regression/issue_152__local_action_wrong_interpreter.yml @@ -15,7 +15,7 @@ content: | #!/bin/bash export CUSTOM_INTERPRETER=1 - exec python2.7 "$@" + exec python "$@" - custom_python_detect_environment: vars: diff --git a/tests/ansible/requirements.txt b/tests/ansible/requirements.txt index c0386cd8..2c3c87c8 100644 --- a/tests/ansible/requirements.txt +++ b/tests/ansible/requirements.txt @@ -1,4 +1,4 @@ paramiko==2.3.2 # Last 2.6-compat version. hdrhistogram==0.6.1 PyYAML==3.11; python_version < '2.7' -PyYAML==3.13; python_version >= '2.7' +PyYAML==5.3.1; python_version >= '2.7' # Latest release (Jan 2021) diff --git a/tests/module_finder_test.py b/tests/module_finder_test.py index fc3a17de..ac3bfe6c 100644 --- a/tests/module_finder_test.py +++ b/tests/module_finder_test.py @@ -308,7 +308,6 @@ if sys.version_info > (2, 6): # AttributeError: module 'html.parser' has no attribute # 'HTMLParseError' # - import pkg_resources._vendor.six from django.utils.six.moves import html_parser as _html_parser _html_parser.HTMLParseError = Exception diff --git a/tests/requirements.txt b/tests/requirements.txt index bbcdc7cc..76e6545d 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -3,7 +3,7 @@ coverage==4.5.1 Django==1.6.11 # Last version supporting 2.6. mock==2.0.0 pytz==2018.5 -cffi==1.11.2 # Random pin to try and fix pyparser==2.18 not having effect +cffi==1.14.3 # Random pin to try and fix pyparser==2.18 not having effect pycparser==2.18 # Last version supporting 2.6. faulthandler==3.1; python_version < '3.3' # used by testlib pytest-catchlog==1.2.2 diff --git a/tests/testlib.py b/tests/testlib.py index ace8f0a2..ee76a26d 100644 --- a/tests/testlib.py +++ b/tests/testlib.py @@ -103,6 +103,18 @@ if hasattr(subprocess.Popen, 'terminate'): Popen__terminate = subprocess.Popen.terminate +def threading__thread_is_alive(thread): + """Return whether the thread is alive (Python version compatibility shim). + + On Python >= 3.8 thread.isAlive() is deprecated (removed in Python 3.9). + On Python <= 2.5 thread.is_alive() isn't present (added in Python 2.6). + """ + try: + return thread.is_alive() + except AttributeError: + return thread.isAlive() + + def wait_for_port( host, port, @@ -334,7 +346,9 @@ class TestCase(unittest2.TestCase): for thread in threading.enumerate(): name = thread.getName() # Python 2.4: enumerate() may return stopped threads. - assert (not thread.isAlive()) or name in self.ALLOWED_THREADS, \ + assert \ + not threading__thread_is_alive(thread) \ + or name in self.ALLOWED_THREADS, \ 'Found thread %r still running after tests.' % (name,) counts[name] = counts.get(name, 0) + 1 diff --git a/tests/utils_test.py b/tests/utils_test.py index a70b23dc..b5204a3c 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -31,14 +31,14 @@ class RunWithRouterTest(testlib.TestCase): def test_run_with_broker(self): router = mitogen.utils.run_with_router(func0) self.assertIsInstance(router, mitogen.master.Router) - self.assertFalse(router.broker._thread.isAlive()) + self.assertFalse(testlib.threading__thread_is_alive(router.broker._thread)) class WithRouterTest(testlib.TestCase): def test_with_broker(self): router = func() self.assertIsInstance(router, mitogen.master.Router) - self.assertFalse(router.broker._thread.isAlive()) + self.assertFalse(testlib.threading__thread_is_alive(router.broker._thread)) class Dict(dict): pass diff --git a/tox.ini b/tox.ini index 8a4ef364..fd4b14f9 100644 --- a/tox.ini +++ b/tox.ini @@ -1,44 +1,89 @@ +# This file is a local convenience. It is not a substitute for the full CI +# suite, and does not cover the full range of Python versions for Mitogen. + +# I use this on Ubuntu 20.04, with the following additions +# +# sudo add-apt-repository ppa:deadsnakes/ppa +# sudo apt update +# sudo apt install python3.5 python3.6 python3.7 python3.9 tox libsasl2-dev libldap2-dev libssl-dev ssh-pass + +# Last version to support each python version +# +# tox vir'env pip ansible coverage +# ========== ======== ======== ======== ======== ======== +# python2.4 1.4 1.8 1.1 ??? +# python2.5 1.6.1 1.9.1 1.3.1 ??? +# python2.6 2.9.1 15.2.0 9.0.3 2.6.20 4.5.4 + [tox] envlist = init, - py26, - py27, - py35, - py36, - py37, + py{27,36,39}-mode_ansible, + py{27,36,39}-mode_mitogen, + py{27,36,39}-mode_mitogen-distro_centos7, report, +requires = + tox-factor [testenv] -usedevelop = True -deps = - -r{toxinidir}/dev_requirements.txt - -r{toxinidir}/tests/ansible/requirements.txt - +basepython = + py26: python2.6 + py27: python2.7 + py36: python3.6 + py37: python3.7 + py38: python3.8 + py39: python3.9 +commands_pre = + mode_ansible: {toxinidir}/.ci/ansible_install.py + mode_debops_common: {toxinidir}/.ci/debops_common_install.py + mode_mitogen: {toxinidir}/.ci/mitogen_install.py commands = - {posargs:bash run_tests} -whitelist_externals = - bash + mode_ansible: {toxinidir}/.ci/ansible_tests.py + mode_debops_common: {toxinidir}/.ci/debops_common_tests.py + mode_mitogen: {toxinidir}/.ci/mitogen_tests.py +passenv = + ANSIBLE_* + HOME setenv = + ANSIBLE_SKIP_TAGS = requires_local_sudo NOCOVERAGE_ERASE = 1 NOCOVERAGE_REPORT = 1 + ansible2.3: VER=2.3.3.0 + ansible2.4: VER=2.4.6.0 + ansible2.8: VER=2.8.3 + ansible2.9: VER=2.9.6 + ansible2.10: VER=2.10.0 + distro_centos5: DISTRO=centos5 + distro_centos6: DISTRO=centos6 + distro_centos7: DISTRO=centos7 + distro_debian: DISTRO=debian + distro_debianpy3: DISTRO=debian-py3 + distros_centos5: DISTROS=centos5 + distros_debian: DISTROS=debian + mode_ansible: MODE=ansible + mode_debops_common: MODE=debops_common + mode_mitogen: MODE=mitogen + strategy_linear: STRATEGY=linear [testenv:init] +basepython = python3 commands = coverage erase deps = - coverage + coverage==4.5.4 [testenv:report] +basepython = python3 commands = coverage html echo "coverage report is at file://{toxinidir}/htmlcov/index.html" deps = - coverage + coverage==4.5.4 whitelist_externals = echo [testenv:docs] -basepython = python +basepython = python3 changedir = docs commands = sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html