Compare commits

..

No commits in common. "e58ccdc8ad3fa5ea4c93b8fa058e4c250d949b96" and "43c553a3dbd1cc4bf817075867b84eb600dbd302" have entirely different histories.

28 changed files with 66 additions and 428 deletions

View File

@ -13,14 +13,11 @@ ENV AHRIMAN_REPOSITORY_ROOT="/var/lib/ahriman/ahriman"
ENV AHRIMAN_USER="ahriman"
# install environment
## update pacman.conf with multilib
RUN echo "[multilib]" >> "/etc/pacman.conf" && \
echo "Include = /etc/pacman.d/mirrorlist" >> "/etc/pacman.conf"
## install minimal required packages
RUN pacman --noconfirm -Syu binutils fakeroot git make sudo
## create build user
RUN useradd -m -d "/home/build" -s "/usr/bin/nologin" build && \
echo "build ALL=(ALL) NOPASSWD: ALL" > "/etc/sudoers.d/build"
RUN useradd -m -d /home/build -s /usr/bin/nologin build && \
echo "build ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/build
COPY "docker/install-aur-package.sh" "/usr/local/bin/install-aur-package"
## install package dependencies
## darcs is not installed by reasons, because it requires a lot haskell packages which dramatically increase image size

View File

@ -26,11 +26,9 @@ Base configuration settings.
libalpm and AUR related configuration.
* ``database`` - path to pacman system database cache, string, required.
* ``mirror`` - package database mirror used by pacman for syncronization, string, required. This option supports standard pacman substitutions with ``$arch`` and ``$repo``. Note that the mentioned mirror should contain all repositories which are set by ``alpm.repositories`` option.
* ``database`` - path to pacman local database cache, string, required.
* ``repositories`` - list of pacman repositories, space separated list of strings, required.
* ``root`` - root for alpm library, string, required.
* ``use_ahriman_cache`` - use local pacman package cache instead of system one, boolean, required. With this option enabled you might want to refresh database periodically (available as additional flag for some subcommands).
``auth`` group
--------------

View File

@ -2,6 +2,6 @@
Description=ArcH linux ReposItory MANager (%I architecture)
[Service]
ExecStart=/usr/bin/ahriman --architecture %i repo-update --refresh
ExecStart=/usr/bin/ahriman --architecture %i update
User=ahriman
Group=ahriman

View File

@ -5,10 +5,8 @@ database = /var/lib/ahriman/ahriman.db
[alpm]
database = /var/lib/pacman
mirror = https://geo.mirror.pkgbuild.com/$repo/os/$arch
repositories = core extra community multilib
root = /
use_ahriman_cache = yes
[auth]
target = disabled

View File

@ -160,9 +160,6 @@ def _set_daemon_parser(root: SubParserAction) -> argparse.ArgumentParser:
parser.add_argument("--no-local", help="do not check local packages for updates", action="store_true")
parser.add_argument("--no-manual", help="do not include manual updates", action="store_true")
parser.add_argument("--no-vcs", help="do not check VCS packages", action="store_true")
parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
"-yy to force refresh even if up to date",
action="count", default=0)
parser.set_defaults(handler=handlers.Daemon, dry_run=False, exit_code=False, package=[])
return parser
@ -254,9 +251,6 @@ def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
parser.add_argument("package", help="package source (base name, path to local files, remote URL)", nargs="+")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.add_argument("-n", "--now", help="run update function after", action="store_true")
parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
"-yy to force refresh even if up to date",
action="count", default=0)
parser.add_argument("-s", "--source", help="explicitly specify the package source for this command",
type=PackageSource, choices=enum_values(PackageSource), default=PackageSource.Auto)
parser.add_argument("--without-dependencies", help="do not add dependencies", action="store_true")
@ -473,9 +467,6 @@ def _set_repo_check_parser(root: SubParserAction) -> argparse.ArgumentParser:
parser.add_argument("package", help="filter check by package base", nargs="*")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.add_argument("--no-vcs", help="do not check VCS packages", action="store_true")
parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
"-yy to force refresh even if up to date",
action="count", default=0)
parser.set_defaults(handler=handlers.Update, dry_run=True, no_aur=False, no_local=False, no_manual=True)
return parser
@ -726,9 +717,6 @@ def _set_repo_update_parser(root: SubParserAction) -> argparse.ArgumentParser:
parser.add_argument("--no-local", help="do not check local packages for updates", action="store_true")
parser.add_argument("--no-manual", help="do not include manual updates", action="store_true")
parser.add_argument("--no-vcs", help="do not check VCS packages", action="store_true")
parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
"-yy to force refresh even if up to date",
action="count", default=0)
parser.set_defaults(handler=handlers.Update)
return parser

