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

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