Example #1
0
def test_add_workaround():
    """Assert helper function adds corrent items to given data"""
    data = defaultdict(lambda: {"data": {}, "used_in": []})
    matches = [('BZ', '123456'), ('BZ', '789456')]

    add_workaround(data, matches, 'test', foo='bar')

    add_workaround(
        data, matches, 'test', validation=lambda *a, **k: False, zaz='traz'  # Should not be added
    )

    for match in matches:
        issue = f"{match[0]}:{match[1]}"
        used_in = data[issue.strip()]['used_in']
        assert {'usage': 'test', 'foo': 'bar'} in used_in
        assert {'usage': 'test', 'zaz': 'traz'} not in used_in
Example #2
0
def generate_issue_collection(items, config):  # pragma: no cover
    """Generates a dictionary with the usage of Issue blockers

    For use in pytest_collection_modifyitems hook

    Arguments:
        items {list} - List of pytest test case objects.
        config {dict} - Pytest config object.

    Returns:
        [List of dicts] - Dicts indexed by "<handler>:<issue>"

        Example of return data::

            {
                "XX:1625783" {
                    "data": {
                        # data taken from REST api,
                        "status": ...,
                        "resolution": ...,
                        ...
                        # Calculated data
                        "is_open": bool,
                        "is_deselected": bool,
                        "clones": [list],
                        "dupe_data": {dict}
                    },
                    "used_in" [
                        {
                            "filepath": "tests/foreman/ui/test_sync.py",
                            "lineno": 124,
                            "testcase": "test_positive_sync_custom_ostree_repo",
                            "component": "Repositories",
                            "usage": "skip_if_open"
                        },
                        ...
                    ]
                },
                ...
            }
    """
    if not settings.configured:
        settings.configure()
    valid_markers = ["skip_if_open", "skip", "deselect"]
    collected_data = defaultdict(lambda: {"data": {}, "used_in": []})

    use_bz_cache = config.getoption('bz_cache', None)  # use existing json cache?
    cached_data = None
    if use_bz_cache:
        try:
            with open(DEFAULT_BZ_CACHE_FILE) as bz_cache_file:
                logger.info(f'Using BZ cache file for issue collection: {DEFAULT_BZ_CACHE_FILE}')
                cached_data = {
                    k: search_version_key(k, v) for k, v in json.load(bz_cache_file).items()
                }
        except FileNotFoundError:
            # no bz cache file exists
            logger.warning(
                f'--bz-cache option used, cache file [{DEFAULT_BZ_CACHE_FILE}] not found'
            )

    deselect_data = {}  # a local cache for deselected tests

    test_modules = set()

    # --- Build the issue marked usage collection ---
    for item in items:
        if item.nodeid.startswith('tests/robottelo/'):
            # Unit test, no bz processing
            # TODO: We very likely don't need to include component and importance in collected_data
            # Removing these would unravel issue_handlers dependence on testimony
            # Allowing for issue_handler use in unit tests
            continue

        bz_marks_to_add = []
        # register test module as processed
        test_modules.add(item.module)
        # Find matches from docstrings top-down from: module, class, function.
        mod_cls_fun = (item.module, getattr(item, 'cls', None), item.function)
        for docstring in [d for d in map(inspect.getdoc, mod_cls_fun) if d is not None]:
            bz_matches = BZ.findall(docstring)
            if bz_matches:
                bz_marks_to_add.extend(b.strip() for b in bz_matches[-1].split(','))

        filepath, lineno, testcase = item.location
        # Component and importance marks are determined by testimony tokens
        # Testimony.yaml as of writing has both as required, so any
        component_mark = item.get_closest_marker('component').args[0]
        component_slug = slugify_component(component_mark, False)
        importance_mark = item.get_closest_marker('importance').args[0]
        for marker in item.iter_markers():
            if marker.name in valid_markers:
                issue = marker.kwargs.get('reason') or marker.args[0]
                issue_key = issue.strip()
                collected_data[issue_key]['used_in'].append(
                    {
                        'filepath': filepath,
                        'lineno': lineno,
                        'testcase': testcase,
                        'component': component_mark,
                        'importance': importance_mark,
                        'component_mark': component_slug,
                        'usage': marker.name,
                    }
                )

                # Store issue key to lookup in the deselection process
                deselect_data[item.location] = issue_key
                # Add issue as a marker to enable filtering e.g: "--BZ 123456"
                bz_marks_to_add.append(issue_key.split(':')[-1])

        # Then take the workarounds using `is_open` helper.
        source = inspect.getsource(item.function)
        if 'is_open(' in source:
            kwargs = {
                'filepath': filepath,
                'lineno': lineno,
                'testcase': testcase,
                'component': component_mark,
                'importance': importance_mark,
                'component_mark': component_slug,
            }
            add_workaround(collected_data, IS_OPEN.findall(source), 'is_open', **kwargs)
            add_workaround(collected_data, NOT_IS_OPEN.findall(source), 'not is_open', **kwargs)

        # Add BZs from tokens as a marker to enable filter e.g: "--BZ 123456"
        if bz_marks_to_add:
            item.add_marker(pytest.mark.BZ(*bz_marks_to_add))

    # Take uses of `is_open` from outside of test cases e.g: SetUp methods
    for test_module in test_modules:
        module_source = inspect.getsource(test_module)
        component_matches = COMPONENT.findall(module_source)
        module_component = None
        if component_matches:
            module_component = component_matches[0]
        if 'is_open(' in module_source:
            kwargs = {
                'filepath': test_module.__file__,
                'lineno': 1,
                'testcase': test_module.__name__,
                'component': module_component,
            }

            def validation(data, issue, usage, **kwargs):
                return issue not in data

            add_workaround(
                collected_data,
                IS_OPEN.findall(module_source),
                'is_open',
                validation=validation,
                **kwargs,
            )
            add_workaround(
                collected_data,
                NOT_IS_OPEN.findall(module_source),
                'not is_open',
                validation=validation,
                **kwargs,
            )

    # --- Collect BUGZILLA data ---
    bugzilla.collect_data_bz(collected_data, cached_data)

    # --- add deselect markers dynamically ---
    for item in items:
        issue = deselect_data.get(item.location)
        if issue and should_deselect(issue, collected_data[issue]['data']):
            collected_data[issue]['data']['is_deselected'] = True
            item.add_marker(pytest.mark.deselect(reason=issue))

    # --- if no cache file existed write a new cache file ---
    if cached_data is None and use_bz_cache:
        collected_data['_meta'] = {
            "version": settings.server.version,
            "hostname": settings.server.hostname,
            "created": datetime.now().isoformat(),
            "pytest": {"args": config.args, "pwd": str(config.invocation_dir)},
        }
        # bz_cache_filename could be None from the option not being passed, write the file anyway
        with open(DEFAULT_BZ_CACHE_FILE, 'w') as collect_file:
            json.dump(collected_data, collect_file, indent=4, cls=VersionEncoder)
            logger.info(f"Generated BZ cache file {DEFAULT_BZ_CACHE_FILE}")

    return collected_data