View File

@ -62,7 +62,7 @@ class Application(ApplicationPackages, ApplicationRepository):
for package, properties in base.packages.items():
known_packages.add(package)
known_packages.update(properties.provides)
known_packages.update(self.repository.pacman.packages())
known_packages.update(self.repository.pacman.all_packages())
return known_packages
def on_result(self, result: Result) -> None:

View File

@ -34,8 +34,7 @@ class ApplicationProperties(LazyLogging):
repository(Repository): repository instance
"""
def __init__(self, architecture: str, configuration: Configuration,
no_report: bool, unsafe: bool, refresh_pacman_database: int = 0) -> None:
def __init__(self, architecture: str, configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
default constructor
@ -44,10 +43,8 @@ class ApplicationProperties(LazyLogging):
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
refresh_pacman_database(int): pacman database syncronization level, ``0`` is disabled
"""
self.configuration = configuration
self.architecture = architecture
self.database = SQLite.load(configuration)
self.repository = Repository(architecture, configuration, self.database,
no_report, unsafe, refresh_pacman_database)
self.repository = Repository(architecture, configuration, self.database, no_report, unsafe)

View File

@ -44,7 +44,7 @@ class Add(Handler):
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
application = Application(architecture, configuration, no_report, unsafe, args.refresh)
application = Application(architecture, configuration, no_report, unsafe)
application.on_start()
application.add(args.package, args.source, args.without_dependencies)
if not args.now:

View File

