add shell and version parser

This commit is contained in:
Evgenii Alekseev 2022-05-23 19:10:26 +03:00
parent 3a5268993e
commit d7966e419d
22 changed files with 511 additions and 26 deletions

View File

@ -11,9 +11,9 @@ assignees: ''
A clear and concise description of what the bug is.
### Steps to Reproduce
### Steps to reproduce
Steps to reproduce the behavior (commands, environment etc)
Steps to reproduce the behavior (commands, environment etc).
### Expected behavior
@ -21,4 +21,8 @@ A clear and concise description of what you expected to happen.
### Logs
Add logs to help explain your problem. Logs to stderr can be generated by using `--no-log` command line option.
Add logs to help explain your problem. By default, the application writes logs into `/dev/log` which is usually default systemd journal and can be accessed by `journalctl` command.
You can also attach any additional information which can be helpful, e.g. configuration used by the application (be aware of passwords and other secrets if any); it can be generated by using `ahriman config` command.
It is also sometimes useful to have information about installed packages which can be accessed by `ahriman version` command.

View File

@ -13,7 +13,7 @@ Brief description of the feature required
### Cause of the feature request
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
A clear and concise description of what the problem is. E.g. I'm always frustrated when [...]
### Proposed changes and/or features

View File

@ -3,9 +3,9 @@
ahriman
.SH SYNOPSIS
.B ahriman
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--no-report] [-q] [--unsafe] [-v] {aur-search,search,help,help-commands-unsafe,key-import,package-add,add,package-update,package-remove,remove,package-status,status,package-status-remove,package-status-update,status-update,patch-add,patch-list,patch-remove,repo-backup,repo-check,check,repo-clean,clean,repo-config,config,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-report,report,repo-restore,repo-setup,init,repo-init,setup,repo-sign,sign,repo-status-update,repo-sync,sync,repo-triggers,repo-update,update,user-add,user-list,user-remove,web} ...
[-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--no-report] [-q] [--unsafe] [-V] {aur-search,search,help,help-commands-unsafe,key-import,package-add,add,package-update,package-remove,remove,package-status,status,package-status-remove,package-status-update,status-update,patch-add,patch-list,patch-remove,repo-backup,repo-check,check,repo-clean,clean,repo-config,config,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-report,report,repo-restore,repo-setup,init,repo-init,setup,repo-sign,sign,repo-status-update,repo-sync,sync,repo-triggers,repo-update,update,shell,user-add,user-list,user-remove,version,web} ...
.SH DESCRIPTION
ArcH Linux ReposItory MANager
ArcH linux ReposItory MANager
.SH OPTIONS
.TP
@ -37,7 +37,7 @@ force disable any logging
allow to run ahriman as non\-ahriman user. Some actions might be unavailable
.TP
\fB\-v\fR, \fB\-\-version\fR
\fB\-V\fR, \fB\-\-version\fR
show program's version number and exit
.SH
@ -121,6 +121,9 @@ run triggers
\fBahriman\fR \fI\,repo-update\/\fR
update packages
.TP
\fBahriman\fR \fI\,shell\/\fR
envoke python shell
.TP
\fBahriman\fR \fI\,user-add\/\fR
create or update user
.TP
@ -130,6 +133,9 @@ user known users and their access
\fBahriman\fR \fI\,user-remove\/\fR
remove user
.TP
\fBahriman\fR \fI\,version\/\fR
application version
.TP
\fBahriman\fR \fI\,web\/\fR
web server
.SH COMMAND \fI\,'ahriman aur-search'\/\fR
@ -544,6 +550,11 @@ do not include manual updates
\fB\-\-no\-vcs\fR
do not check VCS packages
.SH COMMAND \fI\,'ahriman shell'\/\fR
usage: ahriman shell [-h]
drop into python shell while having created application
.SH COMMAND \fI\,'ahriman user-add'\/\fR
usage: ahriman user-add [-h] [--as-service] [-p PASSWORD]
[-r {UserAccess.Unauthorized,UserAccess.Read,UserAccess.Reporter,UserAccess.Full}] [-s]
@ -606,6 +617,11 @@ username for web service
\fB\-s\fR, \fB\-\-secure\fR
set file permissions to user\-only
.SH COMMAND \fI\,'ahriman version'\/\fR
usage: ahriman version [-h]
print application and its dependencies versions
.SH COMMAND \fI\,'ahriman web'\/\fR
usage: ahriman web [-h]

