Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 19 additions & 15 deletions fedora_revdep_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,18 @@
class FedoraRevDepChecker:
"""Check reverse dependencies for Fedora package updates."""

def __init__(self, verbose=False, base=None, repos=None):
def __init__(self, verbose=False, base=None, repos=None, releasever=None):
"""Initialize the checker with DNF base and cached repo data.

Args:
verbose: Enable verbose output
base: Optional DNF base object for testing (if None, creates real DNF base)
repos: List of repository IDs to enable (default: ['rawhide', 'rawhide-source', 'koji', 'koji-source'])
releasever: Fedora release version (e.g. '44', 'rawhide'). Auto-detected from repos if not set.
"""
self.verbose = verbose
self.base = base
self.releasever = releasever
self.repos = repos if repos is not None else ['rawhide', 'rawhide-source', 'koji', 'koji-source']
if self.base is None:
self._init_dnf()
Expand Down Expand Up @@ -114,19 +116,17 @@ def _init_dnf(self):
print("Initializing DNF 5 and loading repository metadata...")
self.base = libdnf5.base.Base()

# Detect releasever from first repo name, or use 'rawhide' as default
# Extract version from repo names like 'fedora-40', 'f40', or use 'rawhide'
releasever = 'rawhide'
if self.repos:
first_repo = self.repos[0]
# Check if repo contains a version number
import re
version_match = re.search(r'(\d+)', first_repo)
if version_match:
releasever = version_match.group(1)
elif 'rawhide' not in first_repo.lower():
# If no version found and not rawhide, still use rawhide as fallback
releasever = 'rawhide'
# Use explicitly provided releasever, or detect from repo names
if self.releasever:
releasever = self.releasever
else:
releasever = 'rawhide'
if self.repos:
first_repo = self.repos[0]
import re
version_match = re.search(r'(\d+)', first_repo)
if version_match:
releasever = version_match.group(1)

