add configurable exit codes to some commands (#55)

This commit is contained in:
Evgenii Alekseev 2022-04-01 18:30:11 +03:00 committed by GitHub
parent 06ec16ac77
commit e909e4f570
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 279 additions and 28 deletions

View File

@ -36,6 +36,7 @@ if [ -n "$AHRIMAN_API_USER" ]; then
# python getpass does not read from stdin # python getpass does not read from stdin
# see thread https://mail.python.org/pipermail/python-dev/2008-February/077235.html # see thread https://mail.python.org/pipermail/python-dev/2008-February/077235.html
# WARNING with debug mode password will be put to stdout # WARNING with debug mode password will be put to stdout
ahriman "${AHRIMAN_DEFAULT_ARGS[@]}" user-list --error-on-empty "$AHRIMAN_API_USER" > /dev/null ||
ahriman "${AHRIMAN_DEFAULT_ARGS[@]}" user-add --as-service --role write --secure "$AHRIMAN_API_USER" -p "$(openssl rand -base64 20)" ahriman "${AHRIMAN_DEFAULT_ARGS[@]}" user-add --as-service --role write --secure "$AHRIMAN_API_USER" -p "$(openssl rand -base64 20)"
fi fi

View File

@ -112,6 +112,7 @@ def _set_aur_search_parser(root: SubParserAction) -> argparse.ArgumentParser:
description="search for package in AUR using API", formatter_class=_formatter) description="search for package in AUR using API", formatter_class=_formatter)
parser.add_argument("search", help="search terms, can be specified multiple times, result will match all terms", parser.add_argument("search", help="search terms, can be specified multiple times, result will match all terms",
nargs="+") nargs="+")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.add_argument("-i", "--info", help="show additional package information", action="store_true") parser.add_argument("-i", "--info", help="show additional package information", action="store_true")
parser.add_argument("--sort-by", help="sort field by this field. In case if two packages have the same value of " parser.add_argument("--sort-by", help="sort field by this field. In case if two packages have the same value of "
"the specified field, they will be always sorted by name", "the specified field, they will be always sorted by name",
@ -189,6 +190,7 @@ def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
"5) and finally you can add package from AUR.", "5) and finally you can add package from AUR.",
formatter_class=_formatter) formatter_class=_formatter)
parser.add_argument("package", help="package source (base name, path to local files, remote URL)", nargs="+") parser.add_argument("package", help="package source (base name, path to local files, remote URL)", nargs="+")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.add_argument("-n", "--now", help="run update function after", action="store_true") parser.add_argument("-n", "--now", help="run update function after", action="store_true")
parser.add_argument("-s", "--source", help="explicitly specify the package source for this command", parser.add_argument("-s", "--source", help="explicitly specify the package source for this command",
type=PackageSource, choices=PackageSource, default=PackageSource.Auto) type=PackageSource, choices=PackageSource, default=PackageSource.Auto)
@ -222,6 +224,7 @@ def _set_package_status_parser(root: SubParserAction) -> argparse.ArgumentParser
formatter_class=_formatter) formatter_class=_formatter)
parser.add_argument("package", help="filter status by package base", nargs="*") parser.add_argument("package", help="filter status by package base", nargs="*")
parser.add_argument("--ahriman", help="get service status itself", action="store_true") parser.add_argument("--ahriman", help="get service status itself", action="store_true")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.add_argument("-i", "--info", help="show additional package information", action="store_true") parser.add_argument("-i", "--info", help="show additional package information", action="store_true")
parser.add_argument("-s", "--status", help="filter packages by status", parser.add_argument("-s", "--status", help="filter packages by status",
type=BuildStatusEnum, choices=BuildStatusEnum) type=BuildStatusEnum, choices=BuildStatusEnum)
@ -293,6 +296,7 @@ def _set_patch_list_parser(root: SubParserAction) -> argparse.ArgumentParser:
parser = root.add_parser("patch-list", help="list patch sets", parser = root.add_parser("patch-list", help="list patch sets",
description="list available patches for the package", formatter_class=_formatter) description="list available patches for the package", formatter_class=_formatter)
parser.add_argument("package", help="package base", nargs="?") parser.add_argument("package", help="package base", nargs="?")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.set_defaults(handler=handlers.Patch, action=Action.List, architecture=[""], lock=None, no_report=True) parser.set_defaults(handler=handlers.Patch, action=Action.List, architecture=[""], lock=None, no_report=True)
return parser return parser
@ -320,6 +324,7 @@ def _set_repo_check_parser(root: SubParserAction) -> argparse.ArgumentParser:
description="check for packages updates. Same as update --dry-run --no-manual", description="check for packages updates. Same as update --dry-run --no-manual",
formatter_class=_formatter) formatter_class=_formatter)
parser.add_argument("package", help="filter check by package base", nargs="*") parser.add_argument("package", help="filter check by package base", nargs="*")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.add_argument("--no-vcs", help="do not check VCS packages", action="store_true") parser.add_argument("--no-vcs", help="do not check VCS packages", action="store_true")
parser.set_defaults(handler=handlers.Update, dry_run=True, no_aur=False, no_local=False, no_manual=True) parser.set_defaults(handler=handlers.Update, dry_run=True, no_aur=False, no_local=False, no_manual=True)
return parser return parser
@ -369,6 +374,7 @@ def _set_repo_rebuild_parser(root: SubParserAction) -> argparse.ArgumentParser:
parser.add_argument("--depends-on", help="only rebuild packages that depend on specified package", action="append") parser.add_argument("--depends-on", help="only rebuild packages that depend on specified package", action="append")
parser.add_argument("--dry-run", help="just perform check for packages without rebuild process itself", parser.add_argument("--dry-run", help="just perform check for packages without rebuild process itself",
action="store_true") action="store_true")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.set_defaults(handler=handlers.Rebuild) parser.set_defaults(handler=handlers.Rebuild)
return parser return parser
@ -484,6 +490,7 @@ def _set_repo_update_parser(root: SubParserAction) -> argparse.ArgumentParser:
formatter_class=_formatter) formatter_class=_formatter)
parser.add_argument("package", help="filter check by package base", nargs="*") parser.add_argument("package", help="filter check by package base", nargs="*")
parser.add_argument("--dry-run", help="just perform check for updates, same as check command", action="store_true") parser.add_argument("--dry-run", help="just perform check for updates, same as check command", action="store_true")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.add_argument("--no-aur", help="do not check for AUR updates. Implies --no-vcs", action="store_true") parser.add_argument("--no-aur", help="do not check for AUR updates. Implies --no-vcs", action="store_true")
parser.add_argument("--no-local", help="do not check local packages for updates", action="store_true") parser.add_argument("--no-local", help="do not check local packages for updates", action="store_true")
parser.add_argument("--no-manual", help="do not include manual updates", action="store_true") parser.add_argument("--no-manual", help="do not include manual updates", action="store_true")
@ -524,6 +531,7 @@ def _set_user_list_parser(root: SubParserAction) -> argparse.ArgumentParser:
description="list users from the user mapping and their roles", description="list users from the user mapping and their roles",
formatter_class=_formatter) formatter_class=_formatter)
parser.add_argument("username", help="filter users by username", nargs="?") parser.add_argument("username", help="filter users by username", nargs="?")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.add_argument("-r", "--role", help="filter users by role", type=UserAccess, choices=UserAccess) parser.add_argument("-r", "--role", help="filter users by role", type=UserAccess, choices=UserAccess)
parser.set_defaults(handler=handlers.User, action=Action.List, architecture=[""], lock=None, no_report=True, # nosec parser.set_defaults(handler=handlers.User, action=Action.List, architecture=[""], lock=None, no_report=True, # nosec
password="", quiet=True, unsafe=True) password="", quiet=True, unsafe=True)

