mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-06-27 22:31:43 +00:00
Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
fcb130e226 | |||
ae99fe4535 | |||
ec23e3f912 | |||
d3ea81d234 | |||
09b0f2914d | |||
7351e20104 | |||
dfd87c502f | |||
0b9ab09879 | |||
47c54f0b40 | |||
a2f2fa0354 | |||
4d68080c05 | |||
eb16ef12f3 |
2
.github/workflows/setup.sh
vendored
2
.github/workflows/setup.sh
vendored
@ -5,6 +5,8 @@ set -ex
|
||||
|
||||
# install dependencies
|
||||
echo -e '[arcanisrepo]\nServer = http://repo.arcanis.me/$arch\nSigLevel = Never' | tee -a /etc/pacman.conf
|
||||
# refresh the image
|
||||
pacman --noconfirm -Syu
|
||||
# main dependencies
|
||||
pacman --noconfirm -Sy base-devel devtools git pyalpm python-aur python-passlib python-srcinfo sudo
|
||||
# make dependencies
|
||||
|
65
README.md
65
README.md
@ -21,70 +21,7 @@ Wrapper for managing custom repository inspired by [repo-scripts](https://github
|
||||
|
||||
## Installation and run
|
||||
|
||||
For installation details please refer to the [documentation](docs/setup.md). For command help, `--help` subcommand must be used, e.g.:
|
||||
|
||||
```shell
|
||||
$ ahriman --help
|
||||
usage: ahriman [-h] [-a ARCHITECTURE] [-c CONFIGURATION] [--force] [-l LOCK] [--no-report] [-q] [--unsafe] [-v]
|
||||
{aur-search,search,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-check,check,repo-clean,clean,repo-config,config,repo-init,init,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-report,report,repo-setup,setup,repo-sign,sign,repo-sync,sync,repo-update,update,user-add,user-remove,web}
|
||||
...
|
||||
|
||||
ArcH Linux ReposItory MANager
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-a ARCHITECTURE, --architecture ARCHITECTURE
|
||||
target architectures (can be used multiple times) (default: None)
|
||||
-c CONFIGURATION, --configuration CONFIGURATION
|
||||
configuration path (default: /etc/ahriman.ini)
|
||||
--force force run, remove file lock (default: False)
|
||||
-l LOCK, --lock LOCK lock file (default: /tmp/ahriman.lock)
|
||||
--no-report force disable reporting to web service (default: False)
|
||||
-q, --quiet force disable any logging (default: False)
|
||||
--unsafe allow to run ahriman as non-ahriman user. Some actions might be unavailable (default: False)
|
||||
-v, --version show program's version number and exit
|
||||
|
||||
command:
|
||||
{aur-search,search,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-check,check,repo-clean,clean,repo-config,config,repo-init,init,repo-rebuild,rebuild,repo-remove-unknown,remove-unknown,repo-report,report,repo-setup,setup,repo-sign,sign,repo-sync,sync,repo-update,update,user-add,user-remove,web}
|
||||
command to run
|
||||
aur-search (search)
|
||||
search for package
|
||||
key-import import PGP key
|
||||
package-add (add, package-update)
|
||||
add package
|
||||
package-remove (remove)
|
||||
remove package
|
||||
package-status (status)
|
||||
get package status
|
||||
package-status-remove
|
||||
remove package status
|
||||
package-status-update (status-update)
|
||||
update package status
|
||||
patch-add add patch set
|
||||
patch-list list patch sets
|
||||
patch-remove remove patch set
|
||||
repo-check (check) check for updates
|
||||
repo-clean (clean) clean local caches
|
||||
repo-config (config)
|
||||
dump configuration
|
||||
repo-init (init) create repository tree
|
||||
repo-rebuild (rebuild)
|
||||
rebuild repository
|
||||
repo-remove-unknown (remove-unknown)
|
||||
remove unknown packages
|
||||
repo-report (report)
|
||||
generate report
|
||||
repo-setup (setup) initial service configuration
|
||||
repo-sign (sign) sign packages
|
||||
repo-sync (sync) sync repository
|
||||
repo-update (update)
|
||||
update packages
|
||||
user-add create or update user
|
||||
user-remove remove user
|
||||
web web server
|
||||
```
|
||||
|
||||
Subcommands have own help message as well.
|
||||
For installation details please refer to the [documentation](docs/setup.md). For command help, `--help` subcommand must be used. Subcommands have own help message as well. The package also provides a [man page](docs/ahriman.1).
|
||||
|
||||
## Configuration
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
Before Width: | Height: | Size: 351 KiB After Width: | Height: | Size: 388 KiB |
133
docs/ahriman.1
133
docs/ahriman.1
@ -118,24 +118,44 @@ remove user
|
||||
\fBahriman\fR \fI\,web\/\fR
|
||||
web server
|
||||
.SH OPTIONS 'ahriman aur-search'
|
||||
usage: ahriman aur-search [-h] search [search ...]
|
||||
usage: ahriman aur-search [-h] [-i]
|
||||
[--sort-by {category_id,description,first_submitted,id,last_modified,license,maintainer,name,num_votes,out_of_date,package_base,package_base_id,url,url_path,version}]
|
||||
search [search ...]
|
||||
|
||||
search for package in AUR using API
|
||||
|
||||
.TP
|
||||
\fBsearch\fR
|
||||
search terms, can be specified multiple times
|
||||
search terms, can be specified multiple times, result will match all terms
|
||||
|
||||
.TP
|
||||
\fB\-i\fR, \fB\-\-info\fR
|
||||
show additional package information
|
||||
|
||||
.TP
|
||||
\fB\-\-sort\-by\fR {category_id,description,first_submitted,id,last_modified,license,maintainer,name,num_votes,out_of_date,package_base,package_base_id,url,url_path,version}
|
||||
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
|
||||
|
||||
.SH OPTIONS 'ahriman search'
|
||||
usage: ahriman aur-search [-h] search [search ...]
|
||||
usage: ahriman aur-search [-h] [-i]
|
||||
[--sort-by {category_id,description,first_submitted,id,last_modified,license,maintainer,name,num_votes,out_of_date,package_base,package_base_id,url,url_path,version}]
|
||||
search [search ...]
|
||||
|
||||
search for package in AUR using API
|
||||
|
||||
.TP
|
||||
\fBsearch\fR
|
||||
search terms, can be specified multiple times
|
||||
search terms, can be specified multiple times, result will match all terms
|
||||
|
||||
.TP
|
||||
\fB\-i\fR, \fB\-\-info\fR
|
||||
show additional package information
|
||||
|
||||
.TP
|
||||
\fB\-\-sort\-by\fR {category_id,description,first_submitted,id,last_modified,license,maintainer,name,num_votes,out_of_date,package_base,package_base_id,url,url_path,version}
|
||||
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
|
||||
|
||||
.SH OPTIONS 'ahriman key-import'
|
||||
usage: ahriman key-import [-h] [--key-server KEY_SERVER] key
|
||||
@ -152,7 +172,7 @@ key server for key import
|
||||
|
||||
.SH OPTIONS 'ahriman package-add'
|
||||
usage: ahriman package-add [-h] [-n]
|
||||
[-s {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local}]
|
||||
[-s {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote}]
|
||||
[--without-dependencies]
|
||||
package [package ...]
|
||||
|
||||
@ -160,15 +180,15 @@ add existing or new package to the build queue
|
||||
|
||||
.TP
|
||||
\fBpackage\fR
|
||||
package base/name or path to local files
|
||||
package source (base name, path to local files, remote URL)
|
||||
|
||||
.TP
|
||||
\fB\-n\fR, \fB\-\-now\fR
|
||||
run update function after
|
||||
|
||||
.TP
|
||||
\fB\-s\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local}, \fB\-\-source\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local}
|
||||
package source
|
||||
\fB\-s\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote}, \fB\-\-source\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote}
|
||||
explicitly specify the package source for this command
|
||||
|
||||
.TP
|
||||
\fB\-\-without\-dependencies\fR
|
||||
@ -176,7 +196,7 @@ do not add dependencies
|
||||
|
||||
.SH OPTIONS 'ahriman add'
|
||||
usage: ahriman package-add [-h] [-n]
|
||||
[-s {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local}]
|
||||
[-s {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote}]
|
||||
[--without-dependencies]
|
||||
package [package ...]
|
||||
|
||||
@ -184,15 +204,15 @@ add existing or new package to the build queue
|
||||
|
||||
.TP
|
||||
\fBpackage\fR
|
||||
package base/name or path to local files
|
||||
package source (base name, path to local files, remote URL)
|
||||
|
||||
.TP
|
||||
\fB\-n\fR, \fB\-\-now\fR
|
||||
run update function after
|
||||
|
||||
.TP
|
||||
\fB\-s\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local}, \fB\-\-source\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local}
|
||||
package source
|
||||
\fB\-s\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote}, \fB\-\-source\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote}
|
||||
explicitly specify the package source for this command
|
||||
|
||||
.TP
|
||||
\fB\-\-without\-dependencies\fR
|
||||
@ -200,7 +220,7 @@ do not add dependencies
|
||||
|
||||
.SH OPTIONS 'ahriman package-update'
|
||||
usage: ahriman package-add [-h] [-n]
|
||||
[-s {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local}]
|
||||
[-s {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote}]
|
||||
[--without-dependencies]
|
||||
package [package ...]
|
||||
|
||||
@ -208,15 +228,15 @@ add existing or new package to the build queue
|
||||
|
||||
.TP
|
||||
\fBpackage\fR
|
||||
package base/name or path to local files
|
||||
package source (base name, path to local files, remote URL)
|
||||
|
||||
.TP
|
||||
\fB\-n\fR, \fB\-\-now\fR
|
||||
run update function after
|
||||
|
||||
.TP
|
||||
\fB\-s\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local}, \fB\-\-source\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local}
|
||||
package source
|
||||
\fB\-s\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote}, \fB\-\-source\fR {PackageSource.Auto,PackageSource.Archive,PackageSource.AUR,PackageSource.Directory,PackageSource.Local,PackageSource.Remote}
|
||||
explicitly specify the package source for this command
|
||||
|
||||
.TP
|
||||
\fB\-\-without\-dependencies\fR
|
||||
@ -243,7 +263,7 @@ package name or base
|
||||
|
||||
|
||||
.SH OPTIONS 'ahriman package-status'
|
||||
usage: ahriman package-status [-h] [--ahriman]
|
||||
usage: ahriman package-status [-h] [--ahriman] [-i]
|
||||
[-s {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}]
|
||||
[package ...]
|
||||
|
||||
@ -257,12 +277,16 @@ filter status by package base
|
||||
\fB\-\-ahriman\fR
|
||||
get service status itself
|
||||
|
||||
.TP
|
||||
\fB\-i\fR, \fB\-\-info\fR
|
||||
show additional package information
|
||||
|
||||
.TP
|
||||
\fB\-s\fR {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}, \fB\-\-status\fR {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}
|
||||
filter packages by status
|
||||
|
||||
.SH OPTIONS 'ahriman status'
|
||||
usage: ahriman package-status [-h] [--ahriman]
|
||||
usage: ahriman package-status [-h] [--ahriman] [-i]
|
||||
[-s {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}]
|
||||
[package ...]
|
||||
|
||||
@ -276,6 +300,10 @@ filter status by package base
|
||||
\fB\-\-ahriman\fR
|
||||
get service status itself
|
||||
|
||||
.TP
|
||||
\fB\-i\fR, \fB\-\-info\fR
|
||||
show additional package information
|
||||
|
||||
.TP
|
||||
\fB\-s\fR {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}, \fB\-\-status\fR {BuildStatusEnum.Unknown,BuildStatusEnum.Pending,BuildStatusEnum.Building,BuildStatusEnum.Failed,BuildStatusEnum.Success}
|
||||
filter packages by status
|
||||
@ -380,56 +408,64 @@ filter check by package base
|
||||
do not check VCS packages
|
||||
|
||||
.SH OPTIONS 'ahriman repo-clean'
|
||||
usage: ahriman repo-clean [-h] [--no-build] [--no-cache] [--no-chroot] [--no-manual] [--no-packages]
|
||||
usage: ahriman repo-clean [-h] [--build] [--cache] [--chroot] [--manual] [--packages] [--patches]
|
||||
|
||||
remove local caches
|
||||
|
||||
|
||||
.TP
|
||||
\fB\-\-no\-build\fR
|
||||
do not clear directory with package sources
|
||||
\fB\-\-build\fR
|
||||
clear directory with package sources
|
||||
|
||||
.TP
|
||||
\fB\-\-no\-cache\fR
|
||||
do not clear directory with package caches
|
||||
\fB\-\-cache\fR
|
||||
clear directory with package caches
|
||||
|
||||
.TP
|
||||
\fB\-\-no\-chroot\fR
|
||||
do not clear build chroot
|
||||
\fB\-\-chroot\fR
|
||||
clear build chroot
|
||||
|
||||
.TP
|
||||
\fB\-\-no\-manual\fR
|
||||
do not clear directory with manually added packages
|
||||
\fB\-\-manual\fR
|
||||
clear directory with manually added packages
|
||||
|
||||
.TP
|
||||
\fB\-\-no\-packages\fR
|
||||
do not clear directory with built packages
|
||||
\fB\-\-packages\fR
|
||||
clear directory with built packages
|
||||
|
||||
.TP
|
||||
\fB\-\-patches\fR
|
||||
clear directory with patches
|
||||
|
||||
.SH OPTIONS 'ahriman clean'
|
||||
usage: ahriman repo-clean [-h] [--no-build] [--no-cache] [--no-chroot] [--no-manual] [--no-packages]
|
||||
usage: ahriman repo-clean [-h] [--build] [--cache] [--chroot] [--manual] [--packages] [--patches]
|
||||
|
||||
remove local caches
|
||||
|
||||
|
||||
.TP
|
||||
\fB\-\-no\-build\fR
|
||||
do not clear directory with package sources
|
||||
\fB\-\-build\fR
|
||||
clear directory with package sources
|
||||
|
||||
.TP
|
||||
\fB\-\-no\-cache\fR
|
||||
do not clear directory with package caches
|
||||
\fB\-\-cache\fR
|
||||
clear directory with package caches
|
||||
|
||||
.TP
|
||||
\fB\-\-no\-chroot\fR
|
||||
do not clear build chroot
|
||||
\fB\-\-chroot\fR
|
||||
clear build chroot
|
||||
|
||||
.TP
|
||||
\fB\-\-no\-manual\fR
|
||||
do not clear directory with manually added packages
|
||||
\fB\-\-manual\fR
|
||||
clear directory with manually added packages
|
||||
|
||||
.TP
|
||||
\fB\-\-no\-packages\fR
|
||||
do not clear directory with built packages
|
||||
\fB\-\-packages\fR
|
||||
clear directory with built packages
|
||||
|
||||
.TP
|
||||
\fB\-\-patches\fR
|
||||
clear directory with patches
|
||||
|
||||
.SH OPTIONS 'ahriman repo-config'
|
||||
usage: ahriman repo-config [-h]
|
||||
@ -480,7 +516,7 @@ force rebuild whole repository
|
||||
only rebuild packages that depend on specified package
|
||||
|
||||
.SH OPTIONS 'ahriman repo-remove-unknown'
|
||||
usage: ahriman repo-remove-unknown [-h] [--dry-run]
|
||||
usage: ahriman repo-remove-unknown [-h] [--dry-run] [-i]
|
||||
|
||||
remove packages which are missing in AUR and do not have local PKGBUILDs
|
||||
|
||||
@ -489,8 +525,12 @@ remove packages which are missing in AUR and do not have local PKGBUILDs
|
||||
\fB\-\-dry\-run\fR
|
||||
just perform check for packages without removal
|
||||
|
||||
.TP
|
||||
\fB\-i\fR, \fB\-\-info\fR
|
||||
show additional package information
|
||||
|
||||
.SH OPTIONS 'ahriman remove-unknown'
|
||||
usage: ahriman repo-remove-unknown [-h] [--dry-run]
|
||||
usage: ahriman repo-remove-unknown [-h] [--dry-run] [-i]
|
||||
|
||||
remove packages which are missing in AUR and do not have local PKGBUILDs
|
||||
|
||||
@ -499,6 +539,10 @@ remove packages which are missing in AUR and do not have local PKGBUILDs
|
||||
\fB\-\-dry\-run\fR
|
||||
just perform check for packages without removal
|
||||
|
||||
.TP
|
||||
\fB\-i\fR, \fB\-\-info\fR
|
||||
show additional package information
|
||||
|
||||
.SH OPTIONS 'ahriman repo-report'
|
||||
usage: ahriman repo-report [-h] [target ...]
|
||||
|
||||
@ -754,6 +798,11 @@ usage: ahriman web [-h]
|
||||
|
||||
start web server
|
||||
|
||||
|
||||
|
||||
.SH COMMENTS
|
||||
Argument list can also be read from file by using @ prefix.
|
||||
|
||||
.SH AUTHORS
|
||||
.B ahriman
|
||||
was written by ahriman team <>.
|
||||
|
@ -12,7 +12,13 @@ Full dependency diagram:
|
||||
|
||||
## `ahriman.application` package
|
||||
|
||||
This package contains application (aka executable) related classes and everything for that. It also contains package called `ahriman.application.handlers` in which all available subcommands are described as separated classes derived from base `ahriman.application.handlers.handler.Handler` class. `ahriman.application.ahriman` contains only command line parses and executes specified `Handler` on success, `ahriman.application.application.Application` is a god class which provides interfaces for all repository related actions. `ahriman.application.lock.Lock` is additional class which provides file-based lock and also performs some common checks.
|
||||
This package contains application (aka executable) related classes and everything for that. It also contains package called `ahriman.application.handlers` in which all available subcommands are described as separated classes derived from base `ahriman.application.handlers.handler.Handler` class.
|
||||
|
||||
`ahriman.application.application.application.Application` (god class) is used for any interaction from parsers with repository, web etc. It is divided into multiple traits by functions (package related and repository related) in the same package.
|
||||
|
||||
`ahriman.application.formatters` package provides `Printer` sub-classes for printing data (e.g. package properties) to stdout which are used by some handlers.
|
||||
|
||||
`ahriman.application.ahriman` contains only command line parses and executes specified `Handler` on success, `ahriman.application.lock.Lock` is additional class which provides file-based lock and also performs some common checks.
|
||||
|
||||
## `ahriman.core` package
|
||||
|
||||
|
11
docs/faq.md
11
docs/faq.md
@ -399,6 +399,17 @@ Don't know, haven't tried it. But it lacks of documentation at least.
|
||||
* `archrepo2` actively uses direct shell calls and `yaourt` components.
|
||||
* It has constantly running process instead of timer process (it is not pro or con).
|
||||
|
||||
#### [repoctl](https://github.com/cassava/repoctl)
|
||||
|
||||
* Web interface.
|
||||
* No reporting.
|
||||
* Local packages and patches support.
|
||||
* Some actions are not fully automated (e.g. package update still requires manual intervention for the build itself).
|
||||
* `repoctl` has better AUR interaction features. With colors!
|
||||
* `repoctl` has much easier configuration and even completion.
|
||||
* `repoctl` is able to store old packages.
|
||||
* Ability to host repository from same command vs external services (e.g. nginx) in `ahriman`.
|
||||
|
||||
#### [repo-scripts](https://github.com/arcan1s/repo-scripts)
|
||||
|
||||
Though originally I've created ahriman by trying to improve the project, it still lacks a lot of features:
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Maintainer: Evgeniy Alekseev
|
||||
|
||||
pkgname='ahriman'
|
||||
pkgver=1.5.0
|
||||
pkgver=1.6.0
|
||||
pkgrel=1
|
||||
pkgdesc="ArcH Linux ReposItory MANager"
|
||||
arch=('any')
|
||||
|
@ -1,5 +1,5 @@
|
||||
[loggers]
|
||||
keys = root,builder,build_details,http,stderr
|
||||
keys = root,build_details,http,stderr,boto3,botocore,nose,s3transfer
|
||||
|
||||
[handlers]
|
||||
keys = console_handler,syslog_handler
|
||||
@ -20,11 +20,11 @@ formatter = syslog_format
|
||||
args = ("/dev/log",)
|
||||
|
||||
[formatter_generic_format]
|
||||
format = [%(levelname)s %(asctime)s] [%(filename)s:%(lineno)d] [%(funcName)s]: %(message)s
|
||||
format = [%(levelname)s %(asctime)s] [%(filename)s:%(lineno)d %(funcName)s]: %(message)s
|
||||
datefmt =
|
||||
|
||||
[formatter_syslog_format]
|
||||
format = [%(levelname)s] [%(filename)s:%(lineno)d] [%(funcName)s]: %(message)s
|
||||
format = [%(levelname)s] [%(name)s] [%(filename)s:%(lineno)d] [%(funcName)s]: %(message)s
|
||||
datefmt =
|
||||
|
||||
[logger_root]
|
||||
@ -32,12 +32,6 @@ level = DEBUG
|
||||
handlers = syslog_handler
|
||||
qualname = root
|
||||
|
||||
[logger_builder]
|
||||
level = DEBUG
|
||||
handlers = syslog_handler
|
||||
qualname = builder
|
||||
propagate = 0
|
||||
|
||||
[logger_build_details]
|
||||
level = DEBUG
|
||||
handlers = syslog_handler
|
||||
@ -54,3 +48,27 @@ propagate = 0
|
||||
level = DEBUG
|
||||
handlers = console_handler
|
||||
qualname = stderr
|
||||
|
||||
[logger_boto3]
|
||||
level = INFO
|
||||
handlers = syslog_handler
|
||||
qualname = boto3
|
||||
propagate = 0
|
||||
|
||||
[logger_botocore]
|
||||
level = INFO
|
||||
handlers = syslog_handler
|
||||
qualname = botocore
|
||||
propagate = 0
|
||||
|
||||
[logger_nose]
|
||||
level = INFO
|
||||
handlers = syslog_handler
|
||||
qualname = nose
|
||||
propagate = 0
|
||||
|
||||
[logger_s3transfer]
|
||||
level = INFO
|
||||
handlers = syslog_handler
|
||||
qualname = s3transfer
|
||||
propagate = 0
|
||||
|
@ -51,7 +51,8 @@ def _parser() -> argparse.ArgumentParser:
|
||||
:return: command line parser for the application
|
||||
"""
|
||||
parser = argparse.ArgumentParser(prog="ahriman", description="ArcH Linux ReposItory MANager",
|
||||
formatter_class=_formatter)
|
||||
epilog="Argument list can also be read from file by using @ prefix.",
|
||||
fromfile_prefix_chars="@", formatter_class=_formatter)
|
||||
parser.add_argument("-a", "--architecture", help="target architectures (can be used multiple times)",
|
||||
action="append")
|
||||
parser.add_argument("-c", "--configuration", help="configuration path", type=Path, default=Path("/etc/ahriman.ini"))
|
||||
@ -103,7 +104,12 @@ def _set_aur_search_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"""
|
||||
parser = root.add_parser("aur-search", aliases=["search"], help="search for package",
|
||||
description="search for package in AUR using API", formatter_class=_formatter)
|
||||
parser.add_argument("search", help="search terms, can be specified multiple times", nargs="+")
|
||||
parser.add_argument("search", help="search terms, can be specified multiple times, result will match all terms",
|
||||
nargs="+")
|
||||
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 "
|
||||
"the specified field, they will be always sorted by name",
|
||||
default="name", choices=sorted(handlers.Search.SORT_FIELDS))
|
||||
parser.set_defaults(handler=handlers.Search, architecture=[""], lock=None, no_report=True, quiet=True, unsafe=True)
|
||||
return parser
|
||||
|
||||
@ -143,11 +149,12 @@ def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"from another repository source); "
|
||||
"3) it is also possible to add package from local PKGBUILD, but in this case it "
|
||||
"will be ignored during the next automatic updates; "
|
||||
"4) and finally you can add package from AUR.",
|
||||
"4) ahriman supports downloading archives from remote (e.g. HTTP) sources; "
|
||||
"5) and finally you can add package from AUR.",
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("package", help="package base/name or path to local files", nargs="+")
|
||||
parser.add_argument("package", help="package source (base name, path to local files, remote URL)", nargs="+")
|
||||
parser.add_argument("-n", "--now", help="run update function after", action="store_true")
|
||||
parser.add_argument("-s", "--source", help="package source",
|
||||
parser.add_argument("-s", "--source", help="explicitly specify the package source for this command",
|
||||
type=PackageSource, choices=PackageSource, default=PackageSource.Auto)
|
||||
parser.add_argument("--without-dependencies", help="do not add dependencies", action="store_true")
|
||||
parser.set_defaults(handler=handlers.Add)
|
||||
@ -179,6 +186,7 @@ def _set_package_status_parser(root: SubParserAction) -> argparse.ArgumentParser
|
||||
formatter_class=_formatter)
|
||||
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("-i", "--info", help="show additional package information", action="store_true")
|
||||
parser.add_argument("-s", "--status", help="filter packages by status",
|
||||
type=BuildStatusEnum, choices=BuildStatusEnum)
|
||||
parser.set_defaults(handler=handlers.Status, lock=None, no_report=True, quiet=True, unsafe=True)
|
||||
@ -293,11 +301,12 @@ def _set_repo_clean_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"you should not run this command manually. Also in case if you are going to clear "
|
||||
"the chroot directories you will need root privileges.",
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("--no-build", help="do not clear directory with package sources", action="store_true")
|
||||
parser.add_argument("--no-cache", help="do not clear directory with package caches", action="store_true")
|
||||
parser.add_argument("--no-chroot", help="do not clear build chroot", action="store_true")
|
||||
parser.add_argument("--no-manual", help="do not clear directory with manually added packages", action="store_true")
|
||||
parser.add_argument("--no-packages", help="do not clear directory with built packages", action="store_true")
|
||||
parser.add_argument("--build", help="clear directory with package sources", action="store_true")
|
||||
parser.add_argument("--cache", help="clear directory with package caches", action="store_true")
|
||||
parser.add_argument("--chroot", help="clear build chroot", action="store_true")
|
||||
parser.add_argument("--manual", help="clear directory with manually added packages", action="store_true")
|
||||
parser.add_argument("--packages", help="clear directory with built packages", action="store_true")
|
||||
parser.add_argument("--patches", help="clear directory with patches", action="store_true")
|
||||
parser.set_defaults(handler=handlers.Clean, quiet=True, unsafe=True)
|
||||
return parser
|
||||
|
||||
@ -351,6 +360,7 @@ def _set_repo_remove_unknown_parser(root: SubParserAction) -> argparse.ArgumentP
|
||||
description="remove packages which are missing in AUR and do not have local PKGBUILDs",
|
||||
formatter_class=_formatter)
|
||||
parser.add_argument("--dry-run", help="just perform check for packages without removal", action="store_true")
|
||||
parser.add_argument("-i", "--info", help="show additional package information", action="store_true")
|
||||
parser.set_defaults(handler=handlers.RemoveUnknown)
|
||||
return parser
|
||||
|
||||
|
20
src/ahriman/application/application/__init__.py
Normal file
20
src/ahriman/application/application/__init__.py
Normal file
@ -0,0 +1,20 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from ahriman.application.application.application import Application
|
51
src/ahriman/application/application/application.py
Normal file
51
src/ahriman/application/application/application.py
Normal file
@ -0,0 +1,51 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from typing import Iterable, Set
|
||||
|
||||
from ahriman.application.application.packages import Packages
|
||||
from ahriman.application.application.repository import Repository
|
||||
from ahriman.models.package import Package
|
||||
|
||||
|
||||
class Application(Packages, Repository):
|
||||
"""
|
||||
base application class
|
||||
"""
|
||||
|
||||
def _finalize(self, built_packages: Iterable[Package]) -> None:
|
||||
"""
|
||||
generate report and sync to remote server
|
||||
"""
|
||||
self.report([], built_packages)
|
||||
self.sync([], built_packages)
|
||||
|
||||
def _known_packages(self) -> Set[str]:
|
||||
"""
|
||||
load packages from repository and pacman repositories
|
||||
:return: list of known packages
|
||||
"""
|
||||
known_packages: Set[str] = set()
|
||||
# local set
|
||||
for base in self.repository.packages():
|
||||
for package, properties in base.packages.items():
|
||||
known_packages.add(package)
|
||||
known_packages.update(properties.provides)
|
||||
known_packages.update(self.repository.pacman.all_packages())
|
||||
return known_packages
|
148
src/ahriman/application/application/packages.py
Normal file
148
src/ahriman/application/application/packages.py
Normal file
@ -0,0 +1,148 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import requests
|
||||
import shutil
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, Iterable, Set
|
||||
|
||||
from ahriman.application.application.properties import Properties
|
||||
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
|
||||
|
||||
|
||||
class Packages(Properties):
|
||||
"""
|
||||
package control class
|
||||
"""
|
||||
|
||||
def _finalize(self, built_packages: Iterable[Package]) -> None:
|
||||
"""
|
||||
generate report and sync to remote server
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _known_packages(self) -> Set[str]:
|
||||
"""
|
||||
load packages from repository and pacman repositories
|
||||
:return: list of known packages
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _add_archive(self, source: str, *_: Any) -> None:
|
||||
"""
|
||||
add package from archive
|
||||
:param source: path to package archive
|
||||
"""
|
||||
local_path = Path(source)
|
||||
dst = self.repository.paths.packages / local_path.name
|
||||
shutil.copy(local_path, dst)
|
||||
|
||||
def _add_aur(self, source: str, known_packages: Set[str], without_dependencies: bool) -> None:
|
||||
"""
|
||||
add package from AUR
|
||||
:param source: package base name
|
||||
:param known_packages: list of packages which are known by the service
|
||||
:param without_dependencies: if set, dependency check will be disabled
|
||||
"""
|
||||
aur_url = self.configuration.get("alpm", "aur_url")
|
||||
package = Package.load(source, self.repository.pacman, aur_url)
|
||||
Sources.load(self.repository.paths.manual_for(package.base), package.git_url,
|
||||
self.repository.paths.patches_for(package.base))
|
||||
|
||||
local_path = self.repository.paths.manual_for(package.base)
|
||||
self._process_dependencies(local_path, known_packages, without_dependencies)
|
||||
|
||||
def _add_directory(self, source: str, *_: Any) -> None:
|
||||
"""
|
||||
add packages from directory
|
||||
:param source: path to local directory
|
||||
"""
|
||||
local_path = Path(source)
|
||||
for full_path in filter(package_like, local_path.iterdir()):
|
||||
self._add_archive(str(full_path))
|
||||
|
||||
def _add_local(self, source: str, known_packages: Set[str], without_dependencies: bool) -> None:
|
||||
"""
|
||||
add package from local PKGBUILDs
|
||||
:param source: path to directory with local source files
|
||||
:param known_packages: list of packages which are known by the service
|
||||
:param without_dependencies: if set, dependency check will be disabled
|
||||
"""
|
||||
local_path = Path(source)
|
||||
aur_url = self.configuration.get("alpm", "aur_url")
|
||||
package = Package.load(local_path, self.repository.pacman, aur_url)
|
||||
cache_dir = self.repository.paths.cache_for(package.base)
|
||||
shutil.copytree(local_path, cache_dir) # copy package to store in caches
|
||||
Sources.init(cache_dir) # we need to run init command in directory where we do have permissions
|
||||
|
||||
dst = self.repository.paths.manual_for(package.base)
|
||||
shutil.copytree(cache_dir, dst) # copy package for the build
|
||||
self._process_dependencies(dst, known_packages, without_dependencies)
|
||||
|
||||
def _add_remote(self, source: str, *_: Any) -> None:
|
||||
"""
|
||||
add package from remote sources (e.g. HTTP)
|
||||
:param remote_url: remote URL to the package archive
|
||||
"""
|
||||
dst = self.repository.paths.packages / Path(source).name # URL is path, is not it?
|
||||
response = requests.get(source, stream=True)
|
||||
response.raise_for_status()
|
||||
|
||||
with dst.open("wb") as local_file:
|
||||
for chunk in response.iter_content(chunk_size=1024):
|
||||
local_file.write(chunk)
|
||||
|
||||
def _process_dependencies(self, local_path: Path, known_packages: Set[str], without_dependencies: bool) -> None:
|
||||
"""
|
||||
process package dependencies
|
||||
:param local_path: path to local package sources (i.e. cloned AUR repository)
|
||||
:param known_packages: list of packages which are known by the service
|
||||
:param without_dependencies: if set, dependency check will be disabled
|
||||
"""
|
||||
if without_dependencies:
|
||||
return
|
||||
|
||||
dependencies = Package.dependencies(local_path)
|
||||
self.add(dependencies.difference(known_packages), PackageSource.AUR, without_dependencies)
|
||||
|
||||
def add(self, names: Iterable[str], source: PackageSource, without_dependencies: bool) -> None:
|
||||
"""
|
||||
add packages for the next build
|
||||
:param names: list of package bases to add
|
||||
:param source: package source to add
|
||||
:param without_dependencies: if set, dependency check will be disabled
|
||||
"""
|
||||
known_packages = self._known_packages() # speedup dependencies processing
|
||||
|
||||
for name in names:
|
||||
resolved_source = source.resolve(name)
|
||||
fn = getattr(self, f"_add_{resolved_source.value}")
|
||||
fn(name, known_packages, without_dependencies)
|
||||
|
||||
def remove(self, names: Iterable[str]) -> None:
|
||||
"""
|
||||
remove packages from repository
|
||||
:param names: list of packages (either base or name) to remove
|
||||
"""
|
||||
self.repository.process_remove(names)
|
||||
self._finalize([])
|
45
src/ahriman/application/application/properties.py
Normal file
45
src/ahriman/application/application/properties.py
Normal file
@ -0,0 +1,45 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import logging
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.repository import Repository
|
||||
|
||||
|
||||
class Properties:
|
||||
"""
|
||||
application base properties class
|
||||
:ivar architecture: repository architecture
|
||||
:ivar configuration: configuration instance
|
||||
:ivar logger: application logger
|
||||
:ivar repository: repository instance
|
||||
"""
|
||||
|
||||
def __init__(self, architecture: str, configuration: Configuration, no_report: bool) -> None:
|
||||
"""
|
||||
default constructor
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
self.logger = logging.getLogger("root")
|
||||
self.configuration = configuration
|
||||
self.architecture = architecture
|
||||
self.repository = Repository(architecture, configuration, no_report)
|
@ -17,167 +17,50 @@
|
||||
# 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 logging
|
||||
import shutil
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Callable, Iterable, List, Set
|
||||
from typing import Callable, Iterable, List
|
||||
|
||||
from ahriman.application.application.properties import Properties
|
||||
from ahriman.core.build_tools.sources import Sources
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.repository.repository import Repository
|
||||
from ahriman.core.tree import Tree
|
||||
from ahriman.core.util import package_like
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_source import PackageSource
|
||||
|
||||
|
||||
class Application:
|
||||
class Repository(Properties):
|
||||
"""
|
||||
base application class
|
||||
:ivar architecture: repository architecture
|
||||
:ivar configuration: configuration instance
|
||||
:ivar logger: application logger
|
||||
:ivar repository: repository instance
|
||||
repository control class
|
||||
"""
|
||||
|
||||
def __init__(self, architecture: str, configuration: Configuration, no_report: bool) -> None:
|
||||
"""
|
||||
default constructor
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
self.logger = logging.getLogger("root")
|
||||
self.configuration = configuration
|
||||
self.architecture = architecture
|
||||
self.repository = Repository(architecture, configuration, no_report)
|
||||
|
||||
def _finalize(self, built_packages: Iterable[Package]) -> None:
|
||||
"""
|
||||
generate report and sync to remote server
|
||||
"""
|
||||
self.report([], built_packages)
|
||||
self.sync([], built_packages)
|
||||
raise NotImplementedError
|
||||
|
||||
def _known_packages(self) -> Set[str]:
|
||||
"""
|
||||
load packages from repository and pacman repositories
|
||||
:return: list of known packages
|
||||
"""
|
||||
known_packages: Set[str] = set()
|
||||
# local set
|
||||
for base in self.repository.packages():
|
||||
for package, properties in base.packages.items():
|
||||
known_packages.add(package)
|
||||
known_packages.update(properties.provides)
|
||||
known_packages.update(self.repository.pacman.all_packages())
|
||||
return known_packages
|
||||
|
||||
def get_updates(self, filter_packages: List[str], no_aur: bool, no_manual: bool, no_vcs: bool,
|
||||
log_fn: Callable[[str], None]) -> List[Package]:
|
||||
"""
|
||||
get list of packages to run update process
|
||||
:param filter_packages: do not check every package just specified in the list
|
||||
:param no_aur: do not check for aur updates
|
||||
:param no_manual: do not check for manual updates
|
||||
:param no_vcs: do not check VCS packages
|
||||
:param log_fn: logger function to log updates
|
||||
:return: list of out-of-dated packages
|
||||
"""
|
||||
updates = []
|
||||
|
||||
if not no_aur:
|
||||
updates.extend(self.repository.updates_aur(filter_packages, no_vcs))
|
||||
if not no_manual:
|
||||
updates.extend(self.repository.updates_manual())
|
||||
|
||||
for package in updates:
|
||||
log_fn(f"{package.base} = {package.version}")
|
||||
|
||||
return updates
|
||||
|
||||
def add(self, names: Iterable[str], source: PackageSource, without_dependencies: bool) -> None:
|
||||
"""
|
||||
add packages for the next build
|
||||
:param names: list of package bases to add
|
||||
:param source: package source to add
|
||||
:param without_dependencies: if set, dependency check will be disabled
|
||||
"""
|
||||
known_packages = self._known_packages()
|
||||
aur_url = self.configuration.get("alpm", "aur_url")
|
||||
|
||||
def add_archive(src: Path) -> None:
|
||||
dst = self.repository.paths.packages / src.name
|
||||
shutil.copy(src, dst)
|
||||
|
||||
def add_directory(path: Path) -> None:
|
||||
for full_path in filter(package_like, path.iterdir()):
|
||||
add_archive(full_path)
|
||||
|
||||
def add_local(path: Path) -> Path:
|
||||
package = Package.load(path, self.repository.pacman, aur_url)
|
||||
cache_dir = self.repository.paths.cache_for(package.base)
|
||||
shutil.copytree(path, cache_dir) # copy package to store in caches
|
||||
Sources.init(cache_dir) # we need to run init command in directory where we do have permissions
|
||||
shutil.copytree(cache_dir, self.repository.paths.manual_for(package.base)) # copy package for the build
|
||||
return self.repository.paths.manual_for(package.base)
|
||||
|
||||
def add_remote(src: str) -> Path:
|
||||
package = Package.load(src, self.repository.pacman, aur_url)
|
||||
Sources.load(self.repository.paths.manual_for(package.base), package.git_url,
|
||||
self.repository.paths.patches_for(package.base))
|
||||
return self.repository.paths.manual_for(package.base)
|
||||
|
||||
def process_dependencies(path: Path) -> None:
|
||||
if without_dependencies:
|
||||
return
|
||||
dependencies = Package.dependencies(path)
|
||||
self.add(dependencies.difference(known_packages), PackageSource.AUR, without_dependencies)
|
||||
|
||||
def process_single(src: str) -> None:
|
||||
resolved_source = source.resolve(src)
|
||||
if resolved_source == PackageSource.Archive:
|
||||
add_archive(Path(src))
|
||||
elif resolved_source == PackageSource.AUR:
|
||||
path = add_remote(src)
|
||||
process_dependencies(path)
|
||||
elif resolved_source == PackageSource.Directory:
|
||||
add_directory(Path(src))
|
||||
elif resolved_source == PackageSource.Local:
|
||||
path = add_local(Path(src))
|
||||
process_dependencies(path)
|
||||
|
||||
for name in names:
|
||||
process_single(name)
|
||||
|
||||
def clean(self, no_build: bool, no_cache: bool, no_chroot: bool, no_manual: bool, no_packages: bool) -> None:
|
||||
def clean(self, build: bool, cache: bool, chroot: bool, manual: bool, packages: bool, patches: bool) -> None:
|
||||
"""
|
||||
run all clean methods. Warning: some functions might not be available under non-root
|
||||
:param no_build: do not clear directory with package sources
|
||||
:param no_cache: do not clear directory with package caches
|
||||
:param no_chroot: do not clear build chroot
|
||||
:param no_manual: do not clear directory with manually added packages
|
||||
:param no_packages: do not clear directory with built packages
|
||||
:param build: clear directory with package sources
|
||||
:param cache: clear directory with package caches
|
||||
:param chroot: clear build chroot
|
||||
:param manual: clear directory with manually added packages
|
||||
:param packages: clear directory with built packages
|
||||
:param patches: clear directory with patches
|
||||
"""
|
||||
if not no_build:
|
||||
if build:
|
||||
self.repository.clear_build()
|
||||
if not no_cache:
|
||||
if cache:
|
||||
self.repository.clear_cache()
|
||||
if not no_chroot:
|
||||
if chroot:
|
||||
self.repository.clear_chroot()
|
||||
if not no_manual:
|
||||
if manual:
|
||||
self.repository.clear_manual()
|
||||
if not no_packages:
|
||||
if packages:
|
||||
self.repository.clear_packages()
|
||||
|
||||
def remove(self, names: Iterable[str]) -> None:
|
||||
"""
|
||||
remove packages from repository
|
||||
:param names: list of packages (either base or name) to remove
|
||||
"""
|
||||
self.repository.process_remove(names)
|
||||
self._finalize([])
|
||||
if patches:
|
||||
self.repository.clear_patches()
|
||||
|
||||
def report(self, target: Iterable[str], built_packages: Iterable[Package]) -> None:
|
||||
"""
|
||||
@ -264,3 +147,26 @@ class Application:
|
||||
self.logger.info("processing level #%i %s", num, [package.base for package in level])
|
||||
packages = self.repository.process_build(level)
|
||||
process_update(packages)
|
||||
|
||||
def updates(self, filter_packages: Iterable[str], no_aur: bool, no_manual: bool, no_vcs: bool,
|
||||
log_fn: Callable[[str], None]) -> List[Package]:
|
||||
"""
|
||||
get list of packages to run update process
|
||||
:param filter_packages: do not check every package just specified in the list
|
||||
:param no_aur: do not check for aur updates
|
||||
:param no_manual: do not check for manual updates
|
||||
:param no_vcs: do not check VCS packages
|
||||
:param log_fn: logger function to log updates
|
||||
:return: list of out-of-dated packages
|
||||
"""
|
||||
updates = []
|
||||
|
||||
if not no_aur:
|
||||
updates.extend(self.repository.updates_aur(filter_packages, no_vcs))
|
||||
if not no_manual:
|
||||
updates.extend(self.repository.updates_manual())
|
||||
|
||||
for package in updates:
|
||||
log_fn(f"{package.base} = {package.version}")
|
||||
|
||||
return updates
|
19
src/ahriman/application/formatters/__init__.py
Normal file
19
src/ahriman/application/formatters/__init__.py
Normal file
@ -0,0 +1,19 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
62
src/ahriman/application/formatters/aur_printer.py
Normal file
62
src/ahriman/application/formatters/aur_printer.py
Normal file
@ -0,0 +1,62 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import aur # type: ignore
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from ahriman.application.formatters.printer import Printer
|
||||
from ahriman.core.util import pretty_datetime
|
||||
from ahriman.models.property import Property
|
||||
|
||||
|
||||
class AurPrinter(Printer):
|
||||
"""
|
||||
print content of the AUR package
|
||||
"""
|
||||
|
||||
def __init__(self, package: aur.Package) -> None:
|
||||
"""
|
||||
default constructor
|
||||
:param package: AUR package description
|
||||
"""
|
||||
self.content = package
|
||||
|
||||
def properties(self) -> List[Property]:
|
||||
"""
|
||||
convert content into printable data
|
||||
: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),
|
||||
Property("Licenses", self.content.license), # it should be actually a list
|
||||
Property("Maintainer", self.content.maintainer or ""), # I think it is optional
|
||||
Property("First submitted", pretty_datetime(self.content.first_submitted)),
|
||||
Property("Last updated", pretty_datetime(self.content.last_modified)),
|
||||
# more fields coming https://github.com/cdown/aur/pull/29
|
||||
]
|
||||
|
||||
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})"
|
55
src/ahriman/application/formatters/configuration_printer.py
Normal file
55
src/ahriman/application/formatters/configuration_printer.py
Normal file
@ -0,0 +1,55 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from ahriman.application.formatters.printer import Printer
|
||||
from ahriman.models.property import Property
|
||||
|
||||
|
||||
class ConfigurationPrinter(Printer):
|
||||
"""
|
||||
print content of the configuration section
|
||||
"""
|
||||
|
||||
def __init__(self, section: str, values: Dict[str, str]) -> None:
|
||||
"""
|
||||
default constructor
|
||||
:param section: section name
|
||||
:param values: configuration values dictionary
|
||||
"""
|
||||
self.section = section
|
||||
self.content = values
|
||||
|
||||
def properties(self) -> List[Property]:
|
||||
"""
|
||||
convert content into printable data
|
||||
:return: list of content properties
|
||||
"""
|
||||
return [
|
||||
Property(key, value, is_required=True)
|
||||
for key, value in sorted(self.content.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}]"
|
60
src/ahriman/application/formatters/package_printer.py
Normal file
60
src/ahriman/application/formatters/package_printer.py
Normal file
@ -0,0 +1,60 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from typing import List, Optional
|
||||
|
||||
from ahriman.application.formatters.printer import Printer
|
||||
from ahriman.models.build_status import BuildStatus
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.property import Property
|
||||
|
||||
|
||||
class PackagePrinter(Printer):
|
||||
"""
|
||||
print content of the internal package object
|
||||
"""
|
||||
|
||||
def __init__(self, package: Package, status: BuildStatus) -> None:
|
||||
"""
|
||||
default constructor
|
||||
:param package: package description
|
||||
:param status: build status
|
||||
"""
|
||||
self.content = package
|
||||
self.status = status
|
||||
|
||||
def properties(self) -> List[Property]:
|
||||
"""
|
||||
convert content into printable data
|
||||
: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("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()
|
55
src/ahriman/application/formatters/printer.py
Normal file
55
src/ahriman/application/formatters/printer.py
Normal file
@ -0,0 +1,55 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from typing import Callable, List, Optional
|
||||
|
||||
from ahriman.models.property import Property
|
||||
|
||||
|
||||
class Printer:
|
||||
"""
|
||||
base class for formatters
|
||||
"""
|
||||
|
||||
def print(self, verbose: bool, log_fn: Callable[[str], None] = print, separator: str = ": ") -> None:
|
||||
"""
|
||||
print content
|
||||
:param verbose: print all fields
|
||||
:param log_fn: logger function to log data
|
||||
:param separator: separator for property name and property value
|
||||
"""
|
||||
if (title := self.title()) is not None:
|
||||
log_fn(title)
|
||||
for prop in self.properties():
|
||||
if not verbose and not prop.is_required:
|
||||
continue
|
||||
log_fn(f"\t{prop.name}{separator}{prop.value}")
|
||||
|
||||
def properties(self) -> List[Property]: # pylint: disable=no-self-use
|
||||
"""
|
||||
convert content into printable data
|
||||
:return: list of content properties
|
||||
"""
|
||||
return []
|
||||
|
||||
def title(self) -> Optional[str]:
|
||||
"""
|
||||
generate entry title from content
|
||||
:return: content title if it can be generated and None otherwise
|
||||
"""
|
51
src/ahriman/application/formatters/status_printer.py
Normal file
51
src/ahriman/application/formatters/status_printer.py
Normal file
@ -0,0 +1,51 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from typing import List, Optional
|
||||
|
||||
from ahriman.application.formatters.printer import Printer
|
||||
from ahriman.models.build_status import BuildStatus
|
||||
from ahriman.models.property import Property
|
||||
|
||||
|
||||
class StatusPrinter(Printer):
|
||||
"""
|
||||
print content of the status object
|
||||
"""
|
||||
|
||||
def __init__(self, status: BuildStatus) -> None:
|
||||
"""
|
||||
default constructor
|
||||
:param status: build status
|
||||
"""
|
||||
self.content = status
|
||||
|
||||
def properties(self) -> List[Property]:
|
||||
"""
|
||||
convert content into printable data
|
||||
:return: list of content properties
|
||||
"""
|
||||
return []
|
||||
|
||||
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()
|
@ -46,5 +46,5 @@ class Add(Handler):
|
||||
if not args.now:
|
||||
return
|
||||
|
||||
packages = application.get_updates(args.package, True, False, True, application.logger.info)
|
||||
packages = application.updates(args.package, True, False, True, application.logger.info)
|
||||
application.update(packages)
|
||||
|
@ -41,5 +41,5 @@ class Clean(Handler):
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
Application(architecture, configuration, no_report).clean(args.no_build, args.no_cache, args.no_chroot,
|
||||
args.no_manual, args.no_packages)
|
||||
Application(architecture, configuration, no_report).clean(
|
||||
args.build, args.cache, args.chroot, args.manual, args.packages, args.patches)
|
||||
|
@ -21,6 +21,7 @@ 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
|
||||
|
||||
@ -32,8 +33,6 @@ class Dump(Handler):
|
||||
|
||||
ALLOW_AUTO_ARCHITECTURE_RUN = False
|
||||
|
||||
_print = print
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||
configuration: Configuration, no_report: bool) -> None:
|
||||
@ -46,7 +45,4 @@ class Dump(Handler):
|
||||
"""
|
||||
dump = configuration.dump()
|
||||
for section, values in sorted(dump.items()):
|
||||
Dump._print(f"[{section}]")
|
||||
for key, value in sorted(values.items()):
|
||||
Dump._print(f"{key} = {value}")
|
||||
Dump._print()
|
||||
ConfigurationPrinter(section, values).print(verbose=False, separator=" = ")
|
||||
|
@ -22,9 +22,10 @@ import argparse
|
||||
from typing import Type
|
||||
|
||||
from ahriman.application.application import Application
|
||||
from ahriman.application.formatters.package_printer import PackagePrinter
|
||||
from ahriman.application.handlers.handler import Handler
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.build_status import BuildStatus
|
||||
|
||||
|
||||
class RemoveUnknown(Handler):
|
||||
@ -46,16 +47,7 @@ class RemoveUnknown(Handler):
|
||||
unknown_packages = application.unknown()
|
||||
if args.dry_run:
|
||||
for package in unknown_packages:
|
||||
RemoveUnknown.log_fn(package)
|
||||
PackagePrinter(package, BuildStatus()).print(args.info)
|
||||
return
|
||||
|
||||
application.remove(package.base for package in unknown_packages)
|
||||
|
||||
@staticmethod
|
||||
def log_fn(package: Package) -> None:
|
||||
"""
|
||||
log package information
|
||||
:param package: package object to log
|
||||
"""
|
||||
print(f"=> {package.base} {package.version}")
|
||||
print(f" {package.web_url}")
|
||||
|
@ -20,10 +20,13 @@
|
||||
import argparse
|
||||
import aur # type: ignore
|
||||
|
||||
from typing import Callable, Type
|
||||
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.configuration import Configuration
|
||||
from ahriman.core.exceptions import InvalidOption
|
||||
from ahriman.core.util import aur_search
|
||||
|
||||
|
||||
class Search(Handler):
|
||||
@ -32,6 +35,7 @@ class Search(Handler):
|
||||
"""
|
||||
|
||||
ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture"
|
||||
SORT_FIELDS = set(aur.Package._fields) # later we will have to remove some fields from here (lists)
|
||||
|
||||
@classmethod
|
||||
def run(cls: Type[Handler], args: argparse.Namespace, architecture: str,
|
||||
@ -43,20 +47,22 @@ class Search(Handler):
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
search = " ".join(args.search)
|
||||
packages = aur.search(search)
|
||||
|
||||
# it actually always should return string
|
||||
# explicit cast to string just to avoid mypy warning for untyped library
|
||||
comparator: Callable[[aur.Package], str] = lambda item: str(item.package_base)
|
||||
for package in sorted(packages, key=comparator):
|
||||
Search.log_fn(package)
|
||||
packages_list = aur_search(*args.search)
|
||||
for package in Search.sort(packages_list, args.sort_by):
|
||||
AurPrinter(package).print(args.info)
|
||||
|
||||
@staticmethod
|
||||
def log_fn(package: aur.Package) -> None:
|
||||
def sort(packages: Iterable[aur.Package], sort_by: str) -> List[aur.Package]:
|
||||
"""
|
||||
log package information
|
||||
:param package: package object as from AUR
|
||||
sort package list by specified field
|
||||
:param packages: packages list to sort
|
||||
:param sort_by: AUR package field name to sort by
|
||||
:return: sorted list for packages
|
||||
"""
|
||||
print(f"=> {package.package_base} {package.version}")
|
||||
print(f" {package.description}")
|
||||
if sort_by not in Search.SORT_FIELDS:
|
||||
raise InvalidOption(sort_by)
|
||||
# always sort by package name at the last
|
||||
# well technically it is not a string, but we can deal with it
|
||||
comparator: Callable[[aur.Package], Tuple[str, str]] =\
|
||||
lambda package: (getattr(package, sort_by), package.name)
|
||||
return sorted(packages, key=comparator)
|
||||
|
@ -22,6 +22,8 @@ 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.models.build_status import BuildStatus
|
||||
@ -49,8 +51,7 @@ class Status(Handler):
|
||||
client = Application(architecture, configuration, no_report=False).repository.reporter
|
||||
if args.ahriman:
|
||||
ahriman = client.get_self()
|
||||
print(ahriman.pretty_print())
|
||||
print()
|
||||
StatusPrinter(ahriman).print(args.info)
|
||||
if args.package:
|
||||
packages: Iterable[Tuple[Package, BuildStatus]] = sum(
|
||||
[client.get(base) for base in args.package],
|
||||
@ -62,6 +63,4 @@ class Status(Handler):
|
||||
filter_fn: Callable[[Tuple[Package, BuildStatus]], bool] =\
|
||||
lambda item: args.status is None or item[1].status == args.status
|
||||
for package, package_status in sorted(filter(filter_fn, packages), key=comparator):
|
||||
print(package.pretty_print())
|
||||
print(f"\t{package.version}")
|
||||
print(f"\t{package_status.pretty_print()}")
|
||||
PackagePrinter(package, package_status).print(args.info)
|
||||
|
@ -42,7 +42,7 @@ class Update(Handler):
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
application = Application(architecture, configuration, no_report)
|
||||
packages = application.get_updates(args.package, args.no_aur, args.no_manual, args.no_vcs,
|
||||
packages = application.updates(args.package, args.no_aur, args.no_manual, args.no_vcs,
|
||||
Update.log_fn(application, args.dry_run))
|
||||
if args.dry_run:
|
||||
return
|
||||
|
@ -113,8 +113,7 @@ class User(Handler):
|
||||
:param salt_length: salt length
|
||||
:return: current salt
|
||||
"""
|
||||
salt = configuration.get("auth", "salt", fallback=None)
|
||||
if salt:
|
||||
if salt := configuration.get("auth", "salt", fallback=None):
|
||||
return salt
|
||||
return MUser.generate_password(salt_length)
|
||||
|
||||
|
@ -49,7 +49,7 @@ class Task:
|
||||
:param configuration: configuration instance
|
||||
:param paths: repository paths instance
|
||||
"""
|
||||
self.logger = logging.getLogger("builder")
|
||||
self.logger = logging.getLogger("root")
|
||||
self.build_logger = logging.getLogger("build_details")
|
||||
self.package = package
|
||||
self.paths = paths
|
||||
|
@ -39,7 +39,7 @@ class Configuration(configparser.RawConfigParser):
|
||||
:cvar DEFAULT_LOG_LEVEL: default log level (in case of fallback)
|
||||
"""
|
||||
|
||||
DEFAULT_LOG_FORMAT = "[%(levelname)s %(asctime)s] [%(filename)s:%(lineno)d] [%(funcName)s]: %(message)s"
|
||||
DEFAULT_LOG_FORMAT = "[%(levelname)s %(asctime)s] [%(filename)s:%(lineno)d %(funcName)s]: %(message)s"
|
||||
DEFAULT_LOG_LEVEL = logging.DEBUG
|
||||
|
||||
ARCHITECTURE_SPECIFIC_SECTIONS = ["build", "sign", "web"]
|
||||
@ -175,7 +175,7 @@ class Configuration(configparser.RawConfigParser):
|
||||
level=self.DEFAULT_LOG_LEVEL)
|
||||
logging.exception("could not load logging from configuration, fallback to stderr")
|
||||
if quiet:
|
||||
logging.disable()
|
||||
logging.disable(logging.WARNING) # only print errors here
|
||||
|
||||
def merge_sections(self, architecture: str) -> None:
|
||||
"""
|
||||
|
@ -43,7 +43,7 @@ class Report:
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
"""
|
||||
self.logger = logging.getLogger("builder")
|
||||
self.logger = logging.getLogger("root")
|
||||
self.architecture = architecture
|
||||
self.configuration = configuration
|
||||
|
||||
|
@ -17,3 +17,4 @@
|
||||
# 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 ahriman.core.repository.repository import Repository
|
||||
|
@ -76,3 +76,11 @@ class Cleaner(Properties):
|
||||
self.logger.info("clear built packages directory")
|
||||
for package in self.packages_built():
|
||||
package.unlink()
|
||||
|
||||
def clear_patches(self) -> None:
|
||||
"""
|
||||
clear directory with patches
|
||||
"""
|
||||
self.logger.info("clear patches directory")
|
||||
for package in self.paths.patches.iterdir():
|
||||
shutil.rmtree(package)
|
||||
|
@ -138,17 +138,17 @@ class Executor(Cleaner):
|
||||
:param packages: list of filenames to run
|
||||
:return: path to repository database
|
||||
"""
|
||||
def update_single(fn: Optional[str], base: str) -> None:
|
||||
if fn is None:
|
||||
def update_single(name: Optional[str], base: str) -> None:
|
||||
if name is None:
|
||||
self.logger.warning("received empty package name for base %s", base)
|
||||
return # suppress type checking, it never can be none actually
|
||||
# in theory it might be NOT packages directory, but we suppose it is
|
||||
full_path = self.paths.packages / fn
|
||||
full_path = self.paths.packages / name
|
||||
files = self.sign.process_sign_package(full_path, base)
|
||||
for src in files:
|
||||
dst = self.paths.repository / src.name
|
||||
shutil.move(src, dst)
|
||||
package_path = self.paths.repository / fn
|
||||
package_path = self.paths.repository / name
|
||||
self.repo.add(package_path)
|
||||
|
||||
# we are iterating over bases, not single packages
|
||||
|
@ -52,7 +52,7 @@ class Properties:
|
||||
:param configuration: configuration instance
|
||||
:param no_report: force disable reporting
|
||||
"""
|
||||
self.logger = logging.getLogger("builder")
|
||||
self.logger = logging.getLogger("root")
|
||||
self.architecture = architecture
|
||||
self.configuration = configuration
|
||||
|
||||
@ -64,7 +64,7 @@ class Properties:
|
||||
check_user(self.paths.root)
|
||||
self.paths.tree_create()
|
||||
except UnsafeRun:
|
||||
self.logger.exception("root owner differs from the current user, skipping tree creation")
|
||||
self.logger.warning("root owner differs from the current user, skipping tree creation")
|
||||
|
||||
self.ignore_list = configuration.getlist("build", "ignore_packages", fallback=[])
|
||||
self.pacman = Pacman(configuration)
|
||||
|
@ -72,16 +72,16 @@ class UpdateHandler(Cleaner):
|
||||
result: List[Package] = []
|
||||
known_bases = {package.base for package in self.packages()}
|
||||
|
||||
for fn in self.paths.manual.iterdir():
|
||||
for filename in self.paths.manual.iterdir():
|
||||
try:
|
||||
local = Package.load(fn, self.pacman, self.aur_url)
|
||||
local = Package.load(filename, self.pacman, self.aur_url)
|
||||
result.append(local)
|
||||
if local.base not in known_bases:
|
||||
self.reporter.set_unknown(local)
|
||||
else:
|
||||
self.reporter.set_pending(local.base)
|
||||
except Exception:
|
||||
self.logger.exception("could not add package from %s", fn)
|
||||
self.logger.exception("could not add package from %s", filename)
|
||||
self.clear_manual()
|
||||
|
||||
return result
|
||||
|
@ -25,7 +25,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import UnknownPackage
|
||||
from ahriman.core.repository.repository import Repository
|
||||
from ahriman.core.repository import Repository
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
from ahriman.models.package import Package
|
||||
|
||||
@ -126,11 +126,10 @@ class Watcher:
|
||||
"""
|
||||
for package in self.repository.packages():
|
||||
# get status of build or assign unknown
|
||||
current = self.known.get(package.base)
|
||||
if current is None:
|
||||
status = BuildStatus()
|
||||
else:
|
||||
if (current := self.known.get(package.base)) is not None:
|
||||
_, status = current
|
||||
else:
|
||||
status = BuildStatus()
|
||||
self.known[package.base] = (package, status)
|
||||
self._cache_load()
|
||||
|
||||
|
@ -44,7 +44,7 @@ class Upload:
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
"""
|
||||
self.logger = logging.getLogger("builder")
|
||||
self.logger = logging.getLogger("root")
|
||||
self.architecture = architecture
|
||||
self.config = configuration
|
||||
|
||||
|
@ -17,6 +17,7 @@
|
||||
# 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 aur # type: ignore
|
||||
import datetime
|
||||
import os
|
||||
import subprocess
|
||||
@ -24,11 +25,29 @@ import requests
|
||||
|
||||
from logging import Logger
|
||||
from pathlib import Path
|
||||
from typing import Generator, Optional, Union
|
||||
from typing import Any, Dict, Generator, Iterable, List, Optional, Union
|
||||
|
||||
from ahriman.core.exceptions import InvalidOption, UnsafeRun
|
||||
|
||||
|
||||
def aur_search(*terms: str) -> List[aur.Package]:
|
||||
"""
|
||||
search in AUR by using API with multiple words. This method is required in order to handle
|
||||
https://bugs.archlinux.org/task/49133. In addition short words will be dropped
|
||||
:param terms: search terms, e.g. "ahriman", "is", "cool"
|
||||
:return: list of packages each of them matches all search terms
|
||||
"""
|
||||
packages: Dict[str, aur.Package] = {}
|
||||
for term in filter(lambda word: len(word) > 3, terms):
|
||||
portion = aur.search(term)
|
||||
packages = {
|
||||
package.package_base: package
|
||||
for package in portion
|
||||
if package.package_base in packages or not packages
|
||||
}
|
||||
return list(packages.values())
|
||||
|
||||
|
||||
def check_output(*args: str, exception: Optional[Exception], cwd: Optional[Path] = None,
|
||||
input_data: Optional[str] = None, logger: Optional[Logger] = None) -> str:
|
||||
"""
|
||||
@ -78,6 +97,16 @@ def exception_response_text(exception: requests.exceptions.HTTPError) -> str:
|
||||
return result
|
||||
|
||||
|
||||
def filter_json(source: Dict[str, Any], known_fields: Iterable[str]) -> Dict[str, Any]:
|
||||
"""
|
||||
filter json object by fields used for json-to-object conversion
|
||||
:param source: raw json object
|
||||
:param known_fields: list of fields which have to be known for the target object
|
||||
:return: json object without unknown and empty fields
|
||||
"""
|
||||
return {key: value for key, value in source.items() if key in known_fields and value is not None}
|
||||
|
||||
|
||||
def package_like(filename: Path) -> bool:
|
||||
"""
|
||||
check if file looks like package
|
||||
@ -88,13 +117,17 @@ def package_like(filename: Path) -> bool:
|
||||
return ".pkg." in name and not name.endswith(".sig")
|
||||
|
||||
|
||||
def pretty_datetime(timestamp: Optional[Union[float, int]]) -> str:
|
||||
def pretty_datetime(timestamp: Optional[Union[datetime.datetime, float, int]]) -> str:
|
||||
"""
|
||||
convert datetime object to string
|
||||
:param timestamp: datetime to convert
|
||||
:return: pretty printable datetime as string
|
||||
"""
|
||||
return "" if timestamp is None else datetime.datetime.utcfromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")
|
||||
if timestamp is None:
|
||||
return ""
|
||||
if isinstance(timestamp, (int, float)):
|
||||
timestamp = datetime.datetime.utcfromtimestamp(timestamp)
|
||||
return timestamp.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
|
||||
def pretty_size(size: Optional[float], level: int = 0) -> str:
|
||||
|
@ -21,10 +21,11 @@ from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
|
||||
from dataclasses import dataclass, fields
|
||||
from enum import Enum
|
||||
from typing import Any, Dict, Optional, Type, Union
|
||||
from typing import Any, Dict, Type
|
||||
|
||||
from ahriman.core.util import pretty_datetime
|
||||
from ahriman.core.util import filter_json, pretty_datetime
|
||||
|
||||
|
||||
class BuildStatusEnum(Enum):
|
||||
@ -74,6 +75,7 @@ class BuildStatusEnum(Enum):
|
||||
return "secondary"
|
||||
|
||||
|
||||
@dataclass
|
||||
class BuildStatus:
|
||||
"""
|
||||
build status holder
|
||||
@ -81,15 +83,14 @@ class BuildStatus:
|
||||
:ivar timestamp: build status update time
|
||||
"""
|
||||
|
||||
def __init__(self, status: Union[BuildStatusEnum, str, None] = None,
|
||||
timestamp: Optional[int] = None) -> None:
|
||||
status: BuildStatusEnum = BuildStatusEnum.Unknown
|
||||
timestamp: int = int(datetime.datetime.utcnow().timestamp())
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
"""
|
||||
default constructor
|
||||
:param status: current build status if known. `BuildStatusEnum.Unknown` will be used if not set
|
||||
:param timestamp: build status timestamp. Current timestamp will be used if not set
|
||||
convert status to enum type
|
||||
"""
|
||||
self.status = BuildStatusEnum(status) if status else BuildStatusEnum.Unknown
|
||||
self.timestamp = timestamp or int(datetime.datetime.utcnow().timestamp())
|
||||
self.status = BuildStatusEnum(self.status)
|
||||
|
||||
@classmethod
|
||||
def from_json(cls: Type[BuildStatus], dump: Dict[str, Any]) -> BuildStatus:
|
||||
@ -98,7 +99,8 @@ class BuildStatus:
|
||||
:param dump: json dump body
|
||||
:return: status properties
|
||||
"""
|
||||
return cls(dump.get("status"), dump.get("timestamp"))
|
||||
known_fields = [pair.name for pair in fields(cls)]
|
||||
return cls(**filter_json(dump, known_fields))
|
||||
|
||||
def pretty_print(self) -> str:
|
||||
"""
|
||||
@ -116,20 +118,3 @@ class BuildStatus:
|
||||
"status": self.status.value,
|
||||
"timestamp": self.timestamp
|
||||
}
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
"""
|
||||
compare object to other
|
||||
:param other: other object to compare
|
||||
:return: True in case if objects are equal
|
||||
"""
|
||||
if not isinstance(other, BuildStatus):
|
||||
return False
|
||||
return self.status == other.status and self.timestamp == other.timestamp
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""
|
||||
generate string representation of object
|
||||
:return: unique string representation
|
||||
"""
|
||||
return f"BuildStatus(status={self.status.value}, timestamp={self.timestamp})"
|
||||
|
@ -22,6 +22,7 @@ from __future__ import annotations
|
||||
from dataclasses import dataclass, fields
|
||||
from typing import Any, Dict, List, Tuple, Type
|
||||
|
||||
from ahriman.core.util import filter_json
|
||||
from ahriman.models.build_status import BuildStatus
|
||||
from ahriman.models.package import Package
|
||||
|
||||
@ -54,8 +55,7 @@ class Counters:
|
||||
"""
|
||||
# filter to only known fields
|
||||
known_fields = [pair.name for pair in fields(cls)]
|
||||
dump = {key: value for key, value in dump.items() if key in known_fields}
|
||||
return cls(**dump)
|
||||
return cls(**filter_json(dump, known_fields))
|
||||
|
||||
@classmethod
|
||||
def from_packages(cls: Type[Counters], packages: List[Tuple[Package, BuildStatus]]) -> Counters:
|
||||
|
@ -24,6 +24,8 @@ from pathlib import Path
|
||||
from pyalpm import Package # type: ignore
|
||||
from typing import Any, Dict, List, Optional, Type
|
||||
|
||||
from ahriman.core.util import filter_json
|
||||
|
||||
|
||||
@dataclass
|
||||
class PackageDescription:
|
||||
@ -70,8 +72,7 @@ class PackageDescription:
|
||||
"""
|
||||
# filter to only known fields
|
||||
known_fields = [pair.name for pair in fields(cls)]
|
||||
dump = {key: value for key, value in dump.items() if key in known_fields}
|
||||
return cls(**dump)
|
||||
return cls(**filter_json(dump, known_fields))
|
||||
|
||||
@classmethod
|
||||
def from_package(cls: Type[PackageDescription], package: Package, path: Path) -> PackageDescription:
|
||||
|
@ -21,6 +21,7 @@ from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from ahriman.core.util import package_like
|
||||
|
||||
@ -33,6 +34,7 @@ class PackageSource(Enum):
|
||||
:cvar AUR: source is an AUR package for which it should search
|
||||
:cvar Directory: source is a directory which contains packages
|
||||
:cvar Local: source is locally stored PKGBUILD
|
||||
:cvar Remote: source is remote (http, ftp etc) link
|
||||
"""
|
||||
|
||||
Auto = "auto"
|
||||
@ -40,6 +42,7 @@ class PackageSource(Enum):
|
||||
AUR = "aur"
|
||||
Directory = "directory"
|
||||
Local = "local"
|
||||
Remote = "remote"
|
||||
|
||||
def resolve(self, source: str) -> PackageSource:
|
||||
"""
|
||||
@ -50,11 +53,16 @@ class PackageSource(Enum):
|
||||
if self != PackageSource.Auto:
|
||||
return self
|
||||
|
||||
maybe_path = Path(source)
|
||||
maybe_url = urlparse(source) # handle file:// like paths
|
||||
maybe_path = Path(maybe_url.path)
|
||||
|
||||
if maybe_url.scheme and maybe_url.scheme not in ("data", "file") and package_like(maybe_path):
|
||||
return PackageSource.Remote
|
||||
if (maybe_path / "PKGBUILD").is_file():
|
||||
return PackageSource.Local
|
||||
if maybe_path.is_dir():
|
||||
return PackageSource.Directory
|
||||
if maybe_path.is_file() and package_like(maybe_path):
|
||||
return PackageSource.Archive
|
||||
|
||||
return PackageSource.AUR
|
||||
|
31
src/ahriman/models/property.py
Normal file
31
src/ahriman/models/property.py
Normal file
@ -0,0 +1,31 @@
|
||||
# (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 dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
|
||||
@dataclass
|
||||
class Property:
|
||||
"""
|
||||
holder of object properties descriptor
|
||||
:ivar name: name of the property
|
||||
:ivar value: property value
|
||||
:ivar is_required: if set to True then this property is required
|
||||
"""
|
||||
|
||||
name: str
|
||||
value: Any
|
||||
is_required: bool = False
|
@ -17,4 +17,4 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
__version__ = "1.5.0"
|
||||
__version__ = "1.6.0"
|
||||
|
@ -81,8 +81,7 @@ def auth_handler() -> MiddlewareType:
|
||||
"""
|
||||
@middleware
|
||||
async def handle(request: Request, handler: HandlerType) -> StreamResponse:
|
||||
permission_method = getattr(handler, "get_permission", None)
|
||||
if permission_method is not None:
|
||||
if (permission_method := getattr(handler, "get_permission", None)) is not None:
|
||||
permission = await permission_method(request)
|
||||
elif isinstance(handler, types.MethodType): # additional wrapper for static resources
|
||||
handler_instance = getattr(handler, "__self__", None)
|
||||
|
@ -18,8 +18,8 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from aiohttp.web import middleware, Request
|
||||
from aiohttp.web_exceptions import HTTPException
|
||||
from aiohttp.web_response import StreamResponse
|
||||
from aiohttp.web_exceptions import HTTPClientError, HTTPException, HTTPServerError
|
||||
from aiohttp.web_response import json_response, StreamResponse
|
||||
from logging import Logger
|
||||
|
||||
from ahriman.web.middlewares import HandlerType, MiddlewareType
|
||||
@ -35,10 +35,15 @@ def exception_handler(logger: Logger) -> MiddlewareType:
|
||||
async def handle(request: Request, handler: HandlerType) -> StreamResponse:
|
||||
try:
|
||||
return await handler(request)
|
||||
except HTTPClientError as e:
|
||||
return json_response(data={"error": e.reason}, status=e.status_code)
|
||||
except HTTPServerError as e:
|
||||
logger.exception("server exception during performing request to %s", request.path)
|
||||
return json_response(data={"error": e.reason}, status=e.status_code)
|
||||
except HTTPException:
|
||||
raise # we do not raise 5xx exceptions actually so it should be fine
|
||||
except Exception:
|
||||
logger.exception("exception during performing request to %s", request.path)
|
||||
raise
|
||||
raise # just raise 2xx and 3xx codes
|
||||
except Exception as e:
|
||||
logger.exception("unknown exception during performing request to %s", request.path)
|
||||
return json_response(data={"error": str(e)}, status=500)
|
||||
|
||||
return handle
|
||||
|
@ -17,7 +17,7 @@
|
||||
# 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 aiohttp.web import HTTPFound, Response, json_response
|
||||
from aiohttp.web import HTTPBadRequest, HTTPFound, Response
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.base import BaseView
|
||||
@ -42,12 +42,11 @@ class AddView(BaseView):
|
||||
|
||||
:return: redirect to main page on success
|
||||
"""
|
||||
data = await self.extract_data(["packages"])
|
||||
|
||||
try:
|
||||
data = await self.extract_data(["packages"])
|
||||
packages = data["packages"]
|
||||
except Exception as e:
|
||||
return json_response(data=str(e), status=400)
|
||||
raise HTTPBadRequest(reason=str(e))
|
||||
|
||||
self.spawner.packages_add(packages, now=True)
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
# 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 aiohttp.web import HTTPFound, Response, json_response
|
||||
from aiohttp.web import HTTPBadRequest, HTTPFound, Response
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.base import BaseView
|
||||
@ -42,12 +42,11 @@ class RemoveView(BaseView):
|
||||
|
||||
:return: redirect to main page on success
|
||||
"""
|
||||
data = await self.extract_data(["packages"])
|
||||
|
||||
try:
|
||||
data = await self.extract_data(["packages"])
|
||||
packages = data["packages"]
|
||||
except Exception as e:
|
||||
return json_response(data=str(e), status=400)
|
||||
raise HTTPBadRequest(reason=str(e))
|
||||
|
||||
self.spawner.packages_remove(packages)
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
# 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 aiohttp.web import HTTPFound, Response, json_response
|
||||
from aiohttp.web import HTTPBadRequest, HTTPFound, Response
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.base import BaseView
|
||||
@ -42,12 +42,11 @@ class RequestView(BaseView):
|
||||
|
||||
:return: redirect to main page on success
|
||||
"""
|
||||
data = await self.extract_data(["packages"])
|
||||
|
||||
try:
|
||||
data = await self.extract_data(["packages"])
|
||||
packages = data["packages"]
|
||||
except Exception as e:
|
||||
return json_response(data=str(e), status=400)
|
||||
raise HTTPBadRequest(reason=str(e))
|
||||
|
||||
self.spawner.packages_add(packages, now=False)
|
||||
|
||||
|
@ -19,9 +19,10 @@
|
||||
#
|
||||
import aur # type: ignore
|
||||
|
||||
from aiohttp.web import Response, json_response
|
||||
from typing import Callable, Iterator
|
||||
from aiohttp.web import HTTPNotFound, Response, json_response
|
||||
from typing import Callable, List
|
||||
|
||||
from ahriman.core.util import aur_search
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
@ -43,12 +44,10 @@ class SearchView(BaseView):
|
||||
|
||||
:return: 200 with found package bases and descriptions sorted by base
|
||||
"""
|
||||
search: Iterator[str] = filter(lambda s: len(s) > 3, self.request.query.getall("for", default=[]))
|
||||
search_string = " ".join(search)
|
||||
|
||||
if not search_string:
|
||||
return json_response(data="Search string must not be empty", status=400)
|
||||
packages = aur.search(search_string)
|
||||
search: List[str] = self.request.query.getall("for", default=[])
|
||||
packages = aur_search(*search)
|
||||
if not packages:
|
||||
raise HTTPNotFound(reason=f"No packages found for terms: {search}")
|
||||
|
||||
comparator: Callable[[aur.Package], str] = lambda item: str(item.package_base)
|
||||
response = [
|
||||
|
@ -17,7 +17,7 @@
|
||||
# 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 aiohttp.web import HTTPNoContent, Response, json_response
|
||||
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
|
||||
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.models.user_access import UserAccess
|
||||
@ -53,12 +53,11 @@ class AhrimanView(BaseView):
|
||||
|
||||
:return: 204 on success
|
||||
"""
|
||||
data = await self.extract_data()
|
||||
|
||||
try:
|
||||
data = await self.extract_data()
|
||||
status = BuildStatusEnum(data["status"])
|
||||
except Exception as e:
|
||||
return json_response(data=str(e), status=400)
|
||||
raise HTTPBadRequest(reason=str(e))
|
||||
|
||||
self.service.update_self(status)
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
# 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 aiohttp.web import HTTPNoContent, HTTPNotFound, Response, json_response
|
||||
from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response
|
||||
|
||||
from ahriman.core.exceptions import UnknownPackage
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
@ -88,11 +88,11 @@ class PackageView(BaseView):
|
||||
package = Package.from_json(data["package"]) if "package" in data else None
|
||||
status = BuildStatusEnum(data["status"])
|
||||
except Exception as e:
|
||||
return json_response(data=str(e), status=400)
|
||||
raise HTTPBadRequest(reason=str(e))
|
||||
|
||||
try:
|
||||
self.service.update(base, status, package)
|
||||
except UnknownPackage:
|
||||
return json_response(data=f"Package {base} is unknown, but no package body set", status=400)
|
||||
raise HTTPBadRequest(reason=f"Package {base} is unknown, but no package body set")
|
||||
|
||||
raise HTTPNoContent()
|
||||
|
@ -45,11 +45,11 @@ class LoginView(BaseView):
|
||||
"""
|
||||
from ahriman.core.auth.oauth import OAuth
|
||||
|
||||
code = self.request.query.getone("code", default=None)
|
||||
oauth_provider = self.validator
|
||||
if not isinstance(oauth_provider, OAuth): # there is actually property, but mypy does not like it anyway
|
||||
raise HTTPMethodNotAllowed(self.request.method, ["POST"])
|
||||
|
||||
code = self.request.query.getone("code", default=None)
|
||||
if not code:
|
||||
raise HTTPFound(oauth_provider.get_oauth_url())
|
||||
|
||||
|
44
tests/ahriman/application/application/conftest.py
Normal file
44
tests/ahriman/application/application/conftest.py
Normal file
@ -0,0 +1,44 @@
|
||||
import pytest
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.application.application.packages import Packages
|
||||
from ahriman.application.application.properties import Properties
|
||||
from ahriman.application.application.repository import Repository
|
||||
from ahriman.core.configuration import Configuration
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def application_packages(configuration: Configuration, mocker: MockerFixture) -> Packages:
|
||||
"""
|
||||
fixture for application with package functions
|
||||
:param configuration: configuration fixture
|
||||
:param mocker: mocker object
|
||||
:return: application test instance
|
||||
"""
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
||||
return Packages("x86_64", configuration, no_report=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def application_properties(configuration: Configuration, mocker: MockerFixture) -> Properties:
|
||||
"""
|
||||
fixture for application with properties only
|
||||
:param configuration: configuration fixture
|
||||
:param mocker: mocker object
|
||||
:return: application test instance
|
||||
"""
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
||||
return Properties("x86_64", configuration, no_report=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def application_repository(configuration: Configuration, mocker: MockerFixture) -> Repository:
|
||||
"""
|
||||
fixture for application with repository functions
|
||||
:param configuration: configuration fixture
|
||||
:param mocker: mocker object
|
||||
:return: application test instance
|
||||
"""
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
||||
return Repository("x86_64", configuration, no_report=True)
|
26
tests/ahriman/application/application/test_application.py
Normal file
26
tests/ahriman/application/application/test_application.py
Normal file
@ -0,0 +1,26 @@
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.application.application import Application
|
||||
from ahriman.models.package import Package
|
||||
|
||||
|
||||
def test_finalize(application: Application, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must report and sync at the last
|
||||
"""
|
||||
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()
|
||||
sync_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_known_packages(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return not empty list of known packages
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman])
|
||||
packages = application._known_packages()
|
||||
assert len(packages) > 1
|
||||
assert package_ahriman.base in packages
|
@ -0,0 +1,207 @@
|
||||
import pytest
|
||||
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest import mock
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
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
|
||||
|
||||
|
||||
def test_finalize(application_packages: Packages) -> None:
|
||||
"""
|
||||
must raise NotImplemented for missing finalize method
|
||||
"""
|
||||
with pytest.raises(NotImplementedError):
|
||||
application_packages._finalize([])
|
||||
|
||||
|
||||
def test_known_packages(application_packages: Packages) -> None:
|
||||
"""
|
||||
must raise NotImplemented for missing finalize method
|
||||
"""
|
||||
with pytest.raises(NotImplementedError):
|
||||
application_packages._known_packages()
|
||||
|
||||
|
||||
def test_add_archive(application_packages: Packages, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must add package from archive
|
||||
"""
|
||||
copy_mock = mocker.patch("shutil.copy")
|
||||
application_packages._add_archive(package_ahriman.base)
|
||||
copy_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_add_aur(application_packages: Packages, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must add package from AUR
|
||||
"""
|
||||
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
|
||||
load_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.load")
|
||||
dependencies_mock = mocker.patch("ahriman.application.application.packages.Packages._process_dependencies")
|
||||
|
||||
application_packages._add_aur(package_ahriman.base, set(), False)
|
||||
load_mock.assert_called_once()
|
||||
dependencies_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_add_directory(application_packages: Packages, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must add packages from directory
|
||||
"""
|
||||
iterdir_mock = mocker.patch("pathlib.Path.iterdir",
|
||||
return_value=[package.filepath for package in package_ahriman.packages.values()])
|
||||
copy_mock = mocker.patch("shutil.copy")
|
||||
|
||||
application_packages._add_directory(package_ahriman.base)
|
||||
iterdir_mock.assert_called_once()
|
||||
copy_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_add_local(application_packages: Packages, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must add package from local sources
|
||||
"""
|
||||
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
|
||||
init_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.init")
|
||||
copytree_mock = mocker.patch("shutil.copytree")
|
||||
dependencies_mock = mocker.patch("ahriman.application.application.packages.Packages._process_dependencies")
|
||||
|
||||
application_packages._add_local(package_ahriman.base, set(), False)
|
||||
init_mock.assert_called_once()
|
||||
copytree_mock.assert_has_calls([
|
||||
mock.call(Path(package_ahriman.base), application_packages.repository.paths.cache_for(package_ahriman.base)),
|
||||
mock.call(application_packages.repository.paths.cache_for(package_ahriman.base),
|
||||
application_packages.repository.paths.manual_for(package_ahriman.base)),
|
||||
])
|
||||
dependencies_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_add_remote(application_packages: Packages, package_description_ahriman: PackageDescription,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must add package from remote source
|
||||
"""
|
||||
response_mock = MagicMock()
|
||||
response_mock.iter_content.return_value = ["chunk"]
|
||||
open_mock = mocker.patch("pathlib.Path.open")
|
||||
request_mock = mocker.patch("requests.get", return_value=response_mock)
|
||||
url = f"https://host/{package_description_ahriman.filename}"
|
||||
|
||||
application_packages._add_remote(url)
|
||||
open_mock.assert_called_once_with("wb")
|
||||
request_mock.assert_called_once_with(url, stream=True)
|
||||
response_mock.raise_for_status.assert_called_once()
|
||||
|
||||
|
||||
def test_process_dependencies(application_packages: Packages, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must process dependencies addition
|
||||
"""
|
||||
missing = {"python"}
|
||||
path = Path("local")
|
||||
dependencies_mock = mocker.patch("ahriman.models.package.Package.dependencies", return_value=missing)
|
||||
add_mock = mocker.patch("ahriman.application.application.packages.Packages.add")
|
||||
|
||||
application_packages._process_dependencies(path, set(), False)
|
||||
dependencies_mock.assert_called_once_with(path)
|
||||
add_mock.assert_called_once_with(missing, PackageSource.AUR, False)
|
||||
|
||||
|
||||
def test_process_dependencies_missing(application_packages: Packages, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must process dependencies addition only for missing packages
|
||||
"""
|
||||
path = Path("local")
|
||||
dependencies_mock = mocker.patch("ahriman.models.package.Package.dependencies",
|
||||
return_value={"python", "python-aiohttp"})
|
||||
add_mock = mocker.patch("ahriman.application.application.packages.Packages.add")
|
||||
|
||||
application_packages._process_dependencies(path, {"python"}, False)
|
||||
dependencies_mock.assert_called_once_with(path)
|
||||
add_mock.assert_called_once_with({"python-aiohttp"}, PackageSource.AUR, False)
|
||||
|
||||
|
||||
def test_process_dependencies_skip(application_packages: Packages, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip dependencies processing
|
||||
"""
|
||||
dependencies_mock = mocker.patch("ahriman.models.package.Package.dependencies")
|
||||
add_mock = mocker.patch("ahriman.application.application.packages.Packages.add")
|
||||
|
||||
application_packages._process_dependencies(Path("local"), set(), True)
|
||||
dependencies_mock.assert_not_called()
|
||||
add_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_add_add_archive(application_packages: Packages, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must add package from archive via add function
|
||||
"""
|
||||
mocker.patch("ahriman.application.application.packages.Packages._known_packages", return_value=set())
|
||||
add_mock = mocker.patch("ahriman.application.application.packages.Packages._add_archive")
|
||||
|
||||
application_packages.add([package_ahriman.base], PackageSource.Archive, False)
|
||||
add_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_add_add_aur(application_packages: Packages, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must add package from AUR via add function
|
||||
"""
|
||||
mocker.patch("ahriman.application.application.packages.Packages._known_packages", return_value=set())
|
||||
add_mock = mocker.patch("ahriman.application.application.packages.Packages._add_aur")
|
||||
|
||||
application_packages.add([package_ahriman.base], PackageSource.AUR, True)
|
||||
add_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_add_add_directory(application_packages: Packages, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must add packages from directory via add function
|
||||
"""
|
||||
mocker.patch("ahriman.application.application.packages.Packages._known_packages", return_value=set())
|
||||
add_mock = mocker.patch("ahriman.application.application.packages.Packages._add_directory")
|
||||
|
||||
application_packages.add([package_ahriman.base], PackageSource.Directory, False)
|
||||
add_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_add_add_local(application_packages: Packages, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must add package from local sources via add function
|
||||
"""
|
||||
mocker.patch("ahriman.application.application.packages.Packages._known_packages", return_value=set())
|
||||
add_mock = mocker.patch("ahriman.application.application.packages.Packages._add_local")
|
||||
|
||||
application_packages.add([package_ahriman.base], PackageSource.Local, False)
|
||||
add_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_add_add_remote(application_packages: Packages, package_description_ahriman: PackageDescription,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must add package from remote source via add function
|
||||
"""
|
||||
mocker.patch("ahriman.application.application.packages.Packages._known_packages", return_value=set())
|
||||
add_mock = mocker.patch("ahriman.application.application.packages.Packages._add_remote")
|
||||
url = f"https://host/{package_description_ahriman.filename}"
|
||||
|
||||
application_packages.add([url], PackageSource.Remote, False)
|
||||
add_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_remove(application_packages: Packages, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must remove package
|
||||
"""
|
||||
executor_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_remove")
|
||||
finalize_mock = mocker.patch("ahriman.application.application.packages.Packages._finalize")
|
||||
|
||||
application_packages.remove([])
|
||||
executor_mock.assert_called_once()
|
||||
finalize_mock.assert_called_once()
|
@ -0,0 +1,8 @@
|
||||
from ahriman.application.application.properties import Properties
|
||||
|
||||
|
||||
def test_create_tree(application_properties: Properties) -> None:
|
||||
"""
|
||||
must have repository attribute
|
||||
"""
|
||||
assert application_properties.repository
|
@ -0,0 +1,270 @@
|
||||
import pytest
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest import mock
|
||||
|
||||
from ahriman.application.application.repository import Repository
|
||||
from ahriman.core.tree import Leaf, Tree
|
||||
from ahriman.models.package import Package
|
||||
|
||||
|
||||
def test_finalize(application_repository: Repository) -> None:
|
||||
"""
|
||||
must raise NotImplemented for missing finalize method
|
||||
"""
|
||||
with pytest.raises(NotImplementedError):
|
||||
application_repository._finalize([])
|
||||
|
||||
|
||||
def test_clean_build(application_repository: Repository, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must clean build directory
|
||||
"""
|
||||
clear_mock = mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_build")
|
||||
application_repository.clean(True, False, False, False, False, False)
|
||||
clear_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_clean_cache(application_repository: Repository, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must clean cache directory
|
||||
"""
|
||||
clear_mock = mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_cache")
|
||||
application_repository.clean(False, True, False, False, False, False)
|
||||
clear_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_clean_chroot(application_repository: Repository, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must clean chroot directory
|
||||
"""
|
||||
clear_mock = mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_chroot")
|
||||
application_repository.clean(False, False, True, False, False, False)
|
||||
clear_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_clean_manual(application_repository: Repository, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must clean manual directory
|
||||
"""
|
||||
clear_mock = mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_manual")
|
||||
application_repository.clean(False, False, False, True, False, False)
|
||||
clear_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_clean_packages(application_repository: Repository, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must clean packages directory
|
||||
"""
|
||||
clear_mock = mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_packages")
|
||||
application_repository.clean(False, False, False, False, True, False)
|
||||
clear_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_clean_patches(application_repository: Repository, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must clean packages directory
|
||||
"""
|
||||
clear_mock = mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_patches")
|
||||
application_repository.clean(False, False, False, False, False, True)
|
||||
clear_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_report(application_repository: Repository, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must generate report
|
||||
"""
|
||||
executor_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_report")
|
||||
application_repository.report([], [])
|
||||
executor_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_sign(application_repository: Repository, package_ahriman: Package, package_python_schedule: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must sign world
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages",
|
||||
return_value=[package_ahriman, package_python_schedule])
|
||||
copy_mock = mocker.patch("shutil.copy")
|
||||
update_mock = mocker.patch("ahriman.application.application.repository.Repository.update")
|
||||
sign_repository_mock = mocker.patch("ahriman.core.sign.gpg.GPG.process_sign_repository")
|
||||
finalize_mock = mocker.patch("ahriman.application.application.repository.Repository._finalize")
|
||||
|
||||
application_repository.sign([])
|
||||
copy_mock.assert_has_calls([
|
||||
mock.call(pytest.helpers.anyvar(str), pytest.helpers.anyvar(str)),
|
||||
mock.call(pytest.helpers.anyvar(str), pytest.helpers.anyvar(str))
|
||||
])
|
||||
update_mock.assert_called_once_with([])
|
||||
sign_repository_mock.assert_called_once()
|
||||
finalize_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_sign_skip(application_repository: Repository, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip sign packages with empty filename
|
||||
"""
|
||||
package_ahriman.packages[package_ahriman.base].filename = None
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.application.application.repository.Repository.update")
|
||||
mocker.patch("ahriman.application.application.repository.Repository._finalize")
|
||||
|
||||
application_repository.sign([])
|
||||
|
||||
|
||||
def test_sign_specific(application_repository: Repository, package_ahriman: Package, package_python_schedule: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must sign only specified packages
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages",
|
||||
return_value=[package_ahriman, package_python_schedule])
|
||||
copy_mock = mocker.patch("shutil.copy")
|
||||
update_mock = mocker.patch("ahriman.application.application.repository.Repository.update")
|
||||
sign_repository_mock = mocker.patch("ahriman.core.sign.gpg.GPG.process_sign_repository")
|
||||
finalize_mock = mocker.patch("ahriman.application.application.repository.Repository._finalize")
|
||||
|
||||
application_repository.sign([package_ahriman.base])
|
||||
copy_mock.assert_called_once()
|
||||
update_mock.assert_called_once_with([])
|
||||
sign_repository_mock.assert_called_once()
|
||||
finalize_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_sync(application_repository: Repository, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must sync to remote
|
||||
"""
|
||||
executor_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_sync")
|
||||
application_repository.sync([], [])
|
||||
executor_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_unknown_no_aur(application_repository: Repository, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return empty list in case if there is locally stored PKGBUILD
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.models.package.Package.from_aur", side_effect=Exception())
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
mocker.patch("ahriman.core.build_tools.sources.Sources.has_remotes", return_value=False)
|
||||
|
||||
assert not application_repository.unknown()
|
||||
|
||||
|
||||
def test_unknown_no_aur_no_local(application_repository: Repository, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return list of packages missing in aur and in local storage
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.models.package.Package.from_aur", side_effect=Exception())
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=False)
|
||||
|
||||
packages = application_repository.unknown()
|
||||
assert packages == [package_ahriman]
|
||||
|
||||
|
||||
def test_unknown_no_local(application_repository: Repository, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return empty list in case if there is package in AUR
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.models.package.Package.from_aur")
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=False)
|
||||
|
||||
assert not application_repository.unknown()
|
||||
|
||||
|
||||
def test_update(application_repository: Repository, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must process package updates
|
||||
"""
|
||||
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)
|
||||
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")
|
||||
finalize_mock = mocker.patch("ahriman.application.application.repository.Repository._finalize")
|
||||
|
||||
application_repository.update([package_ahriman])
|
||||
build_mock.assert_called_once()
|
||||
update_mock.assert_called_once_with(paths)
|
||||
finalize_mock.assert_called_once_with([package_ahriman])
|
||||
|
||||
|
||||
def test_updates_all(application_repository: Repository, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must get updates for all
|
||||
"""
|
||||
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur",
|
||||
return_value=[package_ahriman])
|
||||
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
|
||||
|
||||
application_repository.updates([], no_aur=False, no_manual=False, no_vcs=False, log_fn=print)
|
||||
updates_aur_mock.assert_called_once_with([], False)
|
||||
updates_manual_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_updates_disabled(application_repository: Repository, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must get updates without anything
|
||||
"""
|
||||
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
|
||||
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
|
||||
|
||||
application_repository.updates([], no_aur=True, no_manual=True, no_vcs=False, log_fn=print)
|
||||
updates_aur_mock.assert_not_called()
|
||||
updates_manual_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_updates_no_aur(application_repository: Repository, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must get updates without aur
|
||||
"""
|
||||
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
|
||||
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
|
||||
|
||||
application_repository.updates([], no_aur=True, no_manual=False, no_vcs=False, log_fn=print)
|
||||
updates_aur_mock.assert_not_called()
|
||||
updates_manual_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_updates_no_manual(application_repository: Repository, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must get updates without manual
|
||||
"""
|
||||
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
|
||||
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
|
||||
|
||||
application_repository.updates([], no_aur=False, no_manual=True, no_vcs=False, log_fn=print)
|
||||
updates_aur_mock.assert_called_once_with([], False)
|
||||
updates_manual_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_updates_no_vcs(application_repository: Repository, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must get updates without VCS
|
||||
"""
|
||||
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
|
||||
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
|
||||
|
||||
application_repository.updates([], no_aur=False, no_manual=False, no_vcs=True, log_fn=print)
|
||||
updates_aur_mock.assert_called_once_with([], True)
|
||||
updates_manual_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_updates_with_filter(application_repository: Repository, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must get updates without VCS
|
||||
"""
|
||||
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
|
||||
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
|
||||
|
||||
application_repository.updates(["filter"], no_aur=False, no_manual=False, no_vcs=False, log_fn=print)
|
||||
updates_aur_mock.assert_called_once_with(["filter"], False)
|
||||
updates_manual_mock.assert_called_once()
|
47
tests/ahriman/application/formatters/conftest.py
Normal file
47
tests/ahriman/application/formatters/conftest.py
Normal file
@ -0,0 +1,47 @@
|
||||
import aur
|
||||
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.models.build_status import BuildStatus
|
||||
from ahriman.models.package import Package
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def aur_package_ahriman_printer(aur_package_ahriman: aur.Package) -> AurPrinter:
|
||||
"""
|
||||
fixture for AUR package printer
|
||||
:param aur_package_ahriman: AUR package fixture
|
||||
:return: AUR package printer test instance
|
||||
"""
|
||||
return AurPrinter(aur_package_ahriman)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def configuration_printer() -> ConfigurationPrinter:
|
||||
"""
|
||||
fixture for configuration printer
|
||||
:return: configuration printer test instance
|
||||
"""
|
||||
return ConfigurationPrinter("section", {"key_one": "value_one", "key_two": "value_two"})
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def package_ahriman_printer(package_ahriman: Package) -> PackagePrinter:
|
||||
"""
|
||||
fixture for package printer
|
||||
:param package_ahriman: package fixture
|
||||
:return: package printer test instance
|
||||
"""
|
||||
return PackagePrinter(package_ahriman, BuildStatus())
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def status_printer(package_ahriman: Package) -> StatusPrinter:
|
||||
"""
|
||||
fixture for build status printer
|
||||
:return: build status printer test instance
|
||||
"""
|
||||
return StatusPrinter(BuildStatus())
|
15
tests/ahriman/application/formatters/test_aur_printer.py
Normal file
15
tests/ahriman/application/formatters/test_aur_printer.py
Normal file
@ -0,0 +1,15 @@
|
||||
from ahriman.application.formatters.aur_printer import AurPrinter
|
||||
|
||||
|
||||
def test_properties(aur_package_ahriman_printer: AurPrinter) -> None:
|
||||
"""
|
||||
must return non empty properties list
|
||||
"""
|
||||
assert aur_package_ahriman_printer.properties()
|
||||
|
||||
|
||||
def test_title(aur_package_ahriman_printer: AurPrinter) -> None:
|
||||
"""
|
||||
must return non empty title
|
||||
"""
|
||||
assert aur_package_ahriman_printer.title() is not None
|
@ -0,0 +1,22 @@
|
||||
from ahriman.application.formatters.configuration_printer import ConfigurationPrinter
|
||||
|
||||
|
||||
def test_properties(configuration_printer: ConfigurationPrinter) -> None:
|
||||
"""
|
||||
must return non empty properties list
|
||||
"""
|
||||
assert configuration_printer.properties()
|
||||
|
||||
|
||||
def test_properties_required(configuration_printer: ConfigurationPrinter) -> None:
|
||||
"""
|
||||
must return all properties as required
|
||||
"""
|
||||
assert all(prop.is_required for prop in configuration_printer.properties())
|
||||
|
||||
|
||||
def test_title(configuration_printer: ConfigurationPrinter) -> None:
|
||||
"""
|
||||
must return non empty title
|
||||
"""
|
||||
assert configuration_printer.title() == "[section]"
|
15
tests/ahriman/application/formatters/test_package_printer.py
Normal file
15
tests/ahriman/application/formatters/test_package_printer.py
Normal file
@ -0,0 +1,15 @@
|
||||
from ahriman.application.formatters.package_printer import PackagePrinter
|
||||
|
||||
|
||||
def test_properties(package_ahriman_printer: PackagePrinter) -> None:
|
||||
"""
|
||||
must return non empty properties list
|
||||
"""
|
||||
assert package_ahriman_printer.properties()
|
||||
|
||||
|
||||
def test_title(package_ahriman_printer: PackagePrinter) -> None:
|
||||
"""
|
||||
must return non empty title
|
||||
"""
|
||||
assert package_ahriman_printer.title() is not None
|
45
tests/ahriman/application/formatters/test_printer.py
Normal file
45
tests/ahriman/application/formatters/test_printer.py
Normal file
@ -0,0 +1,45 @@
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from ahriman.application.formatters.package_printer import PackagePrinter
|
||||
from ahriman.application.formatters.printer import Printer
|
||||
|
||||
|
||||
def test_print(package_ahriman_printer: PackagePrinter) -> None:
|
||||
"""
|
||||
must print content
|
||||
"""
|
||||
log_mock = MagicMock()
|
||||
package_ahriman_printer.print(verbose=False, log_fn=log_mock)
|
||||
log_mock.assert_called()
|
||||
|
||||
|
||||
def test_print_empty() -> None:
|
||||
"""
|
||||
must not print empty object
|
||||
"""
|
||||
log_mock = MagicMock()
|
||||
Printer().print(verbose=True, log_fn=log_mock)
|
||||
log_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_print_verbose(package_ahriman_printer: PackagePrinter) -> None:
|
||||
"""
|
||||
must print content with increased verbosity
|
||||
"""
|
||||
log_mock = MagicMock()
|
||||
package_ahriman_printer.print(verbose=True, log_fn=log_mock)
|
||||
log_mock.assert_called()
|
||||
|
||||
|
||||
def test_properties() -> None:
|
||||
"""
|
||||
must return empty properties list
|
||||
"""
|
||||
assert Printer().properties() == []
|
||||
|
||||
|
||||
def test_title() -> None:
|
||||
"""
|
||||
must return empty title
|
||||
"""
|
||||
assert Printer().title() is None
|
15
tests/ahriman/application/formatters/test_status_printer.py
Normal file
15
tests/ahriman/application/formatters/test_status_printer.py
Normal file
@ -0,0 +1,15 @@
|
||||
from ahriman.application.formatters.status_printer import StatusPrinter
|
||||
|
||||
|
||||
def test_properties(status_printer: StatusPrinter) -> None:
|
||||
"""
|
||||
must return empty properties list
|
||||
"""
|
||||
assert not status_printer.properties()
|
||||
|
||||
|
||||
def test_title(status_printer: StatusPrinter) -> None:
|
||||
"""
|
||||
must return non empty title
|
||||
"""
|
||||
assert status_printer.title() is not None
|
@ -41,7 +41,7 @@ def test_run_with_updates(args: argparse.Namespace, configuration: Configuration
|
||||
mocker.patch("ahriman.application.application.Application.add")
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
||||
application_mock = mocker.patch("ahriman.application.application.Application.update")
|
||||
updates_mock = mocker.patch("ahriman.application.application.Application.get_updates")
|
||||
updates_mock = mocker.patch("ahriman.application.application.Application.updates")
|
||||
|
||||
Add.run(args, "x86_64", configuration, True)
|
||||
application_mock.assert_called_once()
|
||||
|
@ -12,11 +12,12 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
:param args: command line arguments fixture
|
||||
:return: generated arguments for these test cases
|
||||
"""
|
||||
args.no_build = False
|
||||
args.no_cache = False
|
||||
args.no_chroot = False
|
||||
args.no_manual = False
|
||||
args.no_packages = False
|
||||
args.build = False
|
||||
args.cache = False
|
||||
args.chroot = False
|
||||
args.manual = False
|
||||
args.packages = False
|
||||
args.patches = False
|
||||
return args
|
||||
|
||||
|
||||
|
@ -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.handlers.dump.Dump._print")
|
||||
print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print")
|
||||
application_mock = mocker.patch("ahriman.core.configuration.Configuration.dump",
|
||||
return_value=configuration.dump())
|
||||
|
||||
|
@ -14,6 +14,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
:return: generated arguments for these test cases
|
||||
"""
|
||||
args.dry_run = False
|
||||
args.info = False
|
||||
return args
|
||||
|
||||
|
||||
@ -42,19 +43,29 @@ 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")
|
||||
log_fn_mock = mocker.patch("ahriman.application.handlers.remove_unknown.RemoveUnknown.log_fn")
|
||||
print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print")
|
||||
|
||||
RemoveUnknown.run(args, "x86_64", configuration, True)
|
||||
application_mock.assert_called_once()
|
||||
remove_mock.assert_not_called()
|
||||
log_fn_mock.assert_called_once_with(package_ahriman)
|
||||
print_mock.assert_called_once_with(False)
|
||||
|
||||
|
||||
def test_log_fn(package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
def test_run_dry_run_verbose(args: argparse.Namespace, configuration: Configuration, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
log function must call print built-in
|
||||
must run simplified command with increased verbosity
|
||||
"""
|
||||
print_mock = mocker.patch("builtins.print")
|
||||
args = _default_args(args)
|
||||
args.dry_run = True
|
||||
args.info = True
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
||||
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")
|
||||
|
||||
RemoveUnknown.log_fn(package_ahriman)
|
||||
print_mock.assert_called() # we don't really care about call details tbh
|
||||
RemoveUnknown.run(args, "x86_64", configuration, True)
|
||||
application_mock.assert_called_once()
|
||||
remove_mock.assert_not_called()
|
||||
print_mock.assert_called_once_with(True)
|
||||
|
@ -1,10 +1,12 @@
|
||||
import argparse
|
||||
import aur
|
||||
import pytest
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.application.handlers import Search
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import InvalidOption
|
||||
|
||||
|
||||
def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
@ -14,6 +16,8 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
:return: generated arguments for these test cases
|
||||
"""
|
||||
args.search = ["ahriman"]
|
||||
args.info = False
|
||||
args.sort_by = "name"
|
||||
return args
|
||||
|
||||
|
||||
@ -23,36 +27,60 @@ def test_run(args: argparse.Namespace, configuration: Configuration, aur_package
|
||||
must run command
|
||||
"""
|
||||
args = _default_args(args)
|
||||
mocker.patch("aur.search", return_value=[aur_package_ahriman])
|
||||
log_mock = mocker.patch("ahriman.application.handlers.search.Search.log_fn")
|
||||
search_mock = mocker.patch("ahriman.application.handlers.search.aur_search", return_value=[aur_package_ahriman])
|
||||
print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print")
|
||||
|
||||
Search.run(args, "x86_64", configuration, True)
|
||||
log_mock.assert_called_once()
|
||||
search_mock.assert_called_once_with("ahriman")
|
||||
print_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_run_multiple_search(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run command with multiple search arguments
|
||||
"""
|
||||
args = _default_args(args)
|
||||
args.search = ["ahriman", "is", "cool"]
|
||||
search_mock = mocker.patch("aur.search")
|
||||
|
||||
Search.run(args, "x86_64", configuration, True)
|
||||
search_mock.assert_called_once_with(" ".join(args.search))
|
||||
|
||||
|
||||
def test_log_fn(args: argparse.Namespace, configuration: Configuration, aur_package_ahriman: aur.Package,
|
||||
def test_run_sort(args: argparse.Namespace, configuration: Configuration, aur_package_ahriman: aur.Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
log function must call print built-in
|
||||
must run command with sorting
|
||||
"""
|
||||
args = _default_args(args)
|
||||
mocker.patch("aur.search", return_value=[aur_package_ahriman])
|
||||
print_mock = mocker.patch("builtins.print")
|
||||
mocker.patch("ahriman.application.handlers.search.aur_search", return_value=[aur_package_ahriman])
|
||||
sort_mock = mocker.patch("ahriman.application.handlers.search.Search.sort")
|
||||
|
||||
Search.run(args, "x86_64", configuration, True)
|
||||
print_mock.assert_called() # we don't really care about call details tbh
|
||||
sort_mock.assert_called_once_with([aur_package_ahriman], "name")
|
||||
|
||||
|
||||
def test_run_sort_by(args: argparse.Namespace, configuration: Configuration, aur_package_ahriman: aur.Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run command with sorting by specified field
|
||||
"""
|
||||
args = _default_args(args)
|
||||
args.sort_by = "field"
|
||||
mocker.patch("ahriman.application.handlers.search.aur_search", return_value=[aur_package_ahriman])
|
||||
sort_mock = mocker.patch("ahriman.application.handlers.search.Search.sort")
|
||||
|
||||
Search.run(args, "x86_64", configuration, True)
|
||||
sort_mock.assert_called_once_with([aur_package_ahriman], "field")
|
||||
|
||||
|
||||
def test_sort(aur_package_ahriman: aur.Package) -> None:
|
||||
"""
|
||||
must sort package list
|
||||
"""
|
||||
another = aur_package_ahriman._replace(name="1", package_base="base")
|
||||
# sort by name
|
||||
assert Search.sort([aur_package_ahriman, another], "name") == [another, aur_package_ahriman]
|
||||
# sort by another field
|
||||
assert Search.sort([aur_package_ahriman, another], "package_base") == [aur_package_ahriman, another]
|
||||
# sort by field with the same values
|
||||
assert Search.sort([aur_package_ahriman, another], "version") == [another, aur_package_ahriman]
|
||||
|
||||
|
||||
def test_sort_exception(aur_package_ahriman: aur.Package) -> None:
|
||||
"""
|
||||
must raise an exception on unknown sorting field
|
||||
"""
|
||||
with pytest.raises(InvalidOption):
|
||||
Search.sort([aur_package_ahriman], "random_field")
|
||||
|
||||
|
||||
def test_disallow_auto_architecture_run() -> None:
|
||||
@ -60,3 +88,10 @@ def test_disallow_auto_architecture_run() -> None:
|
||||
must not allow multi architecture run
|
||||
"""
|
||||
assert not Search.ALLOW_AUTO_ARCHITECTURE_RUN
|
||||
|
||||
|
||||
def test_sort_fields() -> None:
|
||||
"""
|
||||
must store valid field list which are allowed to be used for sorting
|
||||
"""
|
||||
assert all(field in aur.Package._fields for field in Search.SORT_FIELDS)
|
||||
|
@ -1,6 +1,7 @@
|
||||
import argparse
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest import mock
|
||||
|
||||
from ahriman.application.handlers import Status
|
||||
from ahriman.core.configuration import Configuration
|
||||
@ -15,6 +16,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
:return: generated arguments for these test cases
|
||||
"""
|
||||
args.ahriman = True
|
||||
args.info = False
|
||||
args.package = []
|
||||
args.status = None
|
||||
return args
|
||||
@ -31,12 +33,28 @@ 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))])
|
||||
pretty_print_mock = mocker.patch("ahriman.models.package.Package.pretty_print")
|
||||
print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print")
|
||||
|
||||
Status.run(args, "x86_64", configuration, True)
|
||||
application_mock.assert_called_once()
|
||||
packages_mock.assert_called_once()
|
||||
pretty_print_mock.assert_called()
|
||||
print_mock.assert_has_calls([mock.call(False) for _ in range(3)])
|
||||
|
||||
|
||||
def test_run_verbose(args: argparse.Namespace, configuration: Configuration, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run command
|
||||
"""
|
||||
args = _default_args(args)
|
||||
args.info = True
|
||||
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")
|
||||
|
||||
Status.run(args, "x86_64", configuration, True)
|
||||
print_mock.assert_has_calls([mock.call(True) for _ in range(2)])
|
||||
|
||||
|
||||
def test_run_with_package_filter(args: argparse.Namespace, configuration: Configuration, package_ahriman: Package,
|
||||
@ -65,10 +83,10 @@ 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")
|
||||
pretty_print_mock = mocker.patch("ahriman.models.package.Package.pretty_print")
|
||||
print_mock = mocker.patch("ahriman.application.formatters.printer.Printer.print")
|
||||
|
||||
Status.run(args, "x86_64", configuration, True)
|
||||
pretty_print_mock.assert_called_once()
|
||||
print_mock.assert_has_calls([mock.call(False) for _ in range(2)])
|
||||
|
||||
|
||||
def test_imply_with_report(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
|
@ -28,7 +28,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc
|
||||
args = _default_args(args)
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
||||
application_mock = mocker.patch("ahriman.application.application.Application.update")
|
||||
updates_mock = mocker.patch("ahriman.application.application.Application.get_updates")
|
||||
updates_mock = mocker.patch("ahriman.application.application.Application.updates")
|
||||
|
||||
Update.run(args, "x86_64", configuration, True)
|
||||
application_mock.assert_called_once()
|
||||
@ -42,7 +42,7 @@ def test_run_dry_run(args: argparse.Namespace, configuration: Configuration, moc
|
||||
args = _default_args(args)
|
||||
args.dry_run = True
|
||||
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create")
|
||||
updates_mock = mocker.patch("ahriman.application.application.Application.get_updates")
|
||||
updates_mock = mocker.patch("ahriman.application.application.Application.updates")
|
||||
|
||||
Update.run(args, "x86_64", configuration, True)
|
||||
updates_mock.assert_called_once()
|
||||
|
@ -1,370 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest import mock
|
||||
|
||||
from ahriman.application.application import Application
|
||||
from ahriman.core.tree import Leaf, Tree
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_source import PackageSource
|
||||
|
||||
|
||||
def test_finalize(application: Application, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must report and sync at the last
|
||||
"""
|
||||
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()
|
||||
sync_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_known_packages(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return not empty list of known packages
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman])
|
||||
packages = application._known_packages()
|
||||
assert len(packages) > 1
|
||||
assert package_ahriman.base in packages
|
||||
|
||||
|
||||
def test_get_updates_all(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must get updates for all
|
||||
"""
|
||||
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur",
|
||||
return_value=[package_ahriman])
|
||||
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
|
||||
|
||||
application.get_updates([], no_aur=False, no_manual=False, no_vcs=False, log_fn=print)
|
||||
updates_aur_mock.assert_called_once_with([], False)
|
||||
updates_manual_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_get_updates_disabled(application: Application, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must get updates without anything
|
||||
"""
|
||||
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
|
||||
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
|
||||
|
||||
application.get_updates([], no_aur=True, no_manual=True, no_vcs=False, log_fn=print)
|
||||
updates_aur_mock.assert_not_called()
|
||||
updates_manual_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_get_updates_no_aur(application: Application, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must get updates without aur
|
||||
"""
|
||||
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
|
||||
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
|
||||
|
||||
application.get_updates([], no_aur=True, no_manual=False, no_vcs=False, log_fn=print)
|
||||
updates_aur_mock.assert_not_called()
|
||||
updates_manual_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_get_updates_no_manual(application: Application, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must get updates without manual
|
||||
"""
|
||||
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
|
||||
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
|
||||
|
||||
application.get_updates([], no_aur=False, no_manual=True, no_vcs=False, log_fn=print)
|
||||
updates_aur_mock.assert_called_once_with([], False)
|
||||
updates_manual_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_get_updates_no_vcs(application: Application, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must get updates without VCS
|
||||
"""
|
||||
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
|
||||
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
|
||||
|
||||
application.get_updates([], no_aur=False, no_manual=False, no_vcs=True, log_fn=print)
|
||||
updates_aur_mock.assert_called_once_with([], True)
|
||||
updates_manual_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_get_updates_with_filter(application: Application, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must get updates without VCS
|
||||
"""
|
||||
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
|
||||
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
|
||||
|
||||
application.get_updates(["filter"], no_aur=False, no_manual=False, no_vcs=False, log_fn=print)
|
||||
updates_aur_mock.assert_called_once_with(["filter"], False)
|
||||
updates_manual_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_add_archive(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must add package from archive
|
||||
"""
|
||||
mocker.patch("ahriman.application.application.Application._known_packages", return_value=set())
|
||||
copy_mock = mocker.patch("shutil.copy")
|
||||
|
||||
application.add([package_ahriman.base], PackageSource.Archive, False)
|
||||
copy_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_add_remote(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must add package from AUR
|
||||
"""
|
||||
mocker.patch("ahriman.application.application.Application._known_packages", return_value=set())
|
||||
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
|
||||
load_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.load")
|
||||
|
||||
application.add([package_ahriman.base], PackageSource.AUR, True)
|
||||
load_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_add_remote_with_dependencies(application: Application, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must add package from AUR with dependencies
|
||||
"""
|
||||
mocker.patch("ahriman.application.application.Application._known_packages", return_value=set())
|
||||
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
|
||||
mocker.patch("ahriman.core.build_tools.sources.Sources.load")
|
||||
dependencies_mock = mocker.patch("ahriman.models.package.Package.dependencies")
|
||||
|
||||
application.add([package_ahriman.base], PackageSource.AUR, False)
|
||||
dependencies_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_add_directory(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must add packages from directory
|
||||
"""
|
||||
mocker.patch("ahriman.application.application.Application._known_packages", return_value=set())
|
||||
iterdir_mock = mocker.patch("pathlib.Path.iterdir",
|
||||
return_value=[package.filepath for package in package_ahriman.packages.values()])
|
||||
copy_mock = mocker.patch("shutil.copy")
|
||||
|
||||
application.add([package_ahriman.base], PackageSource.Directory, False)
|
||||
iterdir_mock.assert_called_once()
|
||||
copy_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_add_local(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must add package from local sources
|
||||
"""
|
||||
mocker.patch("ahriman.application.application.Application._known_packages", return_value=set())
|
||||
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
|
||||
init_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.init")
|
||||
copytree_mock = mocker.patch("shutil.copytree")
|
||||
|
||||
application.add([package_ahriman.base], PackageSource.Local, True)
|
||||
init_mock.assert_called_once()
|
||||
copytree_mock.assert_has_calls([
|
||||
mock.call(Path(package_ahriman.base), application.repository.paths.cache_for(package_ahriman.base)),
|
||||
mock.call(application.repository.paths.cache_for(package_ahriman.base),
|
||||
application.repository.paths.manual_for(package_ahriman.base)),
|
||||
])
|
||||
|
||||
|
||||
def test_add_local_with_dependencies(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must add package from local sources with dependencies
|
||||
"""
|
||||
mocker.patch("ahriman.application.application.Application._known_packages", return_value=set())
|
||||
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
|
||||
mocker.patch("ahriman.core.build_tools.sources.Sources.init")
|
||||
mocker.patch("shutil.copytree")
|
||||
dependencies_mock = mocker.patch("ahriman.models.package.Package.dependencies")
|
||||
|
||||
application.add([package_ahriman.base], PackageSource.Local, False)
|
||||
dependencies_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_clean_build(application: Application, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must clean build directory
|
||||
"""
|
||||
clear_mock = mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_build")
|
||||
application.clean(False, True, True, True, True)
|
||||
clear_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_clean_cache(application: Application, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must clean cache directory
|
||||
"""
|
||||
clear_mock = mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_cache")
|
||||
application.clean(True, False, True, True, True)
|
||||
clear_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_clean_chroot(application: Application, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must clean chroot directory
|
||||
"""
|
||||
clear_mock = mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_chroot")
|
||||
application.clean(True, True, False, True, True)
|
||||
clear_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_clean_manual(application: Application, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must clean manual directory
|
||||
"""
|
||||
clear_mock = mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_manual")
|
||||
application.clean(True, True, True, False, True)
|
||||
clear_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_clean_packages(application: Application, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must clean packages directory
|
||||
"""
|
||||
clear_mock = mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_packages")
|
||||
application.clean(True, True, True, True, False)
|
||||
clear_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_remove(application: Application, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must remove package
|
||||
"""
|
||||
executor_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_remove")
|
||||
finalize_mock = mocker.patch("ahriman.application.application.Application._finalize")
|
||||
|
||||
application.remove([])
|
||||
executor_mock.assert_called_once()
|
||||
finalize_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_report(application: Application, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must generate report
|
||||
"""
|
||||
executor_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_report")
|
||||
application.report([], [])
|
||||
executor_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_sign(application: Application, package_ahriman: Package, package_python_schedule: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must sign world
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages",
|
||||
return_value=[package_ahriman, package_python_schedule])
|
||||
copy_mock = mocker.patch("shutil.copy")
|
||||
update_mock = mocker.patch("ahriman.application.application.Application.update")
|
||||
sign_repository_mock = mocker.patch("ahriman.core.sign.gpg.GPG.process_sign_repository")
|
||||
finalize_mock = mocker.patch("ahriman.application.application.Application._finalize")
|
||||
|
||||
application.sign([])
|
||||
copy_mock.assert_has_calls([
|
||||
mock.call(pytest.helpers.anyvar(str), pytest.helpers.anyvar(str)),
|
||||
mock.call(pytest.helpers.anyvar(str), pytest.helpers.anyvar(str))
|
||||
])
|
||||
update_mock.assert_called_once_with([])
|
||||
sign_repository_mock.assert_called_once()
|
||||
finalize_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_sign_skip(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip sign packages with empty filename
|
||||
"""
|
||||
package_ahriman.packages[package_ahriman.base].filename = None
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.application.application.Application.update")
|
||||
|
||||
application.sign([])
|
||||
|
||||
|
||||
def test_sign_specific(application: Application, package_ahriman: Package, package_python_schedule: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must sign only specified packages
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages",
|
||||
return_value=[package_ahriman, package_python_schedule])
|
||||
copy_mock = mocker.patch("shutil.copy")
|
||||
update_mock = mocker.patch("ahriman.application.application.Application.update")
|
||||
sign_repository_mock = mocker.patch("ahriman.core.sign.gpg.GPG.process_sign_repository")
|
||||
finalize_mock = mocker.patch("ahriman.application.application.Application._finalize")
|
||||
|
||||
application.sign([package_ahriman.base])
|
||||
copy_mock.assert_called_once()
|
||||
update_mock.assert_called_once_with([])
|
||||
sign_repository_mock.assert_called_once()
|
||||
finalize_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_sync(application: Application, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must sync to remote
|
||||
"""
|
||||
executor_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_sync")
|
||||
application.sync([], [])
|
||||
executor_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_unknown_no_aur(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return empty list in case if there is locally stored PKGBUILD
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.models.package.Package.from_aur", side_effect=Exception())
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
mocker.patch("ahriman.core.build_tools.sources.Sources.has_remotes", return_value=False)
|
||||
|
||||
assert not application.unknown()
|
||||
|
||||
|
||||
def test_unknown_no_aur_no_local(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return list of packages missing in aur and in local storage
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.models.package.Package.from_aur", side_effect=Exception())
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=False)
|
||||
|
||||
packages = application.unknown()
|
||||
assert packages == [package_ahriman]
|
||||
|
||||
|
||||
def test_unknown_no_local(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return empty list in case if there is package in AUR
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.models.package.Package.from_aur")
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=False)
|
||||
|
||||
assert not application.unknown()
|
||||
|
||||
|
||||
def test_update(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must process package updates
|
||||
"""
|
||||
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)
|
||||
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")
|
||||
finalize_mock = mocker.patch("ahriman.application.application.Application._finalize")
|
||||
|
||||
application.update([package_ahriman])
|
||||
build_mock.assert_called_once()
|
||||
update_mock.assert_called_once_with(paths)
|
||||
finalize_mock.assert_called_once_with([package_ahriman])
|
@ -3,10 +3,10 @@ import pytest
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.repository import Repository
|
||||
from ahriman.core.repository.cleaner import Cleaner
|
||||
from ahriman.core.repository.executor import Executor
|
||||
from ahriman.core.repository.properties import Properties
|
||||
from ahriman.core.repository.repository import Repository
|
||||
from ahriman.core.repository.update_handler import UpdateHandler
|
||||
|
||||
|
||||
|
@ -82,3 +82,12 @@ def test_clear_packages(cleaner: Cleaner, mocker: MockerFixture) -> None:
|
||||
|
||||
cleaner.clear_packages()
|
||||
Path.unlink.assert_has_calls([mock.call(), mock.call(), mock.call()])
|
||||
|
||||
|
||||
def test_clear_patches(cleaner: Cleaner, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must clear directory with patches
|
||||
"""
|
||||
_mock_clear(mocker)
|
||||
cleaner.clear_patches()
|
||||
_mock_clear_check()
|
||||
|
@ -1,7 +1,7 @@
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.core.repository.repository import Repository
|
||||
from ahriman.core.repository import Repository
|
||||
from ahriman.models.package import Package
|
||||
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import configparser
|
||||
import logging
|
||||
import pytest
|
||||
|
||||
from pathlib import Path
|
||||
@ -194,7 +195,7 @@ def test_load_logging_quiet(configuration: Configuration, mocker: MockerFixture)
|
||||
"""
|
||||
disable_mock = mocker.patch("logging.disable")
|
||||
configuration.load_logging(quiet=True)
|
||||
disable_mock.assert_called_once()
|
||||
disable_mock.assert_called_once_with(logging.WARNING)
|
||||
|
||||
|
||||
def test_merge_sections_missing(configuration: Configuration) -> None:
|
||||
|
@ -1,15 +1,50 @@
|
||||
import aur
|
||||
import datetime
|
||||
import logging
|
||||
import pytest
|
||||
import subprocess
|
||||
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest import mock
|
||||
|
||||
from ahriman.core.exceptions import InvalidOption, UnsafeRun
|
||||
from ahriman.core.util import check_output, check_user, package_like, pretty_datetime, pretty_size, walk
|
||||
from ahriman.core.util import aur_search, check_output, check_user, filter_json, package_like, pretty_datetime, \
|
||||
pretty_size, walk
|
||||
from ahriman.models.package import Package
|
||||
|
||||
|
||||
def test_aur_search(aur_package_ahriman: aur.Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must search in AUR with multiple words
|
||||
"""
|
||||
terms = ["ahriman", "is", "cool"]
|
||||
search_mock = mocker.patch("aur.search", return_value=[aur_package_ahriman])
|
||||
|
||||
assert aur_search(*terms) == [aur_package_ahriman]
|
||||
search_mock.assert_has_calls([mock.call("ahriman"), mock.call("cool")])
|
||||
|
||||
|
||||
def test_aur_search_empty(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return empty list if no long terms supplied
|
||||
"""
|
||||
terms = ["it", "is"]
|
||||
search_mock = mocker.patch("aur.search")
|
||||
|
||||
assert aur_search(*terms) == []
|
||||
search_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_aur_search_single(aur_package_ahriman: aur.Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must search in AUR with one word
|
||||
"""
|
||||
search_mock = mocker.patch("aur.search", return_value=[aur_package_ahriman])
|
||||
assert aur_search("ahriman") == [aur_package_ahriman]
|
||||
search_mock.assert_called_once_with("ahriman")
|
||||
|
||||
|
||||
def test_check_output(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run command and log result
|
||||
@ -79,6 +114,26 @@ def test_check_user_exception(mocker: MockerFixture) -> None:
|
||||
check_user(cwd)
|
||||
|
||||
|
||||
def test_filter_json(package_ahriman: Package) -> None:
|
||||
"""
|
||||
must filter fields by known list
|
||||
"""
|
||||
expected = package_ahriman.view()
|
||||
probe = package_ahriman.view()
|
||||
probe["unknown_field"] = "value"
|
||||
|
||||
assert expected == filter_json(probe, expected.keys())
|
||||
|
||||
|
||||
def test_filter_json_empty_value(package_ahriman: Package) -> None:
|
||||
"""
|
||||
must return empty values from object
|
||||
"""
|
||||
probe = package_ahriman.view()
|
||||
probe["base"] = None
|
||||
assert "base" not in filter_json(probe, probe.keys())
|
||||
|
||||
|
||||
def test_package_like(package_ahriman: Package) -> None:
|
||||
"""
|
||||
package_like must return true for archives
|
||||
@ -102,6 +157,13 @@ def test_pretty_datetime() -> None:
|
||||
assert pretty_datetime(0) == "1970-01-01 00:00:00"
|
||||
|
||||
|
||||
def test_pretty_datetime_datetime() -> None:
|
||||
"""
|
||||
must generate string from datetime object
|
||||
"""
|
||||
assert pretty_datetime(datetime.datetime(1970, 1, 1, 0, 0, 0)) == "1970-01-01 00:00:00"
|
||||
|
||||
|
||||
def test_pretty_datetime_empty() -> None:
|
||||
"""
|
||||
must generate empty string from None timestamp
|
||||
|
@ -2,6 +2,7 @@ from pytest_mock import MockerFixture
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
from ahriman.models.package_description import PackageDescription
|
||||
from ahriman.models.package_source import PackageSource
|
||||
|
||||
|
||||
@ -24,13 +25,13 @@ def test_resolve_non_auto() -> None:
|
||||
assert source.resolve("") == source
|
||||
|
||||
|
||||
def test_resolve_archive(mocker: MockerFixture) -> None:
|
||||
def test_resolve_archive(package_description_ahriman: PackageDescription, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must resolve auto type into the archive
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=False)
|
||||
mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=_is_file_mock(True, False))
|
||||
assert PackageSource.Auto.resolve("linux-5.14.2.arch1-2-x86_64.pkg.tar.zst") == PackageSource.Archive
|
||||
assert PackageSource.Auto.resolve(package_description_ahriman.filename) == PackageSource.Archive
|
||||
|
||||
|
||||
def test_resolve_aur(mocker: MockerFixture) -> None:
|
||||
@ -56,14 +57,23 @@ def test_resolve_directory(mocker: MockerFixture) -> None:
|
||||
must resolve auto type into the directory
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
mocker.patch("pathlib.Path.is_file", return_value=False)
|
||||
mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=_is_file_mock(False, False))
|
||||
assert PackageSource.Auto.resolve("path") == PackageSource.Directory
|
||||
|
||||
|
||||
def test_resolve_local(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must resolve auto type into the directory
|
||||
must resolve auto type into the local sources
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=False)
|
||||
mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=_is_file_mock(True, True))
|
||||
assert PackageSource.Auto.resolve("path") == PackageSource.Local
|
||||
|
||||
|
||||
def test_resolve_remote(package_description_ahriman: PackageDescription, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must resolve auto type into the remote sources
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=False)
|
||||
mocker.patch("pathlib.Path.is_file", autospec=True, side_effect=_is_file_mock(False, False))
|
||||
assert PackageSource.Auto.resolve(f"https://host/{package_description_ahriman.filename}") == PackageSource.Remote
|
||||
|
0
tests/ahriman/models/test_property.py
Normal file
0
tests/ahriman/models/test_property.py
Normal file
@ -1,13 +1,24 @@
|
||||
import json
|
||||
import logging
|
||||
import pytest
|
||||
|
||||
from aiohttp.web_exceptions import HTTPBadRequest, HTTPNoContent
|
||||
from aiohttp.web_exceptions import HTTPBadRequest, HTTPInternalServerError, HTTPNoContent
|
||||
from pytest_mock import MockerFixture
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from ahriman.web.middlewares.exception_handler import exception_handler
|
||||
|
||||
|
||||
def _extract_body(response: Any) -> Any:
|
||||
"""
|
||||
extract json body from given object
|
||||
:param response: response (any actually) object
|
||||
:return: body key from the object converted to json
|
||||
"""
|
||||
return json.loads(getattr(response, "body"))
|
||||
|
||||
|
||||
async def test_exception_handler(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must pass success response
|
||||
@ -23,7 +34,7 @@ async def test_exception_handler(mocker: MockerFixture) -> None:
|
||||
|
||||
async def test_exception_handler_success(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must pass client exception
|
||||
must pass 2xx and 3xx codes
|
||||
"""
|
||||
request = pytest.helpers.request("", "", "")
|
||||
request_handler = AsyncMock(side_effect=HTTPNoContent())
|
||||
@ -37,27 +48,41 @@ async def test_exception_handler_success(mocker: MockerFixture) -> None:
|
||||
|
||||
async def test_exception_handler_client_error(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must pass client exception
|
||||
must handle client exception
|
||||
"""
|
||||
request = pytest.helpers.request("", "", "")
|
||||
request_handler = AsyncMock(side_effect=HTTPBadRequest())
|
||||
logging_mock = mocker.patch("logging.Logger.exception")
|
||||
|
||||
handler = exception_handler(logging.getLogger())
|
||||
with pytest.raises(HTTPBadRequest):
|
||||
await handler(request, request_handler)
|
||||
response = await handler(request, request_handler)
|
||||
assert _extract_body(response) == {"error": HTTPBadRequest().reason}
|
||||
logging_mock.assert_not_called()
|
||||
|
||||
|
||||
async def test_exception_handler_server_error(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must log server exception and re-raise it
|
||||
must handle server exception
|
||||
"""
|
||||
request = pytest.helpers.request("", "", "")
|
||||
request_handler = AsyncMock(side_effect=Exception())
|
||||
request_handler = AsyncMock(side_effect=HTTPInternalServerError())
|
||||
logging_mock = mocker.patch("logging.Logger.exception")
|
||||
|
||||
handler = exception_handler(logging.getLogger())
|
||||
with pytest.raises(Exception):
|
||||
await handler(request, request_handler)
|
||||
response = await handler(request, request_handler)
|
||||
assert _extract_body(response) == {"error": HTTPInternalServerError().reason}
|
||||
logging_mock.assert_called_once()
|
||||
|
||||
|
||||
async def test_exception_handler_unknown_error(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must log server exception and re-raise it
|
||||
"""
|
||||
request = pytest.helpers.request("", "", "")
|
||||
request_handler = AsyncMock(side_effect=Exception("An error"))
|
||||
logging_mock = mocker.patch("logging.Logger.exception")
|
||||
|
||||
handler = exception_handler(logging.getLogger())
|
||||
response = await handler(request, request_handler)
|
||||
assert _extract_body(response) == {"error": "An error"}
|
||||
logging_mock.assert_called_once()
|
||||
|
@ -21,7 +21,7 @@ async def test_get(client: TestClient, aur_package_ahriman: aur.Package, mocker:
|
||||
"""
|
||||
must call get request correctly
|
||||
"""
|
||||
mocker.patch("aur.search", return_value=[aur_package_ahriman])
|
||||
mocker.patch("ahriman.web.views.service.search.aur_search", return_value=[aur_package_ahriman])
|
||||
response = await client.get("/service-api/v1/search", params={"for": "ahriman"})
|
||||
|
||||
assert response.ok
|
||||
@ -33,41 +33,19 @@ async def test_get_exception(client: TestClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must raise 400 on empty search string
|
||||
"""
|
||||
search_mock = mocker.patch("aur.search")
|
||||
search_mock = mocker.patch("ahriman.web.views.service.search.aur_search", return_value=[])
|
||||
response = await client.get("/service-api/v1/search")
|
||||
|
||||
assert response.status == 400
|
||||
search_mock.assert_not_called()
|
||||
assert response.status == 404
|
||||
search_mock.assert_called_once_with()
|
||||
|
||||
|
||||
async def test_get_join(client: TestClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must join search args with space
|
||||
"""
|
||||
search_mock = mocker.patch("aur.search")
|
||||
search_mock = mocker.patch("ahriman.web.views.service.search.aur_search")
|
||||
response = await client.get("/service-api/v1/search", params=[("for", "ahriman"), ("for", "maybe")])
|
||||
|
||||
assert response.ok
|
||||
search_mock.assert_called_once_with("ahriman maybe")
|
||||
|
||||
|
||||
async def test_get_join_filter(client: TestClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must filter search parameters with less than 3 symbols
|
||||
"""
|
||||
search_mock = mocker.patch("aur.search")
|
||||
response = await client.get("/service-api/v1/search", params=[("for", "ah"), ("for", "maybe")])
|
||||
|
||||
assert response.ok
|
||||
search_mock.assert_called_once_with("maybe")
|
||||
|
||||
|
||||
async def test_get_join_filter_empty(client: TestClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must filter search parameters with less than 3 symbols (empty result)
|
||||
"""
|
||||
search_mock = mocker.patch("aur.search")
|
||||
response = await client.get("/service-api/v1/search", params=[("for", "ah"), ("for", "ma")])
|
||||
|
||||
assert response.status == 400
|
||||
search_mock.assert_not_called()
|
||||
search_mock.assert_called_once_with("ahriman", "maybe")
|
||||
|
Reference in New Issue
Block a user