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):
'repoze.sphinx.autointerface',
],
'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',
......
......@@ -165,3 +165,22 @@ except ImportError:
perf_counter = time.clock
else:
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
from gevent._tracer import GreenletTracer
from gevent._compat import thread_mod_name
from gevent._compat import perf_counter
from gevent._compat import get_this_psutil_process
......@@ -251,22 +252,7 @@ class PeriodicMonitoringThread(object):
self._greenlet_tracer.monitor_current_greenlet_blocking()
def _get_process(self): # pylint:disable=method-hidden
try:
# 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
proc = get_this_psutil_process()
self._get_process = lambda: proc
return proc
......
......@@ -19,6 +19,7 @@
# THE SOFTWARE.
from __future__ import absolute_import, print_function, division
import functools
import unittest
from . import sysinfo
......@@ -94,7 +95,60 @@ skipUnderCoverage = unittest.skip if sysinfo.RUN_COVERAGE else _do_not_skip
skipIf = unittest.skipIf
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:
skipOnLibuv = unittest.skip
......
......@@ -33,6 +33,8 @@ OSX = gsysinfo.OSX
PURE_PYTHON = gsysinfo.PURE_PYTHON
get_this_psutil_process = gsysinfo.get_this_psutil_process
# XXX: Formalize this better
LIBUV = 'libuv' in gevent.core.loop.__module__ # pylint:disable=no-member
CFFI_BACKEND = PYPY or LIBUV or 'cffi' in os.getenv('GEVENT_LOOP', '')
......
......@@ -10,8 +10,9 @@ from greenlet import settrace
from gevent.monkey import get_original
from gevent._compat import thread_mod_name
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 config as GEVENT_CONFIG
......@@ -82,10 +83,11 @@ class TestPeriodicMonitoringThread(_AbstractTestPeriodicMonitoringThread,
self.assertEqual(0xDEADBEEF, self.pmt.monitor_thread_ident)
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):
proc = self.pmt._get_process()
self.assertIsNotNone(proc)
# Same object is returned each time.
self.assertIs(proc, self.pmt._get_process())
def test_hub_wref(self):
......@@ -289,14 +291,14 @@ class MockProcess(object):
return self
@skipOnPyPyOnWindows("psutil doesn't install on PyPy on Win")
@skipWithoutPSUtil("Accessess memory info")
class TestPeriodicMonitorMemory(_AbstractTestPeriodicMonitoringThread,
unittest.TestCase):
rss = 0
def setUp(self):
super(TestPeriodicMonitorMemory, self).setUp()
_AbstractTestPeriodicMonitoringThread.setUp(self)
self._old_max = GEVENT_CONFIG.max_memory_usage
GEVENT_CONFIG.max_memory_usage = None
......@@ -304,10 +306,9 @@ class TestPeriodicMonitorMemory(_AbstractTestPeriodicMonitoringThread,
def tearDown(self):
GEVENT_CONFIG.max_memory_usage = self._old_max
super(TestPeriodicMonitorMemory, self).tearDown()
_AbstractTestPeriodicMonitoringThread.tearDown(self)
def test_can_monitor_and_install(self):
# We run tests with psutil installed, and we have access to our
# process.
self.assertTrue(self.pmt.can_monitor_memory_usage())
......
......@@ -30,6 +30,7 @@ import gevent
from gevent import socket
from gevent.hub import Waiter, get_hub
from gevent._compat import NativeStrIO
from gevent._compat import get_this_psutil_process
DELAY = 0.1
......@@ -204,9 +205,16 @@ class TestPeriodicMonitoringThread(greentest.TestCase):
monitor = hub.start_periodic_monitoring_thread()
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)
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(0.1, monitor.monitoring_functions()[-1].period)
......@@ -219,7 +227,8 @@ class TestPeriodicMonitoringThread(greentest.TestCase):
self._run_monitoring_threads(monitor)
finally:
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
monitor.kill()
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