View File

@ -132,7 +132,7 @@ class Repository(Properties):
result.extend(unknown_aur(package)) # local package not found result.extend(unknown_aur(package)) # local package not found
return result return result
def update(self, updates: Iterable[Package]) -> None: def update(self, updates: Iterable[Package]) -> Result:
""" """
run package updates run package updates
:param updates: list of packages to update :param updates: list of packages to update
@ -144,8 +144,9 @@ class Repository(Properties):
self._finalize(result.merge(update_result)) self._finalize(result.merge(update_result))
# process built packages # process built packages
build_result = Result()
packages = self.repository.packages_built() packages = self.repository.packages_built()
process_update(packages, Result()) process_update(packages, build_result)
# process manual packages # process manual packages
tree = Tree.load(updates, self.database) tree = Tree.load(updates, self.database)
@ -155,6 +156,8 @@ class Repository(Properties):
packages = self.repository.packages_built() packages = self.repository.packages_built()
process_update(packages, build_result) process_update(packages, build_result)
return build_result
def updates(self, filter_packages: Iterable[str], no_aur: bool, no_local: bool, no_manual: bool, no_vcs: bool, 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]: log_fn: Callable[[str], None]) -> List[Package]:
""" """

View File

@ -48,4 +48,5 @@ class Add(Handler):
return return
packages = application.updates(args.package, True, True, False, True, application.logger.info) packages = application.updates(args.package, True, True, False, True, application.logger.info)
application.update(packages) result = application.update(packages)
Add.check_if_empty(args.exit_code, result.is_empty)

