1 """Attribute selector plugin.
3 Oftentimes when testing you will want to select tests based on
4 criteria rather then simply by filename. For example, you might want
5 to run all tests except for the slow ones. You can do this with the
6 Attribute selector plugin by setting attributes on your test methods.
11 def test_big_download():
13 # commence slowness...
15 test_big_download.slow = 1
17 Once you've assigned an attribute ``slow = 1`` you can exclude that
18 test and all other tests having the slow attribute by running ::
20 $ nosetests -a '!slow'
22 There is also a decorator available for you that will set attributes.
23 Here's how to set ``slow=1`` like above with the decorator:
25 .. code-block:: python
27 from nose.plugins.attrib import attr
29 def test_big_download():
31 # commence slowness...
33 And here's how to set an attribute with a specific value:
35 .. code-block:: python
37 from nose.plugins.attrib import attr
39 def test_big_download():
41 # commence slowness...
43 This test could be run with ::
45 $ nosetests -a speed=slow
47 In Python 2.6 and higher, ``@attr`` can be used on a class to set attributes
48 on all its test methods at once. For example:
50 .. code-block:: python
52 from nose.plugins.attrib import attr
55 def test_long_integration(self):
57 def test_end_to_end_something(self):
60 Below is a reference to the different syntaxes available.
65 Examples of using the ``-a`` and ``--attr`` options:
67 * ``nosetests -a status=stable``
68 Only runs tests with attribute "status" having value "stable"
70 * ``nosetests -a priority=2,status=stable``
71 Runs tests having both attributes and values
73 * ``nosetests -a priority=2 -a slow``
74 Runs tests that match either attribute
76 * ``nosetests -a tags=http``
77 If a test's ``tags`` attribute was a list and it contained the value
78 ``http`` then it would be run
80 * ``nosetests -a slow``
81 Runs tests with the attribute ``slow`` if its value does not equal False
82 (False, [], "", etc...)
84 * ``nosetests -a '!slow'``
85 Runs tests that do NOT have the attribute ``slow`` or have a ``slow``
86 attribute that is equal to False
88 if your shell (like bash) interprets '!' as a special character make sure to
89 put single quotes around it.
94 Examples using the ``-A`` and ``--eval-attr`` options:
96 * ``nosetests -A "not slow"``
97 Evaluates the Python expression "not slow" and runs the test if True
99 * ``nosetests -A "(priority > 5) and not slow"``
100 Evaluates a complex Python expression and runs the test if True
107 from inspect import isfunction
108 from nose.plugins.base import Plugin
109 from nose.util import tolist
111 log = logging.getLogger('nose.plugins.attrib')
112 compat_24 = sys.version_info >= (2, 4)
114 def attr(*args, **kwargs):
115 """Decorator that adds attributes to classes or functions
116 for use with the Attribute (-a) plugin.
120 setattr(ob, name, True)
121 for name, value in kwargs.iteritems():
122 setattr(ob, name, value)
126 def get_method_attr(method, cls, attr_name, default = False):
127 """Look up an attribute on a method/ function.
128 If the attribute isn't found there, looking it up in the
129 method's class, if any.
132 value = getattr(method, attr_name, Missing)
133 if value is Missing and cls is not None:
134 value = getattr(cls, attr_name, Missing)
141 """Object that can act as context dictionary for eval and looks up
142 names as attributes on a method/ function and its class.
144 def __init__(self, method, cls):
148 def __getitem__(self, name):
149 return get_method_attr(self.method, self.cls, name)
152 class AttributeSelector(Plugin):
153 """Selects test cases to be run based on their attributes.
157 Plugin.__init__(self)
160 def options(self, parser, env):
161 """Register command line options"""
162 parser.add_option("-a", "--attr",
163 dest="attr", action="append",
164 default=env.get('NOSE_ATTR'),
166 help="Run only tests that have attributes "
167 "specified by ATTR [NOSE_ATTR]")
168 # disable in < 2.4: eval can't take needed args
170 parser.add_option("-A", "--eval-attr",
171 dest="eval_attr", metavar="EXPR", action="append",
172 default=env.get('NOSE_EVAL_ATTR'),
173 help="Run only tests for whose attributes "
174 "the Python expression EXPR evaluates "
175 "to True [NOSE_EVAL_ATTR]")
177 def configure(self, options, config):
178 """Configure the plugin and system, based on selected options.
180 attr and eval_attr may each be lists.
182 self.attribs will be a list of lists of tuples. In that list, each
183 list is a group of attributes, all of which must match for the rule to
188 # handle python eval-expression parameter
189 if compat_24 and options.eval_attr:
190 eval_attr = tolist(options.eval_attr)
191 for attr in eval_attr:
192 # "<python expression>"
193 # -> eval(expr) in attribute context must be True
194 def eval_in_context(expr, obj, cls):
195 return eval(expr, None, ContextHelper(obj, cls))
196 self.attribs.append([(attr, eval_in_context)])
198 # attribute requirements are a comma separated list of
201 std_attr = tolist(options.attr)
202 for attr in std_attr:
203 # all attributes within an attribute group must match
205 for attrib in attr.strip().split(","):
206 # don't die on trailing comma
209 items = attrib.split("=", 1)
212 # -> 'str(obj.name) == value' must be True
218 # 'bool(obj.name)' must be False
223 # -> 'bool(obj.name)' must be True
225 attr_group.append((key, value))
226 self.attribs.append(attr_group)
230 def validateAttrib(self, method, cls = None):
231 """Verify whether a method has the required attributes
232 The method is considered a match if it matches all attributes
233 for any attribute group.
235 # TODO: is there a need for case-sensitive value comparison?
237 for group in self.attribs:
239 for key, value in group:
240 attr = get_method_attr(method, cls, key)
242 if not value(key, method, cls):
246 # value must exist and be True
251 # value must not exist or be False
255 elif type(attr) in (list, tuple):
256 # value must be found in the list attribute
257 if not str(value).lower() in [str(x).lower()
262 # value must match, convert to string and compare
264 and str(value).lower() != str(attr).lower()):
269 # not True because we don't want to FORCE the selection of the
270 # item, only say that it is acceptable
274 def wantFunction(self, function):
275 """Accept the function if its attributes match.
277 return self.validateAttrib(function)
279 def wantMethod(self, method):
280 """Accept the method if its attributes match.
283 cls = method.im_class
284 except AttributeError:
286 return self.validateAttrib(method, cls)