# Configure releasever
vars_map = self.base.get_vars()
Expand Down Expand Up @@ -679,6 +679,7 @@ def main():
%(prog)s python-requests 2.32.0 --verbose
%(prog)s pytest 8.0.0 --repo fedora --repo fedora-source
%(prog)s numpy 2.0.0 --repo fedora-40 --repo fedora-40-source
%(prog)s pytest 8.0.0 --releasever 44
"""
)

Expand All @@ -690,11 +691,14 @@ def main():
help='Repository ID to enable (can be specified multiple times). '
'Default: rawhide, rawhide-source, koji, and koji-source. '
'Known repositories will be auto-configured if not in /etc/yum.repos.d/')
parser.add_argument('--releasever',
help='Fedora release version (e.g. 44, rawhide). '
'Auto-detected from repo names if not specified.')

args = parser.parse_args()

try:
checker = FedoraRevDepChecker(verbose=args.verbose, repos=args.repos)
checker = FedoraRevDepChecker(verbose=args.verbose, repos=args.repos, releasever=args.releasever)
results = checker.simulate_version_change(args.srpm_name, args.new_version)
checker.print_results(results)

Expand Down
92 changes: 77 additions & 15 deletions tests/e2e/test_full_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,68 @@
from fedora_revdep_check import main, FedoraRevDepChecker


class TestReleasever:
"""Test --releasever CLI argument and auto-detection logic."""

def test_releasever_passed_to_checker(self, monkeypatch, mock_dnf_base):
"""Test that --releasever CLI arg is passed through to FedoraRevDepChecker."""
monkeypatch.setattr('sys.argv', ['fedora-revdep-check', 'pytest', '8.0.0', '--releasever', '44'])

captured_releasever = []
original_init = FedoraRevDepChecker.__init__

def mock_init(self, verbose=False, base=None, repos=None, releasever=None):
captured_releasever.append(releasever)
original_init(self, verbose=verbose, base=mock_dnf_base if base is None else base, repos=repos, releasever=releasever)

monkeypatch.setattr(FedoraRevDepChecker, '__init__', mock_init)
main()

assert captured_releasever == ['44']

def test_releasever_none_when_not_specified(self, monkeypatch, mock_dnf_base):
"""Test that releasever is None when --releasever is not passed."""
monkeypatch.setattr('sys.argv', ['fedora-revdep-check', 'pytest', '8.0.0'])

captured_releasever = []
original_init = FedoraRevDepChecker.__init__

def mock_init(self, verbose=False, base=None, repos=None, releasever=None):
captured_releasever.append(releasever)
original_init(self, verbose=verbose, base=mock_dnf_base if base is None else base, repos=repos, releasever=releasever)

monkeypatch.setattr(FedoraRevDepChecker, '__init__', mock_init)
main()

assert captured_releasever == [None]

def test_releasever_rawhide(self, monkeypatch, mock_dnf_base):
"""Test that --releasever accepts 'rawhide'."""
monkeypatch.setattr('sys.argv', ['fedora-revdep-check', 'pytest', '8.0.0', '--releasever', 'rawhide'])

captured_releasever = []
original_init = FedoraRevDepChecker.__init__

def mock_init(self, verbose=False, base=None, repos=None, releasever=None):
captured_releasever.append(releasever)
original_init(self, verbose=verbose, base=mock_dnf_base if base is None else base, repos=repos, releasever=releasever)

monkeypatch.setattr(FedoraRevDepChecker, '__init__', mock_init)
main()

assert captured_releasever == ['rawhide']

def test_releasever_stored_on_instance(self, mock_dnf_base):
"""Test that releasever is stored on the checker instance."""
checker = FedoraRevDepChecker(verbose=False, base=mock_dnf_base, releasever='42')
assert checker.releasever == '42'

def test_releasever_default_is_none(self, mock_dnf_base):
"""Test that releasever defaults to None."""
checker = FedoraRevDepChecker(verbose=False, base=mock_dnf_base)
assert checker.releasever is None


class TestFullWorkflow:
"""Test complete CLI workflow through main() function."""

Expand All @@ -19,8 +81,8 @@ def test_main_with_conflicts_exit_code_one(self, monkeypatch, jupyterlab_base, c
# Mock FedoraRevDepChecker to use test base
original_init = FedoraRevDepChecker.__init__

def mock_init(self, verbose=False, base=None, repos=None):
original_init(self, verbose=verbose, base=jupyterlab_base if base is None else base, repos=repos)
def mock_init(self, verbose=False, base=None, repos=None, releasever=None):
original_init(self, verbose=verbose, base=jupyterlab_base if base is None else base, repos=repos, releasever=releasever)

monkeypatch.setattr(FedoraRevDepChecker, '__init__', mock_init)

Expand All @@ -40,8 +102,8 @@ def test_main_verbose_flag(self, monkeypatch, mock_pytest_base, capsys):

original_init = FedoraRevDepChecker.__init__

def mock_init(self, verbose=False, base=None, repos=None):
original_init(self, verbose=verbose, base=mock_pytest_base if base is None else base, repos=repos)
def mock_init(self, verbose=False, base=None, repos=None, releasever=None):
original_init(self, verbose=verbose, base=mock_pytest_base if base is None else base, repos=repos, releasever=releasever)

monkeypatch.setattr(FedoraRevDepChecker, '__init__', mock_init)

Expand All @@ -58,8 +120,8 @@ def test_main_nonexistent_package(self, monkeypatch, mock_dnf_base, capsys):

original_init = FedoraRevDepChecker.__init__

def mock_init(self, verbose=False, base=None, repos=None):
original_init(self, verbose=verbose, base=mock_dnf_base if base is None else base, repos=repos)
def mock_init(self, verbose=False, base=None, repos=None, releasever=None):
original_init(self, verbose=verbose, base=mock_dnf_base if base is None else base, repos=repos, releasever=releasever)

monkeypatch.setattr(FedoraRevDepChecker, '__init__', mock_init)

Expand All @@ -77,7 +139,7 @@ def test_main_exception_handling(self, monkeypatch, capsys):
monkeypatch.setattr('sys.argv', ['fedora-revdep-check', 'test', '1.0.0'])

# Mock FedoraRevDepChecker to raise an exception
def mock_init(self, verbose=False, base=None, repos=None):
def mock_init(self, verbose=False, base=None, repos=None, releasever=None):
raise RuntimeError("Test error message")

monkeypatch.setattr(FedoraRevDepChecker, '__init__', mock_init)
Expand All @@ -96,8 +158,8 @@ def test_main_output_format_consistency(self, monkeypatch, jupyterlab_base, caps

original_init = FedoraRevDepChecker.__init__

def mock_init(self, verbose=False, base=None, repos=None):
original_init(self, verbose=verbose, base=jupyterlab_base if base is None else base, repos=repos)
def mock_init(self, verbose=False, base=None, repos=None, releasever=None):
original_init(self, verbose=verbose, base=jupyterlab_base if base is None else base, repos=repos, releasever=releasever)

monkeypatch.setattr(FedoraRevDepChecker, '__init__', mock_init)

Expand All @@ -118,8 +180,8 @@ def test_main_deterministic_output(self, monkeypatch, jupyterlab_base):

original_init = FedoraRevDepChecker.__init__

def mock_init(self, verbose=False, base=None, repos=None):
original_init(self, verbose=verbose, base=jupyterlab_base if base is None else base, repos=repos)
def mock_init(self, verbose=False, base=None, repos=None, releasever=None):
original_init(self, verbose=verbose, base=jupyterlab_base if base is None else base, repos=repos, releasever=releasever)

monkeypatch.setattr(FedoraRevDepChecker, '__init__', mock_init)

Expand Down Expand Up @@ -147,8 +209,8 @@ def test_main_exit_code_zero_for_already_broken_only(self, monkeypatch, mock_dnf

original_init = FedoraRevDepChecker.__init__

def mock_init(self, verbose=False, base=None, repos=None):
original_init(self, verbose=verbose, base=mock_dnf_base if base is None else base, repos=repos)
def mock_init(self, verbose=False, base=None, repos=None, releasever=None):
original_init(self, verbose=verbose, base=mock_dnf_base if base is None else base, repos=repos, releasever=releasever)

def mock_simulate(self, srpm_name, new_version):
return {
Expand Down Expand Up @@ -185,8 +247,8 @@ def test_main_exit_code_one_for_mixed_conflicts(self, monkeypatch, mock_dnf_base

original_init = FedoraRevDepChecker.__init__

def mock_init(self, verbose=False, base=None, repos=None):
original_init(self, verbose=verbose, base=mock_dnf_base if base is None else base, repos=repos)
def mock_init(self, verbose=False, base=None, repos=None, releasever=None):
original_init(self, verbose=verbose, base=mock_dnf_base if base is None else base, repos=repos, releasever=releasever)

# Mock simulate_version_change to return mixed conflicts
def mock_simulate(self, srpm_name, new_version):
Expand Down
Loading