diff --git a/src/ahriman/core/build_tools/sources.py b/src/ahriman/core/build_tools/sources.py index 780c167f..3878a3bb 100644 --- a/src/ahriman/core/build_tools/sources.py +++ b/src/ahriman/core/build_tools/sources.py @@ -84,10 +84,11 @@ class Sources: is_initialized_git = (sources_dir / ".git").is_dir() if is_initialized_git and not Sources.branches(sources_dir): # there is git repository, but no remote configured so far + Sources.logger.info("skip update at %s because there are no branches configured", sources_dir) return if is_initialized_git: - Sources.logger.info("update HEAD to remote to %s", sources_dir) + Sources.logger.info("update HEAD to remote at %s", sources_dir) Sources._check_output("git", "fetch", "origin", Sources._branch, exception=None, cwd=sources_dir, logger=Sources.logger) else: diff --git a/src/ahriman/models/repository_paths.py b/src/ahriman/models/repository_paths.py index 7dac934a..baeb97cf 100644 --- a/src/ahriman/models/repository_paths.py +++ b/src/ahriman/models/repository_paths.py @@ -141,7 +141,8 @@ class RepositoryPaths: for directory in ( self.cache_for(package_base), self.manual_for(package_base), - self.patches_for(package_base)): + self.patches_for(package_base), + self.sources_for(package_base)): shutil.rmtree(directory, ignore_errors=True) def tree_create(self) -> None: diff --git a/tests/ahriman/application/test_application.py b/tests/ahriman/application/test_application.py index b8c30341..f830673c 100644 --- a/tests/ahriman/application/test_application.py +++ b/tests/ahriman/application/test_application.py @@ -1,5 +1,6 @@ import pytest +from pathlib import Path from pytest_mock import MockerFixture from unittest import mock @@ -104,6 +105,43 @@ def test_get_updates_with_filter(application: Application, mocker: MockerFixture updates_manual_mock.assert_called_once() +def test_add_archive(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must add package from archive + """ + mocker.patch("ahriman.application.application.Application._known_packages", return_value=set()) + move_mock = mocker.patch("shutil.move") + + application.add([package_ahriman.base], PackageSource.Archive, False) + move_mock.assert_called_once() + + +def test_add_remote(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must add package from AUR + """ + mocker.patch("ahriman.application.application.Application._known_packages", return_value=set()) + mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman) + load_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.load") + + application.add([package_ahriman.base], PackageSource.AUR, True) + load_mock.assert_called_once() + + +def test_add_remote_with_dependencies(application: Application, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must add package from AUR with dependencies + """ + mocker.patch("ahriman.application.application.Application._known_packages", return_value=set()) + mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman) + mocker.patch("ahriman.core.build_tools.sources.Sources.load") + dependencies_mock = mocker.patch("ahriman.models.package.Package.dependencies") + + application.add([package_ahriman.base], PackageSource.AUR, False) + dependencies_mock.assert_called_once() + + def test_add_directory(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None: """ must add packages from directory @@ -118,43 +156,37 @@ def test_add_directory(application: Application, package_ahriman: Package, mocke move_mock.assert_called_once() -def test_add_manual(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None: +def test_add_local(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None: """ - must add package from AUR + must add package from local sources """ mocker.patch("ahriman.application.application.Application._known_packages", return_value=set()) mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman) - load_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.load") + init_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.init") + copytree_mock = mocker.patch("shutil.copytree") - application.add([package_ahriman.base], PackageSource.AUR, True) - load_mock.assert_called_once() + application.add([package_ahriman.base], PackageSource.Local, True) + init_mock.assert_called_once() + copytree_mock.assert_has_calls([ + mock.call(Path(package_ahriman.base), application.repository.paths.manual_for(package_ahriman.base)), + mock.call(Path(package_ahriman.base), application.repository.paths.cache_for(package_ahriman.base)), + ]) -def test_add_manual_with_dependencies(application: Application, package_ahriman: Package, - mocker: MockerFixture) -> None: +def test_add_local_with_dependencies(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None: """ - must add package from AUR with dependencies + must add package from local sources with dependencies """ mocker.patch("ahriman.application.application.Application._known_packages", return_value=set()) mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman) - mocker.patch("ahriman.core.build_tools.sources.Sources.load") + mocker.patch("ahriman.core.build_tools.sources.Sources.init") + mocker.patch("shutil.copytree") dependencies_mock = mocker.patch("ahriman.models.package.Package.dependencies") - application.add([package_ahriman.base], PackageSource.AUR, False) + application.add([package_ahriman.base], PackageSource.Local, False) dependencies_mock.assert_called_once() -def test_add_package(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None: - """ - must add package from archive - """ - mocker.patch("ahriman.application.application.Application._known_packages", return_value=set()) - move_mock = mocker.patch("shutil.move") - - application.add([package_ahriman.base], PackageSource.Archive, False) - move_mock.assert_called_once() - - def test_clean_build(application: Application, mocker: MockerFixture) -> None: """ must clean build directory diff --git a/tests/ahriman/core/build_tools/test_sources.py b/tests/ahriman/core/build_tools/test_sources.py index 084acbad..d1adedb7 100644 --- a/tests/ahriman/core/build_tools/test_sources.py +++ b/tests/ahriman/core/build_tools/test_sources.py @@ -22,6 +22,26 @@ def test_add(mocker: MockerFixture) -> None: exception=None, cwd=local, logger=pytest.helpers.anyvar(int)) +def test_branches(mocker: MockerFixture) -> None: + """ + must ask for available branches + """ + check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output", return_value="b\na") + + local = Path("local") + branches = Sources.branches(local) + check_output_mock.assert_called_with("git", "branch", exception=None, cwd=local, logger=pytest.helpers.anyvar(int)) + assert branches == ["a", "b"] + + +def test_branches_empty(mocker: MockerFixture) -> None: + """ + must ask for available branches and do not fail if no branches found + """ + mocker.patch("ahriman.core.build_tools.sources.Sources._check_output", return_value="") + assert Sources.branches(Path("local")) == [] + + def test_diff(mocker: MockerFixture) -> None: """ must calculate diff @@ -35,19 +55,34 @@ def test_diff(mocker: MockerFixture) -> None: check_output_mock.assert_called_with("git", "diff", exception=None, cwd=local, logger=pytest.helpers.anyvar(int)) -def test_fetch_existing(mocker: MockerFixture) -> None: +def test_fetch_empty(mocker: MockerFixture) -> None: """ - must fetch new package via clone command + must do nothing in case if no branches available """ mocker.patch("pathlib.Path.is_dir", return_value=True) + mocker.patch("ahriman.core.build_tools.sources.Sources.branches", return_value=[]) + check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output") + + Sources.fetch(Path("local"), "remote") + check_output_mock.assert_not_called() + + +def test_fetch_existing(mocker: MockerFixture) -> None: + """ + must fetch new package via fetch command + """ + mocker.patch("pathlib.Path.is_dir", return_value=True) + mocker.patch("ahriman.core.build_tools.sources.Sources.branches", return_value=["master"]) check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output") local = Path("local") - Sources.fetch(local, "remote", "master") + Sources.fetch(local, "remote") check_output_mock.assert_has_calls([ - mock.call("git", "fetch", "origin", "master", exception=None, cwd=local, logger=pytest.helpers.anyvar(int)), - mock.call("git", "checkout", "--force", "master", exception=None, cwd=local, logger=pytest.helpers.anyvar(int)), - mock.call("git", "reset", "--hard", "origin/master", + mock.call("git", "fetch", "origin", Sources._branch, + exception=None, cwd=local, logger=pytest.helpers.anyvar(int)), + mock.call("git", "checkout", "--force", Sources._branch, + exception=None, cwd=local, logger=pytest.helpers.anyvar(int)), + mock.call("git", "reset", "--hard", f"origin/{Sources._branch}", exception=None, cwd=local, logger=pytest.helpers.anyvar(int)) ]) @@ -60,15 +95,28 @@ def test_fetch_new(mocker: MockerFixture) -> None: check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output") local = Path("local") - Sources.fetch(local, "remote", "master") + Sources.fetch(local, "remote") check_output_mock.assert_has_calls([ mock.call("git", "clone", "remote", str(local), exception=None, logger=pytest.helpers.anyvar(int)), - mock.call("git", "checkout", "--force", "master", exception=None, cwd=local, logger=pytest.helpers.anyvar(int)), - mock.call("git", "reset", "--hard", "origin/master", + mock.call("git", "checkout", "--force", Sources._branch, + exception=None, cwd=local, logger=pytest.helpers.anyvar(int)), + mock.call("git", "reset", "--hard", f"origin/{Sources._branch}", exception=None, cwd=local, logger=pytest.helpers.anyvar(int)) ]) +def test_init(mocker: MockerFixture) -> None: + """ + must create empty repository at the specified path + """ + check_output_mock = mocker.patch("ahriman.core.build_tools.sources.Sources._check_output") + + local = Path("local") + Sources.init(local) + check_output_mock.assert_called_with("git", "init", "--initial-branch", Sources._branch, + exception=None, cwd=local, logger=pytest.helpers.anyvar(int)) + + def test_load(mocker: MockerFixture) -> None: """ must load packages sources correctly diff --git a/tests/ahriman/core/repository/test_executor.py b/tests/ahriman/core/repository/test_executor.py index 2062e37a..d8ca4e7c 100644 --- a/tests/ahriman/core/repository/test_executor.py +++ b/tests/ahriman/core/repository/test_executor.py @@ -60,13 +60,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]) + tree_clear_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_clear") repo_remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove") status_client_mock = mocker.patch("ahriman.core.status.client.Client.remove") executor.process_remove([package_ahriman.base]) # must remove via alpm wrapper repo_remove_mock.assert_called_once() - # must update status + # must update status and remove package files + tree_clear_mock.assert_called_with(package_ahriman.base) status_client_mock.assert_called_once() @@ -106,6 +108,15 @@ def test_process_remove_base_single(executor: Executor, package_python_schedule: 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.models.repository_paths.RepositoryPaths.tree_clear", 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 """ diff --git a/tests/ahriman/models/test_package_source.py b/tests/ahriman/models/test_package_source.py index 78068a5d..9bcecb36 100644 --- a/tests/ahriman/models/test_package_source.py +++ b/tests/ahriman/models/test_package_source.py @@ -1,8 +1,21 @@ from pytest_mock import MockerFixture +from pathlib import Path +from typing import Callable from ahriman.models.package_source import PackageSource +def _is_file_mock(is_any_file: bool, is_pkgbuild: bool) -> Callable[[Path], bool]: + """ + helper to mock is_file method + :param is_any_file: value which will be return for any file + :param is_pkgbuild: value which will be return if PKGBUILD like path asked + :return: side effect function for the mocker object + """ + side_effect: Callable[[Path], bool] = lambda source: is_pkgbuild if source.name == "PKGBUILD" else is_any_file + return side_effect + + def test_resolve_non_auto() -> None: """ must resolve non auto type to itself @@ -16,19 +29,10 @@ def test_resolve_archive(mocker: MockerFixture) -> None: must resolve auto type into the archive """ mocker.patch("pathlib.Path.is_dir", return_value=False) - mocker.patch("pathlib.Path.is_file", return_value=True) + mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=_is_file_mock(True, False)) assert PackageSource.Auto.resolve("linux-5.14.2.arch1-2-x86_64.pkg.tar.zst") == PackageSource.Archive -def test_resolve_directory(mocker: MockerFixture) -> None: - """ - must resolve auto type into the directory - """ - mocker.patch("pathlib.Path.is_dir", return_value=True) - mocker.patch("pathlib.Path.is_file", return_value=False) - assert PackageSource.Auto.resolve("path") == PackageSource.Directory - - def test_resolve_aur(mocker: MockerFixture) -> None: """ must resolve auto type into the AUR package @@ -43,5 +47,23 @@ def test_resolve_aur_not_package_like(mocker: MockerFixture) -> None: must resolve auto type into the AUR package if it is file, but does not look like a package archive """ mocker.patch("pathlib.Path.is_dir", return_value=False) - mocker.patch("pathlib.Path.is_file", return_value=True) + mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=_is_file_mock(True, False)) assert PackageSource.Auto.resolve("package") == PackageSource.AUR + + +def test_resolve_directory(mocker: MockerFixture) -> None: + """ + must resolve auto type into the directory + """ + mocker.patch("pathlib.Path.is_dir", return_value=True) + mocker.patch("pathlib.Path.is_file", return_value=False) + assert PackageSource.Auto.resolve("path") == PackageSource.Directory + + +def test_resolve_local(mocker: MockerFixture) -> None: + """ + must resolve auto type into the directory + """ + mocker.patch("pathlib.Path.is_dir", return_value=False) + mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=_is_file_mock(True, True)) + assert PackageSource.Auto.resolve("path") == PackageSource.Local diff --git a/tests/ahriman/models/test_repository_paths.py b/tests/ahriman/models/test_repository_paths.py index bbda6fec..43eb8883 100644 --- a/tests/ahriman/models/test_repository_paths.py +++ b/tests/ahriman/models/test_repository_paths.py @@ -50,6 +50,23 @@ def test_sources_for(repository_paths: RepositoryPaths, package_ahriman: Package assert path.parent == repository_paths.sources +def test_tree_clear(repository_paths: RepositoryPaths, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must remove any package related files + """ + paths = { + getattr(repository_paths, prop)(package_ahriman.base) + for prop in dir(repository_paths) if prop.endswith("_for") + } + rmtree_mock = mocker.patch("shutil.rmtree") + + repository_paths.tree_clear(package_ahriman.base) + rmtree_mock.assert_has_calls( + [ + mock.call(path, ignore_errors=True) for path in paths + ], any_order=True) + + def test_tree_create(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None: """ must create whole tree @@ -68,4 +85,4 @@ def test_tree_create(repository_paths: RepositoryPaths, mocker: MockerFixture) - [ mock.call(mode=0o755, parents=True, exist_ok=True) for _ in paths - ]) + ], any_order=True)