mirror of
https://github.com/arcan1s/ahriman.git
synced 2026-02-24 21:59:48 +00:00
feat: archive package tree implementation (#153)
* store built packages in archive tree instead of repository * write tests to support new changes * implement atomic_move method, move files only with lock * use generic packages tree for all repos * lookup through archive packages before build * add archive trigger * add archive trigger * regenerate docs * gpg loader fix * support requires repostory flag * drop excess REQUIRES_REPOSITORY * simplify symlionk creation * remove generators * fix sttyle * add separate function for symlinks creation * fix rebase * add note about slicing * smol refactoring of archive_tree class * remove duplicate code * fix typos * few review fixes * monor fixes and typos * clean empty directories * remove side effect from getter * drop recursive remove * ensure_exists now accepts only argument * add package like guard to symlinks fix * speedup archive_lookup processing by iterrupting cycle * remove custom filelock * fix naming * remove remove flag from repo * review fixes * restore wrapper around filelock * extract repository explorer to separate class * docs update * fix ide findings
This commit is contained in:
@@ -145,63 +145,11 @@ def test_repositories_extract(args: argparse.Namespace, configuration: Configura
|
||||
args.configuration = configuration.path
|
||||
args.repository = "repo"
|
||||
mocker.patch("ahriman.core.configuration.Configuration.load", new=lambda self, _: self.copy_from(configuration))
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories")
|
||||
extract_mock = mocker.patch("ahriman.core.repository.Explorer.repositories_extract",
|
||||
return_value=[RepositoryId("arch", "repo")])
|
||||
|
||||
assert Handler.repositories_extract(args) == [RepositoryId("arch", "repo")]
|
||||
known_architectures_mock.assert_not_called()
|
||||
known_repositories_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_repositories_extract_repository(args: argparse.Namespace, configuration: Configuration,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must generate list of available repositories based on flags and tree
|
||||
"""
|
||||
args.architecture = "arch"
|
||||
args.configuration = configuration.path
|
||||
mocker.patch("ahriman.core.configuration.Configuration.load", new=lambda self, _: self.copy_from(configuration))
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories",
|
||||
return_value={"repo"})
|
||||
|
||||
assert Handler.repositories_extract(args) == [RepositoryId("arch", "repo")]
|
||||
known_architectures_mock.assert_not_called()
|
||||
known_repositories_mock.assert_called_once_with(configuration.repository_paths.root)
|
||||
|
||||
|
||||
def test_repositories_extract_repository_legacy(args: argparse.Namespace, configuration: Configuration,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must generate list of available repositories based on flags and tree (legacy mode)
|
||||
"""
|
||||
args.architecture = "arch"
|
||||
args.configuration = configuration.path
|
||||
mocker.patch("ahriman.core.configuration.Configuration.load", new=lambda self, _: self.copy_from(configuration))
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories",
|
||||
return_value=set())
|
||||
|
||||
assert Handler.repositories_extract(args) == [RepositoryId("arch", "aur")]
|
||||
known_architectures_mock.assert_not_called()
|
||||
known_repositories_mock.assert_called_once_with(configuration.repository_paths.root)
|
||||
|
||||
|
||||
def test_repositories_extract_architecture(args: argparse.Namespace, configuration: Configuration,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must read repository name from config
|
||||
"""
|
||||
args.configuration = configuration.path
|
||||
args.repository = "repo"
|
||||
mocker.patch("ahriman.core.configuration.Configuration.load", new=lambda self, _: self.copy_from(configuration))
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures",
|
||||
return_value={"arch"})
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories")
|
||||
|
||||
assert Handler.repositories_extract(args) == [RepositoryId("arch", "repo")]
|
||||
known_architectures_mock.assert_called_once_with(configuration.repository_paths.root, "repo")
|
||||
known_repositories_mock.assert_not_called()
|
||||
extract_mock.assert_called_once_with(pytest.helpers.anyvar(Configuration, True), args.repository, args.architecture)
|
||||
|
||||
|
||||
def test_repositories_extract_empty(args: argparse.Namespace, configuration: Configuration,
|
||||
@@ -212,8 +160,7 @@ def test_repositories_extract_empty(args: argparse.Namespace, configuration: Con
|
||||
args.command = "config"
|
||||
args.configuration = configuration.path
|
||||
mocker.patch("ahriman.core.configuration.Configuration.load", new=lambda self, _: self.copy_from(configuration))
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures", return_value=set())
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories", return_value=set())
|
||||
mocker.patch("ahriman.core.repository.Explorer.repositories_extract", return_value=[])
|
||||
|
||||
with pytest.raises(MissingArchitectureError):
|
||||
Handler.repositories_extract(args)
|
||||
@@ -227,12 +174,11 @@ def test_repositories_extract_systemd(args: argparse.Namespace, configuration: C
|
||||
args.configuration = configuration.path
|
||||
args.repository_id = "i686/some/repo/name"
|
||||
mocker.patch("ahriman.core.configuration.Configuration.load", new=lambda self, _: self.copy_from(configuration))
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories")
|
||||
extract_mock = mocker.patch("ahriman.core.repository.Explorer.repositories_extract",
|
||||
return_value=[RepositoryId("i686", "some-repo-name")])
|
||||
|
||||
assert Handler.repositories_extract(args) == [RepositoryId("i686", "some-repo-name")]
|
||||
known_architectures_mock.assert_not_called()
|
||||
known_repositories_mock.assert_not_called()
|
||||
extract_mock.assert_called_once_with(pytest.helpers.anyvar(Configuration, True), "some-repo-name", "i686")
|
||||
|
||||
|
||||
def test_repositories_extract_systemd_with_dash(args: argparse.Namespace, configuration: Configuration,
|
||||
@@ -243,12 +189,11 @@ def test_repositories_extract_systemd_with_dash(args: argparse.Namespace, config
|
||||
args.configuration = configuration.path
|
||||
args.repository_id = "i686-some-repo-name"
|
||||
mocker.patch("ahriman.core.configuration.Configuration.load", new=lambda self, _: self.copy_from(configuration))
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories")
|
||||
extract_mock = mocker.patch("ahriman.core.repository.Explorer.repositories_extract",
|
||||
return_value=[RepositoryId("i686", "some-repo-name")])
|
||||
|
||||
assert Handler.repositories_extract(args) == [RepositoryId("i686", "some-repo-name")]
|
||||
known_architectures_mock.assert_not_called()
|
||||
known_repositories_mock.assert_not_called()
|
||||
extract_mock.assert_called_once_with(pytest.helpers.anyvar(Configuration, True), "some-repo-name", "i686")
|
||||
|
||||
|
||||
def test_repositories_extract_systemd_legacy(args: argparse.Namespace, configuration: Configuration,
|
||||
@@ -259,10 +204,8 @@ def test_repositories_extract_systemd_legacy(args: argparse.Namespace, configura
|
||||
args.configuration = configuration.path
|
||||
args.repository_id = "i686"
|
||||
mocker.patch("ahriman.core.configuration.Configuration.load", new=lambda self, _: self.copy_from(configuration))
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories",
|
||||
return_value=set())
|
||||
extract_mock = mocker.patch("ahriman.core.repository.Explorer.repositories_extract",
|
||||
return_value=[RepositoryId("i686", "aur")])
|
||||
|
||||
assert Handler.repositories_extract(args) == [RepositoryId("i686", "aur")]
|
||||
known_architectures_mock.assert_not_called()
|
||||
known_repositories_mock.assert_called_once_with(configuration.repository_paths.root)
|
||||
extract_mock.assert_called_once_with(pytest.helpers.anyvar(Configuration, True), None, "i686")
|
||||
|
||||
@@ -6,6 +6,7 @@ from unittest.mock import call as MockCall
|
||||
|
||||
from ahriman.application.handlers.tree_migrate import TreeMigrate
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
@@ -16,6 +17,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc
|
||||
"""
|
||||
tree_create_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
||||
application_mock = mocker.patch("ahriman.application.handlers.tree_migrate.TreeMigrate.tree_move")
|
||||
symlinks_mock = mocker.patch("ahriman.application.handlers.tree_migrate.TreeMigrate.symlinks_fix")
|
||||
_, repository_id = configuration.check_loaded()
|
||||
old_paths = configuration.repository_paths
|
||||
new_paths = RepositoryPaths(old_paths.root, old_paths.repository_id, _force_current_tree=True)
|
||||
@@ -23,6 +25,36 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc
|
||||
TreeMigrate.run(args, repository_id, configuration, report=False)
|
||||
tree_create_mock.assert_called_once_with()
|
||||
application_mock.assert_called_once_with(old_paths, new_paths)
|
||||
symlinks_mock.assert_called_once_with(new_paths)
|
||||
|
||||
|
||||
def test_symlinks_fix(repository_paths: RepositoryPaths, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must replace symlinks during migration
|
||||
"""
|
||||
mocker.patch("ahriman.application.handlers.tree_migrate.walk", side_effect=[
|
||||
[
|
||||
repository_paths.archive_for(package_ahriman.base) / "file",
|
||||
repository_paths.archive_for(package_ahriman.base) / "symlink",
|
||||
],
|
||||
[
|
||||
repository_paths.repository / "file",
|
||||
repository_paths.repository / "symlink",
|
||||
],
|
||||
])
|
||||
mocker.patch("pathlib.Path.exists", autospec=True, side_effect=lambda p: p.name == "file")
|
||||
unlink_mock = mocker.patch("pathlib.Path.unlink")
|
||||
symlink_mock = mocker.patch("pathlib.Path.symlink_to")
|
||||
|
||||
TreeMigrate.symlinks_fix(repository_paths)
|
||||
unlink_mock.assert_called_once_with()
|
||||
symlink_mock.assert_called_once_with(
|
||||
Path("..") /
|
||||
".." /
|
||||
".." /
|
||||
repository_paths.archive_for(package_ahriman.base).relative_to(repository_paths.root) /
|
||||
"symlink"
|
||||
)
|
||||
|
||||
|
||||
def test_move_tree(mocker: MockerFixture) -> None:
|
||||
|
||||
@@ -79,7 +79,7 @@ def test_run_repo_specific_triggers(args: argparse.Namespace, configuration: Con
|
||||
_, repository_id = configuration.check_loaded()
|
||||
|
||||
# remove unused sections
|
||||
for section in ("customs3", "github:x86_64", "logs-rotation", "mirrorlist"):
|
||||
for section in ("archive", "customs3", "github:x86_64", "logs-rotation", "mirrorlist"):
|
||||
configuration.remove_section(section)
|
||||
|
||||
configuration.set_option("report", "target", "test")
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import datetime
|
||||
import pytest
|
||||
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
from sqlite3 import Cursor
|
||||
from typing import Any, TypeVar
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
@@ -11,12 +13,14 @@ from ahriman.core.alpm.remote import AUR
|
||||
from ahriman.core.auth import Auth
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.core.database.migrations import Migrations
|
||||
from ahriman.core.repository import Repository
|
||||
from ahriman.core.spawn import Spawn
|
||||
from ahriman.core.status import Client
|
||||
from ahriman.core.status.watcher import Watcher
|
||||
from ahriman.models.aur_package import AURPackage
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
from ahriman.models.migration import Migration
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_description import PackageDescription
|
||||
from ahriman.models.package_source import PackageSource
|
||||
@@ -48,7 +52,9 @@ def anyvar(cls: type[T], strict: bool = False) -> T:
|
||||
T: any wrapper
|
||||
"""
|
||||
class AnyVar(cls):
|
||||
"""any value wrapper"""
|
||||
"""
|
||||
any value wrapper
|
||||
"""
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
"""
|
||||
@@ -271,16 +277,23 @@ def configuration(repository_id: RepositoryId, tmp_path: Path, resource_path_roo
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def database(configuration: Configuration) -> SQLite:
|
||||
def database(configuration: Configuration, mocker: MockerFixture) -> SQLite:
|
||||
"""
|
||||
database fixture
|
||||
|
||||
Args:
|
||||
configuration(Configuration): configuration fixture
|
||||
mocker(MockerFixture): mocker object
|
||||
|
||||
Returns:
|
||||
SQLite: database test instance
|
||||
"""
|
||||
original_method = Migrations.perform_migration
|
||||
|
||||
def perform_migration(self: Migrations, cursor: Cursor, migration: Migration) -> None:
|
||||
original_method(self, cursor, replace(migration, migrate_data=lambda *args: None))
|
||||
|
||||
mocker.patch.object(Migrations, "perform_migration", autospec=True, side_effect=perform_migration)
|
||||
return SQLite.load(configuration)
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,16 @@ from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.core.alpm.repo import Repo
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
|
||||
def test_root(repository_paths: RepositoryPaths) -> None:
|
||||
"""
|
||||
must correctly define repository root
|
||||
"""
|
||||
assert Repo(repository_paths.repository_id.name, repository_paths, []).root == repository_paths.repository
|
||||
assert Repo(repository_paths.repository_id.name, repository_paths, [], Path("path")).root == Path("path")
|
||||
|
||||
|
||||
def test_repo_path(repo: Repo) -> None:
|
||||
@@ -22,6 +32,7 @@ def test_repo_add(repo: Repo, mocker: MockerFixture) -> None:
|
||||
repo.add(Path("path"))
|
||||
check_output_mock.assert_called_once() # it will be checked later
|
||||
assert check_output_mock.call_args[0][0] == "repo-add"
|
||||
assert "--remove" in check_output_mock.call_args[0]
|
||||
|
||||
|
||||
def test_repo_init(repo: Repo, mocker: MockerFixture) -> None:
|
||||
@@ -35,21 +46,23 @@ def test_repo_init(repo: Repo, mocker: MockerFixture) -> None:
|
||||
assert check_output_mock.call_args[0][0] == "repo-add"
|
||||
|
||||
|
||||
def test_repo_remove(repo: Repo, mocker: MockerFixture) -> None:
|
||||
def test_repo_remove(repo: Repo, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call repo-remove on package addition
|
||||
must call repo-remove on package removal
|
||||
"""
|
||||
filepath = package_ahriman.packages[package_ahriman.base].filepath
|
||||
mocker.patch("pathlib.Path.glob", return_value=[])
|
||||
check_output_mock = mocker.patch("ahriman.core.alpm.repo.check_output")
|
||||
|
||||
repo.remove("package", Path("package.pkg.tar.xz"))
|
||||
repo.remove(package_ahriman.base, filepath)
|
||||
check_output_mock.assert_called_once() # it will be checked later
|
||||
assert check_output_mock.call_args[0][0] == "repo-remove"
|
||||
assert package_ahriman.base in check_output_mock.call_args[0]
|
||||
|
||||
|
||||
def test_repo_remove_fail_no_file(repo: Repo, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must fail on missing file
|
||||
must fail removal on missing file
|
||||
"""
|
||||
mocker.patch("pathlib.Path.glob", return_value=[Path("package.pkg.tar.xz")])
|
||||
mocker.patch("pathlib.Path.unlink", side_effect=FileNotFoundError)
|
||||
|
||||
34
tests/ahriman/core/archive/conftest.py
Normal file
34
tests/ahriman/core/archive/conftest.py
Normal file
@@ -0,0 +1,34 @@
|
||||
import pytest
|
||||
|
||||
from ahriman.core.archive import ArchiveTrigger
|
||||
from ahriman.core.archive.archive_tree import ArchiveTree
|
||||
from ahriman.core.configuration import Configuration
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def archive_tree(configuration: Configuration) -> ArchiveTree:
|
||||
"""
|
||||
archive tree fixture
|
||||
|
||||
Args:
|
||||
configuration(Configuration): configuration fixture
|
||||
|
||||
Returns:
|
||||
ArchiveTree: archive tree test instance
|
||||
"""
|
||||
return ArchiveTree(configuration.repository_paths, [])
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def archive_trigger(configuration: Configuration) -> ArchiveTrigger:
|
||||
"""
|
||||
archive trigger fixture
|
||||
|
||||
Args:
|
||||
configuration(Configuration): configuration fixture
|
||||
|
||||
Returns:
|
||||
ArchiveTrigger: archive trigger test instance
|
||||
"""
|
||||
_, repository_id = configuration.check_loaded()
|
||||
return ArchiveTrigger(repository_id, configuration)
|
||||
176
tests/ahriman/core/archive/test_archive_tree.py
Normal file
176
tests/ahriman/core/archive/test_archive_tree.py
Normal file
@@ -0,0 +1,176 @@
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest.mock import call as MockCall
|
||||
|
||||
from ahriman.core.archive.archive_tree import ArchiveTree
|
||||
from ahriman.core.utils import utcnow
|
||||
from ahriman.models.package import Package
|
||||
|
||||
|
||||
def test_repo(archive_tree: ArchiveTree) -> None:
|
||||
"""
|
||||
must return correct repository object
|
||||
"""
|
||||
local = Path("local")
|
||||
repo = archive_tree._repo(local)
|
||||
|
||||
assert repo.sign_args == archive_tree.sign_args
|
||||
assert repo.name == archive_tree.repository_id.name
|
||||
assert repo.root == local
|
||||
|
||||
|
||||
def test_repository_for(archive_tree: ArchiveTree) -> None:
|
||||
"""
|
||||
must correctly generate path to repository
|
||||
"""
|
||||
path = archive_tree.repository_for()
|
||||
assert path.is_relative_to(archive_tree.paths.archive / "repos")
|
||||
assert (archive_tree.repository_id.name, archive_tree.repository_id.architecture) == path.parts[-2:]
|
||||
assert set(map("{:02d}".format, utcnow().timetuple()[:3])).issubset(path.parts)
|
||||
|
||||
|
||||
def test_directories_fix(archive_tree: ArchiveTree, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must remove empty directories recursively
|
||||
"""
|
||||
root = archive_tree.paths.archive / "repos"
|
||||
(root / "a" / "b").mkdir(parents=True, exist_ok=True)
|
||||
(root / "a" / "b" / "file").touch()
|
||||
(root / "a" / "b" / "c" / "d").mkdir(parents=True, exist_ok=True)
|
||||
|
||||
_original_rmdir = Path.rmdir
|
||||
rmdir_mock = mocker.patch("pathlib.Path.rmdir", autospec=True, side_effect=_original_rmdir)
|
||||
|
||||
archive_tree.directories_fix({Path("a") / "b" / "c" / "d"})
|
||||
rmdir_mock.assert_has_calls([
|
||||
MockCall(root / "a" / "b" / "c" / "d"),
|
||||
MockCall(root / "a" / "b" / "c"),
|
||||
])
|
||||
|
||||
|
||||
def test_symlinks_create(archive_tree: ArchiveTree, package_ahriman: Package, package_python_schedule: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must create symlinks
|
||||
"""
|
||||
_original_exists = Path.exists
|
||||
|
||||
symlinks_mock = mocker.patch("pathlib.Path.symlink_to", side_effect=(None, FileExistsError, FileExistsError))
|
||||
add_mock = mocker.patch("ahriman.core.alpm.repo.Repo.add")
|
||||
mocker.patch("pathlib.Path.glob", autospec=True, side_effect=lambda path, name: [path / name[:-1]])
|
||||
|
||||
archive_tree.symlinks_create([package_ahriman, package_python_schedule])
|
||||
symlinks_mock.assert_has_calls([
|
||||
MockCall(Path("..") /
|
||||
".." /
|
||||
".." /
|
||||
".." /
|
||||
".." /
|
||||
".." /
|
||||
archive_tree.paths.archive_for(package.base)
|
||||
.relative_to(archive_tree.paths.root)
|
||||
.relative_to("archive") /
|
||||
single.filename
|
||||
)
|
||||
for package in (package_ahriman, package_python_schedule)
|
||||
for single in package.packages.values()
|
||||
])
|
||||
add_mock.assert_called_once_with(
|
||||
archive_tree.repository_for() / package_ahriman.packages[package_ahriman.base].filename
|
||||
)
|
||||
|
||||
|
||||
def test_symlinks_create_empty_filename(archive_tree: ArchiveTree, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip symlinks creation if filename is not set
|
||||
"""
|
||||
package_ahriman.packages[package_ahriman.base].filename = None
|
||||
symlinks_mock = mocker.patch("pathlib.Path.symlink_to")
|
||||
|
||||
archive_tree.symlinks_create([package_ahriman])
|
||||
symlinks_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_symlinks_fix(archive_tree: ArchiveTree, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must fix broken symlinks
|
||||
"""
|
||||
_original_exists = Path.exists
|
||||
|
||||
def exists_mock(path: Path) -> bool:
|
||||
if path.name.startswith("symlink"):
|
||||
return True
|
||||
return _original_exists(path)
|
||||
|
||||
mocker.patch("pathlib.Path.is_symlink", side_effect=[True, True, False])
|
||||
mocker.patch("pathlib.Path.exists", autospec=True, side_effect=exists_mock)
|
||||
walk_mock = mocker.patch("ahriman.core.archive.archive_tree.walk", return_value=[
|
||||
archive_tree.repository_for() / filename
|
||||
for filename in (
|
||||
"symlink-1.0.0-1-x86_64.pkg.tar.zst",
|
||||
"symlink-1.0.0-1-x86_64.pkg.tar.zst.sig",
|
||||
"broken_symlink-1.0.0-1-x86_64.pkg.tar.zst",
|
||||
"file-1.0.0-1-x86_64.pkg.tar.zst",
|
||||
)
|
||||
])
|
||||
remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove")
|
||||
|
||||
assert list(archive_tree.symlinks_fix()) == [
|
||||
archive_tree.repository_for().relative_to(archive_tree.paths.archive / "repos"),
|
||||
]
|
||||
walk_mock.assert_called_once_with(archive_tree.paths.archive / "repos")
|
||||
remove_mock.assert_called_once_with(
|
||||
"broken_symlink", archive_tree.repository_for() / "broken_symlink-1.0.0-1-x86_64.pkg.tar.zst")
|
||||
|
||||
|
||||
def test_symlinks_fix_foreign_repository(archive_tree: ArchiveTree, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip symlinks check if repository name or architecture doesn't match
|
||||
"""
|
||||
_original_exists = Path.exists
|
||||
|
||||
def exists_mock(path: Path) -> bool:
|
||||
if path.name.startswith("symlink"):
|
||||
return True
|
||||
return _original_exists(path)
|
||||
|
||||
mocker.patch("pathlib.Path.is_symlink", side_effect=[True, True, False])
|
||||
mocker.patch("pathlib.Path.exists", autospec=True, side_effect=exists_mock)
|
||||
mocker.patch("ahriman.core.archive.archive_tree.walk", return_value=[
|
||||
archive_tree.repository_for().with_name("i686") / filename
|
||||
for filename in (
|
||||
"symlink-1.0.0-1-x86_64.pkg.tar.zst",
|
||||
"broken_symlink-1.0.0-1-x86_64.pkg.tar.zst",
|
||||
"file-1.0.0-1-x86_64.pkg.tar.zst",
|
||||
)
|
||||
])
|
||||
remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove")
|
||||
|
||||
assert list(archive_tree.symlinks_fix()) == []
|
||||
remove_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_tree_create(archive_tree: ArchiveTree, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must create repository root if not exists
|
||||
"""
|
||||
owner_guard_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.preserve_owner")
|
||||
mkdir_mock = mocker.patch("pathlib.Path.mkdir")
|
||||
init_mock = mocker.patch("ahriman.core.alpm.repo.Repo.init")
|
||||
|
||||
archive_tree.tree_create()
|
||||
owner_guard_mock.assert_called_once_with()
|
||||
mkdir_mock.assert_called_once_with(0o755, parents=True)
|
||||
init_mock.assert_called_once_with()
|
||||
|
||||
|
||||
def test_tree_create_exists(archive_tree: ArchiveTree, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip directory creation if already exists
|
||||
"""
|
||||
mocker.patch("pathlib.Path.exists", return_value=True)
|
||||
mkdir_mock = mocker.patch("pathlib.Path.mkdir")
|
||||
|
||||
archive_tree.tree_create()
|
||||
mkdir_mock.assert_not_called()
|
||||
37
tests/ahriman/core/archive/test_archive_trigger.py
Normal file
37
tests/ahriman/core/archive/test_archive_trigger.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.core.archive import ArchiveTrigger
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
def test_on_result(archive_trigger: ArchiveTrigger, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must create symlinks for actual repository
|
||||
"""
|
||||
symlinks_mock = mocker.patch("ahriman.core.archive.archive_tree.ArchiveTree.symlinks_create")
|
||||
archive_trigger.on_result(Result(), [package_ahriman])
|
||||
symlinks_mock.assert_called_once_with([package_ahriman])
|
||||
|
||||
|
||||
def test_on_start(archive_trigger: ArchiveTrigger, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must create repository tree on load
|
||||
"""
|
||||
tree_mock = mocker.patch("ahriman.core.archive.archive_tree.ArchiveTree.tree_create")
|
||||
archive_trigger.on_start()
|
||||
tree_mock.assert_called_once_with()
|
||||
|
||||
|
||||
def test_on_stop(archive_trigger: ArchiveTrigger, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must fix broken symlinks on stop
|
||||
"""
|
||||
local = Path("local")
|
||||
symlinks_mock = mocker.patch("ahriman.core.archive.archive_tree.ArchiveTree.symlinks_fix", return_value=[local])
|
||||
directories_mock = mocker.patch("ahriman.core.archive.archive_tree.ArchiveTree.directories_fix")
|
||||
|
||||
archive_trigger.on_stop()
|
||||
symlinks_mock.assert_called_once_with()
|
||||
directories_mock.assert_called_once_with({local})
|
||||
78
tests/ahriman/core/database/migrations/test_m016_archive.py
Normal file
78
tests/ahriman/core/database/migrations/test_m016_archive.py
Normal file
@@ -0,0 +1,78 @@
|
||||
import pytest
|
||||
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
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
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
|
||||
def test_migrate_data(connection: Connection, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must perform data migration
|
||||
"""
|
||||
_, repository_id = configuration.check_loaded()
|
||||
repositories = [
|
||||
repository_id,
|
||||
replace(repository_id, architecture="i686"),
|
||||
]
|
||||
mocker.patch("ahriman.core.repository.Explorer.repositories_extract", return_value=repositories)
|
||||
migration_mock = mocker.patch("ahriman.core.database.migrations.m016_archive.move_packages")
|
||||
|
||||
migrate_data(connection, configuration)
|
||||
migration_mock.assert_has_calls([
|
||||
MockCall(replace(configuration.repository_paths, repository_id=repository), pytest.helpers.anyvar(int))
|
||||
for repository in repositories
|
||||
])
|
||||
|
||||
|
||||
def test_move_packages(repository_paths: RepositoryPaths, pacman: Pacman, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must move packages to the archive directory
|
||||
"""
|
||||
|
||||
def is_file(self: Path, *args: Any, **kwargs: Any) -> bool:
|
||||
return "file" in self.name
|
||||
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[
|
||||
repository_paths.repository / ".hidden-file.pkg.tar.xz",
|
||||
repository_paths.repository / "directory",
|
||||
repository_paths.repository / "file.pkg.tar.xz",
|
||||
repository_paths.repository / "file.pkg.tar.xz.sig",
|
||||
repository_paths.repository / "file2.pkg.tar.xz",
|
||||
repository_paths.repository / "symlink.pkg.tar.xz",
|
||||
])
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=is_file)
|
||||
mocker.patch("pathlib.Path.exists", return_value=True)
|
||||
archive_mock = mocker.patch("ahriman.models.package.Package.from_archive", return_value=package_ahriman)
|
||||
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)
|
||||
archive_mock.assert_has_calls([
|
||||
MockCall(repository_paths.repository / filename, pacman)
|
||||
for filename in ("file.pkg.tar.xz", "file2.pkg.tar.xz")
|
||||
])
|
||||
move_mock.assert_has_calls([
|
||||
MockCall(repository_paths.repository / filename, repository_paths.archive_for(package_ahriman.base) / filename)
|
||||
for filename in ("file.pkg.tar.xz", "file.pkg.tar.xz.sig", "file2.pkg.tar.xz")
|
||||
])
|
||||
symlink_mock.assert_has_calls([
|
||||
MockCall(
|
||||
Path("..") /
|
||||
".." /
|
||||
".." /
|
||||
repository_paths.archive_for(package_ahriman.base).relative_to(repository_paths.root) /
|
||||
filename
|
||||
)
|
||||
for filename in ("file.pkg.tar.xz", "file.pkg.tar.xz.sig", "file2.pkg.tar.xz")
|
||||
])
|
||||
@@ -16,7 +16,7 @@ def test_load(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
init_mock.assert_called_once_with()
|
||||
|
||||
|
||||
def test_init(database: SQLite, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
def test_init(database: SQLite, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run migrations on init
|
||||
"""
|
||||
|
||||
@@ -1,13 +1,28 @@
|
||||
import pytest
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.housekeeping import LogsRotationTrigger
|
||||
from ahriman.core.housekeeping import ArchiveRotationTrigger, LogsRotationTrigger
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def archive_rotation_trigger(configuration: Configuration) -> ArchiveRotationTrigger:
|
||||
"""
|
||||
archive rotation trigger fixture
|
||||
|
||||
Args:
|
||||
configuration(Configuration): configuration fixture
|
||||
|
||||
Returns:
|
||||
ArchiveRotationTrigger: archive rotation trigger test instance
|
||||
"""
|
||||
_, repository_id = configuration.check_loaded()
|
||||
return ArchiveRotationTrigger(repository_id, configuration)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def logs_rotation_trigger(configuration: Configuration) -> LogsRotationTrigger:
|
||||
"""
|
||||
logs roration trigger fixture
|
||||
logs rotation trigger fixture
|
||||
|
||||
Args:
|
||||
configuration(Configuration): configuration fixture
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import pytest
|
||||
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
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.housekeeping import ArchiveRotationTrigger
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
def test_configuration_sections(configuration: Configuration) -> None:
|
||||
"""
|
||||
must correctly parse target list
|
||||
"""
|
||||
assert ArchiveRotationTrigger.configuration_sections(configuration) == ["archive"]
|
||||
|
||||
|
||||
def test_archives_remove(archive_rotation_trigger: ArchiveRotationTrigger, package_ahriman: Package,
|
||||
pacman: Pacman, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must remove older packages
|
||||
"""
|
||||
def package(version: Any, *args: Any, **kwargs: Any) -> Package:
|
||||
generated = replace(package_ahriman, version=str(version))
|
||||
generated.packages = {
|
||||
key: replace(value, filename=str(version))
|
||||
for key, value in generated.packages.items()
|
||||
}
|
||||
return generated
|
||||
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
mocker.patch("ahriman.core.housekeeping.archive_rotation_trigger.package_like", return_value=True)
|
||||
mocker.patch("pathlib.Path.glob", return_value=[Path(str(i)) for i in range(5)])
|
||||
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)
|
||||
unlink_mock = mocker.patch("pathlib.Path.unlink", autospec=True)
|
||||
|
||||
archive_rotation_trigger.archives_remove(package_ahriman, pacman)
|
||||
unlink_mock.assert_has_calls([
|
||||
MockCall(Path("0")),
|
||||
MockCall(Path("1")),
|
||||
])
|
||||
|
||||
|
||||
def test_archives_remove_keep(archive_rotation_trigger: ArchiveRotationTrigger, package_ahriman: Package,
|
||||
pacman: Pacman, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must keep all packages if set to
|
||||
"""
|
||||
def package(version: Any, *args: Any, **kwargs: Any) -> Package:
|
||||
generated = replace(package_ahriman, version=str(version))
|
||||
generated.packages = {
|
||||
key: replace(value, filename=str(version))
|
||||
for key, value in generated.packages.items()
|
||||
}
|
||||
return generated
|
||||
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
mocker.patch("ahriman.core.housekeeping.archive_rotation_trigger.package_like", return_value=True)
|
||||
mocker.patch("pathlib.Path.glob", return_value=[Path(str(i)) for i in range(5)])
|
||||
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)
|
||||
unlink_mock = mocker.patch("pathlib.Path.unlink", autospec=True)
|
||||
|
||||
archive_rotation_trigger.keep_built_packages = 0
|
||||
archive_rotation_trigger.archives_remove(package_ahriman, pacman)
|
||||
unlink_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_on_result(archive_rotation_trigger: ArchiveRotationTrigger, package_ahriman: Package,
|
||||
package_python_schedule: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must rotate archives
|
||||
"""
|
||||
mocker.patch("ahriman.core._Context.get")
|
||||
remove_mock = mocker.patch("ahriman.core.housekeeping.ArchiveRotationTrigger.archives_remove")
|
||||
archive_rotation_trigger.on_result(Result(added=[package_ahriman], failed=[package_python_schedule]), [])
|
||||
remove_mock.assert_called_once_with(package_ahriman, pytest.helpers.anyvar(int))
|
||||
@@ -7,13 +7,6 @@ from ahriman.core.status import Client
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
def test_requires_repository() -> None:
|
||||
"""
|
||||
must require repository identifier to be set to start
|
||||
"""
|
||||
assert LogsRotationTrigger.REQUIRES_REPOSITORY
|
||||
|
||||
|
||||
def test_configuration_sections(configuration: Configuration) -> None:
|
||||
"""
|
||||
must correctly parse target list
|
||||
@@ -21,7 +14,7 @@ def test_configuration_sections(configuration: Configuration) -> None:
|
||||
assert LogsRotationTrigger.configuration_sections(configuration) == ["logs-rotation"]
|
||||
|
||||
|
||||
def test_rotate(logs_rotation_trigger: LogsRotationTrigger, mocker: MockerFixture) -> None:
|
||||
def test_on_result(logs_rotation_trigger: LogsRotationTrigger, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must rotate logs
|
||||
"""
|
||||
|
||||
@@ -2,7 +2,6 @@ import logging
|
||||
import pytest
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest.mock import call as MockCall
|
||||
|
||||
from ahriman.core.alpm.repo import Repo
|
||||
from ahriman.core.build_tools.task import Task
|
||||
|
||||
@@ -5,13 +5,6 @@ from ahriman.core.report import ReportTrigger
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
def test_requires_repository() -> None:
|
||||
"""
|
||||
must require repository identifier to be set to start
|
||||
"""
|
||||
assert ReportTrigger.REQUIRES_REPOSITORY
|
||||
|
||||
|
||||
def test_configuration_sections(configuration: Configuration) -> None:
|
||||
"""
|
||||
must correctly parse target list
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
from typing import Any
|
||||
@@ -13,34 +14,223 @@ from ahriman.models.packagers import Packagers
|
||||
from ahriman.models.user import User
|
||||
|
||||
|
||||
def test_archive_lookup(executor: Executor, package_ahriman: Package, package_python_schedule: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must existing packages which match the version
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[
|
||||
Path("1.pkg.tar.zst"),
|
||||
Path("2.pkg.tar.zst"),
|
||||
Path("3.pkg.tar.zst"),
|
||||
])
|
||||
mocker.patch("ahriman.models.package.Package.from_archive", side_effect=[
|
||||
package_ahriman,
|
||||
package_python_schedule,
|
||||
replace(package_ahriman, version="1"),
|
||||
])
|
||||
glob_mock = mocker.patch("pathlib.Path.glob", return_value=[Path("1.pkg.tar.xz")])
|
||||
|
||||
assert list(executor._archive_lookup(package_ahriman)) == [Path("1.pkg.tar.xz")]
|
||||
glob_mock.assert_called_once_with(f"{package_ahriman.packages[package_ahriman.base].filename}*")
|
||||
|
||||
|
||||
def test_archive_lookup_version_mismatch(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return nothing if no packages found with the same version
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[
|
||||
Path("1.pkg.tar.zst"),
|
||||
])
|
||||
mocker.patch("ahriman.models.package.Package.from_archive", return_value=replace(package_ahriman, version="1"))
|
||||
|
||||
assert list(executor._archive_lookup(package_ahriman)) == []
|
||||
|
||||
|
||||
def test_archive_lookup_architecture_mismatch(executor: Executor, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return nothing if architecture doesn't match
|
||||
"""
|
||||
package_ahriman.packages[package_ahriman.base].architecture = "x86_64"
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.architecture", return_value="i686")
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[
|
||||
Path("1.pkg.tar.zst"),
|
||||
])
|
||||
mocker.patch("ahriman.models.package.Package.from_archive", return_value=package_ahriman)
|
||||
|
||||
assert list(executor._archive_lookup(package_ahriman)) == []
|
||||
|
||||
|
||||
def test_archive_lookup_no_archive_directory(
|
||||
executor: Executor,
|
||||
package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return nothing if no archive directory found
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=False)
|
||||
assert list(executor._archive_lookup(package_ahriman)) == []
|
||||
|
||||
|
||||
def test_archive_rename(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must correctly remove package archive
|
||||
"""
|
||||
path = "gconf-3.2.6+11+g07808097-10-x86_64.pkg.tar.zst"
|
||||
safe_path = "gconf-3.2.6-11-g07808097-10-x86_64.pkg.tar.zst"
|
||||
package_ahriman.packages[package_ahriman.base].filename = path
|
||||
rename_mock = mocker.patch("ahriman.core.repository.executor.atomic_move")
|
||||
|
||||
executor._archive_rename(package_ahriman.packages[package_ahriman.base], package_ahriman.base)
|
||||
rename_mock.assert_called_once_with(executor.paths.packages / path, executor.paths.packages / safe_path)
|
||||
assert package_ahriman.packages[package_ahriman.base].filename == safe_path
|
||||
|
||||
|
||||
def test_archive_rename_empty_filename(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip renaming if filename is not set
|
||||
"""
|
||||
package_ahriman.packages[package_ahriman.base].filename = None
|
||||
rename_mock = mocker.patch("ahriman.core.repository.executor.atomic_move")
|
||||
|
||||
executor._archive_rename(package_ahriman.packages[package_ahriman.base], package_ahriman.base)
|
||||
rename_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_package_build(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must build single package
|
||||
"""
|
||||
mocker.patch("ahriman.core.build_tools.task.Task.build", return_value=[Path(package_ahriman.base)])
|
||||
status_client_mock = mocker.patch("ahriman.core.status.Client.set_building")
|
||||
init_mock = mocker.patch("ahriman.core.build_tools.task.Task.init", return_value="sha")
|
||||
package_mock = mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman)
|
||||
lookup_mock = mocker.patch("ahriman.core.repository.executor.Executor._archive_lookup", return_value=[])
|
||||
with_packages_mock = mocker.patch("ahriman.models.package.Package.with_packages")
|
||||
rename_mock = mocker.patch("ahriman.core.repository.executor.atomic_move")
|
||||
|
||||
assert executor._package_build(package_ahriman, Path("local"), "packager", None) == "sha"
|
||||
status_client_mock.assert_called_once_with(package_ahriman.base)
|
||||
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)
|
||||
rename_mock.assert_called_once_with(Path(package_ahriman.base), executor.paths.packages / package_ahriman.base)
|
||||
|
||||
|
||||
def test_package_build_copy(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must copy package from archive if there are already built ones
|
||||
"""
|
||||
path = package_ahriman.packages[package_ahriman.base].filepath
|
||||
mocker.patch("ahriman.core.build_tools.task.Task.build", return_value=[Path(package_ahriman.base)])
|
||||
mocker.patch("ahriman.core.build_tools.task.Task.init")
|
||||
mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman)
|
||||
mocker.patch("ahriman.core.repository.executor.Executor._archive_lookup", return_value=[path])
|
||||
mocker.patch("ahriman.core.repository.executor.atomic_move")
|
||||
mocker.patch("ahriman.models.package.Package.with_packages")
|
||||
copy_mock = mocker.patch("shutil.copy")
|
||||
rename_mock = mocker.patch("ahriman.core.repository.executor.atomic_move")
|
||||
|
||||
executor._package_build(package_ahriman, Path("local"), "packager", None)
|
||||
copy_mock.assert_called_once_with(path, Path("local"))
|
||||
rename_mock.assert_called_once_with(Path("local") / path, executor.paths.packages / path)
|
||||
|
||||
|
||||
def test_package_remove(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run remove for packages
|
||||
"""
|
||||
repo_remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove")
|
||||
executor._package_remove(package_ahriman.base, package_ahriman.packages[package_ahriman.base].filepath)
|
||||
repo_remove_mock.assert_called_once_with(
|
||||
package_ahriman.base, package_ahriman.packages[package_ahriman.base].filepath)
|
||||
|
||||
|
||||
def test_package_remove_failed(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress errors during archive removal
|
||||
"""
|
||||
mocker.patch("ahriman.core.alpm.repo.Repo.remove", side_effect=Exception)
|
||||
executor._package_remove(package_ahriman.base, package_ahriman.packages[package_ahriman.base].filepath)
|
||||
|
||||
|
||||
def test_package_remove_base(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run remove base from status client
|
||||
"""
|
||||
status_client_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove")
|
||||
executor._package_remove_base(package_ahriman.base)
|
||||
status_client_mock.assert_called_once_with(package_ahriman.base)
|
||||
|
||||
|
||||
def test_package_remove_base_failed(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress errors during base removal
|
||||
"""
|
||||
mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove", side_effect=Exception)
|
||||
executor._package_remove_base(package_ahriman.base)
|
||||
|
||||
|
||||
def test_package_update(executor: Executor, package_ahriman: Package, user: User, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must update built package in repository
|
||||
"""
|
||||
rename_mock = mocker.patch("ahriman.core.repository.executor.atomic_move")
|
||||
symlink_mock = mocker.patch("pathlib.Path.symlink_to")
|
||||
repo_add_mock = mocker.patch("ahriman.core.alpm.repo.Repo.add")
|
||||
sign_package_mock = mocker.patch("ahriman.core.sign.gpg.GPG.process_sign_package", side_effect=lambda fn, _: [fn])
|
||||
filepath = next(package.filepath for package in package_ahriman.packages.values())
|
||||
|
||||
executor._package_update(filepath, package_ahriman.base, user.key)
|
||||
# must move files (once)
|
||||
rename_mock.assert_called_once_with(
|
||||
executor.paths.packages / filepath, executor.paths.archive_for(package_ahriman.base) / filepath)
|
||||
# must sign package
|
||||
sign_package_mock.assert_called_once_with(executor.paths.packages / filepath, user.key)
|
||||
# symlink to the archive
|
||||
symlink_mock.assert_called_once_with(
|
||||
Path("..") /
|
||||
".." /
|
||||
".." /
|
||||
executor.paths.archive_for(package_ahriman.base).relative_to(executor.paths.root) /
|
||||
filepath)
|
||||
# must add package
|
||||
repo_add_mock.assert_called_once_with(executor.paths.repository / filepath)
|
||||
|
||||
|
||||
def test_package_update_empty_filename(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip update for package which does not have path
|
||||
"""
|
||||
rename_mock = mocker.patch("ahriman.core.repository.executor.atomic_move")
|
||||
executor._package_update(None, package_ahriman.base, None)
|
||||
rename_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_process_build(executor: Executor, package_ahriman: Package, passwd: Any, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run build process
|
||||
"""
|
||||
mocker.patch("ahriman.models.repository_paths.getpwuid", return_value=passwd)
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.core.build_tools.task.Task.build", return_value=[Path(package_ahriman.base)])
|
||||
init_mock = mocker.patch("ahriman.core.build_tools.task.Task.init", return_value="sha")
|
||||
move_mock = mocker.patch("shutil.move")
|
||||
status_client_mock = mocker.patch("ahriman.core.status.Client.set_building")
|
||||
changes_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_changes_get",
|
||||
return_value=Changes("commit", "change"))
|
||||
commit_sha_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_changes_update")
|
||||
depends_on_mock = mocker.patch("ahriman.core.build_tools.package_archive.PackageArchive.depends_on",
|
||||
return_value=Dependencies())
|
||||
dependencies_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_dependencies_update")
|
||||
with_packages_mock = mocker.patch("ahriman.models.package.Package.with_packages")
|
||||
build_mock = mocker.patch("ahriman.core.repository.executor.Executor._package_build", return_value="sha")
|
||||
|
||||
executor.process_build([package_ahriman], Packagers("packager"), bump_pkgrel=False)
|
||||
init_mock.assert_called_once_with(pytest.helpers.anyvar(int), pytest.helpers.anyvar(int), None)
|
||||
with_packages_mock.assert_called_once_with([Path(package_ahriman.base)], executor.pacman)
|
||||
changes_mock.assert_called_once_with(package_ahriman.base)
|
||||
build_mock.assert_called_once_with(package_ahriman, pytest.helpers.anyvar(Path, strict=True), None, None)
|
||||
depends_on_mock.assert_called_once_with()
|
||||
dependencies_mock.assert_called_once_with(package_ahriman.base, Dependencies())
|
||||
# must move files (once)
|
||||
move_mock.assert_called_once_with(Path(package_ahriman.base), executor.paths.packages / package_ahriman.base)
|
||||
# must update status
|
||||
status_client_mock.assert_called_once_with(package_ahriman.base)
|
||||
commit_sha_mock.assert_called_once_with(package_ahriman.base, Changes("sha", "change"))
|
||||
|
||||
|
||||
@@ -50,7 +240,7 @@ def test_process_build_bump_pkgrel(executor: Executor, package_ahriman: Package,
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.core.build_tools.task.Task.build", return_value=[Path(package_ahriman.base)])
|
||||
mocker.patch("shutil.move")
|
||||
mocker.patch("ahriman.core.repository.executor.atomic_move")
|
||||
init_mock = mocker.patch("ahriman.core.build_tools.task.Task.init")
|
||||
|
||||
executor.process_build([package_ahriman], Packagers("packager"), bump_pkgrel=True)
|
||||
@@ -67,7 +257,7 @@ def test_process_build_failure(executor: Executor, package_ahriman: Package, moc
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages_built")
|
||||
mocker.patch("ahriman.core.build_tools.task.Task.build", return_value=[Path(package_ahriman.base)])
|
||||
mocker.patch("ahriman.core.build_tools.task.Task.init")
|
||||
mocker.patch("shutil.move", side_effect=Exception)
|
||||
mocker.patch("ahriman.core.repository.executor.atomic_move", side_effect=Exception)
|
||||
status_client_mock = mocker.patch("ahriman.core.status.Client.set_failed")
|
||||
|
||||
executor.process_build([package_ahriman])
|
||||
@@ -79,15 +269,15 @@ def test_process_remove_base(executor: Executor, package_ahriman: Package, mocke
|
||||
must run remove process for whole base
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
|
||||
repo_remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove")
|
||||
status_client_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove")
|
||||
remove_mock = mocker.patch("ahriman.core.repository.executor.Executor._package_remove")
|
||||
base_remove_mock = mocker.patch("ahriman.core.repository.executor.Executor._package_remove_base")
|
||||
|
||||
executor.process_remove([package_ahriman.base])
|
||||
# must remove via alpm wrapper
|
||||
repo_remove_mock.assert_called_once_with(
|
||||
remove_mock.assert_called_once_with(
|
||||
package_ahriman.base, package_ahriman.packages[package_ahriman.base].filepath)
|
||||
# must update status and remove package files
|
||||
status_client_mock.assert_called_once_with(package_ahriman.base)
|
||||
base_remove_mock.assert_called_once_with(package_ahriman.base)
|
||||
|
||||
|
||||
def test_process_remove_with_debug(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
@@ -99,12 +289,12 @@ def test_process_remove_with_debug(executor: Executor, package_ahriman: Package,
|
||||
f"{package_ahriman.base}-debug": package_ahriman.packages[package_ahriman.base],
|
||||
}
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove")
|
||||
repo_remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove")
|
||||
mocker.patch("ahriman.core.repository.executor.Executor._package_remove_base")
|
||||
remove_mock = mocker.patch("ahriman.core.repository.executor.Executor._package_remove")
|
||||
|
||||
executor.process_remove([package_ahriman.base])
|
||||
# must remove via alpm wrapper
|
||||
repo_remove_mock.assert_has_calls([
|
||||
remove_mock.assert_has_calls([
|
||||
MockCall(package_ahriman.base, package_ahriman.packages[package_ahriman.base].filepath),
|
||||
MockCall(f"{package_ahriman.base}-debug", package_ahriman.packages[package_ahriman.base].filepath),
|
||||
])
|
||||
@@ -116,12 +306,12 @@ def test_process_remove_base_multiple(executor: Executor, package_python_schedul
|
||||
must run remove process for whole base with multiple packages
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_python_schedule])
|
||||
repo_remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove")
|
||||
status_client_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove")
|
||||
remove_mock = mocker.patch("ahriman.core.repository.executor.Executor._package_remove")
|
||||
status_client_mock = mocker.patch("ahriman.core.repository.executor.Executor._package_remove_base")
|
||||
|
||||
executor.process_remove([package_python_schedule.base])
|
||||
# must remove via alpm wrapper
|
||||
repo_remove_mock.assert_has_calls([
|
||||
remove_mock.assert_has_calls([
|
||||
MockCall(package, props.filepath)
|
||||
for package, props in package_python_schedule.packages.items()
|
||||
], any_order=True)
|
||||
@@ -135,45 +325,27 @@ def test_process_remove_base_single(executor: Executor, package_python_schedule:
|
||||
must run remove process for single package in base
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_python_schedule])
|
||||
repo_remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove")
|
||||
status_client_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove")
|
||||
remove_mock = mocker.patch("ahriman.core.repository.executor.Executor._package_remove")
|
||||
status_client_mock = mocker.patch("ahriman.core.repository.executor.Executor._package_remove_base")
|
||||
|
||||
executor.process_remove(["python2-schedule"])
|
||||
# must remove via alpm wrapper
|
||||
repo_remove_mock.assert_called_once_with(
|
||||
remove_mock.assert_called_once_with(
|
||||
"python2-schedule", package_python_schedule.packages["python2-schedule"].filepath)
|
||||
# must not update status
|
||||
status_client_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_process_remove_failed(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress tree clear errors during package base removal
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove", side_effect=Exception)
|
||||
executor.process_remove([package_ahriman.base])
|
||||
|
||||
|
||||
def test_process_remove_tree_clear_failed(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress remove errors
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.core.alpm.repo.Repo.remove", side_effect=Exception)
|
||||
executor.process_remove([package_ahriman.base])
|
||||
|
||||
|
||||
def test_process_remove_nothing(executor: Executor, package_ahriman: Package, package_python_schedule: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must not remove anything if it was not requested
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
|
||||
repo_remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove")
|
||||
remove_mock = mocker.patch("ahriman.core.repository.executor.Executor._package_remove")
|
||||
|
||||
executor.process_remove([package_python_schedule.base])
|
||||
repo_remove_mock.assert_not_called()
|
||||
remove_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_process_remove_unknown(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
@@ -181,11 +353,11 @@ def test_process_remove_unknown(executor: Executor, package_ahriman: Package, mo
|
||||
must remove unknown package base
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[])
|
||||
repo_remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove")
|
||||
status_client_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove")
|
||||
remove_mock = mocker.patch("ahriman.core.repository.executor.Executor._package_remove")
|
||||
status_client_mock = mocker.patch("ahriman.core.repository.executor.Executor._package_remove_base")
|
||||
|
||||
executor.process_remove([package_ahriman.base])
|
||||
repo_remove_mock.assert_not_called()
|
||||
remove_mock.assert_not_called()
|
||||
status_client_mock.assert_called_once_with(package_ahriman.base)
|
||||
|
||||
|
||||
@@ -195,9 +367,8 @@ def test_process_update(executor: Executor, package_ahriman: Package, user: User
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.load_archives", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
|
||||
move_mock = mocker.patch("shutil.move")
|
||||
repo_add_mock = mocker.patch("ahriman.core.alpm.repo.Repo.add")
|
||||
sign_package_mock = mocker.patch("ahriman.core.sign.gpg.GPG.process_sign_package", side_effect=lambda fn, _: [fn])
|
||||
rename_mock = mocker.patch("ahriman.core.repository.executor.Executor._archive_rename")
|
||||
update_mock = mocker.patch("ahriman.core.repository.executor.Executor._package_update")
|
||||
status_client_mock = mocker.patch("ahriman.core.status.Client.set_success")
|
||||
remove_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_remove")
|
||||
packager_mock = mocker.patch("ahriman.core.repository.executor.Executor.packager", return_value=user)
|
||||
@@ -206,12 +377,8 @@ def test_process_update(executor: Executor, package_ahriman: Package, user: User
|
||||
# must return complete
|
||||
assert executor.process_update([filepath], Packagers("packager"))
|
||||
packager_mock.assert_called_once_with(Packagers("packager"), "ahriman")
|
||||
# must move files (once)
|
||||
move_mock.assert_called_once_with(executor.paths.packages / filepath, executor.paths.repository / filepath)
|
||||
# must sign package
|
||||
sign_package_mock.assert_called_once_with(executor.paths.packages / filepath, user.key)
|
||||
# must add package
|
||||
repo_add_mock.assert_called_once_with(executor.paths.repository / filepath)
|
||||
rename_mock.assert_called_once_with(package_ahriman.packages[package_ahriman.base], package_ahriman.base)
|
||||
update_mock.assert_called_once_with(filepath.name, package_ahriman.base, user.key)
|
||||
# must update status
|
||||
status_client_mock.assert_called_once_with(package_ahriman)
|
||||
# must clear directory
|
||||
@@ -226,58 +393,26 @@ def test_process_update_group(executor: Executor, package_python_schedule: Packa
|
||||
"""
|
||||
must group single packages under one base
|
||||
"""
|
||||
mocker.patch("shutil.move")
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.load_archives", return_value=[package_python_schedule])
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_python_schedule])
|
||||
repo_add_mock = mocker.patch("ahriman.core.alpm.repo.Repo.add")
|
||||
update_mock = mocker.patch("ahriman.core.repository.executor.Executor._package_update")
|
||||
status_client_mock = mocker.patch("ahriman.core.status.Client.set_success")
|
||||
remove_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_remove")
|
||||
|
||||
executor.process_update([package.filepath for package in package_python_schedule.packages.values()])
|
||||
repo_add_mock.assert_has_calls([
|
||||
MockCall(executor.paths.repository / package.filepath)
|
||||
update_mock.assert_has_calls([
|
||||
MockCall(package.filename, package_python_schedule.base, None)
|
||||
for package in package_python_schedule.packages.values()
|
||||
], any_order=True)
|
||||
status_client_mock.assert_called_once_with(package_python_schedule)
|
||||
remove_mock.assert_called_once_with([])
|
||||
|
||||
|
||||
def test_process_update_unsafe(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must encode file name
|
||||
"""
|
||||
path = "gconf-3.2.6+11+g07808097-10-x86_64.pkg.tar.zst"
|
||||
safe_path = "gconf-3.2.6-11-g07808097-10-x86_64.pkg.tar.zst"
|
||||
package_ahriman.packages[package_ahriman.base].filename = path
|
||||
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.load_archives", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
|
||||
move_mock = mocker.patch("shutil.move")
|
||||
repo_add_mock = mocker.patch("ahriman.core.alpm.repo.Repo.add")
|
||||
|
||||
executor.process_update([Path(path)])
|
||||
move_mock.assert_has_calls([
|
||||
MockCall(executor.paths.packages / path, executor.paths.packages / safe_path),
|
||||
MockCall(executor.paths.packages / safe_path, executor.paths.repository / safe_path)
|
||||
])
|
||||
repo_add_mock.assert_called_once_with(executor.paths.repository / safe_path)
|
||||
|
||||
|
||||
def test_process_empty_filename(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip update for package which does not have path
|
||||
"""
|
||||
package_ahriman.packages[package_ahriman.base].filename = None
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.load_archives", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
|
||||
executor.process_update([package.filepath for package in package_ahriman.packages.values()])
|
||||
|
||||
|
||||
def test_process_update_failed(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must process update for failed package
|
||||
"""
|
||||
mocker.patch("shutil.move", side_effect=Exception)
|
||||
mocker.patch("ahriman.core.repository.executor.Executor._package_update", side_effect=Exception)
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.load_archives", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
|
||||
status_client_mock = mocker.patch("ahriman.core.status.Client.set_failed")
|
||||
@@ -294,8 +429,7 @@ def test_process_update_removed_package(executor: Executor, package_python_sched
|
||||
without_python2 = Package.from_json(package_python_schedule.view())
|
||||
del without_python2.packages["python2-schedule"]
|
||||
|
||||
mocker.patch("shutil.move")
|
||||
mocker.patch("ahriman.core.alpm.repo.Repo.add")
|
||||
mocker.patch("ahriman.core.repository.executor.Executor._package_update")
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.load_archives", return_value=[without_python2])
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_python_schedule])
|
||||
remove_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_remove")
|
||||
|
||||
56
tests/ahriman/core/repository/test_explorer.py
Normal file
56
tests/ahriman/core/repository/test_explorer.py
Normal file
@@ -0,0 +1,56 @@
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.repository import Explorer
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
def test_repositories_extract(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must generate list of available repositories based on arguments
|
||||
"""
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories")
|
||||
|
||||
assert Explorer.repositories_extract(configuration, "repo", "arch") == [RepositoryId("arch", "repo")]
|
||||
known_architectures_mock.assert_not_called()
|
||||
known_repositories_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_repositories_extract_repository(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must generate list of available repositories based on arguments and tree
|
||||
"""
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories",
|
||||
return_value={"repo"})
|
||||
|
||||
assert Explorer.repositories_extract(configuration, architecture="arch") == [RepositoryId("arch", "repo")]
|
||||
known_architectures_mock.assert_not_called()
|
||||
known_repositories_mock.assert_called_once_with(configuration.repository_paths.root)
|
||||
|
||||
|
||||
def test_repositories_extract_repository_legacy(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must generate list of available repositories based on arguments and tree (legacy mode)
|
||||
"""
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories",
|
||||
return_value=set())
|
||||
|
||||
assert Explorer.repositories_extract(configuration, architecture="arch") == [RepositoryId("arch", "aur")]
|
||||
known_architectures_mock.assert_not_called()
|
||||
known_repositories_mock.assert_called_once_with(configuration.repository_paths.root)
|
||||
|
||||
|
||||
def test_repositories_extract_architecture(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must read repository name from config
|
||||
"""
|
||||
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures",
|
||||
return_value={"arch"})
|
||||
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories")
|
||||
|
||||
assert Explorer.repositories_extract(configuration, repository="repo") == [RepositoryId("arch", "repo")]
|
||||
known_architectures_mock.assert_called_once_with(configuration.repository_paths.root, "repo")
|
||||
known_repositories_mock.assert_not_called()
|
||||
@@ -35,7 +35,7 @@ def test_event_get(local_client: LocalClient, package_ahriman: Package, mocker:
|
||||
local_client.repository_id)
|
||||
|
||||
|
||||
def test_logs_rotate(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
def test_logs_rotate(local_client: LocalClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must rotate logs
|
||||
"""
|
||||
|
||||
@@ -7,13 +7,6 @@ from ahriman.core.sign.gpg import GPG
|
||||
from ahriman.core.support import KeyringTrigger
|
||||
|
||||
|
||||
def test_requires_repository() -> None:
|
||||
"""
|
||||
must require repository identifier to be set to start
|
||||
"""
|
||||
assert KeyringTrigger.REQUIRES_REPOSITORY
|
||||
|
||||
|
||||
def test_configuration_sections(configuration: Configuration) -> None:
|
||||
"""
|
||||
must correctly parse target list
|
||||
|
||||
@@ -4,13 +4,6 @@ from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.support import MirrorlistTrigger
|
||||
|
||||
|
||||
def test_requires_repository() -> None:
|
||||
"""
|
||||
must require repository identifier to be set to start
|
||||
"""
|
||||
assert MirrorlistTrigger.REQUIRES_REPOSITORY
|
||||
|
||||
|
||||
def test_configuration_sections(configuration: Configuration) -> None:
|
||||
"""
|
||||
must correctly parse target list
|
||||
|
||||
@@ -16,6 +16,18 @@ from ahriman.models.repository_id import RepositoryId
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
|
||||
|
||||
def test_atomic_move(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must move file with locking
|
||||
"""
|
||||
filelock_mock = mocker.patch("ahriman.core.utils.filelock")
|
||||
move_mock = mocker.patch("shutil.move")
|
||||
|
||||
atomic_move(Path("source"), Path("destination"))
|
||||
filelock_mock.assert_called_once_with(Path("destination"))
|
||||
move_mock.assert_called_once_with(Path("source"), Path("destination"))
|
||||
|
||||
|
||||
def test_check_output(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run command and log result
|
||||
@@ -235,6 +247,30 @@ def test_extract_user() -> None:
|
||||
assert extract_user() == "doas"
|
||||
|
||||
|
||||
def test_filelock(tmp_path: Path) -> None:
|
||||
"""
|
||||
must acquire lock and remove lock file after
|
||||
"""
|
||||
local = tmp_path / "local"
|
||||
lock = local.with_name(f".{local.name}.lock")
|
||||
|
||||
with filelock(local):
|
||||
assert lock.exists()
|
||||
assert not lock.exists()
|
||||
|
||||
|
||||
def test_filelock_cleanup_on_missing(tmp_path: Path) -> None:
|
||||
"""
|
||||
must not fail if lock file is already removed
|
||||
"""
|
||||
local = tmp_path / "local"
|
||||
lock = local.with_name(f".{local.name}.lock")
|
||||
|
||||
with filelock(local):
|
||||
lock.unlink(missing_ok=True)
|
||||
assert not lock.exists()
|
||||
|
||||
|
||||
def test_filter_json(package_ahriman: Package) -> None:
|
||||
"""
|
||||
must filter fields by known list
|
||||
@@ -470,6 +506,23 @@ def test_srcinfo_property_list() -> None:
|
||||
assert srcinfo_property_list("key", {"key_x86_64": ["overrides"]}, {}, architecture="x86_64") == ["overrides"]
|
||||
|
||||
|
||||
def test_symlink_relative(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must create symlinks with relative paths
|
||||
"""
|
||||
symlink_mock = mocker.patch("pathlib.Path.symlink_to")
|
||||
|
||||
symlink_relative(Path("a"), Path("b"))
|
||||
symlink_relative(Path("root/a"), Path("root/c"))
|
||||
symlink_relative(Path("root/sub/a"), Path("root/c"))
|
||||
|
||||
symlink_mock.assert_has_calls([
|
||||
MockCall(Path("b")),
|
||||
MockCall(Path("c")),
|
||||
MockCall(Path("../c")),
|
||||
])
|
||||
|
||||
|
||||
def test_trim_package() -> None:
|
||||
"""
|
||||
must trim package version
|
||||
|
||||
@@ -58,7 +58,7 @@ def test_configuration_schema_no_schema(configuration: Configuration) -> None:
|
||||
assert ReportTrigger.configuration_schema(configuration) == {}
|
||||
|
||||
|
||||
def test_configuration_schema_empty(configuration: Configuration) -> None:
|
||||
def test_configuration_schema_empty() -> None:
|
||||
"""
|
||||
must return default schema if no configuration set
|
||||
"""
|
||||
|
||||
@@ -5,13 +5,6 @@ from ahriman.core.upload import UploadTrigger
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
def test_requires_repository() -> None:
|
||||
"""
|
||||
must require repository identifier to be set to start
|
||||
"""
|
||||
assert UploadTrigger.REQUIRES_REPOSITORY
|
||||
|
||||
|
||||
def test_configuration_sections(configuration: Configuration) -> None:
|
||||
"""
|
||||
must correctly parse target list
|
||||
|
||||
@@ -4,16 +4,12 @@ from pathlib import Path
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
from ahriman import __version__
|
||||
from ahriman.core.alpm.remote import AUR
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
from ahriman.models.counters import Counters
|
||||
from ahriman.models.filesystem_package import FilesystemPackage
|
||||
from ahriman.models.internal_status import InternalStatus
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_description import PackageDescription
|
||||
from ahriman.models.package_source import PackageSource
|
||||
from ahriman.models.pkgbuild import Pkgbuild
|
||||
from ahriman.models.remote_source import RemoteSource
|
||||
from ahriman.models.repository_stats import RepositoryStats
|
||||
|
||||
|
||||
|
||||
@@ -353,6 +353,15 @@ def test_build_status_pretty_print(package_ahriman: Package) -> None:
|
||||
assert isinstance(package_ahriman.pretty_print(), str)
|
||||
|
||||
|
||||
def test_vercmp(package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call vercmp
|
||||
"""
|
||||
vercmp_mock = mocker.patch("ahriman.models.package.vercmp")
|
||||
package_ahriman.vercmp("version")
|
||||
vercmp_mock.assert_called_once_with(package_ahriman.version, "version")
|
||||
|
||||
|
||||
def test_with_packages(package_ahriman: Package, package_python_schedule: Package, pacman: Pacman,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
|
||||
@@ -185,6 +185,14 @@ def test_known_repositories_empty(repository_paths: RepositoryPaths, mocker: Moc
|
||||
iterdir_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_archive_for(repository_paths: RepositoryPaths, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must correctly define archive path
|
||||
"""
|
||||
path = repository_paths.archive_for(package_ahriman.base)
|
||||
assert path == repository_paths.archive / "packages" / "a" / package_ahriman.base
|
||||
|
||||
|
||||
def test_cache_for(repository_paths: RepositoryPaths, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must return correct path for cache directory
|
||||
@@ -194,6 +202,29 @@ def test_cache_for(repository_paths: RepositoryPaths, package_ahriman: Package)
|
||||
assert path.parent == repository_paths.cache
|
||||
|
||||
|
||||
def test_ensure_exists(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must create directory if it doesn't exist
|
||||
"""
|
||||
owner_guard_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.preserve_owner")
|
||||
mkdir_mock = mocker.patch("pathlib.Path.mkdir")
|
||||
|
||||
repository_paths.ensure_exists(repository_paths.archive)
|
||||
owner_guard_mock.assert_called_once_with()
|
||||
mkdir_mock.assert_called_once_with(mode=0o755, parents=True)
|
||||
|
||||
|
||||
def test_ensure_exists_skip(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must do not create directory if it already exists
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
mkdir_mock = mocker.patch("pathlib.Path.mkdir")
|
||||
|
||||
repository_paths.ensure_exists(repository_paths.archive)
|
||||
mkdir_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_preserve_owner(tmp_path: Path, repository_id: RepositoryId, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must preserve file owner during operations
|
||||
@@ -248,8 +279,8 @@ def test_tree_clear(repository_paths: RepositoryPaths, package_ahriman: Package,
|
||||
must remove any package related files
|
||||
"""
|
||||
paths = {
|
||||
getattr(repository_paths, prop)(package_ahriman.base)
|
||||
for prop in dir(repository_paths) if prop.endswith("_for")
|
||||
repository_paths.cache_for(package_ahriman.base),
|
||||
repository_paths.archive_for(package_ahriman.base),
|
||||
}
|
||||
rmtree_mock = mocker.patch("shutil.rmtree")
|
||||
|
||||
@@ -269,6 +300,7 @@ def test_tree_create(repository_paths: RepositoryPaths, mocker: MockerFixture) -
|
||||
for prop in dir(repository_paths)
|
||||
if not prop.startswith("_")
|
||||
and prop not in (
|
||||
"archive_for",
|
||||
"build_root",
|
||||
"logger_name",
|
||||
"logger",
|
||||
@@ -282,8 +314,12 @@ def test_tree_create(repository_paths: RepositoryPaths, mocker: MockerFixture) -
|
||||
owner_guard_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.preserve_owner")
|
||||
|
||||
repository_paths.tree_create()
|
||||
mkdir_mock.assert_has_calls([MockCall(mode=0o755, parents=True, exist_ok=True) for _ in paths], any_order=True)
|
||||
owner_guard_mock.assert_called_once_with()
|
||||
mkdir_mock.assert_has_calls([MockCall(mode=0o755, parents=True) for _ in paths], any_order=True)
|
||||
owner_guard_mock.assert_has_calls([
|
||||
MockCall(),
|
||||
MockCall().__enter__(),
|
||||
MockCall().__exit__(None, None, None)
|
||||
] * len(paths))
|
||||
|
||||
|
||||
def test_tree_create_skip(mocker: MockerFixture) -> None:
|
||||
|
||||
@@ -66,7 +66,7 @@ async def test_delete_partially(client: TestClient, package_ahriman: Package) ->
|
||||
assert json
|
||||
|
||||
|
||||
async def test_delete_exception(client: TestClient, package_ahriman: Package) -> None:
|
||||
async def test_delete_exception(client: TestClient) -> None:
|
||||
"""
|
||||
must raise exception on invalid payload
|
||||
"""
|
||||
|
||||
@@ -109,7 +109,7 @@ async def test_post(client: TestClient, repository_paths: RepositoryPaths, mocke
|
||||
local = Path("local")
|
||||
save_mock = pytest.helpers.patch_view(client.app, "save_file",
|
||||
AsyncMock(return_value=("filename", local / ".filename")))
|
||||
rename_mock = mocker.patch("pathlib.Path.rename")
|
||||
rename_mock = mocker.patch("ahriman.web.views.v1.service.upload.atomic_move")
|
||||
# no content validation here because it has invalid schema
|
||||
|
||||
data = FormData()
|
||||
@@ -118,7 +118,7 @@ async def test_post(client: TestClient, repository_paths: RepositoryPaths, mocke
|
||||
response = await client.post("/api/v1/service/upload", data=data)
|
||||
assert response.ok
|
||||
save_mock.assert_called_once_with(pytest.helpers.anyvar(int), repository_paths.packages, max_body_size=None)
|
||||
rename_mock.assert_called_once_with(local / "filename")
|
||||
rename_mock.assert_called_once_with(local / ".filename", local / "filename")
|
||||
|
||||
|
||||
async def test_post_with_sig(client: TestClient, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
|
||||
@@ -131,7 +131,7 @@ async def test_post_with_sig(client: TestClient, repository_paths: RepositoryPat
|
||||
("filename", local / ".filename"),
|
||||
("filename.sig", local / ".filename.sig"),
|
||||
]))
|
||||
rename_mock = mocker.patch("pathlib.Path.rename")
|
||||
rename_mock = mocker.patch("ahriman.web.views.v1.service.upload.atomic_move")
|
||||
# no content validation here because it has invalid schema
|
||||
|
||||
data = FormData()
|
||||
@@ -145,8 +145,8 @@ async def test_post_with_sig(client: TestClient, repository_paths: RepositoryPat
|
||||
MockCall(pytest.helpers.anyvar(int), repository_paths.packages, max_body_size=None),
|
||||
])
|
||||
rename_mock.assert_has_calls([
|
||||
MockCall(local / "filename"),
|
||||
MockCall(local / "filename.sig"),
|
||||
MockCall(local / ".filename", local / "filename"),
|
||||
MockCall(local / ".filename.sig", local / "filename.sig"),
|
||||
])
|
||||
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@ root = /
|
||||
sync_files_database = no
|
||||
use_ahriman_cache = no
|
||||
|
||||
[archive]
|
||||
keep_built_packages = 3
|
||||
|
||||
[auth]
|
||||
client_id = client_id
|
||||
client_secret = client_secret
|
||||
|
||||
Reference in New Issue
Block a user