View File

@ -116,6 +116,14 @@ ahriman.application.handlers.setup module
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.shell module
-----------------------------------------
.. automodule:: ahriman.application.handlers.shell
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.sign module
----------------------------------------
@ -172,6 +180,14 @@ ahriman.application.handlers.users module
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.versions module
--------------------------------------------
.. automodule:: ahriman.application.handlers.versions
:members:
:no-undoc-members:
:show-inheritance:
ahriman.application.handlers.web module
---------------------------------------

View File

@ -76,6 +76,14 @@ ahriman.core.formatters.user\_printer module
:no-undoc-members:
:show-inheritance:
ahriman.core.formatters.version\_printer module
-----------------------------------------------
.. automodule:: ahriman.core.formatters.version_printer
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------

View File

@ -104,5 +104,4 @@ autodoc_member_order = "groupwise"
autodoc_default_options = {
"no-undoc-members": True,
"special-members": "__init__",
}

View File

@ -0,0 +1,18 @@

▄▄▄ ▄▄▄▄▄▄█▀ 
▄▄▄▄▄▄▄▄▄██▄▄▄█▄▄ 
██▄▄███▄▄▄▄▄██▄▄█▄▄ 
█▄██████▄▄▄████▄▄█▄▄ ▄
█▄▄▄█████████▄▄▄▄▀▄█▄█▀
█▄▀▄████▄█▄▄▄▄███▄▄ ▀▀ 
█▄▄▄████▄██████████ 
▄▄▄▄▄▄▄▄▄ ▀█▄█████▄▄▄▄█▄███▄ 
▄▄███▄▄▄▄▄▄▄▄▄ ▀ ▀▄█████▄▄█▄███ 
███▄▄████▄▄█▄██▄▄▄ ███▄▄▄▄▄▄█▀▀ 
███████▄▀ ▄▄▄██▄▄▄█████ 
██▄▄████ █▄█▄▄█████████ 
▄▄█▄▄██▄▀ ▀▄████▄██▄██▄▄ 
████████ ▄███▄▄▄█▄████ 
▄▄▄▄███▄▀ ▄▄█████ ███▄▄▄▄ 
▄▄▄██▄▄█▄▄▄▀ █████▄█ █████▄█ 
▀▀▀▀▀▀▀ ▀▀▀▀ ▀▀▀▀ 

View File

