Blame SOURCES/0015-Add-tests-and-docs-for-fill-sack-from-repos-in-cache-RhBug-1865803.patch

4f4af9
From a777ff01c79d5e0e2cf3ae7b0652795577253bc3 Mon Sep 17 00:00:00 2001
4f4af9
From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= <amatej@redhat.com>
4f4af9
Date: Thu, 14 Jan 2021 09:58:30 +0100
4f4af9
Subject: [PATCH 1/3] Fix recreate script
4f4af9
4f4af9
---
4f4af9
 tests/repos/rpm/recreate | 2 +-
4f4af9
 1 file changed, 1 insertion(+), 1 deletion(-)
4f4af9
4f4af9
diff --git a/tests/repos/rpm/recreate b/tests/repos/rpm/recreate
4f4af9
index da348d9799..0fbb9396bd 100755
4f4af9
--- a/tests/repos/rpm/recreate
4f4af9
+++ b/tests/repos/rpm/recreate
4f4af9
@@ -1,6 +1,6 @@
4f4af9
 #!/bin/bash
4f4af9
 
4f4af9
-THISDIR="$( readlink -f "$( dirname "$0 )" )"
4f4af9
+THISDIR="$( readlink -f "$( dirname "$0" )" )"
4f4af9
 cd "$THISDIR"
4f4af9
 git rm -rf repodata/
4f4af9
 createrepo --no-database -o . ..
4f4af9
4f4af9
From 5d4c0266f6967c7cd5f0e675b13fa3e9b395e4dd Mon Sep 17 00:00:00 2001
4f4af9
From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= <amatej@redhat.com>
4f4af9
Date: Thu, 14 Jan 2021 10:28:53 +0100
4f4af9
Subject: [PATCH 2/3] Add unit test for fill_sack_from_repos_in_cache
4f4af9
 (RhBug:1865803)
4f4af9
4f4af9
https://bugzilla.redhat.com/show_bug.cgi?id=1865803
4f4af9
---
4f4af9
 tests/test_fill_sack_from_repos_in_cache.py | 262 ++++++++++++++++++++
4f4af9
 1 file changed, 262 insertions(+)
4f4af9
 create mode 100644 tests/test_fill_sack_from_repos_in_cache.py