@ -20,7 +20,6 @@
import argparse
from pathlib import Path
from pwd import getpwuid
from typing import Type
from ahriman.application.application import Application
@ -174,9 +173,7 @@ class Setup(Handler):
packager(str): packager identifier (e.g. name, email)
paths(RepositoryPaths): repository paths instance
"""
uid, _ = paths.root_owner
home_dir = Path(getpwuid(uid).pw_dir)
(home_dir / ".makepkg.conf").write_text(f"PACKAGER='{packager}'\n", encoding="utf8")
(paths.root / ".makepkg.conf").write_text(f"PACKAGER='{packager}'\n", encoding="utf8")
@staticmethod
def configuration_create_sudo(paths: RepositoryPaths, prefix: str, architecture: str) -> None:

View File

@ -44,7 +44,7 @@ class Update(Handler):
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
application = Application(architecture, configuration, no_report, unsafe, args.refresh)
application = Application(architecture, configuration, no_report, unsafe)
application.on_start()
packages = application.updates(args.package, args.no_aur, args.no_local, args.no_manual, args.no_vcs,
Update.log_fn(application, args.dry_run))

View File

@ -17,18 +17,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import shutil
from pathlib import Path
from pyalpm import DB, Handle, Package, SIG_PACKAGE, error as PyalpmError # type: ignore
from pyalpm import Handle, Package, SIG_PACKAGE # type: ignore
from typing import Generator, Set
from ahriman.core.configuration import Configuration
from ahriman.core.lazy_logging import LazyLogging
from ahriman.models.repository_paths import RepositoryPaths
class Pacman(LazyLogging):
class Pacman:
"""
alpm wrapper
@ -36,96 +31,35 @@ class Pacman(LazyLogging):
handle(Handle): pyalpm root ``Handle``
"""
def __init__(self, architecture: str, configuration: Configuration, *, refresh_database: int) -> None:
def __init__(self, configuration: Configuration) -> None:
"""
default constructor
Args:
architecture(str): repository architecture
configuration(Configuration): configuration instance
refresh_database(int): synchronize local cache to remote. If set to ``0``, no syncronization will be
enabled, if set to ``1`` - normal syncronization, if set to ``2`` - force syncronization
"""
root = configuration.getpath("alpm", "root")
root = configuration.get("alpm", "root")
pacman_root = configuration.getpath("alpm", "database")
use_ahriman_cache = configuration.getboolean("alpm", "use_ahriman_cache")
mirror = configuration.get("alpm", "mirror")
paths = configuration.repository_paths
database_path = paths.pacman if use_ahriman_cache else pacman_root
self.handle = Handle(str(root), str(database_path))
self.handle = Handle(root, str(pacman_root))
for repository in configuration.getlist("alpm", "repositories"):
database = self.database_init(repository, mirror, architecture)
self.database_copy(database, pacman_root, paths, use_ahriman_cache=use_ahriman_cache)
self.handle.register_syncdb(repository, SIG_PACKAGE)
if use_ahriman_cache and refresh_database:
self.database_sync(refresh_database > 1)
def database_copy(self, database: DB, pacman_root: Path, paths: RepositoryPaths, *,
use_ahriman_cache: bool) -> None:
def all_packages(self) -> Set[str]:
"""
copy database from the operating system root to the ahriman local home
Args:
database(DB): pacman database instance to be copied
pacman_root(Path): operating system pacman's root
paths(RepositoryPaths): repository paths instance
use_ahriman_cache(bool): use local ahriman cache instead of system one
"""
def repository_database(root: Path) -> Path:
return root / "sync" / f"{database.name}.db"
if not use_ahriman_cache:
return
# copy root database if no local copy found
pacman_db_path = Path(self.handle.dbpath)
if not pacman_db_path.is_dir():
return # root directory does not exist yet
dst = repository_database(pacman_db_path)
if dst.is_file():
return # file already exists, do not copy
src = repository_database(pacman_root)
if not src.is_file():
self.logger.warning("repository %s is set to be used, however, no working copy was found", database.name)
return # database for some reasons deos not exist
self.logger.info("copy pacman database from operating system root to ahriman's home")
shutil.copy(src, dst)
paths.chown(dst)
def database_init(self, repository: str, mirror: str, architecture: str) -> DB:
"""
create database instance from pacman handler and set its properties
Args:
repository(str): pacman repository name (e.g. core)
mirror(str): arch linux mirror url
architecture(str): repository architecture
get list of packages known for alpm
Returns:
DB: loaded pacman database instance
Set[str]: list of package names
"""
database: DB = self.handle.register_syncdb(repository, SIG_PACKAGE)
# replace variables in mirror address
database.servers = [mirror.replace("$repo", repository).replace("$arch", architecture)]
return database
def database_sync(self, force: bool) -> None:
"""
sync local database
Args:
force(bool): force database syncronization (same as ``pacman -Syy``)
"""
self.logger.info("refresh ahriman's home pacman database (force refresh %s)", force)
transaction = self.handle.init_transaction()
result: Set[str] = set()
for database in self.handle.get_syncdbs():
try:
database.update(force)
except PyalpmError:
self.logger.exception("exception during update %s", database.name)
transaction.release()
for package in database.pkgcache:
result.add(package.name) # package itself
result.update(package.provides) # provides list for meta-packages
def package_get(self, package_name: str) -> Generator[Package, None, None]:
return result
def get(self, package_name: str) -> Generator[Package, None, None]:
"""
retrieve list of the packages from the repository by name
@ -140,18 +74,3 @@ class Pacman(LazyLogging):
if package is None:
continue
yield package
def packages(self) -> Set[str]:
"""
get list of packages known for alpm
Returns:
Set[str]: list of package names
"""
result: Set[str] = set()
for database in self.handle.get_syncdbs():
for package in database.pkgcache:
result.add(package.name) # package itself
result.update(package.provides) # provides list for meta-packages
return result

View File

@ -48,4 +48,4 @@ class OfficialSyncdb(Official):
Returns:
AURPackage: package which match the package name
"""
return next(AURPackage.from_pacman(package) for package in pacman.package_get(package_name))
return next(AURPackage.from_pacman(package) for package in pacman.get(package_name))

View File

@ -225,14 +225,14 @@ class Configuration(configparser.RawConfigParser):
# pylint and mypy are too stupid to find these methods
# pylint: disable=missing-function-docstring,multiple-statements,unused-argument
def getlist(self, *args: Any, **kwargs: Any) -> List[str]: ... # type: ignore
def getlist(self, *args: Any, **kwargs: Any) -> List[str]: ...
def getpath(self, *args: Any, **kwargs: Any) -> Path: ... # type: ignore
def getpath(self, *args: Any, **kwargs: Any) -> Path: ...
def gettype(self, section: str, architecture: str) -> Tuple[str, str]:
"""
get type variable with fallback to old logic. Despite the fact that it has same semantics as other get* methods,
but it has different argument list
get type variable with fallback to old logic
Despite the fact that it has same semantics as other get* methods, but it has different argument list
Args:
section(str): section name

