Commit e77218f9 authored by Jason Madden's avatar Jason Madden Committed by GitHub

Merge pull request #1414 from gevent/issue1349

Rework testrunner and tests to support resources and disabling network tests
parents b5e9c638 8d2cffcb
......@@ -98,7 +98,7 @@ ignored-classes=SSLContext, SSLSocket, greenlet, Greenlet, parent, dead
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis. It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=gevent._corecffi,gevent.os,os,greenlet,threading,gevent.libev.corecffi,gevent.socket,gevent.core
ignored-modules=gevent._corecffi,gevent.os,os,greenlet,threading,gevent.libev.corecffi,gevent.socket,gevent.core,gevent.testing.support
[DESIGN]
max-attributes=12
......
......@@ -19,7 +19,7 @@ env:
- CCACHE_NOHASHDIR=true
- BUILD_LIBS=$HOME/.libs
- CFLAGS="-g -pipe"
- CPPFLAGS="-I$BUILD_LIBS/include"
- CPPFLAGS="-I$BUILD_LIBS/include -DEV_VERIFY=3"
- LDFLAGS="-L$BUILD_LIBS/lib"
- LD_LIBRARY_PATH="$BUILD_LIBS/lib"
......@@ -42,7 +42,7 @@ env:
- TRAVIS_PYTHON_VERSION=3.7
- TRAVIS_PYTHON_VERSION=pypy2.7
- TRAVIS_PYTHON_VERSION=pypy3.6
- TRAVIS_PYTHON_VERSION=2.7 EMBED=0
- TRAVIS_PYTHON_VERSION=2.7 GEVENTSETUP_EMBED=0
matrix:
fast_finish: true
......@@ -110,7 +110,7 @@ jobs:
# so that we don't have to use build isolation and can better use the cache;
# Note that we can't use -U for cffi and greenlet on PyPy.
- &build-gevent-deps pip install -U setuptools wheel && pip install cffi cython greenlet
- GEVENTSETUP_EV_VERIFY=3 pip install --no-build-isolation .[test,events,dnspython]
- pip install --no-build-isolation .[test]
script: ccache -s
before_script: true
after_success: true
......@@ -125,13 +125,13 @@ jobs:
- <<: *build-gevent
env: TRAVIS_PYTHON_VERSION=pypy3.6
- <<: *build-gevent
env: TRAVIS_PYTHON_VERSION=2.7 EMBED=0
env: TRAVIS_PYTHON_VERSION=2.7 GEVENTSETUP_EMBED=0
install:
# Install the Python runtime
- *build-gevent-python
- *build-gevent-deps
# Install the C dependencies to a known location. This is used
# to test "no embed" cases. It might seem like it would prime
# to test 'no embed' cases. It might seem like it would prime
# the CCache for when we *do* embed if we did it as part of the generic build stage,
# but overall it just seems to slow things down. The Travis caching and CCache is not working as
# well as we would hope.
......@@ -142,7 +142,7 @@ jobs:
# delete to avoid repacking the archive
- rm -rf $BUILD_LIBS/share/man/
- ls -l $BUILD_LIBS $BUILD_LIBS/lib
- EMBED=0 pip install --no-build-isolation .[test,events,dnspython]
- pip install --no-build-isolation .[test]
# Ok, now we switch to the test stage. These are all in addition
# to the jobs created by the matrix (and should override the `script` command).
......@@ -236,16 +236,19 @@ jobs:
name: pure37
# 2.7, no-embed. Run the tests that exercise the libraries we linked to.
# 2.7, no-embed. Run the tests that exercise the libraries we
# linked to.
# XXX: The CFFI backends, as exercised by test-lib[eu]v-jobs
# don't really support this!
- <<: *test-ares-jobs
env: TRAVIS_PYTHON_VERSION=2.7 EMBED=0
env: TRAVIS_PYTHON_VERSION=2.7 GEVENTSETUP_EMBED=0
name: ares27-noembed
- <<: *test-libuv-jobs
env: TRAVIS_PYTHON_VERSION=2.7 EMBED=0
name: libuv27-noembed
- <<: *test-libev-jobs
env: TRAVIS_PYTHON_VERSION=2.7 EMBED=0
name: libev27-noembed
# - <<: *test-libuv-jobs
# env: TRAVIS_PYTHON_VERSION=2.7 GEVENTSETUP_EMBED=0
# name: libuv27-noembed
# - <<: *test-libev-jobs
# env: TRAVIS_PYTHON_VERSION=2.7 GEVENTSETUP_EMBED=0
# name: libev27-noembed
# PyPy 2.7
- <<: *test-dnspython-jobs
......
......@@ -86,6 +86,10 @@
-a``, respectively. The remainder of the ``Makefile`` contained
Travis CI commands that have been moved to ``.travis.yml``.
- Deprecate the ``EMBED`` and ``LIBEV_EMBED``, etc, build-time environment
variables. Instead, use ``GEVENTSETUP_EMBED`` and
``GEVENTSETUP_EMBED_LIBEV``. See :issue:`1402`.
1.4.0 (2019-01-04)
==================
......
Basics
======
========================
Contributing to gevent
========================
Please see `contribution-guide.org <http://www.contribution-guide.org/>`_ for
general details on what we expect from contributors. Thanks!
Please see `contribution-guide.org
<http://www.contribution-guide.org/>`_ for general details on what we
need from contributors.
If you're filing a bug that needs a code example, please be sure it's
a `Short, Self Contained, Correct, Example <http://sscce.org>`_
Thanks!
gevent-specific details
=======================
For information on building gevent, and adding and updating test
cases, see `the development documentation
<http://www.gevent.org/contents.html#contents-developing>`_.
There are a number of systems in place to help ensure gevent is of the
highest possible quality:
- Builds on Travis CI automatically submit updates to `coveralls.io`_ to
monitor test coverage. Pull requests that don't feature adequate test
coverage will be automatically failed.
.. image:: https://coveralls.io/repos/gevent/gevent/badge.svg?branch=master&service=github
:target: https://coveralls.io/github/gevent/gevent?branch=master
- Likewise, builds on Travis CI will automatically submit updates to
`landscape.io`_ to monitor code health (adherence to PEP8, absence of
common code smells, etc). Pull requests that decrease code health will
be automatically failed.
.. image:: https://landscape.io/github/gevent/gevent/master/landscape.svg?style=flat
:target: https://landscape.io/github/gevent/gevent/master
:alt: Code Health
- A test suite is run for every push and pull request submitted. Travis
CI is used to test on Linux, and `AppVeyor`_ runs the builds on
Windows. Pull requests with tests that don't pass will be
automatically failed.
.. image:: https://travis-ci.org/gevent/gevent.svg?branch=master
:target: https://travis-ci.org/gevent/gevent
.. image:: https://ci.appveyor.com/api/projects/status/q4kl21ng2yo2ixur?svg=true
:target: https://ci.appveyor.com/project/denik/gevent
.. image:: https://travis-ci.org/gevent/gevent.svg?branch=master
:target: https://travis-ci.org/gevent/gevent
.. image:: https://ci.appveyor.com/api/projects/status/q4kl21ng2yo2ixur?svg=true
:target: https://ci.appveyor.com/project/denik/gevent
- Builds on Travis CI automatically submit updates to `coveralls.io`_ to
monitor test coverage. Pull requests that don't feature adequate test
coverage will be automatically failed.
.. image:: https://coveralls.io/repos/gevent/gevent/badge.svg?branch=master&service=github
:target: https://coveralls.io/github/gevent/gevent?branch=master
- Travis CI builds also run `pylint
<https://pylint.readthedocs.io/en/latest/>`_ to enforce code quality
conventions (PEP8 compliance and the like).
.. _landscape.io: https://landscape.io/github/gevent/gevent
.. _coveralls.io: https://coveralls.io/github/gevent/gevent
.. _AppVeyor: https://ci.appveyor.com/project/denik/gevent
......
......@@ -70,7 +70,12 @@ def glob_many(*globs):
## Configuration
def _parse_environ(key):
# Environment variables that are intended to be used outside of our own
# CI should be documented in ``installing_from_source.rst``.
# They should all begin with ``GEVENTSETUP_``
def _bool_from_environ(key):
value = os.environ.get(key)
if not value:
return
......@@ -82,34 +87,50 @@ def _parse_environ(key):
raise ValueError('Environment variable %r has invalid value %r. '
'Please set it to 1, 0 or an empty string' % (key, value))
IGNORE_CFFI = _parse_environ("GEVENT_NO_CFFI_BUILD")
SKIP_LIBUV = _parse_environ('GEVENT_NO_LIBUV_BUILD')
IGNORE_CFFI = _bool_from_environ("GEVENTSETUP_NO_CFFI_BUILD")
def _get_config_value(key, defkey, path=None):
def _check_embed(key, defkey, path=None, warn=False):
"""
Find a boolean value, configured in the environment at *key* or
*defkey* (typically, *defkey* will be shared by several calls). If
those don't exist, then check for the existence of *path* and return
that (if path is given)
"""
value = _parse_environ(key)
value = _bool_from_environ(key)
if value is None:
value = _parse_environ(defkey)
value = _bool_from_environ(defkey)
if value is not None:
if warn:
print("Warning: gevent setup: legacy environment key %s or %s found"
% (key, defkey))
return value
return os.path.exists(path) if path is not None else False
return os.path.exists(path) if path is not None else None
def should_embed(dep_name):
"""
Check the configuration for the dep_name and see if it
should be embedded. Environment keys are derived from the
dep name: libev becomes LIBEV_EMBED and c-ares becomes CARES_EMBED.
Check the configuration for the dep_name and see if it should be
embedded. Environment keys are derived from the dep name: libev
becomes GEVENTSETUP_EMBED_LIBEV and c-ares becomes
GEVENTSETUP_EMBED_CARES.
"""
path = dep_abspath(dep_name)
defkey = 'EMBED'
key = dep_name.replace('-', '').upper() + '_' + defkey
normal_dep_key = dep_name.replace('-', '').upper()
default_key = 'GEVENTSETUP_EMBED'
dep_key = default_key + '_' + normal_dep_key
result = _check_embed(dep_key, default_key)
if result is not None:
return result
# Not defined, check legacy settings, and fallback to the path
legacy_default_key = 'EMBED'
legacy_dep_key = normal_dep_key + '_' + legacy_default_key
return _get_config_value(key, defkey, path)
return _check_embed(legacy_dep_key, legacy_default_key, path,
warn=True)
## Headers
......
......@@ -7,6 +7,10 @@ environment:
CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\appveyor\\run_with_env.cmd"
# Use a fixed hash seed for reproducability
PYTHONHASHSEED: 8675309
# Disable tests that use external network resources;
# too often we get failures to resolve DNS names or failures
# to connect on AppVeyor.
GEVENTTEST_USE_RESOURCES: "-network"
matrix:
......@@ -140,7 +144,7 @@ cache:
build_script:
# Build the compiled extension
# pip 19.1 (exactly) won't work; but 19.1.1 should with --no-use-pep517
- if not "%GWHEEL_ONLY%"=="true" %PYEXE% -m pip install -U --upgrade-strategy=eager -r dev-requirements.txt
- if not "%GWHEEL_ONLY%"=="true" %PYEXE% -m pip install -r dev-requirements.txt
test_script:
# Run the project tests
......@@ -154,7 +158,7 @@ after_test:
# https://ci.appveyor.com/project/denik/gevent/builds/23810605/job/83aw4u67artt002b#L602
# So we violate DRY and repeate some requirements in order to use
# --no-build-isolation
- "%CMD_IN_ENV% %PYEXE% -m pip install -U --upgrade-strategy=eager pycparser wheel cython setuptools"
- "%CMD_IN_ENV% %PYEXE% -m pip install -U pycparser wheel cython setuptools cffi"
- "%CMD_IN_ENV% %PYEXE% -m pip wheel --no-build-isolation . -w dist"
- ps: "ls dist"
......
......@@ -9,10 +9,8 @@ pylint>=1.8.0 ; python_version < "3.4"
pylint >= 2.3.1 ; python_version >= "3.4" and platform_python_implementation == "CPython"
astroid >= 2.2.5 ; python_version >= "3.4" and platform_python_implementation == "CPython"
# We need this at runtime to use the libev-CFFI and libuv backends
cffi >= 1.12.2 ; platform_python_implementation == 'CPython'
# benchmarks use this
perf >= 1.6.0
-e .[test,events,dnspython,docs]
-e .[test,docs]
......@@ -51,5 +51,5 @@ Check out the detailed changelog_ for this version.
.. _inspired by eventlet: http://blog.gevent.org/2010/02/27/why-gevent/
.. _use gevent: http://groups.google.com/group/gevent/browse_thread/thread/4de9703e5dca8271
.. _open source projects based on gevent: https://github.com/gevent/gevent/wiki/Projects
.. _what's new: http://www.gevent.org/whatsnew_1_4.html
.. _what's new: http://www.gevent.org/whatsnew_1_5.html
.. _changelog: http://www.gevent.org/changelog.html
......@@ -39,10 +39,15 @@ extensions = [
'sphinx.ext.doctest',
'sphinx.ext.coverage',
'sphinx.ext.intersphinx',
'mysphinxext',
'sphinx.ext.extlinks',
'sphinx.ext.viewcode',
# Third-party
'repoze.sphinx.autointerface',
'sphinxcontrib.programoutput',
# Ours
'mysphinxext',
]
intersphinx_mapping = {
......
......@@ -10,8 +10,7 @@ Introduction and Basics
install
intro
whatsnew_1_4
whatsnew_1_3
whatsnew_1_5
api/gevent
api/gevent.greenlet
servers
......@@ -29,6 +28,20 @@ API Details
api/index
.. This anchor is referenced from CONTRIBUTING.rst
.. _contents-developing:
Developing and Packaging gevent
===============================
.. toctree::
:maxdepth: 2
installing_from_source
development
Related Information
===================
......
=============
Development
=============
This document provides information about developing gevent itself,
including information about running tests.
More information is in the ``CONTRIBUTING.rst`` document in the root
of the gevent repository.
..
The contributor guide (CONTRIBUTING.rst) references this document.
Things to include:
- Custom commands in ``setup.py``
- Writing tests and the gevent test framework:
- Avoiding hard test dependencies.
- Resource usage.
- test files must be executable
- Maybe these things belong in a README in the gevent.tests directory?
Getting Started
===============
Developing gevent requires being able to install gevent from source.
See :doc:`installing_from_source` for general information about that.
It is recommended to install the development copy of gevent in a
`virtual environment <https://docs.python.org/3/tutorial/venv.html>`_;
you can use the :mod:`venv` module distributed with Python 3, or
`virtualenv <https://pypi.org/project/virtualenv/>`_, possibly with
`virtualenvwrapper <https://pypi.org/project/virtualenvwrapper/>`_.
You may want a different virtual environment for each Python
implementation and version that you'll be working with. gevent
includes a `tox <http://tox.readthedocs.org/>`_ configuration for
automating the process of testing across multiple Python versions, but
that can be slow.
The rest of this document will assume working in an isolated virtual
environment, but usually won't show that in the prompt. An example of
creating a virtual environment is shown here::
$ python3 -m venv gevent-env
$ cd gevent-env
$ . bin/activate
(gevent-env) $
To work on gevent, we'll need to get the source, install gevent's
dependencies, including test dependencies, and install gevent as an
`editable install
<https://pip.pypa.io/en/stable/reference/pip_install/#editable-installs>`_
using pip's `-e option` (also known as `development mode
<https://setuptools.readthedocs.io/en/latest/setuptools.html#development-mode>`_,
this is mostly the same as running ``python setup.py develop``).
Getting the source means cloning the git repository::
(gevent-env) $ git clone https://github.com/gevent/gevent.git
(gevent-env) $ cd gevent
Installing gevent's dependencies, test dependencies, and gevent itself
can be done in one line by installing the ``dev-requirements.txt`` file::
(gevent-env) $ pip install -r dev-requirements.txt
.. warning::
This pip command does not work with pip 19.1. Either use pip 19.0
or below, or use pip 19.1.1 with ``--no-use-pep517``. See `issue
1412 <https://github.com/gevent/gevent/issues/1412>`_.
Running Tests
=============
gevent has an extensive regression test suite, implemented using the
standard :mod:`unittest` module. It uses a :mod:`custom testrunner
<gevent.testing.testrunner>` that provides enhanced test isolation
(important for monkey-patching), runs tests in parallel, and takes
care of other gevent-specific quirks.
The test runner has a number of options:
.. command-output:: python -mgevent.tests --help
The simplest way to run all the tests is just to invoke the test
runner, typically from the root of the source checkout::
(gevent-env) $ python -mgevent.tests
Running tests in parallel with concurrency 7
...
Ran 3107 tests (skipped=333) in 132 files in 01:52
You can also run an individual gevent test file using the test runner::
(gevent-env) $ python -m gevent.tests test__util.py
Running tests in parallel with concurrency 1
+ /.../python -u -mgevent.tests.test__util
- /.../python -u -mgevent.tests.test__util [Ran 9 tests in 1.1s]
Longest-running tests:
1.1 seconds: /.../python -u -mgevent.tests.test__util
Ran 9 tests in 1 files in 1.1s
Or you can run a monkey-patched standard library test::
(gevent-env) $ python -m gevent.tests.test___monkey_patching test_socket.py
Running tests in parallel with concurrency 1
+ /.../python -u -W ignore -m gevent.testing.monkey_test test_socket.py
Running with patch_all(Event=False): test_socket.py
Added imports 1
Skipped testEmptyFileSend (1)
...
Ran 555 tests in 23.042s
OK (skipped=172)
- /.../python -u -W ignore -m gevent.testing.monkey_test test_socket.py [took 26.7s]
Longest-running tests:
26.7 seconds: /.../python -u -W ignore -m gevent.testing.monkey_test test_socket.py
Ran 0 tests in 1 files in 00:27
Environment Variables
---------------------
Some testrunner options have equivalent environment variables.
Notably, ``--quiet`` is ``GEVENTTEST_QUIET`` and ``-u`` is
``GEVENTTEST_USE_RESOURCES``.
Using tox
---------
Before submitting a pull request, it's a good idea to run the tests
across all supported versions of Python, and to check the code quality
using prospector. This is what is done on Travis CI. Locally it
can be done using tox::
pip install tox
tox
Measuring Code Coverage
-----------------------
This is done on CI so it's not often necessary to do locally.
The testrunner accepts a ``--coverage`` argument to enable code
coverage metrics through the `coverage.py`_ package. That would go
something like this::
python -m gevent.tests --coverage
coverage combine
coverage html -i
<open htmlcov/index.html>
Limiting Resource Usage
-----------------------
gevent supports the standard library test suite's resources. All
resources are enabled by default. Disabling resources disables the
tests that use those resources. For example, to disable tests that
access the external network (the Internet), disable the ``network``
resource. There's an option for this::
$ python -m gevent.tests -u-network
And an environment variable::
$ GEVENTTEST_USE_RESOURCES=-network python -m gevent.tests
Continuous integration
======================
A test suite is run for every push and pull request submitted. Travis
CI is used to test on Linux, and `AppVeyor`_ runs the builds on
Windows.
.. image:: https://travis-ci.org/gevent/gevent.svg?branch=master
:target: https://travis-ci.org/gevent/gevent
.. image:: https://ci.appveyor.com/api/projects/status/q4kl21ng2yo2ixur?svg=true
:target: https://ci.appveyor.com/project/denik/gevent
Builds on Travis CI automatically submit updates to `coveralls.io`_ to
monitor test coverage.
.. image:: https://coveralls.io/repos/gevent/gevent/badge.svg?branch=master&service=github
:target: https://coveralls.io/github/gevent/gevent?branch=master
.. note:: On Debian, you will probably need ``libpythonX.Y-testsuite``
installed to run all the tests.
.. _coverage.py: https://pypi.python.org/pypi/coverage/
.. _coveralls.io: https://coveralls.io/github/gevent/gevent
.. _AppVeyor: https://ci.appveyor.com/project/denik/gevent
......@@ -4,8 +4,6 @@
.. include:: _about.rst
:ref:`Continue reading <installation>` »
If you like gevent, :doc:`donate <sfc>` to support the development.
......
This diff is collapsed.
========================
Installing From Source
========================
If you are unable to use the binary wheels (for platforms where no
pre-built wheels are available or if wheel installation is disabled),
you can build gevent from source. A normal ``pip install`` will
fallback to doing this if no binary wheel is available. (If you'll be
:ref:`developing <development>` gevent, you'll need to install from
source also; follow that link for more details.)
General Notes
=============
- You can force installation of gevent from source with ``pip
install --no-binary gevent gevent``. This is useful if there is a
binary wheel available, but you want to change the compile-time
options, such as to use a system version of libuv instead of the
embedded version. See ``
- You'll need a working C compiler that can build Python extensions.
On some platforms, you may need to install Python development
packages.
- Installing from source requires ``setuptools``. This is installed
automatically in virtual environments and by buildout. However,
gevent uses :pep:`496` environment markers in ``setup.py``.
Consequently, you'll need a version of setuptools newer than 25
(mid 2016) to install gevent from source; a version that's too old
will produce a ``ValueError``. Older versions of pipenv may also
`have issues installing gevent for this reason
<https://github.com/pypa/pipenv/issues/2113>`_.
- gevent 1.5 comes with a ``pyproject.toml`` file that installs the
build dependencies, including CFFI (needed for libuv support). pip
18 or above is required for this support.
- You can use pip's `VCS support
<https://pip.pypa.io/en/stable/reference/pip_install/#vcs-support>`_
to install gevent directly from its code repository. This can be
useful to check if a bug you're experiencing has been fixed. For
example, to install the current development version::
pip install git+git://github.com/gevent/gevent.git#egg=gevent
Often one would install this way into a virtual environment.
If you're using pip 18 or above, that should be all you need. If you
have difficulties, see the development instructions for more information.
Common Installation Issues
==========================
The following are some common installation problems and solutions for
those compiling gevent from source.
- Some Linux distributions are now mounting their temporary
directories with the ``noexec`` option. This can cause a standard
``pip install gevent`` to fail with an error like ``cannot run C
compiled programs``. One fix is to mount the temporary directory
without that option. Another may be to use the ``--build`` option to
``pip install`` to specify another directory. See `issue #570
<https://github.com/gevent/gevent/issues/570>`_ and `issue #612
<https://github.com/gevent/gevent/issues/612>`_ for examples.
- Also check for conflicts with environment variables like ``CFLAGS``.
For example, see `Library Updates
<http://www.gevent.org/whatsnew_1_1.html#library-updates-label>`_.
- Users of a recent SmartOS release may need to customize the
``CPPFLAGS`` (the environment variable containing the default
options for the C preprocessor) if they are using the libev shipped
with gevent. See `Operating Systems
<http://www.gevent.org/whatsnew_1_1.html#operating-systems-label>`_
for more information.
- If you see ``ValueError: ("Expected ',' or end-of-list in", "cffi >=
1.11.5 ; sys_platform == 'win32' and platform_python_implementation
== 'CPython'", 'at', " ; sys_platform == 'win32' and
platform_python_implementation == 'CPython'")``, the version of
setuptools is too old. Install a more recent version of setuptools.
Build-Time Configuration
========================
There are a few knobs that can be tweaked at gevent build time. These
are mostly useful for downstream packagers. They all take the form of
environment variables that must be set when ``setup.py`` is called
(note that ``pip install`` will invoke ``setup.py``). Toggle flags
that have boolean values may take the form of 0/1, true/false, off/on,
yes/no.
``CPPFLAGS``
A standard variable used when building the C extensions. gevent may
make slight modifications to this variable.
``CFLAGS``
A standard variable used when building the C extensions. gevent may
make slight modifications to this variable.
``LDFLAGS``
A standard variable used when building the C extensions. gevent may
make slight modifications to this variable.
``GEVENTSETUP_EV_VERIFY``
If set, the value is passed through as the value of the
``EV_VERIFY`` C compiler macro when libev is embedded.
In general, setting ``CPPFLAGS`` is more general and can contain
other macros recognized by libev.
``GEVENTSETUP_NO_CFFI_BUILD``
A boolean; when set to true, this disables all attempts at building
the CFFI modules. *This disables libuv.* (TODO: verify that.)
Ignored on PyPy and ignored on Windows.
Embedding Libraries
-------------------
By default, gevent builds and embeds tested versions of its
C dependencies libev, libuv, and c-ares. This is the
recommended configuration as the specific versions used are tested by
gevent, and sometimes require patches to be applied. Moreover,
embedding, especially in the case of libev, can be more efficient as
features not needed by gevent can be disabled, resulting in smaller or
faster libraries or runtimes.
However, this can be disabled (TODO: verify how this interacts with
CFFI; see NO_CFFI_BUILD), either for all libraries at once or for
individual libraries.
When embedding a library is disabled, the library must already be
installed on the system in a way the compiler can access and link
(i.e., correct ``CPPFLAGS``, etc) in order to use the corresponding C
extension.
``GEVENTSETUP_EMBED``
A boolean defaulting to true. When turned off (e.g.,
``GEVENTSETUP_EMBED=0``), libraries are not embedded in the gevent C
extensions. The value of this is used as the default for all the
libraries if no more specific version is defined.
``GEVENTSETUP_EMBED_LIBEV``
Controls embedding libev.
``GEVENTSETUP_EMBED_CARES``
Controls embedding c-ares.
``GEVENTSETUP_EMBED_LIBUV``
This is not defined or used, only a CFFI extension is available and
those are always embedded.
Older versions of gevent supported ``EMBED`` and ``LIBEV_EMBED``, etc,
to mean the same thing. Those aliases still work but are deprecated
and print a warning.
......@@ -48,6 +48,12 @@ in a multi-greenlet environment.
See :doc:`examples/concurrent_download`.
.. tip::
Insight into the monkey-patching process can be obtained by
observing the events :mod:`gevent.monkey` emits.
Beyond sockets
--------------
......
......@@ -57,6 +57,11 @@ be emitted.
Visibility
==========
.. tip::
Insight into the monkey-patching process can be obtained by
observing the events :mod:`gevent.monkey` emits.
It is sometimes useful to get an overview of all existing greenlets
and their stack traces. The function
:func:`gevent.util.print_run_info` will collect this info and print it
......
......@@ -556,7 +556,7 @@ img.alignright, img.right {margin: 0 0 1em 1.5em;}
/* Wrapper */
#site-wrapper {
margin: 0 auto;
width: 920px;
width: 80%;
}
......
......@@ -3,8 +3,10 @@
==================================
.. toctree::
:maxdepth: 2
whatsnew_1_4
whatsnew_1_3
whatsnew_1_2
whatsnew_1_1
whatsnew_1_0
......
......@@ -22,3 +22,13 @@ Platform Support
gevent 1.4 supports the platforms that gevent 1.3 supported, with the
exception that for users of Python 3.4, Python 3.4.3 is the minimum
supported version.
Test Changes
============
gevent's own test suite is now packaged as part of the gevent install,
and the ``greentest/testrunner.py`` script is now gone from a source
distribution or checkout. Instead, tests can be run with ``python -m
gevent.tests``. Many tests can be run given an installed version of
gevent, although the test dependencies, including cffi, must be
installed for all of them to run.
==========================
What's new in gevent 1.5
==========================
.. currentmodule:: gevent
.. toctree::
:maxdepth: 2
changelog
.. caution::
This document is currently being written.
Detailed information on what has changed is available in the
:doc:`changelog`. This document summarizes the most important changes
since :doc:`gevent 1.4 <whatsnew_1_4>`.
gevent 1.5 is a small maintenance release featuring bug fixes and a
small number of API improvements.
Platform Support
================
gevent 1.5 drops support for Python 3.4, and drops support for PyPy
< 7.
Packaging Changes
=================
gevent now distributes `manylinux2010
<https://www.python.org/dev/peps/pep-0571/>`_ binary wheels for Linux,
instead of the older ``manylinux1`` standard. This updated
platform tag allows gevent to distribute libuv support by default.
CentOS 6 is the baseline for this tag.
Library Updates
===============
Test Updates
============
gevent's test suite has adopted the standard library's notion of "test
resources," allowing users to disable
certain tests based on their resource usage. This is primarily
intended to support downstream packagers. For example, to disable
tests that require Internet access, one could disable the ``network``
resource using ``python -m gevent.tests -u-network`` or
``GEVENTTEST_USE_RESOURCES=-network python -m gevent.tests``. See
TODO: WRITE ME for more information.
#!/usr/bin/python
# Copyright (c) 2009 Denis Bilenko. See LICENSE for details.
# gevent-test-requires-resource: network
"""Spawn multiple workers and wait for them to complete"""
from __future__ import print_function
import gevent
......
#!/usr/bin/python
# gevent-test-requires-resource: network
"""Resolve hostnames concurrently, exit after 2 seconds.
Under the hood, this might use an asynchronous resolver based on
......
......@@ -2,6 +2,7 @@
[1] http://pypi.python.org/pypi/py-sendfile/
"""
# gevent-test-requires-resource: sendfile
# pylint:disable=import-error
from errno import EAGAIN
from sendfile import sendfile as original_sendfile
......
from __future__ import print_function
# gevent-test-requires-resource: psycopg2
# pylint:disable=import-error,broad-except,bare-except
import sys
import contextlib
......
......@@ -11,7 +11,7 @@ from __future__ import print_function
import sys
from gevent import socket
address = ('localhost', 9001)
address = ('127.0.0.1', 9001)
message = ' '.join(sys.argv[1:])
sock = socket.socket(type=socket.SOCK_DGRAM)
sock.connect(address)
......
from __future__ import print_function
# gevent-test-requires-resource: unixsocket_server
import socket
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
......
# gevent-test-requires-resource: unixsocket_client
import os
from gevent.pywsgi import WSGIServer
from gevent import socket
......
#!/usr/bin/python
# gevent-test-requires-resource: webpy
"""A web.py application powered by gevent"""
from __future__ import print_function
......
......@@ -18,13 +18,13 @@ from _setuputils import read_version
from _setuputils import system
from _setuputils import PYPY, WIN
from _setuputils import IGNORE_CFFI
from _setuputils import SKIP_LIBUV
from _setuputils import ConfiguringBuildExt
from _setuputils import GeventClean
from _setuputils import BuildFailed
from _setuputils import cythonize1
# Environment variables that are intended to be used outside of our own
# CI should be documented in ``installing_from_source.rst``
if WIN:
# Make sure the env vars that make.cmd needs are set
......@@ -193,10 +193,7 @@ if not WIN:
LIBEV_CFFI_MODULE
)
if not SKIP_LIBUV:
# libuv can't be built on manylinux1 because it needs glibc >= 2.12
# but manylinux1 has only 2.5, so we set SKIP_LIBUV in the script make-manylinux
cffi_modules.append(LIBUV_CFFI_MODULE)
cffi_modules.append(LIBUV_CFFI_MODULE)
greenlet_requires = [
# We need to watch our greenlet version fairly carefully,
......@@ -215,12 +212,13 @@ greenlet_requires = [
# The exception is on Windows, where we want the libuv backend we distribute
# to be the default, and that requires cffi; but don't try to install it
# on PyPy or it messes up the build
cffi_requires = [
"cffi >= 1.12.2 ; sys_platform == 'win32' and platform_python_implementation == 'CPython'",
CFFI_DEP = "cffi >= 1.12.2 ; platform_python_implementation == 'CPython'"
CFFI_REQUIRES = [
CFFI_DEP + " and sys_platform == 'win32'"
]
install_requires = greenlet_requires + cffi_requires
install_requires = greenlet_requires + CFFI_REQUIRES
# We use headers from greenlet, so it needs to be installed before we
# can compile. If it isn't already installed before we start
......@@ -234,7 +232,7 @@ install_requires = greenlet_requires + cffi_requires
# to install the headers at all, AFAICS, we don't need to bother with
# the buggy setup_requires.)
setup_requires = cffi_requires + []
setup_requires = CFFI_REQUIRES + []
if PYPY:
# These use greenlet/greenlet.h, which doesn't exist on PyPy
......@@ -286,12 +284,38 @@ for mod in _to_cythonize:
del _to_cythonize
if IGNORE_CFFI and not PYPY:
if IGNORE_CFFI and not PYPY and not WIN:
# Allow distributors to turn off CFFI builds
# even if it's available, because CFFI always embeds
# our copy of libev/libuv and they may not want that.
# Not allowed on PyPy and not allowed on Windows, because those
# backends are required there.
# TODO: CONFIRM if this is still the case.
del cffi_modules[:]
## Extras
EXTRA_DNSPYTHON = [
'dnspython >= 1.16.0',
'idna',
]
EXTRA_EVENTS = [
'zope.event',
'zope.interface',
]
# Fails to build on PyPy on Windows.
# TODO: Is that still the case?
EXTRA_PSUTIL_DEP = 'psutil >= 5.6.1 ; platform_python_implementation == "CPython" or sys_platform != "win32"'
EXTRA_MONITOR = [
EXTRA_PSUTIL_DEP,
]
EXTRA_RECOMMENDED = [
# We need this at runtime to use the libev-CFFI and libuv backends
CFFI_DEP,
] + EXTRA_DNSPYTHON + EXTRA_EVENTS + EXTRA_MONITOR
# If we are running info / help commands, or we're being imported by
# tools like pyroma, we don't need to build anything
_BUILDING = True
......@@ -366,28 +390,21 @@ def run_setup(ext_modules, run_make):
install_requires=install_requires,
setup_requires=setup_requires,
extras_require={
'dnspython': [
'dnspython >= 1.16.0',
'idna',
],
'events': [
'zope.event',
'zope.interface',
],
# Each extra intended for end users must be documented in install.rst
'dnspython': EXTRA_DNSPYTHON,
'events': EXTRA_EVENTS,
'monitor': EXTRA_MONITOR,
'recommended': EXTRA_RECOMMENDED,
# End end-user extras
'docs': [
'repoze.sphinx.autointerface',
'sphinxcontrib-programoutput',
],
'test': [
# To the extent possible, we should work to make sure
# our tests run, at least a basic set, without any of
# these extra dependencies (i.e., skip things when they are
# missing). This helps serve as a smoketest for users.
'zope.interface',
'zope.event',
# Makes tests faster
# Fails to build on PyPy on Windows.
'psutil >= 5.6.1 ; platform_python_implementation == "CPython" or sys_platform != "win32"',
# To the extent possible, we should work to make sure
# our tests run, at least a basic set, without any of
# these extra dependencies (i.e., skip things when they are
# missing). This helps serve as a smoketest for users.
'test': EXTRA_RECOMMENDED + [
# examples, called from tests, use this
'requests',
......@@ -401,7 +418,7 @@ def run_setup(ext_modules, run_make):
# leak checks. previously we had a hand-rolled version.
'objgraph',
]
],
},
# It's always safe to pass the CFFI keyword, even if
# cffi is not installed: it's just ignored in that case.
......
......@@ -438,6 +438,9 @@ class IGeventDidPatchBuiltinModulesEvent(IGeventDidPatchEvent):
"""
Event emitted *after* the builtin modules have been patched.
If you're going to monkey-patch a third-party library, this is
usually the event to listen for.
The values of the *source* and *target* attributes are undefined.
"""
......
......@@ -45,8 +45,10 @@ module provides functions for that purpose.
- :func:`is_object_patched`
- :func:`get_original`
Plugins
=======
.. _plugins:
Plugins and Events
==================
Beginning in gevent 1.3, events are emitted during the monkey patching process.
These events are delivered first to :mod:`gevent.events` subscribers, and then
......
......@@ -78,6 +78,8 @@ from .skipping import skipOnPurePython
from .skipping import skipWithCExtensions
from .skipping import skipOnLibuvOnTravisOnCPython27
from .skipping import skipOnPy37
from .skipping import skipWithoutResource
from .skipping import skipWithoutExternalNetwork
from .exception import ExpectedException
......@@ -91,12 +93,15 @@ from .params import LARGE_TIMEOUT
from .params import DEFAULT_LOCAL_HOST_ADDR
from .params import DEFAULT_LOCAL_HOST_ADDR6
from .params import DEFAULT_BIND_ADDR
from .params import DEFAULT_BIND_ADDR_TUPLE
from .params import DEFAULT_CONNECT_HOST
from .params import DEFAULT_SOCKET_TIMEOUT
from .params import DEFAULT_XPC_SOCKET_TIMEOUT
main = unittest.main
SkipTest = unittest.SkipTest
from .hub import QuietHub
......
......@@ -21,23 +21,26 @@ monkey.patch_all(**kwargs)
from .sysinfo import RUNNING_ON_APPVEYOR
from .sysinfo import PY37
from .patched_tests_setup import disable_tests_in_source
try:
from test import support
except ImportError:
from test import test_support as support
support.is_resource_enabled = lambda *args: True
del support.use_resources
from . import support
from . import resources
from . import SkipTest
if RUNNING_ON_APPVEYOR and PY37:
# 3.7 added a stricter mode for thread cleanup.
# It appears to be unstable on Windows (at least appveyor)
# and test_socket.py constantly fails with an extra thread
# on some random test. We disable it entirely.
# XXX: Figure out how to make a *definition* in ./support.py actually
# override the original in test.support, without having to
# manually set it
import contextlib
@contextlib.contextmanager
def wait_threads_exit(timeout=None): # pylint:disable=unused-argument
yield
support.wait_threads_exit = wait_threads_exit
# Configure allowed resources
resources.setup_resources()
__file__ = os.path.join(os.getcwd(), test_filename)
......@@ -71,5 +74,14 @@ try:
'exec',
dont_inherit=True)
exec(module_code, globals())
except SkipTest as e:
# Some tests can raise test.support.ResourceDenied
# in their main method before the testrunner takes over.
# That's a kind of SkipTest. we can't get a skip count because it
# hasn't run, though.
print(e)
# Match the regular unittest output, including ending with skipped
print("Ran 0 tests in 0.0s")
print('OK (skipped=0)')
finally:
os.remove(temp_path)
......@@ -54,10 +54,11 @@ DEFAULT_LOCAL_HOST_ADDR = support.HOST
DEFAULT_LOCAL_HOST_ADDR6 = support.HOSTv6
# Not all TCP stacks support dual binding where ''
# binds to both v4 and v6.
# XXX: This is badly named; you often want DEFAULT_BIND_ADDR_TUPLE
DEFAULT_BIND_ADDR = support.HOST
DEFAULT_CONNECT = DEFAULT_LOCAL_HOST_ADDR
DEFAULT_CONNECT_HOST = DEFAULT_CONNECT = DEFAULT_LOCAL_HOST_ADDR
DEFAULT_BIND_ADDR_TUPLE = (DEFAULT_BIND_ADDR, 0)
# For in-process sockets
......
# -*- coding: utf-8 -*-
# Copyright (c) 2018 gevent community
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
"""
Test environment setup.
This establishes the resources that are available for use,
which are tested with `support.is_resource_enabled`.
"""
from __future__ import absolute_import, division, print_function
# This file may be imported early, so it should take care not to import
# things it doesn't need, which means deferred imports.
def get_ALL_RESOURCES():
"Return a fresh list of resource names."
# RESOURCE_NAMES is the list of all known resources, including those that
# shouldn't be enabled by default or when asking for "all" resources.
# ALL_RESOURCES is the list of resources enabled by default or with "all" resources.
try:
# 3.6 and 3.7
from test.libregrtest import ALL_RESOURCES
except ImportError:
# 2.7 through 3.5
# Don't do this:
## from test.regrtest import ALL_RESOURCES
# On Python 2.7 to 3.5, importing regrtest iterates
# sys.modules and does modifications. That doesn't work well
# when it's imported from another module at module scope.
# Also, it makes some assumptions about module __file__ that
# may not hold true (at least on 2.7), especially when six or
# other module proxy objects are involved.
# So we hardcode the list. This is from 2.7, which is a superset
# of the defined resources through 3.5.
ALL_RESOURCES = (
'audio', 'curses', 'largefile', 'network', 'bsddb',
'decimal', 'cpu', 'subprocess', 'urlfetch', 'gui',
'xpickle'
)
return list(ALL_RESOURCES)
def parse_resources(resource_str=None):
# str -> Sequence[str]
# Parse it like libregrtest.cmdline documents:
# -u is used to specify which special resource intensive tests to run,
# such as those requiring large file support or network connectivity.
# The argument is a comma-separated list of words indicating the
# resources to test. Currently only the following are defined:
# all - Enable all special resources.
#
# none - Disable all special resources (this is the default).
# <snip>
# network - It is okay to run tests that use external network
# resource, e.g. testing SSL support for sockets.
# <snip>
#
# subprocess Run all tests for the subprocess module.
# <snip>
#
# To enable all resources except one, use '-uall,-<resource>'. For
# example, to run all the tests except for the gui tests, give the
# option '-uall,-gui'.
# We make a change though: we default to 'all' resources, instead of
# 'none'. Encountering either of those later in the string resets
# it, for ease of working with appending to environment variables.
if resource_str is None:
import os
resource_str = os.environ.get('GEVENTTEST_USE_RESOURCES')
resources = get_ALL_RESOURCES()
if not resource_str:
return resources
requested_resources = resource_str.split(',')
for requested_resource in requested_resources:
# empty strings are ignored; this can happen when working with
# the environment variable if not already set:
# ENV=$ENV,-network
if not requested_resource:
continue
if requested_resource == 'all':
resources = get_ALL_RESOURCES()
elif requested_resource == 'none':
resources = []
elif requested_resource.startswith('-'):
if requested_resource[1:] in resources:
resources.remove(requested_resource[1:])
else:
# TODO: Produce a warning if it's an unknown resource?
resources.append(requested_resource)
return resources
def unparse_resources(resources):
"""
Given a list of enabled resources, produce the correct environment variable
setting to enable (only) that list.
"""
# By default, we assume all resources are enabled, so explicitly
# listing them here doesn't actually disable anything. To do that, we want to
# list the ones that are disabled. This is usually shorter than starting with
# 'none', and manually adding them back in one by one.
#
# 'none' must be special cased because an empty option string
# means 'all'. Still, we're explicit about that.
#
# TODO: Make this produce the minimal output; sometimes 'none' and
# adding would be shorter.
all_resources = set(get_ALL_RESOURCES())
enabled = set(resources)
if enabled == all_resources:
result = 'all'
elif resources:
explicitly_disabled = all_resources - enabled
result = ''.join(sorted('-' + x for x in explicitly_disabled))
else:
result = 'none'
return result
def setup_resources(resources=None):
"""
Call either with a list of resources or a resource string.
If ``None`` is given, get the resource string from the environment.
"""
if isinstance(resources, str) or resources is None:
resources = parse_resources(resources)
from . import support
support.use_resources = list(resources)
support.gevent_has_setup_resources = True
return resources
def ensure_setup_resources():
# Call when you don't know if resources have been setup and you want to
# get the environment variable if needed.
# Returns an object with `is_resource_enabled`.
from . import support
if not support.gevent_has_setup_resources:
setup_resources()
return support
def exit_without_resource(resource):
"""
Call this in standalone test modules that can't use unittest.SkipTest.
Exits with a status of 0 if the resource isn't enabled.
"""
if not ensure_setup_resources().is_resource_enabled(resource):
print("Skipped: %r not enabled" % (resource,))
import sys
sys.exit(0)
def skip_without_resource(resource, reason=''):
requires = 'Requires resource %r' % (resource,)
if not reason:
reason = requires
else:
reason = reason + ' (' + requires + ')'
if not ensure_setup_resources().is_resource_enabled(resource):
import unittest
raise unittest.SkipTest(reason)
if __name__ == '__main__':
print(setup_resources())
......@@ -103,53 +103,48 @@ def _check_psutil():
return _has_psutil_process
def skipWithoutPSUtil(reason):
# Important: If you use this on classes, you must not use the
# two-argument form of super()
reason = "psutil not available: " + reason
def _make_runtime_skip_decorator(reason, predicate):
def decorator(test_item):
# Defer the check until runtime to avoid imports
if not isinstance(test_item, type):
f = test_item
@functools.wraps(test_item)
def skip_wrapper(*args):
if not _check_psutil():
def skip_wrapper(*args, **kwargs):
if not predicate():
raise unittest.SkipTest(reason)
return f(*args)
return f(*args, **kwargs)
test_item = skip_wrapper
else:
# given a class, subclass its setUp method to do the same.
# The trouble with this is that the decorator automatically
# rebinds to the same name, and if there are two-argument calls
# like `super(MyClass, self).thing` in the class, we get infinite
# recursion (because MyClass has been rebound to the object we return.)
# This is easy to fix on Python 3: use the zero argument `super()`, because
# the lookup relies not on names but implicit slots.
# given a class, override setUp() to skip it.
#
# Internally, unittest uses two flags on the class to do this:
# __unittest_skip__ and __unittest_skip_why__. It *appears*
# these are evaluated for each method in the test, so we can safely
# change them at runtime. **This isn't documented.**
#
# I didn't find a good workaround for this on Python 2, so
# I'm just forbidding using the two argument super.
# If they are set before execution begins, then the class setUpClass
# and tearDownClass are skipped. So changing them at runtime could result
# in something being set up but not torn down. It is substantially
# faster, though, to set them.
base = test_item
class SkipWrapper(base):
def setUp(self):
if not _check_psutil():
raise unittest.SkipTest(reason)
base.setUp(self)
def _super(self):
return super(base, self)
SkipWrapper.__name__ = test_item.__name__
SkipWrapper.__module__ = test_item.__module__
try:
SkipWrapper.__qualname__ = test_item.__qualname__
except AttributeError:
# Python 2
pass
test_item = SkipWrapper
base_setUp = base.setUp
@functools.wraps(test_item)
def setUp(self):
if not predicate():
base.__unittest_skip__ = True
base.__unittest_skip_why__ = reason
raise unittest.SkipTest(reason)
base_setUp(self)
base.setUp = setUp
return test_item
return decorator
def skipWithoutPSUtil(reason):
reason = "psutil not available: " + reason
# Defer the check until runtime to avoid imports
return _make_runtime_skip_decorator(reason, _check_psutil)
if sysinfo.LIBUV:
skipOnLibuv = unittest.skip
......@@ -168,3 +163,28 @@ if sysinfo.LIBUV:
skipOnLibuvOnPyPyOnWin = unittest.skip
else:
skipOnLibev = unittest.skip
def skipWithoutResource(resource, reason=''):
requires = 'Requires resource %r' % (resource,)
if not reason:
reason = requires
else:
reason = reason + ' (' + requires + ')'
# Defer until runtime; resources are established as part
# of test startup.
def predicate(): # This is easily cached if needed
from . import resources
return resources.ensure_setup_resources().is_resource_enabled(resource)
return _make_runtime_skip_decorator(reason, predicate)
def skipWithoutExternalNetwork(reason=''):
# Use to decorate test functions or classes that
# need access to external network resources (e.g., DNS, HTTP servers, etc)
#
# Important: If you use this on classes, you must not use the
# two-argument form of super()
return skipWithoutResource('network', reason)
......@@ -30,7 +30,8 @@ def bind_and_listen(sock, address=DEFAULT_BIND_ADDR_TUPLE, backlog=50, reuse_add
except error:
pass
sock.bind(address)
sock.listen(backlog)
if backlog is not None: # udp
sock.listen(backlog)
def tcp_listener(address=DEFAULT_BIND_ADDR_TUPLE, backlog=50, reuse_addr=True):
......@@ -39,3 +40,10 @@ def tcp_listener(address=DEFAULT_BIND_ADDR_TUPLE, backlog=50, reuse_addr=True):
sock = socket.socket()
bind_and_listen(sock, address, backlog=backlog, reuse_addr=reuse_addr)
return sock
def udp_listener(address=DEFAULT_BIND_ADDR_TUPLE, reuse_addr=True):
"""A shortcut to create a UDF socket, bind it and put it into listening state."""
from gevent import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
bind_and_listen(sock, address, backlog=None, reuse_addr=reuse_addr)
return sock
......@@ -17,22 +17,76 @@
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
"""
A re-export of the support module from Python's test package, with some
version compatibility shims and overrides.
"""
# A re-export of the support module from Python's test package, with some
# compatibility shims.
import sys
# Note that this imports a lot of modules.
# Proxy through, so that changes to this module reflect in the
# real module too. In 3.7, this is natively supported.
# This breaks static analysis (pylint), so we configure pylint
# to ignore this module.
# pylint:disable=wildcard-import,unused-wildcard-import
class _Default(object):
# A descriptor-like object that will
# only be used if the actual stdlib module
# doesn't have the value.
try:
# Renamed from test_support in Python 3,
# *and* in 2.7.14 (but with a BWC module)
from test.support import *
except ImportError:
from test.test_support import *
def __init__(self, value):
self.value = value
try:
HOSTv6
except NameError:
HOSTv6 = '::1'
class _ModuleProxy(object):
__slots__ = ('_orig_mod', '_stdlib_support')
def __init__(self):
self._orig_mod = sys.modules[__name__]
self._stdlib_support = None
def __get_stdlib_support(self):
if self._stdlib_support is None:
try:
# Renamed from test_support in Python 3,
# *and* in 2.7.14 (but with a BWC module)
from test import support as stdlib_support
except ImportError:
from test import test_support as stdlib_support
self._stdlib_support = stdlib_support
return self._stdlib_support
def __getattr__(self, name):
try:
local_val = getattr(self._orig_mod, name)
except AttributeError:
return getattr(self.__get_stdlib_support(), name)
if isinstance(local_val, _Default):
try:
return getattr(self.__get_stdlib_support(), name)
except AttributeError:
return local_val.value
return local_val
def __setattr__(self, name, value):
if name in _ModuleProxy.__slots__:
super(_ModuleProxy, self).__setattr__(name, value)
return
# Setting it deletes it from this module, so that
# we then continue to fall through to the original module.
try:
delattr(self._orig_mod, name)
except AttributeError:
pass
setattr(self.__get_stdlib_support(), name, value)
# When is this not defined in test.support?
HOSTv6 = _Default('::1')
# Set by resources.setup_resources()
gevent_has_setup_resources = False
sys.modules[__name__] = _ModuleProxy()
......@@ -12,6 +12,9 @@ from datetime import timedelta
from multiprocessing.pool import ThreadPool
from multiprocessing import cpu_count
from . import util
from .resources import parse_resources
from .resources import setup_resources
from .resources import unparse_resources
from .sysinfo import RUNNING_ON_CI
from .sysinfo import PYPY
from .sysinfo import PY2
......@@ -94,6 +97,11 @@ class Runner(object):
failfast=False,
quiet=False,
configured_run_alone_tests=()):
"""
:keyword quiet: Set to True or False to explicitly choose. Set to
`None` to use the default, which may come from the environment variable
``GEVENTTEST_QUIET``.
"""
self._tests = tests
self._configured_failing_tests = configured_failing_tests
self._failfast = failfast
......@@ -107,7 +115,8 @@ class Runner(object):
self._worker_count = min(len(tests), NWORKERS) or 1
def _run_one(self, cmd, **kwargs):
kwargs['quiet'] = self._quiet
if self._quiet is not None:
kwargs['quiet'] = self._quiet
result = util.run(cmd, **kwargs)
if not result and self._failfast:
sys.exit(1)
......@@ -295,6 +304,12 @@ def discover(
# XXX: Rework this to avoid importing.
to_import.append(qualified_name)
else:
# XXX: For simple python module tests, try this with `runpy.run_module`,
# very similar to the way we run things for monkey patching.
# The idea here is that we can perform setup ahead of time (e.g., setup_resources())
# in each test without having to do it manually or force calls or modifications to those
# tests.
cmd = [sys.executable, '-u']
if PYPY and PY2:
# Doesn't seem to be an env var for this
......@@ -407,14 +422,14 @@ def report(total, failed, passed, exit=True, took=None,
if failed_unexpected:
util.log('\n%s/%s unexpected failures', len(failed_unexpected), total, color='error')
print_list(failed_unexpected)
else:
util.log(
'\nRan %s tests%s in %s files%s',
total_cases,
util._colorize('skipped', " (skipped=%d)" % total_skipped) if total_skipped else '',
total,
took,
)
util.log(
'\nRan %s tests%s in %s files%s',
total_cases,
util._colorize('skipped', " (skipped=%d)" % total_skipped) if total_skipped else '',
total,
took,
)
if exit:
if failed_unexpected:
......@@ -492,10 +507,29 @@ def main():
parser.add_argument("--verbose", action="store_false", dest='quiet')
parser.add_argument("--debug", action="store_true", default=False)
parser.add_argument("--package", default="gevent.tests")
parser.add_argument('-u', '--use', metavar='RES1,RES2,...',
action='store', type=parse_resources,
help='specify which special resource intensive tests '
'to run. "all" is the default; "none" may also be used. '
'Disable individual resources with a leading -.'
'For example, "-u-network". GEVENTTEST_USE_RESOURCES is used '
'if no argument is given. To only use one resources, specify '
'"-unone,resource".')
parser.add_argument("--travis-fold", metavar="MSG",
help="Emit Travis CI log fold markers around the output.")
parser.add_argument('tests', nargs='*')
options = parser.parse_args()
# options.use will be either None for not given, or a list
# of the last specified -u argument.
# If not given, use the default, which we'll take from the environment, if set.
options.use = list(set(parse_resources() if options.use is None else options.use))
# Whether or not it came from the environment, put it in the
# environment now.
os.environ['GEVENTTEST_USE_RESOURCES'] = unparse_resources(options.use)
setup_resources(options.use)
# Set this before any test imports in case of 'from .util import QUIET';
# not that this matters much because we spawn tests in subprocesses,
......
......@@ -198,7 +198,8 @@ IGNORED_GEVENT_ENV_KEYS = {
# These should match the defaults.
IGNORED_GEVENT_ENV_ITEMS = {
('GEVENT_RESOLVER', 'thread'),
('GEVENT_RESOLVER_NAMESERVERS', '8.8.8.8')
('GEVENT_RESOLVER_NAMESERVERS', '8.8.8.8'),
('GEVENTTEST_USE_RESOURCES', 'all'),
}
def getname(command, env=None, setenv=None):
......
......@@ -39,9 +39,6 @@ if sys.platform == 'win32':
# other Windows-related issues (need investigating)
FAILING_TESTS += [
'FLAKY test__greenletset.py',
# This has been seen to fail on Py3 and Py2 due to socket reuse
# errors, probably timing related again.
'FLAKY test___example_servers.py',
]
if APPVEYOR:
......@@ -344,9 +341,14 @@ TEST_FILE_OPTIONS = {
# tests that don't do well when run on busy box
# or that are mutually exclusive
RUN_ALONE = [
'test__threadpool.py',
'test__examples.py',
# These share the same port, which means they can conflict
# between concurrent test runs too
# XXX: Fix this by dynamically picking a port.
'test__example_wsgiserver.py',
'test__example_webproxy.py',
]
......
......@@ -106,7 +106,9 @@ def TESTRUNNER(tests=None):
def main():
from gevent.testing import testrunner
return testrunner.Runner(list(TESTRUNNER(sys.argv[1:])))()
discovered_tests = TESTRUNNER(sys.argv[1:])
discovered_tests = list(discovered_tests)
return testrunner.Runner(discovered_tests, quiet=None)()
if __name__ == '__main__':
......
from __future__ import print_function
import errno
import unittest
import gevent
......@@ -11,6 +10,7 @@ except ImportError as ex:
from gevent import socket
import gevent.testing as greentest
from gevent.testing.sockets import udp_listener
@unittest.skipIf(
Resolver is None,
......@@ -20,19 +20,9 @@ class TestTimeout(greentest.TestCase):
__timeout__ = 30
address = ('', 7153)
def test(self):
listener = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
listener.bind(self.address)
except socket.error as ex:
if ex.errno in (errno.EPERM, errno.EADDRNOTAVAIL) or 'permission denied' in str(ex).lower():
raise unittest.SkipTest(
'This test binds on port a port that was already in use or not allowed.\n'
)
raise
listener = self._close_on_teardown(udp_listener())
address = listener.getsockname()
def reader():
......@@ -41,8 +31,8 @@ class TestTimeout(greentest.TestCase):
gevent.spawn(reader)
r = Resolver(servers=['127.0.0.1'], timeout=0.001, tries=1,
udp_port=self.address[-1])
r = Resolver(servers=[address[0]], timeout=0.001, tries=1,
udp_port=address[-1])
with self.assertRaisesRegex(socket.gaierror, "ARES_ETIMEOUT"):
r.gethostbyname('www.google.com')
......
from __future__ import print_function
import gevent.testing as greentest
import gevent
from gevent import socket
from gevent import backdoor
import gevent.testing as greentest
from gevent.testing.params import DEFAULT_BIND_ADDR_TUPLE
from gevent.testing.params import DEFAULT_CONNECT
def read_until(conn, postfix):
read = b''
assert isinstance(postfix, bytes)
......@@ -36,14 +40,14 @@ class Test(greentest.TestCase):
def _make_server(self, *args, **kwargs):
assert self._server is None
self._server = backdoor.BackdoorServer(('127.0.0.1', 0), *args, **kwargs)
self._server = backdoor.BackdoorServer(DEFAULT_BIND_ADDR_TUPLE, *args, **kwargs)
self._close_on_teardown(self._server.stop)
self._server.start()
def _create_connection(self):
conn = socket.socket()
self._close_on_teardown(conn)
conn.connect(('127.0.0.1', self._server.server_port))
conn.connect((DEFAULT_CONNECT, self._server.server_port))
return conn
def _close(self, conn, cmd=b'quit()\r\n)'):
......
from __future__ import absolute_import, print_function, division
import sys
import unittest
import gevent.testing as greentest
......@@ -36,13 +36,11 @@ class TestWatchers(unittest.TestCase):
del self.loop
def test_io(self):
if sys.platform == 'win32':
if greentest.WIN:
# libev raises IOError, libuv raises ValueError
Error = (IOError, ValueError)
win32 = True
else:
Error = ValueError
win32 = False
with self.assertRaises(Error):
self.loop.io(-1, 1)
......@@ -53,7 +51,7 @@ class TestWatchers(unittest.TestCase):
self.loop.io(1, core.TIMER) # pylint:disable=no-member
# Test we can set events and io before it's started
if not win32:
if not greentest.WIN:
# We can't do this with arbitrary FDs on windows;
# see libev_vfd.h
io = self.loop.io(1, core.READ) # pylint:disable=no-member
......@@ -144,7 +142,7 @@ class TestLibev(unittest.TestCase):
def test_flags_conversion(self):
# pylint: disable=no-member
if sys.platform != 'win32':
if not greentest.WIN:
self.assertEqual(core.loop(2, default=False).backend_int, 2)
self.assertEqual(core.loop('select', default=False).backend, 'select')
self.assertEqual(core._flags_to_int(None), 0)
......
from __future__ import print_function, absolute_import
from gevent import monkey; monkey.patch_all(subprocess=True)
import signal
import sys
import socket
from time import sleep
......@@ -14,16 +14,17 @@ from gevent.testing import util
@greentest.skipOnLibuvOnCIOnPyPy("Timing issues sometimes lead to connection refused")
class Test(util.TestServer):
server = 'portforwarder.py'
# [listen on, forward to]
args = ['127.0.0.1:10011', '127.0.0.1:10012']
if sys.platform.startswith('win'):
if greentest.WIN:
from subprocess import CREATE_NEW_PROCESS_GROUP
# Must be in a new process group to use CTRL_C_EVENT, otherwise
# we get killed too
start_kwargs = {'creationflags': CREATE_NEW_PROCESS_GROUP}
def after(self):
if sys.platform == 'win32':
if greentest.WIN:
self.assertIsNotNone(self.popen.poll())
else:
self.assertEqual(self.popen.poll(), 0)
......
from unittest import SkipTest
import gevent.testing as greentest
from . import test__example_wsgiserver
@greentest.skipOnCI("Timing issues sometimes lead to a connection refused")
@greentest.skipWithoutExternalNetwork("Tries to reach google.com")
class Test_webproxy(test__example_wsgiserver.Test_wsgiserver):
server = 'webproxy.py'
def _run_all_tests(self):
status, data = self.read('/')
self.assertEqual(status, '200 OK')
self.assertIn(b"gevent example", data)
status, data = self.read('/http://www.google.com')
self.assertEqual(status, '200 OK')
self.assertIn(b'google', data.lower())
def test_a_blocking_client(self):
# Not applicable
raise SkipTest("Not applicable")
if __name__ == '__main__':
greentest.main()
......@@ -4,7 +4,7 @@ try:
from urllib import request as urllib2
except ImportError:
import urllib2
from unittest import SkipTest
import socket
import ssl
......@@ -59,7 +59,7 @@ class Test_wsgiserver(util.TestServer):
self._test_hello()
# Now create a connection and only partway finish
# the transaction
sock = socket.create_connection(('localhost', self.PORT))
sock = socket.create_connection((params.DEFAULT_LOCAL_HOST_ADDR, self.PORT))
ssl_sock = None
if self._use_ssl:
ssl_sock = ssl.wrap_socket(sock)
......@@ -89,53 +89,5 @@ class Test_wsgiserver(util.TestServer):
def test_a_blocking_client(self):
self._do_test_a_blocking_client()
@greentest.skipOnCI("Timing issues sometimes lead to a connection refused")
class Test_wsgiserver_ssl(Test_wsgiserver):
server = 'wsgiserver_ssl.py'
URL = 'https://%s:8443' % (params.DEFAULT_LOCAL_HOST_ADDR,)
PORT = 8443
_use_ssl = True
if hasattr(ssl, '_create_unverified_context'):
# Disable verification for our self-signed cert
# on Python >= 2.7.9 and 3.4
ssl_ctx = ssl._create_unverified_context()
@greentest.skipOnCI("Timing issues sometimes lead to a connection refused")
class Test_webproxy(Test_wsgiserver):
server = 'webproxy.py'
def _run_all_tests(self):
status, data = self.read('/')
self.assertEqual(status, '200 OK')
self.assertIn(b"gevent example", data)
status, data = self.read('/http://www.google.com')
self.assertEqual(status, '200 OK')
self.assertIn(b'google', data.lower())
def test_a_blocking_client(self):
# Not applicable
raise SkipTest("Not applicable")
# class Test_webpy(Test_wsgiserver):
# server = 'webpy.py'
# not_found_message = 'not found'
#
# def _test_hello(self):
# status, data = self.read('/')
# self.assertEqual(status, '200 OK')
# assert "Hello, world" in data, repr(data)
#
# def _test_long(self):
# start = time.time()
# status, data = self.read('/long')
# delay = time.time() - start
# assert 10 - 0.5 < delay < 10 + 0.5, delay
# self.assertEqual(status, '200 OK')
# self.assertEqual(data, 'Hello, 10 seconds later')
if __name__ == '__main__':
greentest.main()
import ssl
import gevent.testing as greentest
from gevent.testing import params
from . import test__example_wsgiserver
@greentest.skipOnCI("Timing issues sometimes lead to a connection refused")
class Test_wsgiserver_ssl(test__example_wsgiserver.Test_wsgiserver):
server = 'wsgiserver_ssl.py'
URL = 'https://%s:8443' % (params.DEFAULT_LOCAL_HOST_ADDR,)
PORT = 8443
_use_ssl = True
if hasattr(ssl, '_create_unverified_context'):
# Disable verification for our self-signed cert
# on Python >= 2.7.9 and 3.4
ssl_ctx = ssl._create_unverified_context()
if __name__ == '__main__':
greentest.main()
"""
Test the contents of the ``examples/`` directory.
If an existing test in *this* directory named ``test__example_<fn>.py`` exists,
where ``<fn>`` is the base filename of an example file, it will not be tested
here.
Examples can specify that they need particular test resources to be enabled
by commenting (one per line) ``# gevent-test-requires-resource: <resource>``;
most commonly the resource will be ``network``. You can use this technique to specify
non-existant resources for things that should never be tested.
"""
import re
import sys
import os
import glob
......@@ -14,38 +27,41 @@ def _find_files_to_ignore():
try:
os.chdir(this_dir)
result = [
'wsgiserver.py',
'wsgiserver_ssl.py',
'webproxy.py',
'webpy.py',
'unixsocket_server.py',
'unixsocket_client.py',
'psycopg2_pool.py',
'geventsendfile.py',
]
result = [x[14:] for x in glob.glob('test__example_*.py')]
if greentest.PYPY and greentest.RUNNING_ON_APPVEYOR:
# For some reason on Windows with PyPy, this times out,
# when it should be very fast.
result.append("processes.py")
result += [x[14:] for x in glob.glob('test__example_*.py')]
finally:
os.chdir(old_dir)
return result
default_time_range = (2, 4)
default_time_range = (2, 10)
time_ranges = {
'concurrent_download.py': (0, 30),
'processes.py': (0, 4)
'processes.py': (0, default_time_range[-1])
}
class _AbstractTestMixin(util.ExampleMixin):
time_range = (2, 4)
time_range = default_time_range
filename = None
def _check_resources(self):
from gevent.testing import resources
with open(os.path.join(self.cwd, self.filename), 'r') as f:
contents = f.read()
pattern = re.compile('^# gevent-test-requires-resource: (.*)$', re.MULTILINE)
resources_needed = re.finditer(pattern, contents)
for match in resources_needed:
needed = contents[match.start(1):match.end(1)]
resources.skip_without_resource(needed)
def test_runs(self):
self._check_resources()
start = time.time()
min_time, max_time = self.time_range
if not util.run([sys.executable, '-u', self.filename],
......@@ -73,6 +89,7 @@ def _build_test_classes():
bn = os.path.basename(filename)
if bn in ignore:
continue
tc = type(
'Test_' + bn,
(_AbstractTestMixin, greentest.TestCase),
......
......@@ -274,5 +274,4 @@ class TestTextMode(unittest.TestCase):
if __name__ == '__main__':
sys.argv.append('-v')
greentest.main()
......@@ -19,7 +19,7 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
"""Test than modules in gevent.green package are indeed green.
"""Test that modules in gevent.green package are indeed green.
To do that spawn a green server and then access it using a green socket.
If either operation blocked the whole script would block and timeout.
"""
......
......@@ -4,13 +4,14 @@ from gevent import monkey; monkey.patch_all()
import socket
import ssl
import threading
import unittest
import errno
import weakref
import gevent.testing as greentest
from gevent.testing.params import DEFAULT_BIND_ADDR_TUPLE
from gevent.testing.params import DEFAULT_CONNECT
from gevent.testing.sockets import tcp_listener
dirname = os.path.dirname(os.path.abspath(__file__))
certfile = os.path.join(dirname, '2_7_keycert.pem')
......@@ -109,7 +110,7 @@ class Test(greentest.TestCase):
def make_open_socket(self):
s = socket.socket()
s.bind(('127.0.0.1', 0))
s.bind(DEFAULT_BIND_ADDR_TUPLE)
self._close_on_teardown(s)
if WIN or greentest.LINUX:
# Windows and linux (with psutil) doesn't show as open until
......@@ -175,16 +176,14 @@ class TestSocket(Test):
self.assert_closed(s, fileno)
def test_server_simple(self):
listener = socket.socket()
listener.bind(('127.0.0.1', 0))
listener = tcp_listener(backlog=1)
port = listener.getsockname()[1]
listener.listen(1)
connector = socket.socket()
self._close_on_teardown(connector)
def connect():
connector.connect(('127.0.0.1', port))
connector.connect((DEFAULT_CONNECT, port))
t = threading.Thread(target=connect)
t.start()
......@@ -201,16 +200,14 @@ class TestSocket(Test):
connector.close()
def test_server_makefile1(self):
listener = socket.socket()
listener.bind(('127.0.0.1', 0))
listener = tcp_listener(backlog=1)
port = listener.getsockname()[1]
listener.listen(1)
connector = socket.socket()
self._close_on_teardown(connector)
def connect():
connector.connect(('127.0.0.1', port))
connector.connect((DEFAULT_CONNECT, port))
t = threading.Thread(target=connect)
t.start()
......@@ -236,16 +233,14 @@ class TestSocket(Test):
connector.close()
def test_server_makefile2(self):
listener = socket.socket()
listener.bind(('127.0.0.1', 0))
listener = tcp_listener(backlog=1)
port = listener.getsockname()[1]
listener.listen(1)
connector = socket.socket()
self._close_on_teardown(connector)
def connect():
connector.connect(('127.0.0.1', port))
connector.connect((DEFAULT_CONNECT, port))
t = threading.Thread(target=connect)
t.start()
......@@ -270,7 +265,7 @@ class TestSocket(Test):
class TestSSL(Test):
def _ssl_connect_task(self, connector, port):
connector.connect(('127.0.0.1', port))
connector.connect((DEFAULT_CONNECT, port))
try:
# Note: We get ResourceWarning about 'x'
# on Python 3 if we don't join the spawned thread
......@@ -357,10 +352,8 @@ class TestSSL(Test):
self.assert_closed(s, fileno)
def test_server_simple(self):
listener = socket.socket()
listener.bind(('127.0.0.1', 0))
listener = tcp_listener(backlog=1)
port = listener.getsockname()[1]
listener.listen(1)
connector = socket.socket()
self._close_on_teardown(connector)
......@@ -381,12 +374,8 @@ class TestSSL(Test):
self.__cleanup(t, listener, connector)
def test_server_makefile1(self):
listener = socket.socket()
self._close_on_teardown(listener)
listener.bind(('127.0.0.1', 0))
listener = self._close_on_teardown(tcp_listener(backlog=1))
port = listener.getsockname()[1]
listener.listen(1)
connector = socket.socket()
self._close_on_teardown(connector)
......@@ -412,10 +401,8 @@ class TestSSL(Test):
def test_server_makefile2(self):
listener = socket.socket()
listener.bind(('127.0.0.1', 0))
listener = tcp_listener(backlog=1)
port = listener.getsockname()[1]
listener.listen(1)
connector = socket.socket()
self._close_on_teardown(connector)
......@@ -441,12 +428,9 @@ class TestSSL(Test):
self.__cleanup(t, connector, listener, client_socket)
def test_serverssl_makefile1(self):
listener = socket.socket()
listener = self._close_on_teardown(tcp_listener(backlog=1))
fileno = listener.fileno()
listener.bind(('127.0.0.1', 0))
port = listener.getsockname()[1]
listener.listen(1)
self._close_on_teardown(listener)
listener = ssl.wrap_socket(listener, keyfile=certfile, certfile=certfile)
connector = socket.socket()
......@@ -473,17 +457,14 @@ class TestSSL(Test):
"Not too worried about this before Python 3.7rc1. "
"https://travis-ci.org/gevent/gevent/jobs/327357684")
def test_serverssl_makefile2(self):
listener = socket.socket()
self._close_on_teardown(listener)
listener.bind(('127.0.0.1', 0))
listener = self._close_on_teardown(tcp_listener(backlog=1))
port = listener.getsockname()[1]
listener.listen(1)
listener = ssl.wrap_socket(listener, keyfile=certfile, certfile=certfile)
connector = socket.socket()
def connect():
connector.connect(('127.0.0.1', port))
connector.connect((DEFAULT_CONNECT, port))
s = ssl.wrap_socket(connector)
s.sendall(b'test_serverssl_makefile2')
s.close()
......@@ -513,4 +494,4 @@ class TestSSL(Test):
if __name__ == '__main__':
unittest.main()
greentest.main()
import sys
import gevent.testing as greentest
try:
import selectors # Do this before the patch, just to force it
......@@ -8,14 +7,16 @@ except ImportError:
from gevent.monkey import patch_all
patch_all()
if sys.platform != 'win32' and greentest.PY3:
@greentest.skipUnless(
not greentest.WIN and greentest.PY3,
"selectors only guaranteed on Python 3 and not windows"
)
class TestSelectors(greentest.TestCase):
class TestSelectors(greentest.TestCase):
def test_selectors_select_is_patched(self):
# https://github.com/gevent/gevent/issues/835
_select = selectors.SelectSelector._select
self.assertTrue(hasattr(_select, '_gevent_monkey'), dir(_select))
def test_selectors_select_is_patched(self):
# https://github.com/gevent/gevent/issues/835
_select = selectors.SelectSelector._select
self.assertTrue(hasattr(_select, '_gevent_monkey'), dir(_select))
if __name__ == '__main__':
......
......@@ -8,6 +8,7 @@ import os
import gevent.testing as greentest
from gevent.testing import PY3
from gevent.testing import DEFAULT_SOCKET_TIMEOUT as _DEFAULT_SOCKET_TIMEOUT
from gevent.testing.sockets import tcp_listener
from gevent import socket
import gevent
from gevent.server import StreamServer
......@@ -87,11 +88,7 @@ class TestCase(greentest.TestCase):
self.server = None
def get_listener(self):
sock = socket.socket()
sock.bind(('127.0.0.1', 0))
sock.listen(5)
self._close_on_teardown(sock)
return sock
return self._close_on_teardown(tcp_listener(backlog=5))
def get_server_host_port_family(self):
server_host = self.server.server_host
......@@ -307,7 +304,7 @@ class TestDefaultSpawn(TestCase):
self.server.stop()
def test_serve_forever(self):
self.server = self.ServerSubClass(('127.0.0.1', 0))
self.server = self.ServerSubClass((greentest.DEFAULT_BIND_ADDR, 0))
self.assertFalse(self.server.started)
self.assertConnectionRefused()
self._test_serve_forever()
......@@ -456,10 +453,7 @@ class TestSSLSocketNotAllowed(TestCase):
@unittest.skipUnless(hasattr(socket, 'ssl'), "Uses socket.ssl")
def test(self):
from gevent.socket import ssl
from gevent.socket import socket as gsocket
listener = gsocket()
listener.bind(('0.0.0.0', 0))
listener.listen(5)
listener = self._close_on_teardown(tcp_listener(backlog=5))
listener = ssl(listener)
self.assertRaises(TypeError, self.ServerSubClass, listener)
......
......@@ -19,102 +19,102 @@ def raise_Expected():
raise Expected('TestSignal')
if hasattr(signal, 'SIGALRM'):
class TestSignal(greentest.TestCase):
error_fatal = False
__timeout__ = greentest.LARGE_TIMEOUT
def test_handler(self):
self.assertRaises(TypeError, gevent.signal, signal.SIGALRM, 1)
def test_alarm(self):
sig = gevent.signal(signal.SIGALRM, raise_Expected)
assert sig.ref is False, repr(sig.ref)
sig.ref = True
assert sig.ref is True
sig.ref = False
@greentest.skipUnless(hasattr(signal, 'SIGALRM'),
"Uses SIGALRM")
class TestSignal(greentest.TestCase):
error_fatal = False
__timeout__ = greentest.LARGE_TIMEOUT
def test_handler(self):
self.assertRaises(TypeError, gevent.signal, signal.SIGALRM, 1)
def test_alarm(self):
sig = gevent.signal(signal.SIGALRM, raise_Expected)
assert sig.ref is False, repr(sig.ref)
sig.ref = True
assert sig.ref is True
sig.ref = False
try:
signal.alarm(1)
try:
signal.alarm(1)
try:
gevent.sleep(2)
raise AssertionError('must raise Expected')
except Expected as ex:
assert str(ex) == 'TestSignal', ex
# also let's check that alarm is persistent
signal.alarm(1)
try:
gevent.sleep(2)
raise AssertionError('must raise Expected')
except Expected as ex:
assert str(ex) == 'TestSignal', ex
finally:
sig.cancel() # pylint:disable=no-member
@greentest.skipIf((greentest.PY3
and greentest.CFFI_BACKEND
and cffi_version < pkg_resources.parse_version('1.11.3')),
"https://bitbucket.org/cffi/cffi/issues/352/systemerror-returned-a-result-with-an")
@greentest.ignores_leakcheck
def test_reload(self):
# The site module tries to set attributes
# on all the modules that are loaded (specifically, __file__).
# If gevent.signal is loaded, and is our compatibility shim,
# this used to fail on Python 2: sys.modules['gevent.signal'] has no
# __loader__ attribute, so site.py's main() function tries to do
# gevent.signal.__file__ = os.path.abspath(gevent.signal.__file__), which
# used to not be allowed. (Under Python 3, __loader__ is present so this
# doesn't happen). See
# https://github.com/gevent/gevent/issues/805
# This fails on Python 3.5 under linux (travis CI) but not
# locally on macOS with (for both libuv and libev cffi); sometimes it
# failed with libuv on Python 3.6 too, but not always:
# AttributeError: cffi library 'gevent.libuv._corecffi' has no function,
# constant or global variable named '__loader__'
# which in turn leads to:
# SystemError: <built-in function getattr> returned a result with an error set
# It's not safe to continue after a SystemError, so we just skip the test there.
# As of Jan 2018 with CFFI 1.11.2 this happens reliably on macOS 3.6 and 3.7
# as well.
# See https://bitbucket.org/cffi/cffi/issues/352/systemerror-returned-a-result-with-an
# This is fixed in 1.11.3
import gevent.signal # make sure it's in sys.modules pylint:disable=redefined-outer-name
assert gevent.signal
import site
if greentest.PY3:
from importlib import reload as reload_module
else:
# builtin on py2
reload_module = reload # pylint:disable=undefined-variable
gevent.sleep(2)
raise AssertionError('must raise Expected')
except Expected as ex:
assert str(ex) == 'TestSignal', ex
# also let's check that alarm is persistent
signal.alarm(1)
try:
reload_module(site)
except TypeError:
# Non-CFFI on Travis triggers this, for some reason,
# but only on 3.6, not 3.4 or 3.5, and not yet on 3.7.
# The only module seen to trigger this is __main__, i.e., this module.
# This is hard to trigger in a virtualenv since it appears they
# install their own site.py, different from the one that ships with
# Python 3.6., and at least the version I have doesn't mess with
# __cached__
assert greentest.PY36
import sys
for m in set(sys.modules.values()):
try:
if m.__cached__ is None:
print("Module has None __cached__", m, file=sys.stderr)
except AttributeError:
continue
gevent.sleep(2)
raise AssertionError('must raise Expected')
except Expected as ex:
assert str(ex) == 'TestSignal', ex
finally:
sig.cancel() # pylint:disable=no-member
@greentest.skipIf((greentest.PY3
and greentest.CFFI_BACKEND
and cffi_version < pkg_resources.parse_version('1.11.3')),
"https://bitbucket.org/cffi/cffi/issues/352/systemerror-returned-a-result-with-an")
@greentest.ignores_leakcheck
def test_reload(self):
# The site module tries to set attributes
# on all the modules that are loaded (specifically, __file__).
# If gevent.signal is loaded, and is our compatibility shim,
# this used to fail on Python 2: sys.modules['gevent.signal'] has no
# __loader__ attribute, so site.py's main() function tries to do
# gevent.signal.__file__ = os.path.abspath(gevent.signal.__file__), which
# used to not be allowed. (Under Python 3, __loader__ is present so this
# doesn't happen). See
# https://github.com/gevent/gevent/issues/805
# This fails on Python 3.5 under linux (travis CI) but not
# locally on macOS with (for both libuv and libev cffi); sometimes it
# failed with libuv on Python 3.6 too, but not always:
# AttributeError: cffi library 'gevent.libuv._corecffi' has no function,
# constant or global variable named '__loader__'
# which in turn leads to:
# SystemError: <built-in function getattr> returned a result with an error set
# It's not safe to continue after a SystemError, so we just skip the test there.
# As of Jan 2018 with CFFI 1.11.2 this happens reliably on macOS 3.6 and 3.7
# as well.
# See https://bitbucket.org/cffi/cffi/issues/352/systemerror-returned-a-result-with-an
# This is fixed in 1.11.3
import gevent.signal # make sure it's in sys.modules pylint:disable=redefined-outer-name
assert gevent.signal
import site
if greentest.PY3:
from importlib import reload as reload_module
else:
# builtin on py2
reload_module = reload # pylint:disable=undefined-variable
try:
reload_module(site)
except TypeError:
# Non-CFFI on Travis triggers this, for some reason,
# but only on 3.6, not 3.4 or 3.5, and not yet on 3.7.
# The only module seen to trigger this is __main__, i.e., this module.
# This is hard to trigger in a virtualenv since it appears they
# install their own site.py, different from the one that ships with
# Python 3.6., and at least the version I have doesn't mess with
# __cached__
assert greentest.PY36
import sys
for m in set(sys.modules.values()):
try:
if m.__cached__ is None:
print("Module has None __cached__", m, file=sys.stderr)
except AttributeError:
continue
if __name__ == '__main__':
greentest.main()
......@@ -15,31 +15,29 @@ from functools import wraps
from gevent.testing import six
from gevent.testing import LARGE_TIMEOUT
from gevent.testing import support
from gevent.testing import params
from gevent.testing.sockets import tcp_listener
from gevent.testing.skipping import skipWithoutExternalNetwork
# we use threading on purpose so that we can test both regular and gevent sockets with the same code
from threading import Thread as _Thread
errno_types = int
def wrap_error(func):
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except: # pylint:disable=bare-except
traceback.print_exc()
os._exit(2)
return wrapper
class Thread(_Thread):
def __init__(self, **kwargs):
target = kwargs.pop('target')
target = wrap_error(target)
_Thread.__init__(self, target=target, **kwargs)
@wraps(target)
def errors_are_fatal(*args, **kwargs):
try:
return target(*args, **kwargs)
except: # pylint:disable=bare-except
traceback.print_exc()
os._exit(2)
_Thread.__init__(self, target=errors_are_fatal, **kwargs)
self.start()
......@@ -73,19 +71,17 @@ class TestTCP(greentest.TestCase):
self.port = self.listener.getsockname()[1]
def _setup_listener(self):
listener = socket.socket()
greentest.bind_and_listen(listener, ('127.0.0.1', 0))
return listener
return tcp_listener()
def create_connection(self, host='127.0.0.1', port=None, timeout=None,
def create_connection(self, host=None, port=None, timeout=None,
blocking=None):
sock = socket.socket()
sock.connect((host, port or self.port))
sock = self._close_on_teardown(socket.socket())
sock.connect((host or params.DEFAULT_CONNECT, port or self.port))
if timeout is not None:
sock.settimeout(timeout)
if blocking is not None:
sock.setblocking(blocking)
return self._close_on_teardown(sock)
return sock
def _test_sendall(self, data, match_data=None, client_method='sendall',
**client_args):
......@@ -305,6 +301,7 @@ class TestTCP(greentest.TestCase):
finally:
s.close()
@skipWithoutExternalNetwork("Tries to resolve hostname")
def test_connect_ex_gaierror(self):
# Issue 841
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
......@@ -341,7 +338,7 @@ class TestTCP(greentest.TestCase):
conn.close()
acceptor = Thread(target=accept_once)
s.connect(('127.0.0.1', self.port))
s.connect((params.DEFAULT_CONNECT, self.port))
fd = s.makefile(mode='rb')
self.assertEqual(fd.readline(), b'hello\n')
......@@ -363,7 +360,9 @@ class TestCreateConnection(greentest.TestCase):
# where/why we would get '[errno -2] name or service not known'
# but it seems some systems generate that.
# https://github.com/gevent/gevent/issues/1389
'refused|not known'
# Somehow extremly rarely we've also seen 'address already in use',
# which makes even less sense.
'refused|not known|already in use'
):
socket.create_connection(
(greentest.DEFAULT_BIND_ADDR, connect_port),
......@@ -380,6 +379,7 @@ class TestCreateConnection(greentest.TestCase):
@greentest.ignores_leakcheck
@skipWithoutExternalNetwork("Tries to resolve hostname")
def test_base_exception(self):
# such as a GreenletExit or a gevent.timeout.Timeout
......@@ -395,7 +395,7 @@ class TestCreateConnection(greentest.TestCase):
MockSocket.created += (self,)
def connect(self, _):
raise E()
raise E(_)
def close(self):
self.closed = True
......
......@@ -17,7 +17,7 @@ class Test(greentest.TestCase):
error_fatal = False
def setUp(self):
self.server = server.StreamServer(('127.0.0.1', 0), readall)
self.server = server.StreamServer(greentest.DEFAULT_BIND_ADDR_TUPLE, readall)
self.server.start()
def tearDown(self):
......@@ -25,7 +25,7 @@ class Test(greentest.TestCase):
def test_recv_closed(self):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('localhost', self.server.server_port))
sock.connect((greentest.DEFAULT_CONNECT_HOST, self.server.server_port))
receiver = gevent.spawn(sock.recv, 25)
try:
gevent.sleep(0.001)
......@@ -43,7 +43,7 @@ class Test(greentest.TestCase):
@greentest.skipOnLibuvOnCI("Sometimes randomly times out")
def test_recv_twice(self):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('localhost', self.server.server_port))
sock.connect((greentest.DEFAULT_CONNECT_HOST, self.server.server_port))
receiver = gevent.spawn(sock.recv, 25)
try:
gevent.sleep(0.001)
......
......@@ -20,6 +20,7 @@ from gevent.testing import util
from gevent.testing import six
from gevent.testing.six import xrange
from gevent.testing import flaky
from gevent.testing.skipping import skipWithoutExternalNetwork
resolver = gevent.get_hub().resolver
......@@ -225,7 +226,7 @@ def add(klass, hostname, name=None,
test5.__name__ = 'test_%s_getnameinfo' % name
_setattr(klass, test5.__name__, test5)
@skipWithoutExternalNetwork("Tries to resolve and compare hostnames/addrinfo")
class TestCase(greentest.TestCase):
__timeout__ = 30
......@@ -466,7 +467,7 @@ class TestBroadcast(TestCase):
add(TestBroadcast, '<broadcast>')
from gevent.resolver.dnspython import HostsFile
from gevent.resolver.dnspython import HostsFile # XXX: This will move.
class SanitizedHostsFile(HostsFile):
def iter_all_host_addr_pairs(self):
for name, addr in super(SanitizedHostsFile, self).iter_all_host_addr_pairs():
......@@ -485,6 +486,7 @@ class SanitizedHostsFile(HostsFile):
continue
yield name, addr
@greentest.skipIf(greentest.RUNNING_ON_CI,
"This sometimes randomly fails on Travis with ares and on appveyor, beginning Feb 13, 2018")
# Probably due to round-robin DNS,
......@@ -612,7 +614,7 @@ add(TestInternational, u'президент.рф', 'russian',
skip=(PY2 and RESOLVER_DNSPYTHON), skip_reason="dnspython can actually resolve these")
add(TestInternational, u'президент.рф'.encode('idna'), 'idna')
@skipWithoutExternalNetwork("Tries to resolve and compare hostnames/addrinfo")
class TestInterrupted_gethostbyname(gevent.testing.timing.AbstractGenericWaitTestCase):
# There are refs to a Waiter in the C code that don't go
......
......@@ -20,6 +20,7 @@
# THE SOFTWARE.
import gevent.testing as greentest
from gevent.testing import support
from gevent.socket import socket, error
try:
......@@ -33,13 +34,16 @@ class TestSocketErrors(greentest.TestCase):
__timeout__ = 5
def test_connection_refused(self):
port = support.find_unused_port()
s = socket()
self._close_on_teardown(s)
try:
s.connect(('127.0.0.1', 81))
s.connect((greentest.DEFAULT_CONNECT_HOST, port))
except error as ex:
assert ex.args[0] == ECONNREFUSED, repr(ex)
assert 'refused' in str(ex).lower(), str(ex)
self.assertEqual(ex.args[0], ECONNREFUSED, ex)
self.assertIn('refused', str(ex).lower())
else:
self.fail("Shouldn't have connected")
if __name__ == '__main__':
......
......@@ -13,7 +13,7 @@ class TestClosedSocket(greentest.TestCase):
sock.close()
try:
sock.send(b'a', timeout=1)
raise AssertionError("Should not get here")
self.fail("Should raise socket error")
except (socket.error, OSError) as ex:
if ex.args[0] != errno.EBADF:
if sys.platform.startswith('win'):
......@@ -30,12 +30,13 @@ class TestRef(greentest.TestCase):
switch_expected = False
def test(self):
# pylint:disable=no-member
sock = socket.socket()
assert sock.ref is True, sock.ref
self.assertTrue(sock.ref)
sock.ref = False
assert sock.ref is False, sock.ref
assert sock._read_event.ref is False, sock.ref
assert sock._write_event.ref is False, sock.ref
self.assertFalse(sock.ref)
self.assertFalse(sock._read_event.ref)
self.assertFalse(sock._write_event.ref)
sock.close()
......
......@@ -2,6 +2,7 @@
import unittest
import ctypes
import gevent.testing as greentest
class AnStructure(ctypes.Structure):
_fields_ = [("x", ctypes.c_int)]
......@@ -11,12 +12,12 @@ def _send(socket):
for meth in ('sendall', 'send'):
anStructure = AnStructure()
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect(('127.0.0.1', 12345))
sock.connect((greentest.DEFAULT_CONNECT_HOST, 12345))
getattr(sock, meth)(anStructure)
sock.close()
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect(('127.0.0.1', 12345))
sock.connect((greentest.DEFAULT_CONNECT_HOST, 12345))
sock.settimeout(1.0)
getattr(sock, meth)(anStructure)
sock.close()
......@@ -36,4 +37,4 @@ class TestSendGeventSocket(unittest.TestCase):
if __name__ == '__main__':
unittest.main()
greentest.main()
......@@ -2,7 +2,7 @@
from gevent import monkey
monkey.patch_all()
import unittest
try:
import httplib
except ImportError:
......@@ -13,17 +13,17 @@ import socket
import gevent.testing as greentest
@unittest.skipUnless(
@greentest.skipUnless(
hasattr(socket, 'ssl'),
"Needs socket.ssl"
"Needs socket.ssl (Python 2)"
)
@greentest.skipWithoutExternalNetwork("Tries to access amazon.com")
class AmazonHTTPSTests(greentest.TestCase):
__timeout__ = 30
def test_amazon_response(self):
conn = httplib.HTTPSConnection('sdb.amazonaws.com')
conn.debuglevel = 1
conn.request('GET', '/')
conn.getresponse()
......
......@@ -10,7 +10,6 @@ class Test(greentest.TestCase):
server_port = None
def _accept(self):
self.server.listen(1)
try:
conn, _ = self.server.accept()
self._close_on_teardown(conn)
......@@ -19,9 +18,7 @@ class Test(greentest.TestCase):
def setUp(self):
super(Test, self).setUp()
self.server = socket.socket()
self._close_on_teardown(self.server)
self.server.bind(('127.0.0.1', 0))
self.server = self._close_on_teardown(greentest.tcp_listener(backlog=1))
self.server_port = self.server.getsockname()[1]
self.acceptor = gevent.spawn(self._accept)
gevent.sleep(0)
......@@ -39,7 +36,7 @@ class Test(greentest.TestCase):
gevent.sleep(0)
sock = socket.socket()
self._close_on_teardown(sock)
sock.connect(('127.0.0.1', self.server_port))
sock.connect((greentest.DEFAULT_CONNECT_HOST, self.server_port))
sock.settimeout(0.1)
with self.assertRaises(socket.error) as cm:
......
......@@ -9,9 +9,16 @@ import test__socket
import ssl
import unittest
#import unittest
from gevent.hub import LoopExit
def ssl_listener(private_key, certificate):
raw_listener = socket.socket()
greentest.bind_and_listen(raw_listener)
sock = ssl.wrap_socket(raw_listener, private_key, certificate)
return sock, raw_listener
class TestSSL(test__socket.TestTCP):
certfile = os.path.join(os.path.dirname(__file__), 'test_server.crt')
......@@ -24,7 +31,7 @@ class TestSSL(test__socket.TestTCP):
TIMEOUT_ERROR = getattr(socket, 'sslerror', socket.timeout)
def _setup_listener(self):
listener, raw_listener = ssl_listener(('127.0.0.1', 0), self.privfile, self.certfile)
listener, raw_listener = ssl_listener(self.privfile, self.certfile)
self._close_on_teardown(raw_listener)
return listener
......@@ -65,10 +72,9 @@ class TestSSL(test__socket.TestTCP):
except LoopExit:
if greentest.LIBUV and greentest.WIN:
# XXX: Unable to duplicate locally
raise unittest.SkipTest("libuv on Windows sometimes raises LoopExit")
raise greentest.SkipTest("libuv on Windows sometimes raises LoopExit")
raise
@greentest.ignores_leakcheck
def test_empty_send(self):
# Issue 719
......@@ -93,11 +99,6 @@ class TestSSL(test__socket.TestTCP):
# Override; doesn't work with SSL sockets.
pass
def ssl_listener(address, private_key, certificate):
raw_listener = socket.socket()
greentest.bind_and_listen(raw_listener, address)
sock = ssl.wrap_socket(raw_listener, private_key, certificate)
return sock, raw_listener
if __name__ == '__main__':
......
......@@ -33,11 +33,11 @@ python_universal_newlines = hasattr(sys.stdout, 'newlines')
# See gevent.subprocess for more details.
python_universal_newlines_broken = PY3 and subprocess.mswindows
@greentest.skipWithoutResource('subprocess')
class Test(greentest.TestCase):
def setUp(self):
super(Test, self).setUp()
greentest.TestCase.setUp(self)
gc.collect()
gc.collect()
......@@ -186,27 +186,26 @@ class Test(greentest.TestCase):
finally:
p.stdout.close()
if sys.platform != 'win32':
def test_nonblock_removed(self):
# see issue #134
r, w = os.pipe()
stdin = subprocess.FileObject(r)
p = subprocess.Popen(['grep', 'text'], stdin=stdin)
try:
# Closing one half of the pipe causes Python 3 on OS X to terminate the
# child process; it exits with code 1 and the assert that p.poll is None
# fails. Removing the close lets it pass under both Python 3 and 2.7.
# If subprocess.Popen._remove_nonblock_flag is changed to a noop, then
# the test fails (as expected) even with the close removed
#os.close(w)
time.sleep(0.1)
self.assertEqual(p.poll(), None)
finally:
if p.poll() is None:
p.kill()
stdin.close()
os.close(w)
@greentest.skipOnWindows("Uses 'grep' command")
def test_nonblock_removed(self):
# see issue #134
r, w = os.pipe()
stdin = subprocess.FileObject(r)
p = subprocess.Popen(['grep', 'text'], stdin=stdin)
try:
# Closing one half of the pipe causes Python 3 on OS X to terminate the
# child process; it exits with code 1 and the assert that p.poll is None
# fails. Removing the close lets it pass under both Python 3 and 2.7.
# If subprocess.Popen._remove_nonblock_flag is changed to a noop, then
# the test fails (as expected) even with the close removed
#os.close(w)
time.sleep(0.1)
self.assertEqual(p.poll(), None)
finally:
if p.poll() is None:
p.kill()
stdin.close()
os.close(w)
def test_issue148(self):
for _ in range(7):
......@@ -387,6 +386,7 @@ class RunFuncTestCase(greentest.TestCase):
__timeout__ = greentest.LARGE_TIMEOUT
@greentest.skipWithoutResource('subprocess')
def run_python(self, code, **kwargs):
"""Run Python code in a subprocess using subprocess.run"""
argv = [sys.executable, "-c", code]
......
......@@ -9,6 +9,10 @@ if 'runtestcase' in sys.argv[1:]: # pragma: no cover
gevent.subprocess.Popen([sys.executable, '-c', '"1/0"'])
gevent.sleep(1)
else:
# XXX: Handle this more automatically. See comments in the testrunner.
from gevent.testing.resources import exit_without_resource
exit_without_resource('subprocess')
import subprocess
for _ in range(5):
out, err = subprocess.Popen([sys.executable, '-W', 'ignore',
......
import sys
# XXX: Handle this more automatically. See comments in the testrunner.
from gevent.testing.resources import exit_without_resource
exit_without_resource('subprocess')
from gevent.subprocess import Popen
from gevent.testing.util import alarm
......
test___example_servers.py
test___config.py
test___ident.py
test___monitor.py
test__ares_timeout.py
test__backdoor.py
test__close_backend_fd.py
test__events.py
test__example_echoserver.py
test__example_portforwarder.py
test__example_udp_client.py
test__example_wsgiserver.py
test__example_wsgiserver_ssl.py
test__example_webproxy.py
test__examples.py
test__getaddrinfo_import.py
test__example_portforwarder.py
test__hub_join.py
test__hub_join_timeout.py
test__issue330.py
test__iwait.py
test__monkey_scope.py
test__pywsgi.py
test__server.py
test__server_pywsgi.py
......@@ -12,15 +26,3 @@ test__socket_dns6.py
test__socket_errors.py
test__socket_send_memoryview.py
test__socket_timeout.py
test__examples.py
test__issue330.py
test___ident.py
test___config.py
test___monitor.py
test__events.py
test__monkey_scope.py
test__iwait.py
test__ares_timeout.py
test__close_backend_fd.py
test__hub_join.py
test__hub_join_timeout.py
......@@ -18,7 +18,7 @@ test__environ.py
test__event.py
# uses socket test__example_echoserver.py
# uses socket test__example_portforwarder.py
# uses socket test___example_servers.py
# uses socket test__example_w*.py
# uses bunch of things test__examples.py
# uses socket test__example_udp_client.py
# uses socket test__example_udp_server.py
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment