From 2f51b299dd65d4580564695e146fcca83ed945a7 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Mon, 11 Sep 2017 06:50:47 -0500 Subject: Fix ZOPE_WATCH_CHECKERS=1 in pure-Python mode. Fixes #8. Test environments still to come. --- CHANGES.rst | 3 +++ src/zope/security/checker.py | 55 ++++++++++++++++++++++++++++---------------- tox.ini | 2 +- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index ab5790b..450fc34 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -35,6 +35,9 @@ Changes - Respect ``PURE_PYTHON`` at runtime. See `issue 33 `_. +- Fix watching checkers (``ZOPE_WATCH_CHECKERS=1``) in pure-Python + mode. See `issue 8 `_. + 4.1.1 (2017-05-17) ------------------ diff --git a/src/zope/security/checker.py b/src/zope/security/checker.py index 238f748..575d16d 100644 --- a/src/zope/security/checker.py +++ b/src/zope/security/checker.py @@ -55,17 +55,17 @@ from zope.security.proxy import getChecker try: from zope.exceptions import DuplicationError -except ImportError: #pragma NO COVER +except ImportError: # pragma: no cover class DuplicationError(Exception): """A duplicate registration was attempted""" -if os.environ.get('ZOPE_WATCH_CHECKERS'): #pragma NO COVER +WATCH_CHECKERS = 0 + +if os.environ.get('ZOPE_WATCH_CHECKERS'): try: WATCH_CHECKERS = int(os.environ.get('ZOPE_WATCH_CHECKERS')) except ValueError: WATCH_CHECKERS = 1 -else: - WATCH_CHECKERS = 0 def ProxyFactory(object, checker=None): @@ -256,19 +256,19 @@ class TracebackSupplement(object): cls = self.obj.__class__ if hasattr(cls, "__module__"): s = "%s.%s" % (cls.__module__, cls.__name__) - else: #pragma NO COVER XXX + else: # pragma: no cover XXX s = str(cls.__name__) result.append(" - class: " + s) - except: #pragma NO COVER XXX + except: # pragma: no cover XXX pass try: cls = type(self.obj) if hasattr(cls, "__module__"): s = "%s.%s" % (cls.__module__, cls.__name__) - else: #pragma NO COVER XXX + else: # pragma: no cover XXX s = str(cls.__name__) result.append(" - type: " + s) - except: #pragma NO COVER XXX + except: # pragma: no cover XXX pass return "\n".join(result) @@ -282,7 +282,7 @@ class Global(object): """ def __init__(self, name, module=None): - if module is None: #pragma NO COVER XXX + if module is None: # pragma: no cover XXX module = sys._getframe(1).f_locals['__name__'] self.__name__ = name @@ -395,6 +395,7 @@ def selectCheckerPy(object): while not isinstance(checker, Checker): checker = checker(object) + if checker is NoProxy or checker is None: return None @@ -443,7 +444,7 @@ _c_available = not PURE_PYTHON if _c_available: try: import zope.security._zope_security_checker - except (ImportError, AttributeError): #pragma NO COVER PyPy / PURE_PYTHON + except (ImportError, AttributeError): # pragma: no cover PyPy / PURE_PYTHON _c_available = False if _c_available: @@ -479,6 +480,7 @@ class CombinedChecker(Checker): Checker.__init__(self, checker1.get_permissions, checker1.set_permissions) + self._checker2 = checker2 def check(self, object, name): @@ -580,11 +582,24 @@ class CheckerLoggingMixin(object): raise -if WATCH_CHECKERS: #pragma NO COVER - class Checker(CheckerLoggingMixin, Checker): - verbosity = WATCH_CHECKERS - class CombinedChecker(CheckerLoggingMixin, CombinedChecker): - verbosity = WATCH_CHECKERS +# We have to be careful with the order of inheritance +# here. See https://github.com/zopefoundation/zope.security/issues/8 +class WatchingChecker(CheckerLoggingMixin, Checker): + verbosity = WATCH_CHECKERS +class WatchingCombinedChecker(CombinedChecker, WatchingChecker): + verbosity = WATCH_CHECKERS + +if WATCH_CHECKERS: # pragma: no cover + # When we make these the default, we also need to be sure + # to update the _defaultChecker's type (if it's not the C + # extension) so that selectCheckerPy can properly recognize + # it as a Checker. + # See https://github.com/zopefoundation/zope.security/issues/8 + Checker = WatchingChecker + CombinedChecker = WatchingCombinedChecker + + if not _c_available: + _defaultChecker.__class__ = Checker def _instanceChecker(inst): return _checkers.get(inst.__class__, _defaultChecker) @@ -658,7 +673,7 @@ _basic_types = { if PYTHON2: _basic_types[long] = NoProxy _basic_types[unicode] = NoProxy -else: #pragma NO COVER +else: # pragma: no cover _basic_types[type({}.values())] = NoProxy _basic_types[type({}.keys())] = NoProxy _basic_types[type({}.items())] = NoProxy @@ -667,7 +682,7 @@ try: import pytz except ImportError: pass -else: #pragma NO COVER +else: # pragma: no cover _basic_types[type(pytz.UTC)] = NoProxy BasicTypes = BasicTypes(_basic_types) @@ -694,7 +709,7 @@ if PYTHON2: BasicTypes_examples[long] = long(65536) -class _Sequence(object): #pragma NO COVER +class _Sequence(object): # pragma: no cover def __len__(self): return 0 def __getitem__(self, i): raise IndexError @@ -706,7 +721,7 @@ _Declaration_checker = InterfaceChecker( __call__=CheckerPublic, ) -def f(): #pragma NO COVER +def f(): # pragma: no cover yield f @@ -940,7 +955,7 @@ _clear() try: from zope.testing.cleanup import addCleanUp -except ImportError: #pragma NO COVER +except ImportError: # pragma: no cover pass else: addCleanUp(_clear) diff --git a/tox.ini b/tox.ini index c0e57fb..b39940b 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ envlist = # Jython support pending 2.7 support, due 2012-07-15 or so. See: # http://fwierzbicki.blogspot.com/2012/03/adconion-to-fund-jython-27.html # py27,pypy,jython,py33,coverage,docs - py27,py27-pure,pypy,py34-pure,py34,py35,py36,coverage,docs + py27,py27-pure,pypy,pypy3,py34-pure,py34,py35,py36,coverage,docs [testenv] commands = -- cgit v1.2.1 From 0a852da59af04f98c50e0cbd8fbd68f2ab5fefbe Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Mon, 11 Sep 2017 07:14:45 -0500 Subject: Add test environments for ZOPE_WATCH_CHECKERS They cover both the pure-python and C cases. This modified two existing test environments, but without loss of test coverage (since PyPy will always cover the pure-python case). --- .travis.yml | 6 ++++-- docs/conf.py | 34 ++++++++++++++++++++++++++++++---- tox.ini | 9 +++++---- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index c38e00c..76848c2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,9 +9,11 @@ matrix: - python: pypy - python: pypy3 - python: 2.7 - env: PURE_PYTHON=1 + env: + - PURE_PYTHON=1 + - ZOPE_WATCH_CHECKERS=1 - python: 3.4 - env: PURE_PYTHON=1 + env: ZOPE_WATCH_CHECKERS=1 install: - pip install -U pip setuptools - pip install -U coveralls coverage diff --git a/docs/conf.py b/docs/conf.py index 2bcdf49..caabf00 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -11,8 +11,16 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import os +import sys +import pkg_resources +sys.path.append(os.path.abspath('../src')) +rqmt = pkg_resources.require('zope.security')[0] + +# We can't have ZOPE_WATCH_CHECKERS set because it interferes +# with doctests +os.environ['ZOPE_WATCH_CHECKERS'] = '0' # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. @@ -28,6 +36,7 @@ import sys, os extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', + 'sphinx.ext.extlinks', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode', 'repoze.sphinx.autointerface', @@ -54,9 +63,9 @@ copyright = u'2012, Zope Foundation Contributors ' # built documents. # # The short X.Y version. -version = '4.0' +version = '%s.%s' % tuple(map(int, rqmt.version.split('.')[:2])) # The full version, including alpha/beta/rc tags. -release = '4.0dev' +release = rqmt.version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -249,4 +258,21 @@ texinfo_documents = [ # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'http://docs.python.org/': None} +intersphinx_mapping = { + 'https://docs.python.org/': None, + 'https://zopeinterface.readthedocs.io/en/latest': None, + 'https://zopeproxy.readthedocs.io/en/latest': None, + 'https://zopeschema.readthedocs.io/en/latest': None, +} + +extlinks = {'issue': ('https://github.com/zopefoundation/zope.datetime/issues/%s', + 'issue #'), + 'pr': ('https://github.com/zopefoundation/zope.datetime/pull/%s', + 'pull request #')} + +autodoc_default_flags = [ + 'members', + 'show-inheritance', +] +autoclass_content = 'both' +autodoc_member_order = 'bysource' diff --git a/tox.ini b/tox.ini index b39940b..36a065c 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ envlist = # Jython support pending 2.7 support, due 2012-07-15 or so. See: # http://fwierzbicki.blogspot.com/2012/03/adconion-to-fund-jython-27.html # py27,pypy,jython,py33,coverage,docs - py27,py27-pure,pypy,pypy3,py34-pure,py34,py35,py36,coverage,docs + py27,py27-watch,pypy,pypy3,py34-watch,py34,py35,py36,coverage,docs [testenv] commands = @@ -12,18 +12,19 @@ commands = deps = .[test,docs] -[testenv:py27-pure] +[testenv:py27-watch] basepython = python2.7 setenv = PURE_PYTHON = 1 + ZOPE_WATCH_CHECKERS = 1 PIP_CACHE_DIR = {envdir}/.cache -[testenv:py34-pure] +[testenv:py34-watch] basepython = python3.4 setenv = - PURE_PYTHON = 1 + ZOPE_WATCH_CHECKERS = 1 PIP_CACHE_DIR = {envdir}/.cache [testenv:coverage] -- cgit v1.2.1 From aac82c40249095ec98afca4e88332e1db1790470 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Mon, 11 Sep 2017 07:36:00 -0500 Subject: Always test watching checkers. Quiet their output to stderr. --- src/zope/security/tests/__init__.py | 18 ++++++++++-- src/zope/security/tests/test_checker.py | 48 ++++++++++++++++++++++--------- src/zope/security/tests/test_decorator.py | 11 ++++--- src/zope/security/tests/test_location.py | 20 ++++++------- 4 files changed, 65 insertions(+), 32 deletions(-) diff --git a/src/zope/security/tests/__init__.py b/src/zope/security/tests/__init__.py index b711d36..cab4c90 100644 --- a/src/zope/security/tests/__init__.py +++ b/src/zope/security/tests/__init__.py @@ -1,2 +1,16 @@ -# -# This file is necessary to make this directory a package. +import io + + +class QuietWatchingChecker(object): + # zope.testrunner does not support setUp/tearDownModule, + # so we use a mixin class to make sure we don't flood stderr + # with pointless printing when testing watching checkers + + def setUp(self): + from zope.security import checker + self.__old_file = checker.CheckerLoggingMixin._file + checker.CheckerLoggingMixin._file = io.StringIO() if bytes is not str else io.BytesIO() + + def tearDown(self): + from zope.security import checker + checker.CheckerLoggingMixin._file = self.__old_file diff --git a/src/zope/security/tests/test_checker.py b/src/zope/security/tests/test_checker.py index 9aaee38..d5cdc7f 100644 --- a/src/zope/security/tests/test_checker.py +++ b/src/zope/security/tests/test_checker.py @@ -13,8 +13,11 @@ ############################################################################## """Tests for zope.security.checker """ + import unittest + from zope.security import checker +from zope.security.tests import QuietWatchingChecker def _skip_if_not_Py2(testfunc): import sys @@ -28,6 +31,7 @@ def _skip_if_no_btrees(testfunc): else: return testfunc + class Test_ProxyFactory(unittest.TestCase): def _callFUT(self, object, checker=None): @@ -199,7 +203,7 @@ class Test_canAccess(unittest.TestCase): _marker = [] -class CheckerTestsBase(object): +class CheckerTestsBase(QuietWatchingChecker): def _getTargetClass(self): raise NotImplementedError("Subclasses must define") @@ -586,16 +590,20 @@ class CheckerTestsBase(object): class TestCheckerPy(CheckerTestsBase, unittest.TestCase): def _getTargetClass(self): - from zope.security.checker import CheckerPy - return CheckerPy + return checker.CheckerPy class TestChecker(CheckerTestsBase, unittest.TestCase): def _getTargetClass(self): - from zope.security.checker import Checker - return Checker + return checker.Checker +@unittest.skipIf(checker.Checker is checker.WatchingChecker, + "WatchingChecker is the default") +class TestWatchingChecker(TestChecker): + + def _getTargetClass(self): + return checker.WatchingChecker class TestTracebackSupplement(unittest.TestCase): @@ -1116,7 +1124,8 @@ class Test_undefineChecker(unittest.TestCase): self.assertFalse(Foo in _checkers) -class TestCombinedChecker(unittest.TestCase): +class TestCombinedChecker(QuietWatchingChecker, + unittest.TestCase): def _getTargetClass(self): from zope.security.checker import CombinedChecker @@ -1339,6 +1348,12 @@ class TestCombinedChecker(unittest.TestCase): finally: del thread_local.interaction +@unittest.skipIf(checker.WatchingCombinedChecker is checker.CombinedChecker, + "WatchingCombinedChecker is the default") +class TestWatchingCombinedChecker(TestCombinedChecker): + + def _getTargetClass(self): + return checker.WatchingCombinedChecker class TestCheckerLoggingMixin(unittest.TestCase): @@ -1605,23 +1620,26 @@ class TestBasicTypes(unittest.TestCase): # Pre-geddon tests start here -class TestSecurityPolicy(unittest.TestCase): +class TestSecurityPolicy(QuietWatchingChecker, + unittest.TestCase): def setUp(self): + super(TestSecurityPolicy, self).setUp() + from zope.security.management import newInteraction from zope.security.management import setSecurityPolicy - from zope.security.checker import _clear - _clear() + checker._clear() self.__oldpolicy = setSecurityPolicy(self._makeSecurityPolicy()) newInteraction() def tearDown(self): + super(TestSecurityPolicy, self).tearDown() + from zope.security.management import endInteraction from zope.security.management import setSecurityPolicy - from zope.security.checker import _clear endInteraction() setSecurityPolicy(self.__oldpolicy) - _clear() + checker._clear() def _makeSecurityPolicy(self): from zope.interface import implementer @@ -2193,15 +2211,17 @@ class TestMixinDecoratedChecker(unittest.TestCase): from zope.security.checker import Checker return Checker(self.decorationGetMap, self.decorationSetMap) -class TestCombinedCheckerMixin(TestMixinDecoratedChecker, unittest.TestCase): +class TestCombinedCheckerMixin(QuietWatchingChecker, + TestMixinDecoratedChecker, + unittest.TestCase): def setUp(self): - unittest.TestCase.setUp(self) + super(TestCombinedCheckerMixin, self).setUp() self.decoratedSetUp() def tearDown(self): self.decoratedTearDown() - unittest.TestCase.tearDown(self) + super(TestCombinedCheckerMixin, self).tearDown() def test_checking(self): from zope.security.interfaces import Unauthorized diff --git a/src/zope/security/tests/test_decorator.py b/src/zope/security/tests/test_decorator.py index 9319e6b..650380d 100644 --- a/src/zope/security/tests/test_decorator.py +++ b/src/zope/security/tests/test_decorator.py @@ -14,14 +14,19 @@ """Test zope.security.decorator """ import unittest +from zope.security.tests import QuietWatchingChecker -class DecoratedSecurityCheckerDescriptorTests(unittest.TestCase): + +class DecoratedSecurityCheckerDescriptorTests(QuietWatchingChecker, + unittest.TestCase): def setUp(self): + super(DecoratedSecurityCheckerDescriptorTests, self).setUp() from zope.security.checker import _clear _clear() def tearDown(self): + super(DecoratedSecurityCheckerDescriptorTests, self).tearDown() from zope.security.checker import _clear _clear() @@ -168,6 +173,4 @@ class DecoratedSecurityCheckerDescriptorTests(unittest.TestCase): def test_suite(): - return unittest.TestSuite(( - unittest.makeSuite(DecoratedSecurityCheckerDescriptorTests), - )) + return unittest.defaultTestLoader.loadTestsFromName(__name__) diff --git a/src/zope/security/tests/test_location.py b/src/zope/security/tests/test_location.py index 8905fca..5625f4e 100644 --- a/src/zope/security/tests/test_location.py +++ b/src/zope/security/tests/test_location.py @@ -15,21 +15,19 @@ """ import unittest +from zope.security.tests import QuietWatchingChecker def _skip_wo_zope_location(testfunc): try: - import zope.location + import zope.location as zl except ImportError: - from functools import update_wrapper - def dummy(self): - pass - update_wrapper(dummy, testfunc) - return dummy - else: - return testfunc + zl = None + return unittest.skipIf(zl is None, "Need zope.location")(testfunc) -class LocationSecurityProxyTests(unittest.TestCase): + +class LocationSecurityProxyTests(QuietWatchingChecker, + unittest.TestCase): @_skip_wo_zope_location def test_locationproxy_security(self): @@ -53,6 +51,4 @@ class LocationSecurityProxyTests(unittest.TestCase): def test_suite(): - return unittest.TestSuite(( - unittest.makeSuite(LocationSecurityProxyTests), - )) + return unittest.defaultTestLoader.loadTestsFromName(__name__) -- cgit v1.2.1