View File

@ -48,7 +48,7 @@ class RepositoryProperties(LazyLogging):
"""
def __init__(self, architecture: str, configuration: Configuration, database: SQLite,
no_report: bool, unsafe: bool, refresh_pacman_database: int = 0) -> None:
no_report: bool, unsafe: bool) -> None:
"""
default constructor
@ -58,7 +58,6 @@ class RepositoryProperties(LazyLogging):
database(SQLite): database instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
refresh_pacman_database(int): pacman database syncronization level, ``0`` is disabled
"""
self.architecture = architecture
self.configuration = configuration
@ -74,7 +73,7 @@ class RepositoryProperties(LazyLogging):
self.logger.warning("root owner differs from the current user, skipping tree creation")
self.ignore_list = configuration.getlist("build", "ignore_packages", fallback=[])
self.pacman = Pacman(architecture, configuration, refresh_database=refresh_pacman_database)
self.pacman = Pacman(configuration)
self.sign = GPG(architecture, configuration)
self.repo = Repo(self.name, self.paths, self.sign.repository_sign_args)
self.reporter = Client() if no_report else Client.load(configuration)

View File

@ -67,10 +67,8 @@ class AURPackage:
>>>
>>>
>>> from ahriman.core.alpm.pacman import Pacman
>>> from ahriman.core.configuration import Configuration
>>>
>>> configuration = Configuration()
>>> pacman = Pacman("x86_64", configuration)
>>> pacman = Pacman(configuration)
>>> metadata = pacman.get("pacman")
>>> package = AURPackage.from_pacman(next(metadata)) # load package from pyalpm wrapper
"""

View File

@ -57,7 +57,7 @@ class PackageDescription:
>>> from ahriman.core.configuration import Configuration
>>>
>>> configuration = Configuration()
>>> pacman = Pacman("x86_64", configuration)
>>> pacman = Pacman(configuration)
>>> pyalpm_description = next(package for package in pacman.get("pacman"))
>>> description = PackageDescription.from_package(
>>> pyalpm_description, Path("/var/cache/pacman/pkg/pacman-6.0.1-4-x86_64.pkg.tar.zst"))

View File

@ -87,16 +87,6 @@ class RepositoryPaths:
"""
return self.root / "packages" / self.architecture
@property
def pacman(self) -> Path:
"""
get directory for pacman local package cache
Returns:
Path: full path to pacman local database cache
"""
return self.root / "pacman" / self.architecture
@property
def repository(self) -> Path:
"""
@ -204,7 +194,6 @@ class RepositoryPaths:
self.cache,
self.chroot,
self.packages,
self.pacman / "sync", # we need sync directory in order to be able to copy databases
self.repository,
):
directory.mkdir(mode=0o755, parents=True, exist_ok=True)

View File

@ -21,8 +21,8 @@ from __future__ import annotations
from dataclasses import dataclass, replace
from typing import Optional, Type
from passlib.pwd import genword as generate_password
from passlib.handlers.sha2_crypt import sha512_crypt
from passlib.pwd import genword as generate_password # type: ignore
from passlib.handlers.sha2_crypt import sha512_crypt # type: ignore
from ahriman.models.user_access import UserAccess

View File

@ -1,17 +0,0 @@
import pytest
from collections import namedtuple
_passwd = namedtuple("passwd", ["pw_dir"])
@pytest.fixture
def passwd() -> _passwd:
"""
get passwd structure for the user
Returns:
_passwd: passwd structure test instance
"""
return _passwd("home")

View File

@ -23,7 +23,6 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
args.package = []
args.exit_code = False
args.now = False
args.refresh = 0
args.source = PackageSource.Auto
args.without_dependencies = False
return args

View File

