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
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