View File

@ -119,3 +119,13 @@ class Handler:
:param unsafe: if set no user check will be performed before path creation :param unsafe: if set no user check will be performed before path creation
""" """
raise NotImplementedError raise NotImplementedError
@staticmethod
def check_if_empty(enabled: bool, predicate: bool) -> None:
"""
check condition and flag and raise ExitCode exception in case if it is enabled and condition match
:param enabled: if False no check will be performed
:param predicate: indicates condition on which exception should be thrown
"""
if enabled and predicate:
raise ExitCode()

View File

@ -51,7 +51,7 @@ class Patch(Handler):
application = Application(architecture, configuration, no_report, unsafe) application = Application(architecture, configuration, no_report, unsafe)
if args.action == Action.List: if args.action == Action.List:
Patch.patch_set_list(application, args.package) Patch.patch_set_list(application, args.package, args.exit_code)
elif args.action == Action.Remove: elif args.action == Action.Remove:
Patch.patch_set_remove(application, args.package) Patch.patch_set_remove(application, args.package)
elif args.action == Action.Update: elif args.action == Action.Update:
@ -71,13 +71,16 @@ class Patch(Handler):
application.database.patches_insert(package.base, patch) application.database.patches_insert(package.base, patch)
@staticmethod @staticmethod
def patch_set_list(application: Application, package_base: Optional[str]) -> None: def patch_set_list(application: Application, package_base: Optional[str], exit_code: bool) -> None:
""" """
list patches available for the package base list patches available for the package base
:param application: application instance :param application: application instance
:param package_base: package base :param package_base: package base
:param exit_code: raise ExitCode on empty search result
""" """
patches = application.database.patches_list(package_base) patches = application.database.patches_list(package_base)
Patch.check_if_empty(exit_code, not patches)
for base, patch in patches.items(): for base, patch in patches.items():
content = base if package_base is None else patch content = base if package_base is None else patch
StringPrinter(content).print(verbose=True) StringPrinter(content).print(verbose=True)

View File

@ -47,9 +47,12 @@ class Rebuild(Handler):
application = Application(architecture, configuration, no_report, unsafe) application = Application(architecture, configuration, no_report, unsafe)
updates = application.repository.packages_depends_on(depends_on) updates = application.repository.packages_depends_on(depends_on)
Rebuild.check_if_empty(args.exit_code, not updates)
if args.dry_run: if args.dry_run:
for package in updates: for package in updates:
UpdatePrinter(package, package.version).print(verbose=True) UpdatePrinter(package, package.version).print(verbose=True)
return return
application.update(updates) result = application.update(updates)
Rebuild.check_if_empty(args.exit_code, result.is_empty)

View File

@ -45,6 +45,7 @@ class RemoveUnknown(Handler):
""" """
application = Application(architecture, configuration, no_report, unsafe) application = Application(architecture, configuration, no_report, unsafe)
unknown_packages = application.unknown() unknown_packages = application.unknown()
if args.dry_run: if args.dry_run:
for package in sorted(unknown_packages): for package in sorted(unknown_packages):
StringPrinter(package).print(args.info) StringPrinter(package).print(args.info)

View File

@ -37,8 +37,7 @@ class Search(Handler):
""" """
ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture" ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture"
# later we will have to remove some fields from here (lists) SORT_FIELDS = {field.name for field in fields(AURPackage) if field.default_factory is not list}
SORT_FIELDS = {pair.name for pair in fields(AURPackage)}
@classmethod @classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
@ -52,6 +51,7 @@ class Search(Handler):
:param unsafe: if set no user check will be performed before path creation :param unsafe: if set no user check will be performed before path creation
""" """
packages_list = AUR.multisearch(*args.search) packages_list = AUR.multisearch(*args.search)
Search.check_if_empty(args.exit_code, not packages_list)
for package in Search.sort(packages_list, args.sort_by): for package in Search.sort(packages_list, args.sort_by):
AurPrinter(package).print(args.info) AurPrinter(package).print(args.info)

View File