4f4af9
4f4af9
diff --git a/tests/test_fill_sack_from_repos_in_cache.py b/tests/test_fill_sack_from_repos_in_cache.py
4f4af9
new file mode 100644
4f4af9
index 0000000000..24b0d4598d
4f4af9
--- /dev/null
4f4af9
+++ b/tests/test_fill_sack_from_repos_in_cache.py
4f4af9
@@ -0,0 +1,262 @@
4f4af9
+# -*- coding: utf-8 -*-
4f4af9
+
4f4af9
+# Copyright (C) 2012-2021 Red Hat, Inc.
4f4af9
+#
4f4af9
+# This copyrighted material is made available to anyone wishing to use,
4f4af9
+# modify, copy, or redistribute it subject to the terms and conditions of
4f4af9
+# the GNU General Public License v.2, or (at your option) any later version.
4f4af9
+# This program is distributed in the hope that it will be useful, but WITHOUT
4f4af9
+# ANY WARRANTY expressed or implied, including the implied warranties of
4f4af9
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
4f4af9
+# Public License for more details.  You should have received a copy of the
4f4af9
+# GNU General Public License along with this program; if not, write to the
4f4af9
+# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
4f4af9
+# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
4f4af9
+# source code or documentation are not subject to the GNU General Public
4f4af9
+# License and may only be used or replicated with the express permission of
4f4af9
+# Red Hat, Inc.
4f4af9
+#
4f4af9
+
4f4af9
+from __future__ import absolute_import
4f4af9
+from __future__ import unicode_literals
4f4af9
+
4f4af9
+import os
4f4af9
+import tempfile
4f4af9
+import glob
4f4af9
+import shutil
4f4af9
+import unittest
4f4af9
+
4f4af9
+import dnf.exceptions
4f4af9
+import dnf.repo
4f4af9
+import dnf.sack
4f4af9
+
4f4af9
+import hawkey
4f4af9
+
4f4af9
+import tests.support
4f4af9
+from tests.support import mock
4f4af9
+
4f4af9
+TEST_REPO_NAME = "test-repo"
4f4af9
+
4f4af9
+
4f4af9
+class FillSackFromReposInCacheTest(unittest.TestCase):
4f4af9
+    def _create_cache_for_repo(self, repopath, tmpdir):
4f4af9
+        conf = dnf.conf.MainConf()
4f4af9
+        conf.cachedir = os.path.join(tmpdir, "cache")
4f4af9
+
4f4af9
+        base = dnf.Base(conf=conf)
4f4af9
+
4f4af9
+        repoconf = dnf.repo.Repo(TEST_REPO_NAME, base.conf)
4f4af9
+        repoconf.baseurl = repopath
4f4af9
+        repoconf.enable()
4f4af9
+
4f4af9
+        base.repos.add(repoconf)
4f4af9
+
4f4af9
+        base.fill_sack(load_system_repo=False)
4f4af9
+        base.close()
4f4af9
+
4f4af9
+    def _setUp_from_repo_path(self, original_repo_path):
4f4af9
+        self.tmpdir = tempfile.mkdtemp(prefix="dnf_test_")
4f4af9
+
4f4af9
+        self.repo_copy_path = os.path.join(self.tmpdir, "repo")
4f4af9
+        shutil.copytree(original_repo_path, self.repo_copy_path)
4f4af9
+
4f4af9
+        self._create_cache_for_repo(self.repo_copy_path, self.tmpdir)
4f4af9
+
4f4af9
+        # Just to be sure remove repo (it shouldn't be used)
4f4af9
+        shutil.rmtree(self.repo_copy_path)
4f4af9
+
4f4af9
+        # Prepare base for the actual test
4f4af9
+        conf = dnf.conf.MainConf()
4f4af9
+        conf.cachedir = os.path.join(self.tmpdir, "cache")
4f4af9
+        self.test_base = dnf.Base(conf=conf)
4f4af9
+        repoconf = dnf.repo.Repo(TEST_REPO_NAME, conf)
4f4af9
+        repoconf.baseurl = self.repo_copy_path
4f4af9
+        repoconf.enable()
4f4af9
+        self.test_base.repos.add(repoconf)
4f4af9
+
4f4af9
+    def tearDown(self):
4f4af9
+        self.test_base.close()
4f4af9
+        shutil.rmtree(self.tmpdir)
4f4af9
+
4f4af9
+    def test_with_solv_solvx_repomd(self):
4f4af9
+        self._setUp_from_repo_path(os.path.join(os.path.abspath(os.path.dirname(__file__)), "repos/rpm"))
4f4af9
+
4f4af9
+        # Remove xml metadata except repomd
4f4af9
+        # repomd.xml is not compressed and doesn't end with .gz
4f4af9
+        repodata_without_repomd = glob.glob(os.path.join(self.tmpdir, "cache/test-repo-*/repodata/*.gz"))
4f4af9
+        for f in repodata_without_repomd:
4f4af9
+            os.remove(f)
4f4af9
+
4f4af9
+        # Now we only have cache with just solv, solvx files and repomd.xml
4f4af9
+
4f4af9
+        self.test_base.fill_sack_from_repos_in_cache(load_system_repo=False)
4f4af9
+
4f4af9
+        q = self.test_base.sack.query()
4f4af9
+        packages = q.run()
4f4af9
+        self.assertEqual(len(packages), 9)
4f4af9
+        self.assertEqual(packages[0].evr, "4-4")
4f4af9
+
4f4af9
+        # Use *-updateinfo.solvx
4f4af9
+        adv_pkgs = q.get_advisory_pkgs(hawkey.LT | hawkey.EQ | hawkey.GT)
4f4af9
+        adv_titles = set()
4f4af9
+        for pkg in adv_pkgs:
4f4af9
+            adv_titles.add(pkg.get_advisory(self.test_base.sack).title)
4f4af9
+        self.assertEqual(len(adv_titles), 3)
4f4af9
+
4f4af9
+    def test_with_just_solv_repomd(self):
4f4af9
+        self._setUp_from_repo_path(os.path.join(os.path.abspath(os.path.dirname(__file__)), "repos/rpm"))
4f4af9
+
4f4af9
+        # Remove xml metadata except repomd
4f4af9
+        # repomd.xml is not compressed and doesn't end with .gz
4f4af9
+        repodata_without_repomd = glob.glob(os.path.join(self.tmpdir, "cache/test-repo-*/repodata/*.gz"))
4f4af9
+        for f in repodata_without_repomd:
4f4af9
+            os.remove(f)
4f4af9
+
4f4af9
+        # Remove solvx files
4f4af9
+        solvx = glob.glob(os.path.join(self.tmpdir, "cache/*.solvx"))
4f4af9
+        for f in solvx:
4f4af9
+            os.remove(f)
4f4af9
+
4f4af9
+        # Now we only have cache with just solv files and repomd.xml
4f4af9
+
4f4af9
+        self.test_base.fill_sack_from_repos_in_cache(load_system_repo=False)
4f4af9
+
4f4af9
+        q = self.test_base.sack.query()
4f4af9
+        packages = q.run()
4f4af9
+        self.assertEqual(len(packages), 9)
4f4af9
+        self.assertEqual(packages[0].evr, "4-4")
4f4af9
+
4f4af9
+        # No *-updateinfo.solvx -> we get no advisory packages
4f4af9
+        adv_pkgs = q.get_advisory_pkgs(hawkey.LT | hawkey.EQ | hawkey.GT)
4f4af9
+        self.assertEqual(len(adv_pkgs), 0)
4f4af9
+
4f4af9
+    def test_with_xml_metadata(self):
4f4af9
+        self._setUp_from_repo_path(os.path.join(os.path.abspath(os.path.dirname(__file__)), "repos/rpm"))
4f4af9
+
4f4af9
+        # Remove all solv and solvx files
4f4af9
+        solvx = glob.glob(os.path.join(self.tmpdir, "cache/*.solv*"))
4f4af9
+        for f in solvx:
4f4af9
+            os.remove(f)
4f4af9
+
4f4af9
+        # Now we only have cache with just xml metadata
4f4af9
+
4f4af9
+        self.test_base.fill_sack_from_repos_in_cache(load_system_repo=False)
4f4af9
+
4f4af9
+        q = self.test_base.sack.query()
4f4af9
+        packages = q.run()
4f4af9
+        self.assertEqual(len(packages), 9)
4f4af9
+        self.assertEqual(packages[0].evr, "4-4")
4f4af9
+
4f4af9
+    def test_exception_without_repomd(self):
4f4af9
+        self._setUp_from_repo_path(os.path.join(os.path.abspath(os.path.dirname(__file__)), "repos/rpm"))
4f4af9
+
4f4af9
+        # Remove xml metadata
4f4af9
+        repodata_without_repomd = glob.glob(os.path.join(self.tmpdir, "cache/test-repo-*/repodata/*"))
4f4af9
+        for f in repodata_without_repomd:
4f4af9
+            os.remove(f)
4f4af9
+
4f4af9
+        # Now we only have cache with just solv and solvx files
4f4af9
+        # Since we don't have repomd we cannot verify checksums -> fail (exception)
4f4af9
+
4f4af9
+        self.assertRaises(dnf.exceptions.RepoError,
4f4af9
+                          self.test_base.fill_sack_from_repos_in_cache, load_system_repo=False)
4f4af9
+
4f4af9
+    def test_exception_with_just_repomd(self):
4f4af9
+        self._setUp_from_repo_path(os.path.join(os.path.abspath(os.path.dirname(__file__)), "repos/rpm"))
4f4af9
+
4f4af9
+        # Remove xml metadata except repomd
4f4af9
+        # repomd.xml is not compressed and doesn't end with .gz
4f4af9
+        repodata_without_repomd = glob.glob(os.path.join(self.tmpdir, "cache/test-repo-*/repodata/*.gz"))
4f4af9
+        for f in repodata_without_repomd:
4f4af9
+            os.remove(f)
4f4af9
+
4f4af9
+        # Remove all solv and solvx files
4f4af9
+        solvx = glob.glob(os.path.join(self.tmpdir, "cache/*.solv*"))
4f4af9
+        for f in solvx:
4f4af9
+            os.remove(f)
4f4af9
+
4f4af9
+        # Now we only have cache with just repomd
4f4af9
+        # repomd is not enough, it doesn't contain the metadata it self -> fail (exception)
4f4af9
+
4f4af9
+        self.assertRaises(dnf.exceptions.RepoError,
4f4af9
+                          self.test_base.fill_sack_from_repos_in_cache, load_system_repo=False)
4f4af9
+
4f4af9
+    def test_exception_with_checksum_mismatch_and_only_repomd(self):
4f4af9
+        self._setUp_from_repo_path(os.path.join(os.path.abspath(os.path.dirname(__file__)), "repos/rpm"))
4f4af9
+
4f4af9
+        # Remove xml metadata except repomd
4f4af9
+        # repomd.xml is not compressed and doesn't end with .gz
4f4af9
+        repodata_without_repomd = glob.glob(os.path.join(self.tmpdir, "cache/test-repo-*/repodata/*.gz"))
4f4af9
+        for f in repodata_without_repomd:
4f4af9
+            os.remove(f)
4f4af9
+
4f4af9
+        # Modify checksum of solv file so it doesn't match with repomd
4f4af9
+        solv = glob.glob(os.path.join(self.tmpdir, "cache/*.solv"))[0]
4f4af9
+        with open(solv, "a") as opensolv:
4f4af9
+            opensolv.write("appended text to change checksum")
4f4af9
+
4f4af9
+        # Now we only have cache with solvx, modified solv file and just repomd
4f4af9
+        # Since we don't have original xml metadata we cannot regenerate solv -> fail (exception)
4f4af9
+
4f4af9
+        self.assertRaises(dnf.exceptions.RepoError,
4f4af9
+                          self.test_base.fill_sack_from_repos_in_cache, load_system_repo=False)
4f4af9
+
4f4af9
+    def test_checksum_mistmatch_regenerates_solv(self):
4f4af9
+        self._setUp_from_repo_path(os.path.join(os.path.abspath(os.path.dirname(__file__)), "repos/rpm"))
4f4af9
+
4f4af9
+        # Modify checksum of solv file so it doesn't match with repomd
4f4af9
+        solv = glob.glob(os.path.join(self.tmpdir, "cache/*.solv"))[0]
4f4af9
+        with open(solv, "a") as opensolv:
4f4af9
+            opensolv.write("appended text to change checksum")
4f4af9
+
4f4af9
+        # Now we only have cache with solvx, modified solv file and xml metadata.
4f4af9
+        # Checksum mistmatch causes regeneration of solv file and repo works.
4f4af9
+
4f4af9
+        self.test_base.fill_sack_from_repos_in_cache(load_system_repo=False)
4f4af9
+
4f4af9
+        q = self.test_base.sack.query()
4f4af9
+        packages = q.run()
4f4af9
+        self.assertEqual(len(packages), 9)
4f4af9
+        self.assertEqual(packages[0].evr, "4-4")
4f4af9
+
4f4af9
+    def test_with_modules_yaml(self):
4f4af9
+        self._setUp_from_repo_path(os.path.join(os.path.abspath(os.path.dirname(__file__)),
4f4af9
+                                                "modules/modules/_all/x86_64"))
4f4af9
+
4f4af9
+        # Now we have full cache (also with modules.yaml)
4f4af9
+
4f4af9
+        self.test_base.fill_sack_from_repos_in_cache(load_system_repo=False)
4f4af9
+
4f4af9
+        q = self.test_base.sack.query()
4f4af9
+        packages = q.run()
4f4af9
+        self.assertEqual(len(packages), 8)
4f4af9
+        self.assertEqual(packages[0].evr, "2.02-0.40")
4f4af9
+
4f4af9
+        self.module_base = dnf.module.module_base.ModuleBase(self.test_base)
4f4af9
+        modules, _ = self.module_base._get_modules("base-runtime*")
4f4af9
+        self.assertEqual(len(modules), 3)
4f4af9
+        self.assertEqual(modules[0].getFullIdentifier(), "base-runtime:f26:1::")
4f4af9
+
4f4af9
+    def test_with_modular_repo_without_modules_yaml(self):
4f4af9
+        self._setUp_from_repo_path(os.path.join(os.path.abspath(os.path.dirname(__file__)),
4f4af9
+                                                "modules/modules/_all/x86_64"))
4f4af9
+
4f4af9
+        # Remove xml and yaml metadata except repomd
4f4af9
+        # repomd.xml is not compressed and doesn't end with .gz
4f4af9
+        repodata_without_repomd = glob.glob(os.path.join(self.tmpdir, "cache/test-repo-*/repodata/*.gz"))
4f4af9
+        for f in repodata_without_repomd:
4f4af9
+            os.remove(f)
4f4af9
+
4f4af9
+        # Now we have just solv, *-filenames.solvx and repomd.xml (modules.yaml are not processed into *-modules.solvx)
4f4af9
+
4f4af9
+        self.test_base.fill_sack_from_repos_in_cache(load_system_repo=False)
4f4af9
+
4f4af9
+        q = self.test_base.sack.query()
4f4af9
+        packages = q.run()
4f4af9
+        # We have many more packages because they are not hidden by modules
4f4af9
+        self.assertEqual(len(packages), 44)
4f4af9
+        self.assertEqual(packages[0].evr, "10.0-7")
4f4af9
+
4f4af9
+        self.module_base = dnf.module.module_base.ModuleBase(self.test_base)
4f4af9
+        modules, _ = self.module_base._get_modules("base-runtime*")
4f4af9
+        self.assertEqual(len(modules), 0)
4f4af9
4f4af9
From de6177dba3dc20191e275eec14672570a0c4f4a8 Mon Sep 17 00:00:00 2001
4f4af9
From: =?UTF-8?q?Ale=C5=A1=20Mat=C4=9Bj?= <amatej@redhat.com>
4f4af9
Date: Thu, 14 Jan 2021 12:29:06 +0100
4f4af9
Subject: [PATCH 3/3] Add docs and examples for fill_sack_from_repos_in_cache
4f4af9
 (RhBug:1865803)