@ -66,6 +66,7 @@ setup(
"package/share/ahriman/templates/build-status.jinja2",
"package/share/ahriman/templates/email-index.jinja2",
"package/share/ahriman/templates/repo-index.jinja2",
"package/share/ahriman/templates/shell",
"package/share/ahriman/templates/telegram-index.jinja2",
]),
("share/ahriman/templates/build-status", [

View File

@ -26,6 +26,7 @@ from typing import List, TypeVar
from ahriman import version
from ahriman.application import handlers
from ahriman.core.util import enum_values
from ahriman.models.action import Action
from ahriman.models.build_status import BuildStatusEnum
from ahriman.models.package_source import PackageSource
@ -76,7 +77,7 @@ def _parser() -> argparse.ArgumentParser:
parser.add_argument("-q", "--quiet", help="force disable any logging", action="store_true")
parser.add_argument("--unsafe", help="allow to run ahriman as non-ahriman user. Some actions might be unavailable",
action="store_true")
parser.add_argument("-v", "--version", action="version", version=version.__version__)
parser.add_argument("-V", "--version", action="version", version=version.__version__)
subparsers = parser.add_subparsers(title="command", help="command to run", dest="command", required=True)
@ -106,9 +107,11 @@ def _parser() -> argparse.ArgumentParser:
_set_repo_sync_parser(subparsers)
_set_repo_triggers_parser(subparsers)
_set_repo_update_parser(subparsers)
_set_shell_parser(subparsers)
_set_user_add_parser(subparsers)
_set_user_list_parser(subparsers)
_set_user_remove_parser(subparsers)
_set_version_parser(subparsers)
_set_web_parser(subparsers)
return parser
@ -225,7 +228,7 @@ def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
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("-s", "--source", help="explicitly specify the package source for this command",
type=PackageSource, choices=PackageSource, default=PackageSource.Auto)
type=PackageSource, choices=enum_values(PackageSource), default=PackageSource.Auto)
parser.add_argument("--without-dependencies", help="do not add dependencies", action="store_true")
parser.set_defaults(handler=handlers.Add)
return parser
@ -267,7 +270,7 @@ def _set_package_status_parser(root: SubParserAction) -> argparse.ArgumentParser
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("-s", "--status", help="filter packages by status",
type=BuildStatusEnum, choices=BuildStatusEnum)
type=BuildStatusEnum, choices=enum_values(BuildStatusEnum))
parser.set_defaults(handler=handlers.Status, lock=None, no_report=True, quiet=True, unsafe=True)
return parser
@ -309,7 +312,7 @@ def _set_package_status_update_parser(root: SubParserAction) -> argparse.Argumen
"If no packages supplied, service status will be updated",
nargs="*")
parser.add_argument("-s", "--status", help="new status",
type=BuildStatusEnum, choices=BuildStatusEnum, default=BuildStatusEnum.Success)
type=BuildStatusEnum, choices=enum_values(BuildStatusEnum), default=BuildStatusEnum.Success)
parser.set_defaults(handler=handlers.StatusUpdate, action=Action.Update, lock=None, no_report=True, quiet=True,
unsafe=True)
return parser
@ -556,7 +559,7 @@ def _set_repo_setup_parser(root: SubParserAction) -> argparse.ArgumentParser:
parser.add_argument("--repository", help="repository name", required=True)
parser.add_argument("--sign-key", help="sign key id")
parser.add_argument("--sign-target", help="sign options", action="append",
type=SignSettings.from_option, choices=SignSettings)
type=SignSettings.from_option, choices=enum_values(SignSettings))
parser.add_argument("--web-port", help="port of the web service", type=int)
parser.set_defaults(handler=handlers.Setup, lock=None, no_report=True, quiet=True, unsafe=True)
return parser
@ -594,7 +597,7 @@ def _set_repo_status_update_parser(root: SubParserAction) -> argparse.ArgumentPa
parser = root.add_parser("repo-status-update", help="update repository status",
description="update repository status on the status page", formatter_class=_formatter)
parser.add_argument("-s", "--status", help="new status",
type=BuildStatusEnum, choices=BuildStatusEnum, default=BuildStatusEnum.Success)
type=BuildStatusEnum, choices=enum_values(BuildStatusEnum), default=BuildStatusEnum.Success)
parser.set_defaults(handler=handlers.StatusUpdate, action=Action.Update, lock=None, no_report=True, package=[],
quiet=True, unsafe=True)
return parser
@ -661,6 +664,24 @@ def _set_repo_update_parser(root: SubParserAction) -> argparse.ArgumentParser:
return parser
def _set_shell_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for shell subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("shell", help="envoke python shell",
description="drop into python shell while having created application",
formatter_class=_formatter)
parser.add_argument("-v", "--verbose", help=argparse.SUPPRESS, action="store_true")
parser.set_defaults(handler=handlers.Shell, lock=None, no_report=True)
return parser
def _set_user_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for create user subcommand
@ -680,7 +701,7 @@ def _set_user_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
parser.add_argument("-p", "--password", help="user password. Blank password will be treated as empty password, "
"which is in particular must be used for OAuth2 authorization type.")
parser.add_argument("-r", "--role", help="user access level",
type=UserAccess, choices=UserAccess, default=UserAccess.Read)
type=UserAccess, choices=enum_values(UserAccess), default=UserAccess.Read)
parser.add_argument("-s", "--secure", help="set file permissions to user-only", action="store_true")
parser.set_defaults(handler=handlers.Users, action=Action.Update, architecture=[""], lock=None, no_report=True,
quiet=True, unsafe=True)
@ -702,7 +723,7 @@ def _set_user_list_parser(root: SubParserAction) -> argparse.ArgumentParser:
formatter_class=_formatter)
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=enum_values(UserAccess))
parser.set_defaults(handler=handlers.Users, action=Action.List, architecture=[""], lock=None, no_report=True, # nosec
password="", quiet=True, unsafe=True)
return parser
@ -728,6 +749,23 @@ def _set_user_remove_parser(root: SubParserAction) -> argparse.ArgumentParser:
return parser
def _set_version_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for version subcommand
Args:
root(SubParserAction): subparsers for the commands
Returns:
argparse.ArgumentParser: created argument parser
"""
parser = root.add_parser("version", help="application version",
description="print application and its dependencies versions", formatter_class=_formatter)
parser.set_defaults(handler=handlers.Versions, architecture=[""], lock=None, no_report=True, quiet=True,
unsafe=True)
return parser
def _set_web_parser(root: SubParserAction) -> argparse.ArgumentParser:
"""
add parser for web subcommand

