#!/usr/bin/python3 """ Reproduce a problem with libdnf5 returning more than one NEVRA requires: python3-libdnf5 python3-rpmfluff According to the documentation for filter_latest_evr() it should return one package per arch. But if there are multiple repositories with identical NEVRAs, and their priorities are the same it will return more than one. This script creates 2 repos with 3 fake rpm packages. fake-bart has 1 version, one copy. fake-milhouse has 2 versions, one in each repo. fake-marge has 1 version in both repos. When fake-marge is queried it returns both packages instead of picking one. If SET_PRIORITY is set to True it will decrement the priority while creating the repos, and will return 1 fake-marge. """ # Set this to true to assign different priorities to the repos SET_PRIORITY = False from contextlib import contextmanager import os import shutil import subprocess import tempfile import libdnf5 from libdnf5.common import QueryCmp_EQ as EQ from rpmfluff import SimpleRpmBuild, SourceFile, expectedArch @contextmanager def in_tempdir(prefix='tmp'): """Execute a block of code with chdir in a temporary location""" oldcwd = os.getcwd() tmpdir = tempfile.mkdtemp(prefix=prefix) os.chdir(tmpdir) try: yield finally: os.chdir(oldcwd) shutil.rmtree(tmpdir) def makeFakeRPM(repo_dir, name, epoch, version, release, files=None): """Make a fake rpm file in repo_dir""" p = SimpleRpmBuild(name, version, release) if epoch: p.epoch = epoch if not files: p.add_simple_payload_file_random() else: # Make a number of fake file entries in the rpm for f in files: p.add_installed_file( installPath = f, sourceFile = SourceFile(os.path.basename(f), "THIS IS A FAKE FILE")) with in_tempdir("lorax-test-rpms."): p.make() rpmfile = p.get_built_rpm(expectedArch) shutil.move(rpmfile, repo_dir) def setup_fake_repos(): # Make a couple temporary repos with some fake rpms repo1_dir = tempfile.mkdtemp(prefix="lorax.test.repo.") repo2_dir = tempfile.mkdtemp(prefix="lorax.test.repo.") # A newer fake-milhouse and a duplicate fake-marge makeFakeRPM(repo1_dir, "fake-bart", 2, "1.13.0", "6") makeFakeRPM(repo1_dir, "fake-milhouse", 0, "1.3.0", "1", ["/fake-milhouse/1.3.0-1"]) makeFakeRPM(repo1_dir, "fake-marge", 0, "2.3.0", "1", ["/fake-marge/2.3.0-1"]) makeFakeRPM(repo2_dir, "fake-milhouse", 0, "1.0.0", "4", ["/fake-milhouse/1.0.0-4"]) makeFakeRPM(repo2_dir, "fake-marge", 0, "2.3.0", "1", ["/fake-marge/2.3.0-1"]) subprocess.check_call(["createrepo_c", repo1_dir]) subprocess.check_call(["createrepo_c", repo2_dir]) return [repo1_dir, repo2_dir] def setup_base(sources, set_priority=False): base = libdnf5.base.Base() conf = base.get_config() conf.zchunk = False base.setup() sack = base.get_repo_sack() priority = 99 for i, r in enumerate(sources): repo_name = "lorax-repo-%d" % i repo = sack.create_repo(repo_name) rc = repo.get_config() rc.baseurl = f"file://{r}" if set_priority: rc.priority = priority - i else: rc.priority = 99 print(f"Added '{repo_name}': {r}") print("Fetching metadata...") try: sack.update_and_load_enabled_repos(False) except RuntimeError as e: print("Problem fetching metadata: %s", e) return return base def print_nevras(query): query.filter_arch(["x86_64", "noarch"]) query.filter_latest_evr() query.filter_priority() for p in query: print(p.get_nevra()) def main(): sources = setup_fake_repos() base = setup_base(sources, SET_PRIORITY) query = libdnf5.rpm.PackageQuery(base) query.filter_name(["fake-milhouse"], EQ) print("Should print a single fake-milhouse") print_nevras(query) query = libdnf5.rpm.PackageQuery(base) query.filter_name(["fake-marge"], EQ) print("Should print a single fake-marge but prints two") print_nevras(query) if __name__=="__main__": main()