Commit 29e795ee authored by Jason Madden's avatar Jason Madden

Do something reasonable when psutil isn't available for tests.

parent a9842bca
...@@ -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
......
...@@ -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
print(test_item.mro())
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', '')
......
...@@ -10,8 +10,9 @@ from greenlet import settrace ...@@ -10,8 +10,9 @@ from greenlet import settrace
from gevent.monkey import get_original 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._compat import get_this_psutil_process
from gevent.testing.skipping import skipOnPyPyOnWindows 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):
...@@ -289,14 +291,14 @@ class MockProcess(object): ...@@ -289,14 +291,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 +306,9 @@ class TestPeriodicMonitorMemory(_AbstractTestPeriodicMonitoringThread, ...@@ -304,10 +306,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())
......
...@@ -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
......
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