View File

@ -32,6 +32,7 @@ from ahriman.application.handlers.remove_unknown import RemoveUnknown
from ahriman.application.handlers.restore import Restore
from ahriman.application.handlers.search import Search
from ahriman.application.handlers.setup import Setup
from ahriman.application.handlers.shell import Shell
from ahriman.application.handlers.sign import Sign
from ahriman.application.handlers.status import Status
from ahriman.application.handlers.status_update import StatusUpdate
@ -39,4 +40,5 @@ from ahriman.application.handlers.triggers import Triggers
from ahriman.application.handlers.unsafe_commands import UnsafeCommands
from ahriman.application.handlers.update import Update
from ahriman.application.handlers.users import Users
from ahriman.application.handlers.versions import Versions
from ahriman.application.handlers.web import Web

View File

@ -0,0 +1,59 @@
#
# Copyright (c) 2021-2022 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 <http://www.gnu.org/licenses/>.
#
import argparse
import code
import sys
from pathlib import Path
from typing import Type
from ahriman.application.application import Application
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.core.formatters import StringPrinter
class Shell(Handler):
"""
python shell handler
"""
ALLOW_MULTI_ARCHITECTURE_RUN = False
@classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
callback for command line
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
# pylint: disable=possibly-unused-variable
application = Application(architecture, configuration, no_report, unsafe)
if args.verbose:
# licensed by https://creativecommons.org/licenses/by-sa/3.0
path = Path(sys.prefix) / "share" / "ahriman" / "templates" / "shell"
StringPrinter(path.read_text(encoding="utf8")).print(verbose=False)
code.interact(local=locals())

View File

