123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- """Attribute selector plugin.
- Oftentimes when testing you will want to select tests based on
- criteria rather then simply by filename. For example, you might want
- to run all tests except for the slow ones. You can do this with the
- Attribute selector plugin by setting attributes on your test methods.
- Here is an example:
- .. code-block:: python
- def test_big_download():
- import urllib
- # commence slowness...
- test_big_download.slow = 1
- Once you've assigned an attribute ``slow = 1`` you can exclude that
- test and all other tests having the slow attribute by running ::
- $ nosetests -a '!slow'
- There is also a decorator available for you that will set attributes.
- Here's how to set ``slow=1`` like above with the decorator:
- .. code-block:: python
- from nose.plugins.attrib import attr
- @attr('slow')
- def test_big_download():
- import urllib
- # commence slowness...
- And here's how to set an attribute with a specific value:
- .. code-block:: python
- from nose.plugins.attrib import attr
- @attr(speed='slow')
- def test_big_download():
- import urllib
- # commence slowness...
- This test could be run with ::
- $ nosetests -a speed=slow
- In Python 2.6 and higher, ``@attr`` can be used on a class to set attributes
- on all its test methods at once. For example:
- .. code-block:: python
- from nose.plugins.attrib import attr
- @attr(speed='slow')
- class MyTestCase:
- def test_long_integration(self):
- pass
- def test_end_to_end_something(self):
- pass
- Below is a reference to the different syntaxes available.
- Simple syntax
- -------------
- Examples of using the ``-a`` and ``--attr`` options:
- * ``nosetests -a status=stable``
- Only runs tests with attribute "status" having value "stable"
- * ``nosetests -a priority=2,status=stable``
- Runs tests having both attributes and values
- * ``nosetests -a priority=2 -a slow``
- Runs tests that match either attribute
- * ``nosetests -a tags=http``
- If a test's ``tags`` attribute was a list and it contained the value
- ``http`` then it would be run
- * ``nosetests -a slow``
- Runs tests with the attribute ``slow`` if its value does not equal False
- (False, [], "", etc...)
- * ``nosetests -a '!slow'``
- Runs tests that do NOT have the attribute ``slow`` or have a ``slow``
- attribute that is equal to False
- **NOTE**:
- if your shell (like bash) interprets '!' as a special character make sure to
- put single quotes around it.
- Expression Evaluation
- ---------------------
- Examples using the ``-A`` and ``--eval-attr`` options:
- * ``nosetests -A "not slow"``
- Evaluates the Python expression "not slow" and runs the test if True
- * ``nosetests -A "(priority > 5) and not slow"``
- Evaluates a complex Python expression and runs the test if True
- """
- import inspect
- import logging
- import os
- import sys
- from inspect import isfunction
- from nose.plugins.base import Plugin
- from nose.util import tolist
- log = logging.getLogger('nose.plugins.attrib')
- compat_24 = sys.version_info >= (2, 4)
- def attr(*args, **kwargs):
- """Decorator that adds attributes to classes or functions
- for use with the Attribute (-a) plugin.
- """
- def wrap_ob(ob):
- for name in args:
- setattr(ob, name, True)
- for name, value in kwargs.iteritems():
- setattr(ob, name, value)
- return ob
- return wrap_ob
- def get_method_attr(method, cls, attr_name, default = False):
- """Look up an attribute on a method/ function.
- If the attribute isn't found there, looking it up in the
- method's class, if any.
- """
- Missing = object()
- value = getattr(method, attr_name, Missing)
- if value is Missing and cls is not None:
- value = getattr(cls, attr_name, Missing)
- if value is Missing:
- return default
- return value
- class ContextHelper:
- """Object that can act as context dictionary for eval and looks up
- names as attributes on a method/ function and its class.
- """
- def __init__(self, method, cls):
- self.method = method
- self.cls = cls
- def __getitem__(self, name):
- return get_method_attr(self.method, self.cls, name)
- class AttributeSelector(Plugin):
- """Selects test cases to be run based on their attributes.
- """
- def __init__(self):
- Plugin.__init__(self)
- self.attribs = []
- def options(self, parser, env):
- """Register command line options"""
- parser.add_option("-a", "--attr",
- dest="attr", action="append",
- default=env.get('NOSE_ATTR'),
- metavar="ATTR",
- help="Run only tests that have attributes "
- "specified by ATTR [NOSE_ATTR]")
- # disable in < 2.4: eval can't take needed args
- if compat_24:
- parser.add_option("-A", "--eval-attr",
- dest="eval_attr", metavar="EXPR", action="append",
- default=env.get('NOSE_EVAL_ATTR'),
- help="Run only tests for whose attributes "
- "the Python expression EXPR evaluates "
- "to True [NOSE_EVAL_ATTR]")
- def configure(self, options, config):
- """Configure the plugin and system, based on selected options.
- attr and eval_attr may each be lists.
- self.attribs will be a list of lists of tuples. In that list, each
- list is a group of attributes, all of which must match for the rule to
- match.
- """
- self.attribs = []
- # handle python eval-expression parameter
- if compat_24 and options.eval_attr:
- eval_attr = tolist(options.eval_attr)
- for attr in eval_attr:
- # "<python expression>"
- # -> eval(expr) in attribute context must be True
- def eval_in_context(expr, obj, cls):
- return eval(expr, None, ContextHelper(obj, cls))
- self.attribs.append([(attr, eval_in_context)])
- # attribute requirements are a comma separated list of
- # 'key=value' pairs
- if options.attr:
- std_attr = tolist(options.attr)
- for attr in std_attr:
- # all attributes within an attribute group must match
- attr_group = []
- for attrib in attr.strip().split(","):
- # don't die on trailing comma
- if not attrib:
- continue
- items = attrib.split("=", 1)
- if len(items) > 1:
- # "name=value"
- # -> 'str(obj.name) == value' must be True
- key, value = items
- else:
- key = items[0]
- if key[0] == "!":
- # "!name"
- # 'bool(obj.name)' must be False
- key = key[1:]
- value = False
- else:
- # "name"
- # -> 'bool(obj.name)' must be True
- value = True
- attr_group.append((key, value))
- self.attribs.append(attr_group)
- if self.attribs:
- self.enabled = True
- def validateAttrib(self, method, cls = None):
- """Verify whether a method has the required attributes
- The method is considered a match if it matches all attributes
- for any attribute group.
- ."""
- # TODO: is there a need for case-sensitive value comparison?
- any = False
- for group in self.attribs:
- match = True
- for key, value in group:
- attr = get_method_attr(method, cls, key)
- if callable(value):
- if not value(key, method, cls):
- match = False
- break
- elif value is True:
- # value must exist and be True
- if not bool(attr):
- match = False
- break
- elif value is False:
- # value must not exist or be False
- if bool(attr):
- match = False
- break
- elif type(attr) in (list, tuple):
- # value must be found in the list attribute
- if not str(value).lower() in [str(x).lower()
- for x in attr]:
- match = False
- break
- else:
- # value must match, convert to string and compare
- if (value != attr
- and str(value).lower() != str(attr).lower()):
- match = False
- break
- any = any or match
- if any:
- # not True because we don't want to FORCE the selection of the
- # item, only say that it is acceptable
- return None
- return False
- def wantFunction(self, function):
- """Accept the function if its attributes match.
- """
- return self.validateAttrib(function)
- def wantMethod(self, method):
- """Accept the method if its attributes match.
- """
- try:
- cls = method.im_class
- except AttributeError:
- return False
- return self.validateAttrib(method, cls)
|