From 81aeb56ba31ca04b1567073071b2581df0cd6f98 Mon Sep 17 00:00:00 2001 From: Evgenii Alekseev Date: Wed, 11 Mar 2026 17:11:37 +0200 Subject: [PATCH] refactor: lightweight Package.from_archive method --- docs/ahriman.core.alpm.rst | 8 ++ src/ahriman/core/alpm/pacman.py | 25 +++--- src/ahriman/core/alpm/pacman_handle.py | 81 +++++++++++++++++++ .../migrations/m005_make_opt_depends.py | 7 +- .../database/migrations/m007_check_depends.py | 7 +- .../database/migrations/m008_packagers.py | 7 +- .../core/database/migrations/m016_archive.py | 10 +-- src/ahriman/core/repository/executor.py | 4 +- src/ahriman/core/repository/package_info.py | 30 +++++-- src/ahriman/models/package.py | 11 ++- tests/ahriman/core/alpm/test_pacman_handle.py | 37 +++++++++ .../migrations/test_m005_make_opt_depends.py | 3 +- .../migrations/test_m007_check_depends.py | 3 +- .../migrations/test_m008_packagers.py | 3 +- .../database/migrations/test_m016_archive.py | 9 +-- tests/ahriman/core/repository/conftest.py | 18 ----- .../ahriman/core/repository/test_executor.py | 2 +- .../core/repository/test_package_info.py | 65 +++++++-------- tests/ahriman/models/conftest.py | 16 ---- tests/ahriman/models/test_package.py | 16 ++-- 20 files changed, 223 insertions(+), 139 deletions(-) create mode 100644 src/ahriman/core/alpm/pacman_handle.py create mode 100644 tests/ahriman/core/alpm/test_pacman_handle.py diff --git a/docs/ahriman.core.alpm.rst b/docs/ahriman.core.alpm.rst index ec47db9a..2b78bea7 100644 --- a/docs/ahriman.core.alpm.rst +++ b/docs/ahriman.core.alpm.rst @@ -28,6 +28,14 @@ ahriman.core.alpm.pacman\_database module :no-undoc-members: :show-inheritance: +ahriman.core.alpm.pacman\_handle module +--------------------------------------- + +.. automodule:: ahriman.core.alpm.pacman_handle + :members: + :no-undoc-members: + :show-inheritance: + ahriman.core.alpm.pkgbuild\_parser module ----------------------------------------- diff --git a/src/ahriman/core/alpm/pacman.py b/src/ahriman/core/alpm/pacman.py index 797428fe..224e9db2 100644 --- a/src/ahriman/core/alpm/pacman.py +++ b/src/ahriman/core/alpm/pacman.py @@ -24,10 +24,11 @@ import tarfile from collections.abc import Iterable, Iterator from functools import cached_property from pathlib import Path -from pyalpm import DB, Handle, Package, SIG_DATABASE_OPTIONAL, SIG_PACKAGE_OPTIONAL # type: ignore[import-not-found] +from pyalpm import DB, Package, SIG_DATABASE_OPTIONAL, SIG_PACKAGE_OPTIONAL # type: ignore[import-not-found] from string import Template from ahriman.core.alpm.pacman_database import PacmanDatabase +from ahriman.core.alpm.pacman_handle import PacmanHandle from ahriman.core.configuration import Configuration from ahriman.core.log import LazyLogging from ahriman.core.utils import trim_package @@ -61,16 +62,16 @@ class Pacman(LazyLogging): self.refresh_database = refresh_database @cached_property - def handle(self) -> Handle: + def handle(self) -> PacmanHandle: """ pyalpm handle Returns: - Handle: generated pyalpm handle instance + PacmanHandle: generated pyalpm handle instance """ return self.__create_handle(refresh_database=self.refresh_database) - def __create_handle(self, *, refresh_database: PacmanSynchronization) -> Handle: + def __create_handle(self, *, refresh_database: PacmanSynchronization) -> PacmanHandle: """ create lazy handle function @@ -78,14 +79,14 @@ class Pacman(LazyLogging): refresh_database(PacmanSynchronization): synchronize local cache to remote Returns: - Handle: fully initialized pacman handle + PacmanHandle: fully initialized pacman handle """ pacman_root = self.configuration.getpath("alpm", "database") use_ahriman_cache = self.configuration.getboolean("alpm", "use_ahriman_cache") database_path = self.repository_paths.pacman if use_ahriman_cache else pacman_root root = self.configuration.getpath("alpm", "root") - handle = Handle(str(root), str(database_path)) + handle = PacmanHandle(str(root), str(database_path)) for repository in self.configuration.getlist("alpm", "repositories"): database = self.database_init(handle, repository, self.repository_id.architecture) @@ -99,12 +100,12 @@ class Pacman(LazyLogging): return handle - def database_copy(self, handle: Handle, database: DB, pacman_root: Path, *, use_ahriman_cache: bool) -> None: + def database_copy(self, handle: PacmanHandle, database: DB, pacman_root: Path, *, use_ahriman_cache: bool) -> None: """ copy database from the operating system root to the ahriman local home Args: - handle(Handle): pacman handle which will be used for database copying + handle(PacmanHandle): pacman handle which will be used for database copying database(DB): pacman database instance to be copied pacman_root(Path): operating system pacman root use_ahriman_cache(bool): use local ahriman cache instead of system one @@ -133,12 +134,12 @@ class Pacman(LazyLogging): with self.repository_paths.preserve_owner(): shutil.copy(src, dst) - def database_init(self, handle: Handle, repository: str, architecture: str) -> DB: + def database_init(self, handle: PacmanHandle, repository: str, architecture: str) -> DB: """ create database instance from pacman handler and set its properties Args: - handle(Handle): pacman handle which will be used for database initializing + handle(PacmanHandle): pacman handle which will be used for database initializing repository(str): pacman repository name (e.g. core) architecture(str): repository architecture @@ -164,12 +165,12 @@ class Pacman(LazyLogging): return database - def database_sync(self, handle: Handle, *, force: bool) -> None: + def database_sync(self, handle: PacmanHandle, *, force: bool) -> None: """ sync local database Args: - handle(Handle): pacman handle which will be used for database sync + handle(PacmanHandle): pacman handle which will be used for database sync force(bool): force database synchronization (same as ``pacman -Syy``) """ self.logger.info("refresh ahriman's home pacman database (force refresh %s)", force) diff --git a/src/ahriman/core/alpm/pacman_handle.py b/src/ahriman/core/alpm/pacman_handle.py new file mode 100644 index 00000000..32bc2a5d --- /dev/null +++ b/src/ahriman/core/alpm/pacman_handle.py @@ -0,0 +1,81 @@ +# +# Copyright (c) 2021-2026 ahriman team. +# +# This file is part of ahriman +# (see https://github.com/arcan1s/ahriman). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +from pathlib import Path +from pyalpm import Handle, Package # type: ignore[import-not-found] +from tempfile import TemporaryDirectory +from typing import Any, ClassVar, Self + + +class PacmanHandle: + """ + lightweight wrapper for pacman handle to be used for direct alpm operations (e.g. package load) + + Attributes: + handle(Handle): pyalpm handle instance + """ + + _ephemeral: ClassVar[Self | None] = None + + def __init__(self, *args: Any, **kwargs: Any) -> None: + """ + Args: + *args(Any): positional arguments for :class:`pyalpm.Handle` + **kwargs(Any): keyword arguments for :class:`pyalpm.Handle` + """ + self.handle = Handle(*args, **kwargs) + + @classmethod + def ephemeral(cls) -> Self: + """ + create temporary instance with no access to real databases + + Returns: + Self: loaded class + """ + if cls._ephemeral is None: + # handle creates alpm version file, but we don't use it + # so it is ok to just remove it + with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name: + cls._ephemeral = cls("/", dir_name) + return cls._ephemeral + + def package_load(self, path: Path) -> Package: + """ + load package from path to the archive + + Args: + path(Path): path to package archive + + Returns: + Package: package instance + """ + return self.handle.load_pkg(str(path)) + + def __getattr__(self, item: str) -> Any: + """ + proxy methods for :class:`pyalpm.Handle`, because it doesn't allow subclassing + + Args: + item(str): property name + + Returns: + Any: attribute by its name + """ + return self.handle.__getattribute__(item) diff --git a/src/ahriman/core/database/migrations/m005_make_opt_depends.py b/src/ahriman/core/database/migrations/m005_make_opt_depends.py index 1cc0fb75..709171cc 100644 --- a/src/ahriman/core/database/migrations/m005_make_opt_depends.py +++ b/src/ahriman/core/database/migrations/m005_make_opt_depends.py @@ -19,11 +19,9 @@ # from sqlite3 import Connection -from ahriman.core.alpm.pacman import Pacman from ahriman.core.configuration import Configuration from ahriman.core.utils import package_like from ahriman.models.package import Package -from ahriman.models.pacman_synchronization import PacmanSynchronization __all__ = ["migrate_data", "steps"] @@ -61,12 +59,9 @@ def migrate_package_depends(connection: Connection, configuration: Configuration if not configuration.repository_paths.repository.is_dir(): return - _, repository_id = configuration.check_loaded() - pacman = Pacman(repository_id, configuration, refresh_database=PacmanSynchronization.Disabled) - package_list = [] for full_path in filter(package_like, configuration.repository_paths.repository.iterdir()): - base = Package.from_archive(full_path, pacman) + base = Package.from_archive(full_path) for package, description in base.packages.items(): package_list.append({ "make_depends": description.make_depends, diff --git a/src/ahriman/core/database/migrations/m007_check_depends.py b/src/ahriman/core/database/migrations/m007_check_depends.py index dd0ff5d0..c00af4e0 100644 --- a/src/ahriman/core/database/migrations/m007_check_depends.py +++ b/src/ahriman/core/database/migrations/m007_check_depends.py @@ -19,11 +19,9 @@ # from sqlite3 import Connection -from ahriman.core.alpm.pacman import Pacman from ahriman.core.configuration import Configuration from ahriman.core.utils import package_like from ahriman.models.package import Package -from ahriman.models.pacman_synchronization import PacmanSynchronization __all__ = ["migrate_data", "steps"] @@ -58,12 +56,9 @@ def migrate_package_check_depends(connection: Connection, configuration: Configu if not configuration.repository_paths.repository.is_dir(): return - _, repository_id = configuration.check_loaded() - pacman = Pacman(repository_id, configuration, refresh_database=PacmanSynchronization.Disabled) - package_list = [] for full_path in filter(package_like, configuration.repository_paths.repository.iterdir()): - base = Package.from_archive(full_path, pacman) + base = Package.from_archive(full_path) for package, description in base.packages.items(): package_list.append({ "check_depends": description.check_depends, diff --git a/src/ahriman/core/database/migrations/m008_packagers.py b/src/ahriman/core/database/migrations/m008_packagers.py index 53fd2a56..88b8791d 100644 --- a/src/ahriman/core/database/migrations/m008_packagers.py +++ b/src/ahriman/core/database/migrations/m008_packagers.py @@ -19,11 +19,9 @@ # from sqlite3 import Connection -from ahriman.core.alpm.pacman import Pacman from ahriman.core.configuration import Configuration from ahriman.core.utils import package_like from ahriman.models.package import Package -from ahriman.models.pacman_synchronization import PacmanSynchronization __all__ = ["migrate_data", "steps"] @@ -64,12 +62,9 @@ def migrate_package_base_packager(connection: Connection, configuration: Configu if not configuration.repository_paths.repository.is_dir(): return - _, repository_id = configuration.check_loaded() - pacman = Pacman(repository_id, configuration, refresh_database=PacmanSynchronization.Disabled) - package_list = [] for full_path in filter(package_like, configuration.repository_paths.repository.iterdir()): - package = Package.from_archive(full_path, pacman) + package = Package.from_archive(full_path) package_list.append({ "package_base": package.base, "packager": package.packager, diff --git a/src/ahriman/core/database/migrations/m016_archive.py b/src/ahriman/core/database/migrations/m016_archive.py index 65bf6b53..a90a80f9 100644 --- a/src/ahriman/core/database/migrations/m016_archive.py +++ b/src/ahriman/core/database/migrations/m016_archive.py @@ -20,13 +20,11 @@ from dataclasses import replace from sqlite3 import Connection -from ahriman.core.alpm.pacman import Pacman from ahriman.core.configuration import Configuration from ahriman.core.repository import Explorer from ahriman.core.sign.gpg import GPG from ahriman.core.utils import atomic_move, package_like, symlink_relative from ahriman.models.package import Package -from ahriman.models.pacman_synchronization import PacmanSynchronization from ahriman.models.repository_paths import RepositoryPaths @@ -45,29 +43,27 @@ def migrate_data(connection: Connection, configuration: Configuration) -> None: for repository_id in Explorer.repositories_extract(configuration): paths = replace(configuration.repository_paths, repository_id=repository_id) - pacman = Pacman(repository_id, configuration, refresh_database=PacmanSynchronization.Disabled) # create archive directory if required if not paths.archive.is_dir(): with paths.preserve_owner(): paths.archive.mkdir(mode=0o755, parents=True) - move_packages(paths, pacman) + move_packages(paths) -def move_packages(repository_paths: RepositoryPaths, pacman: Pacman) -> None: +def move_packages(repository_paths: RepositoryPaths) -> None: """ move packages from repository to archive and create symbolic links Args: repository_paths(RepositoryPaths): repository paths instance - pacman(Pacman): alpm wrapper instance """ for archive in filter(package_like, repository_paths.repository.iterdir()): if not archive.is_file(follow_symlinks=False): continue # skip symbolic links if any - package = Package.from_archive(archive, pacman) + package = Package.from_archive(archive) artifacts = [archive] # check if there are signatures for this package and append it here too if (signature := GPG.signature(archive)).exists(): diff --git a/src/ahriman/core/repository/executor.py b/src/ahriman/core/repository/executor.py index 4e03f000..ca77709a 100644 --- a/src/ahriman/core/repository/executor.py +++ b/src/ahriman/core/repository/executor.py @@ -57,7 +57,7 @@ class Executor(PackageInfo, Cleaner): for path in filter(package_like, archive.iterdir()): # check if package version is the same - built = Package.from_archive(path, self.pacman) + built = Package.from_archive(path) if built.version != package.version: continue @@ -117,7 +117,7 @@ class Executor(PackageInfo, Cleaner): else: built = task.build(path, PACKAGER=packager) - package.with_packages(built, self.pacman) + package.with_packages(built) for src in built: dst = self.paths.packages / src.name atomic_move(src, dst) diff --git a/src/ahriman/core/repository/package_info.py b/src/ahriman/core/repository/package_info.py index cce63285..80652ceb 100644 --- a/src/ahriman/core/repository/package_info.py +++ b/src/ahriman/core/repository/package_info.py @@ -24,19 +24,31 @@ from functools import cmp_to_key from pathlib import Path from tempfile import TemporaryDirectory +from ahriman.core.alpm.pacman import Pacman from ahriman.core.build_tools.package_version import PackageVersion from ahriman.core.build_tools.sources import Sources -from ahriman.core.repository.repository_properties import RepositoryProperties +from ahriman.core.configuration import Configuration +from ahriman.core.log import LazyLogging +from ahriman.core.status import Client from ahriman.core.utils import package_like from ahriman.models.changes import Changes from ahriman.models.package import Package -class PackageInfo(RepositoryProperties): +class PackageInfo(LazyLogging): """ handler for the package information + + Attributes: + configuration(Configuration): configuration instance + pacman(Pacman): alpm wrapper instance + reporter(Client): build status reporter instance """ + configuration: Configuration + pacman: Pacman + reporter: Client + def full_depends(self, package: Package, packages: Iterable[Package]) -> list[str]: """ generate full dependencies list including transitive dependencies @@ -87,7 +99,7 @@ class PackageInfo(RepositoryProperties): # we are iterating over bases, not single packages for full_path in packages: try: - local = Package.from_archive(full_path, self.pacman) + local = Package.from_archive(full_path) if (source := sources.get(local.base)) is not None: # update source with remote local.remote = source @@ -115,10 +127,12 @@ class PackageInfo(RepositoryProperties): Returns: list[Package]: list of packages belonging to this base, sorted by version by ascension """ + paths = self.configuration.repository_paths + packages: dict[tuple[str, str], Package] = {} # we can't use here load_archives, because it ignores versions - for full_path in filter(package_like, self.paths.archive_for(package_base).iterdir()): - local = Package.from_archive(full_path, self.pacman) + for full_path in filter(package_like, paths.archive_for(package_base).iterdir()): + local = Package.from_archive(full_path) packages.setdefault((local.base, local.version), local).packages.update(local.packages) comparator: Callable[[Package, Package], int] = lambda left, right: left.vercmp(right.version) @@ -138,7 +152,7 @@ class PackageInfo(RepositoryProperties): with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name: dir_path = Path(dir_name) patches = self.reporter.package_patches_get(package.base, None) - current_commit_sha = Sources.load(dir_path, package, patches, self.paths) + current_commit_sha = Sources.load(dir_path, package, patches, self.configuration.repository_paths) if current_commit_sha != last_commit_sha: return Sources.changes(dir_path, last_commit_sha) @@ -154,7 +168,7 @@ class PackageInfo(RepositoryProperties): Returns: list[Package]: list of packages properties """ - packages = self.load_archives(filter(package_like, self.paths.repository.iterdir())) + packages = self.load_archives(filter(package_like, self.configuration.repository_paths.repository.iterdir())) if filter_packages: packages = [package for package in packages if package.base in filter_packages] @@ -167,7 +181,7 @@ class PackageInfo(RepositoryProperties): Returns: list[Path]: list of filenames from the directory """ - return list(filter(package_like, self.paths.packages.iterdir())) + return list(filter(package_like, self.configuration.repository_paths.packages.iterdir())) def packages_depend_on(self, packages: list[Package], depends_on: Iterable[str] | None) -> list[Package]: """ diff --git a/src/ahriman/models/package.py b/src/ahriman/models/package.py index c9fb5e4c..b2157e99 100644 --- a/src/ahriman/models/package.py +++ b/src/ahriman/models/package.py @@ -26,6 +26,7 @@ from pyalpm import vercmp # type: ignore[import-not-found] from typing import Any, Self from ahriman.core.alpm.pacman import Pacman +from ahriman.core.alpm.pacman_handle import PacmanHandle from ahriman.core.alpm.remote import AUR, Official, OfficialSyncdb from ahriman.core.log import LazyLogging from ahriman.core.utils import dataclass_view, full_version, list_flatmap, parse_version, srcinfo_property_list @@ -186,18 +187,17 @@ class Package(LazyLogging): return sorted(packages) @classmethod - def from_archive(cls, path: Path, pacman: Pacman) -> Self: + def from_archive(cls, path: Path) -> Self: """ construct package properties from package archive Args: path(Path): path to package archive - pacman(Pacman): alpm wrapper instance Returns: Self: package properties """ - package = pacman.handle.load_pkg(str(path)) + package = PacmanHandle.ephemeral().package_load(path) description = PackageDescription.from_package(package, path) return cls( base=package.base or package.name, @@ -400,17 +400,16 @@ class Package(LazyLogging): """ return dataclass_view(self) - def with_packages(self, packages: Iterable[Path], pacman: Pacman) -> None: + def with_packages(self, packages: Iterable[Path]) -> None: """ replace packages descriptions with ones from archives Args: packages(Iterable[Path]): paths to package archives - pacman(Pacman): alpm wrapper instance """ self.packages = {} # reset state for package in packages: - archive = self.from_archive(package, pacman) + archive = self.from_archive(package) if archive.base != self.base: continue diff --git a/tests/ahriman/core/alpm/test_pacman_handle.py b/tests/ahriman/core/alpm/test_pacman_handle.py new file mode 100644 index 00000000..f1c234e0 --- /dev/null +++ b/tests/ahriman/core/alpm/test_pacman_handle.py @@ -0,0 +1,37 @@ +import pytest + +from pathlib import Path +from unittest.mock import MagicMock + +from ahriman.core.alpm.pacman_handle import PacmanHandle + + +def test_package_load() -> None: + """ + must load package from archive path + """ + local = Path("local") + instance = PacmanHandle.ephemeral() + handle_mock = instance.handle = MagicMock() + + instance.package_load(local) + handle_mock.load_pkg.assert_called_once_with(str(local)) + + PacmanHandle._ephemeral = None + + +def test_getattr() -> None: + """ + must proxy attribute access to underlying handle + """ + instance = PacmanHandle.ephemeral() + assert instance.dbpath + + +def test_getattr_not_found() -> None: + """ + must raise AttributeError for missing handle attributes + """ + instance = PacmanHandle.ephemeral() + with pytest.raises(AttributeError): + assert instance.random_attribute diff --git a/tests/ahriman/core/database/migrations/test_m005_make_opt_depends.py b/tests/ahriman/core/database/migrations/test_m005_make_opt_depends.py index 83335a17..26deca7b 100644 --- a/tests/ahriman/core/database/migrations/test_m005_make_opt_depends.py +++ b/tests/ahriman/core/database/migrations/test_m005_make_opt_depends.py @@ -34,8 +34,7 @@ def test_migrate_package_depends(connection: Connection, configuration: Configur package_mock = mocker.patch("ahriman.models.package.Package.from_archive", return_value=package_ahriman) migrate_package_depends(connection, configuration) - package_mock.assert_called_once_with( - package_ahriman.packages[package_ahriman.base].filepath, pytest.helpers.anyvar(int)) + package_mock.assert_called_once_with(package_ahriman.packages[package_ahriman.base].filepath) connection.executemany.assert_called_once_with(pytest.helpers.anyvar(str, strict=True), [{ "make_depends": package_ahriman.packages[package_ahriman.base].make_depends, "opt_depends": package_ahriman.packages[package_ahriman.base].opt_depends, diff --git a/tests/ahriman/core/database/migrations/test_m007_check_depends.py b/tests/ahriman/core/database/migrations/test_m007_check_depends.py index 2f537af6..85232e98 100644 --- a/tests/ahriman/core/database/migrations/test_m007_check_depends.py +++ b/tests/ahriman/core/database/migrations/test_m007_check_depends.py @@ -34,8 +34,7 @@ def test_migrate_package_depends(connection: Connection, configuration: Configur package_mock = mocker.patch("ahriman.models.package.Package.from_archive", return_value=package_ahriman) migrate_package_check_depends(connection, configuration) - package_mock.assert_called_once_with( - package_ahriman.packages[package_ahriman.base].filepath, pytest.helpers.anyvar(int)) + package_mock.assert_called_once_with(package_ahriman.packages[package_ahriman.base].filepath) connection.executemany.assert_called_once_with(pytest.helpers.anyvar(str, strict=True), [{ "check_depends": package_ahriman.packages[package_ahriman.base].check_depends, "package": package_ahriman.base, diff --git a/tests/ahriman/core/database/migrations/test_m008_packagers.py b/tests/ahriman/core/database/migrations/test_m008_packagers.py index f3861fcc..17e51e65 100644 --- a/tests/ahriman/core/database/migrations/test_m008_packagers.py +++ b/tests/ahriman/core/database/migrations/test_m008_packagers.py @@ -34,8 +34,7 @@ def test_migrate_package_base_packager(connection: Connection, configuration: Co package_mock = mocker.patch("ahriman.models.package.Package.from_archive", return_value=package_ahriman) migrate_package_base_packager(connection, configuration) - package_mock.assert_called_once_with( - package_ahriman.packages[package_ahriman.base].filepath, pytest.helpers.anyvar(int)) + package_mock.assert_called_once_with(package_ahriman.packages[package_ahriman.base].filepath) connection.executemany.assert_called_once_with(pytest.helpers.anyvar(str, strict=True), [{ "package_base": package_ahriman.base, "packager": package_ahriman.packager, diff --git a/tests/ahriman/core/database/migrations/test_m016_archive.py b/tests/ahriman/core/database/migrations/test_m016_archive.py index 962e4c7a..1d7ac8b6 100644 --- a/tests/ahriman/core/database/migrations/test_m016_archive.py +++ b/tests/ahriman/core/database/migrations/test_m016_archive.py @@ -7,7 +7,6 @@ from sqlite3 import Connection from typing import Any from unittest.mock import call as MockCall -from ahriman.core.alpm.pacman import Pacman from ahriman.core.configuration import Configuration from ahriman.core.database.migrations.m016_archive import migrate_data, move_packages from ahriman.models.package import Package @@ -28,12 +27,12 @@ def test_migrate_data(connection: Connection, configuration: Configuration, mock migrate_data(connection, configuration) migration_mock.assert_has_calls([ - MockCall(replace(configuration.repository_paths, repository_id=repository), pytest.helpers.anyvar(int)) + MockCall(replace(configuration.repository_paths, repository_id=repository)) for repository in repositories ]) -def test_move_packages(repository_paths: RepositoryPaths, pacman: Pacman, package_ahriman: Package, +def test_move_packages(repository_paths: RepositoryPaths, package_ahriman: Package, mocker: MockerFixture) -> None: """ must move packages to the archive directory @@ -57,9 +56,9 @@ def test_move_packages(repository_paths: RepositoryPaths, pacman: Pacman, packag move_mock = mocker.patch("ahriman.core.database.migrations.m016_archive.atomic_move") symlink_mock = mocker.patch("pathlib.Path.symlink_to") - move_packages(repository_paths, pacman) + move_packages(repository_paths) archive_mock.assert_has_calls([ - MockCall(repository_paths.repository / filename, pacman) + MockCall(repository_paths.repository / filename) for filename in ("file.pkg.tar.xz", "file2.pkg.tar.xz") ]) move_mock.assert_has_calls([ diff --git a/tests/ahriman/core/repository/conftest.py b/tests/ahriman/core/repository/conftest.py index d15b7c32..439cf473 100644 --- a/tests/ahriman/core/repository/conftest.py +++ b/tests/ahriman/core/repository/conftest.py @@ -6,7 +6,6 @@ from ahriman.core.configuration import Configuration from ahriman.core.database import SQLite from ahriman.core.repository.cleaner import Cleaner from ahriman.core.repository.executor import Executor -from ahriman.core.repository.package_info import PackageInfo from ahriman.core.repository.update_handler import UpdateHandler from ahriman.models.pacman_synchronization import PacmanSynchronization @@ -50,23 +49,6 @@ def executor(configuration: Configuration, database: SQLite, mocker: MockerFixtu refresh_pacman_database=PacmanSynchronization.Disabled) -@pytest.fixture -def package_info(configuration: Configuration, database: SQLite) -> PackageInfo: - """ - fixture for package info - - Args: - configuration(Configuration): configuration fixture - database(SQLite): database fixture - - Returns: - PackageInfo: package info test instance - """ - _, repository_id = configuration.check_loaded() - return PackageInfo(repository_id, configuration, database, report=False, - refresh_pacman_database=PacmanSynchronization.Disabled) - - @pytest.fixture def update_handler(configuration: Configuration, database: SQLite, mocker: MockerFixture) -> UpdateHandler: """ diff --git a/tests/ahriman/core/repository/test_executor.py b/tests/ahriman/core/repository/test_executor.py index 17e2c3b8..3e4caafa 100644 --- a/tests/ahriman/core/repository/test_executor.py +++ b/tests/ahriman/core/repository/test_executor.py @@ -118,7 +118,7 @@ def test_package_build(executor: Executor, package_ahriman: Package, mocker: Moc init_mock.assert_called_once_with(pytest.helpers.anyvar(int), pytest.helpers.anyvar(int), None) package_mock.assert_called_once_with(Path("local"), executor.architecture, None) lookup_mock.assert_called_once_with(package_ahriman) - with_packages_mock.assert_called_once_with([Path(package_ahriman.base)], executor.pacman) + with_packages_mock.assert_called_once_with([Path(package_ahriman.base)]) rename_mock.assert_called_once_with(Path(package_ahriman.base), executor.paths.packages / package_ahriman.base) diff --git a/tests/ahriman/core/repository/test_package_info.py b/tests/ahriman/core/repository/test_package_info.py index d64637ad..cb97e55d 100644 --- a/tests/ahriman/core/repository/test_package_info.py +++ b/tests/ahriman/core/repository/test_package_info.py @@ -4,12 +4,12 @@ from pathlib import Path from pytest_mock import MockerFixture from unittest.mock import MagicMock -from ahriman.core.repository.package_info import PackageInfo +from ahriman.core.repository import Repository from ahriman.models.changes import Changes from ahriman.models.package import Package -def test_full_depends(package_info: PackageInfo, package_ahriman: Package, package_python_schedule: Package, +def test_full_depends(repository: Repository, package_ahriman: Package, package_python_schedule: Package, pyalpm_package_ahriman: MagicMock) -> None: """ must extract all dependencies from the package @@ -18,18 +18,18 @@ def test_full_depends(package_info: PackageInfo, package_ahriman: Package, packa database_mock = MagicMock() database_mock.pkgcache = [pyalpm_package_ahriman] - package_info.pacman = MagicMock() - package_info.pacman.handle.get_syncdbs.return_value = [database_mock] + repository.pacman = MagicMock() + repository.pacman.handle.get_syncdbs.return_value = [database_mock] - assert package_info.full_depends(package_ahriman, [package_python_schedule]) == package_ahriman.depends + assert repository.full_depends(package_ahriman, [package_python_schedule]) == package_ahriman.depends package_python_schedule.packages[package_python_schedule.base].depends = [package_ahriman.base] expected = sorted(set(package_python_schedule.depends + package_ahriman.depends)) - assert package_info.full_depends(package_python_schedule, [package_python_schedule]) == expected + assert repository.full_depends(package_python_schedule, [package_python_schedule]) == expected -def test_load_archives(package_ahriman: Package, package_python_schedule: Package, - package_info: PackageInfo, mocker: MockerFixture) -> None: +def test_load_archives(repository: Repository, package_ahriman: Package, package_python_schedule: Package, + mocker: MockerFixture) -> None: """ must return all packages grouped by package base """ @@ -45,7 +45,7 @@ def test_load_archives(package_ahriman: Package, package_python_schedule: Packag (package_ahriman, None), ]) - packages = package_info.load_archives([Path("a.pkg.tar.xz"), Path("b.pkg.tar.xz"), Path("c.pkg.tar.xz")]) + packages = repository.load_archives([Path("a.pkg.tar.xz"), Path("b.pkg.tar.xz"), Path("c.pkg.tar.xz")]) assert len(packages) == 2 assert {package.base for package in packages} == {package_ahriman.base, package_python_schedule.base} @@ -56,22 +56,22 @@ def test_load_archives(package_ahriman: Package, package_python_schedule: Packag assert set(archives) == expected -def test_load_archives_failed(package_info: PackageInfo, mocker: MockerFixture) -> None: +def test_load_archives_failed(repository: Repository, mocker: MockerFixture) -> None: """ must skip packages which cannot be loaded """ mocker.patch("ahriman.models.package.Package.from_archive", side_effect=Exception) - assert not package_info.load_archives([Path("a.pkg.tar.xz")]) + assert not repository.load_archives([Path("a.pkg.tar.xz")]) -def test_load_archives_not_package(package_info: PackageInfo) -> None: +def test_load_archives_not_package(repository: Repository) -> None: """ must skip not packages from iteration """ - assert not package_info.load_archives([Path("a.tar.xz")]) + assert not repository.load_archives([Path("a.tar.xz")]) -def test_load_archives_different_version(package_info: PackageInfo, package_python_schedule: Package, +def test_load_archives_different_version(repository: Repository, package_python_schedule: Package, mocker: MockerFixture) -> None: """ must load packages with different versions choosing maximal @@ -86,12 +86,12 @@ def test_load_archives_different_version(package_info: PackageInfo, package_pyth single_packages[0].version = "0.0.1-1" mocker.patch("ahriman.models.package.Package.from_archive", side_effect=single_packages) - packages = package_info.load_archives([Path("a.pkg.tar.xz"), Path("b.pkg.tar.xz")]) + packages = repository.load_archives([Path("a.pkg.tar.xz"), Path("b.pkg.tar.xz")]) assert len(packages) == 1 assert packages[0].version == package_python_schedule.version -def test_package_archives(package_info: PackageInfo, package_ahriman: Package, mocker: MockerFixture) -> None: +def test_package_archives(repository: Repository, package_ahriman: Package, mocker: MockerFixture) -> None: """ must load package archives sorted by version """ @@ -110,12 +110,12 @@ def test_package_archives(package_info: PackageInfo, package_ahriman: Package, m mocker.patch("pathlib.Path.iterdir", return_value=[Path(str(i)) for i in range(5)]) mocker.patch("ahriman.models.package.Package.from_archive", side_effect=package) - result = package_info.package_archives(package_ahriman.base) + result = repository.package_archives(package_ahriman.base) assert len(result) == 5 assert [p.version for p in result] == [str(i) for i in range(5)] -def test_package_changes(package_info: PackageInfo, package_ahriman: Package, mocker: MockerFixture) -> None: +def test_package_changes(repository: Repository, package_ahriman: Package, mocker: MockerFixture) -> None: """ must load package changes """ @@ -123,23 +123,24 @@ def test_package_changes(package_info: PackageInfo, package_ahriman: Package, mo load_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.load", return_value="sha2") changes_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.changes", return_value=changes) - assert package_info.package_changes(package_ahriman, changes.last_commit_sha) == changes - load_mock.assert_called_once_with(pytest.helpers.anyvar(int), package_ahriman, [], package_info.paths) + assert repository.package_changes(package_ahriman, changes.last_commit_sha) == changes + load_mock.assert_called_once_with( + pytest.helpers.anyvar(int), package_ahriman, [], repository.configuration.repository_paths) changes_mock.assert_called_once_with(pytest.helpers.anyvar(int), changes.last_commit_sha) -def test_package_changes_skip(package_info: PackageInfo, package_ahriman: Package, mocker: MockerFixture) -> None: +def test_package_changes_skip(repository: Repository, package_ahriman: Package, mocker: MockerFixture) -> None: """ must skip loading package changes if no new commits """ mocker.patch("ahriman.core.build_tools.sources.Sources.load", return_value="sha") changes_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.changes") - assert package_info.package_changes(package_ahriman, "sha") is None + assert repository.package_changes(package_ahriman, "sha") is None changes_mock.assert_not_called() -def test_packages(package_info: PackageInfo, package_ahriman: Package, package_python_schedule: Package, +def test_packages(repository: Repository, package_ahriman: Package, package_python_schedule: Package, mocker: MockerFixture) -> None: """ must return repository packages @@ -147,12 +148,12 @@ def test_packages(package_info: PackageInfo, package_ahriman: Package, package_p mocker.patch("pathlib.Path.iterdir") load_mock = mocker.patch("ahriman.core.repository.package_info.PackageInfo.load_archives", return_value=[package_ahriman, package_python_schedule]) - assert package_info.packages() == [package_ahriman, package_python_schedule] + assert repository.packages() == [package_ahriman, package_python_schedule] # it uses filter object, so we cannot verify argument list =/ load_mock.assert_called_once_with(pytest.helpers.anyvar(int)) -def test_packages_filter(package_info: PackageInfo, package_ahriman: Package, package_python_schedule: Package, +def test_packages_filter(repository: Repository, package_ahriman: Package, package_python_schedule: Package, mocker: MockerFixture) -> None: """ must filter result by bases @@ -160,33 +161,33 @@ def test_packages_filter(package_info: PackageInfo, package_ahriman: Package, pa mocker.patch("pathlib.Path.iterdir") mocker.patch("ahriman.core.repository.package_info.PackageInfo.load_archives", return_value=[package_ahriman, package_python_schedule]) - assert package_info.packages([package_ahriman.base]) == [package_ahriman] + assert repository.packages([package_ahriman.base]) == [package_ahriman] -def test_packages_built(package_info: PackageInfo, mocker: MockerFixture) -> None: +def test_packages_built(repository: Repository, mocker: MockerFixture) -> None: """ must return build packages """ mocker.patch("pathlib.Path.iterdir", return_value=[Path("a.tar.xz"), Path("b.pkg.tar.xz")]) - assert package_info.packages_built() == [Path("b.pkg.tar.xz")] + assert repository.packages_built() == [Path("b.pkg.tar.xz")] -def test_packages_depend_on(package_info: PackageInfo, package_ahriman: Package, package_python_schedule: Package, +def test_packages_depend_on(repository: Repository, package_ahriman: Package, package_python_schedule: Package, mocker: MockerFixture) -> None: """ must filter packages by depends list """ mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman, package_python_schedule]) - assert package_info.packages_depend_on([package_ahriman], {"python-srcinfo"}) == [package_ahriman] + assert repository.packages_depend_on([package_ahriman], {"python-srcinfo"}) == [package_ahriman] -def test_packages_depend_on_empty(package_info: PackageInfo, package_ahriman: Package, package_python_schedule: Package, +def test_packages_depend_on_empty(repository: Repository, package_ahriman: Package, package_python_schedule: Package, mocker: MockerFixture) -> None: """ must return all packages in case if no filter is provided """ mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman, package_python_schedule]) - assert package_info.packages_depend_on([package_ahriman, package_python_schedule], None) == \ + assert repository.packages_depend_on([package_ahriman, package_python_schedule], None) == \ [package_ahriman, package_python_schedule] diff --git a/tests/ahriman/models/conftest.py b/tests/ahriman/models/conftest.py index 165e43c2..886927a2 100644 --- a/tests/ahriman/models/conftest.py +++ b/tests/ahriman/models/conftest.py @@ -89,22 +89,6 @@ def pkgbuild_ahriman(resource_path_root: Path) -> Pkgbuild: return Pkgbuild.from_file(pkgbuild) -@pytest.fixture -def pyalpm_handle(pyalpm_package_ahriman: MagicMock) -> MagicMock: - """ - mock object for pyalpm - - Args: - pyalpm_package_ahriman(MagicMock): mock object for pyalpm package - - Returns: - MagicMock: pyalpm mock - """ - mock = MagicMock() - mock.handle.load_pkg.return_value = pyalpm_package_ahriman - return mock - - @pytest.fixture def pyalpm_package_description_ahriman(package_description_ahriman: PackageDescription) -> MagicMock: """ diff --git a/tests/ahriman/models/test_package.py b/tests/ahriman/models/test_package.py index 1843dbf4..c7629e47 100644 --- a/tests/ahriman/models/test_package.py +++ b/tests/ahriman/models/test_package.py @@ -148,13 +148,14 @@ def test_packages_full(package_ahriman: Package) -> None: assert package_ahriman.packages_full == [package_ahriman.base, f"{package_ahriman.base}-git"] -def test_from_archive(package_ahriman: Package, pyalpm_handle: MagicMock, mocker: MockerFixture) -> None: +def test_from_archive(package_ahriman: Package, pyalpm_package_ahriman: MagicMock, mocker: MockerFixture) -> None: """ must construct package from alpm library """ + mocker.patch("ahriman.core.alpm.pacman_handle.PacmanHandle.package_load", return_value=pyalpm_package_ahriman) mocker.patch("ahriman.models.package_description.PackageDescription.from_package", return_value=package_ahriman.packages[package_ahriman.base]) - generated = Package.from_archive(Path("path"), pyalpm_handle) + generated = Package.from_archive(Path("path")) generated.remote = package_ahriman.remote assert generated == package_ahriman @@ -165,13 +166,12 @@ def test_from_archive_empty_base(package_ahriman: Package, pyalpm_package_ahrima """ must construct package with empty base from alpm library """ - pyalpm_handle = MagicMock() type(pyalpm_package_ahriman).base = PropertyMock(return_value=None) - pyalpm_handle.handle.load_pkg.return_value = pyalpm_package_ahriman + mocker.patch("ahriman.core.alpm.pacman_handle.PacmanHandle.package_load", return_value=pyalpm_package_ahriman) mocker.patch("ahriman.models.package_description.PackageDescription.from_package", return_value=package_ahriman.packages[package_ahriman.base]) - generated = Package.from_archive(Path("path"), pyalpm_handle) + generated = Package.from_archive(Path("path")) generated.remote = package_ahriman.remote assert generated == package_ahriman @@ -362,7 +362,7 @@ def test_vercmp(package_ahriman: Package, mocker: MockerFixture) -> None: vercmp_mock.assert_called_once_with(package_ahriman.version, "version") -def test_with_packages(package_ahriman: Package, package_python_schedule: Package, pacman: Pacman, +def test_with_packages(package_ahriman: Package, package_python_schedule: Package, mocker: MockerFixture) -> None: """ must correctly replace packages descriptions @@ -375,8 +375,8 @@ def test_with_packages(package_ahriman: Package, package_python_schedule: Packag result = copy.deepcopy(package_ahriman) package_ahriman.packages[package_ahriman.base].architecture = "i686" - result.with_packages(paths, pacman) + result.with_packages(paths) from_archive_mock.assert_has_calls([ - MockCall(path, pacman) for path in paths + MockCall(path) for path in paths ]) assert result.packages[result.base] == package_ahriman.packages[package_ahriman.base]