diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2017-01-16 07:58:53 -0500 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2017-01-16 07:58:53 -0500 |
commit | 5f5510fba829e12d57adb949667ce8e3c5ff09d9 (patch) | |
tree | 9bdf075c6e0b02abf2865f21a56b655a07bb4c52 | |
parent | 8175a927c0189ff06e9f961bd780c9a8b7280abf (diff) | |
download | python-coveragepy-git-5f5510fba829e12d57adb949667ce8e3c5ff09d9.tar.gz |
A one_of decorator for checking function arguments.
-rw-r--r-- | coverage/misc.py | 17 | ||||
-rw-r--r-- | pylintrc | 27 | ||||
-rw-r--r-- | tests/test_misc.py | 23 |
3 files changed, 49 insertions, 18 deletions
diff --git a/coverage/misc.py b/coverage/misc.py index 240a2587..e78a1537 100644 --- a/coverage/misc.py +++ b/coverage/misc.py @@ -58,6 +58,17 @@ if env.TESTING: new_contract('bytes', lambda v: isinstance(v, bytes)) if env.PY3: new_contract('unicode', lambda v: isinstance(v, unicode_class)) + + def one_of(argnames): + """Ensure that only one of the argnames is non-None.""" + def _decorator(func): + argnameset = set(name.strip() for name in argnames.split(",")) + def _wrapped(*args, **kwargs): + vals = set(kwargs.get(name) for name in argnameset) + assert sum(val is not None for val in vals) == 1 + return func(*args, **kwargs) + return _wrapped + return _decorator else: # pragma: not covered # We aren't using real PyContracts, so just define a no-op decorator as a # stunt double. @@ -69,6 +80,12 @@ else: # pragma: not covered """Dummy no-op implementation of `new_contract`.""" pass + def one_of(argnames_unused): + """Dummy no-op implementation of `one_of`.""" + def _decorator(func): + return func + return _decorator + def nice_pair(pair): """Make a nice string representation of a pair of numbers. @@ -2,13 +2,13 @@ # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt # lint Python modules using external checkers. -# +# # This is the main checker controlling the other ones and the reports # generation. It is itself both a raw checker and an astng checker in order # to: # * handle message activation / deactivation at the module level # * handle some basic but necessary stats'data (number of classes, methods...) -# +# [MASTER] # Specify a configuration file. @@ -54,7 +54,7 @@ enable= useless-suppression # Disable the message(s) with the given id(s). -disable= +disable= spelling, # Messages that are just silly: locally-disabled, @@ -118,7 +118,7 @@ evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / stateme # * dangerous default values as arguments # * redefinition of function / method / class # * uses of the global statement -# +# [BASIC] # Regular expression which should only match functions or classes name which do @@ -126,8 +126,9 @@ evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / stateme # Special methods don't: __foo__ # Test methods don't: testXXXX # TestCase overrides don't: setUp, tearDown +# Nested decorator implementations: _decorator, _wrapped # Dispatched methods don't: _xxx__Xxxx -no-docstring-rgx=__.*__|test[A-Z_].*|setUp|tearDown|_.*__.* +no-docstring-rgx=__.*__|test[A-Z_].*|setUp|tearDown|_decorator|_wrapped|_.*__.* # Regular expression which should only match correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ @@ -168,7 +169,7 @@ bad-functions= # try to find bugs in the code using type inference -# +# [TYPECHECK] # Tells wether missing members accessed in mixin class should be ignored. A @@ -189,7 +190,7 @@ acquired-members=REQUEST,acl_users,aq_parent # * undefined variables # * redefinition of variable from builtins or from an outer scope # * use of variable before assigment -# +# [VARIABLES] # Tells wether we should check for unused import in __init__ files. @@ -210,7 +211,7 @@ additional-builtins= # * attributes not defined in the __init__ method # * supported interfaces implementation # * unreachable code -# +# [CLASSES] # List of method names used to declare (i.e. assign) instance attributes. @@ -220,7 +221,7 @@ defining-attr-methods=__init__,__new__,setUp,reset # checks for sign of poor/misdesign: # * number of methods, attributes, local variables... # * size, complexity of functions, methods -# +# [DESIGN] # Maximum number of arguments for function / method @@ -256,7 +257,7 @@ max-public-methods=500 # * relative / wildcard imports # * cyclic imports # * uses of deprecated modules -# +# [IMPORTS] # Deprecated modules which should not be used, separated by a comma @@ -280,7 +281,7 @@ int-import-graph= # * strict indentation # * line length # * use of <> instead of != -# +# [FORMAT] # Maximum number of characters on a single line. @@ -297,7 +298,7 @@ indent-string=' ' # checks for: # * warning notes in the code like FIXME, XXX # * PEP 263: source code with non ascii character but no encoding declaration -# +# [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. @@ -307,7 +308,7 @@ notes=FIXME,XXX,TODO # checks for similarities and duplicated code. This computation may be # memory / CPU intensive, so you should disable it if you experiments some # problems. -# +# [SIMILARITIES] # Minimum lines number of a similarity. diff --git a/tests/test_misc.py b/tests/test_misc.py index d3dede4f..0b6c9144 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -9,7 +9,7 @@ import pytest import coverage from coverage.version import _make_url, _make_version -from coverage.misc import contract, Hasher, file_be_gone +from coverage.misc import contract, file_be_gone, Hasher, one_of from tests.coveragetest import CoverageTest @@ -78,24 +78,37 @@ class ContractTest(CoverageTest): def test_bytes(self): @contract(text='bytes|None') - def need_bytes(text): + def need_bytes(text=None): # pylint: disable=missing-docstring return text assert need_bytes(b"Hey") == b"Hey" - assert need_bytes(None) == None + assert need_bytes() is None with pytest.raises(Exception): need_bytes(u"Oops") def test_unicode(self): @contract(text='unicode|None') - def need_unicode(text): + def need_unicode(text=None): # pylint: disable=missing-docstring return text assert need_unicode(u"Hey") == u"Hey" - assert need_unicode(None) == None + assert need_unicode() is None with pytest.raises(Exception): need_unicode(b"Oops") + def test_one_of(self): + @one_of("a, b, c") + def give_me_one(a=None, b=None, c=None): # pylint: disable=missing-docstring + return (a, b, c) + + assert give_me_one(a=17) == (17, None, None) + assert give_me_one(b=17) == (None, 17, None) + assert give_me_one(c=17) == (None, None, 17) + with pytest.raises(AssertionError): + give_me_one(a=17, b=23) + with pytest.raises(AssertionError): + give_me_one() + class VersionTest(CoverageTest): """Tests of version.py""" |