From 83084a318d56c9f9e9c499b6dea9f6e2d70335e0 Mon Sep 17 00:00:00 2001 From: Evgeniy Alekseev Date: Fri, 26 Mar 2021 04:21:19 +0300 Subject: [PATCH] repository component tests --- package/etc/ahriman.ini | 2 - package/etc/ahriman.ini.d/logging.ini | 6 +- setup.cfg | 4 +- setup.py | 3 +- src/ahriman/application/handlers/handler.py | 2 +- src/ahriman/core/build_tools/task.py | 1 + src/ahriman/core/configuration.py | 2 +- src/ahriman/core/report/report.py | 2 +- src/ahriman/core/repository/executor.py | 26 +-- src/ahriman/core/repository/repository.py | 2 +- src/ahriman/core/repository/update_handler.py | 4 +- src/ahriman/core/upload/uploader.py | 2 +- src/ahriman/core/watcher/watcher.py | 4 +- src/ahriman/core/watcher/web_client.py | 24 +-- .../web/middlewares/exception_handler.py | 2 +- src/ahriman/web/web.py | 2 +- tests/ahriman/conftest.py | 33 +++ tests/ahriman/core/build_tools/test_task.py | 6 + tests/ahriman/core/report/test_html.py | 0 tests/ahriman/core/report/test_report.py | 0 tests/ahriman/core/repository/conftest.py | 49 +++++ tests/ahriman/core/repository/test_cleaner.py | 68 +++++++ .../ahriman/core/repository/test_executor.py | 189 ++++++++++++++++++ .../core/repository/test_properties.py | 14 ++ .../core/repository/test_repository.py | 34 ++++ .../core/repository/test_update_handler.py | 124 ++++++++++++ tests/ahriman/models/conftest.py | 33 --- .../ahriman/models/test_package_desciption.py | 0 tests/testresources/core/ahriman.ini | 2 - tests/testresources/core/logging.ini | 6 +- 30 files changed, 565 insertions(+), 81 deletions(-) create mode 100644 tests/ahriman/core/report/test_html.py create mode 100644 tests/ahriman/core/report/test_report.py create mode 100644 tests/ahriman/core/repository/conftest.py create mode 100644 tests/ahriman/core/repository/test_cleaner.py create mode 100644 tests/ahriman/core/repository/test_executor.py create mode 100644 tests/ahriman/core/repository/test_properties.py create mode 100644 tests/ahriman/core/repository/test_repository.py create mode 100644 tests/ahriman/core/repository/test_update_handler.py create mode 100644 tests/ahriman/models/test_package_desciption.py diff --git a/package/etc/ahriman.ini b/package/etc/ahriman.ini index 68f8a965..bb31ce99 100644 --- a/package/etc/ahriman.ini +++ b/package/etc/ahriman.ini @@ -42,6 +42,4 @@ remote = bucket = [web] -host = -port = templates = /usr/share/ahriman \ No newline at end of file diff --git a/package/etc/ahriman.ini.d/logging.ini b/package/etc/ahriman.ini.d/logging.ini index 42c58d0e..fedc4897 100644 --- a/package/etc/ahriman.ini.d/logging.ini +++ b/package/etc/ahriman.ini.d/logging.ini @@ -17,19 +17,19 @@ args = (sys.stderr,) class = logging.handlers.RotatingFileHandler level = DEBUG formatter = generic_format -args = ('/var/log/ahriman/ahriman.log', 'a', 20971520, 20) +args = ("/var/log/ahriman/ahriman.log", "a", 20971520, 20) [handler_build_file_handler] class = logging.handlers.RotatingFileHandler level = DEBUG formatter = generic_format -args = ('/var/log/ahriman/build.log', 'a', 20971520, 20) +args = ("/var/log/ahriman/build.log", "a", 20971520, 20) [handler_http_handler] class = logging.handlers.RotatingFileHandler level = DEBUG formatter = generic_format -args = ('/var/log/ahriman/http.log', 'a', 20971520, 20) +args = ("/var/log/ahriman/http.log", "a", 20971520, 20) [formatter_generic_format] format = [%(levelname)s %(asctime)s] [%(filename)s:%(lineno)d] [%(funcName)s]: %(message)s diff --git a/setup.cfg b/setup.cfg index 78d0270d..f3e013e1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [aliases] -test=pytest +test = pytest [tool:pytest] -addopts = --pspec +addopts = --cov=ahriman --pspec diff --git a/setup.py b/setup.py index abbf7581..7a067673 100644 --- a/setup.py +++ b/setup.py @@ -36,6 +36,7 @@ setup( ], tests_require=[ "pytest", + "pytest-cov", "pytest-helpers-namespace", "pytest-mock", "pytest-pspec", @@ -70,7 +71,7 @@ setup( extras_require={ "html-templates": ["Jinja2"], - "test": ["coverage", "pytest", "pytest-helpers-namespace", "pytest-mock", "pytest-pspec", "pytest-resource-path"], + "test": ["pytest", "pytest-cov", "pytest-helpers-namespace", "pytest-mock", "pytest-pspec", "pytest-resource-path"], "web": ["Jinja2", "aiohttp", "aiohttp_jinja2", "requests"], }, ) diff --git a/src/ahriman/application/handlers/handler.py b/src/ahriman/application/handlers/handler.py index 1650b1be..e91de85e 100644 --- a/src/ahriman/application/handlers/handler.py +++ b/src/ahriman/application/handlers/handler.py @@ -48,7 +48,7 @@ class Handler: cls.run(args, architecture, config) return True except Exception: - logging.getLogger("root").exception("process exception", exc_info=True) + logging.getLogger("root").exception("process exception") return False @classmethod diff --git a/src/ahriman/core/build_tools/task.py b/src/ahriman/core/build_tools/task.py index 92bfbe41..914143e9 100644 --- a/src/ahriman/core/build_tools/task.py +++ b/src/ahriman/core/build_tools/task.py @@ -89,6 +89,7 @@ class Task: else: Task._check_output("git", "clone", remote, str(local), exception=None, logger=logger) # and now force reset to our branch + Task._check_output("git", "checkout", "--force", branch, exception=None, cwd=local, logger=logger) Task._check_output("git", "reset", "--hard", f"origin/{branch}", exception=None, cwd=local, logger=logger) def build(self) -> List[Path]: diff --git a/src/ahriman/core/configuration.py b/src/ahriman/core/configuration.py index e5c6b618..918c51ad 100644 --- a/src/ahriman/core/configuration.py +++ b/src/ahriman/core/configuration.py @@ -140,7 +140,7 @@ class Configuration(configparser.RawConfigParser): fileConfig(self.get("settings", "logging")) except PermissionError: console_logger() - logging.error("could not create logfile, fallback to stderr", exc_info=True) + logging.exception("could not create logfile, fallback to stderr") def console_logger() -> None: logging.basicConfig(filename=None, format=Configuration.DEFAULT_LOG_FORMAT, diff --git a/src/ahriman/core/report/report.py b/src/ahriman/core/report/report.py index e9f1e445..5155fe86 100644 --- a/src/ahriman/core/report/report.py +++ b/src/ahriman/core/report/report.py @@ -64,7 +64,7 @@ class Report: try: report.generate(packages) except Exception: - report.logger.exception("report generation failed", exc_info=True) + report.logger.exception(f"report generation failed for target {provider.name}") raise ReportFailed() def generate(self, packages: Iterable[Package]) -> None: diff --git a/src/ahriman/core/repository/executor.py b/src/ahriman/core/repository/executor.py index 74e275c6..8fb60fd6 100644 --- a/src/ahriman/core/repository/executor.py +++ b/src/ahriman/core/repository/executor.py @@ -56,13 +56,12 @@ class Executor(Cleaner): dst = self.paths.packages / src.name shutil.move(src, dst) - for package in updates: + for single in updates: try: - build_single(package) + build_single(single) except Exception: - self.reporter.set_failed(package.base) - self.logger.exception(f"{package.base} ({self.architecture}) build exception", exc_info=True) - continue + self.reporter.set_failed(single.base) + self.logger.exception(f"{single.base} ({self.architecture}) build exception") self.clear_build() return self.packages_built() @@ -73,11 +72,11 @@ class Executor(Cleaner): :param packages: list of package names or bases to remove :return: path to repository database """ - def remove_single(package: str, filename: Path) -> None: + def remove_single(package: str, fn: Path) -> None: try: - self.repo.remove(package, filename) + self.repo.remove(package, fn) except Exception: - self.logger.exception(f"could not remove {package}", exc_info=True) + self.logger.exception(f"could not remove {package}") requested = set(packages) for local in self.packages(): @@ -142,9 +141,12 @@ class Executor(Cleaner): # we are iterating over bases, not single packages updates: Dict[str, Package] = {} - for fn in packages: - local = Package.load(fn, self.pacman, self.aur_url) - updates.setdefault(local.base, local).packages.update(local.packages) + for filename in packages: + try: + local = Package.load(filename, self.pacman, self.aur_url) + updates.setdefault(local.base, local).packages.update(local.packages) + except Exception: + self.logger.exception(f"could not load package from {filename}") for local in updates.values(): try: @@ -153,7 +155,7 @@ class Executor(Cleaner): self.reporter.set_success(local) except Exception: self.reporter.set_failed(local.base) - self.logger.exception(f"could not process {local.base}", exc_info=True) + self.logger.exception(f"could not process {local.base}") self.clear_packages() return self.repo.repo_path diff --git a/src/ahriman/core/repository/repository.py b/src/ahriman/core/repository/repository.py index 5906ac31..89f6d966 100644 --- a/src/ahriman/core/repository/repository.py +++ b/src/ahriman/core/repository/repository.py @@ -44,7 +44,7 @@ class Repository(Executor, UpdateHandler): local = Package.load(full_path, self.pacman, self.aur_url) result.setdefault(local.base, local).packages.update(local.packages) except Exception: - self.logger.exception(f"could not load package from {full_path}", exc_info=True) + self.logger.exception(f"could not load package from {full_path}") continue return list(result.values()) diff --git a/src/ahriman/core/repository/update_handler.py b/src/ahriman/core/repository/update_handler.py index 4f495ca2..a9b2ea38 100644 --- a/src/ahriman/core/repository/update_handler.py +++ b/src/ahriman/core/repository/update_handler.py @@ -62,7 +62,7 @@ class UpdateHandler(Cleaner): result.append(remote) except Exception: self.reporter.set_failed(local.base) - self.logger.exception(f"could not load remote package {local.base}", exc_info=True) + self.logger.exception(f"could not load remote package {local.base}") continue return result @@ -84,7 +84,7 @@ class UpdateHandler(Cleaner): else: self.reporter.set_pending(local.base) except Exception: - self.logger.exception(f"could not add package from {fn}", exc_info=True) + self.logger.exception(f"could not add package from {fn}") self.clear_manual() return result diff --git a/src/ahriman/core/upload/uploader.py b/src/ahriman/core/upload/uploader.py index f6a3a956..18970f96 100644 --- a/src/ahriman/core/upload/uploader.py +++ b/src/ahriman/core/upload/uploader.py @@ -66,7 +66,7 @@ class Uploader: try: uploader.sync(path) except Exception: - uploader.logger.exception("remote sync failed", exc_info=True) + uploader.logger.exception(f"remote sync failed for {provider.name}") raise SyncFailed() def sync(self, path: Path) -> None: diff --git a/src/ahriman/core/watcher/watcher.py b/src/ahriman/core/watcher/watcher.py index d1ddb749..dc418774 100644 --- a/src/ahriman/core/watcher/watcher.py +++ b/src/ahriman/core/watcher/watcher.py @@ -85,7 +85,7 @@ class Watcher: try: parse_single(item) except Exception: - self.logger.exception(f"cannot parse item f{item} to package", exc_info=True) + self.logger.exception(f"cannot parse item f{item} to package") def _cache_save(self) -> None: """ @@ -103,7 +103,7 @@ class Watcher: with self.cache_path.open("w") as cache: json.dump(dump, cache) except Exception: - self.logger.exception("cannot dump cache", exc_info=True) + self.logger.exception("cannot dump cache") def get(self, base: str) -> Tuple[Package, BuildStatus]: """ diff --git a/src/ahriman/core/watcher/web_client.py b/src/ahriman/core/watcher/web_client.py index a0122af8..d4280464 100644 --- a/src/ahriman/core/watcher/web_client.py +++ b/src/ahriman/core/watcher/web_client.py @@ -75,9 +75,9 @@ class WebClient(Client): response = requests.post(self._package_url(package.base), json=payload) response.raise_for_status() except requests.exceptions.HTTPError as e: - self.logger.exception(f"could not add {package.base}: {e.response.text}", exc_info=True) + self.logger.exception(f"could not add {package.base}: {e.response.text}") except Exception: - self.logger.exception(f"could not add {package.base}", exc_info=True) + self.logger.exception(f"could not add {package.base}") def get(self, base: Optional[str]) -> List[Tuple[Package, BuildStatus]]: """ @@ -95,9 +95,9 @@ class WebClient(Client): for package in status_json ] except requests.exceptions.HTTPError as e: - self.logger.exception(f"could not get {base}: {e.response.text}", exc_info=True) + self.logger.exception(f"could not get {base}: {e.response.text}") except Exception: - self.logger.exception(f"could not get {base}", exc_info=True) + self.logger.exception(f"could not get {base}") return [] def get_self(self) -> BuildStatus: @@ -112,9 +112,9 @@ class WebClient(Client): status_json = response.json() return BuildStatus.from_json(status_json) except requests.exceptions.HTTPError as e: - self.logger.exception(f"could not get service status: {e.response.text}", exc_info=True) + self.logger.exception(f"could not get service status: {e.response.text}") except Exception: - self.logger.exception("could not get service status", exc_info=True) + self.logger.exception("could not get service status") return BuildStatus() def remove(self, base: str) -> None: @@ -126,9 +126,9 @@ class WebClient(Client): response = requests.delete(self._package_url(base)) response.raise_for_status() except requests.exceptions.HTTPError as e: - self.logger.exception(f"could not delete {base}: {e.response.text}", exc_info=True) + self.logger.exception(f"could not delete {base}: {e.response.text}") except Exception: - self.logger.exception(f"could not delete {base}", exc_info=True) + self.logger.exception(f"could not delete {base}") def update(self, base: str, status: BuildStatusEnum) -> None: """ @@ -142,9 +142,9 @@ class WebClient(Client): response = requests.post(self._package_url(base), json=payload) response.raise_for_status() except requests.exceptions.HTTPError as e: - self.logger.exception(f"could not update {base}: {e.response.text}", exc_info=True) + self.logger.exception(f"could not update {base}: {e.response.text}") except Exception: - self.logger.exception(f"could not update {base}", exc_info=True) + self.logger.exception(f"could not update {base}") def update_self(self, status: BuildStatusEnum) -> None: """ @@ -157,6 +157,6 @@ class WebClient(Client): response = requests.post(self._ahriman_url(), json=payload) response.raise_for_status() except requests.exceptions.HTTPError as e: - self.logger.exception(f"could not update service status: {e.response.text}", exc_info=True) + self.logger.exception(f"could not update service status: {e.response.text}") except Exception: - self.logger.exception("could not update service status", exc_info=True) + self.logger.exception("could not update service status") diff --git a/src/ahriman/web/middlewares/exception_handler.py b/src/ahriman/web/middlewares/exception_handler.py index acc9b68d..c4640041 100644 --- a/src/ahriman/web/middlewares/exception_handler.py +++ b/src/ahriman/web/middlewares/exception_handler.py @@ -40,7 +40,7 @@ def exception_handler(logger: Logger) -> Callable[[Request, HandlerType], Awaita except HTTPClientError: raise except Exception: - logger.exception(f"exception during performing request to {request.path}", exc_info=True) + logger.exception(f"exception during performing request to {request.path}") raise return handle diff --git a/src/ahriman/web/web.py b/src/ahriman/web/web.py index 5bc0d061..4e5af577 100644 --- a/src/ahriman/web/web.py +++ b/src/ahriman/web/web.py @@ -47,7 +47,7 @@ async def on_startup(application: web.Application) -> None: try: application["watcher"].load() except Exception: - application.logger.exception("could not load packages", exc_info=True) + application.logger.exception("could not load packages") raise InitializeException() diff --git a/tests/ahriman/conftest.py b/tests/ahriman/conftest.py index 46445117..6b6d23e6 100644 --- a/tests/ahriman/conftest.py +++ b/tests/ahriman/conftest.py @@ -31,6 +31,21 @@ def package_ahriman(package_description_ahriman: PackageDescription) -> Package: packages=packages) +@pytest.fixture +def package_python_schedule( + package_description_python_schedule: PackageDescription, + package_description_python2_schedule: PackageDescription) -> Package: + packages = { + "python-schedule": package_description_python_schedule, + "python2-schedule": package_description_python2_schedule + } + return Package( + base="python-schedule", + version="1.0.0-2", + aur_url="https://aur.archlinux.org", + packages=packages) + + @pytest.fixture def package_description_ahriman() -> PackageDescription: return PackageDescription( @@ -40,6 +55,24 @@ def package_description_ahriman() -> PackageDescription: installed_size=4200000) +@pytest.fixture +def package_description_python_schedule() -> PackageDescription: + return PackageDescription( + archive_size=4201, + build_date=421, + filename="python-schedule-1.0.0-2-any.pkg.tar.zst", + installed_size=4200001) + + +@pytest.fixture +def package_description_python2_schedule() -> PackageDescription: + return PackageDescription( + archive_size=4202, + build_date=422, + filename="python2-schedule-1.0.0-2-any.pkg.tar.zst", + installed_size=4200002) + + @pytest.fixture def repository_paths() -> RepositoryPaths: return RepositoryPaths( diff --git a/tests/ahriman/core/build_tools/test_task.py b/tests/ahriman/core/build_tools/test_task.py index 1a3fbd99..7a142112 100644 --- a/tests/ahriman/core/build_tools/test_task.py +++ b/tests/ahriman/core/build_tools/test_task.py @@ -21,6 +21,9 @@ def test_fetch_existing(mocker: MockerFixture) -> None: mock.call("git", "fetch", "origin", "master", exception=pytest.helpers.anyvar(int), cwd=local, logger=pytest.helpers.anyvar(int)), + mock.call("git", "checkout", "--force", "master", + exception=pytest.helpers.anyvar(int), + cwd=local, logger=pytest.helpers.anyvar(int)), mock.call("git", "reset", "--hard", "origin/master", exception=pytest.helpers.anyvar(int), cwd=local, logger=pytest.helpers.anyvar(int)) @@ -40,6 +43,9 @@ def test_fetch_new(mocker: MockerFixture) -> None: mock.call("git", "clone", "remote", str(local), exception=pytest.helpers.anyvar(int), logger=pytest.helpers.anyvar(int)), + mock.call("git", "checkout", "--force", "master", + exception=pytest.helpers.anyvar(int), + cwd=local, logger=pytest.helpers.anyvar(int)), mock.call("git", "reset", "--hard", "origin/master", exception=pytest.helpers.anyvar(int), cwd=local, logger=pytest.helpers.anyvar(int)) diff --git a/tests/ahriman/core/report/test_html.py b/tests/ahriman/core/report/test_html.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/ahriman/core/report/test_report.py b/tests/ahriman/core/report/test_report.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/ahriman/core/repository/conftest.py b/tests/ahriman/core/repository/conftest.py new file mode 100644 index 00000000..4a8e8e22 --- /dev/null +++ b/tests/ahriman/core/repository/conftest.py @@ -0,0 +1,49 @@ +import pytest + +from pytest_mock import MockerFixture + +from ahriman.core.configuration import Configuration +from ahriman.core.repository.cleaner import Cleaner +from ahriman.core.repository.executor import Executor +from ahriman.core.repository.properties import Properties +from ahriman.core.repository.repository import Repository +from ahriman.core.repository.update_handler import UpdateHandler + + +@pytest.fixture +def cleaner(configuration: Configuration, mocker: MockerFixture) -> Cleaner: + mocker.patch("pathlib.Path.mkdir") + return Cleaner("x86_64", configuration) + + +@pytest.fixture +def executor(configuration: Configuration, mocker: MockerFixture) -> Executor: + mocker.patch("pathlib.Path.mkdir") + mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_build") + mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_cache") + mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_chroot") + mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_manual") + mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_packages") + return Executor("x86_64", configuration) + + +@pytest.fixture +def repository(configuration: Configuration, mocker: MockerFixture) -> Repository: + mocker.patch("pathlib.Path.mkdir") + return Repository("x86_64", configuration) + + +@pytest.fixture +def properties(configuration: Configuration) -> Properties: + return Properties("x86_64", configuration) + + +@pytest.fixture +def update_handler(configuration: Configuration, mocker: MockerFixture) -> UpdateHandler: + mocker.patch("pathlib.Path.mkdir") + mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_build") + mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_cache") + mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_chroot") + mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_manual") + mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_packages") + return UpdateHandler("x86_64", configuration) diff --git a/tests/ahriman/core/repository/test_cleaner.py b/tests/ahriman/core/repository/test_cleaner.py new file mode 100644 index 00000000..052644b7 --- /dev/null +++ b/tests/ahriman/core/repository/test_cleaner.py @@ -0,0 +1,68 @@ +import shutil + +from pathlib import Path +from pytest_mock import MockerFixture +from unittest import mock + +from ahriman.core.repository.cleaner import Cleaner + + +def _mock_clear(mocker: MockerFixture) -> None: + mocker.patch("pathlib.Path.iterdir", return_value=[Path("a"), Path("b"), Path("c")]) + mocker.patch("shutil.rmtree") + + +def _mock_clear_check() -> None: + shutil.rmtree.assert_has_calls([ + mock.call(Path("a")), + mock.call(Path("b")), + mock.call(Path("c")) + ]) + + +def test_clear_build(cleaner: Cleaner, mocker: MockerFixture) -> None: + """ + must remove directories with sources + """ + _mock_clear(mocker) + cleaner.clear_build() + _mock_clear_check() + + +def test_clear_cache(cleaner: Cleaner, mocker: MockerFixture) -> None: + """ + must remove every cached sources + """ + _mock_clear(mocker) + cleaner.clear_cache() + _mock_clear_check() + + +def test_clear_chroot(cleaner: Cleaner, mocker: MockerFixture) -> None: + """ + must clear chroot + """ + _mock_clear(mocker) + cleaner.clear_chroot() + _mock_clear_check() + + +def test_clear_manual(cleaner: Cleaner, mocker: MockerFixture) -> None: + """ + must clear directory with manual packages + """ + _mock_clear(mocker) + cleaner.clear_manual() + _mock_clear_check() + + +def test_clear_packages(cleaner: Cleaner, mocker: MockerFixture) -> None: + """ + must delete built packages + """ + mocker.patch("ahriman.core.repository.cleaner.Cleaner.packages_built", + return_value=[Path("a"), Path("b"), Path("c")]) + mocker.patch("pathlib.Path.unlink") + + cleaner.clear_packages() + Path.unlink.assert_has_calls([mock.call(), mock.call(), mock.call()]) diff --git a/tests/ahriman/core/repository/test_executor.py b/tests/ahriman/core/repository/test_executor.py new file mode 100644 index 00000000..1f2bd79f --- /dev/null +++ b/tests/ahriman/core/repository/test_executor.py @@ -0,0 +1,189 @@ +from pathlib import Path +from unittest import mock + +from pytest_mock import MockerFixture + +from ahriman.core.repository.executor import Executor +from ahriman.models.package import Package + + +def test_process_build(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must run build process + """ + mocker.patch("ahriman.core.repository.executor.Executor.packages_built", return_value=[package_ahriman]) + mocker.patch("ahriman.core.build_tools.task.Task.build", return_value=[Path(package_ahriman.base)]) + mocker.patch("ahriman.core.build_tools.task.Task.init") + move_mock = mocker.patch("shutil.move") + watcher_client_mock = mocker.patch("ahriman.core.watcher.client.Client.set_building") + + # must return list of built packages + assert executor.process_build([package_ahriman]) == [package_ahriman] + # must move files (once) + move_mock.assert_called_once() + # must update status + watcher_client_mock.assert_called_once() + # must clear directory + from ahriman.core.repository.cleaner import Cleaner + Cleaner.clear_build.assert_called_once() + + +def test_process_build_failure(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must run correct process failed builds + """ + 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()) + watcher_client_mock = mocker.patch("ahriman.core.watcher.client.Client.set_failed") + + executor.process_build([package_ahriman]) + watcher_client_mock.assert_called_once() + + +def test_process_remove_base(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + 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") + watcher_client_mock = mocker.patch("ahriman.core.watcher.client.Client.remove") + + executor.process_remove([package_ahriman.base]) + # must remove via alpm wrapper + repo_remove_mock.assert_called_once() + # must update status + watcher_client_mock.assert_called_once() + + +def test_process_remove_base_multiple(executor: Executor, package_python_schedule: Package, + mocker: MockerFixture) -> None: + """ + 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") + watcher_client_mock = mocker.patch("ahriman.core.watcher.client.Client.remove") + + executor.process_remove([package_python_schedule.base]) + # must remove via alpm wrapper + repo_remove_mock.assert_has_calls([ + mock.call(package, Path(props.filename)) + for package, props in package_python_schedule.packages.items() + ], any_order=True) + # must update status + watcher_client_mock.assert_called_once() + + +def test_process_remove_base_single(executor: Executor, package_python_schedule: Package, + mocker: MockerFixture) -> None: + """ + 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") + watcher_client_mock = mocker.patch("ahriman.core.watcher.client.Client.remove") + + executor.process_remove(["python2-schedule"]) + # must remove via alpm wrapper + repo_remove_mock.assert_called_once() + # must not update status + watcher_client_mock.assert_not_called() + + +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") + + executor.process_remove([package_python_schedule.base]) + repo_remove_mock.assert_not_called() + + +def test_process_report_auto(executor: Executor, mocker: MockerFixture) -> None: + """ + must process report in auto mode if no targets supplied + """ + config_getlist_mock = mocker.patch("ahriman.core.configuration.Configuration.getlist") + + executor.process_report(None) + config_getlist_mock.assert_called_once() + + +def test_process_sync_auto(executor: Executor, mocker: MockerFixture) -> None: + """ + must process sync in auto mode if no targets supplied + """ + config_getlist_mock = mocker.patch("ahriman.core.configuration.Configuration.getlist") + + executor.process_sync(None) + config_getlist_mock.assert_called_once() + + +def test_process_update(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must run update process + """ + mocker.patch("ahriman.models.package.Package.load", 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.sign_package", side_effect=lambda fn, _: [fn]) + watcher_client_mock = mocker.patch("ahriman.core.watcher.client.Client.set_success") + + # must return complete + assert executor.process_update([Path(package.filename) for package in package_ahriman.packages.values()]) + # must move files (once) + move_mock.assert_called_once() + # must sign package + sign_package_mock.assert_called_once() + # must add package + repo_add_mock.assert_called_once() + # must update status + watcher_client_mock.assert_called_once() + # must clear directory + from ahriman.core.repository.cleaner import Cleaner + Cleaner.clear_packages.assert_called_once() + + +def test_process_update_group(executor: Executor, package_python_schedule: Package, + mocker: MockerFixture) -> None: + """ + must group single packages under one base + """ + mocker.patch("shutil.move") + mocker.patch("ahriman.models.package.Package.load", return_value=package_python_schedule) + repo_add_mock = mocker.patch("ahriman.core.alpm.repo.Repo.add") + watcher_client_mock = mocker.patch("ahriman.core.watcher.client.Client.set_success") + + executor.process_update([Path(package.filename) for package in package_python_schedule.packages.values()]) + repo_add_mock.assert_has_calls([ + mock.call(executor.paths.repository / package.filename) + for package in package_python_schedule.packages.values() + ], any_order=True) + watcher_client_mock.assert_called_with(package_python_schedule) + + +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.models.package.Package.load", return_value=package_ahriman) + watcher_client_mock = mocker.patch("ahriman.core.watcher.client.Client.set_failed") + + executor.process_update([Path(package.filename) for package in package_ahriman.packages.values()]) + watcher_client_mock.assert_called_once() + + +def test_process_update_failed_on_load(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must process update even with failed package load + """ + mocker.patch("shutil.move") + mocker.patch("ahriman.models.package.Package.load", side_effect=Exception()) + + assert executor.process_update([Path(package.filename) for package in package_ahriman.packages.values()]) diff --git a/tests/ahriman/core/repository/test_properties.py b/tests/ahriman/core/repository/test_properties.py new file mode 100644 index 00000000..760176c1 --- /dev/null +++ b/tests/ahriman/core/repository/test_properties.py @@ -0,0 +1,14 @@ +from pytest_mock import MockerFixture + +from ahriman.core.configuration import Configuration +from ahriman.core.repository.properties import Properties + + +def test_create_tree_on_load(configuration: Configuration, mocker: MockerFixture) -> None: + """ + must create tree on load + """ + create_tree_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.create_tree") + Properties("x86_64", configuration) + + create_tree_mock.assert_called_once() diff --git a/tests/ahriman/core/repository/test_repository.py b/tests/ahriman/core/repository/test_repository.py new file mode 100644 index 00000000..8f9a74d2 --- /dev/null +++ b/tests/ahriman/core/repository/test_repository.py @@ -0,0 +1,34 @@ +from pathlib import Path + +from pytest_mock import MockerFixture + +from ahriman.core.repository.repository import Repository +from ahriman.models.package import Package + + +def test_packages(package_ahriman: Package, package_python_schedule: Package, + repository: Repository, mocker: MockerFixture) -> None: + """ + must return all packages grouped by package base + """ + single_packages = [ + Package(base=package_python_schedule.base, + version=package_python_schedule.version, + aur_url=package_python_schedule.aur_url, + packages={package: props}) + for package, props in package_python_schedule.packages.items() + ] + [package_ahriman] + + mocker.patch("pathlib.Path.iterdir", + return_value=[Path("a.pkg.tar.xz"), Path("b.pkg.tar.xz"), Path("c.pkg.tar.xz")]) + mocker.patch("ahriman.models.package.Package.load", side_effect=single_packages) + + packages = repository.packages() + assert len(packages) == 2 + assert {package.base for package in packages} == {package_ahriman.base, package_python_schedule.base} + + archives = sum([list(package.packages.keys()) for package in packages], start=[]) + assert len(archives) == 3 + expected = set(package_ahriman.packages.keys()) + expected.update(package_python_schedule.packages.keys()) + assert set(archives) == expected diff --git a/tests/ahriman/core/repository/test_update_handler.py b/tests/ahriman/core/repository/test_update_handler.py new file mode 100644 index 00000000..6b959d86 --- /dev/null +++ b/tests/ahriman/core/repository/test_update_handler.py @@ -0,0 +1,124 @@ +from pytest_mock import MockerFixture + +from ahriman.core.repository.update_handler import UpdateHandler +from ahriman.models.package import Package + + +def test_updates_aur(update_handler: UpdateHandler, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must provide updates with status watcher updates + """ + mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman]) + mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True) + mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman) + watcher_client_mock = mocker.patch("ahriman.core.watcher.client.Client.set_pending") + + assert update_handler.updates_aur([], False) == [package_ahriman] + watcher_client_mock.assert_called_once() + + +def test_updates_aur_failed(update_handler: UpdateHandler, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must update status watcher via client for failed load + """ + mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman]) + mocker.patch("ahriman.models.package.Package.load", side_effect=Exception()) + watcher_client_mock = mocker.patch("ahriman.core.watcher.client.Client.set_failed") + + update_handler.updates_aur([], False) + watcher_client_mock.assert_called_once() + + +def test_updates_aur_filter(update_handler: UpdateHandler, package_ahriman: Package, package_python_schedule: Package, + mocker: MockerFixture) -> None: + """ + must provide updates only for filtered packages + """ + mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", + return_value=[package_ahriman, package_python_schedule]) + mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True) + package_load_mock = mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman) + + assert update_handler.updates_aur([package_ahriman.base], False) == [package_ahriman] + package_load_mock.assert_called_once() + + +def test_updates_aur_ignore(update_handler: UpdateHandler, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must skip ignore packages + """ + mocker.patch("ahriman.core.configuration.Configuration.getlist", return_value=[package_ahriman.base]) + mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman]) + package_load_mock = mocker.patch("ahriman.models.package.Package.load") + + update_handler.updates_aur([], False) + package_load_mock.assert_not_called() + + +def test_updates_aur_ignore_vcs(update_handler: UpdateHandler, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must skip VCS packages check if requested + """ + mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman]) + mocker.patch("ahriman.models.package.Package.is_vcs", return_value=True) + package_is_outdated_mock = mocker.patch("ahriman.models.package.Package.is_outdated") + + update_handler.updates_aur([], True) + package_is_outdated_mock.assert_not_called() + + +def test_updates_manual_clear(update_handler: UpdateHandler, mocker: MockerFixture) -> None: + """ + requesting manual updates must clear packages directory + """ + mocker.patch("pathlib.Path.iterdir", return_value=[]) + mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages") + + update_handler.updates_manual() + + from ahriman.core.repository.cleaner import Cleaner + Cleaner.clear_manual.assert_called_once() + + +def test_updates_manual_status_known(update_handler: UpdateHandler, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must create record for known package via reporter + """ + mocker.patch("pathlib.Path.iterdir", return_value=[package_ahriman.base]) + mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman]) + mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman) + watcher_client_mock = mocker.patch("ahriman.core.watcher.client.Client.set_pending") + + update_handler.updates_manual() + watcher_client_mock.assert_called_once() + + +def test_updates_manual_status_unknown(update_handler: UpdateHandler, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must create record for unknown package via reporter + """ + mocker.patch("pathlib.Path.iterdir", return_value=[package_ahriman.base]) + mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[]) + mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman) + watcher_client_mock = mocker.patch("ahriman.core.watcher.client.Client.set_unknown") + + update_handler.updates_manual() + watcher_client_mock.assert_called_once() + + +def test_updates_manual_with_failures(update_handler: UpdateHandler, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must process through the packages with failure + """ + mocker.patch("pathlib.Path.iterdir", return_value=[package_ahriman.base]) + mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[]) + mocker.patch("ahriman.models.package.Package.load", side_effect=Exception()) + + assert update_handler.updates_manual() == [] diff --git a/tests/ahriman/models/conftest.py b/tests/ahriman/models/conftest.py index 0b5471c2..cef09533 100644 --- a/tests/ahriman/models/conftest.py +++ b/tests/ahriman/models/conftest.py @@ -10,21 +10,6 @@ def build_status_failed() -> BuildStatus: return BuildStatus(BuildStatusEnum.Failed, 42) -@pytest.fixture -def package_python_schedule( - package_description_python_schedule: PackageDescription, - package_description_python2_schedule: PackageDescription) -> Package: - packages = { - "python-schedule": package_description_python_schedule, - "python2-schedule": package_description_python2_schedule - } - return Package( - base="python-schedule", - version="1.0.0-2", - aur_url="https://aur.archlinux.org", - packages=packages) - - @pytest.fixture def package_tpacpi_bat_git() -> Package: return Package( @@ -32,21 +17,3 @@ def package_tpacpi_bat_git() -> Package: version="3.1.r12.g4959b52-1", aur_url="https://aur.archlinux.org", packages={"tpacpi-bat-git": PackageDescription()}) - - -@pytest.fixture -def package_description_python_schedule() -> PackageDescription: - return PackageDescription( - archive_size=4201, - build_date=421, - filename="python-schedule-1.0.0-2-any.pkg.tar.zst", - installed_size=4200001) - - -@pytest.fixture -def package_description_python2_schedule() -> PackageDescription: - return PackageDescription( - archive_size=4202, - build_date=422, - filename="python2-schedule-1.0.0-2-any.pkg.tar.zst", - installed_size=4200002) diff --git a/tests/ahriman/models/test_package_desciption.py b/tests/ahriman/models/test_package_desciption.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/testresources/core/ahriman.ini b/tests/testresources/core/ahriman.ini index 649bb56b..190835df 100644 --- a/tests/testresources/core/ahriman.ini +++ b/tests/testresources/core/ahriman.ini @@ -41,6 +41,4 @@ remote = bucket = [web] -host = -port = templates = /usr/share/ahriman \ No newline at end of file diff --git a/tests/testresources/core/logging.ini b/tests/testresources/core/logging.ini index 42c58d0e..fedc4897 100644 --- a/tests/testresources/core/logging.ini +++ b/tests/testresources/core/logging.ini @@ -17,19 +17,19 @@ args = (sys.stderr,) class = logging.handlers.RotatingFileHandler level = DEBUG formatter = generic_format -args = ('/var/log/ahriman/ahriman.log', 'a', 20971520, 20) +args = ("/var/log/ahriman/ahriman.log", "a", 20971520, 20) [handler_build_file_handler] class = logging.handlers.RotatingFileHandler level = DEBUG formatter = generic_format -args = ('/var/log/ahriman/build.log', 'a', 20971520, 20) +args = ("/var/log/ahriman/build.log", "a", 20971520, 20) [handler_http_handler] class = logging.handlers.RotatingFileHandler level = DEBUG formatter = generic_format -args = ('/var/log/ahriman/http.log', 'a', 20971520, 20) +args = ("/var/log/ahriman/http.log", "a", 20971520, 20) [formatter_generic_format] format = [%(levelname)s %(asctime)s] [%(filename)s:%(lineno)d] [%(funcName)s]: %(message)s