@ -3,7 +3,6 @@ import pytest
from pathlib import Path
from pytest_mock import MockerFixture
from typing import Any
from unittest import mock
from ahriman.application.handlers import Setup
@ -131,17 +130,15 @@ def test_configuration_create_devtools_no_multilib(args: argparse.Namespace, rep
def test_configuration_create_makepkg(args: argparse.Namespace, repository_paths: RepositoryPaths,
passwd: Any, mocker: MockerFixture) -> None:
mocker: MockerFixture) -> None:
"""
must create makepkg configuration
"""
args = _default_args(args)
mocker.patch("ahriman.application.handlers.setup.getpwuid", return_value=passwd)
write_text_mock = mocker.patch("pathlib.Path.write_text", autospec=True)
write_text_mock = mocker.patch("pathlib.Path.write_text")
Setup.configuration_create_makepkg(args.packager, repository_paths)
write_text_mock.assert_called_once_with(
Path("home") / ".makepkg.conf", pytest.helpers.anyvar(str, True), encoding="utf8")
write_text_mock.assert_called_once_with(pytest.helpers.anyvar(str, True), encoding="utf8")
def test_configuration_create_sudo(args: argparse.Namespace, repository_paths: RepositoryPaths,

View File

@ -28,7 +28,6 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
args.no_local = False
args.no_manual = False
args.no_vcs = False
args.refresh = 0
return args

View File

@ -75,18 +75,6 @@ def test_subparsers_daemon(parser: argparse.ArgumentParser) -> None:
assert args.package == []
def test_subparsers_daemon_option_refresh(parser: argparse.ArgumentParser) -> None:
"""
daemon command must count refresh options
"""
args = parser.parse_args(["daemon"])
assert args.refresh == 0
args = parser.parse_args(["daemon", "-y"])
assert args.refresh == 1
args = parser.parse_args(["daemon", "-yy"])
assert args.refresh == 2
def test_subparsers_daemon_option_interval(parser: argparse.ArgumentParser) -> None:
"""
daemon command must convert interval option to int instance
@ -167,18 +155,6 @@ def test_subparsers_package_add_architecture(parser: argparse.ArgumentParser) ->
assert args.architecture == ["x86_64"]
def test_subparsers_package_add_option_refresh(parser: argparse.ArgumentParser) -> None:
"""
package-add command must count refresh options
"""
args = parser.parse_args(["package-add", "ahriman"])
assert args.refresh == 0
args = parser.parse_args(["package-add", "ahriman", "-y"])
assert args.refresh == 1
args = parser.parse_args(["package-add", "ahriman", "-yy"])
assert args.refresh == 2
def test_subparsers_package_remove_architecture(parser: argparse.ArgumentParser) -> None:
"""
package-remove command must correctly parse architecture list
@ -369,18 +345,6 @@ def test_subparsers_repo_check_architecture(parser: argparse.ArgumentParser) ->
assert args.architecture == ["x86_64"]
def test_subparsers_repo_check_option_refresh(parser: argparse.ArgumentParser) -> None:
"""
repo-check command must count refresh options
"""
args = parser.parse_args(["repo-check"])
assert args.refresh == 0
args = parser.parse_args(["repo-check", "-y"])
assert args.refresh == 1
args = parser.parse_args(["repo-check", "-yy"])
assert args.refresh == 2
def test_subparsers_repo_clean(parser: argparse.ArgumentParser) -> None:
"""
repo-clean command must imply quiet and unsafe
@ -576,18 +540,6 @@ def test_subparsers_repo_update_architecture(parser: argparse.ArgumentParser) ->
assert args.architecture == ["x86_64"]
def test_subparsers_repo_update_option_refresh(parser: argparse.ArgumentParser) -> None:
"""
repo-update command must count refresh options
"""
args = parser.parse_args(["repo-update"])
assert args.refresh == 0
args = parser.parse_args(["repo-update", "-y"])
assert args.refresh == 1
args = parser.parse_args(["repo-update", "-yy"])
assert args.refresh == 2
def test_subparsers_shell(parser: argparse.ArgumentParser) -> None:
"""
shell command must imply lock and no-report

View File

@ -360,7 +360,7 @@ def pacman(configuration: Configuration) -> Pacman:
Returns:
Pacman: pacman wrapper test instance
"""
return Pacman("x86_64", configuration, refresh_database=0)
return Pacman(configuration)
@pytest.fixture

View File

@ -11,7 +11,7 @@ def test_package_info(official_syncdb: OfficialSyncdb, aur_package_akonadi: AURP
must return package info from the database
"""
mocker.patch("ahriman.models.aur_package.AURPackage.from_pacman", return_value=aur_package_akonadi)
get_mock = mocker.patch("ahriman.core.alpm.pacman.Pacman.package_get", return_value=[aur_package_akonadi])
get_mock = mocker.patch("ahriman.core.alpm.pacman.Pacman.get", return_value=[aur_package_akonadi])
package = official_syncdb.package_info(aur_package_akonadi.name, pacman=pacman)
get_mock.assert_called_once_with(aur_package_akonadi.name)

View File

@ -1,205 +1,31 @@
from pathlib import Path
from pyalpm import error as PyalpmError
from pytest_mock import MockerFixture
from tempfile import TemporaryDirectory
from unittest.mock import MagicMock
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.configuration import Configuration
from ahriman.models.repository_paths import RepositoryPaths
def test_init_with_local_cache(configuration: Configuration, mocker: MockerFixture) -> None:
"""
must sync repositories at the start if set
"""
mocker.patch("ahriman.core.alpm.pacman.Pacman.database_copy")
sync_mock = mocker.patch("ahriman.core.alpm.pacman.Pacman.database_sync")
configuration.set_option("alpm", "use_ahriman_cache", "yes")
# pyalpm.Handle is trying to reach the directory we've asked, thus we need to patch it a bit
with TemporaryDirectory(ignore_cleanup_errors=True) as pacman_root:
mocker.patch.object(RepositoryPaths, "pacman", Path(pacman_root))
# during the creation pyalpm.Handle will create also version file which we would like to remove later
Pacman("x86_64", configuration, refresh_database=1)
sync_mock.assert_called_once_with(False)
def test_init_with_local_cache_forced(configuration: Configuration, mocker: MockerFixture) -> None:
"""
must sync repositories at the start if set with force flag
"""
mocker.patch("ahriman.core.alpm.pacman.Pacman.database_copy")
sync_mock = mocker.patch("ahriman.core.alpm.pacman.Pacman.database_sync")
configuration.set_option("alpm", "use_ahriman_cache", "yes")
# pyalpm.Handle is trying to reach the directory we've asked, thus we need to patch it a bit
with TemporaryDirectory(ignore_cleanup_errors=True) as pacman_root:
mocker.patch.object(RepositoryPaths, "pacman", Path(pacman_root))
# during the creation pyalpm.Handle will create also version file which we would like to remove later
Pacman("x86_64", configuration, refresh_database=2)
sync_mock.assert_called_once_with(True)
def test_database_copy(pacman: Pacman, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
"""
must copy database from root
"""
database = next(db for db in pacman.handle.get_syncdbs() if db.name == "core")
path = Path("randomname")
dst_path = Path("/var/lib/pacman/sync/core.db")
mocker.patch("pathlib.Path.is_dir", return_value=True)
# root database exists, local database does not
mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=lambda p: True if p.is_relative_to(path) else False)
copy_mock = mocker.patch("shutil.copy")
chown_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.chown")
pacman.database_copy(database, path, repository_paths, use_ahriman_cache=True)
copy_mock.assert_called_once_with(path / "sync" / "core.db", dst_path)
chown_mock.assert_called_once_with(dst_path)
def test_database_copy_skip(pacman: Pacman, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
"""
must do not copy database from root if local cache is disabled
"""
database = next(db for db in pacman.handle.get_syncdbs() if db.name == "core")
path = Path("randomname")
mocker.patch("pathlib.Path.is_dir", return_value=True)
# root database exists, local database does not
mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=lambda p: True if p.is_relative_to(path) else False)
copy_mock = mocker.patch("shutil.copy")
pacman.database_copy(database, path, repository_paths, use_ahriman_cache=False)
copy_mock.assert_not_called()
def test_database_copy_no_directory(pacman: Pacman, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
"""
must do not copy database if local cache already exists
"""
database = next(db for db in pacman.handle.get_syncdbs() if db.name == "core")
path = Path("randomname")
mocker.patch("pathlib.Path.is_dir", return_value=False)
# root database exists, local database does not
mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=lambda p: True if p.is_relative_to(path) else False)
copy_mock = mocker.patch("shutil.copy")
pacman.database_copy(database, path, repository_paths, use_ahriman_cache=True)
copy_mock.assert_not_called()
def test_database_copy_no_root_file(pacman: Pacman, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
"""
must do not copy database if no repository file exists in filesystem
"""
database = next(db for db in pacman.handle.get_syncdbs() if db.name == "core")
path = Path("randomname")
mocker.patch("pathlib.Path.is_dir", return_value=True)
# root database does not exist, local database does not either
mocker.patch("pathlib.Path.is_file", return_value=False)
copy_mock = mocker.patch("shutil.copy")
pacman.database_copy(database, path, repository_paths, use_ahriman_cache=True)
copy_mock.assert_not_called()
def test_database_copy_database_exist(pacman: Pacman, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
"""
must do not copy database if local cache already exists
"""
database = next(db for db in pacman.handle.get_syncdbs() if db.name == "core")
mocker.patch("pathlib.Path.is_dir", return_value=True)
# root database exists, local database does either
mocker.patch("pathlib.Path.is_file", return_value=True)
copy_mock = mocker.patch("shutil.copy")
pacman.database_copy(database, Path("root"), repository_paths, use_ahriman_cache=True)
copy_mock.assert_not_called()
def test_database_init(pacman: Pacman, configuration: Configuration) -> None:
"""
must init database with settings
"""
mirror = configuration.get("alpm", "mirror")
database = pacman.database_init("test", mirror, "x86_64")
assert len(database.servers) == 1
def test_database_sync(pacman: Pacman) -> None:
"""
must sync databases
"""
handle_mock = MagicMock()
core_mock = MagicMock()
extra_mock = MagicMock()
transaction_mock = MagicMock()
handle_mock.get_syncdbs.return_value = [core_mock, extra_mock]
handle_mock.init_transaction.return_value = transaction_mock
pacman.handle = handle_mock
pacman.database_sync(False)
handle_mock.init_transaction.assert_called_once_with()
core_mock.update.assert_called_once_with(False)
extra_mock.update.assert_called_once_with(False)
transaction_mock.release.assert_called_once_with()
def test_database_sync_failed(pacman: Pacman) -> None:
"""
must sync databases even if there was exception
"""
handle_mock = MagicMock()
core_mock = MagicMock()
core_mock.update.side_effect = PyalpmError()
extra_mock = MagicMock()
handle_mock.get_syncdbs.return_value = [core_mock, extra_mock]
pacman.handle = handle_mock
pacman.database_sync(False)
extra_mock.update.assert_called_once_with(False)
def test_database_sync_forced(pacman: Pacman) -> None:
"""
must sync databases with force flag
"""
handle_mock = MagicMock()
core_mock = MagicMock()
handle_mock.get_syncdbs.return_value = [core_mock]
pacman.handle = handle_mock
pacman.database_sync(True)
handle_mock.init_transaction.assert_called_once_with()
core_mock.update.assert_called_once_with(True)
def test_package_get(pacman: Pacman) -> None:
"""
must retrieve package
"""
assert list(pacman.package_get("pacman"))
def test_package_get_empty(pacman: Pacman) -> None:
"""
must return empty packages list without exception
"""
assert not list(pacman.package_get("some-random-name"))
def test_packages(pacman: Pacman) -> None:
def test_all_packages(pacman: Pacman) -> None:
"""
package list must not be empty
"""
packages = pacman.packages()
packages = pacman.all_packages()
assert packages
assert "pacman" in packages
def test_packages_with_provides(pacman: Pacman) -> None:
def test_all_packages_with_provides(pacman: Pacman) -> None:
"""
package list must contain provides packages
"""
assert "sh" in pacman.packages()
assert "sh" in pacman.all_packages()
def test_get(pacman: Pacman) -> None:
"""
must retrieve package
"""
assert list(pacman.get("pacman"))
def test_get_empty(pacman: Pacman) -> None:
"""
must return empty packages list without exception
"""
assert not list(pacman.get("some-random-name"))

View File

@ -154,5 +154,9 @@ def test_tree_create(repository_paths: RepositoryPaths, mocker: MockerFixture) -
chown_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.chown")
repository_paths.tree_create()
mkdir_mock.assert_has_calls([mock.call(mode=0o755, parents=True, exist_ok=True) for _ in paths], any_order=True)
chown_mock.assert_has_calls([mock.call(pytest.helpers.anyvar(int)) for _ in paths], any_order=True)
mkdir_mock.assert_has_calls(
[
mock.call(mode=0o755, parents=True, exist_ok=True)
for _ in paths
], any_order=True)
chown_mock.assert_has_calls([mock.call(getattr(repository_paths, path)) for path in paths], any_order=True)

View File

@ -5,10 +5,8 @@ database = ../../../ahriman-test.db
[alpm]
database = /var/lib/pacman
mirror = https://geo.mirror.pkgbuild.com/$repo/os/$arch
repositories = core extra community multilib
root = /
use_ahriman_cache = no
[auth]
client_id = client_id