"""
uncollect
---------
Used internally to mark a test to be "uncollected"
This mark should be used at any point before or during test collection to
dynamically flag a test to be removed from the list of collected tests.
py.test adds marks to test items a few different ways. When marking in a py.test
hook that takes an ``Item`` or :py:class:`Node <pytest:_pytest.main.Node>` (``Item``
is a subclass of ``Node``), use ``item.add_marker('uncollect')`` or
``item.add_marker(pytest.mark.uncollect)``
When dealing with the test function directly, using the mark decorator is preferred.
In this case, either decorate a test function directly (and have a good argument ready
for adding a test that won't run...), e.g. ``@pytest.mark.uncollect`` before the test
``def``, or instantiate the mark decorator and use it to wrap a test function, e.g.
``pytest.mark.uncollect()(test_function)``
uncollectif
-----------
The ``uncollectif`` marker is very special and can cause harm to innocent kittens if used
incorrectly. The ``uncollectif`` marker enables the ability to uncollect a specific test
if a certain condition is evaluated to ``True``. The following is an example:
.. code-block:: python
@pytest.mark.uncollectif(lambda appliance: appliance.version < '5.3')
In this case, when pytest runs the modify items hook, it will evaluate the lambda function
and if it results in ``True``, then the test will be uncollected. Fixtures that are
generated by testgen, such as provider_key, provider_data etc, are also usable inside
the ``collectif`` marker, assuming the fixture name is also a prerequisite for the test
itself. For example:: python
.. code-block:: python
@pytest.mark.uncollectif(lambda provider_type: provider_type != 'virtualcenter')
def test_delete_all_snapshots(test_vm, provider_key, provider_type):
pass
Here, the fixture provider_type is special as it comes from testgen and is passed to the
lambda for comparison.
Note:
Be aware, that this cannot be used for any other fixture types. Doing so will break
pytest and may invalidate your puppies.
"""
import inspect
import pytest
from cfme.utils.log import logger
MARKDECORATOR_TYPE = type(pytest.mark.skip)
# work around https://github.com/pytest-dev/pytest/issues/2400
[docs]def get_uncollect_function(marker_or_markdecorator):
if isinstance(marker_or_markdecorator, MARKDECORATOR_TYPE):
return marker_or_markdecorator.args[0]
else:
return list(marker_or_markdecorator)[0].args[0]
[docs]def uncollectif(item):
""" Evaluates if an item should be uncollected
Tests markers against a supplied lambda from the markers object to determine
if the item should be uncollected or not.
"""
from cfme.utils.appliance import find_appliance
from cfme.utils.pytest_shortcuts import extract_fixtures_values
markers = item.get_marker('uncollectif')
if not markers:
return False, None
for mark in markers:
log_msg = 'Trying uncollecting {}: {}'.format(
item.name,
mark.kwargs.get('reason', 'No reason given'))
logger.debug(log_msg)
try:
arg_names = inspect.getargspec(get_uncollect_function(mark)).args
except TypeError:
logger.debug(log_msg)
return not bool(mark.args[0]), mark.kwargs.get('reason', 'No reason given')
app = find_appliance(item, require=False)
if app:
global_vars = {'appliance': app}
else:
logger.info("while uncollecting %s - appliance not known", item)
global_vars = {}
try:
values = extract_fixtures_values(item)
values.update(global_vars)
# The test has already been uncollected
if arg_names and not values:
return True, None
args = [values[arg] for arg in arg_names]
except KeyError:
missing_argnames = list(set(arg_names) - set(item._request.funcargnames))
func_name = item.name
if missing_argnames:
raise Exception("You asked for a fixture which wasn't in the function {} "
"prototype {}".format(func_name, missing_argnames))
else:
raise Exception("Failed to uncollect {}, best guess a fixture wasn't "
"ready".format(func_name))
retval = mark.args[0](*args)
if retval:
# shortcut
return retval, mark.kwargs.get('reason', "No reason given")
else:
return False, None
else:
return False, None
[docs]def pytest_collection_modifyitems(session, config, items):
from cfme.fixtures.pytest_store import store
len_collected = len(items)
new_items = []
from cfme.utils.path import log_path
with log_path.join('uncollected.log').open('w') as f:
for item in items:
# First filter out all items who have the uncollect mark
uncollect_marker = item.get_marker('uncollect')
if uncollect_marker:
uncollect_reason = uncollect_marker.kwargs.get('reason', "No reason given")
f.write("{} - {}\n".format(item.name, uncollect_reason))
else:
uncollectif_result, uncollectif_reason = uncollectif(item)
if uncollectif_result:
f.write("{} - {}\n".format(item.name, uncollectif_reason))
else:
new_items.append(item)
items[:] = new_items
len_filtered = len(items)
filtered_count = len_collected - len_filtered
store.uncollection_stats['uncollectif'] = filtered_count