@ -0,0 +1,87 @@
#
# Copyright (c) 2021-2022 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 <http://www.gnu.org/licenses/>.
#
import argparse
import pkg_resources
import sys
from typing import Dict, List, Tuple, Type
from ahriman import version
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
from ahriman.core.formatters import VersionPrinter
class Versions(Handler):
"""
version handler
"""
ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture"
@classmethod
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
configuration: Configuration, no_report: bool, unsafe: bool) -> None:
"""
callback for command line
Args:
args(argparse.Namespace): command line args
architecture(str): repository architecture
configuration(Configuration): configuration instance
no_report(bool): force disable reporting
unsafe(bool): if set no user check will be performed before path creation
"""
VersionPrinter(f"Module version {version.__version__}",
{"Python": sys.version}).print(verbose=False, separator=" ")
packages = Versions.package_dependencies("ahriman", ("pacman", "s3", "web"))
VersionPrinter("Installed packages", packages).print(verbose=False, separator=" ")
@staticmethod
def package_dependencies(root: str, root_extras: Tuple[str, ...] = ()) -> Dict[str, str]:
"""
extract list of ahriman package dependencies installed into system with their versions
Args:
root(str): root package name
root_extras(Tuple[str, ...]): extras for the root package (Default value = ())
Returns:
Dict[str, str]: map of installed dependency to its version
"""
resources: Dict[str, pkg_resources.Distribution] = pkg_resources.working_set.by_key # type: ignore
def dependencies_by_key(key: str, extras: Tuple[str, ...] = ()) -> List[str]:
return [entry.key for entry in resources[key].requires(extras)]
keys: List[str] = []
portion = {key for key in dependencies_by_key(root, root_extras) if key in resources}
while portion:
keys.extend(portion)
portion = {
key
for key in sum([dependencies_by_key(key) for key in portion], start=[])
if key not in keys and key in resources
}
return {
resource.project_name: resource.version
for resource in map(lambda key: resources[key], keys)
}

View File

@ -27,3 +27,4 @@ from ahriman.core.formatters.package_printer import PackagePrinter
from ahriman.core.formatters.status_printer import StatusPrinter
from ahriman.core.formatters.update_printer import UpdatePrinter
from ahriman.core.formatters.user_printer import UserPrinter
from ahriman.core.formatters.version_printer import VersionPrinter

View File

@ -25,6 +25,9 @@ from ahriman.core.formatters import Printer
class StringPrinter(Printer):
"""
print content of the random string
Attributes:
content(str): any content string
"""
def __init__(self, content: str) -> None:

View File

@ -0,0 +1,55 @@
#
# Copyright (c) 2021-2022 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 <http://www.gnu.org/licenses/>.
#
from typing import Dict, List
from ahriman.core.formatters import StringPrinter
from ahriman.models.property import Property
class VersionPrinter(StringPrinter):
"""
print content of the python package versions
Attributes:
packages(Dict[str, str]): map of package name to its version
"""
def __init__(self, title: str, packages: Dict[str, str]) -> None:
"""
default constructor
Args:
title(str): title of the message
packages(Dict[str, str]): map of package name to its version
"""
StringPrinter.__init__(self, title)
self.packages = packages
def properties(self) -> List[Property]:
"""
convert content into printable data
Returns:
List[Property]: list of content properties
"""
return [
Property(package, version, is_required=True)
for package, version in sorted(self.packages.items())
]

View File