@ -60,6 +60,8 @@ class Status(Handler):
else: else:
packages = client.get(None) packages = client.get(None)
Status.check_if_empty(args.exit_code, not packages)
comparator: Callable[[Tuple[Package, BuildStatus]], str] = lambda item: item[0].base comparator: Callable[[Tuple[Package, BuildStatus]], str] = lambda item: item[0].base
filter_fn: Callable[[Tuple[Package, BuildStatus]], bool] =\ filter_fn: Callable[[Tuple[Package, BuildStatus]], bool] =\
lambda item: args.status is None or item[1].status == args.status lambda item: args.status is None or item[1].status == args.status

View File

@ -45,10 +45,12 @@ class Update(Handler):
application = Application(architecture, configuration, no_report, unsafe) application = Application(architecture, configuration, no_report, unsafe)
packages = application.updates(args.package, args.no_aur, args.no_local, args.no_manual, args.no_vcs, packages = application.updates(args.package, args.no_aur, args.no_local, args.no_manual, args.no_vcs,
Update.log_fn(application, args.dry_run)) Update.log_fn(application, args.dry_run))
Update.check_if_empty(args.exit_code, not packages)
if args.dry_run: if args.dry_run:
return return
application.update(packages) result = application.update(packages)
Update.check_if_empty(args.exit_code, result.is_empty)
@staticmethod @staticmethod
def log_fn(application: Application, dry_run: bool) -> Callable[[str], None]: def log_fn(application: Application, dry_run: bool) -> Callable[[str], None]:

View File

@ -60,8 +60,10 @@ class User(Handler):
User.configuration_create(auth_configuration, user, salt, args.as_service, args.secure) User.configuration_create(auth_configuration, user, salt, args.as_service, args.secure)
database.user_update(user.hash_password(salt)) database.user_update(user.hash_password(salt))
elif args.action == Action.List: elif args.action == Action.List:
for found_user in database.user_list(args.username, args.access): users = database.user_list(args.username, args.role)
UserPrinter(found_user).print(verbose=True) User.check_if_empty(args.exit_code, not users)
for user in users:
UserPrinter(user).print(verbose=True)
elif args.action == Action.Remove: elif args.action == Action.Remove:
database.user_remove(args.username) database.user_remove(args.username)

View File

@ -48,6 +48,13 @@ class Result:
""" """
return list(self._failed.values()) return list(self._failed.values())
@property
def is_empty(self) -> bool:
"""
:return: True in case if success list is empty and False otherwise
"""
return not bool(self._success)
@property @property
def success(self) -> List[Package]: def success(self) -> List[Package]:
""" """

View File

@ -136,3 +136,14 @@ def test_run(args: argparse.Namespace, configuration: Configuration) -> None:
""" """
with pytest.raises(NotImplementedError): with pytest.raises(NotImplementedError):
Handler.run(args, "x86_64", configuration, True, True) Handler.run(args, "x86_64", configuration, True, True)
def test_check_if_empty() -> None:
"""
must raise exception in case if predicate is True and enabled
"""
Handler.check_if_empty(False, False)
Handler.check_if_empty(True, False)
Handler.check_if_empty(False, True)
with pytest.raises(ExitCode):
Handler.check_if_empty(True, True)

View File

