def test_full_version() -> None: """ must construct full version """ assert Package.full_version("1", "r2388.d30e3201", "1") == "1:r2388.d30e3201-1" assert Package.full_version(None, "0.12.1", "1") == "0.12.1-1"
async def test_get(client: TestClient, package_ahriman: Package, package_python_schedule: Package) -> None: """ must return status for all packages """ await client.post(f"/api/v1/packages/{package_ahriman.base}", json={ "status": BuildStatusEnum.Success.value, "package": package_ahriman.view() }) await client.post(f"/api/v1/packages/{package_python_schedule.base}", json={ "status": BuildStatusEnum.Success.value, "package": package_python_schedule.view() }) response = await client.get("/api/v1/packages") assert response.status == 200 packages = [ Package.from_json(item["package"]) for item in await response.json() ] assert packages assert {package.base for package in packages } == {package_ahriman.base, package_python_schedule.base}
def test_is_outdated_true(package_ahriman: Package, repository_paths: RepositoryPaths) -> None: """ must be outdated for the new version """ other = Package.from_json(package_ahriman.view()) other.version = other.version.replace("-1", "-2") assert package_ahriman.is_outdated(other, repository_paths)
def test_load_from_aur(package_ahriman: Package, pyalpm_handle: MagicMock, mocker: MockerFixture) -> None: """ must load package from AUR """ load_mock = mocker.patch("ahriman.models.package.Package.from_aur") Package.load(Path("path"), pyalpm_handle, package_ahriman.aur_url) load_mock.assert_called_once()
def test_load_from_build(package_ahriman: Package, pyalpm_handle: MagicMock, mocker: MockerFixture) -> None: """ must load package from build directory """ mocker.patch("pathlib.Path.is_dir", return_value=True) load_mock = mocker.patch("ahriman.models.package.Package.from_build") Package.load(Path("path"), pyalpm_handle, package_ahriman.aur_url) load_mock.assert_called_once()
def test_dependencies_failed(mocker: MockerFixture) -> None: """ must raise exception if there are errors during srcinfo load """ mocker.patch("pathlib.Path.read_text", return_value="") mocker.patch("ahriman.models.package.parse_srcinfo", return_value=({ "packages": {} }, ["an error"])) with pytest.raises(InvalidPackageInfo): Package.dependencies(Path("path"))
def test_from_build_failed(package_ahriman: Package, mocker: MockerFixture) -> None: """ must raise exception if there are errors during srcinfo load """ mocker.patch("pathlib.Path.read_text", return_value="") mocker.patch("ahriman.models.package.parse_srcinfo", return_value=({ "packages": {} }, ["an error"])) with pytest.raises(InvalidPackageInfo): Package.from_build(Path("path"), package_ahriman.aur_url)
def test_from_build(package_ahriman: Package, mocker: MockerFixture, resource_path_root: Path) -> None: """ must construct package from srcinfo """ srcinfo = (resource_path_root / "models" / "package_ahriman_srcinfo").read_text() mocker.patch("pathlib.Path.read_text", return_value=srcinfo) package = Package.from_build(Path("path"), package_ahriman.aur_url) assert package_ahriman.packages.keys() == package.packages.keys() package_ahriman.packages = package.packages # we are not going to test PackageDescription here assert package_ahriman == package
def test_load_failure(package_ahriman: Package, pyalpm_handle: MagicMock, mocker: MockerFixture) -> None: """ must raise InvalidPackageInfo on exception """ mocker.patch("pathlib.Path.is_dir", side_effect=InvalidPackageInfo("exception!")) with pytest.raises(InvalidPackageInfo): Package.load(Path("path"), pyalpm_handle, package_ahriman.aur_url) mocker.patch("pathlib.Path.is_dir", side_effect=Exception()) with pytest.raises(InvalidPackageInfo): Package.load(Path("path"), pyalpm_handle, package_ahriman.aur_url)
def package_ahriman( package_description_ahriman: PackageDescription) -> Package: packages = {"ahriman": package_description_ahriman} return Package(base="ahriman", version="0.12.1-1", aur_url="https://aur.archlinux.org", packages=packages)
async def post(self) -> Response: """ update package build status JSON body must be supplied, the following model is used: { "status": "unknown", # package build status string, must be valid `BuildStatusEnum` "package": {} # package body (use `dataclasses.asdict` to generate one), optional. # Must be supplied in case if package base is unknown } :return: 204 on success """ base = self.request.match_info["package"] data = await self.request.json() try: package = Package.from_json( data["package"]) if "package" in data else None status = BuildStatusEnum(data["status"]) except Exception as e: raise HTTPBadRequest(text=str(e)) try: self.service.update(base, status, package) except UnknownPackage: raise HTTPBadRequest( text=f"Package {base} is unknown, but no package body set") return HTTPNoContent()
def updates_aur(self, filter_packages: Iterable[str], no_vcs: bool) -> List[Package]: """ check AUR for updates :param filter_packages: do not check every package just specified in the list :param no_vcs: do not check VCS packages :return: list of packages which are out-of-dated """ result: List[Package] = [] for local in self.packages(): if local.base in self.ignore_list: continue if local.is_vcs and no_vcs: continue if filter_packages and local.base not in filter_packages: continue try: remote = Package.load(local.base, self.pacman, self.aur_url) if local.is_outdated(remote, self.paths): self.reporter.set_pending(local.base) result.append(remote) except Exception: self.reporter.set_failed(local.base) self.logger.exception( f"could not load remote package {local.base}") continue return result
def test_actual_version(package_ahriman: Package, repository_paths: RepositoryPaths) -> None: """ must return same actual_version as version is """ assert package_ahriman.actual_version( repository_paths) == package_ahriman.version
def test_dependencies_with_version(mocker: MockerFixture, resource_path_root: Path) -> None: """ must load correct list of dependencies with version """ srcinfo = (resource_path_root / "models" / "package_yay_srcinfo").read_text() mocker.patch("pathlib.Path.read_text", return_value=srcinfo) assert Package.dependencies(Path("path")) == {"git", "go", "pacman"}
def test_from_archive(package_ahriman: Package, pyalpm_handle: MagicMock, mocker: MockerFixture) -> None: """ must construct package from alpm library """ mocker.patch( "ahriman.models.package_description.PackageDescription.from_package", return_value=package_ahriman.packages[package_ahriman.base]) assert Package.from_archive(Path("path"), pyalpm_handle, package_ahriman.aur_url) == package_ahriman
def package_python_schedule( package_description_python_schedule: PackageDescription, package_description_python2_schedule: PackageDescription) -> Package: packages = { "python-schedule": package_description_python_schedule, "python2-schedule": package_description_python2_schedule } return Package(base="python-schedule", version="1.0.0-2", aur_url="https://aur.archlinux.org", packages=packages)
def test_actual_version_srcinfo_failed(package_tpacpi_bat_git: Package, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None: """ must return same version in case if exception occurred """ mocker.patch("ahriman.models.package.Package._check_output", side_effect=Exception()) mocker.patch("ahriman.core.build_tools.task.Task.fetch") assert package_tpacpi_bat_git.actual_version( repository_paths) == package_tpacpi_bat_git.version
def load(cls: Type[Leaf], package: Package) -> Leaf: """ load leaf from package with dependencies :param package: package properties :return: loaded class """ clone_dir = Path(tempfile.mkdtemp()) try: Task.fetch(clone_dir, package.git_url) dependencies = Package.dependencies(clone_dir) finally: shutil.rmtree(clone_dir, ignore_errors=True) return cls(package, dependencies)
def packages(self) -> List[Package]: """ generate list of repository packages :return: list of packages properties """ result: Dict[str, Package] = {} for full_path in filter(package_like, self.paths.repository.iterdir()): try: local = Package.load(full_path, self.pacman, self.aur_url) result.setdefault(local.base, local).packages.update(local.packages) except Exception: self.logger.exception(f"could not load package from {full_path}") continue return list(result.values())
def test_from_aur(package_ahriman: Package, mocker: MockerFixture) -> None: """ must construct package from aur """ mock = MagicMock() type(mock).name = PropertyMock(return_value=package_ahriman.base) type(mock).package_base = PropertyMock(return_value=package_ahriman.base) type(mock).version = PropertyMock(return_value=package_ahriman.version) mocker.patch("aur.info", return_value=mock) package = Package.from_aur(package_ahriman.base, package_ahriman.aur_url) assert package_ahriman.base == package.base assert package_ahriman.version == package.version assert package_ahriman.packages.keys() == package.packages.keys()
def test_actual_version_vcs(package_tpacpi_bat_git: Package, repository_paths: RepositoryPaths, mocker: MockerFixture, resource_path_root: Path) -> None: """ must return valid actual_version for VCS package """ srcinfo = (resource_path_root / "models" / "package_tpacpi-bat-git_srcinfo").read_text() mocker.patch("ahriman.models.package.Package._check_output", return_value=srcinfo) mocker.patch("ahriman.core.build_tools.task.Task.fetch") assert package_tpacpi_bat_git.actual_version( repository_paths) == "3.1.r13.g4959b52-1"
def test_actual_version_vcs_failed(package_tpacpi_bat_git: Package, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None: """ must return same version in case if exception occurred """ mocker.patch("pathlib.Path.read_text", return_value="") mocker.patch("ahriman.models.package.parse_srcinfo", return_value=({ "packages": {} }, ["an error"])) mocker.patch("ahriman.models.package.Package._check_output") mocker.patch("ahriman.core.build_tools.task.Task.fetch") assert package_tpacpi_bat_git.actual_version( repository_paths) == package_tpacpi_bat_git.version
async def test_get(client: TestClient, package_ahriman: Package) -> None: """ must generate web service status correctly """ await client.post(f"/api/v1/packages/{package_ahriman.base}", json={ "status": BuildStatusEnum.Success.value, "package": package_ahriman.view() }) response = await client.get("/api/v1/status") assert response.status == 200 json = await response.json() assert json["version"] == version.__version__ assert json["packages"] assert json["packages"]["total"] == 1
def add(self, package: Package, status: BuildStatusEnum) -> None: """ add new package with status :param package: package properties :param status: current package build status """ payload = {"status": status.value, "package": package.view()} try: response = requests.post(self._package_url(package.base), json=payload) response.raise_for_status() except requests.exceptions.HTTPError as e: self.logger.exception( f"could not add {package.base}: {exception_response_text(e)}") except Exception: self.logger.exception(f"could not add {package.base}")
def process_update(self, packages: Iterable[Path]) -> Path: """ sign packages, add them to repository and update repository database :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: self.logger.warning( f"received empty package name for base {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 files = self.sign.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 self.repo.add(package_path) # we are iterating over bases, not single packages updates: Dict[str, Package] = {} for filename in packages: try: local = Package.load(filename, self.pacman, self.aur_url) updates.setdefault(local.base, local).packages.update(local.packages) except Exception: self.logger.exception( f"could not load package from {filename}") for local in updates.values(): try: for description in local.packages.values(): update_single(description.filename, local.base) self.reporter.set_success(local) except Exception: self.reporter.set_failed(local.base) self.logger.exception(f"could not process {local.base}") self.clear_packages() return self.repo.repo_path
def get(self, base: Optional[str]) -> List[Tuple[Package, BuildStatus]]: """ get package status :param base: package base to get :return: list of current package description and status if it has been found """ try: response = requests.get(self._package_url(base or "")) response.raise_for_status() status_json = response.json() return [(Package.from_json(package["package"]), BuildStatus.from_json(package["status"])) for package in status_json] except requests.exceptions.HTTPError as e: self.logger.exception( f"could not get {base}: {exception_response_text(e)}") except Exception: self.logger.exception(f"could not get {base}") return []
def updates_manual(self) -> List[Package]: """ check for packages for which manual update has been requested :return: list of packages which are out-of-dated """ result: List[Package] = [] known_bases = {package.base for package in self.packages()} for fn in self.paths.manual.iterdir(): try: local = Package.load(fn, 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(f"could not add package from {fn}") self.clear_manual() return result
def package_tpacpi_bat_git() -> Package: return Package(base="tpacpi-bat-git", version="3.1.r12.g4959b52-1", aur_url="https://aur.archlinux.org", packages={"tpacpi-bat-git": PackageDescription()})
def process_update(paths: Iterable[Path]) -> None: updated = [Package.load(path, self.repository.pacman, self.repository.aur_url) for path in paths] self.repository.process_update(paths) self._finalize(updated)
def process_dependencies(path: Path) -> None: if without_dependencies: return dependencies = Package.dependencies(path) self.add(dependencies.difference(known_packages), without_dependencies)