Commit 0138e418 authored by Jason Madden's avatar Jason Madden

Make gevent.killall do what Greenlet.kill does.

And stop a fresh greenlet from even running.

Fixes #1473.

Free user-provided resources as soon as the greenlet is cancelled, if it's not running.
parent ba3b15de
...@@ -101,10 +101,11 @@ dummy-variables-rgx=_.* ...@@ -101,10 +101,11 @@ dummy-variables-rgx=_.*
generated-members=exc_clear generated-members=exc_clear
# List of classes names for which member attributes should not be checked # List of classes names for which member attributes should not be checked
# (useful for classes with attributes dynamically set). This supports can work # (useful for classes with attributes dynamically set). This can work
# with qualified names. # with qualified names.
#ignored-classes=SSLContext, SSLSocket, greenlet, Greenlet, parent, dead #ignored-classes=SSLContext, SSLSocket, greenlet, Greenlet, parent, dead
# List of module names for which member attributes should not be checked # List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime # (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis. It # and thus existing member attributes cannot be deduced by static analysis. It
......
...@@ -81,6 +81,19 @@ ...@@ -81,6 +81,19 @@
raised an exception. This could happen if the hub's ``handle_error`` raised an exception. This could happen if the hub's ``handle_error``
function was poorly customized, for example. See :issue:`1482` function was poorly customized, for example. See :issue:`1482`
- Make ``gevent.killall`` stop greenlets from running that hadn't been
run yet. This make it consistent with ``Greenlet.kill()``. See
:issue:`1473` reported by kochelmonster.
- Make ``gevent.spawn_raw`` set the ``loop`` attribute on returned
greenlets. This lets them work with more gevent APIs, notably
``gevent.killall()``. They already had dictionaries, but this may
make them slightly larger, depending on platform (on CPython 2.7
through 3.6 there is no apparent difference for one attribute but on
CPython 3.7 and 3.8 dictionaries are initially empty and only
allocate space once an attribute is added; they're still smaller
than on earlier versions though).
1.5a2 (2019-10-21) 1.5a2 (2019-10-21)
================== ==================
......
...@@ -12,9 +12,9 @@ requires = [ ...@@ -12,9 +12,9 @@ requires = [
# 0.28 is faster, and (important!) lets us specify the target module # 0.28 is faster, and (important!) lets us specify the target module
# name to be created so that we can have both foo.py and _foo.so # name to be created so that we can have both foo.py and _foo.so
# at the same time. 0.29 fixes some issues with Python 3.7, # at the same time. 0.29 fixes some issues with Python 3.7,
# and adds the 3str mode for transition to Python 3. 0.29.12+ is # and adds the 3str mode for transition to Python 3. 0.29.14+ is
# required for Python 3.8 # required for Python 3.8
"Cython >= 0.29.13", "Cython >= 0.29.14",
# See version requirements in setup.py # See version requirements in setup.py
"cffi >= 1.12.3 ; platform_python_implementation == 'CPython'", "cffi >= 1.12.3 ; platform_python_implementation == 'CPython'",
# Python 3.7 requires at least 0.4.14, which is ABI incompatible with earlier # Python 3.7 requires at least 0.4.14, which is ABI incompatible with earlier
......
...@@ -10,6 +10,7 @@ import functools ...@@ -10,6 +10,7 @@ import functools
import warnings import warnings
from gevent._config import config from gevent._config import config
from gevent._util import LazyOnClass
try: try:
from tracemalloc import get_object_traceback from tracemalloc import get_object_traceback
...@@ -108,26 +109,6 @@ def only_if_watcher(func): ...@@ -108,26 +109,6 @@ def only_if_watcher(func):
return if_w return if_w
class LazyOnClass(object):
@classmethod
def lazy(cls, cls_dict, func):
"Put a LazyOnClass object in *cls_dict* with the same name as *func*"
cls_dict[func.__name__] = cls(func)
def __init__(self, func, name=None):
self.name = name or func.__name__
self.func = func
def __get__(self, inst, klass):
if inst is None: # pragma: no cover
return self
val = self.func(inst)
setattr(klass, self.name, val)
return val
class AbstractWatcherType(type): class AbstractWatcherType(type):
""" """
Base metaclass for watchers. Base metaclass for watchers.
......
...@@ -133,6 +133,9 @@ cdef class Greenlet(greenlet): ...@@ -133,6 +133,9 @@ cdef class Greenlet(greenlet):
cpdef rawlink(self, object callback) cpdef rawlink(self, object callback)
cpdef str _formatinfo(self) cpdef str _formatinfo(self)
# Helper for killall()
cpdef bint _maybe_kill_before_start(self, exception)
# This is a helper function for a @property getter; # This is a helper function for a @property getter;
# defining locals() for a @property doesn't seem to work. # defining locals() for a @property doesn't seem to work.
@cython.locals(reg=IdentRegistry) @cython.locals(reg=IdentRegistry)
...@@ -144,11 +147,13 @@ cdef class Greenlet(greenlet): ...@@ -144,11 +147,13 @@ cdef class Greenlet(greenlet):
cdef bint __never_started_or_killed(self) cdef bint __never_started_or_killed(self)
cdef bint __start_completed(self) cdef bint __start_completed(self)
cdef __handle_death_before_start(self, tuple args) cdef __handle_death_before_start(self, tuple args)
@cython.final
cdef inline void __free(self)
cdef __cancel_start(self) cdef __cancel_start(self)
cdef _report_result(self, object result) cdef inline __report_result(self, object result)
cdef _report_error(self, tuple exc_info) cdef inline __report_error(self, tuple exc_info)
# This is used as the target of a callback # This is used as the target of a callback
# from the loop, and so needs to be a cpdef # from the loop, and so needs to be a cpdef
cpdef _notify_links(self) cpdef _notify_links(self)
......
...@@ -119,7 +119,9 @@ class Lazy(object): ...@@ -119,7 +119,9 @@ class Lazy(object):
A non-data descriptor used just like @property. The A non-data descriptor used just like @property. The
difference is the function value is assigned to the instance difference is the function value is assigned to the instance
dict the first time it is accessed and then the function is never dict the first time it is accessed and then the function is never
called agoin. called again.
Contrast with `readproperty`.
""" """
def __init__(self, func): def __init__(self, func):
self.data = (func, func.__name__) self.data = (func, func.__name__)
...@@ -136,9 +138,16 @@ class Lazy(object): ...@@ -136,9 +138,16 @@ class Lazy(object):
class readproperty(object): class readproperty(object):
""" """
A non-data descriptor like @property. The difference is that A non-data descriptor similar to :class:`property`.
when the property is assigned to, it is cached in the instance
and the function is not called on that instance again. The difference is that the property can be assigned to directly,
without invoking a setter function. When the property is assigned
to, it is cached in the instance and the function is not called on
that instance again.
Contrast with `Lazy`, which caches the result of the function in the
instance the first time it is called and never calls the function on that
instance again.
""" """
def __init__(self, func): def __init__(self, func):
...@@ -151,6 +160,35 @@ class readproperty(object): ...@@ -151,6 +160,35 @@ class readproperty(object):
return self.func(inst) return self.func(inst)
class LazyOnClass(object):
"""
Similar to `Lazy`, but stores the value in the class.
This is useful when the getter is expensive and conceptually
a shared class value, but we don't want import-time side-effects
such as expensive imports because it may not always be used.
Probably doesn't mix well with inheritance?
"""
@classmethod
def lazy(cls, cls_dict, func):
"Put a LazyOnClass object in *cls_dict* with the same name as *func*"
cls_dict[func.__name__] = cls(func)
def __init__(self, func, name=None):
self.name = name or func.__name__
self.func = func
def __get__(self, inst, klass):
if inst is None: # pragma: no cover
return self
val = self.func(inst)
setattr(klass, self.name, val)
return val
def gmctime(): def gmctime():
""" """
Returns the current time as a string in RFC3339 format. Returns the current time as a string in RFC3339 format.
......
# Copyright (c) 2009-2012 Denis Bilenko. See LICENSE for details. # Copyright (c) 2009-2012 Denis Bilenko. See LICENSE for details.
# cython: auto_pickle=False,embedsignature=True,always_allow_keywords=False # cython: auto_pickle=False,embedsignature=True,always_allow_keywords=False
# pylint:disable=too-many-lines
from __future__ import absolute_import, print_function, division from __future__ import absolute_import, print_function, division
from sys import _getframe as sys_getframe from sys import _getframe as sys_getframe
...@@ -260,8 +260,10 @@ class Greenlet(greenlet): ...@@ -260,8 +260,10 @@ class Greenlet(greenlet):
#: start() and start_later() as those two objects, respectively. #: start() and start_later() as those two objects, respectively.
#: Once this becomes non-None, the Greenlet cannot be started again. Conversely, #: Once this becomes non-None, the Greenlet cannot be started again. Conversely,
#: kill() and throw() check for non-None to determine if this object has ever been #: kill() and throw() check for non-None to determine if this object has ever been
#: scheduled for starting. A placeholder _dummy_event is assigned by them to prevent #: scheduled for starting. A placeholder _cancelled_start_event is assigned by them to prevent
#: the greenlet from being started in the future, if necessary. #: the greenlet from being started in the future, if necessary.
#: In the usual case, this transitions as follows: None -> event -> _start_completed_event.
#: A value of None means we've never been started.
self._start_event = None self._start_event = None
self._notifier = None self._notifier = None
...@@ -393,15 +395,31 @@ class Greenlet(greenlet): ...@@ -393,15 +395,31 @@ class Greenlet(greenlet):
else: else:
@property @property
def dead(self): def dead(self):
"Boolean indicating that the greenlet is dead and will not run again." """
return self.__start_cancelled_by_kill() or self.__started_but_aborted() or greenlet.dead.__get__(self) Boolean indicating that the greenlet is dead and will not run again.
This is true if:
1. We were never started, but were :meth:`killed <kill>`
immediately after creation (not possible with :meth:`spawn`); OR
2. We were started, but were killed before running; OR
3. We have run and terminated (by raising an exception out of the
started function or by reaching the end of the started function).
"""
return (
self.__start_cancelled_by_kill()
or self.__started_but_aborted()
or greenlet.dead.__get__(self)
)
def __never_started_or_killed(self): def __never_started_or_killed(self):
return self._start_event is None return self._start_event is None
def __start_pending(self): def __start_pending(self):
return (self._start_event is not None return (
and (self._start_event.pending or getattr(self._start_event, 'active', False))) self._start_event is not None
and (self._start_event.pending or getattr(self._start_event, 'active', False))
)
def __start_cancelled_by_kill(self): def __start_cancelled_by_kill(self):
return self._start_event is _cancelled_start_event return self._start_event is _cancelled_start_event
...@@ -410,10 +428,12 @@ class Greenlet(greenlet): ...@@ -410,10 +428,12 @@ class Greenlet(greenlet):
return self._start_event is _start_completed_event return self._start_event is _start_completed_event
def __started_but_aborted(self): def __started_but_aborted(self):
return (not self.__never_started_or_killed() # we have been started or killed return (
not self.__never_started_or_killed() # we have been started or killed
and not self.__start_cancelled_by_kill() # we weren't killed, so we must have been started and not self.__start_cancelled_by_kill() # we weren't killed, so we must have been started
and not self.__start_completed() # the start never completed and not self.__start_completed() # the start never completed
and not self.__start_pending()) # and we're not pending, so we must have been aborted and not self.__start_pending() # and we're not pending, so we must have been aborted
)
def __cancel_start(self): def __cancel_start(self):
if self._start_event is None: if self._start_event is None:
...@@ -431,20 +451,25 @@ class Greenlet(greenlet): ...@@ -431,20 +451,25 @@ class Greenlet(greenlet):
def __handle_death_before_start(self, args): def __handle_death_before_start(self, args):
# args is (t, v, tb) or simply t or v # args is (t, v, tb) or simply t or v
if self._exc_info is None and self.dead: if self._exc_info is None and self.dead:
# the greenlet was never switched to before and it will never be, _report_error was not called # the greenlet was never switched to before and it will
# the result was not set and the links weren't notified. let's do it here. # never be; _report_error was not called, the result was
# checking that self.dead is true is essential, because throw() does not necessarily kill the greenlet # not set, and the links weren't notified. Let's do it
# (if the exception raised by throw() is caught somewhere inside the greenlet). # here.
#
# checking that self.dead is true is essential, because
# throw() does not necessarily kill the greenlet (if the
# exception raised by throw() is caught somewhere inside
# the greenlet).
if len(args) == 1: if len(args) == 1:
arg = args[0] arg = args[0]
#if isinstance(arg, type): if issubclass(arg, BaseException):
if type(arg) is type(Exception):
args = (arg, arg(), None) args = (arg, arg(), None)
else: else:
args = (type(arg), arg, None) args = (type(arg), arg, None)
elif not args: elif not args:
args = (GreenletExit, GreenletExit(), None) args = (GreenletExit, GreenletExit(), None)
self._report_error(args) assert issubclass(args[0], BaseException)
self.__report_error(args)
@property @property
def started(self): def started(self):
...@@ -661,14 +686,27 @@ class Greenlet(greenlet): ...@@ -661,14 +686,27 @@ class Greenlet(greenlet):
g.start_later(seconds) g.start_later(seconds)
return g return g
def _maybe_kill_before_start(self, exception):
# Helper for Greenlet.kill(), and also for killall()
self.__cancel_start()
self.__free()
dead = self.dead
if dead:
self.__handle_death_before_start((exception,))
return dead
def kill(self, exception=GreenletExit, block=True, timeout=None): def kill(self, exception=GreenletExit, block=True, timeout=None):
""" """
Raise the ``exception`` in the greenlet. Raise the ``exception`` in the greenlet.
If ``block`` is ``True`` (the default), wait until the greenlet dies or the optional timeout expires. If ``block`` is ``True`` (the default), wait until the greenlet
dies or the optional timeout expires; this may require switching
greenlets.
If block is ``False``, the current greenlet is not unscheduled. If block is ``False``, the current greenlet is not unscheduled.
The function always returns ``None`` and never raises an error. This function always returns ``None`` and never raises an error. It
may be called multpile times on the same greenlet object, and may be
called on an unstarted or dead greenlet.
.. note:: .. note::
...@@ -687,7 +725,9 @@ class Greenlet(greenlet): ...@@ -687,7 +725,9 @@ class Greenlet(greenlet):
Use care when killing greenlets. If the code executing is not Use care when killing greenlets. If the code executing is not
exception safe (e.g., makes proper use of ``finally``) then an exception safe (e.g., makes proper use of ``finally``) then an
unexpected exception could result in corrupted state. unexpected exception could result in corrupted state. Using
a :meth:`link` or :meth:`rawlink` (cheaper) may be a safer way to
clean up resources.
See also :func:`gevent.kill`. See also :func:`gevent.kill`.
...@@ -698,21 +738,17 @@ class Greenlet(greenlet): ...@@ -698,21 +738,17 @@ class Greenlet(greenlet):
.. versionchanged:: 0.13.0 .. versionchanged:: 0.13.0
*block* is now ``True`` by default. *block* is now ``True`` by default.
.. versionchanged:: 1.1a2 .. versionchanged:: 1.1a2
If this greenlet had never been switched to, killing it will prevent it from ever being switched to. If this greenlet had never been switched to, killing it will
prevent it from *ever* being switched to. Links (:meth:`rawlink`)
will still be executed, though.
""" """
self.__cancel_start() if not self._maybe_kill_before_start(exception):
if self.dead:
self.__handle_death_before_start((exception,))
else:
waiter = Waiter() if block else None # pylint:disable=undefined-variable waiter = Waiter() if block else None # pylint:disable=undefined-variable
hub = get_my_hub(self) # pylint:disable=undefined-variable hub = get_my_hub(self) # pylint:disable=undefined-variable
hub.loop.run_callback(_kill, self, exception, waiter) hub.loop.run_callback(_kill, self, exception, waiter)
if waiter is not None: if waiter is not None:
waiter.get() waiter.get()
self.join(timeout) self.join(timeout)
# it should be OK to use kill() in finally or kill a greenlet from more than one place;
# thus it should not raise when the greenlet is already killed (= not started)
def get(self, block=True, timeout=None): def get(self, block=True, timeout=None):
""" """
...@@ -786,16 +822,16 @@ class Greenlet(greenlet): ...@@ -786,16 +822,16 @@ class Greenlet(greenlet):
self.unlink(switch) self.unlink(switch)
raise raise
def _report_result(self, result): def __report_result(self, result):
self._exc_info = (None, None, None) self._exc_info = (None, None, None)
self.value = result self.value = result
if self._links and not self._notifier: if self._links and not self._notifier:
hub = get_my_hub(self) # pylint:disable=undefined-variable hub = get_my_hub(self) # pylint:disable=undefined-variable
self._notifier = hub.loop.run_callback(self._notify_links) self._notifier = hub.loop.run_callback(self._notify_links)
def _report_error(self, exc_info): def __report_error(self, exc_info):
if isinstance(exc_info[1], GreenletExit): if isinstance(exc_info[1], GreenletExit):
self._report_result(exc_info[1]) self.__report_result(exc_info[1])
return return
self._exc_info = exc_info[0], exc_info[1], dump_traceback(exc_info[2]) self._exc_info = exc_info[0], exc_info[1], dump_traceback(exc_info[2])
...@@ -817,11 +853,21 @@ class Greenlet(greenlet): ...@@ -817,11 +853,21 @@ class Greenlet(greenlet):
try: try:
result = self._run(*self.args, **self.kwargs) result = self._run(*self.args, **self.kwargs)
except: # pylint:disable=bare-except except: # pylint:disable=bare-except
self._report_error(sys_exc_info()) self.__report_error(sys_exc_info())
return else:
self._report_result(result) self.__report_result(result)
finally: finally:
self.__dict__.pop('_run', None) self.__free()
def __free(self):
try:
# It seems that Cython 0.29.13 sometimes miscompiles
# self.__dict__.pop('_run', None) ? When we moved this out of the
# inline finally: block in run(), we started getting strange
# exceptions from places that subclassed Greenlet.
del self._run
except AttributeError:
pass
self.args = () self.args = ()
self.kwargs.clear() self.kwargs.clear()
...@@ -850,7 +896,9 @@ class Greenlet(greenlet): ...@@ -850,7 +896,9 @@ class Greenlet(greenlet):
The *callback* will be called with this instance as an The *callback* will be called with this instance as an
argument. argument.
.. caution:: The callable will be called in the HUB greenlet. .. caution::
The *callback* will be called in the hub and
**MUST NOT** raise an exception.
""" """
if not callable(callback): if not callable(callback):
raise TypeError('Expected callable: %r' % (callback, )) raise TypeError('Expected callable: %r' % (callback, ))
...@@ -1007,15 +1055,16 @@ _spawn_callbacks = None ...@@ -1007,15 +1055,16 @@ _spawn_callbacks = None
def killall(greenlets, exception=GreenletExit, block=True, timeout=None): def killall(greenlets, exception=GreenletExit, block=True, timeout=None):
""" """
Forceably terminate all the ``greenlets`` by causing them to raise ``exception``. Forceably terminate all the *greenlets* by causing them to raise *exception*.
.. caution:: Use care when killing greenlets. If they are not prepared for exceptions, .. caution:: Use care when killing greenlets. If they are not prepared for exceptions,
this could result in corrupted state. this could result in corrupted state.
:param greenlets: A **bounded** iterable of the non-None greenlets to terminate. :param greenlets: A **bounded** iterable of the non-None greenlets to terminate.
*All* the items in this iterable must be greenlets that belong to the same hub, *All* the items in this iterable must be greenlets that belong to the same hub,
which should be the hub for this current thread. which should be the hub for this current thread. If this is a generator or iterator
:keyword exception: The exception to raise in the greenlets. By default this is that switches greenlets, the results are undefined.
:keyword exception: The type of exception to raise in the greenlets. By default this is
:class:`GreenletExit`. :class:`GreenletExit`.
:keyword bool block: If True (the default) then this function only returns when all the :keyword bool block: If True (the default) then this function only returns when all the
greenlets are dead; the current greenlet is unscheduled during that process. greenlets are dead; the current greenlet is unscheduled during that process.
...@@ -1031,15 +1080,38 @@ def killall(greenlets, exception=GreenletExit, block=True, timeout=None): ...@@ -1031,15 +1080,38 @@ def killall(greenlets, exception=GreenletExit, block=True, timeout=None):
.. versionchanged:: 1.1a2 .. versionchanged:: 1.1a2
*greenlets* can be any iterable of greenlets, like an iterator or a set. *greenlets* can be any iterable of greenlets, like an iterator or a set.
Previously it had to be a list or tuple. Previously it had to be a list or tuple.
.. versionchanged:: 1.5a3
Any :class:`Greenlet` in the *greenlets* list that hadn't been switched to before
calling this method will never be switched to. This makes this function
behave like :meth:`Greenlet.kill`. This does not apply to raw greenlets.
.. versionchanged:: 1.5a3
Now accepts raw greenlets created by :func:`gevent.spawn_raw`.
""" """
# support non-indexable containers like iterators or set objects
greenlets = list(greenlets) need_killed = [] # type: list
if not greenlets: for glet in greenlets:
# Quick pass through to prevent any greenlet from
# actually being switched to if it hasn't already.
# (Previously we called ``list(greenlets)`` so we're still
# linear.)
#
# We don't use glet.kill() here because we don't want to schedule
# any callbacks in the loop; we're about to handle that more directly.
try:
cancel = glet._maybe_kill_before_start
except AttributeError:
need_killed.append(glet)
else:
if not cancel(exception):
need_killed.append(glet)
if not need_killed:
return return
loop = greenlets[0].loop
loop = glet.loop # pylint:disable=undefined-loop-variable
if block: if block:
waiter = Waiter() # pylint:disable=undefined-variable waiter = Waiter() # pylint:disable=undefined-variable
loop.run_callback(_killall3, greenlets, exception, waiter) loop.run_callback(_killall3, need_killed, exception, waiter)
t = Timeout._start_new_or_dummy(timeout) t = Timeout._start_new_or_dummy(timeout)
try: try:
alive = waiter.get() alive = waiter.get()
...@@ -1048,7 +1120,7 @@ def killall(greenlets, exception=GreenletExit, block=True, timeout=None): ...@@ -1048,7 +1120,7 @@ def killall(greenlets, exception=GreenletExit, block=True, timeout=None):
finally: finally:
t.cancel() t.cancel()
else: else:
loop.run_callback(_killall, greenlets, exception) loop.run_callback(_killall, need_killed, exception)
def _init(): def _init():
greenlet_init() # pylint:disable=undefined-variable greenlet_init() # pylint:disable=undefined-variable
......
...@@ -96,12 +96,16 @@ def spawn_raw(function, *args, **kwargs): ...@@ -96,12 +96,16 @@ def spawn_raw(function, *args, **kwargs):
if ``GEVENT_TRACK_GREENLET_TREE`` is enabled (the default). If not enabled, if ``GEVENT_TRACK_GREENLET_TREE`` is enabled (the default). If not enabled,
those attributes will not be set. those attributes will not be set.
.. versionchanged:: 1.5a3
The returned greenlet always has a *loop* attribute matching the
current hub's loop. This helps it work better with more gevent APIs.
""" """
if not callable(function): if not callable(function):
raise TypeError("function must be callable") raise TypeError("function must be callable")
# The hub is always the parent. # The hub is always the parent.
hub = _get_hub_noargs() hub = _get_hub_noargs()
loop = hub.loop
factory = TrackedRawGreenlet if GEVENT_CONFIG.track_greenlet_tree else RawGreenlet factory = TrackedRawGreenlet if GEVENT_CONFIG.track_greenlet_tree else RawGreenlet
...@@ -111,11 +115,11 @@ def spawn_raw(function, *args, **kwargs): ...@@ -111,11 +115,11 @@ def spawn_raw(function, *args, **kwargs):
if kwargs: if kwargs:
function = _functools_partial(function, *args, **kwargs) function = _functools_partial(function, *args, **kwargs)
g = factory(function, hub) g = factory(function, hub)
hub.loop.run_callback(g.switch) loop.run_callback(g.switch)
else: else:
g = factory(function, hub) g = factory(function, hub)
hub.loop.run_callback(g.switch, *args) loop.run_callback(g.switch, *args)
g.loop = hub.loop
return g return g
......
...@@ -27,6 +27,7 @@ from unittest import TestCase as BaseTestCase ...@@ -27,6 +27,7 @@ from unittest import TestCase as BaseTestCase
from functools import wraps from functools import wraps
import gevent import gevent
from gevent._util import LazyOnClass
from . import sysinfo from . import sysinfo
from . import params from . import params
...@@ -77,6 +78,66 @@ class TimeAssertMixin(object): ...@@ -77,6 +78,66 @@ class TimeAssertMixin(object):
fuzzy=(0.01 if not sysinfo.EXPECT_POOR_TIMER_RESOLUTION and not sysinfo.LIBUV else 1.0)): fuzzy=(0.01 if not sysinfo.EXPECT_POOR_TIMER_RESOLUTION and not sysinfo.LIBUV else 1.0)):
return self.runs_in_given_time(0.0, fuzzy) return self.runs_in_given_time(0.0, fuzzy)
class GreenletAssertMixin(object):
"""Assertions related to greenlets."""
def assert_greenlet_ready(self, g):
self.assertTrue(g.dead, g)
self.assertTrue(g.ready(), g)
self.assertFalse(g, g)
def assert_greenlet_not_ready(self, g):
self.assertFalse(g.dead, g)
self.assertFalse(g.ready(), g)
def assert_greenlet_spawned(self, g):
self.assertTrue(g.started, g)
self.assertFalse(g.dead, g)
# No difference between spawned and switched-to once
assert_greenlet_started = assert_greenlet_spawned
def assert_greenlet_finished(self, g):
self.assertFalse(g.started, g)
self.assertTrue(g.dead, g)
class StringAssertMixin(object):
"""
Assertions dealing with strings.
"""
@LazyOnClass
def HEX_NUM_RE(self):
import re
return re.compile('-?0x[0123456789abcdef]+L?', re.I)
def normalize_addr(self, s, replace='X'):
# https://github.com/PyCQA/pylint/issues/1127
return self.HEX_NUM_RE.sub(replace, s) # pylint:disable=no-member
def normalize_module(self, s, module=None, replace='module'):
if module is None:
module = type(self).__module__
return s.replace(module, replace)
def normalize(self, s):
return self.normalize_module(self.normalize_addr(s))
def assert_nstr_endswith(self, o, val):
s = str(o)
n = self.normalize(s)
self.assertTrue(n.endswith(val), (s, n))
def assert_nstr_startswith(self, o, val):
s = str(o)
n = self.normalize(s)
self.assertTrue(n.startswith(val), (s, n))
class TestTimeout(gevent.Timeout): class TestTimeout(gevent.Timeout):
_expire_info = '' _expire_info = ''
...@@ -178,7 +239,11 @@ class SubscriberCleanupMixin(object): ...@@ -178,7 +239,11 @@ class SubscriberCleanupMixin(object):
class TestCase(TestCaseMetaClass("NewBase", class TestCase(TestCaseMetaClass("NewBase",
(SubscriberCleanupMixin, TimeAssertMixin, BaseTestCase,), (SubscriberCleanupMixin,
TimeAssertMixin,
GreenletAssertMixin,
StringAssertMixin,
BaseTestCase,),
{})): {})):
__timeout__ = params.LOCAL_TIMEOUT if not sysinfo.RUNNING_ON_CI else params.CI_TIMEOUT __timeout__ = params.LOCAL_TIMEOUT if not sysinfo.RUNNING_ON_CI else params.CI_TIMEOUT
......
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # 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 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE. # THE SOFTWARE.
import re import functools
import unittest import unittest
import gevent.testing as greentest import gevent.testing as greentest
...@@ -32,6 +32,7 @@ from gevent.queue import Queue, Channel ...@@ -32,6 +32,7 @@ from gevent.queue import Queue, Channel
from gevent.testing.timing import AbstractGenericWaitTestCase from gevent.testing.timing import AbstractGenericWaitTestCase
from gevent.testing.timing import AbstractGenericGetTestCase from gevent.testing.timing import AbstractGenericGetTestCase
from gevent.testing import timing from gevent.testing import timing
from gevent.testing import ignores_leakcheck
DELAY = timing.SMALL_TICK DELAY = timing.SMALL_TICK
greentest.TestCase.error_fatal = False greentest.TestCase.error_fatal = False
...@@ -90,7 +91,7 @@ class TestLink(greentest.TestCase): ...@@ -90,7 +91,7 @@ class TestLink(greentest.TestCase):
p2.link(q.put) p2.link(q.put)
p3.link(q.put) p3.link(q.put)
results = [q.get().get(), q.get().get(), q.get().get()] results = [q.get().get(), q.get().get(), q.get().get()]
assert sorted(results) == [101, 102, 103], results self.assertEqual(sorted(results), [101, 102, 103], results)
class TestUnlink(greentest.TestCase): class TestUnlink(greentest.TestCase):
...@@ -157,8 +158,10 @@ class LinksTestCase(greentest.TestCase): ...@@ -157,8 +158,10 @@ class LinksTestCase(greentest.TestCase):
return event, queue return event, queue
def check_timed_out(self, event, queue): def check_timed_out(self, event, queue):
assert with_timeout(DELAY, event.get, timeout_value=X) is X, repr(event.get()) got = with_timeout(DELAY, event.get, timeout_value=X)
assert with_timeout(DELAY, queue.get, timeout_value=X) is X, queue.get() self.assertIs(got, X)
got = with_timeout(DELAY, queue.get, timeout_value=X)
self.assertIs(got, X)
def return25(): def return25():
...@@ -233,12 +236,12 @@ class TestRaise_link(LinksTestCase): ...@@ -233,12 +236,12 @@ class TestRaise_link(LinksTestCase):
xxxxx = self.set_links_timeout(p.link_value) xxxxx = self.set_links_timeout(p.link_value)
sleep(DELAY) sleep(DELAY)
assert not p, p self.assertFalse(p, p)
self.assertRaises(ExpectedError, event.get) self.assertRaises(ExpectedError, event.get)
self.assertEqual(queue.get(), p) self.assertEqual(queue.get(), p)
sleep(DELAY) sleep(DELAY)
assert not callback_flag, callback_flag self.assertFalse(callback_flag, callback_flag)
self.check_timed_out(*xxxxx) self.check_timed_out(*xxxxx)
...@@ -274,6 +277,7 @@ class TestStuff(greentest.TestCase): ...@@ -274,6 +277,7 @@ class TestStuff(greentest.TestCase):
x.link(e) x.link(e)
self.assertEqual(e.get(), 1) self.assertEqual(e.get(), 1)
@ignores_leakcheck
def test_wait_error(self): def test_wait_error(self):
def x(): def x():
...@@ -284,8 +288,8 @@ class TestStuff(greentest.TestCase): ...@@ -284,8 +288,8 @@ class TestStuff(greentest.TestCase):
self.assertRaises(ExpectedError, gevent.joinall, [x, y], raise_error=True) self.assertRaises(ExpectedError, gevent.joinall, [x, y], raise_error=True)
self.assertRaises(ExpectedError, gevent.joinall, [y], raise_error=True) self.assertRaises(ExpectedError, gevent.joinall, [y], raise_error=True)
x.join() x.join()
test_wait_error.ignore_leakcheck = True
@ignores_leakcheck
def test_joinall_exception_order(self): def test_joinall_exception_order(self):
# if there're several exceptions raised, the earliest one must be raised by joinall # if there're several exceptions raised, the earliest one must be raised by joinall
def first(): def first():
...@@ -293,12 +297,10 @@ class TestStuff(greentest.TestCase): ...@@ -293,12 +297,10 @@ class TestStuff(greentest.TestCase):
raise ExpectedError('first') raise ExpectedError('first')
a = gevent.spawn(first) a = gevent.spawn(first)
b = gevent.spawn(lambda: getcurrent().throw(ExpectedError('second'))) b = gevent.spawn(lambda: getcurrent().throw(ExpectedError('second')))
try: with self.assertRaisesRegex(ExpectedError, 'second'):
gevent.joinall([a, b], raise_error=True) gevent.joinall([a, b], raise_error=True)
except ExpectedError as ex:
assert 'second' in str(ex), repr(str(ex))
gevent.joinall([a, b]) gevent.joinall([a, b])
test_joinall_exception_order.ignore_leakcheck = True
def test_joinall_count_raise_error(self): def test_joinall_count_raise_error(self):
# When joinall is asked not to raise an error, the 'count' param still # When joinall is asked not to raise an error, the 'count' param still
...@@ -314,12 +316,12 @@ class TestStuff(greentest.TestCase): ...@@ -314,12 +316,12 @@ class TestStuff(greentest.TestCase):
raiser = gevent.spawn(raises_but_ignored) raiser = gevent.spawn(raises_but_ignored)
gevent.joinall([sleeper, raiser], raise_error=False, count=1) gevent.joinall([sleeper, raiser], raise_error=False, count=1)
assert_ready(raiser) self.assert_greenlet_ready(raiser)
assert_not_ready(sleeper) self.assert_greenlet_not_ready(sleeper)
# Clean up our mess # Clean up our mess
sleeper.kill() sleeper.kill()
assert_ready(sleeper) self.assert_greenlet_ready(sleeper)
def test_multiple_listeners_error(self): def test_multiple_listeners_error(self):
# if there was an error while calling a callback # if there was an error while calling a callback
...@@ -408,8 +410,6 @@ class A(object): ...@@ -408,8 +410,6 @@ class A(object):
def method(self): def method(self):
pass pass
hexobj = re.compile('-?0x[0123456789abcdef]+L?', re.I)
class Subclass(gevent.Greenlet): class Subclass(gevent.Greenlet):
pass pass
...@@ -417,47 +417,38 @@ class TestStr(greentest.TestCase): ...@@ -417,47 +417,38 @@ class TestStr(greentest.TestCase):
def test_function(self): def test_function(self):
g = gevent.Greenlet.spawn(dummy_test_func) g = gevent.Greenlet.spawn(dummy_test_func)
self.assertTrue(hexobj.sub('X', str(g)).endswith('at X: dummy_test_func>')) self.assert_nstr_endswith(g, 'at X: dummy_test_func>')
assert_not_ready(g) self.assert_greenlet_not_ready(g)
g.join() g.join()
assert_ready(g) self.assert_greenlet_ready(g)
self.assertTrue(hexobj.sub('X', str(g)).endswith(' at X: dummy_test_func>'), str(g)) self.assert_nstr_endswith(g, 'at X: dummy_test_func>')
def test_method(self): def test_method(self):
g = gevent.Greenlet.spawn(A().method) g = gevent.Greenlet.spawn(A().method)
str_g = hexobj.sub('X', str(g)) self.assert_nstr_startswith(g, '<Greenlet at X:')
str_g = str_g.replace(__name__, 'module')
self.assertTrue(str_g.startswith('<Greenlet at X:'), str_g)
# Accessing the name to generate a minimal_ident will cause it to be included. # Accessing the name to generate a minimal_ident will cause it to be included.
getattr(g, 'name') getattr(g, 'name')
str_g = hexobj.sub('X', str(g)) self.assert_nstr_startswith(g, '<Greenlet "Greenlet-')
str_g = str_g.replace(__name__, 'module')
self.assertTrue(str_g.startswith('<Greenlet "Greenlet-'), str_g)
# Assigning to the name changes it # Assigning to the name changes it
g.name = 'Foo' g.name = 'Foo'
str_g = hexobj.sub('X', str(g)) self.assert_nstr_startswith(g, '<Greenlet "Foo"')
str_g = str_g.replace(__name__, 'module')
self.assertTrue(str_g.startswith('<Greenlet "Foo"'), str_g)
self.assertTrue(str_g.endswith('at X: <bound method A.method of <module.A object at X>>>')) self.assert_nstr_endswith(g, 'at X: <bound method A.method of <module.A object at X>>>')
assert_not_ready(g) self.assert_greenlet_not_ready(g)
g.join() g.join()
assert_ready(g) self.assert_greenlet_ready(g)
str_g = hexobj.sub('X', str(g)) self.assert_nstr_endswith(g, 'at X: <bound method A.method of <module.A object at X>>>')
str_g = str_g.replace(__name__, 'module')
self.assertTrue(str_g.endswith('at X: <bound method A.method of <module.A object at X>>>'))
def test_subclass(self): def test_subclass(self):
g = Subclass() g = Subclass()
str_g = hexobj.sub('X', str(g)) self.assert_nstr_startswith(g, '<Subclass ')
str_g = str_g.replace(__name__, 'module') self.assert_nstr_endswith(g, 'at X: _run>')
self.assertTrue(str_g.startswith('<Subclass '), str_g)
self.assertTrue(str_g.endswith('at X: _run>'))
g = Subclass(None, 'question', answer=42) g = Subclass(None, 'question', answer=42)
str_g = hexobj.sub('X', str(g)) self.assert_nstr_endswith(g, " at X: _run('question', answer=42)>")
str_g = str_g.replace(__name__, 'module')
self.assertTrue(str_g.endswith(" at X: _run('question', answer=42)>"))
class TestJoin(AbstractGenericWaitTestCase): class TestJoin(AbstractGenericWaitTestCase):
...@@ -518,7 +509,7 @@ class TestBasic(greentest.TestCase): ...@@ -518,7 +509,7 @@ class TestBasic(greentest.TestCase):
g = gevent.spawn_raw(f, 1, name='value') g = gevent.spawn_raw(f, 1, name='value')
gevent.sleep(0.01) gevent.sleep(0.01)
assert not g self.assertFalse(g)
self.assertEqual(value[0], (1,)) self.assertEqual(value[0], (1,))
self.assertEqual(value[1], {'name': 'value'}) self.assertEqual(value[1], {'name': 'value'})
...@@ -567,7 +558,13 @@ class TestBasic(greentest.TestCase): ...@@ -567,7 +558,13 @@ class TestBasic(greentest.TestCase):
self.assertEqual(g.value, 5) # changed self.assertEqual(g.value, 5) # changed
self.assertIsNone(g.exception, g) self.assertIsNone(g.exception, g)
self.assertTrue(link_test == [g] or greentest.RUNNING_ON_CI, link_test) # changed self._check_flaky_eq(link_test, g)
def _check_flaky_eq(self, link_test, g):
if not greentest.RUNNING_ON_CI:
# TODO: Change this to assertEqualFlakyRaceCondition and figure
# out what the CI issue is.
self.assertEqual(link_test, [g]) # changed
def test_error_exit(self): def test_error_exit(self):
link_test = [] link_test = []
...@@ -591,91 +588,7 @@ class TestBasic(greentest.TestCase): ...@@ -591,91 +588,7 @@ class TestBasic(greentest.TestCase):
self.assertFalse(g.successful()) self.assertFalse(g.successful())
self.assertIsNone(g.value) # not changed self.assertIsNone(g.value) # not changed
self.assertEqual(g.exception.myattr, 5) self.assertEqual(g.exception.myattr, 5)
self._check_flaky_eq(link_test, g)
assert link_test == [g] or greentest.RUNNING_ON_APPVEYOR, link_test
def _assertKilled(self, g):
assert not g
assert g.dead
assert not g.started
assert g.ready()
assert g.successful(), (repr(g), g.value, g.exception)
assert isinstance(g.value, gevent.GreenletExit), (repr(g), g.value, g.exception)
assert g.exception is None
def assertKilled(self, g):
self._assertKilled(g)
gevent.sleep(0.01)
self._assertKilled(g)
def _test_kill(self, g, block):
g.kill(block=block)
if not block:
gevent.sleep(0.01)
self.assertKilled(g)
# kill second time must not hurt
g.kill(block=block)
self.assertKilled(g)
def _test_kill_not_started(self, block):
link_test = []
result = []
g = gevent.Greenlet(lambda: result.append(1))
g.link(link_test.append)
self._test_kill(g, block=block)
assert not result
assert link_test == [g]
def test_kill_not_started_block(self):
self._test_kill_not_started(block=True)
def test_kill_not_started_noblock(self):
self._test_kill_not_started(block=False)
def _test_kill_just_started(self, block):
result = []
link_test = []
g = gevent.Greenlet(lambda: result.append(1))
g.link(link_test.append)
g.start()
self._test_kill(g, block=block)
assert not result, result
assert link_test == [g]
def test_kill_just_started_block(self):
self._test_kill_just_started(block=True)
def test_kill_just_started_noblock(self):
self._test_kill_just_started(block=False)
def _test_kill_just_started_later(self, block):
result = []
link_test = []
g = gevent.Greenlet(lambda: result.append(1))
g.link(link_test.append)
g.start_later(1)
self._test_kill(g, block=block)
assert not result
def test_kill_just_started_later_block(self):
self._test_kill_just_started_later(block=True)
def test_kill_just_started_later_noblock(self):
self._test_kill_just_started_later(block=False)
def _test_kill_running(self, block):
link_test = []
g = gevent.spawn(gevent.sleep, 10)
g.link(link_test.append)
self._test_kill(g, block=block)
gevent.sleep(0.01)
assert link_test == [g]
def test_kill_running_block(self):
self._test_kill_running(block=True)
def test_kill_running_noblock(self):
self._test_kill_running(block=False)
def test_exc_info_no_error(self): def test_exc_info_no_error(self):
# Before running # Before running
...@@ -776,34 +689,120 @@ class TestBasic(greentest.TestCase): ...@@ -776,34 +689,120 @@ class TestBasic(greentest.TestCase):
g.kill() g.kill()
class TestKill(greentest.TestCase):
def __assertKilled(self, g):
self.assertFalse(g)
self.assertTrue(g.dead)
self.assertFalse(g.started)
self.assertTrue(g.ready())
self.assertTrue(g.successful(), (repr(g), g.value, g.exception))
self.assertIsInstance(g.value, gevent.GreenletExit)
self.assertIsNone(g.exception)
class TestStart(greentest.TestCase): def assertKilled(self, g):
self.__assertKilled(g)
gevent.sleep(0.01) # spin the loop to make sure it doesn't run.
self.__assertKilled(g)
def __kill_greenlet(self, g, block, killall):
if killall:
killer = functools.partial(gevent.killall, [g], block=block)
else:
killer = functools.partial(g.kill, block=block)
killer()
if not block:
# Must spin the loop to take effect (if it was scheduled)
gevent.sleep(timing.SMALLEST_RELIABLE_DELAY)
self.assertKilled(g)
# kill second time must not hurt
killer()
self.assertKilled(g)
def test(self): @staticmethod
g = gevent.spawn(gevent.sleep, 0.01) def _run_in_greenlet(result_collector):
assert g.started result_collector.append(1)
assert not g.dead
g.start() def _start_greenlet(self, g):
assert g.started """
assert not g.dead Subclasses should override. This doesn't actually start a greenlet.
g.join() """
assert not g.started
assert g.dead _after_kill_greenlet = _start_greenlet
def _do_test(self, block, killall):
link_test = []
result = []
g = gevent.Greenlet(self._run_in_greenlet, result)
g.link(link_test.append)
self._start_greenlet(g)
self.__kill_greenlet(g, block, killall)
self._after_kill_greenlet(g)
self.assertFalse(result)
self.assertEqual(link_test, [g])
def test_block(self):
self._do_test(block=True, killall=False)
def test_non_block(self):
self._do_test(block=False, killall=False)
def test_block_killal(self):
self._do_test(block=True, killall=True)
def test_non_block_killal(self):
self._do_test(block=False, killall=True)
class TestKillAfterStart(TestKill):
def _start_greenlet(self, g):
g.start() g.start()
assert not g.started
assert g.dead
class TestKillAfterStartLater(TestKill):
def _start_greenlet(self, g):
g.start_later(timing.LARGE_TICK)
class TestKillWhileRunning(TestKill):
@staticmethod
def _run_in_greenlet(result_collector):
gevent.sleep(10)
# The above should die with the GreenletExit exception,
# so this should never run
TestKill._run_in_greenlet(result_collector)
def _after_kill_greenlet(self, g):
TestKill._after_kill_greenlet(self, g)
gevent.sleep(0.01)
class TestKillallRawGreenlet(greentest.TestCase):
def test_killall_raw(self):
g = gevent.spawn_raw(lambda: 1)
gevent.killall([g])
def assert_ready(g): class TestStart(greentest.TestCase):
assert g.dead, g
assert g.ready(), g
assert not bool(g), g
def test_start(self):
g = gevent.spawn(gevent.sleep, timing.SMALL_TICK)
self.assert_greenlet_spawned(g)
def assert_not_ready(g): g.start()
assert not g.dead, g self.assert_greenlet_started(g)
assert not g.ready(), g
g.join()
self.assert_greenlet_finished(g)
# cannot start again
g.start()
self.assert_greenlet_finished(g)
class TestRef(greentest.TestCase): class TestRef(greentest.TestCase):
...@@ -814,12 +813,12 @@ class TestRef(greentest.TestCase): ...@@ -814,12 +813,12 @@ class TestRef(greentest.TestCase):
gevent.Greenlet() gevent.Greenlet()
def test_kill_scheduled(self): def test_kill_scheduled(self):
gevent.spawn(gevent.sleep, 10).kill() gevent.spawn(gevent.sleep, timing.LARGE_TICK).kill()
def test_kill_started(self): def test_kill_started(self):
g = gevent.spawn(gevent.sleep, 10) g = gevent.spawn(gevent.sleep, timing.LARGE_TICK)
try: try:
gevent.sleep(0.001) gevent.sleep(timing.SMALLEST_RELIABLE_DELAY)
finally: finally:
g.kill() g.kill()
......
...@@ -138,7 +138,7 @@ class Test(greentest.TestCase): ...@@ -138,7 +138,7 @@ class Test(greentest.TestCase):
for g in s: for g in s:
assert g.dead assert g.dead
def test_killall_iterable_argument_timeout(self): def test_killall_iterable_argument_timeout_not_started(self):
def f(): def f():
try: try:
gevent.sleep(1.5) gevent.sleep(1.5)
...@@ -149,6 +149,25 @@ class Test(greentest.TestCase): ...@@ -149,6 +149,25 @@ class Test(greentest.TestCase):
s = set() s = set()
s.add(p1) s.add(p1)
s.add(p2) s.add(p2)
gevent.killall(s, timeout=0.5)
for g in s:
self.assertTrue(g.dead, g)
def test_killall_iterable_argument_timeout_started(self):
def f():
try:
gevent.sleep(1.5)
except: # pylint:disable=bare-except
gevent.sleep(1)
p1 = GreenletSubclass.spawn(f)
p2 = GreenletSubclass.spawn(f)
s = set()
s.add(p1)
s.add(p2)
# Get them both running.
gevent.sleep(timing.SMALLEST_RELIABLE_DELAY)
with self.assertRaises(Timeout): with self.assertRaises(Timeout):
gevent.killall(s, timeout=0.5) gevent.killall(s, timeout=0.5)
......
...@@ -159,6 +159,7 @@ class TestPeriodicMonitoringThread(greentest.TestCase): ...@@ -159,6 +159,7 @@ class TestPeriodicMonitoringThread(greentest.TestCase):
gevent.config.monitor_thread = self.monitor_thread gevent.config.monitor_thread = self.monitor_thread
self.monitored_hubs = None self.monitored_hubs = None
self._reset_hub() self._reset_hub()
super(TestPeriodicMonitoringThread, self).tearDown()
def _monitor(self, hub): def _monitor(self, hub):
self.monitor_fired += 1 self.monitor_fired += 1
......
...@@ -65,7 +65,8 @@ class TestSwitch(greentest.TestCase): ...@@ -65,7 +65,8 @@ class TestSwitch(greentest.TestCase):
self.caught = e self.caught = e
def test_kill_exception(self): def test_kill_exception(self):
# Killing with gevent.kill gets the right exception # Killing with gevent.kill gets the right exception,
# and we can pass exception objects, not just exception classes.
g = gevent.spawn(self.catcher) g = gevent.spawn(self.catcher)
g.start() g.start()
......
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # 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 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE. # THE SOFTWARE.
# pylint: disable=too-many-lines,unused-argument # pylint: disable=too-many-lines,unused-argument,too-many-ancestors
from __future__ import print_function from __future__ import print_function
from gevent import monkey from gevent import monkey
......
...@@ -93,13 +93,13 @@ class TestDefaultSpawn(test__server.TestDefaultSpawn): ...@@ -93,13 +93,13 @@ class TestDefaultSpawn(test__server.TestDefaultSpawn):
class TestSSLSocketNotAllowed(test__server.TestSSLSocketNotAllowed): class TestSSLSocketNotAllowed(test__server.TestSSLSocketNotAllowed):
Settings = Settings Settings = Settings
class TestRawSpawn(test__server.TestRawSpawn): class TestRawSpawn(test__server.TestRawSpawn): # pylint:disable=too-many-ancestors
Settings = Settings Settings = Settings
class TestSSLGetCertificate(test__server.TestSSLGetCertificate): class TestSSLGetCertificate(test__server.TestSSLGetCertificate):
Settings = Settings Settings = Settings
class TestPoolSpawn(test__server.TestPoolSpawn): class TestPoolSpawn(test__server.TestPoolSpawn): # pylint:disable=too-many-ancestors
Settings = Settings Settings = Settings
if __name__ == '__main__': if __name__ == '__main__':
......
...@@ -9,7 +9,7 @@ import gevent.testing as greentest ...@@ -9,7 +9,7 @@ import gevent.testing as greentest
from . import test__threadpool from . import test__threadpool
class TestPatchedTPE(test__threadpool.TestTPE): class TestPatchedTPE(test__threadpool.TestTPE): # pylint:disable=too-many-ancestors
MONKEY_PATCHED = True MONKEY_PATCHED = True
......
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