update dependencies before build (#91)

Old implementation has used add step in order to fetch dependencies,
which could lead to build errors in case if dependency list was updated.

New solution uses dependencies which are declared at current version and
fetch them (if required and if enabled) before update process.

Closes #90
This commit is contained in:
Evgenii Alekseev 2023-02-12 05:02:30 +02:00 committed by GitHub
parent 19bb19e9f5
commit 37e57c13c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 159 additions and 125 deletions

View File

@ -245,6 +245,8 @@ def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
"5) and finally you can add package from AUR.",
formatter_class=_formatter)
parser.add_argument("package", help="package source (base name, path to local files, remote URL)", nargs="+")
parser.add_argument("--dependencies", help="process missing package dependencies",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.add_argument("-n", "--now", help="run update function after", action="store_true")
parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
@ -252,7 +254,6 @@ def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
action="count", default=False)
parser.add_argument("-s", "--source", help="explicitly specify the package source for this command",
type=PackageSource, choices=enum_values(PackageSource), default=PackageSource.Auto)
parser.add_argument("--without-dependencies", help="do not add dependencies", action="store_true")
parser.set_defaults(handler=handlers.Add)
return parser
@ -472,7 +473,7 @@ def _set_repo_check_parser(root: SubParserAction) -> argparse.ArgumentParser:
parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
"-yy to force refresh even if up to date",
action="count", default=False)
parser.set_defaults(handler=handlers.Update, dry_run=True, aur=True, local=True, manual=False)
parser.set_defaults(handler=handlers.Update, dependencies=False, dry_run=True, aur=True, local=True, manual=False)
return parser
@ -492,6 +493,8 @@ def _set_repo_daemon_parser(root: SubParserAction) -> argparse.ArgumentParser:
parser.add_argument("-i", "--interval", help="interval between runs in seconds", type=int, default=60 * 60 * 12)
parser.add_argument("--aur", help="enable or disable checking for AUR updates",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--dependencies", help="process missing package dependencies",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--local", help="enable or disable checking of local packages for updates",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--manual", help="include or exclude manual updates",
@ -691,10 +694,12 @@ def _set_repo_update_parser(root: SubParserAction) -> argparse.ArgumentParser:
description="check for packages updates and run build process if requested",
formatter_class=_formatter)
parser.add_argument("package", help="filter check by package base", nargs="*")
parser.add_argument("--dry-run", help="just perform check for updates, same as check command", action="store_true")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.add_argument("--aur", help="enable or disable checking for AUR updates",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--dependencies", help="process missing package dependencies",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--dry-run", help="just perform check for updates, same as check command", action="store_true")
parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true")
parser.add_argument("--local", help="enable or disable checking of local packages for updates",
action=argparse.BooleanOptionalAction, default=True)
parser.add_argument("--manual", help="include or exclude manual updates",

View File

@ -17,10 +17,11 @@
# 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 Set
from typing import Iterable, List, Set
from ahriman.application.application.application_packages import ApplicationPackages
from ahriman.application.application.application_repository import ApplicationRepository
from ahriman.models.package import Package
from ahriman.models.result import Result
@ -87,3 +88,39 @@ class Application(ApplicationPackages, ApplicationRepository):
directly as it will be called after on_start action
"""
self.repository.triggers.on_stop()
def with_dependencies(self, packages: List[Package], *, process_dependencies: bool) -> List[Package]:
"""
add missing dependencies to list of packages
Args:
packages(List[Package]): list of source packages of which dependencies have to be processed
process_dependencies(bool): if no set, dependencies will not be processed
"""
def missing_dependencies(source: Iterable[Package]) -> Set[str]:
# build initial list of dependencies
result = set()
for package in source:
result.update(package.depends_build)
# remove ones which are already well-known
result = result.difference(known_packages)
# remove ones which are in this list already
for package in source:
result = result.difference(package.packages_full)
return result
if not process_dependencies or not packages:
return packages
known_packages = self._known_packages()
with_dependencies = {package.base: package for package in packages}
while missing := missing_dependencies(with_dependencies.values()):
for package_name in missing:
package = Package.from_aur(package_name, self.repository.pacman)
with_dependencies[package.base] = package
return list(with_dependencies.values())

View File

@ -21,7 +21,7 @@ import requests
import shutil
from pathlib import Path
from typing import Any, Iterable, Set
from typing import Any, Iterable
from ahriman.application.application.application_properties import ApplicationProperties
from ahriman.core.build_tools.sources import Sources
@ -47,22 +47,18 @@ class ApplicationPackages(ApplicationProperties):
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:
def _add_aur(self, source: str) -> None:
"""
add package from AUR
Args:
source(str): package base name
known_packages(Set[str]): list of packages which are known by the service
without_dependencies(bool): if set, dependency check will be disabled
"""
package = Package.from_aur(source, self.repository.pacman)
self.database.build_queue_insert(package)
self.database.remote_update(package)
self._process_dependencies(package, known_packages, without_dependencies)
def _add_directory(self, source: str, *_: Any) -> None:
"""
add packages from directory
@ -74,14 +70,12 @@ class ApplicationPackages(ApplicationProperties):
for full_path in filter(package_like, local_dir.iterdir()):
self._add_archive(str(full_path))
def _add_local(self, source: str, known_packages: Set[str], without_dependencies: bool) -> None:
def _add_local(self, source: str) -> None:
"""
add package from local PKGBUILDs
Args:
source(str): path to directory with local source files
known_packages(Set[str]): list of packages which are known by the service
without_dependencies(bool): if set, dependency check will be disabled
"""
source_dir = Path(source)
package = Package.from_build(source_dir, self.architecture)
@ -91,8 +85,6 @@ class ApplicationPackages(ApplicationProperties):
self.database.build_queue_insert(package)
self._process_dependencies(package, known_packages, without_dependencies)
def _add_remote(self, source: str, *_: Any) -> None:
"""
add package from remote sources (e.g. HTTP)
@ -118,50 +110,19 @@ class ApplicationPackages(ApplicationProperties):
package = Package.from_official(source, self.repository.pacman)
self.database.build_queue_insert(package)
self.database.remote_update(package)
# repository packages must not depend on unknown packages, thus we are not going to process dependencies
def _known_packages(self) -> Set[str]:
"""
load packages from repository and pacman repositories
Returns:
Set[str]: list of known packages
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
def _process_dependencies(self, package: Package, known_packages: Set[str], without_dependencies: bool) -> None:
"""
process package dependencies
Args:
package(Package): source package of which dependencies have to be processed
known_packages(Set[str]): list of packages which are known by the service
without_dependencies(bool): if set, dependency check will be disabled
"""
if without_dependencies:
return
dependencies = package.depends_build
self.add(dependencies.difference(known_packages), PackageSource.AUR, without_dependencies)
def add(self, names: Iterable[str], source: PackageSource, without_dependencies: bool) -> None:
def add(self, names: Iterable[str], source: PackageSource) -> None:
"""
add packages for the next build
Args:
names(Iterable[str]): list of package bases to add
source(PackageSource): package source to add
without_dependencies(bool): 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)
fn(name)
def on_result(self, result: Result) -> None:
"""

View File

@ -47,11 +47,12 @@ class Add(Handler):
application = Application(architecture, configuration,
report=report, unsafe=unsafe, refresh_pacman_database=args.refresh)
application.on_start()
application.add(args.package, args.source, args.without_dependencies)
application.add(args.package, args.source)
if not args.now:
return
packages = application.updates(args.package, aur=False, local=False, manual=True, vcs=False,
log_fn=application.logger.info)
packages = application.with_dependencies(packages, process_dependencies=args.dependencies)
result = application.update(packages)
Add.check_if_empty(args.exit_code, result.is_empty)

View File

@ -53,6 +53,7 @@ class Update(Handler):
if args.dry_run:
return
packages = application.with_dependencies(packages, process_dependencies=args.dependencies)
result = application.update(packages)
Update.check_if_empty(args.exit_code, result.is_empty)

View File

@ -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/>.
#
# pylint: disable=too-many-lines
# pylint: disable=too-many-lines,too-many-public-methods
from __future__ import annotations
import copy
@ -96,7 +96,7 @@ class Package(LazyLogging):
Returns:
Set[str]: full dependencies list used by devtools
"""
return (set(self.depends) | set(self.depends_make)) - self.packages.keys()
return (set(self.depends) | set(self.depends_make)).difference(self.packages_full)
@property
def depends_make(self) -> List[str]:
@ -163,6 +163,20 @@ class Package(LazyLogging):
"""
return sorted(set(sum((package.licenses for package in self.packages.values()), start=[])))
@property
def packages_full(self) -> List[str]:
"""
get full packages list including provides
Returns:
List[str]: full list of packages which this base contains
"""
packages = set()
for package, properties in self.packages.items():
packages.add(package)
packages.update(properties.provides)
return sorted(packages)
@classmethod
def from_archive(cls: Type[Package], path: Path, pacman: Pacman, remote: Optional[RemoteSource]) -> Package:
"""

View File

@ -1,4 +1,5 @@
from pytest_mock import MockerFixture
from unittest.mock import MagicMock, call as MockCall
from ahriman.application.application import Application
from ahriman.models.package import Package
@ -44,3 +45,55 @@ def test_on_stop(application: Application, mocker: MockerFixture) -> None:
application.on_stop()
triggers_mock.assert_called_once_with()
def test_with_dependencies(application: Application, package_ahriman: Package, package_python_schedule: Package,
mocker: MockerFixture) -> None:
"""
must append list of missing dependencies
"""
def create_package_mock(package_base) -> MagicMock:
mock = MagicMock()
mock.base = package_base
mock.depends_build = []
mock.packages_full = [package_base]
return mock
package_python_schedule.packages = {
package_python_schedule.base: package_python_schedule.packages[package_python_schedule.base]
}
package_ahriman.packages[package_ahriman.base].depends = ["devtools", "python", package_python_schedule.base]
package_ahriman.packages[package_ahriman.base].make_depends = ["python-build", "python-installer"]
packages = {
package_ahriman.base: package_ahriman,
package_python_schedule.base: package_python_schedule,
"python": create_package_mock("python"),
"python-installer": create_package_mock("python-installer"),
}
package_mock = mocker.patch("ahriman.models.package.Package.from_aur", side_effect=lambda p, _: packages[p])
packages_mock = mocker.patch("ahriman.application.application.Application._known_packages",
return_value=["devtools", "python-build"])
result = application.with_dependencies([package_ahriman], process_dependencies=True)
assert {package.base: package for package in result} == packages
package_mock.assert_has_calls([
MockCall(package_python_schedule.base, application.repository.pacman),
MockCall("python", application.repository.pacman),
MockCall("python-installer", application.repository.pacman),
], any_order=True)
packages_mock.assert_called_once_with()
def test_with_dependencies_skip(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must skip processing of dependencies
"""
packages_mock = mocker.patch("ahriman.application.application.Application._known_packages")
assert application.with_dependencies([package_ahriman], process_dependencies=False) == [package_ahriman]
packages_mock.assert_not_called()
assert application.with_dependencies([], process_dependencies=True) == []
packages_mock.assert_not_called()

View File

@ -29,13 +29,10 @@ def test_add_aur(application_packages: ApplicationPackages, package_ahriman: Pac
must add package from AUR
"""
mocker.patch("ahriman.models.package.Package.from_aur", return_value=package_ahriman)
dependencies_mock = mocker.patch(
"ahriman.application.application.application_packages.ApplicationPackages._process_dependencies")
build_queue_mock = mocker.patch("ahriman.core.database.SQLite.build_queue_insert")
update_remote_mock = mocker.patch("ahriman.core.database.SQLite.remote_update")
application_packages._add_aur(package_ahriman.base, set(), False)
dependencies_mock.assert_called_once_with(pytest.helpers.anyvar(int), set(), False)
application_packages._add_aur(package_ahriman.base)
build_queue_mock.assert_called_once_with(package_ahriman)
update_remote_mock.assert_called_once_with(package_ahriman)
@ -64,15 +61,12 @@ def test_add_local(application_packages: ApplicationPackages, package_ahriman: P
mocker.patch("ahriman.models.package.Package.from_build", 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.application_packages.ApplicationPackages._process_dependencies")
build_queue_mock = mocker.patch("ahriman.core.database.SQLite.build_queue_insert")
application_packages._add_local(package_ahriman.base, set(), False)
application_packages._add_local(package_ahriman.base)
copytree_mock.assert_called_once_with(
Path(package_ahriman.base), application_packages.repository.paths.cache_for(package_ahriman.base))
init_mock.assert_called_once_with(application_packages.repository.paths.cache_for(package_ahriman.base))
dependencies_mock.assert_called_once_with(pytest.helpers.anyvar(int), set(), False)
build_queue_mock.assert_called_once_with(package_ahriman)
@ -107,59 +101,15 @@ def test_add_repository(application_packages: ApplicationPackages, package_ahrim
update_remote_mock.assert_called_once_with(package_ahriman)
def test_known_packages(application_packages: ApplicationPackages) -> None:
"""
must raise NotImplemented for missing known_packages method
"""
with pytest.raises(NotImplementedError):
application_packages._known_packages()
def test_process_dependencies(application_packages: ApplicationPackages, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must process dependencies addition
"""
add_mock = mocker.patch("ahriman.application.application.application_packages.ApplicationPackages.add")
application_packages._process_dependencies(package_ahriman, set(), False)
add_mock.assert_called_once_with(package_ahriman.depends_build, PackageSource.AUR, False)
def test_process_dependencies_missing(application_packages: ApplicationPackages, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must process dependencies addition only for missing packages
"""
missing = {"devtools"}
add_mock = mocker.patch("ahriman.application.application.application_packages.ApplicationPackages.add")
application_packages._process_dependencies(
package_ahriman, package_ahriman.depends_build.difference(missing), False)
add_mock.assert_called_once_with(missing, PackageSource.AUR, False)
def test_process_dependencies_skip(application_packages: ApplicationPackages, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must skip dependencies processing
"""
add_mock = mocker.patch("ahriman.application.application.application_packages.ApplicationPackages.add")
application_packages._process_dependencies(package_ahriman, set(), True)
add_mock.assert_not_called()
def test_add_add_archive(application_packages: ApplicationPackages, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must add package from archive via add function
"""
mocker.patch("ahriman.application.application.application_packages.ApplicationPackages._known_packages",
return_value=set())
add_mock = mocker.patch("ahriman.application.application.application_packages.ApplicationPackages._add_archive")
application_packages.add([package_ahriman.base], PackageSource.Archive, False)
add_mock.assert_called_once_with(package_ahriman.base, set(), False)
application_packages.add([package_ahriman.base], PackageSource.Archive)
add_mock.assert_called_once_with(package_ahriman.base)
def test_add_add_aur(
@ -169,12 +119,10 @@ def test_add_add_aur(
"""
must add package from AUR via add function
"""
mocker.patch("ahriman.application.application.application_packages.ApplicationPackages._known_packages",
return_value=set())
add_mock = mocker.patch("ahriman.application.application.application_packages.ApplicationPackages._add_aur")
application_packages.add([package_ahriman.base], PackageSource.AUR, True)
add_mock.assert_called_once_with(package_ahriman.base, set(), True)
application_packages.add([package_ahriman.base], PackageSource.AUR)
add_mock.assert_called_once_with(package_ahriman.base)
def test_add_add_directory(application_packages: ApplicationPackages, package_ahriman: Package,
@ -182,12 +130,10 @@ def test_add_add_directory(application_packages: ApplicationPackages, package_ah
"""
must add packages from directory via add function
"""
mocker.patch("ahriman.application.application.application_packages.ApplicationPackages._known_packages",
return_value=set())
add_mock = mocker.patch("ahriman.application.application.application_packages.ApplicationPackages._add_directory")
application_packages.add([package_ahriman.base], PackageSource.Directory, False)
add_mock.assert_called_once_with(package_ahriman.base, set(), False)
application_packages.add([package_ahriman.base], PackageSource.Directory)
add_mock.assert_called_once_with(package_ahriman.base)
def test_add_add_local(application_packages: ApplicationPackages, package_ahriman: Package,
@ -195,12 +141,10 @@ def test_add_add_local(application_packages: ApplicationPackages, package_ahrima
"""
must add package from local sources via add function
"""
mocker.patch("ahriman.application.application.application_packages.ApplicationPackages._known_packages",
return_value=set())
add_mock = mocker.patch("ahriman.application.application.application_packages.ApplicationPackages._add_local")
application_packages.add([package_ahriman.base], PackageSource.Local, False)
add_mock.assert_called_once_with(package_ahriman.base, set(), False)
application_packages.add([package_ahriman.base], PackageSource.Local)
add_mock.assert_called_once_with(package_ahriman.base)
def test_add_add_remote(application_packages: ApplicationPackages, package_description_ahriman: PackageDescription,
@ -208,13 +152,11 @@ def test_add_add_remote(application_packages: ApplicationPackages, package_descr
"""
must add package from remote source via add function
"""
mocker.patch("ahriman.application.application.application_packages.ApplicationPackages._known_packages",
return_value=set())
add_mock = mocker.patch("ahriman.application.application.application_packages.ApplicationPackages._add_remote")
url = f"https://host/{package_description_ahriman.filename}"
application_packages.add([url], PackageSource.Remote, False)
add_mock.assert_called_once_with(url, set(), False)
application_packages.add([url], PackageSource.Remote)
add_mock.assert_called_once_with(url)
def test_on_result(application_packages: ApplicationPackages) -> None:

View File

@ -26,7 +26,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
args.now = False
args.refresh = 0
args.source = PackageSource.Auto
args.without_dependencies = False
args.dependencies = True
return args
@ -38,10 +38,12 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository:
args = _default_args(args)
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
application_mock = mocker.patch("ahriman.application.application.Application.add")
dependencies_mock = mocker.patch("ahriman.application.application.Application.with_dependencies")
on_start_mock = mocker.patch("ahriman.application.application.Application.on_start")
Add.run(args, "x86_64", configuration, report=False, unsafe=False)
application_mock.assert_called_once_with(args.package, args.source, args.without_dependencies)
application_mock.assert_called_once_with(args.package, args.source)
dependencies_mock.assert_not_called()
on_start_mock.assert_called_once_with()
@ -59,11 +61,14 @@ def test_run_with_updates(args: argparse.Namespace, configuration: Configuration
application_mock = mocker.patch("ahriman.application.application.Application.update", return_value=result)
check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty")
updates_mock = mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman])
dependencies_mock = mocker.patch("ahriman.application.application.Application.with_dependencies",
return_value=[package_ahriman])
Add.run(args, "x86_64", configuration, report=False, unsafe=False)
updates_mock.assert_called_once_with(args.package, aur=False, local=False, manual=True, vcs=False,
log_fn=pytest.helpers.anyvar(int))
application_mock.assert_called_once_with([package_ahriman])
dependencies_mock.assert_called_once_with([package_ahriman], process_dependencies=args.dependencies)
check_mock.assert_called_once_with(False, False)
@ -78,6 +83,7 @@ def test_run_empty_exception(args: argparse.Namespace, configuration: Configurat
mocker.patch("ahriman.application.application.Application.add")
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
mocker.patch("ahriman.application.application.Application.update", return_value=Result())
mocker.patch("ahriman.application.application.Application.with_dependencies")
mocker.patch("ahriman.application.application.Application.updates")
check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty")

View File

@ -23,6 +23,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
argparse.Namespace: generated arguments for these test cases
"""
args.package = []
args.dependencies = True
args.dry_run = False
args.exit_code = False
args.aur = True
@ -44,6 +45,8 @@ def test_run(args: argparse.Namespace, package_ahriman: Package, configuration:
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
application_mock = mocker.patch("ahriman.application.application.Application.update", return_value=result)
check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty")
dependencies_mock = mocker.patch("ahriman.application.application.Application.with_dependencies",
return_value=[package_ahriman])
updates_mock = mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman])
on_start_mock = mocker.patch("ahriman.application.application.Application.on_start")
@ -51,6 +54,7 @@ def test_run(args: argparse.Namespace, package_ahriman: Package, configuration:
application_mock.assert_called_once_with([package_ahriman])
updates_mock.assert_called_once_with(args.package, aur=args.aur, local=args.local, manual=args.manual, vcs=args.vcs,
log_fn=pytest.helpers.anyvar(int))
dependencies_mock.assert_called_once_with([package_ahriman], process_dependencies=args.dependencies)
check_mock.assert_has_calls([MockCall(False, False), MockCall(False, False)])
on_start_mock.assert_called_once_with()
@ -81,6 +85,7 @@ def test_run_update_empty_exception(args: argparse.Namespace, package_ahriman: P
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
mocker.patch("ahriman.application.application.Application.update", return_value=Result())
mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman])
mocker.patch("ahriman.application.application.Application.with_dependencies", return_value=[package_ahriman])
check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty")
Update.run(args, "x86_64", configuration, report=False, unsafe=False)

View File

@ -342,9 +342,10 @@ def test_subparsers_repo_backup_architecture(parser: argparse.ArgumentParser) ->
def test_subparsers_repo_check(parser: argparse.ArgumentParser) -> None:
"""
repo-check command must imply dry-run, aur and manual
repo-check command must imply dependencies, dry-run, aur and manual
"""
args = parser.parse_args(["repo-check"])
assert not args.dependencies
assert args.dry_run
assert args.aur
assert not args.manual

View File

@ -125,6 +125,14 @@ def test_licenses(package_ahriman: Package) -> None:
assert sorted(package_ahriman.licenses) == package_ahriman.licenses
def test_packages_full(package_ahriman: Package) -> None:
"""
must return full list of packages including provides
"""
package_ahriman.packages[package_ahriman.base].provides = [f"{package_ahriman.base}-git"]
assert package_ahriman.packages_full == [package_ahriman.base, f"{package_ahriman.base}-git"]
def test_from_archive(package_ahriman: Package, pyalpm_handle: MagicMock, mocker: MockerFixture) -> None:
"""
must construct package from alpm library