@ -7,6 +7,7 @@ from ahriman.application.handlers import Add
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.package_source import PackageSource from ahriman.models.package_source import PackageSource
from ahriman.models.result import Result
def _default_args(args: argparse.Namespace) -> argparse.Namespace: def _default_args(args: argparse.Namespace) -> argparse.Namespace:
@ -16,6 +17,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
:return: generated arguments for these test cases :return: generated arguments for these test cases
""" """
args.package = [] args.package = []
args.exit_code = False
args.now = False args.now = False
args.source = PackageSource.Auto args.source = PackageSource.Auto
args.without_dependencies = False args.without_dependencies = False
@ -41,11 +43,32 @@ def test_run_with_updates(args: argparse.Namespace, configuration: Configuration
""" """
args = _default_args(args) args = _default_args(args)
args.now = True args.now = True
result = Result()
result.add_success(package_ahriman)
mocker.patch("ahriman.application.application.Application.add") mocker.patch("ahriman.application.application.Application.add")
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
application_mock = mocker.patch("ahriman.application.application.Application.update") application_mock = mocker.patch("ahriman.application.application.Application.update", return_value=result)
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty")
updates_mock = mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman]) updates_mock = mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman])
Add.run(args, "x86_64", configuration, True, False) Add.run(args, "x86_64", configuration, True, False)
updates_mock.assert_called_once_with(args.package, True, True, False, True, pytest.helpers.anyvar(int)) updates_mock.assert_called_once_with(args.package, True, True, False, True, pytest.helpers.anyvar(int))
application_mock.assert_called_once_with([package_ahriman]) application_mock.assert_called_once_with([package_ahriman])
check_mock.assert_called_once_with(False, False)
def test_run_empty_exception(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must raise ExitCode exception on empty result
"""
args = _default_args(args)
args.now = True
args.exit_code = True
mocker.patch("ahriman.application.application.Application.add")
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
mocker.patch("ahriman.application.application.Application.update", return_value=Result())
mocker.patch("ahriman.application.application.Application.updates")
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty")
Add.run(args, "x86_64", configuration, True, False)
check_mock.assert_called_once_with(True, True)

View File

@ -1,7 +1,6 @@
import argparse import argparse
import pytest import pytest
from pathlib import Path
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from ahriman.application.application import Application from ahriman.application.application import Application
@ -18,6 +17,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
:return: generated arguments for these test cases :return: generated arguments for these test cases
""" """
args.package = "ahriman" args.package = "ahriman"
args.exit_code = False
args.remove = False args.remove = False
args.track = ["*.diff", "*.patch"] args.track = ["*.diff", "*.patch"]
return args return args
@ -46,7 +46,7 @@ def test_run_list(args: argparse.Namespace, configuration: Configuration, mocker
application_mock = mocker.patch("ahriman.application.handlers.patch.Patch.patch_set_list") application_mock = mocker.patch("ahriman.application.handlers.patch.Patch.patch_set_list")
Patch.run(args, "x86_64", configuration, True, False) Patch.run(args, "x86_64", configuration, True, False)
application_mock.assert_called_once_with(pytest.helpers.anyvar(int), args.package) application_mock.assert_called_once_with(pytest.helpers.anyvar(int), args.package, False)
def test_run_remove(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None: def test_run_remove(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
@ -66,25 +66,25 @@ def test_patch_set_list(application: Application, mocker: MockerFixture) -> None
""" """
must list available patches for the command must list available patches for the command
""" """
mocker.patch("pathlib.Path.is_dir", return_value=True)
get_mock = mocker.patch("ahriman.core.database.sqlite.SQLite.patches_list", return_value={"ahriman": "patch"}) get_mock = mocker.patch("ahriman.core.database.sqlite.SQLite.patches_list", return_value={"ahriman": "patch"})
print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print") print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print")
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty")
Patch.patch_set_list(application, "ahriman") Patch.patch_set_list(application, "ahriman", False)
get_mock.assert_called_once_with("ahriman") get_mock.assert_called_once_with("ahriman")
print_mock.assert_called_once_with(verbose=True) print_mock.assert_called_once_with(verbose=True)
check_mock.assert_called_once_with(False, False)
def test_patch_set_list_no_patches(application: Application, mocker: MockerFixture) -> None: def test_patch_set_list_empty_exception(application: Application, mocker: MockerFixture) -> None:
""" """
must not fail if no patches directory found must raise ExitCode exception on empty patch list
""" """
mocker.patch("pathlib.Path.is_dir", return_value=False) mocker.patch("ahriman.core.database.sqlite.SQLite.patches_list", return_value={})
mocker.patch("ahriman.core.database.sqlite.SQLite.patches_get", return_value=None) check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty")
print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print")
Patch.patch_set_list(application, "ahriman") Patch.patch_set_list(application, "ahriman", True)
print_mock.assert_not_called() check_mock.assert_called_once_with(True, True)
def test_patch_set_create(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None: def test_patch_set_create(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:

View File

@ -1,10 +1,13 @@
import argparse import argparse
import pytest
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from unittest import mock
from ahriman.application.handlers import Rebuild from ahriman.application.handlers import Rebuild
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.result import Result
def _default_args(args: argparse.Namespace) -> argparse.Namespace: def _default_args(args: argparse.Namespace) -> argparse.Namespace:
@ -15,6 +18,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
""" """
args.depends_on = [] args.depends_on = []
args.dry_run = False args.dry_run = False
args.exit_code = False
return args return args
@ -24,14 +28,18 @@ def test_run(args: argparse.Namespace, package_ahriman: Package,
must run command must run command
""" """
args = _default_args(args) args = _default_args(args)
result = Result()
result.add_success(package_ahriman)
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
application_packages_mock = mocker.patch("ahriman.core.repository.repository.Repository.packages_depends_on", application_packages_mock = mocker.patch("ahriman.core.repository.repository.Repository.packages_depends_on",
return_value=[package_ahriman]) return_value=[package_ahriman])
application_mock = mocker.patch("ahriman.application.application.Application.update") application_mock = mocker.patch("ahriman.application.application.Application.update", return_value=result)
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty")
Rebuild.run(args, "x86_64", configuration, True, False) Rebuild.run(args, "x86_64", configuration, True, False)
application_packages_mock.assert_called_once_with(None) application_packages_mock.assert_called_once_with(None)
application_mock.assert_called_once_with([package_ahriman]) application_mock.assert_called_once_with([package_ahriman])
check_mock.assert_has_calls([mock.call(False, False), mock.call(False, False)])
def test_run_dry_run(args: argparse.Namespace, configuration: Configuration, def test_run_dry_run(args: argparse.Namespace, configuration: Configuration,
@ -44,9 +52,11 @@ def test_run_dry_run(args: argparse.Namespace, configuration: Configuration,
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
mocker.patch("ahriman.core.repository.repository.Repository.packages_depends_on", return_value=[package_ahriman]) mocker.patch("ahriman.core.repository.repository.Repository.packages_depends_on", return_value=[package_ahriman])
application_mock = mocker.patch("ahriman.application.application.Application.update") application_mock = mocker.patch("ahriman.application.application.Application.update")
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty")
Rebuild.run(args, "x86_64", configuration, True, False) Rebuild.run(args, "x86_64", configuration, True, False)
application_mock.assert_not_called() application_mock.assert_not_called()
check_mock.assert_called_once_with(False, False)
def test_run_filter(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None: def test_run_filter(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
@ -74,3 +84,35 @@ def test_run_without_filter(args: argparse.Namespace, configuration: Configurati
Rebuild.run(args, "x86_64", configuration, True, False) Rebuild.run(args, "x86_64", configuration, True, False)
application_packages_mock.assert_called_once_with(None) application_packages_mock.assert_called_once_with(None)
def test_run_update_empty_exception(args: argparse.Namespace, configuration: Configuration,
mocker: MockerFixture) -> None:
"""
must raise ExitCode exception on empty update list
"""
args = _default_args(args)
args.exit_code = True
args.dry_run = True
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
mocker.patch("ahriman.core.repository.repository.Repository.packages_depends_on", return_value=[])
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty")
Rebuild.run(args, "x86_64", configuration, True, False)
check_mock.assert_called_once_with(True, True)
def test_run_build_empty_exception(args: argparse.Namespace, configuration: Configuration,
package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must raise ExitCode exception on empty update result
"""
args = _default_args(args)
args.exit_code = True
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
mocker.patch("ahriman.core.repository.repository.Repository.packages_depends_on", return_value=[package_ahriman])
mocker.patch("ahriman.application.application.Application.update", return_value=Result())
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty")
Rebuild.run(args, "x86_64", configuration, True, False)
check_mock.assert_has_calls([mock.call(True, False), mock.call(True, True)])

View File

@ -17,6 +17,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
:return: generated arguments for these test cases :return: generated arguments for these test cases
""" """
args.search = ["ahriman"] args.search = ["ahriman"]
args.exit_code = False
args.info = False args.info = False
args.sort_by = "name" args.sort_by = "name"
return args return args
@ -29,13 +30,29 @@ def test_run(args: argparse.Namespace, configuration: Configuration, aur_package
""" """
args = _default_args(args) args = _default_args(args)
search_mock = mocker.patch("ahriman.core.alpm.aur.AUR.multisearch", return_value=[aur_package_ahriman]) search_mock = mocker.patch("ahriman.core.alpm.aur.AUR.multisearch", return_value=[aur_package_ahriman])
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty")
print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print") print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print")
Search.run(args, "x86_64", configuration, True, False) Search.run(args, "x86_64", configuration, True, False)
search_mock.assert_called_once_with("ahriman") search_mock.assert_called_once_with("ahriman")
check_mock.assert_called_once_with(False, False)
print_mock.assert_called_once_with(False) print_mock.assert_called_once_with(False)
def test_run_empty_exception(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run command
"""
args = _default_args(args)
args.exit_code = True
mocker.patch("ahriman.core.alpm.aur.AUR.multisearch", return_value=[])
mocker.patch("ahriman.core.formatters.printer.Printer.print")
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty")
Search.run(args, "x86_64", configuration, True, False)
check_mock.assert_called_once_with(True, True)
def test_run_sort(args: argparse.Namespace, configuration: Configuration, aur_package_ahriman: AURPackage, def test_run_sort(args: argparse.Namespace, configuration: Configuration, aur_package_ahriman: AURPackage,
mocker: MockerFixture) -> None: mocker: MockerFixture) -> None:
""" """
@ -95,5 +112,5 @@ def test_sort_fields() -> None:
""" """
must store valid field list which are allowed to be used for sorting must store valid field list which are allowed to be used for sorting
""" """
expected = {pair.name for pair in dataclasses.fields(AURPackage)} expected = {field.name for field in dataclasses.fields(AURPackage)}
assert all(field in expected for field in Search.SORT_FIELDS) assert all(field in expected for field in Search.SORT_FIELDS)

View File

@ -16,6 +16,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
:return: generated arguments for these test cases :return: generated arguments for these test cases
""" """
args.ahriman = True args.ahriman = True
args.exit_code = False
args.info = False args.info = False
args.package = [] args.package = []
args.status = None args.status = None
@ -33,14 +34,31 @@ def test_run(args: argparse.Namespace, configuration: Configuration, package_ahr
packages_mock = mocker.patch("ahriman.core.status.client.Client.get", packages_mock = mocker.patch("ahriman.core.status.client.Client.get",
return_value=[(package_ahriman, BuildStatus(BuildStatusEnum.Success)), return_value=[(package_ahriman, BuildStatus(BuildStatusEnum.Success)),
(package_python_schedule, BuildStatus(BuildStatusEnum.Failed))]) (package_python_schedule, BuildStatus(BuildStatusEnum.Failed))])
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty")
print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print") print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print")
Status.run(args, "x86_64", configuration, True, False) Status.run(args, "x86_64", configuration, True, False)
application_mock.assert_called_once_with() application_mock.assert_called_once_with()
packages_mock.assert_called_once_with(None) packages_mock.assert_called_once_with(None)
check_mock.assert_called_once_with(False, False)
print_mock.assert_has_calls([mock.call(False) for _ in range(3)]) print_mock.assert_has_calls([mock.call(False) for _ in range(3)])
def test_run_empty_exception(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must raise ExitCode exception on empty status result
"""
args = _default_args(args)
args.exit_code = True
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
mocker.patch("ahriman.core.status.client.Client.get_self")
mocker.patch("ahriman.core.status.client.Client.get", return_value=[])
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty")
Status.run(args, "x86_64", configuration, True, False)
check_mock.assert_called_once_with(True, True)
def test_run_verbose(args: argparse.Namespace, configuration: Configuration, package_ahriman: Package, def test_run_verbose(args: argparse.Namespace, configuration: Configuration, package_ahriman: Package,
mocker: MockerFixture) -> None: mocker: MockerFixture) -> None:
""" """

View File

@ -2,11 +2,13 @@ import argparse
import pytest import pytest
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from unittest import mock
from ahriman.application.application import Application from ahriman.application.application import Application
from ahriman.application.handlers import Update from ahriman.application.handlers import Update
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.result import Result
def _default_args(args: argparse.Namespace) -> argparse.Namespace: def _default_args(args: argparse.Namespace) -> argparse.Namespace:
@ -17,6 +19,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
""" """
args.package = [] args.package = []
args.dry_run = False args.dry_run = False
args.exit_code = False
args.no_aur = False args.no_aur = False
args.no_local = False args.no_local = False
args.no_manual = False args.no_manual = False
@ -30,14 +33,49 @@ def test_run(args: argparse.Namespace, package_ahriman: Package,
must run command must run command
""" """
args = _default_args(args) args = _default_args(args)
result = Result()
result.add_success(package_ahriman)
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
application_mock = mocker.patch("ahriman.application.application.Application.update") application_mock = mocker.patch("ahriman.application.application.Application.update", return_value=result)
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty")
updates_mock = mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman]) updates_mock = mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman])
Update.run(args, "x86_64", configuration, True, False) Update.run(args, "x86_64", configuration, True, False)
application_mock.assert_called_once_with([package_ahriman]) application_mock.assert_called_once_with([package_ahriman])
updates_mock.assert_called_once_with(args.package, args.no_aur, args.no_local, args.no_manual, args.no_vcs, updates_mock.assert_called_once_with(args.package, args.no_aur, args.no_local, args.no_manual, args.no_vcs,
pytest.helpers.anyvar(int)) pytest.helpers.anyvar(int))
check_mock.assert_has_calls([mock.call(False, False), mock.call(False, False)])
def test_run_empty_exception(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must raise ExitCode exception on empty update list
"""
args = _default_args(args)
args.exit_code = True
args.dry_run = True
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
mocker.patch("ahriman.application.application.Application.updates", return_value=[])
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty")
Update.run(args, "x86_64", configuration, True, False)
check_mock.assert_called_once_with(True, True)
def test_run_update_empty_exception(args: argparse.Namespace, package_ahriman: Package,
configuration: Configuration, mocker: MockerFixture) -> None:
"""
must raise ExitCode exception on empty build result
"""
args = _default_args(args)
args.exit_code = True
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
mocker.patch("ahriman.application.application.Application.update", return_value=Result())
mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman])
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty")
Update.run(args, "x86_64", configuration, True, False)
check_mock.assert_has_calls([mock.call(True, False), mock.call(True, True)])
def test_run_dry_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None: def test_run_dry_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
@ -47,11 +85,15 @@ def test_run_dry_run(args: argparse.Namespace, configuration: Configuration, moc
args = _default_args(args) args = _default_args(args)
args.dry_run = True args.dry_run = True
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
application_mock = mocker.patch("ahriman.application.application.Application.update")
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty")
updates_mock = mocker.patch("ahriman.application.application.Application.updates") updates_mock = mocker.patch("ahriman.application.application.Application.updates")
Update.run(args, "x86_64", configuration, True, False) Update.run(args, "x86_64", configuration, True, False)
updates_mock.assert_called_once_with(args.package, args.no_aur, args.no_local, args.no_manual, args.no_vcs, updates_mock.assert_called_once_with(args.package, args.no_aur, args.no_local, args.no_manual, args.no_vcs,
pytest.helpers.anyvar(int)) pytest.helpers.anyvar(int))
application_mock.assert_not_called()
check_mock.assert_called_once_with(False, pytest.helpers.anyvar(int))
def test_log_fn(application: Application, mocker: MockerFixture) -> None: def test_log_fn(application: Application, mocker: MockerFixture) -> None:

View File

@ -22,6 +22,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
args.username = "user" args.username = "user"
args.action = Action.Update args.action = Action.Update
args.as_service = False args.as_service = False
args.exit_code = False
args.password = "pa55w0rd" args.password = "pa55w0rd"
args.role = UserAccess.Read args.role = UserAccess.Read
args.secure = False args.secure = False
@ -58,12 +59,29 @@ def test_run_list(args: argparse.Namespace, configuration: Configuration, databa
""" """
args = _default_args(args) args = _default_args(args)
args.action = Action.List args.action = Action.List
args.access = None
mocker.patch("ahriman.core.database.sqlite.SQLite.load", return_value=database) mocker.patch("ahriman.core.database.sqlite.SQLite.load", return_value=database)
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty")
list_mock = mocker.patch("ahriman.core.database.sqlite.SQLite.user_list", return_value=[user]) list_mock = mocker.patch("ahriman.core.database.sqlite.SQLite.user_list", return_value=[user])
User.run(args, "x86_64", configuration, True, False) User.run(args, "x86_64", configuration, True, False)
list_mock.assert_called_once_with("user", None) list_mock.assert_called_once_with("user", args.role)
check_mock.assert_called_once_with(False, False)
def test_run_empty_exception(args: argparse.Namespace, configuration: Configuration, database: SQLite,
mocker: MockerFixture) -> None:
"""
must raise ExitCode exception on empty user list
"""
args = _default_args(args)
args.action = Action.List
args.exit_code = True
mocker.patch("ahriman.core.database.sqlite.SQLite.load", return_value=database)
mocker.patch("ahriman.core.database.sqlite.SQLite.user_list", return_value=[])
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty")
User.run(args, "x86_64", configuration, True, False)
check_mock.assert_called_once_with(True, True)
def test_run_remove(args: argparse.Namespace, configuration: Configuration, database: SQLite, def test_run_remove(args: argparse.Namespace, configuration: Configuration, database: SQLite,

View File

@ -5,6 +5,43 @@ from ahriman.models.package import Package
from ahriman.models.result import Result from ahriman.models.result import Result
def test_is_empty() -> None:
"""
must return is empty for empty builds
"""
result = Result()
assert result.is_empty
def test_non_empty_success(package_ahriman: Package) -> None:
"""
must be non-empty if there is success build
"""
result = Result()
result.add_success(package_ahriman)
assert not result.is_empty
def test_is_empty_failed(package_ahriman: Package) -> None:
"""
must be empty if there is only failed build
"""
result = Result()
result.add_failed(package_ahriman)
assert result.is_empty
def test_non_empty_full(package_ahriman: Package) -> None:
"""
must be non-empty if there are both failed and success builds
"""
result = Result()
result.add_failed(package_ahriman)
result.add_success(package_ahriman)
assert not result.is_empty
def test_add_failed(package_ahriman: Package) -> None: def test_add_failed(package_ahriman: Package) -> None:
""" """
must add package to failed list must add package to failed list