@ -18,7 +18,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import datetime
import io
import os
from enum import Enum
import requests
import shutil
import subprocess
@ -27,14 +30,14 @@ import tempfile
from contextlib import contextmanager
from logging import Logger
from pathlib import Path
from typing import Any, Dict, Generator, Iterable, List, Optional, Union
from typing import Any, Dict, Generator, IO, Iterable, List, Optional, Type, Union
from ahriman.core.exceptions import InvalidOption, UnsafeRun
from ahriman.models.repository_paths import RepositoryPaths
__all__ = ["check_output", "check_user", "exception_response_text", "filter_json", "full_version", "package_like",
"pretty_datetime", "pretty_size", "tmpdir", "walk"]
__all__ = ["check_output", "check_user", "exception_response_text", "filter_json", "full_version", "enum_values",
"package_like", "pretty_datetime", "pretty_size", "tmpdir", "walk"]
def check_output(*args: str, exception: Optional[Exception], cwd: Optional[Path] = None,
@ -73,6 +76,11 @@ def check_output(*args: str, exception: Optional[Exception], cwd: Optional[Path]
>>> check_output("false", exception=RuntimeError("An exception occurred"))
"""
# hack for Optional[IO[str]] handle
def get_io(proc: subprocess.Popen[str], channel_name: str) -> IO[str]:
channel: Optional[IO[str]] = getattr(proc, channel_name, None)
return channel if channel is not None else io.StringIO()
def log(single: str) -> None:
if logger is not None:
logger.debug(single)
@ -80,14 +88,15 @@ def check_output(*args: str, exception: Optional[Exception], cwd: Optional[Path]
# FIXME additional workaround for linter and type check which do not know that user arg is supported
# pylint: disable=unexpected-keyword-arg
with subprocess.Popen(args, cwd=cwd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
user=user, text=True, encoding="utf8", bufsize=1) as process: # type: ignore
user=user, text=True, encoding="utf8", bufsize=1) as process:
if input_data is not None:
process.stdin.write(input_data)
process.stdin.close()
input_channel = get_io(process, "stdin")
input_channel.write(input_data)
input_channel.close()
# read stdout and append to output result
result: List[str] = []
for line in iter(process.stdout.readline, ""):
for line in iter(get_io(process, "stdout").readline, ""):
line = line.strip()
if not line: # skip empty lines
continue
@ -95,7 +104,7 @@ def check_output(*args: str, exception: Optional[Exception], cwd: Optional[Path]
log(line)
# read stderr and write info to logs
for line in iter(process.stderr.readline, ""):
for line in iter(get_io(process, "stderr").readline, ""):
log(line.strip())
process.terminate() # make sure that process is terminated
@ -134,6 +143,19 @@ def check_user(paths: RepositoryPaths, unsafe: bool) -> None:
raise UnsafeRun(current_uid, root_uid)
def enum_values(enum: Type[Enum]) -> List[str]:
"""
generate list of enumeration values from the source
Args:
enum(Type[Enum]): source enumeration class
Returns:
List[str]: available enumeration values as string
"""
return [key.value for key in enum]
def exception_response_text(exception: requests.exceptions.HTTPError) -> str:
"""
safe response exception text generation

View File

@ -0,0 +1,48 @@
import argparse
import pytest
from pytest_mock import MockerFixture
from ahriman.application.handlers import Shell
from ahriman.core.configuration import Configuration
def _default_args(args: argparse.Namespace) -> argparse.Namespace:
"""
default arguments for these test cases
Args:
args(argparse.Namespace): command line arguments fixture
Returns:
argparse.Namespace: generated arguments for these test cases
"""
args.verbose = False
return args
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run command
"""
args = _default_args(args)
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
application_mock = mocker.patch("code.interact")
Shell.run(args, "x86_64", configuration, True, False)
application_mock.assert_called_once_with(local=pytest.helpers.anyvar(int))
def test_run_verbose(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run command with verbose option
"""
args = _default_args(args)
args.verbose = True
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
print_mock = mocker.patch("ahriman.core.formatters.Printer.print")
application_mock = mocker.patch("code.interact")
Shell.run(args, "x86_64", configuration, True, False)
application_mock.assert_called_once_with(local=pytest.helpers.anyvar(int))
print_mock.assert_called_once_with(verbose=False)

View File

@ -0,0 +1,38 @@
import argparse
from pytest_mock import MockerFixture
from unittest import mock
from ahriman.application.handlers import Versions
from ahriman.core.configuration import Configuration
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run command
"""
application_mock = mocker.patch("ahriman.application.handlers.Versions.package_dependencies")
print_mock = mocker.patch("ahriman.core.formatters.Printer.print")
Versions.run(args, "x86_64", configuration, True, False)
application_mock.assert_called_once_with("ahriman", ("pacman", "s3", "web"))
print_mock.assert_has_calls([mock.call(verbose=False, separator=" "), mock.call(verbose=False, separator=" ")])
def test_package_dependencies() -> None:
"""
must extract package dependencies
"""
packages = Versions.package_dependencies("srcinfo")
assert packages
assert packages.get("parse") is not None
def test_package_dependencies_missing() -> None:
"""
must extract package dependencies even if some of them are missing
"""
packages = Versions.package_dependencies("ahriman", ("docs", "pacman", "s3", "web"))
assert packages
assert packages.get("pyalpm") is not None
assert packages.get("Sphinx") is None

View File

@ -492,6 +492,15 @@ def test_subparsers_repo_update_architecture(parser: argparse.ArgumentParser) ->
assert args.architecture == ["x86_64"]
def test_subparsers_shell(parser: argparse.ArgumentParser) -> None:
"""
shell command must imply lock and no-report
"""
args = parser.parse_args(["shell"])
assert args.lock is None
assert args.no_report
def test_subparsers_user_add(parser: argparse.ArgumentParser) -> None:
"""
user-add command must imply action, architecture, lock, no-report, quiet and unsafe
@ -575,6 +584,26 @@ def test_subparsers_user_remove_architecture(parser: argparse.ArgumentParser) ->
assert args.architecture == [""]
def test_subparsers_version(parser: argparse.ArgumentParser) -> None:
"""
version command must imply architecture, lock, no-report, quiet and unsafe
"""
args = parser.parse_args(["version"])
assert args.architecture == [""]
assert args.lock is None
assert args.no_report
assert args.quiet
assert args.unsafe
def test_subparsers_version_architecture(parser: argparse.ArgumentParser) -> None:
"""
version command must correctly parse architecture list
"""
args = parser.parse_args(["-a", "x86_64", "version"])
assert args.architecture == [""]
def test_subparsers_web(parser: argparse.ArgumentParser) -> None:
"""
web command must imply lock, no_report and parser

View File

@ -1,6 +1,7 @@
import pytest
from ahriman.core.formatters import AurPrinter, ConfigurationPrinter, PackagePrinter, StatusPrinter, StringPrinter, UpdatePrinter, UserPrinter
from ahriman.core.formatters import AurPrinter, ConfigurationPrinter, PackagePrinter, StatusPrinter, StringPrinter, \
UpdatePrinter, UserPrinter, VersionPrinter
from ahriman.models.aur_package import AURPackage
from ahriman.models.build_status import BuildStatus
from ahriman.models.package import Package
@ -94,3 +95,17 @@ def user_printer(user: User) -> UserPrinter:
UserPrinter: user printer test instance
"""
return UserPrinter(user)
@pytest.fixture
def version_printer(package_ahriman: Package) -> VersionPrinter:
"""
fixture for version printer
Args:
package_ahriman(Package): package fixture
Returns:
VersionPrinter: version printer test instance
"""
return VersionPrinter("package", {package_ahriman.base: package_ahriman.version})

View File

@ -0,0 +1,15 @@
from ahriman.core.formatters import VersionPrinter
def test_properties(version_printer: VersionPrinter) -> None:
"""
must return empty properties list
"""
assert version_printer.properties()
def test_title(version_printer: VersionPrinter) -> None:
"""
must return non empty title
"""
assert version_printer.title() is not None

View File

@ -10,8 +10,9 @@ from unittest.mock import MagicMock
from ahriman.core.exceptions import BuildFailed, InvalidOption, UnsafeRun
from ahriman.core.util import check_output, check_user, exception_response_text, filter_json, full_version, \
package_like, pretty_datetime, pretty_size, tmpdir, walk
enum_values, package_like, pretty_datetime, pretty_size, tmpdir, walk
from ahriman.models.package import Package
from ahriman.models.package_source import PackageSource
from ahriman.models.repository_paths import RepositoryPaths
@ -177,6 +178,15 @@ def test_filter_json_empty_value(package_ahriman: Package) -> None:
assert "base" not in filter_json(probe, probe.keys())
def test_enum_values() -> None:
"""
must correctly generate choices from enumeration classes
"""
values = enum_values(PackageSource)
for value in values:
assert PackageSource(value).value == value
def test_full_version() -> None:
"""
must construct full version
@ -331,6 +341,7 @@ def test_walk(resource_path_root: Path) -> None:
resource_path_root / "web" / "templates" / "build-status.jinja2",
resource_path_root / "web" / "templates" / "email-index.jinja2",
resource_path_root / "web" / "templates" / "repo-index.jinja2",
resource_path_root / "web" / "templates" / "shell",
resource_path_root / "web" / "templates" / "telegram-index.jinja2",
])
local_files = list(sorted(walk(resource_path_root)))