4f4af9
4f4af9
https://bugzilla.redhat.com/show_bug.cgi?id=1865803
4f4af9
---
4f4af9
 doc/api_base.rst | 41 +++++++++++++++++++++++++++++++++++++++++
4f4af9
 1 file changed, 41 insertions(+)
4f4af9
4f4af9
diff --git a/doc/api_base.rst b/doc/api_base.rst
4f4af9
index 24ecb50e43..f0b1992e88 100644
4f4af9
--- a/doc/api_base.rst
4f4af9
+++ b/doc/api_base.rst
4f4af9
@@ -111,6 +111,47 @@
4f4af9
             print("id: {}".format(repo.id))
4f4af9
             print("baseurl: {}".format(repo.baseurl))
4f4af9
 
4f4af9
+  .. method:: fill_sack_from_repos_in_cache(load_system_repo=True)
4f4af9
+
4f4af9
+    Prepare Sack and Goal objects and load all enabled repositories from cache only, it doesn't download anything and it doesn't check if metadata are expired.
4f4af9
+    To successfully load a repository cache it requires repond.xml plus metadata (xml, yaml) or repond.xml plus generated cache files (solv, solvx).
4f4af9
+    If there is not enough metadata given repo is either skipped or it throws a :exc:`dnf.exceptions.RepoError` exception depending on :attr:`dnf.conf.Conf.skip_if_unavailable` configuration.
4f4af9
+
4f4af9
+    All additional metadata are loaded if present but are not generally required. Note that some metadata like updateinfo.xml get processed into a solvx cache file and its sufficient to have either xml or solvx. Module metadata represented by modules.yaml are not processed therefore they are needed when they are defined in repomd.xml.
4f4af9
+
4f4af9
+    Example of loading all configured repositories from cache and printing available packages' names::
4f4af9
+
4f4af9
+        #!/usr/bin/python3
4f4af9
+        import dnf
4f4af9
+
4f4af9
+        with dnf.Base() as base:
4f4af9
+            base.read_all_repos()
4f4af9
+
4f4af9
+            base.fill_sack_from_repos_in_cache(load_system_repo=False)
4f4af9
+
4f4af9
+            query = base.sack.query().available()
4f4af9
+            for pkg in query.run():
4f4af9
+                print(pkg.name)
4f4af9
+
4f4af9
+    Example of loading a single repository and printing available packages' names without reading repository configuration::
4f4af9
+
4f4af9
+        #!/usr/bin/python3
4f4af9
+        import dnf
4f4af9
+
4f4af9
+        with dnf.Base() as base:
4f4af9
+            repo = dnf.repo.Repo("rawhide", base.conf)
4f4af9
+
4f4af9
+            # Repository cache is also identified by its source therefore to find it you need to
4f4af9
+            # set metalink, mirrorlist or baseurl to the same value from which it was created.
4f4af9
+            repo.metalink = "https://mirrors.fedoraproject.org/metalink?repo=rawhide&arch=x86_64"
4f4af9
+
4f4af9
+            base.repos.add(repo)
4f4af9
+
4f4af9
+            base.fill_sack_from_repos_in_cache(load_system_repo=False)
4f4af9
+
4f4af9
+            query = base.sack.query().available()
4f4af9
+            for pkg in query.run():
4f4af9
+                print(pkg.name)
4f4af9
 
4f4af9
   .. method:: do_transaction([display])
4f4af9