Commit 19df34fc authored by Jason Madden's avatar Jason Madden Committed by GitHub

Merge pull request #1388 from gevent/issue1365

Tests do reasonable things when some of the test deps are missing
parents a9842bca 2a4e2c28
...@@ -363,6 +363,10 @@ def run_setup(ext_modules, run_make): ...@@ -363,6 +363,10 @@ def run_setup(ext_modules, run_make):
'repoze.sphinx.autointerface', 'repoze.sphinx.autointerface',
], ],
'test': [ '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.interface',
'zope.event', 'zope.event',
......
...@@ -165,3 +165,22 @@ except ImportError: ...@@ -165,3 +165,22 @@ except ImportError:
perf_counter = time.clock perf_counter = time.clock
else: else:
perf_counter = time.time perf_counter = time.time
## Monitoring
def get_this_psutil_process():
# Depends on psutil. Defer the import until needed, who knows what
# it imports (psutil imports subprocess which on Python 3 imports
# selectors. This can expose issues with monkey-patching.)
# Returns a freshly queried object each time.
try:
from psutil import Process, AccessDenied
# Make sure it works (why would we be denied access to our own process?)
try:
proc = Process()
proc.memory_full_info()
except AccessDenied: # pragma: no cover
proc = None
except ImportError:
proc = None
return proc
...@@ -20,6 +20,7 @@ from gevent.events import implementer ...@@ -20,6 +20,7 @@ from gevent.events import implementer
from gevent._tracer import GreenletTracer from gevent._tracer import GreenletTracer
from gevent._compat import thread_mod_name from gevent._compat import thread_mod_name
from gevent._compat import perf_counter from gevent._compat import perf_counter
from gevent._compat import get_this_psutil_process
...@@ -251,22 +252,7 @@ class PeriodicMonitoringThread(object): ...@@ -251,22 +252,7 @@ class PeriodicMonitoringThread(object):
self._greenlet_tracer.monitor_current_greenlet_blocking() self._greenlet_tracer.monitor_current_greenlet_blocking()
def _get_process(self): # pylint:disable=method-hidden def _get_process(self): # pylint:disable=method-hidden
try: proc = get_this_psutil_process()
# The standard library 'resource' module doesn't provide
# a standard way to get the RSS measure, only the maximum.
# You might be tempted to try to compute something by adding
# together text and data sizes, but on many systems those come back
# zero. So our only option is psutil.
from psutil import Process, AccessDenied
# Make sure it works (why would we be denied access to our own process?)
try:
proc = Process()
proc.memory_full_info()
except AccessDenied: # pragma: no cover
proc = None
except ImportError:
proc = None
self._get_process = lambda: proc self._get_process = lambda: proc
return proc return proc
......
...@@ -128,9 +128,35 @@ def gc_collect_if_needed(): ...@@ -128,9 +128,35 @@ def gc_collect_if_needed():
if PYPY: # pragma: no cover if PYPY: # pragma: no cover
gc.collect() gc.collect()
# Our usage of mock should be limited to '@mock.patch()'
# and other things that are easily...mocked...here on Python 2
# when mock is not installed.
try: try:
from unittest import mock from unittest import mock
except ImportError: # Python 2 except ImportError: # Python 2
import mock try:
import mock
except ImportError: # pragma: no cover
# Backport not installed
class mock(object):
@staticmethod
def patch(reason):
return unittest.skip(reason)
mock = mock mock = mock
# zope.interface
try:
from zope.interface import verify
except ImportError:
class verify(object):
@staticmethod
def verifyObject(*_):
import warnings
warnings.warn("zope.interface is not installed; not verifying")
return
verify = verify
...@@ -25,7 +25,11 @@ import types ...@@ -25,7 +25,11 @@ import types
from functools import wraps from functools import wraps
import unittest import unittest
import objgraph try:
import objgraph
except ImportError: # pragma: no cover
# Optional test dependency
objgraph = None
import gevent import gevent
import gevent.core import gevent.core
...@@ -193,6 +197,11 @@ class _RefCountChecker(object): ...@@ -193,6 +197,11 @@ class _RefCountChecker(object):
def wrap_refcount(method): def wrap_refcount(method):
if objgraph is None:
import warnings
warnings.warn("objgraph not available, leakchecks disabled")
return method
if getattr(method, 'ignore_leakcheck', False): if getattr(method, 'ignore_leakcheck', False):
return method return method
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
# THE SOFTWARE. # THE SOFTWARE.
from __future__ import absolute_import, print_function, division from __future__ import absolute_import, print_function, division
import functools
import unittest import unittest
from . import sysinfo from . import sysinfo
...@@ -94,7 +95,60 @@ skipUnderCoverage = unittest.skip if sysinfo.RUN_COVERAGE else _do_not_skip ...@@ -94,7 +95,60 @@ skipUnderCoverage = unittest.skip if sysinfo.RUN_COVERAGE else _do_not_skip
skipIf = unittest.skipIf skipIf = unittest.skipIf
skipUnless = unittest.skipUnless skipUnless = unittest.skipUnless
_has_psutil_process = None
def _check_psutil():
global _has_psutil_process
if _has_psutil_process is None:
_has_psutil_process = sysinfo.get_this_psutil_process() is not None
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 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():
raise unittest.SkipTest(reason)
return f(*args)
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.
#
# I didn't find a good workaround for this on Python 2, so
# I'm just forbidding using the two argument super.
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
return test_item
return decorator
if sysinfo.LIBUV: if sysinfo.LIBUV:
skipOnLibuv = unittest.skip skipOnLibuv = unittest.skip
......
...@@ -33,6 +33,8 @@ OSX = gsysinfo.OSX ...@@ -33,6 +33,8 @@ OSX = gsysinfo.OSX
PURE_PYTHON = gsysinfo.PURE_PYTHON PURE_PYTHON = gsysinfo.PURE_PYTHON
get_this_psutil_process = gsysinfo.get_this_psutil_process
# XXX: Formalize this better # XXX: Formalize this better
LIBUV = 'libuv' in gevent.core.loop.__module__ # pylint:disable=no-member LIBUV = 'libuv' in gevent.core.loop.__module__ # pylint:disable=no-member
CFFI_BACKEND = PYPY or LIBUV or 'cffi' in os.getenv('GEVENT_LOOP', '') CFFI_BACKEND = PYPY or LIBUV or 'cffi' in os.getenv('GEVENT_LOOP', '')
......
...@@ -11,7 +11,8 @@ from gevent.monkey import get_original ...@@ -11,7 +11,8 @@ from gevent.monkey import get_original
from gevent._compat import thread_mod_name from gevent._compat import thread_mod_name
from gevent._compat import NativeStrIO from gevent._compat import NativeStrIO
from gevent.testing.skipping import skipOnPyPyOnWindows from gevent.testing import verify
from gevent.testing.skipping import skipWithoutPSUtil
from gevent import _monitor as monitor from gevent import _monitor as monitor
from gevent import config as GEVENT_CONFIG from gevent import config as GEVENT_CONFIG
...@@ -82,10 +83,11 @@ class TestPeriodicMonitoringThread(_AbstractTestPeriodicMonitoringThread, ...@@ -82,10 +83,11 @@ class TestPeriodicMonitoringThread(_AbstractTestPeriodicMonitoringThread,
self.assertEqual(0xDEADBEEF, self.pmt.monitor_thread_ident) self.assertEqual(0xDEADBEEF, self.pmt.monitor_thread_ident)
self.assertEqual(gettrace(), self.pmt._greenlet_tracer) self.assertEqual(gettrace(), self.pmt._greenlet_tracer)
@skipOnPyPyOnWindows("psutil doesn't install on PyPy on Win") @skipWithoutPSUtil("Verifies the process")
def test_get_process(self): def test_get_process(self):
proc = self.pmt._get_process() proc = self.pmt._get_process()
self.assertIsNotNone(proc) self.assertIsNotNone(proc)
# Same object is returned each time.
self.assertIs(proc, self.pmt._get_process()) self.assertIs(proc, self.pmt._get_process())
def test_hub_wref(self): def test_hub_wref(self):
...@@ -245,7 +247,6 @@ class TestPeriodicMonitorBlocking(_AbstractTestPeriodicMonitoringThread, ...@@ -245,7 +247,6 @@ class TestPeriodicMonitorBlocking(_AbstractTestPeriodicMonitoringThread,
# so nothing is considered blocked # so nothing is considered blocked
from gevent.events import subscribers from gevent.events import subscribers
from gevent.events import IEventLoopBlocked from gevent.events import IEventLoopBlocked
from zope.interface.verify import verifyObject
events = [] events = []
subscribers.append(events.append) subscribers.append(events.append)
...@@ -263,7 +264,7 @@ class TestPeriodicMonitorBlocking(_AbstractTestPeriodicMonitoringThread, ...@@ -263,7 +264,7 @@ class TestPeriodicMonitorBlocking(_AbstractTestPeriodicMonitoringThread,
# Again without switching is a problem. # Again without switching is a problem.
self.assertTrue(self.pmt.monitor_blocking(self.hub)) self.assertTrue(self.pmt.monitor_blocking(self.hub))
self.assertTrue(events) self.assertTrue(events)
verifyObject(IEventLoopBlocked, events[0]) verify.verifyObject(IEventLoopBlocked, events[0])
del events[:] del events[:]
# But we can order it not to be a problem # But we can order it not to be a problem
...@@ -289,14 +290,14 @@ class MockProcess(object): ...@@ -289,14 +290,14 @@ class MockProcess(object):
return self return self
@skipOnPyPyOnWindows("psutil doesn't install on PyPy on Win") @skipWithoutPSUtil("Accessess memory info")
class TestPeriodicMonitorMemory(_AbstractTestPeriodicMonitoringThread, class TestPeriodicMonitorMemory(_AbstractTestPeriodicMonitoringThread,
unittest.TestCase): unittest.TestCase):
rss = 0 rss = 0
def setUp(self): def setUp(self):
super(TestPeriodicMonitorMemory, self).setUp() _AbstractTestPeriodicMonitoringThread.setUp(self)
self._old_max = GEVENT_CONFIG.max_memory_usage self._old_max = GEVENT_CONFIG.max_memory_usage
GEVENT_CONFIG.max_memory_usage = None GEVENT_CONFIG.max_memory_usage = None
...@@ -304,10 +305,9 @@ class TestPeriodicMonitorMemory(_AbstractTestPeriodicMonitoringThread, ...@@ -304,10 +305,9 @@ class TestPeriodicMonitorMemory(_AbstractTestPeriodicMonitoringThread,
def tearDown(self): def tearDown(self):
GEVENT_CONFIG.max_memory_usage = self._old_max GEVENT_CONFIG.max_memory_usage = self._old_max
super(TestPeriodicMonitorMemory, self).tearDown() _AbstractTestPeriodicMonitoringThread.tearDown(self)
def test_can_monitor_and_install(self): def test_can_monitor_and_install(self):
# We run tests with psutil installed, and we have access to our # We run tests with psutil installed, and we have access to our
# process. # process.
self.assertTrue(self.pmt.can_monitor_memory_usage()) self.assertTrue(self.pmt.can_monitor_memory_usage())
......
...@@ -8,8 +8,19 @@ from __future__ import print_function ...@@ -8,8 +8,19 @@ from __future__ import print_function
import unittest import unittest
from gevent import events from gevent import events
from zope.interface import verify
try:
from zope.interface import verify
except ImportError:
verify = None
try:
from zope import event
except ImportError:
event = None
@unittest.skipIf(verify is None, "Needs zope.interface")
class TestImplements(unittest.TestCase): class TestImplements(unittest.TestCase):
def test_event_loop_blocked(self): def test_event_loop_blocked(self):
...@@ -28,10 +39,10 @@ class TestImplements(unittest.TestCase): ...@@ -28,10 +39,10 @@ class TestImplements(unittest.TestCase):
events.MemoryUsageUnderThreshold(0, 0, 0, 0)) events.MemoryUsageUnderThreshold(0, 0, 0, 0))
@unittest.skipIf(event is None, "Needs zope.event")
class TestEvents(unittest.TestCase): class TestEvents(unittest.TestCase):
def test_is_zope(self): def test_is_zope(self):
from zope import event
self.assertIs(events.subscribers, event.subscribers) self.assertIs(events.subscribers, event.subscribers)
self.assertIs(events.notify, event.notify) self.assertIs(events.notify, event.notify)
......
...@@ -30,6 +30,7 @@ import gevent ...@@ -30,6 +30,7 @@ import gevent
from gevent import socket from gevent import socket
from gevent.hub import Waiter, get_hub from gevent.hub import Waiter, get_hub
from gevent._compat import NativeStrIO from gevent._compat import NativeStrIO
from gevent._compat import get_this_psutil_process
DELAY = 0.1 DELAY = 0.1
...@@ -204,9 +205,16 @@ class TestPeriodicMonitoringThread(greentest.TestCase): ...@@ -204,9 +205,16 @@ class TestPeriodicMonitoringThread(greentest.TestCase):
monitor = hub.start_periodic_monitoring_thread() monitor = hub.start_periodic_monitoring_thread()
self.assertIsNotNone(monitor) self.assertIsNotNone(monitor)
self.assertEqual(2, len(monitor.monitoring_functions())) basic_monitor_func_count = 1
if get_this_psutil_process() is not None:
# psutil is installed
basic_monitor_func_count += 1
self.assertEqual(basic_monitor_func_count,
len(monitor.monitoring_functions()))
monitor.add_monitoring_function(self._monitor, 0.1) monitor.add_monitoring_function(self._monitor, 0.1)
self.assertEqual(3, len(monitor.monitoring_functions())) self.assertEqual(basic_monitor_func_count + 1,
len(monitor.monitoring_functions()))
self.assertEqual(self._monitor, monitor.monitoring_functions()[-1].function) self.assertEqual(self._monitor, monitor.monitoring_functions()[-1].function)
self.assertEqual(0.1, monitor.monitoring_functions()[-1].period) self.assertEqual(0.1, monitor.monitoring_functions()[-1].period)
...@@ -219,7 +227,8 @@ class TestPeriodicMonitoringThread(greentest.TestCase): ...@@ -219,7 +227,8 @@ class TestPeriodicMonitoringThread(greentest.TestCase):
self._run_monitoring_threads(monitor) self._run_monitoring_threads(monitor)
finally: finally:
monitor.add_monitoring_function(self._monitor, None) monitor.add_monitoring_function(self._monitor, None)
self.assertEqual(2, len(monitor._monitoring_functions)) self.assertEqual(basic_monitor_func_count,
len(monitor._monitoring_functions))
assert hub.exception_stream is stream assert hub.exception_stream is stream
monitor.kill() monitor.kill()
del hub.exception_stream del hub.exception_stream
...@@ -320,7 +329,7 @@ class TestPeriodicMonitoringThread(greentest.TestCase): ...@@ -320,7 +329,7 @@ class TestPeriodicMonitoringThread(greentest.TestCase):
class TestLoopInterface(unittest.TestCase): class TestLoopInterface(unittest.TestCase):
def test_implemensts_ILoop(self): def test_implemensts_ILoop(self):
from zope.interface import verify from gevent.testing import verify
from gevent._interfaces import ILoop from gevent._interfaces import ILoop
loop = get_hub().loop loop = get_hub().loop
......
...@@ -70,7 +70,7 @@ class TestMonkey(SubscriberCleanupMixin, unittest.TestCase): ...@@ -70,7 +70,7 @@ class TestMonkey(SubscriberCleanupMixin, unittest.TestCase):
def test_patch_twice_warnings_events(self): def test_patch_twice_warnings_events(self):
import warnings import warnings
from zope.interface import verify from gevent.testing import verify
orig_saved = {} orig_saved = {}
for k, v in monkey.saved.items(): for k, v in monkey.saved.items():
......
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