From b1f50b8bf81748ccdfebecdfd3a1fade2a80ff62 Mon Sep 17 00:00:00 2001 From: Tasos Papaioannou Date: Fri, 3 May 2024 11:48:06 -0400 Subject: [PATCH] Filter test collection based on files changed in upstream PRs (#14931) --- conf/github.yaml.template | 13 +++++++ conftest.py | 1 + pytest_plugins/upstream_pr.py | 68 ++++++++++++++++++++++++++++++++++ requirements.txt | 1 + robottelo/config/validators.py | 8 ++++ 5 files changed, 91 insertions(+) create mode 100644 conf/github.yaml.template create mode 100644 pytest_plugins/upstream_pr.py diff --git a/conf/github.yaml.template b/conf/github.yaml.template new file mode 100644 index 00000000000..56927c8db6d --- /dev/null +++ b/conf/github.yaml.template @@ -0,0 +1,13 @@ +GITHUB: + FOREMAN: + ORG: theforeman + REPO: foreman + RULES: + - PATH: + MARKER: + KATELLO: + ORG: Katello + REPO: katello + RULES: + - PATH: + MARKER: diff --git a/conftest.py b/conftest.py index f50f81e2975..4f2b6745bfd 100644 --- a/conftest.py +++ b/conftest.py @@ -23,6 +23,7 @@ 'pytest_plugins.sanity_plugin', 'pytest_plugins.video_cleanup', 'pytest_plugins.capsule_n-minus', + 'pytest_plugins.upstream_pr', # Fixtures 'pytest_fixtures.core.broker', 'pytest_fixtures.core.sat_cap_factory', diff --git a/pytest_plugins/upstream_pr.py b/pytest_plugins/upstream_pr.py new file mode 100644 index 00000000000..ff18a12e3cf --- /dev/null +++ b/pytest_plugins/upstream_pr.py @@ -0,0 +1,68 @@ +from github import Github + +from robottelo.config import settings +from robottelo.logging import collection_logger as logger + + +def pytest_addoption(parser): + """Add CLI option to specify upstream GitHub PRs. + + Add --upstream-pr option for filtering tests based on the files modified by upstream + PRs. + """ + parser.addoption( + "--upstream-pr", + help=( + "Filter test collection based on files modified by upstream PR." + " Example: `--upstream-pr REPO/PR_ID`" + ), + ) + + +def pytest_collection_modifyitems(session, items, config): + """Filter tests based on upstream PRs. + 1. Get the list of modified files in the upstream PRs. + 2. Map each file to at most one component. + 3. Filter the collected tests to include only those with matching components. + """ + upstream_prs = [ + pr_info for pr_info in (config.getoption('upstream_pr') or '').split(',') if pr_info != '' + ] + if not upstream_prs: + return + + markers = set() + for pr_info in upstream_prs: + # Get all filenames modified by this PR + repo_key, pr_id = pr_info.split('/') + if not (repo_config := settings.github.get(repo_key)): + raise Exception(f"Key {repo_key} not found in settings file.") + pr = Github().get_repo(f"{repo_config.org}/{repo_config.repo}").get_pull(int(pr_id)) + pr_filenames = {file.filename for commit in pr.get_commits() for file in commit.files} + + # Get a list of markers from all of the matching rules + unprocessed_filenames = pr_filenames.copy() + for rule in repo_config.rules: + if matched_filenames := { + filename for filename in unprocessed_filenames if filename.startswith(rule.path) + }: + markers.add(rule.marker) + unprocessed_filenames.difference_update(matched_filenames) + + # If no markers were found above, deselect all tests. + # Any unprocessed filenames are ignored. + selected = [] + deselected = [] + for item in items: + if markers: + if any(item_marker.name in markers for item_marker in item.iter_markers()): + selected.append(item) + else: + logger.debug(f'Deselected test {item.nodeid} due to PR filter {upstream_prs}') + deselected.append(item) + else: + logger.debug(f'Deselected test {item.nodeid} due to PR filter {upstream_prs}') + deselected.append(item) + + config.hook.pytest_deselected(items=deselected) + items[:] = selected diff --git a/requirements.txt b/requirements.txt index 8c521b30b9b..2f76f0e70bf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,6 +11,7 @@ jinja2==3.1.3 manifester==0.0.14 navmazing==1.2.2 productmd==1.38 +PyGithub==2.3.0 pyotp==2.9.0 python-box==7.1.1 pytest==8.2.0 diff --git a/robottelo/config/validators.py b/robottelo/config/validators.py index 1a6a6d7f4aa..79c5eb8a477 100644 --- a/robottelo/config/validators.py +++ b/robottelo/config/validators.py @@ -161,6 +161,14 @@ must_exist=True, ), ], + github=[ + Validator('github.foreman.org', default='theforeman'), + Validator('github.foreman.repo', default='foreman'), + Validator('github.foreman.rules', default=[], is_type_of=list), + Validator('github.katello.org', default='Katello'), + Validator('github.katello.repo', default='katello'), + Validator('github.katello.rules', default=[], is_type_of=list), + ], http_proxy=[ Validator( 'http_proxy.un_auth_proxy_url',