From 57f25c309a50494cce589d4c4578c9a35f0239b3 Mon Sep 17 00:00:00 2001 From: Evgeniy Alekseev Date: Sat, 19 Mar 2022 23:48:43 +0300 Subject: [PATCH] add console printer also add python-requests as explicit dependency and escape symbols in repository name for badges in default tempate --- Dockerfile | 2 +- package/archlinux/PKGBUILD | 2 +- package/etc/ahriman.ini | 5 +- package/share/ahriman/build-status.jinja2 | 2 +- .../application/application/application.py | 11 +- .../application/application/packages.py | 6 +- .../application/application/repository.py | 32 +++-- src/ahriman/application/handlers/dump.py | 2 +- src/ahriman/application/handlers/rebuild.py | 2 +- .../application/handlers/remove_unknown.py | 2 +- src/ahriman/application/handlers/report.py | 3 +- src/ahriman/application/handlers/search.py | 2 +- src/ahriman/application/handlers/status.py | 4 +- .../application/handlers/unsafe_commands.py | 2 +- src/ahriman/core/exceptions.py | 13 ++ .../formatters/__init__.py | 0 .../formatters/aur_printer.py | 33 +++-- src/ahriman/core/formatters/build_printer.py | 48 +++++++ .../formatters/configuration_printer.py | 20 ++- .../formatters/package_printer.py | 26 ++-- .../formatters/printer.py | 0 .../formatters/status_printer.py | 15 +-- .../formatters/string_printer.py | 2 +- .../formatters/update_printer.py | 18 ++- src/ahriman/core/report/console.py | 54 ++++++++ src/ahriman/core/report/email.py | 13 +- src/ahriman/core/report/html.py | 8 +- src/ahriman/core/report/jinja_template.py | 10 +- src/ahriman/core/report/report.py | 14 ++- src/ahriman/core/repository/executor.py | 21 ++-- src/ahriman/models/report_settings.py | 4 + src/ahriman/models/result.py | 105 ++++++++++++++++ .../application/test_application.py | 5 +- .../application/test_application_packages.py | 3 +- .../test_application_repository.py | 35 ++++-- .../application/handlers/test_handler_dump.py | 2 +- .../handlers/test_handler_remove_unknown.py | 4 +- .../handlers/test_handler_report.py | 3 +- .../handlers/test_handler_search.py | 2 +- .../handlers/test_handler_status.py | 6 +- .../handlers/test_handler_unsafe_commands.py | 2 +- tests/ahriman/conftest.py | 13 ++ .../formatters/conftest.py | 12 +- .../formatters/test_aur_printer.py | 2 +- .../core/formatters/test_build_printer.py | 36 ++++++ .../formatters/test_configuration_printer.py | 2 +- .../formatters/test_package_printer.py | 2 +- .../formatters/test_printer.py | 4 +- .../formatters/test_status_printer.py | 2 +- .../formatters/test_string_printer.py | 2 +- .../formatters/test_update_printer.py | 2 +- tests/ahriman/core/report/test_console.py | 20 +++ tests/ahriman/core/report/test_email.py | 17 +-- .../core/report/test_jinja_template.py | 3 +- tests/ahriman/core/report/test_report.py | 30 +++-- .../ahriman/core/repository/test_executor.py | 3 - tests/ahriman/models/test_report_settings.py | 3 + tests/ahriman/models/test_result.py | 119 ++++++++++++++++++ tests/testresources/core/ahriman.ini | 3 + 59 files changed, 631 insertions(+), 187 deletions(-) rename src/ahriman/{application => core}/formatters/__init__.py (100%) rename src/ahriman/{application => core}/formatters/aur_printer.py (57%) create mode 100644 src/ahriman/core/formatters/build_printer.py rename src/ahriman/{application => core}/formatters/configuration_printer.py (73%) rename src/ahriman/{application => core}/formatters/package_printer.py (69%) rename src/ahriman/{application => core}/formatters/printer.py (100%) rename src/ahriman/{application => core}/formatters/status_printer.py (73%) rename src/ahriman/{application => core}/formatters/string_printer.py (95%) rename src/ahriman/{application => core}/formatters/update_printer.py (78%) create mode 100644 src/ahriman/core/report/console.py create mode 100644 src/ahriman/models/result.py rename tests/ahriman/{application => core}/formatters/conftest.py (78%) rename tests/ahriman/{application => core}/formatters/test_aur_printer.py (84%) create mode 100644 tests/ahriman/core/formatters/test_build_printer.py rename tests/ahriman/{application => core}/formatters/test_configuration_printer.py (87%) rename tests/ahriman/{application => core}/formatters/test_package_printer.py (82%) rename tests/ahriman/{application => core}/formatters/test_printer.py (87%) rename tests/ahriman/{application => core}/formatters/test_status_printer.py (81%) rename tests/ahriman/{application => core}/formatters/test_string_printer.py (81%) rename tests/ahriman/{application => core}/formatters/test_update_printer.py (80%) create mode 100644 tests/ahriman/core/report/test_console.py create mode 100644 tests/ahriman/models/test_result.py diff --git a/Dockerfile b/Dockerfile index 50d5d5c9..7bf68346 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,7 +24,7 @@ RUN YAY_DIR="$(runuser -u build -- mktemp -d)" && \ runuser -u build -- makepkg --noconfirm --install && \ cd - && rm -r "$YAY_DIR" ## install package dependencies -RUN runuser -u build -- yay --noconfirm -Sy devtools git pyalpm python-inflection python-passlib python-srcinfo && \ +RUN runuser -u build -- yay --noconfirm -Sy devtools git pyalpm python-inflection python-passlib python-requests python-srcinfo && \ runuser -u build -- yay --noconfirm -Sy python-pip && \ runuser -u build -- yay --noconfirm -Sy breezy darcs mercurial python-aioauth-client python-aiohttp \ python-aiohttp-debugtoolbar python-aiohttp-jinja2 python-aiohttp-security \ diff --git a/package/archlinux/PKGBUILD b/package/archlinux/PKGBUILD index e8a7d8b3..64aa8934 100644 --- a/package/archlinux/PKGBUILD +++ b/package/archlinux/PKGBUILD @@ -7,7 +7,7 @@ pkgdesc="ArcH Linux ReposItory MANager" arch=('any') url="https://github.com/arcan1s/ahriman" license=('GPL3') -depends=('devtools' 'git' 'pyalpm' 'python-inflection' 'python-passlib' 'python-srcinfo') +depends=('devtools' 'git' 'pyalpm' 'python-inflection' 'python-passlib' 'python-requests' 'python-srcinfo') makedepends=('python-pip') optdepends=('breezy: -bzr packages support' 'darcs: -darcs packages support' diff --git a/package/etc/ahriman.ini b/package/etc/ahriman.ini index cb143ba6..0c1caa34 100644 --- a/package/etc/ahriman.ini +++ b/package/etc/ahriman.ini @@ -30,7 +30,10 @@ root = /var/lib/ahriman target = [report] -target = +target = console + +[console] +use_utf = yes [email] full_template_path = /usr/share/ahriman/repo-index.jinja2 diff --git a/package/share/ahriman/build-status.jinja2 b/package/share/ahriman/build-status.jinja2 index 8df0a598..6aa15815 100644 --- a/package/share/ahriman/build-status.jinja2 +++ b/package/share/ahriman/build-status.jinja2 @@ -16,7 +16,7 @@

ahriman {% if auth.authenticated %} {{ version }} - {{ repository }} + {{ repository }} {{ architecture }} {{ service.status }} {% endif %} diff --git a/src/ahriman/application/application/application.py b/src/ahriman/application/application/application.py index 9680c9a2..72286b92 100644 --- a/src/ahriman/application/application/application.py +++ b/src/ahriman/application/application/application.py @@ -17,11 +17,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from typing import Iterable, Set +from typing import Set from ahriman.application.application.packages import Packages from ahriman.application.application.repository import Repository -from ahriman.models.package import Package +from ahriman.models.result import Result class Application(Packages, Repository): @@ -29,12 +29,13 @@ class Application(Packages, Repository): base application class """ - def _finalize(self, built_packages: Iterable[Package]) -> None: + def _finalize(self, result: Result) -> None: """ generate report and sync to remote server + :param result: build result """ - self.report([], built_packages) - self.sync([], built_packages) + self.report([], result) + self.sync([], result.success) def _known_packages(self) -> Set[str]: """ diff --git a/src/ahriman/application/application/packages.py b/src/ahriman/application/application/packages.py index 66eb4070..852b4fbe 100644 --- a/src/ahriman/application/application/packages.py +++ b/src/ahriman/application/application/packages.py @@ -28,6 +28,7 @@ from ahriman.core.build_tools.sources import Sources from ahriman.core.util import package_like from ahriman.models.package import Package from ahriman.models.package_source import PackageSource +from ahriman.models.result import Result class Packages(Properties): @@ -35,9 +36,10 @@ class Packages(Properties): package control class """ - def _finalize(self, built_packages: Iterable[Package]) -> None: + def _finalize(self, result: Result) -> None: """ generate report and sync to remote server + :param result: build result """ raise NotImplementedError @@ -141,4 +143,4 @@ class Packages(Properties): :param names: list of packages (either base or name) to remove """ self.repository.process_remove(names) - self._finalize([]) + self._finalize(Result()) diff --git a/src/ahriman/application/application/repository.py b/src/ahriman/application/application/repository.py index 7607ce51..bfa2b291 100644 --- a/src/ahriman/application/application/repository.py +++ b/src/ahriman/application/application/repository.py @@ -23,11 +23,11 @@ from pathlib import Path from typing import Callable, Iterable, List from ahriman.application.application.properties import Properties -from ahriman.application.formatters.update_printer import UpdatePrinter from ahriman.core.build_tools.sources import Sources +from ahriman.core.formatters.update_printer import UpdatePrinter from ahriman.core.tree import Tree from ahriman.models.package import Package -from ahriman.models.package_source import PackageSource +from ahriman.models.result import Result class Repository(Properties): @@ -35,9 +35,10 @@ class Repository(Properties): repository control class """ - def _finalize(self, built_packages: Iterable[Package]) -> None: + def _finalize(self, result: Result) -> None: """ generate report and sync to remote server + :param result: build result """ raise NotImplementedError @@ -64,14 +65,14 @@ class Repository(Properties): if patches: self.repository.clear_patches() - def report(self, target: Iterable[str], built_packages: Iterable[Package]) -> None: + def report(self, target: Iterable[str], result: Result) -> None: """ generate report :param target: list of targets to run (e.g. html) - :param built_packages: list of packages which has just been built + :param result: build result """ targets = target or None - self.repository.process_report(targets, built_packages) + self.repository.process_report(targets, result) def sign(self, packages: Iterable[str]) -> None: """ @@ -94,7 +95,7 @@ class Repository(Properties): self.update([]) # sign repository database if set self.repository.sign.process_sign_repository(self.repository.repo.repo_path) - self._finalize([]) + self._finalize(Result()) def sync(self, target: Iterable[str], built_packages: Iterable[Package]) -> None: """ @@ -142,26 +143,23 @@ class Repository(Properties): run package updates :param updates: list of packages to update """ - def process_update(paths: Iterable[Path]) -> None: + def process_update(paths: Iterable[Path], result: Result) -> None: if not paths: return # don't need to process if no update supplied - updated = [ - Package.load(str(path), PackageSource.Archive, self.repository.pacman, self.repository.aur_url) - for path in paths - ] - self.repository.process_update(paths) - self._finalize(updated) + update_result = self.repository.process_update(paths) + self._finalize(result.merge(update_result)) # process built packages packages = self.repository.packages_built() - process_update(packages) + process_update(packages, Result()) # process manual packages tree = Tree.load(updates, self.repository.paths) for num, level in enumerate(tree.levels()): self.logger.info("processing level #%i %s", num, [package.base for package in level]) - packages = self.repository.process_build(level) - process_update(packages) + build_result = self.repository.process_build(level) + packages = self.repository.packages_built() + process_update(packages, build_result) def updates(self, filter_packages: Iterable[str], no_aur: bool, no_local: bool, no_manual: bool, no_vcs: bool, log_fn: Callable[[str], None]) -> List[Package]: diff --git a/src/ahriman/application/handlers/dump.py b/src/ahriman/application/handlers/dump.py index fd4f4b91..db7f6702 100644 --- a/src/ahriman/application/handlers/dump.py +++ b/src/ahriman/application/handlers/dump.py @@ -21,9 +21,9 @@ import argparse from typing import Type -from ahriman.application.formatters.configuration_printer import ConfigurationPrinter from ahriman.application.handlers.handler import Handler from ahriman.core.configuration import Configuration +from ahriman.core.formatters.configuration_printer import ConfigurationPrinter class Dump(Handler): diff --git a/src/ahriman/application/handlers/rebuild.py b/src/ahriman/application/handlers/rebuild.py index 09f9e9ac..1b28dfa8 100644 --- a/src/ahriman/application/handlers/rebuild.py +++ b/src/ahriman/application/handlers/rebuild.py @@ -22,9 +22,9 @@ import argparse from typing import Type from ahriman.application.application import Application -from ahriman.application.formatters.update_printer import UpdatePrinter from ahriman.application.handlers.handler import Handler from ahriman.core.configuration import Configuration +from ahriman.core.formatters.update_printer import UpdatePrinter class Rebuild(Handler): diff --git a/src/ahriman/application/handlers/remove_unknown.py b/src/ahriman/application/handlers/remove_unknown.py index 4912e17e..07ae70ad 100644 --- a/src/ahriman/application/handlers/remove_unknown.py +++ b/src/ahriman/application/handlers/remove_unknown.py @@ -22,9 +22,9 @@ import argparse from typing import Type from ahriman.application.application import Application -from ahriman.application.formatters.string_printer import StringPrinter from ahriman.application.handlers.handler import Handler from ahriman.core.configuration import Configuration +from ahriman.core.formatters.string_printer import StringPrinter class RemoveUnknown(Handler): diff --git a/src/ahriman/application/handlers/report.py b/src/ahriman/application/handlers/report.py index 9c10c3b8..46b6b4ab 100644 --- a/src/ahriman/application/handlers/report.py +++ b/src/ahriman/application/handlers/report.py @@ -24,6 +24,7 @@ from typing import Type from ahriman.application.application import Application from ahriman.application.handlers.handler import Handler from ahriman.core.configuration import Configuration +from ahriman.models.result import Result class Report(Handler): @@ -42,4 +43,4 @@ class Report(Handler): :param no_report: force disable reporting :param unsafe: if set no user check will be performed before path creation """ - Application(architecture, configuration, no_report, unsafe).report(args.target, []) + Application(architecture, configuration, no_report, unsafe).report(args.target, Result()) diff --git a/src/ahriman/application/handlers/search.py b/src/ahriman/application/handlers/search.py index 0bd22c94..5f13c6df 100644 --- a/src/ahriman/application/handlers/search.py +++ b/src/ahriman/application/handlers/search.py @@ -22,11 +22,11 @@ import argparse from dataclasses import fields from typing import Callable, Iterable, List, Tuple, Type -from ahriman.application.formatters.aur_printer import AurPrinter from ahriman.application.handlers.handler import Handler from ahriman.core.alpm.aur import AUR from ahriman.core.configuration import Configuration from ahriman.core.exceptions import InvalidOption +from ahriman.core.formatters.aur_printer import AurPrinter from ahriman.models.aur_package import AURPackage diff --git a/src/ahriman/application/handlers/status.py b/src/ahriman/application/handlers/status.py index 60a29643..f220f9c7 100644 --- a/src/ahriman/application/handlers/status.py +++ b/src/ahriman/application/handlers/status.py @@ -22,10 +22,10 @@ import argparse from typing import Callable, Iterable, Tuple, Type from ahriman.application.application import Application -from ahriman.application.formatters.package_printer import PackagePrinter -from ahriman.application.formatters.status_printer import StatusPrinter from ahriman.application.handlers.handler import Handler from ahriman.core.configuration import Configuration +from ahriman.core.formatters.package_printer import PackagePrinter +from ahriman.core.formatters.status_printer import StatusPrinter from ahriman.models.build_status import BuildStatus from ahriman.models.package import Package diff --git a/src/ahriman/application/handlers/unsafe_commands.py b/src/ahriman/application/handlers/unsafe_commands.py index 8be04341..3f874cb1 100644 --- a/src/ahriman/application/handlers/unsafe_commands.py +++ b/src/ahriman/application/handlers/unsafe_commands.py @@ -21,9 +21,9 @@ import argparse from typing import List, Type -from ahriman.application.formatters.string_printer import StringPrinter from ahriman.application.handlers.handler import Handler from ahriman.core.configuration import Configuration +from ahriman.core.formatters.string_printer import StringPrinter class UnsafeCommands(Handler): diff --git a/src/ahriman/core/exceptions.py b/src/ahriman/core/exceptions.py index ba41574f..cd207627 100644 --- a/src/ahriman/core/exceptions.py +++ b/src/ahriman/core/exceptions.py @@ -136,6 +136,19 @@ class ReportFailed(RuntimeError): RuntimeError.__init__(self, "Report failed") +class SuccessFailed(ValueError): + """ + exception for merging invalid statues + """ + + def __init__(self, package_base: str) -> None: + """ + default constructor + :param package_base: package base name + """ + ValueError.__init__(self, f"Package base {package_base} had status failed, but new status is success") + + class SyncFailed(RuntimeError): """ remote synchronization exception diff --git a/src/ahriman/application/formatters/__init__.py b/src/ahriman/core/formatters/__init__.py similarity index 100% rename from src/ahriman/application/formatters/__init__.py rename to src/ahriman/core/formatters/__init__.py diff --git a/src/ahriman/application/formatters/aur_printer.py b/src/ahriman/core/formatters/aur_printer.py similarity index 57% rename from src/ahriman/application/formatters/aur_printer.py rename to src/ahriman/core/formatters/aur_printer.py index 0ec2ddc6..31382cd1 100644 --- a/src/ahriman/application/formatters/aur_printer.py +++ b/src/ahriman/core/formatters/aur_printer.py @@ -17,17 +17,18 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from typing import List, Optional +from typing import List -from ahriman.application.formatters.printer import Printer +from ahriman.core.formatters.string_printer import StringPrinter from ahriman.core.util import pretty_datetime from ahriman.models.aur_package import AURPackage from ahriman.models.property import Property -class AurPrinter(Printer): +class AurPrinter(StringPrinter): """ print content of the AUR package + :ivar package: AUR package description """ def __init__(self, package: AURPackage) -> None: @@ -35,7 +36,8 @@ class AurPrinter(Printer): default constructor :param package: AUR package description """ - self.content = package + StringPrinter.__init__(self, f"{package.name} {package.version} ({package.num_votes})") + self.package = package def properties(self) -> List[Property]: """ @@ -43,19 +45,12 @@ class AurPrinter(Printer): :return: list of content properties """ return [ - Property("Package base", self.content.package_base), - Property("Description", self.content.description, is_required=True), - Property("Upstream URL", self.content.url or ""), - Property("Licenses", ",".join(self.content.license)), - Property("Maintainer", self.content.maintainer or ""), - Property("First submitted", pretty_datetime(self.content.first_submitted)), - Property("Last updated", pretty_datetime(self.content.last_modified)), - Property("Keywords", ",".join(self.content.keywords)), + Property("Package base", self.package.package_base), + Property("Description", self.package.description, is_required=True), + Property("Upstream URL", self.package.url or ""), + Property("Licenses", ",".join(self.package.license)), + Property("Maintainer", self.package.maintainer or ""), + Property("First submitted", pretty_datetime(self.package.first_submitted)), + Property("Last updated", pretty_datetime(self.package.last_modified)), + Property("Keywords", ",".join(self.package.keywords)), ] - - def title(self) -> Optional[str]: - """ - generate entry title from content - :return: content title if it can be generated and None otherwise - """ - return f"{self.content.name} {self.content.version} ({self.content.num_votes})" diff --git a/src/ahriman/core/formatters/build_printer.py b/src/ahriman/core/formatters/build_printer.py new file mode 100644 index 00000000..1df31112 --- /dev/null +++ b/src/ahriman/core/formatters/build_printer.py @@ -0,0 +1,48 @@ +# +# Copyright (c) 2021 ahriman team. +# +# This file is part of ahriman +# (see https://github.com/arcan1s/ahriman). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +from ahriman.core.formatters.string_printer import StringPrinter +from ahriman.models.package import Package + + +class BuildPrinter(StringPrinter): + """ + print content of the build result + """ + + def __init__(self, package: Package, is_success: bool, use_utf: bool) -> None: + """ + default constructor + :param package: built package + :param is_success: True in case if build has success status and False otherwise + :param use_utf: use utf instead of normal symbols + """ + StringPrinter.__init__(self, f"{self.sign(is_success, use_utf)} {package.base}") + + @staticmethod + def sign(is_success: bool, use_utf: bool) -> str: + """ + generate sign according to settings + :param use_utf: use utf instead of normal symbols + :param is_success: True in case if build has success status and False otherwise + :return: sign symbol according to current settings + """ + if is_success: + return "[✔]" if use_utf else "[x]" + return "[❌]" if use_utf else "[ ]" diff --git a/src/ahriman/application/formatters/configuration_printer.py b/src/ahriman/core/formatters/configuration_printer.py similarity index 73% rename from src/ahriman/application/formatters/configuration_printer.py rename to src/ahriman/core/formatters/configuration_printer.py index 6a725747..aa035be0 100644 --- a/src/ahriman/application/formatters/configuration_printer.py +++ b/src/ahriman/core/formatters/configuration_printer.py @@ -17,15 +17,16 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from typing import Dict, List, Optional +from typing import Dict, List -from ahriman.application.formatters.printer import Printer +from ahriman.core.formatters.string_printer import StringPrinter from ahriman.models.property import Property -class ConfigurationPrinter(Printer): +class ConfigurationPrinter(StringPrinter): """ print content of the configuration section + :ivar values: configuration values dictionary """ def __init__(self, section: str, values: Dict[str, str]) -> None: @@ -34,8 +35,8 @@ class ConfigurationPrinter(Printer): :param section: section name :param values: configuration values dictionary """ - self.section = section - self.content = values + StringPrinter.__init__(self, f"[{section}]") + self.values = values def properties(self) -> List[Property]: """ @@ -44,12 +45,5 @@ class ConfigurationPrinter(Printer): """ return [ Property(key, value, is_required=True) - for key, value in sorted(self.content.items()) + for key, value in sorted(self.values.items()) ] - - def title(self) -> Optional[str]: - """ - generate entry title from content - :return: content title if it can be generated and None otherwise - """ - return f"[{self.section}]" diff --git a/src/ahriman/application/formatters/package_printer.py b/src/ahriman/core/formatters/package_printer.py similarity index 69% rename from src/ahriman/application/formatters/package_printer.py rename to src/ahriman/core/formatters/package_printer.py index d5fe80a0..019a4387 100644 --- a/src/ahriman/application/formatters/package_printer.py +++ b/src/ahriman/core/formatters/package_printer.py @@ -17,17 +17,19 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from typing import List, Optional +from typing import List -from ahriman.application.formatters.printer import Printer +from ahriman.core.formatters.string_printer import StringPrinter from ahriman.models.build_status import BuildStatus from ahriman.models.package import Package from ahriman.models.property import Property -class PackagePrinter(Printer): +class PackagePrinter(StringPrinter): """ print content of the internal package object + :ivar package: package description + :ivar status: build status """ def __init__(self, package: Package, status: BuildStatus) -> None: @@ -36,7 +38,8 @@ class PackagePrinter(Printer): :param package: package description :param status: build status """ - self.content = package + StringPrinter.__init__(self, package.pretty_print()) + self.package = package self.status = status def properties(self) -> List[Property]: @@ -45,16 +48,9 @@ class PackagePrinter(Printer): :return: list of content properties """ return [ - Property("Version", self.content.version, is_required=True), - Property("Groups", " ".join(self.content.groups)), - Property("Licenses", " ".join(self.content.licenses)), - Property("Depends", " ".join(self.content.depends)), + Property("Version", self.package.version, is_required=True), + Property("Groups", " ".join(self.package.groups)), + Property("Licenses", " ".join(self.package.licenses)), + Property("Depends", " ".join(self.package.depends)), Property("Status", self.status.pretty_print(), is_required=True), ] - - def title(self) -> Optional[str]: - """ - generate entry title from content - :return: content title if it can be generated and None otherwise - """ - return self.content.pretty_print() diff --git a/src/ahriman/application/formatters/printer.py b/src/ahriman/core/formatters/printer.py similarity index 100% rename from src/ahriman/application/formatters/printer.py rename to src/ahriman/core/formatters/printer.py diff --git a/src/ahriman/application/formatters/status_printer.py b/src/ahriman/core/formatters/status_printer.py similarity index 73% rename from src/ahriman/application/formatters/status_printer.py rename to src/ahriman/core/formatters/status_printer.py index c3b6e6cb..76256939 100644 --- a/src/ahriman/application/formatters/status_printer.py +++ b/src/ahriman/core/formatters/status_printer.py @@ -17,13 +17,11 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from typing import Optional - -from ahriman.application.formatters.printer import Printer +from ahriman.core.formatters.string_printer import StringPrinter from ahriman.models.build_status import BuildStatus -class StatusPrinter(Printer): +class StatusPrinter(StringPrinter): """ print content of the status object """ @@ -33,11 +31,4 @@ class StatusPrinter(Printer): default constructor :param status: build status """ - self.content = status - - def title(self) -> Optional[str]: - """ - generate entry title from content - :return: content title if it can be generated and None otherwise - """ - return self.content.pretty_print() + StringPrinter.__init__(self, status.pretty_print()) diff --git a/src/ahriman/application/formatters/string_printer.py b/src/ahriman/core/formatters/string_printer.py similarity index 95% rename from src/ahriman/application/formatters/string_printer.py rename to src/ahriman/core/formatters/string_printer.py index 3d9e2706..52d8a8af 100644 --- a/src/ahriman/application/formatters/string_printer.py +++ b/src/ahriman/core/formatters/string_printer.py @@ -19,7 +19,7 @@ # from typing import Optional -from ahriman.application.formatters.printer import Printer +from ahriman.core.formatters.printer import Printer class StringPrinter(Printer): diff --git a/src/ahriman/application/formatters/update_printer.py b/src/ahriman/core/formatters/update_printer.py similarity index 78% rename from src/ahriman/application/formatters/update_printer.py rename to src/ahriman/core/formatters/update_printer.py index 6659bceb..e0b4ca63 100644 --- a/src/ahriman/application/formatters/update_printer.py +++ b/src/ahriman/core/formatters/update_printer.py @@ -19,14 +19,16 @@ # from typing import List, Optional -from ahriman.application.formatters.printer import Printer +from ahriman.core.formatters.string_printer import StringPrinter from ahriman.models.package import Package from ahriman.models.property import Property -class UpdatePrinter(Printer): +class UpdatePrinter(StringPrinter): """ print content of the package update + :ivar package: remote (new) package object + :ivar local_version: local version of the package if any """ def __init__(self, remote: Package, local_version: Optional[str]) -> None: @@ -35,7 +37,8 @@ class UpdatePrinter(Printer): :param remote: remote (new) package object :param local_version: local version of the package if any """ - self.content = remote + StringPrinter.__init__(self, remote.base) + self.package = remote self.local_version = local_version or "N/A" def properties(self) -> List[Property]: @@ -43,11 +46,4 @@ class UpdatePrinter(Printer): convert content into printable data :return: list of content properties """ - return [Property(self.local_version, self.content.version, is_required=True)] - - def title(self) -> Optional[str]: - """ - generate entry title from content - :return: content title if it can be generated and None otherwise - """ - return self.content.base + return [Property(self.local_version, self.package.version, is_required=True)] diff --git a/src/ahriman/core/report/console.py b/src/ahriman/core/report/console.py new file mode 100644 index 00000000..759e84b3 --- /dev/null +++ b/src/ahriman/core/report/console.py @@ -0,0 +1,54 @@ +# +# Copyright (c) 2021 ahriman team. +# +# This file is part of ahriman +# (see https://github.com/arcan1s/ahriman). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +from typing import Iterable + +from ahriman.core.configuration import Configuration +from ahriman.core.formatters.build_printer import BuildPrinter +from ahriman.core.report.report import Report +from ahriman.models.package import Package +from ahriman.models.result import Result + + +class Console(Report): + """ + html report generator + :ivar use_utf: print utf8 symbols instead of ASCII + """ + + def __init__(self, architecture: str, configuration: Configuration, section: str) -> None: + """ + default constructor + :param architecture: repository architecture + :param configuration: configuration instance + :param section: settings section name + """ + Report.__init__(self, architecture, configuration) + self.use_utf = configuration.getboolean(section, "use_utf") + + def generate(self, packages: Iterable[Package], result: Result) -> None: + """ + generate report for the specified packages + :param packages: list of packages to generate report + :param result: build result + """ + for package in result.success: + BuildPrinter(package, is_success=True, use_utf=self.use_utf).print(verbose=True) + for package in result.failed: + BuildPrinter(package, is_success=True, use_utf=self.use_utf).print(verbose=True) diff --git a/src/ahriman/core/report/email.py b/src/ahriman/core/report/email.py index 64f784ea..123084fe 100644 --- a/src/ahriman/core/report/email.py +++ b/src/ahriman/core/report/email.py @@ -29,12 +29,14 @@ from ahriman.core.report.jinja_template import JinjaTemplate from ahriman.core.report.report import Report from ahriman.core.util import pretty_datetime from ahriman.models.package import Package +from ahriman.models.result import Result from ahriman.models.smtp_ssl_settings import SmtpSSLSettings class Email(Report, JinjaTemplate): """ email report generator + :ivar full_template_path: path to template for full package list :ivar host: SMTP host to connect :ivar no_empty_report: skip empty report generation :ivar password: password to authenticate via SMTP @@ -42,6 +44,7 @@ class Email(Report, JinjaTemplate): :ivar receivers: list of receivers emails :ivar sender: sender email address :ivar ssl: SSL mode for SMTP connection + :ivar template_path: path to template for built packages :ivar user: username to authenticate via SMTP """ @@ -96,17 +99,17 @@ class Email(Report, JinjaTemplate): session.sendmail(self.sender, self.receivers, message.as_string()) session.quit() - def generate(self, packages: Iterable[Package], built_packages: Iterable[Package]) -> None: + def generate(self, packages: Iterable[Package], result: Result) -> None: """ generate report for the specified packages :param packages: list of packages to generate report - :param built_packages: list of packages which has just been built + :param result: build result """ - if self.no_empty_report and not built_packages: + if self.no_empty_report and not result.success: return - text = self.make_html(built_packages, self.template_path) + text = self.make_html(result, self.template_path) if self.full_template_path is not None: - attachments = {"index.html": self.make_html(packages, self.full_template_path)} + attachments = {"index.html": self.make_html(Result(success=packages), self.full_template_path)} else: attachments = {} self._send(text, attachments) diff --git a/src/ahriman/core/report/html.py b/src/ahriman/core/report/html.py index e38084fd..dd0e99a1 100644 --- a/src/ahriman/core/report/html.py +++ b/src/ahriman/core/report/html.py @@ -23,12 +23,14 @@ from ahriman.core.configuration import Configuration from ahriman.core.report.jinja_template import JinjaTemplate from ahriman.core.report.report import Report from ahriman.models.package import Package +from ahriman.models.result import Result class HTML(Report, JinjaTemplate): """ html report generator :ivar report_path: output path to html report + :ivar template_path: path to template for full package list """ def __init__(self, architecture: str, configuration: Configuration, section: str) -> None: @@ -44,11 +46,11 @@ class HTML(Report, JinjaTemplate): self.report_path = configuration.getpath(section, "path") self.template_path = configuration.getpath(section, "template_path") - def generate(self, packages: Iterable[Package], built_packages: Iterable[Package]) -> None: + def generate(self, packages: Iterable[Package], result: Result) -> None: """ generate report for the specified packages :param packages: list of packages to generate report - :param built_packages: list of packages which has just been built + :param result: build result """ - html = self.make_html(packages, self.template_path) + html = self.make_html(Result(success=packages), self.template_path) self.report_path.write_text(html) diff --git a/src/ahriman/core/report/jinja_template.py b/src/ahriman/core/report/jinja_template.py index 983790b5..e2155898 100644 --- a/src/ahriman/core/report/jinja_template.py +++ b/src/ahriman/core/report/jinja_template.py @@ -20,12 +20,12 @@ import jinja2 from pathlib import Path -from typing import Callable, Dict, Iterable +from typing import Callable, Dict from ahriman.core.configuration import Configuration from ahriman.core.sign.gpg import GPG from ahriman.core.util import pretty_datetime, pretty_size -from ahriman.models.package import Package +from ahriman.models.result import Result from ahriman.models.sign_settings import SignSettings @@ -76,10 +76,10 @@ class JinjaTemplate: self.sign_targets, self.default_pgp_key = GPG.sign_options(configuration) - def make_html(self, packages: Iterable[Package], template_path: Path) -> str: + def make_html(self, result: Result, template_path: Path) -> str: """ generate report for the specified packages - :param packages: list of packages to generate report + :param result: build result :param template_path: path to jinja template """ # idea comes from https://stackoverflow.com/a/38642558 @@ -101,7 +101,7 @@ class JinjaTemplate: "name": package, "url": properties.url or "", "version": base.version - } for base in packages for package, properties in base.packages.items() + } for base in result.updated for package, properties in base.packages.items() ] comparator: Callable[[Dict[str, str]], str] = lambda item: item["filename"] diff --git a/src/ahriman/core/report/report.py b/src/ahriman/core/report/report.py index 173ee431..91a335c9 100644 --- a/src/ahriman/core/report/report.py +++ b/src/ahriman/core/report/report.py @@ -27,6 +27,7 @@ from ahriman.core.configuration import Configuration from ahriman.core.exceptions import ReportFailed from ahriman.models.package import Package from ahriman.models.report_settings import ReportSettings +from ahriman.models.result import Result class Report: @@ -64,23 +65,26 @@ class Report: if provider == ReportSettings.Email: from ahriman.core.report.email import Email return Email(architecture, configuration, section) + if provider == ReportSettings.Console: + from ahriman.core.report.console import Console + return Console(architecture, configuration, section) return cls(architecture, configuration) # should never happen - def generate(self, packages: Iterable[Package], built_packages: Iterable[Package]) -> None: + def generate(self, packages: Iterable[Package], result: Result) -> None: """ generate report for the specified packages :param packages: list of packages to generate report - :param built_packages: list of packages which has just been built + :param result: build result """ - def run(self, packages: Iterable[Package], built_packages: Iterable[Package]) -> None: + def run(self, packages: Iterable[Package], result: Result) -> None: """ run report generation :param packages: list of packages to generate report - :param built_packages: list of packages which has just been built + :param result: build result """ try: - self.generate(packages, built_packages) + self.generate(packages, result) except Exception: self.logger.exception("report generation failed") raise ReportFailed() diff --git a/src/ahriman/core/repository/executor.py b/src/ahriman/core/repository/executor.py index c5a74f8f..1403d9ce 100644 --- a/src/ahriman/core/repository/executor.py +++ b/src/ahriman/core/repository/executor.py @@ -27,6 +27,7 @@ from ahriman.core.report.report import Report from ahriman.core.repository.cleaner import Cleaner from ahriman.core.upload.upload import Upload from ahriman.models.package import Package +from ahriman.models.result import Result class Executor(Cleaner): @@ -49,7 +50,7 @@ class Executor(Cleaner): """ raise NotImplementedError - def process_build(self, updates: Iterable[Package]) -> List[Path]: + def process_build(self, updates: Iterable[Package]) -> Result: """ build packages :param updates: list of packages properties to build @@ -64,15 +65,18 @@ class Executor(Cleaner): dst = self.paths.packages / src.name shutil.move(src, dst) + result = Result() for single in updates: try: build_single(single) + result.add_success(single) except Exception: self.reporter.set_failed(single.base) + result.add_failed(single) self.logger.exception("%s (%s) build exception", single.base, self.architecture) self.clear_build() - return self.packages_built() + return result def process_remove(self, packages: Iterable[str]) -> Path: """ @@ -116,17 +120,17 @@ class Executor(Cleaner): return self.repo.repo_path - def process_report(self, targets: Optional[Iterable[str]], built_packages: Iterable[Package]) -> None: + def process_report(self, targets: Optional[Iterable[str]], result: Result) -> None: """ generate reports :param targets: list of targets to generate reports. Configuration option will be used if it is not set - :param built_packages: list of packages which has just been built + :param result: build result """ if targets is None: targets = self.configuration.getlist("report", "target") for target in targets: runner = Report.load(self.architecture, self.configuration, target) - runner.run(self.packages(), built_packages) + runner.run(self.packages(), result) def process_sync(self, targets: Optional[Iterable[str]], built_packages: Iterable[Package]) -> None: """ @@ -140,7 +144,7 @@ class Executor(Cleaner): runner = Upload.load(self.architecture, self.configuration, target) runner.run(self.paths.repository, built_packages) - def process_update(self, packages: Iterable[Path]) -> Path: + def process_update(self, packages: Iterable[Path]) -> Result: """ sign packages, add them to repository and update repository database :param packages: list of filenames to run @@ -163,20 +167,23 @@ class Executor(Cleaner): removed_packages: List[str] = [] # list of packages which have been removed from the base updates = self.load_archives(packages) + result = Result() for local in updates: try: for description in local.packages.values(): update_single(description.filename, local.base) self.reporter.set_success(local) + result.add_success(local) current_package_archives: Set[str] = next( (set(current.packages) for current in current_packages if current.base == local.base), set()) removed_packages.extend(current_package_archives.difference(local.packages)) except Exception: self.reporter.set_failed(local.base) + result.add_failed(local) self.logger.exception("could not process %s", local.base) self.clear_packages() self.process_remove(removed_packages) - return self.repo.repo_path + return result diff --git a/src/ahriman/models/report_settings.py b/src/ahriman/models/report_settings.py index c9217876..703fc58c 100644 --- a/src/ahriman/models/report_settings.py +++ b/src/ahriman/models/report_settings.py @@ -31,11 +31,13 @@ class ReportSettings(Enum): :cvar Disabled: option which generates no report for testing purpose :cvar HTML: html report generation :cvar Email: email report generation + :cvar Console: print result to console """ Disabled = "disabled" # for testing purpose HTML = "html" Email = "email" + Console = "console" @classmethod def from_option(cls: Type[ReportSettings], value: str) -> ReportSettings: @@ -48,4 +50,6 @@ class ReportSettings(Enum): return cls.HTML if value.lower() in ("email",): return cls.Email + if value.lower() in ("console",): + return cls.Console raise InvalidOption(value) diff --git a/src/ahriman/models/result.py b/src/ahriman/models/result.py new file mode 100644 index 00000000..7498c81f --- /dev/null +++ b/src/ahriman/models/result.py @@ -0,0 +1,105 @@ +# +# Copyright (c) 2021 ahriman team. +# +# This file is part of ahriman +# (see https://github.com/arcan1s/ahriman). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +from __future__ import annotations + +from typing import Any, List, Optional, Iterable + +from ahriman.core.exceptions import SuccessFailed +from ahriman.models.package import Package + + +class Result: + """ + build result class holder + """ + + def __init__(self, success: Optional[Iterable[Package]] = None, failed: Optional[Iterable[Package]] = None) -> None: + """ + default constructor + :param success: initial list of successes packages + :param failed: initial list of failed packages + """ + success = success or [] + self._success = {package.base: package for package in success} + failed = failed or [] + self._failed = {package.base: package for package in failed} + + @property + def failed(self) -> List[Package]: + """ + :return: list of packages which were failed + """ + return list(self._failed.values()) + + @property + def success(self) -> List[Package]: + """ + :return: list of packages with success result + """ + return list(self._success.values()) + + @property + def updated(self) -> List[Package]: + """ + :return: list of updated packages inclding both success and failed + """ + return self.success + self.failed + + def add_failed(self, package: Package) -> None: + """ + add new package to failed built + :param package: package with errors during build + """ + self._failed[package.base] = package + + def add_success(self, package: Package) -> None: + """ + add new package to success built + :param package: package built + """ + self._success[package.base] = package + + # pylint: disable=protected-access + def merge(self, other: Result) -> Result: + """ + merge other result into this one. This method assumes that other has fresh info about status and override it + :param other: instance of the newest result + :return: updated instance + """ + for base, package in other._failed.items(): + if base in self._success: + del self._success[base] + self.add_failed(package) + for base, package in other._success.items(): + if base in self._failed: + raise SuccessFailed(base) + self.add_success(package) + return self + + # required for tests at least + def __eq__(self, other: Any) -> bool: + """ + check if other is the same object + :param other: other object instance + :return: True if the other object is the same and False otherwise + """ + if not isinstance(other, Result): + return False + return self.success == other.success and self.failed == other.failed diff --git a/tests/ahriman/application/application/test_application.py b/tests/ahriman/application/application/test_application.py index 8437156f..79e0aac6 100644 --- a/tests/ahriman/application/application/test_application.py +++ b/tests/ahriman/application/application/test_application.py @@ -2,6 +2,7 @@ from pytest_mock import MockerFixture from ahriman.application.application import Application from ahriman.models.package import Package +from ahriman.models.result import Result def test_finalize(application: Application, mocker: MockerFixture) -> None: @@ -11,8 +12,8 @@ def test_finalize(application: Application, mocker: MockerFixture) -> None: report_mock = mocker.patch("ahriman.application.application.Application.report") sync_mock = mocker.patch("ahriman.application.application.Application.sync") - application._finalize([]) - report_mock.assert_called_once_with([], []) + application._finalize(Result()) + report_mock.assert_called_once_with([], Result()) sync_mock.assert_called_once_with([], []) diff --git a/tests/ahriman/application/application/test_application_packages.py b/tests/ahriman/application/application/test_application_packages.py index b3b531d5..7ba56b34 100644 --- a/tests/ahriman/application/application/test_application_packages.py +++ b/tests/ahriman/application/application/test_application_packages.py @@ -9,6 +9,7 @@ from ahriman.application.application.packages import Packages from ahriman.models.package import Package from ahriman.models.package_description import PackageDescription from ahriman.models.package_source import PackageSource +from ahriman.models.result import Result def test_finalize(application_packages: Packages) -> None: @@ -211,4 +212,4 @@ def test_remove(application_packages: Packages, mocker: MockerFixture) -> None: application_packages.remove([]) executor_mock.assert_called_once_with([]) - finalize_mock.assert_called_once_with([]) + finalize_mock.assert_called_once_with(Result()) diff --git a/tests/ahriman/application/application/test_application_repository.py b/tests/ahriman/application/application/test_application_repository.py index 62e08397..3c3d9e98 100644 --- a/tests/ahriman/application/application/test_application_repository.py +++ b/tests/ahriman/application/application/test_application_repository.py @@ -6,6 +6,7 @@ from unittest import mock from ahriman.application.application.repository import Repository from ahriman.core.tree import Leaf, Tree from ahriman.models.package import Package +from ahriman.models.result import Result def test_finalize(application_repository: Repository) -> None: @@ -98,7 +99,7 @@ def test_sign(application_repository: Repository, package_ahriman: Package, pack ]) update_mock.assert_called_once_with([]) sign_repository_mock.assert_called_once_with(application_repository.repository.repo.repo_path) - finalize_mock.assert_called_once_with([]) + finalize_mock.assert_called_once_with(Result()) def test_sign_skip(application_repository: Repository, package_ahriman: Package, mocker: MockerFixture) -> None: @@ -132,7 +133,7 @@ def test_sign_specific(application_repository: Repository, package_ahriman: Pack application_repository.repository.paths.packages / filename.name) update_mock.assert_called_once_with([]) sign_repository_mock.assert_called_once_with(application_repository.repository.repo.repo_path) - finalize_mock.assert_called_once_with([]) + finalize_mock.assert_called_once_with(Result()) def test_sync(application_repository: Repository, mocker: MockerFixture) -> None: @@ -181,7 +182,8 @@ def test_unknown_no_local(application_repository: Repository, package_ahriman: P assert not application_repository.unknown() -def test_update(application_repository: Repository, package_ahriman: Package, mocker: MockerFixture) -> None: +def test_update(application_repository: Repository, package_ahriman: Package, result: Result, + mocker: MockerFixture) -> None: """ must process package updates """ @@ -189,16 +191,33 @@ def test_update(application_repository: Repository, package_ahriman: Package, mo tree = Tree([Leaf(package_ahriman, set())]) mocker.patch("ahriman.core.tree.Tree.load", return_value=tree) - mocker.patch("ahriman.core.repository.repository.Repository.packages_built", return_value=[]) + mocker.patch("ahriman.core.repository.repository.Repository.packages_built", return_value=paths) mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman) - build_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_build", return_value=paths) - update_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_update") + build_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_build", return_value=result) + update_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_update", return_value=result) finalize_mock = mocker.patch("ahriman.application.application.repository.Repository._finalize") application_repository.update([package_ahriman]) build_mock.assert_called_once_with([package_ahriman]) - update_mock.assert_called_once_with(paths) - finalize_mock.assert_called_once_with([package_ahriman]) + update_mock.assert_has_calls([mock.call(paths), mock.call(paths)]) + finalize_mock.assert_has_calls([mock.call(result), mock.call(result)]) + + +def test_update_empty(application_repository: Repository, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must skip updating repository if no packages supplied + """ + paths = [package.filepath for package in package_ahriman.packages.values()] + tree = Tree([Leaf(package_ahriman, set())]) + + mocker.patch("ahriman.core.tree.Tree.load", return_value=tree) + mocker.patch("ahriman.core.repository.repository.Repository.packages_built", return_value=[]) + mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman) + mocker.patch("ahriman.core.repository.executor.Executor.process_build") + update_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_update") + + application_repository.update([package_ahriman]) + update_mock.assert_not_called() def test_updates_all(application_repository: Repository, package_ahriman: Package, mocker: MockerFixture) -> None: diff --git a/tests/ahriman/application/handlers/test_handler_dump.py b/tests/ahriman/application/handlers/test_handler_dump.py index f04bbf2a..a8ea9de5 100644 --- a/tests/ahriman/application/handlers/test_handler_dump.py +++ b/tests/ahriman/application/handlers/test_handler_dump.py @@ -11,7 +11,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc must run command """ mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") - print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print") + print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print") application_mock = mocker.patch("ahriman.core.configuration.Configuration.dump", return_value=configuration.dump()) diff --git a/tests/ahriman/application/handlers/test_handler_remove_unknown.py b/tests/ahriman/application/handlers/test_handler_remove_unknown.py index e3271b44..25057731 100644 --- a/tests/ahriman/application/handlers/test_handler_remove_unknown.py +++ b/tests/ahriman/application/handlers/test_handler_remove_unknown.py @@ -45,7 +45,7 @@ def test_run_dry_run(args: argparse.Namespace, configuration: Configuration, pac application_mock = mocker.patch("ahriman.application.application.Application.unknown", return_value=[package_ahriman]) remove_mock = mocker.patch("ahriman.application.application.Application.remove") - print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print") + print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print") RemoveUnknown.run(args, "x86_64", configuration, True, False) application_mock.assert_called_once_with() @@ -65,7 +65,7 @@ def test_run_dry_run_verbose(args: argparse.Namespace, configuration: Configurat application_mock = mocker.patch("ahriman.application.application.Application.unknown", return_value=[package_ahriman]) remove_mock = mocker.patch("ahriman.application.application.Application.remove") - print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print") + print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print") RemoveUnknown.run(args, "x86_64", configuration, True, False) application_mock.assert_called_once_with() diff --git a/tests/ahriman/application/handlers/test_handler_report.py b/tests/ahriman/application/handlers/test_handler_report.py index e44fd7cd..5c2ee75c 100644 --- a/tests/ahriman/application/handlers/test_handler_report.py +++ b/tests/ahriman/application/handlers/test_handler_report.py @@ -4,6 +4,7 @@ from pytest_mock import MockerFixture from ahriman.application.handlers import Report from ahriman.core.configuration import Configuration +from ahriman.models.result import Result def _default_args(args: argparse.Namespace) -> argparse.Namespace: @@ -25,4 +26,4 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc application_mock = mocker.patch("ahriman.application.application.Application.report") Report.run(args, "x86_64", configuration, True, False) - application_mock.assert_called_once_with(args.target, []) + application_mock.assert_called_once_with(args.target, Result()) diff --git a/tests/ahriman/application/handlers/test_handler_search.py b/tests/ahriman/application/handlers/test_handler_search.py index 1d04cc7b..b3f33dd0 100644 --- a/tests/ahriman/application/handlers/test_handler_search.py +++ b/tests/ahriman/application/handlers/test_handler_search.py @@ -29,7 +29,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, aur_package """ args = _default_args(args) search_mock = mocker.patch("ahriman.core.alpm.aur.AUR.multisearch", return_value=[aur_package_ahriman]) - print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print") + print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print") Search.run(args, "x86_64", configuration, True, False) search_mock.assert_called_once_with("ahriman") diff --git a/tests/ahriman/application/handlers/test_handler_status.py b/tests/ahriman/application/handlers/test_handler_status.py index 1d13cadd..1f635396 100644 --- a/tests/ahriman/application/handlers/test_handler_status.py +++ b/tests/ahriman/application/handlers/test_handler_status.py @@ -33,7 +33,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, package_ahr packages_mock = mocker.patch("ahriman.core.status.client.Client.get", return_value=[(package_ahriman, BuildStatus(BuildStatusEnum.Success)), (package_python_schedule, BuildStatus(BuildStatusEnum.Failed))]) - print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print") + print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print") Status.run(args, "x86_64", configuration, True, False) application_mock.assert_called_once_with() @@ -51,7 +51,7 @@ def test_run_verbose(args: argparse.Namespace, configuration: Configuration, pac mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") mocker.patch("ahriman.core.status.client.Client.get", return_value=[(package_ahriman, BuildStatus(BuildStatusEnum.Success))]) - print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print") + print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print") Status.run(args, "x86_64", configuration, True, False) print_mock.assert_has_calls([mock.call(True) for _ in range(2)]) @@ -83,7 +83,7 @@ def test_run_by_status(args: argparse.Namespace, configuration: Configuration, p return_value=[(package_ahriman, BuildStatus(BuildStatusEnum.Success)), (package_python_schedule, BuildStatus(BuildStatusEnum.Failed))]) mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") - print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print") + print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print") Status.run(args, "x86_64", configuration, True, False) print_mock.assert_has_calls([mock.call(False) for _ in range(2)]) diff --git a/tests/ahriman/application/handlers/test_handler_unsafe_commands.py b/tests/ahriman/application/handlers/test_handler_unsafe_commands.py index 80621aac..2da5145c 100644 --- a/tests/ahriman/application/handlers/test_handler_unsafe_commands.py +++ b/tests/ahriman/application/handlers/test_handler_unsafe_commands.py @@ -15,7 +15,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc args.parser = _parser commands_mock = mocker.patch("ahriman.application.handlers.UnsafeCommands.get_unsafe_commands", return_value=["command"]) - print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print") + print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print") UnsafeCommands.run(args, "x86_64", configuration, True, False) commands_mock.assert_called_once_with(pytest.helpers.anyvar(int)) diff --git a/tests/ahriman/conftest.py b/tests/ahriman/conftest.py index 96afe586..1208e278 100644 --- a/tests/ahriman/conftest.py +++ b/tests/ahriman/conftest.py @@ -14,6 +14,7 @@ from ahriman.models.aur_package import AURPackage from ahriman.models.package import Package from ahriman.models.package_description import PackageDescription from ahriman.models.repository_paths import RepositoryPaths +from ahriman.models.result import Result from ahriman.models.user import User from ahriman.models.user_access import UserAccess @@ -234,6 +235,18 @@ def repository_paths(configuration: Configuration) -> RepositoryPaths: root=configuration.getpath("repository", "root")) +@pytest.fixture +def result(package_ahriman: Package) -> Result: + """ + result fixture + :param package_ahriman: package fixture + :return: result test instance + """ + result = Result() + result.add_success(package_ahriman) + return result + + @pytest.fixture def spawner(configuration: Configuration) -> Spawn: """ diff --git a/tests/ahriman/application/formatters/conftest.py b/tests/ahriman/core/formatters/conftest.py similarity index 78% rename from tests/ahriman/application/formatters/conftest.py rename to tests/ahriman/core/formatters/conftest.py index 6a38c710..d193a759 100644 --- a/tests/ahriman/application/formatters/conftest.py +++ b/tests/ahriman/core/formatters/conftest.py @@ -1,11 +1,11 @@ import pytest -from ahriman.application.formatters.aur_printer import AurPrinter -from ahriman.application.formatters.configuration_printer import ConfigurationPrinter -from ahriman.application.formatters.package_printer import PackagePrinter -from ahriman.application.formatters.status_printer import StatusPrinter -from ahriman.application.formatters.string_printer import StringPrinter -from ahriman.application.formatters.update_printer import UpdatePrinter +from ahriman.core.formatters.aur_printer import AurPrinter +from ahriman.core.formatters.configuration_printer import ConfigurationPrinter +from ahriman.core.formatters.package_printer import PackagePrinter +from ahriman.core.formatters.status_printer import StatusPrinter +from ahriman.core.formatters.string_printer import StringPrinter +from ahriman.core.formatters.update_printer import UpdatePrinter from ahriman.models.aur_package import AURPackage from ahriman.models.build_status import BuildStatus from ahriman.models.package import Package diff --git a/tests/ahriman/application/formatters/test_aur_printer.py b/tests/ahriman/core/formatters/test_aur_printer.py similarity index 84% rename from tests/ahriman/application/formatters/test_aur_printer.py rename to tests/ahriman/core/formatters/test_aur_printer.py index 8725dff6..1678fcd9 100644 --- a/tests/ahriman/application/formatters/test_aur_printer.py +++ b/tests/ahriman/core/formatters/test_aur_printer.py @@ -1,4 +1,4 @@ -from ahriman.application.formatters.aur_printer import AurPrinter +from ahriman.core.formatters.aur_printer import AurPrinter def test_properties(aur_package_ahriman_printer: AurPrinter) -> None: diff --git a/tests/ahriman/core/formatters/test_build_printer.py b/tests/ahriman/core/formatters/test_build_printer.py new file mode 100644 index 00000000..285f6a46 --- /dev/null +++ b/tests/ahriman/core/formatters/test_build_printer.py @@ -0,0 +1,36 @@ +import pytest + +from ahriman.core.formatters.build_printer import BuildPrinter +from ahriman.models.package import Package + + +def test_properties(package_ahriman: Package) -> None: + """ + must return empty properties list + """ + assert not BuildPrinter(package_ahriman, is_success=True, use_utf=False).properties() + + +def test_sign_ascii(package_ahriman: Package) -> None: + """ + must correctly generate sign in ascii + """ + BuildPrinter(package_ahriman, is_success=True, use_utf=False).title().encode("ascii") + BuildPrinter(package_ahriman, is_success=False, use_utf=False).title().encode("ascii") + + +def test_sign_utf8(package_ahriman: Package) -> None: + """ + must correctly generate sign in ascii + """ + with pytest.raises(UnicodeEncodeError): + BuildPrinter(package_ahriman, is_success=True, use_utf=True).title().encode("ascii") + with pytest.raises(UnicodeEncodeError): + BuildPrinter(package_ahriman, is_success=False, use_utf=True).title().encode("ascii") + + +def test_title(package_ahriman: Package) -> None: + """ + must return non empty title + """ + assert BuildPrinter(package_ahriman, is_success=True, use_utf=False).title() is not None diff --git a/tests/ahriman/application/formatters/test_configuration_printer.py b/tests/ahriman/core/formatters/test_configuration_printer.py similarity index 87% rename from tests/ahriman/application/formatters/test_configuration_printer.py rename to tests/ahriman/core/formatters/test_configuration_printer.py index b7117d4a..c82dcbf7 100644 --- a/tests/ahriman/application/formatters/test_configuration_printer.py +++ b/tests/ahriman/core/formatters/test_configuration_printer.py @@ -1,4 +1,4 @@ -from ahriman.application.formatters.configuration_printer import ConfigurationPrinter +from ahriman.core.formatters.configuration_printer import ConfigurationPrinter def test_properties(configuration_printer: ConfigurationPrinter) -> None: diff --git a/tests/ahriman/application/formatters/test_package_printer.py b/tests/ahriman/core/formatters/test_package_printer.py similarity index 82% rename from tests/ahriman/application/formatters/test_package_printer.py rename to tests/ahriman/core/formatters/test_package_printer.py index 06525a6b..fa6f77df 100644 --- a/tests/ahriman/application/formatters/test_package_printer.py +++ b/tests/ahriman/core/formatters/test_package_printer.py @@ -1,4 +1,4 @@ -from ahriman.application.formatters.package_printer import PackagePrinter +from ahriman.core.formatters.package_printer import PackagePrinter def test_properties(package_ahriman_printer: PackagePrinter) -> None: diff --git a/tests/ahriman/application/formatters/test_printer.py b/tests/ahriman/core/formatters/test_printer.py similarity index 87% rename from tests/ahriman/application/formatters/test_printer.py rename to tests/ahriman/core/formatters/test_printer.py index 92b3d31f..6532d646 100644 --- a/tests/ahriman/application/formatters/test_printer.py +++ b/tests/ahriman/core/formatters/test_printer.py @@ -1,7 +1,7 @@ from unittest.mock import MagicMock -from ahriman.application.formatters.package_printer import PackagePrinter -from ahriman.application.formatters.printer import Printer +from ahriman.core.formatters.package_printer import PackagePrinter +from ahriman.core.formatters.printer import Printer def test_print(package_ahriman_printer: PackagePrinter) -> None: diff --git a/tests/ahriman/application/formatters/test_status_printer.py b/tests/ahriman/core/formatters/test_status_printer.py similarity index 81% rename from tests/ahriman/application/formatters/test_status_printer.py rename to tests/ahriman/core/formatters/test_status_printer.py index b3026b9b..66849c93 100644 --- a/tests/ahriman/application/formatters/test_status_printer.py +++ b/tests/ahriman/core/formatters/test_status_printer.py @@ -1,4 +1,4 @@ -from ahriman.application.formatters.status_printer import StatusPrinter +from ahriman.core.formatters.status_printer import StatusPrinter def test_properties(status_printer: StatusPrinter) -> None: diff --git a/tests/ahriman/application/formatters/test_string_printer.py b/tests/ahriman/core/formatters/test_string_printer.py similarity index 81% rename from tests/ahriman/application/formatters/test_string_printer.py rename to tests/ahriman/core/formatters/test_string_printer.py index c4e81906..b2e4ddc2 100644 --- a/tests/ahriman/application/formatters/test_string_printer.py +++ b/tests/ahriman/core/formatters/test_string_printer.py @@ -1,4 +1,4 @@ -from ahriman.application.formatters.string_printer import StringPrinter +from ahriman.core.formatters.string_printer import StringPrinter def test_properties(string_printer: StringPrinter) -> None: diff --git a/tests/ahriman/application/formatters/test_update_printer.py b/tests/ahriman/core/formatters/test_update_printer.py similarity index 80% rename from tests/ahriman/application/formatters/test_update_printer.py rename to tests/ahriman/core/formatters/test_update_printer.py index 37f8b199..ad37c9fc 100644 --- a/tests/ahriman/application/formatters/test_update_printer.py +++ b/tests/ahriman/core/formatters/test_update_printer.py @@ -1,4 +1,4 @@ -from ahriman.application.formatters.update_printer import UpdatePrinter +from ahriman.core.formatters.update_printer import UpdatePrinter def test_properties(update_printer: UpdatePrinter) -> None: diff --git a/tests/ahriman/core/report/test_console.py b/tests/ahriman/core/report/test_console.py new file mode 100644 index 00000000..ade89c3f --- /dev/null +++ b/tests/ahriman/core/report/test_console.py @@ -0,0 +1,20 @@ +from pytest_mock import MockerFixture +from unittest import mock + +from ahriman.core.configuration import Configuration +from ahriman.core.report.console import Console +from ahriman.models.package import Package +from ahriman.models.result import Result + + +def test_generate(configuration: Configuration, result: Result, package_python_schedule: Package, + mocker: MockerFixture) -> None: + """ + must print result to stdout + """ + print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print") + result.add_failed(package_python_schedule) + report = Console("x86_64", configuration, "console") + + report.generate([], result) + print_mock.assert_has_calls([mock.call(verbose=True), mock.call(verbose=True)]) diff --git a/tests/ahriman/core/report/test_email.py b/tests/ahriman/core/report/test_email.py index 8a212073..343e4ee0 100644 --- a/tests/ahriman/core/report/test_email.py +++ b/tests/ahriman/core/report/test_email.py @@ -5,6 +5,7 @@ from pytest_mock import MockerFixture from ahriman.core.configuration import Configuration from ahriman.core.report.email import Email from ahriman.models.package import Package +from ahriman.models.result import Result def test_send(configuration: Configuration, mocker: MockerFixture) -> None: @@ -92,24 +93,26 @@ def test_generate(configuration: Configuration, package_ahriman: Package, mocker send_mock = mocker.patch("ahriman.core.report.email.Email._send") report = Email("x86_64", configuration, "email") - report.generate([package_ahriman], []) + report.generate([package_ahriman], Result()) send_mock.assert_called_once_with(pytest.helpers.anyvar(int), {}) -def test_generate_with_built(configuration: Configuration, package_ahriman: Package, mocker: MockerFixture) -> None: +def test_generate_with_built(configuration: Configuration, package_ahriman: Package, result: Result, + mocker: MockerFixture) -> None: """ must generate report with built packages """ send_mock = mocker.patch("ahriman.core.report.email.Email._send") report = Email("x86_64", configuration, "email") - report.generate([package_ahriman], [package_ahriman]) + report.generate([package_ahriman], result) send_mock.assert_called_once_with(pytest.helpers.anyvar(int), {}) def test_generate_with_built_and_full_path( configuration: Configuration, package_ahriman: Package, + result: Result, mocker: MockerFixture) -> None: """ must generate report with built packages @@ -118,7 +121,7 @@ def test_generate_with_built_and_full_path( report = Email("x86_64", configuration, "email") report.full_template_path = report.template_path - report.generate([package_ahriman], [package_ahriman]) + report.generate([package_ahriman], result) send_mock.assert_called_once_with(pytest.helpers.anyvar(int), pytest.helpers.anyvar(int)) @@ -130,11 +133,11 @@ def test_generate_no_empty(configuration: Configuration, package_ahriman: Packag send_mock = mocker.patch("ahriman.core.report.email.Email._send") report = Email("x86_64", configuration, "email") - report.generate([package_ahriman], []) + report.generate([package_ahriman], Result()) send_mock.assert_not_called() -def test_generate_no_empty_with_built(configuration: Configuration, package_ahriman: Package, +def test_generate_no_empty_with_built(configuration: Configuration, package_ahriman: Package, result: Result, mocker: MockerFixture) -> None: """ must generate report with built packages if no_empty_report is set @@ -143,5 +146,5 @@ def test_generate_no_empty_with_built(configuration: Configuration, package_ahri send_mock = mocker.patch("ahriman.core.report.email.Email._send") report = Email("x86_64", configuration, "email") - report.generate([package_ahriman], [package_ahriman]) + report.generate([package_ahriman], result) send_mock.assert_called_once_with(pytest.helpers.anyvar(int), {}) diff --git a/tests/ahriman/core/report/test_jinja_template.py b/tests/ahriman/core/report/test_jinja_template.py index 18d8f9ec..93deccbf 100644 --- a/tests/ahriman/core/report/test_jinja_template.py +++ b/tests/ahriman/core/report/test_jinja_template.py @@ -1,6 +1,7 @@ from ahriman.core.configuration import Configuration from ahriman.core.report.jinja_template import JinjaTemplate from ahriman.models.package import Package +from ahriman.models.result import Result def test_generate(configuration: Configuration, package_ahriman: Package) -> None: @@ -9,4 +10,4 @@ def test_generate(configuration: Configuration, package_ahriman: Package) -> Non """ path = configuration.getpath("html", "template_path") report = JinjaTemplate("html", configuration) - assert report.make_html([package_ahriman], path) + assert report.make_html(Result(success=[package_ahriman]), path) diff --git a/tests/ahriman/core/report/test_report.py b/tests/ahriman/core/report/test_report.py index 883ed45d..623dd0cd 100644 --- a/tests/ahriman/core/report/test_report.py +++ b/tests/ahriman/core/report/test_report.py @@ -6,6 +6,7 @@ from ahriman.core.configuration import Configuration from ahriman.core.exceptions import ReportFailed from ahriman.core.report.report import Report from ahriman.models.report_settings import ReportSettings +from ahriman.models.result import Result def test_report_failure(configuration: Configuration, mocker: MockerFixture) -> None: @@ -14,32 +15,41 @@ def test_report_failure(configuration: Configuration, mocker: MockerFixture) -> """ mocker.patch("ahriman.core.report.html.HTML.generate", side_effect=Exception()) with pytest.raises(ReportFailed): - Report.load("x86_64", configuration, "html").run([], []) + Report.load("x86_64", configuration, "html").run([], Result()) -def test_report_dummy(configuration: Configuration, mocker: MockerFixture) -> None: +def test_report_dummy(configuration: Configuration, result: Result, mocker: MockerFixture) -> None: """ must construct dummy report class """ mocker.patch("ahriman.models.report_settings.ReportSettings.from_option", return_value=ReportSettings.Disabled) report_mock = mocker.patch("ahriman.core.report.report.Report.generate") - Report.load("x86_64", configuration, "disabled").run([], []) - report_mock.assert_called_once_with([], []) + Report.load("x86_64", configuration, "disabled").run([], result) + report_mock.assert_called_once_with([], result) -def test_report_email(configuration: Configuration, mocker: MockerFixture) -> None: +def test_report_console(configuration: Configuration, result: Result, mocker: MockerFixture) -> None: + """ + must generate console report + """ + report_mock = mocker.patch("ahriman.core.report.console.Console.generate") + Report.load("x86_64", configuration, "console").run([], result) + report_mock.assert_called_once_with([], result) + + +def test_report_email(configuration: Configuration, result: Result, mocker: MockerFixture) -> None: """ must generate email report """ report_mock = mocker.patch("ahriman.core.report.email.Email.generate") - Report.load("x86_64", configuration, "email").run([], []) - report_mock.assert_called_once_with([], []) + Report.load("x86_64", configuration, "email").run([], result) + report_mock.assert_called_once_with([], result) -def test_report_html(configuration: Configuration, mocker: MockerFixture) -> None: +def test_report_html(configuration: Configuration, result: Result, mocker: MockerFixture) -> None: """ must generate html report """ report_mock = mocker.patch("ahriman.core.report.html.HTML.generate") - Report.load("x86_64", configuration, "html").run([], []) - report_mock.assert_called_once_with([], []) + Report.load("x86_64", configuration, "html").run([], result) + report_mock.assert_called_once_with([], result) diff --git a/tests/ahriman/core/repository/test_executor.py b/tests/ahriman/core/repository/test_executor.py index 9a8b15e0..579b766f 100644 --- a/tests/ahriman/core/repository/test_executor.py +++ b/tests/ahriman/core/repository/test_executor.py @@ -35,7 +35,6 @@ def test_process_build(executor: Executor, package_ahriman: Package, mocker: Moc mocker.patch("ahriman.core.build_tools.task.Task.init") move_mock = mocker.patch("shutil.move") status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_building") - built_packages_mock = mocker.patch("ahriman.core.repository.executor.Executor.packages_built") executor.process_build([package_ahriman]) # must move files (once) @@ -45,8 +44,6 @@ def test_process_build(executor: Executor, package_ahriman: Package, mocker: Moc # must clear directory from ahriman.core.repository.cleaner import Cleaner Cleaner.clear_build.assert_called_once_with() - # must return build packages after all - built_packages_mock.assert_called_once_with() def test_process_build_failure(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None: diff --git a/tests/ahriman/models/test_report_settings.py b/tests/ahriman/models/test_report_settings.py index effb67c9..86f489d4 100644 --- a/tests/ahriman/models/test_report_settings.py +++ b/tests/ahriman/models/test_report_settings.py @@ -21,3 +21,6 @@ def test_from_option_valid() -> None: assert ReportSettings.from_option("email") == ReportSettings.Email assert ReportSettings.from_option("EmAil") == ReportSettings.Email + + assert ReportSettings.from_option("console") == ReportSettings.Console + assert ReportSettings.from_option("conSOle") == ReportSettings.Console diff --git a/tests/ahriman/models/test_result.py b/tests/ahriman/models/test_result.py new file mode 100644 index 00000000..14461bb7 --- /dev/null +++ b/tests/ahriman/models/test_result.py @@ -0,0 +1,119 @@ +import pytest + +from ahriman.core.exceptions import SuccessFailed +from ahriman.models.package import Package +from ahriman.models.result import Result + + +def test_add_failed(package_ahriman: Package) -> None: + """ + must add package to failed list + """ + result = Result() + result.add_failed(package_ahriman) + assert result.failed == [package_ahriman] + assert not result.success + + +def test_add_success(package_ahriman: Package) -> None: + """ + must add package to success list + """ + result = Result() + result.add_success(package_ahriman) + assert result.success == [package_ahriman] + assert not result.failed + + +def test_merge(package_ahriman: Package, package_python_schedule: Package) -> None: + """ + must merge success packages + """ + left = Result() + left.add_success(package_ahriman) + right = Result() + right.add_success(package_python_schedule) + + result = left.merge(right) + assert result.success == [package_ahriman, package_python_schedule] + assert not left.failed + + +def test_merge_failed(package_ahriman: Package) -> None: + """ + must merge and remove failed packages from success list + """ + left = Result() + left.add_success(package_ahriman) + right = Result() + right.add_failed(package_ahriman) + + result = left.merge(right) + assert result.failed == [package_ahriman] + assert not left.success + + +def test_merge_exception(package_ahriman: Package) -> None: + """ + must raise exception in case if package was failed + """ + left = Result() + left.add_failed(package_ahriman) + right = Result() + right.add_success(package_ahriman) + + with pytest.raises(SuccessFailed): + left.merge(right) + + +def test_eq(package_ahriman: Package, package_python_schedule: Package) -> None: + """ + must return True for same objects + """ + left = Result() + left.add_success(package_ahriman) + left.add_failed(package_python_schedule) + right = Result() + right.add_success(package_ahriman) + right.add_failed(package_python_schedule) + + assert left == right + + +def test_eq_false(package_ahriman: Package) -> None: + """ + must return False in case if lists do not match + """ + left = Result() + left.add_success(package_ahriman) + right = Result() + right.add_failed(package_ahriman) + + assert left != right + + +def test_eq_false_failed(package_ahriman: Package) -> None: + """ + must return False in case if failed does not match + """ + left = Result() + left.add_failed(package_ahriman) + + assert left != Result() + + +def test_eq_false_success(package_ahriman: Package) -> None: + """ + must return False in case if success does not match + """ + left = Result() + left.add_success(package_ahriman) + + assert left != Result() + + +def test_eq_other() -> None: + """ + must return False in case if object is not an instance of result + """ + assert Result() != 42 diff --git a/tests/testresources/core/ahriman.ini b/tests/testresources/core/ahriman.ini index 93e81e11..0397c762 100644 --- a/tests/testresources/core/ahriman.ini +++ b/tests/testresources/core/ahriman.ini @@ -42,6 +42,9 @@ receivers = mail@example.com sender = mail@example.com template_path = ../web/templates/repo-index.jinja2 +[console] +use_utf = yes + [html] path = homepage =