From f4339a746aec20e74923427195596a5bc3e2a155 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Thu, 18 Dec 2025 14:04:44 +0100 Subject: [PATCH] executet 'ruff format --check .' --- src/pkgmgr/__init__.py | 16 ++-- src/pkgmgr/actions/__init__.py | 2 +- src/pkgmgr/actions/branch/close_branch.py | 10 ++- src/pkgmgr/actions/branch/drop_branch.py | 12 ++- src/pkgmgr/actions/config/add.py | 15 ++-- src/pkgmgr/actions/config/init.py | 12 ++- src/pkgmgr/actions/config/show.py | 7 +- src/pkgmgr/actions/install/__init__.py | 22 ++--- .../actions/install/installers/__init__.py | 8 +- src/pkgmgr/actions/install/installers/base.py | 4 +- .../actions/install/installers/makefile.py | 8 +- .../install/installers/nix/conflicts.py | 8 +- .../install/installers/nix/installer.py | 44 +++++++--- .../installers/nix/profile/inspector.py | 4 +- .../install/installers/nix/profile_list.py | 4 +- .../actions/install/installers/nix/retry.py | 21 ++++- .../actions/install/installers/nix/runner.py | 5 +- .../install/installers/nix/textparse.py | 4 +- .../actions/install/installers/python.py | 4 +- src/pkgmgr/actions/install/pipeline.py | 6 +- src/pkgmgr/actions/mirror/merge_cmd.py | 2 + src/pkgmgr/actions/mirror/setup_cmd.py | 4 +- src/pkgmgr/actions/mirror/url_utils.py | 4 +- src/pkgmgr/actions/proxy.py | 17 +++- src/pkgmgr/actions/release/files.py | 4 +- src/pkgmgr/actions/release/git_ops.py | 8 +- src/pkgmgr/actions/release/workflow.py | 24 ++++-- src/pkgmgr/actions/repository/clone.py | 10 ++- .../actions/repository/create/parser.py | 4 +- .../actions/repository/create/templates.py | 4 +- src/pkgmgr/actions/repository/deinstall.py | 10 ++- src/pkgmgr/actions/repository/delete.py | 19 ++++- src/pkgmgr/actions/repository/list.py | 12 +-- src/pkgmgr/actions/update/manager.py | 16 +++- src/pkgmgr/actions/update/os_release.py | 5 +- src/pkgmgr/actions/update/system_updater.py | 8 +- src/pkgmgr/cli/__init__.py | 5 +- src/pkgmgr/cli/commands/changelog.py | 4 +- src/pkgmgr/cli/commands/config.py | 4 +- src/pkgmgr/cli/commands/mirror.py | 15 +++- src/pkgmgr/cli/commands/publish.py | 4 +- src/pkgmgr/cli/commands/release.py | 8 +- src/pkgmgr/cli/commands/repos.py | 5 +- src/pkgmgr/cli/commands/version.py | 6 +- src/pkgmgr/cli/parser/branch_cmd.py | 9 +-- src/pkgmgr/cli/parser/mirror_cmd.py | 16 +++- src/pkgmgr/cli/parser/navigation_cmd.py | 5 +- src/pkgmgr/cli/proxy.py | 16 +--- src/pkgmgr/cli/tools/paths.py | 5 +- src/pkgmgr/cli/tools/vscode.py | 4 +- src/pkgmgr/core/command/alias.py | 5 +- src/pkgmgr/core/command/ink.py | 3 +- src/pkgmgr/core/command/layer.py | 1 + src/pkgmgr/core/command/resolve.py | 9 +-- src/pkgmgr/core/config/load.py | 21 ++--- src/pkgmgr/core/config/save.py | 7 +- src/pkgmgr/core/credentials/providers/env.py | 4 +- src/pkgmgr/core/credentials/providers/gh.py | 1 + .../core/credentials/providers/keyring.py | 4 +- src/pkgmgr/core/credentials/resolver.py | 12 ++- src/pkgmgr/core/credentials/store_keys.py | 16 ++-- src/pkgmgr/core/git/commands/add_all.py | 4 +- src/pkgmgr/core/git/commands/branch_move.py | 4 +- src/pkgmgr/core/git/errors.py | 5 ++ .../core/git/queries/get_config_value.py | 1 + .../core/git/queries/get_current_branch.py | 2 +- .../git/queries/get_remote_head_commit.py | 2 +- .../core/git/queries/get_remote_push_urls.py | 1 + src/pkgmgr/core/git/run.py | 4 +- src/pkgmgr/core/repository/dir.py | 10 ++- src/pkgmgr/core/repository/identifier.py | 2 +- src/pkgmgr/core/repository/ignored.py | 2 +- src/pkgmgr/core/repository/paths.py | 4 +- src/pkgmgr/core/repository/resolve.py | 9 ++- src/pkgmgr/core/repository/verify.py | 16 +++- src/pkgmgr/core/version/installed.py | 1 + src/pkgmgr/core/version/semver.py | 8 +- tests/e2e/test_branch_commands.py | 8 +- .../e2e/test_install_makefile_three_times.py | 5 +- tests/e2e/test_install_pkgmgr_shallow.py | 4 +- .../test_install_pkgmgr_three_times_nix.py | 9 +-- .../test_install_pkgmgr_three_times_venv.py | 6 +- tests/e2e/test_make_commands.py | 4 +- tests/e2e/test_nix_build_pkgmgr.py | 23 +++--- tests/e2e/test_release_commands.py | 1 + tests/e2e/test_tools_help.py | 3 +- tests/e2e/test_update_all_no_system.py | 5 +- tests/e2e/test_update_pkgmgr_system.py | 5 +- tests/e2e/test_version_commands.py | 4 +- tests/integration/test_branch_cli.py | 4 +- tests/integration/test_install_repos.py | 16 ++-- tests/integration/test_mirror_commands.py | 79 +++++++++++++++--- .../integration/test_nix_profile_list_json.py | 18 ++++- .../test_recursive_capabilities.py | 18 +++-- .../integration/test_release_publish_hook.py | 17 ++-- .../integration/test_repos_create_preview.py | 5 +- .../test_repository_paths_exist.py | 8 +- tests/integration/test_token_resolver_flow.py | 5 -- .../test_update_silent_continues.py | 22 +++-- .../actions/branch/test_close_branch.py | 49 ++++++++--- .../pkgmgr/actions/branch/test_drop_branch.py | 29 +++++-- .../installers/nix/test_conflicts_resolver.py | 38 ++++++--- .../install/installers/nix/test_inspector.py | 38 ++++++--- .../installers/nix/test_installer_core.py | 8 +- .../install/installers/nix/test_legacy.py | 39 ++++++--- .../install/installers/nix/test_matcher.py | 29 +++++-- .../installers/nix/test_nix_retry_403.py | 31 ++++--- .../install/installers/nix/test_normalizer.py | 9 ++- .../install/installers/nix/test_parser.py | 4 +- .../nix/test_profile_list_reader.py | 4 +- .../install/installers/nix/test_result.py | 3 + .../install/installers/nix/test_textparse.py | 8 +- .../os_packages/test_arch_pkgbuild.py | 24 ++++-- .../os_packages/test_debian_control.py | 8 +- .../installers/test_makefile_installer.py | 8 +- .../actions/install/test_capabilities.py | 12 ++- .../pkgmgr/actions/install/test_context.py | 2 +- .../pkgmgr/actions/mirror/test_context.py | 8 +- .../pkgmgr/actions/mirror/test_diff_cmd.py | 16 +++- .../pkgmgr/actions/mirror/test_git_remote.py | 13 ++- .../mirror/test_git_remote_primary_push.py | 26 +++--- tests/unit/pkgmgr/actions/mirror/test_io.py | 15 +++- .../pkgmgr/actions/mirror/test_list_cmd.py | 8 +- .../actions/mirror/test_remote_provision.py | 4 +- .../pkgmgr/actions/mirror/test_setup_cmd.py | 28 +++++-- .../pkgmgr/actions/mirror/test_url_utils.py | 18 ++++- .../pkgmgr/actions/publish/test_git_tags.py | 5 +- .../pkgmgr/actions/publish/test_pypi_url.py | 1 - .../actions/publish/test_workflow_preview.py | 5 +- .../unit/pkgmgr/actions/release/test_files.py | 81 ++++++++++++------- .../test_git_ops_is_highest_version_tag.py | 10 ++- .../pkgmgr/actions/release/test_workflow.py | 16 +++- .../create/test_scaffold_render_preview.py | 2 +- .../actions/repository/test_deinstall.py | 66 ++++++++++----- tests/unit/pkgmgr/actions/test_changelog.py | 12 ++- .../unit/pkgmgr/cli/commands/test_publish.py | 1 - .../unit/pkgmgr/cli/commands/test_release.py | 4 +- .../cli/commands/test_release_publish_hook.py | 17 ++-- tests/unit/pkgmgr/cli/commands/test_repos.py | 33 ++++---- tests/unit/pkgmgr/cli/test_cli.py | 9 ++- tests/unit/pkgmgr/cli/test_handle_branch.py | 24 ++++-- tests/unit/pkgmgr/cli/tools/test_paths.py | 4 +- tests/unit/pkgmgr/cli/tools/test_vscode.py | 75 +++++++++++------ tests/unit/pkgmgr/core/command/test_ink.py | 5 +- .../unit/pkgmgr/core/command/test_resolve.py | 28 ++++--- tests/unit/pkgmgr/core/command/test_run.py | 18 ++++- .../queries/test_get_latest_signing_key.py | 15 +++- .../queries/test_get_remote_head_commit.py | 15 +++- .../core/git/queries/test_remote_check.py | 12 ++- tests/unit/pkgmgr/core/git/test_run.py | 11 ++- tests/unit/pkgmgr/core/repository/test_dir.py | 5 +- .../pkgmgr/core/repository/test_ignored.py | 14 +++- .../pkgmgr/core/repository/test_selected.py | 4 +- .../core/repository/test_verify_repository.py | 61 +++++++++----- tests/unit/pkgmgr/core/test_create_ink.py | 31 ++++--- 155 files changed, 1327 insertions(+), 636 deletions(-) diff --git a/src/pkgmgr/__init__.py b/src/pkgmgr/__init__.py index 2798285..54e3c11 100644 --- a/src/pkgmgr/__init__.py +++ b/src/pkgmgr/__init__.py @@ -25,12 +25,12 @@ __all__ = ["cli"] def __getattr__(name: str) -> Any: - """ - Lazily expose ``pkgmgr.cli`` as attribute on the top-level package. + """ + Lazily expose ``pkgmgr.cli`` as attribute on the top-level package. - This keeps ``import pkgmgr`` lightweight while still allowing - ``from pkgmgr import cli`` in tests and entry points. - """ - if name == "cli": - return import_module("pkgmgr.cli") - raise AttributeError(f"module 'pkgmgr' has no attribute {name!r}") + This keeps ``import pkgmgr`` lightweight while still allowing + ``from pkgmgr import cli`` in tests and entry points. + """ + if name == "cli": + return import_module("pkgmgr.cli") + raise AttributeError(f"module 'pkgmgr' has no attribute {name!r}") diff --git a/src/pkgmgr/actions/__init__.py b/src/pkgmgr/actions/__init__.py index 0dc8c6e..a1e49fe 100644 --- a/src/pkgmgr/actions/__init__.py +++ b/src/pkgmgr/actions/__init__.py @@ -3,4 +3,4 @@ from __future__ import annotations # expose subpackages for patch() / resolve_name() friendliness from . import release as release # noqa: F401 -__all__ = ["release"] \ No newline at end of file +__all__ = ["release"] diff --git a/src/pkgmgr/actions/branch/close_branch.py b/src/pkgmgr/actions/branch/close_branch.py index c9353f3..86c5a82 100644 --- a/src/pkgmgr/actions/branch/close_branch.py +++ b/src/pkgmgr/actions/branch/close_branch.py @@ -48,9 +48,13 @@ def close_branch( # Confirmation if not force: - answer = input( - f"Merge branch '{name}' into '{target_base}' and delete it afterwards? (y/N): " - ).strip().lower() + answer = ( + input( + f"Merge branch '{name}' into '{target_base}' and delete it afterwards? (y/N): " + ) + .strip() + .lower() + ) if answer != "y": print("Aborted closing branch.") return diff --git a/src/pkgmgr/actions/branch/drop_branch.py b/src/pkgmgr/actions/branch/drop_branch.py index a941f3d..e50873a 100644 --- a/src/pkgmgr/actions/branch/drop_branch.py +++ b/src/pkgmgr/actions/branch/drop_branch.py @@ -41,15 +41,19 @@ def drop_branch( # Confirmation if not force: - answer = input( - f"Delete branch '{name}' locally and on origin? This is destructive! (y/N): " - ).strip().lower() + answer = ( + input( + f"Delete branch '{name}' locally and on origin? This is destructive! (y/N): " + ) + .strip() + .lower() + ) if answer != "y": print("Aborted dropping branch.") return delete_local_branch(name, cwd=cwd, force=False) - + # Remote delete (special-case message) try: delete_remote_branch("origin", name, cwd=cwd) diff --git a/src/pkgmgr/actions/config/add.py b/src/pkgmgr/actions/config/add.py index 40d225b..dd4a5c9 100644 --- a/src/pkgmgr/actions/config/add.py +++ b/src/pkgmgr/actions/config/add.py @@ -1,15 +1,18 @@ import yaml import os -from pkgmgr.core.config.save import save_user_config +from pkgmgr.core.config.save import save_user_config -def interactive_add(config,USER_CONFIG_PATH:str): + +def interactive_add(config, USER_CONFIG_PATH: str): """Interactively prompt the user to add a new repository entry to the user config.""" print("Adding a new repository configuration entry.") new_entry = {} new_entry["provider"] = input("Provider (e.g., github.com): ").strip() new_entry["account"] = input("Account (e.g., yourusername): ").strip() new_entry["repository"] = input("Repository name (e.g., mytool): ").strip() - new_entry["command"] = input("Command (optional, leave blank to auto-detect): ").strip() + new_entry["command"] = input( + "Command (optional, leave blank to auto-detect): " + ).strip() new_entry["description"] = input("Description (optional): ").strip() new_entry["replacement"] = input("Replacement (optional): ").strip() new_entry["alias"] = input("Alias (optional): ").strip() @@ -25,12 +28,12 @@ def interactive_add(config,USER_CONFIG_PATH:str): confirm = input("Add this entry to user config? (y/N): ").strip().lower() if confirm == "y": if os.path.exists(USER_CONFIG_PATH): - with open(USER_CONFIG_PATH, 'r') as f: + with open(USER_CONFIG_PATH, "r") as f: user_config = yaml.safe_load(f) or {} else: user_config = {"repositories": []} user_config.setdefault("repositories", []) user_config["repositories"].append(new_entry) - save_user_config(user_config,USER_CONFIG_PATH) + save_user_config(user_config, USER_CONFIG_PATH) else: - print("Entry not added.") \ No newline at end of file + print("Entry not added.") diff --git a/src/pkgmgr/actions/config/init.py b/src/pkgmgr/actions/config/init.py index 93950a8..9c9a6f9 100644 --- a/src/pkgmgr/actions/config/init.py +++ b/src/pkgmgr/actions/config/init.py @@ -107,11 +107,15 @@ def config_init( # Already known? if key in default_keys: skipped += 1 - print(f"[SKIP] (defaults) {provider}/{account}/{repo_name}") + print( + f"[SKIP] (defaults) {provider}/{account}/{repo_name}" + ) continue if key in existing_keys: skipped += 1 - print(f"[SKIP] (user-config) {provider}/{account}/{repo_name}") + print( + f"[SKIP] (user-config) {provider}/{account}/{repo_name}" + ) continue print(f"[ADD] {provider}/{account}/{repo_name}") @@ -121,7 +125,9 @@ def config_init( if verified_commit: print(f"[INFO] Latest commit: {verified_commit}") else: - print("[WARN] Could not read commit (not a git repo or no commits).") + print( + "[WARN] Could not read commit (not a git repo or no commits)." + ) entry: Dict[str, Any] = { "provider": provider, diff --git a/src/pkgmgr/actions/config/show.py b/src/pkgmgr/actions/config/show.py index 0f063f8..e973eeb 100644 --- a/src/pkgmgr/actions/config/show.py +++ b/src/pkgmgr/actions/config/show.py @@ -1,6 +1,7 @@ import yaml from pkgmgr.core.config.load import load_config + def show_config(selected_repos, user_config_path, full_config=False): """Display configuration for one or more repositories, or the entire merged config.""" if full_config: @@ -8,8 +9,10 @@ def show_config(selected_repos, user_config_path, full_config=False): print(yaml.dump(merged, default_flow_style=False)) else: for repo in selected_repos: - identifier = f'{repo.get("provider")}/{repo.get("account")}/{repo.get("repository")}' + identifier = ( + f"{repo.get('provider')}/{repo.get('account')}/{repo.get('repository')}" + ) print(f"Repository: {identifier}") for key, value in repo.items(): print(f" {key}: {value}") - print("-" * 40) \ No newline at end of file + print("-" * 40) diff --git a/src/pkgmgr/actions/install/__init__.py b/src/pkgmgr/actions/install/__init__.py index aaa6977..56ecc71 100644 --- a/src/pkgmgr/actions/install/__init__.py +++ b/src/pkgmgr/actions/install/__init__.py @@ -66,10 +66,7 @@ def _ensure_repo_dir( repo_dir = get_repo_dir(repositories_base_dir, repo) if not os.path.exists(repo_dir): - print( - f"Repository directory '{repo_dir}' does not exist. " - "Cloning it now..." - ) + print(f"Repository directory '{repo_dir}' does not exist. Cloning it now...") clone_repos( [repo], repositories_base_dir, @@ -79,10 +76,7 @@ def _ensure_repo_dir( clone_mode, ) if not os.path.exists(repo_dir): - print( - f"Cloning failed for repository {identifier}. " - "Skipping installation." - ) + print(f"Cloning failed for repository {identifier}. Skipping installation.") return None return repo_dir @@ -115,7 +109,9 @@ def _verify_repo( if silent: # Non-interactive mode: continue with a warning. - print(f"[Warning] Continuing despite verification failure for {identifier} (--silent).") + print( + f"[Warning] Continuing despite verification failure for {identifier} (--silent)." + ) else: choice = input("Continue anyway? [y/N]: ").strip().lower() if choice != "y": @@ -232,12 +228,16 @@ def install_repos( code = exc.code if isinstance(exc.code, int) else str(exc.code) failures.append((identifier, f"installer failed (exit={code})")) if not quiet: - print(f"[Warning] install: repository {identifier} failed (exit={code}). Continuing...") + print( + f"[Warning] install: repository {identifier} failed (exit={code}). Continuing..." + ) continue except Exception as exc: failures.append((identifier, f"unexpected error: {exc}")) if not quiet: - print(f"[Warning] install: repository {identifier} hit an unexpected error: {exc}. Continuing...") + print( + f"[Warning] install: repository {identifier} hit an unexpected error: {exc}. Continuing..." + ) continue if failures and emit_summary and not quiet: diff --git a/src/pkgmgr/actions/install/installers/__init__.py b/src/pkgmgr/actions/install/installers/__init__.py index 30ea0ab..f728a59 100644 --- a/src/pkgmgr/actions/install/installers/__init__.py +++ b/src/pkgmgr/actions/install/installers/__init__.py @@ -14,6 +14,10 @@ from pkgmgr.actions.install.installers.python import PythonInstaller # noqa: F4 from pkgmgr.actions.install.installers.makefile import MakefileInstaller # noqa: F401 # OS-specific installers -from pkgmgr.actions.install.installers.os_packages.arch_pkgbuild import ArchPkgbuildInstaller # noqa: F401 -from pkgmgr.actions.install.installers.os_packages.debian_control import DebianControlInstaller # noqa: F401 +from pkgmgr.actions.install.installers.os_packages.arch_pkgbuild import ( + ArchPkgbuildInstaller, +) # noqa: F401 +from pkgmgr.actions.install.installers.os_packages.debian_control import ( + DebianControlInstaller, +) # noqa: F401 from pkgmgr.actions.install.installers.os_packages.rpm_spec import RpmSpecInstaller # noqa: F401 diff --git a/src/pkgmgr/actions/install/installers/base.py b/src/pkgmgr/actions/install/installers/base.py index df052be..851f04c 100644 --- a/src/pkgmgr/actions/install/installers/base.py +++ b/src/pkgmgr/actions/install/installers/base.py @@ -41,7 +41,9 @@ class BaseInstaller(ABC): return caps for matcher in CAPABILITY_MATCHERS: - if matcher.applies_to_layer(self.layer) and matcher.is_provided(ctx, self.layer): + if matcher.applies_to_layer(self.layer) and matcher.is_provided( + ctx, self.layer + ): caps.add(matcher.name) return caps diff --git a/src/pkgmgr/actions/install/installers/makefile.py b/src/pkgmgr/actions/install/installers/makefile.py index a8148f1..5018f4f 100644 --- a/src/pkgmgr/actions/install/installers/makefile.py +++ b/src/pkgmgr/actions/install/installers/makefile.py @@ -16,7 +16,9 @@ class MakefileInstaller(BaseInstaller): def supports(self, ctx: RepoContext) -> bool: if os.environ.get("PKGMGR_DISABLE_MAKEFILE_INSTALLER") == "1": if not ctx.quiet: - print("[INFO] PKGMGR_DISABLE_MAKEFILE_INSTALLER=1 – skipping MakefileInstaller.") + print( + "[INFO] PKGMGR_DISABLE_MAKEFILE_INSTALLER=1 – skipping MakefileInstaller." + ) return False makefile_path = os.path.join(ctx.repo_dir, self.MAKEFILE_NAME) @@ -46,7 +48,9 @@ class MakefileInstaller(BaseInstaller): return if not ctx.quiet: - print(f"[pkgmgr] Running make install for {ctx.identifier} (MakefileInstaller)") + print( + f"[pkgmgr] Running make install for {ctx.identifier} (MakefileInstaller)" + ) run_command("make install", cwd=ctx.repo_dir, preview=ctx.preview) diff --git a/src/pkgmgr/actions/install/installers/nix/conflicts.py b/src/pkgmgr/actions/install/installers/nix/conflicts.py index a7c3486..2967b6e 100644 --- a/src/pkgmgr/actions/install/installers/nix/conflicts.py +++ b/src/pkgmgr/actions/install/installers/nix/conflicts.py @@ -57,7 +57,9 @@ class NixConflictResolver: # 3) Fallback: output-name based lookup (also covers nix suggesting: `nix profile remove pkgmgr`) if not tokens: - tokens = self._profile.find_remove_tokens_for_output(ctx, self._runner, output) + tokens = self._profile.find_remove_tokens_for_output( + ctx, self._runner, output + ) if tokens: if not quiet: @@ -94,7 +96,9 @@ class NixConflictResolver: continue if not quiet: - print("[nix] conflict detected but could not resolve profile entries to remove.") + print( + "[nix] conflict detected but could not resolve profile entries to remove." + ) return False return False diff --git a/src/pkgmgr/actions/install/installers/nix/installer.py b/src/pkgmgr/actions/install/installers/nix/installer.py index 36c4174..3c53301 100644 --- a/src/pkgmgr/actions/install/installers/nix/installer.py +++ b/src/pkgmgr/actions/install/installers/nix/installer.py @@ -75,7 +75,9 @@ class NixFlakeInstaller(BaseInstaller): # Core install path # --------------------------------------------------------------------- - def _install_only(self, ctx: "RepoContext", output: str, allow_failure: bool) -> None: + def _install_only( + self, ctx: "RepoContext", output: str, allow_failure: bool + ) -> None: install_cmd = f"nix profile install {self._installable(ctx, output)}" if not ctx.quiet: @@ -96,7 +98,9 @@ class NixFlakeInstaller(BaseInstaller): output=output, ): if not ctx.quiet: - print(f"[nix] output '{output}' successfully installed after conflict cleanup.") + print( + f"[nix] output '{output}' successfully installed after conflict cleanup." + ) return if not ctx.quiet: @@ -107,20 +111,26 @@ class NixFlakeInstaller(BaseInstaller): # If indices are supported, try legacy index-upgrade path. if self._indices_supported is not False: - indices = self._profile.find_installed_indices_for_output(ctx, self._runner, output) + indices = self._profile.find_installed_indices_for_output( + ctx, self._runner, output + ) upgraded = False for idx in indices: if self._upgrade_index(ctx, idx): upgraded = True if not ctx.quiet: - print(f"[nix] output '{output}' successfully upgraded (index {idx}).") + print( + f"[nix] output '{output}' successfully upgraded (index {idx})." + ) if upgraded: return if indices and not ctx.quiet: - print(f"[nix] upgrade failed; removing indices {indices} and reinstalling '{output}'.") + print( + f"[nix] upgrade failed; removing indices {indices} and reinstalling '{output}'." + ) for idx in indices: self._remove_index(ctx, idx) @@ -139,7 +149,9 @@ class NixFlakeInstaller(BaseInstaller): print(f"[nix] output '{output}' successfully re-installed.") return - print(f"[ERROR] Failed to install Nix flake output '{output}' (exit {final.returncode})") + print( + f"[ERROR] Failed to install Nix flake output '{output}' (exit {final.returncode})" + ) if not allow_failure: raise SystemExit(final.returncode) @@ -149,7 +161,9 @@ class NixFlakeInstaller(BaseInstaller): # force_update path # --------------------------------------------------------------------- - def _force_upgrade_output(self, ctx: "RepoContext", output: str, allow_failure: bool) -> None: + def _force_upgrade_output( + self, ctx: "RepoContext", output: str, allow_failure: bool + ) -> None: # Prefer token path if indices unsupported (new nix) if self._indices_supported is False: self._remove_tokens_for_output(ctx, output) @@ -158,14 +172,18 @@ class NixFlakeInstaller(BaseInstaller): print(f"[nix] output '{output}' successfully upgraded.") return - indices = self._profile.find_installed_indices_for_output(ctx, self._runner, output) + indices = self._profile.find_installed_indices_for_output( + ctx, self._runner, output + ) upgraded_any = False for idx in indices: if self._upgrade_index(ctx, idx): upgraded_any = True if not ctx.quiet: - print(f"[nix] output '{output}' successfully upgraded (index {idx}).") + print( + f"[nix] output '{output}' successfully upgraded (index {idx})." + ) if upgraded_any: if not ctx.quiet: @@ -173,7 +191,9 @@ class NixFlakeInstaller(BaseInstaller): return if indices and not ctx.quiet: - print(f"[nix] upgrade failed; removing indices {indices} and reinstalling '{output}'.") + print( + f"[nix] upgrade failed; removing indices {indices} and reinstalling '{output}'." + ) for idx in indices: self._remove_index(ctx, idx) @@ -223,7 +243,9 @@ class NixFlakeInstaller(BaseInstaller): return if not ctx.quiet: - print(f"[nix] indices unsupported; removing by token(s): {', '.join(tokens)}") + print( + f"[nix] indices unsupported; removing by token(s): {', '.join(tokens)}" + ) for t in tokens: self._runner.run(ctx, f"nix profile remove {t}", allow_failure=True) diff --git a/src/pkgmgr/actions/install/installers/nix/profile/inspector.py b/src/pkgmgr/actions/install/installers/nix/profile/inspector.py index 6daf83a..1725fbf 100644 --- a/src/pkgmgr/actions/install/installers/nix/profile/inspector.py +++ b/src/pkgmgr/actions/install/installers/nix/profile/inspector.py @@ -101,7 +101,9 @@ class NixProfileInspector: data = self.list_json(ctx, runner) entries = normalize_elements(data) - tokens: List[str] = [out] # critical: matches nix's own suggestion for conflicts + tokens: List[str] = [ + out + ] # critical: matches nix's own suggestion for conflicts for e in entries: if entry_matches_output(e, out): diff --git a/src/pkgmgr/actions/install/installers/nix/profile_list.py b/src/pkgmgr/actions/install/installers/nix/profile_list.py index 7a0835e..34e1b03 100644 --- a/src/pkgmgr/actions/install/installers/nix/profile_list.py +++ b/src/pkgmgr/actions/install/installers/nix/profile_list.py @@ -48,7 +48,9 @@ class NixProfileListReader: return uniq - def indices_matching_store_prefixes(self, ctx: "RepoContext", prefixes: List[str]) -> List[int]: + def indices_matching_store_prefixes( + self, ctx: "RepoContext", prefixes: List[str] + ) -> List[int]: prefixes = [self._store_prefix(p) for p in prefixes if p] prefixes = [p for p in prefixes if p] if not prefixes: diff --git a/src/pkgmgr/actions/install/installers/nix/retry.py b/src/pkgmgr/actions/install/installers/nix/retry.py index 52cec7f..3f2a663 100644 --- a/src/pkgmgr/actions/install/installers/nix/retry.py +++ b/src/pkgmgr/actions/install/installers/nix/retry.py @@ -11,6 +11,7 @@ if TYPE_CHECKING: from pkgmgr.actions.install.context import RepoContext from .runner import CommandRunner + @dataclass(frozen=True) class RetryPolicy: max_attempts: int = 7 @@ -35,13 +36,19 @@ class GitHubRateLimitRetry: install_cmd: str, ) -> RunResult: quiet = bool(getattr(ctx, "quiet", False)) - delays = list(self._fibonacci_backoff(self._policy.base_delay_seconds, self._policy.max_attempts)) + delays = list( + self._fibonacci_backoff( + self._policy.base_delay_seconds, self._policy.max_attempts + ) + ) last: RunResult | None = None for attempt, base_delay in enumerate(delays, start=1): if not quiet: - print(f"[nix] attempt {attempt}/{self._policy.max_attempts}: {install_cmd}") + print( + f"[nix] attempt {attempt}/{self._policy.max_attempts}: {install_cmd}" + ) res = runner.run(ctx, install_cmd, allow_failure=True) last = res @@ -56,7 +63,9 @@ class GitHubRateLimitRetry: if attempt >= self._policy.max_attempts: break - jitter = random.randint(self._policy.jitter_seconds_min, self._policy.jitter_seconds_max) + jitter = random.randint( + self._policy.jitter_seconds_min, self._policy.jitter_seconds_max + ) wait_time = base_delay + jitter if not quiet: @@ -67,7 +76,11 @@ class GitHubRateLimitRetry: time.sleep(wait_time) - return last if last is not None else RunResult(returncode=1, stdout="", stderr="nix install retry failed") + return ( + last + if last is not None + else RunResult(returncode=1, stdout="", stderr="nix install retry failed") + ) @staticmethod def _is_github_rate_limit_error(text: str) -> bool: diff --git a/src/pkgmgr/actions/install/installers/nix/runner.py b/src/pkgmgr/actions/install/installers/nix/runner.py index e317fef..c805489 100644 --- a/src/pkgmgr/actions/install/installers/nix/runner.py +++ b/src/pkgmgr/actions/install/installers/nix/runner.py @@ -9,6 +9,7 @@ from .types import RunResult if TYPE_CHECKING: from pkgmgr.actions.install.context import RepoContext + class CommandRunner: """ Executes commands (shell=True) inside a repository directory (if provided). @@ -40,7 +41,9 @@ class CommandRunner: raise return RunResult(returncode=1, stdout="", stderr=str(e)) - res = RunResult(returncode=p.returncode, stdout=p.stdout or "", stderr=p.stderr or "") + res = RunResult( + returncode=p.returncode, stdout=p.stdout or "", stderr=p.stderr or "" + ) if res.returncode != 0 and not quiet: self._print_compact_failure(res) diff --git a/src/pkgmgr/actions/install/installers/nix/textparse.py b/src/pkgmgr/actions/install/installers/nix/textparse.py index 862fbd0..ac5dad7 100644 --- a/src/pkgmgr/actions/install/installers/nix/textparse.py +++ b/src/pkgmgr/actions/install/installers/nix/textparse.py @@ -20,7 +20,9 @@ class NixConflictTextParser: tokens: List[str] = [] for m in pat.finditer(text or ""): t = (m.group(1) or "").strip() - if (t.startswith("'") and t.endswith("'")) or (t.startswith('"') and t.endswith('"')): + if (t.startswith("'") and t.endswith("'")) or ( + t.startswith('"') and t.endswith('"') + ): t = t[1:-1] if t: tokens.append(t) diff --git a/src/pkgmgr/actions/install/installers/python.py b/src/pkgmgr/actions/install/installers/python.py index ca3d6fa..45f1e37 100644 --- a/src/pkgmgr/actions/install/installers/python.py +++ b/src/pkgmgr/actions/install/installers/python.py @@ -14,7 +14,9 @@ class PythonInstaller(BaseInstaller): def supports(self, ctx: RepoContext) -> bool: if os.environ.get("PKGMGR_DISABLE_PYTHON_INSTALLER") == "1": - print("[INFO] PythonInstaller disabled via PKGMGR_DISABLE_PYTHON_INSTALLER.") + print( + "[INFO] PythonInstaller disabled via PKGMGR_DISABLE_PYTHON_INSTALLER." + ) return False return os.path.exists(os.path.join(ctx.repo_dir, "pyproject.toml")) diff --git a/src/pkgmgr/actions/install/pipeline.py b/src/pkgmgr/actions/install/pipeline.py index d7c8506..a9b18f3 100644 --- a/src/pkgmgr/actions/install/pipeline.py +++ b/src/pkgmgr/actions/install/pipeline.py @@ -132,7 +132,11 @@ class InstallationPipeline: continue if not quiet: - if ctx.force_update and state.layer is not None and installer_layer == state.layer: + if ( + ctx.force_update + and state.layer is not None + and installer_layer == state.layer + ): print( f"[pkgmgr] Running installer {installer.__class__.__name__} " f"for {identifier} in '{repo_dir}' (upgrade requested)..." diff --git a/src/pkgmgr/actions/mirror/merge_cmd.py b/src/pkgmgr/actions/mirror/merge_cmd.py index 3f0596d..448020b 100644 --- a/src/pkgmgr/actions/mirror/merge_cmd.py +++ b/src/pkgmgr/actions/mirror/merge_cmd.py @@ -16,6 +16,7 @@ from .types import MirrorMap, Repository # Helpers # ----------------------------------------------------------------------------- + def _repo_key(repo: Repository) -> Tuple[str, str, str]: """ Normalised key for identifying a repository in config files. @@ -47,6 +48,7 @@ def _load_user_config(path: str) -> Dict[str, object]: # Main merge command # ----------------------------------------------------------------------------- + def merge_mirrors( selected_repos: List[Repository], repositories_base_dir: str, diff --git a/src/pkgmgr/actions/mirror/setup_cmd.py b/src/pkgmgr/actions/mirror/setup_cmd.py index bf282d6..8f3057f 100644 --- a/src/pkgmgr/actions/mirror/setup_cmd.py +++ b/src/pkgmgr/actions/mirror/setup_cmd.py @@ -66,7 +66,9 @@ def _setup_remote_mirrors_for_repo( # Probe only git URLs (do not try ls-remote against PyPI etc.) # If there are no mirrors at all, probe the primary git URL. - git_mirrors = {k: v for k, v in ctx.resolved_mirrors.items() if _is_git_remote_url(v)} + git_mirrors = { + k: v for k, v in ctx.resolved_mirrors.items() if _is_git_remote_url(v) + } if not git_mirrors: primary = determine_primary_remote_url(repo, ctx) diff --git a/src/pkgmgr/actions/mirror/url_utils.py b/src/pkgmgr/actions/mirror/url_utils.py index f97eb3c..61e392f 100644 --- a/src/pkgmgr/actions/mirror/url_utils.py +++ b/src/pkgmgr/actions/mirror/url_utils.py @@ -17,7 +17,7 @@ def hostport_from_git_url(url: str) -> Tuple[str, Optional[str]]: netloc = netloc.split("@", 1)[1] if netloc.startswith("[") and "]" in netloc: - host = netloc[1:netloc.index("]")] + host = netloc[1 : netloc.index("]")] rest = netloc[netloc.index("]") + 1 :] port = rest[1:] if rest.startswith(":") else None return host.strip(), (port.strip() if port else None) @@ -43,7 +43,7 @@ def normalize_provider_host(host: str) -> str: return "" if host.startswith("[") and "]" in host: - host = host[1:host.index("]")] + host = host[1 : host.index("]")] if ":" in host and host.count(":") == 1: host = host.rsplit(":", 1)[0] diff --git a/src/pkgmgr/actions/proxy.py b/src/pkgmgr/actions/proxy.py index a1b4d4a..c8b2171 100644 --- a/src/pkgmgr/actions/proxy.py +++ b/src/pkgmgr/actions/proxy.py @@ -4,7 +4,16 @@ from pkgmgr.core.repository.dir import get_repo_dir from pkgmgr.core.command.run import run_command import sys -def exec_proxy_command(proxy_prefix: str, selected_repos, repositories_base_dir, all_repos, proxy_command: str, extra_args, preview: bool): + +def exec_proxy_command( + proxy_prefix: str, + selected_repos, + repositories_base_dir, + all_repos, + proxy_command: str, + extra_args, + preview: bool, +): """Execute a given proxy command with extra arguments for each repository.""" error_repos = [] max_exit_code = 0 @@ -22,7 +31,9 @@ def exec_proxy_command(proxy_prefix: str, selected_repos, repositories_base_dir, try: run_command(full_cmd, cwd=repo_dir, preview=preview) except SystemExit as e: - print(f"[ERROR] Command failed in {repo_identifier} with exit code {e.code}.") + print( + f"[ERROR] Command failed in {repo_identifier} with exit code {e.code}." + ) error_repos.append((repo_identifier, e.code)) max_exit_code = max(max_exit_code, e.code) @@ -30,4 +41,4 @@ def exec_proxy_command(proxy_prefix: str, selected_repos, repositories_base_dir, print("\nSummary of failed commands:") for repo_identifier, exit_code in error_repos: print(f"- {repo_identifier} failed with exit code {exit_code}") - sys.exit(max_exit_code) \ No newline at end of file + sys.exit(max_exit_code) diff --git a/src/pkgmgr/actions/release/files.py b/src/pkgmgr/actions/release/files.py index c56f13b..b3af0fe 100644 --- a/src/pkgmgr/actions/release/files.py +++ b/src/pkgmgr/actions/release/files.py @@ -121,7 +121,7 @@ def update_pyproject_version( pattern = r'^(version\s*=\s*")([^"]+)(")' new_content, count = re.subn( pattern, - lambda m: f'{m.group(1)}{new_version}{m.group(3)}', + lambda m: f"{m.group(1)}{new_version}{m.group(3)}", content, flags=re.MULTILINE, ) @@ -162,7 +162,7 @@ def update_flake_version( pattern = r'(version\s*=\s*")([^"]+)(")' new_content, count = re.subn( pattern, - lambda m: f'{m.group(1)}{new_version}{m.group(3)}', + lambda m: f"{m.group(1)}{new_version}{m.group(3)}", content, ) diff --git a/src/pkgmgr/actions/release/git_ops.py b/src/pkgmgr/actions/release/git_ops.py index 6c1441b..4004801 100644 --- a/src/pkgmgr/actions/release/git_ops.py +++ b/src/pkgmgr/actions/release/git_ops.py @@ -80,7 +80,9 @@ def is_highest_version_tag(tag: str) -> bool: return True latest = max(parsed_all) - print(f"[INFO] Latest tag (parsed): v{'.'.join(map(str, latest))}, Current tag: {tag}") + print( + f"[INFO] Latest tag (parsed): v{'.'.join(map(str, latest))}, Current tag: {tag}" + ) return parsed_current >= latest @@ -93,7 +95,9 @@ def update_latest_tag(new_tag: str, *, preview: bool = False) -> None: - 'latest' is forced (floating tag), therefore the push uses --force. """ target_ref = f"{new_tag}^{{}}" - print(f"[INFO] Updating 'latest' tag to point at {new_tag} (commit {target_ref})...") + print( + f"[INFO] Updating 'latest' tag to point at {new_tag} (commit {target_ref})..." + ) tag_force_annotated( name="latest", diff --git a/src/pkgmgr/actions/release/workflow.py b/src/pkgmgr/actions/release/workflow.py index e62b3e5..f1b90c1 100644 --- a/src/pkgmgr/actions/release/workflow.py +++ b/src/pkgmgr/actions/release/workflow.py @@ -76,7 +76,9 @@ def _release_impl( if paths.arch_pkgbuild: update_pkgbuild_version(paths.arch_pkgbuild, new_ver_str, preview=preview) else: - print("[INFO] No PKGBUILD found (packaging/arch/PKGBUILD or PKGBUILD). Skipping.") + print( + "[INFO] No PKGBUILD found (packaging/arch/PKGBUILD or PKGBUILD). Skipping." + ) if paths.rpm_spec: update_spec_version(paths.rpm_spec, new_ver_str, preview=preview) @@ -123,7 +125,9 @@ def _release_impl( paths.rpm_spec, paths.debian_changelog, ] - existing_files = [p for p in files_to_add if isinstance(p, str) and p and os.path.exists(p)] + existing_files = [ + p for p in files_to_add if isinstance(p, str) and p and os.path.exists(p) + ] if preview: add(existing_files, preview=True) @@ -135,13 +139,17 @@ def _release_impl( if is_highest_version_tag(new_tag): update_latest_tag(new_tag, preview=True) else: - print(f"[PREVIEW] Skipping 'latest' update (tag {new_tag} is not the highest).") + print( + f"[PREVIEW] Skipping 'latest' update (tag {new_tag} is not the highest)." + ) if close and branch not in ("main", "master"): if force: print(f"[PREVIEW] Would delete branch {branch} (forced).") else: - print(f"[PREVIEW] Would ask whether to delete branch {branch} after release.") + print( + f"[PREVIEW] Would ask whether to delete branch {branch} after release." + ) return add(existing_files, preview=False) @@ -157,7 +165,9 @@ def _release_impl( if is_highest_version_tag(new_tag): update_latest_tag(new_tag, preview=False) else: - print(f"[INFO] Skipping 'latest' update (tag {new_tag} is not the highest).") + print( + f"[INFO] Skipping 'latest' update (tag {new_tag} is not the highest)." + ) except GitRunError as exc: print(f"[WARN] Failed to update floating 'latest' tag for {new_tag}: {exc}") print("'latest' tag was not updated.") @@ -166,7 +176,9 @@ def _release_impl( if close: if branch in ("main", "master"): - print(f"[INFO] close=True but current branch is {branch}; skipping branch deletion.") + print( + f"[INFO] close=True but current branch is {branch}; skipping branch deletion." + ) return if not should_delete_branch(force=force): diff --git a/src/pkgmgr/actions/repository/clone.py b/src/pkgmgr/actions/repository/clone.py index 3e408c2..5dcc326 100644 --- a/src/pkgmgr/actions/repository/clone.py +++ b/src/pkgmgr/actions/repository/clone.py @@ -55,7 +55,9 @@ def clone_repos( clone_url = _build_clone_url(repo, clone_mode) if not clone_url: - print(f"[WARNING] Cannot build clone URL for '{repo_identifier}'. Skipping.") + print( + f"[WARNING] Cannot build clone URL for '{repo_identifier}'. Skipping." + ) continue shallow = clone_mode == "shallow" @@ -84,7 +86,11 @@ def clone_repos( continue print(f"[WARNING] SSH clone failed for '{repo_identifier}': {exc}") - choice = input("Do you want to attempt HTTPS clone instead? (y/N): ").strip().lower() + choice = ( + input("Do you want to attempt HTTPS clone instead? (y/N): ") + .strip() + .lower() + ) if choice != "y": print(f"[INFO] HTTPS clone not attempted for '{repo_identifier}'.") continue diff --git a/src/pkgmgr/actions/repository/create/parser.py b/src/pkgmgr/actions/repository/create/parser.py index 9bc90b3..ef789cb 100644 --- a/src/pkgmgr/actions/repository/create/parser.py +++ b/src/pkgmgr/actions/repository/create/parser.py @@ -63,6 +63,4 @@ def _strip_git_suffix(name: str) -> str: def _ensure_valid_repo_name(name: str) -> None: if not _NAME_RE.fullmatch(name): - raise ValueError( - "Repository name must match: lowercase a-z, 0-9, '_' and '-'." - ) + raise ValueError("Repository name must match: lowercase a-z, 0-9, '_' and '-'.") diff --git a/src/pkgmgr/actions/repository/create/templates.py b/src/pkgmgr/actions/repository/create/templates.py index 967650e..8d566b2 100644 --- a/src/pkgmgr/actions/repository/create/templates.py +++ b/src/pkgmgr/actions/repository/create/templates.py @@ -66,9 +66,7 @@ class TemplateRenderer: for root, _, files in os.walk(self.templates_dir): for fn in files: if fn.endswith(".j2"): - rel = os.path.relpath( - os.path.join(root, fn), self.templates_dir - ) + rel = os.path.relpath(os.path.join(root, fn), self.templates_dir) print(f"[Preview] Would render template: {rel} -> {rel[:-3]}") @staticmethod diff --git a/src/pkgmgr/actions/repository/deinstall.py b/src/pkgmgr/actions/repository/deinstall.py index 95e526f..921b847 100644 --- a/src/pkgmgr/actions/repository/deinstall.py +++ b/src/pkgmgr/actions/repository/deinstall.py @@ -24,9 +24,13 @@ def deinstall_repos( # Remove alias link/file (interactive) if os.path.exists(alias_path): - confirm = input( - f"Are you sure you want to delete link '{alias_path}' for {repo_identifier}? [y/N]: " - ).strip().lower() + confirm = ( + input( + f"Are you sure you want to delete link '{alias_path}' for {repo_identifier}? [y/N]: " + ) + .strip() + .lower() + ) if confirm == "y": if preview: print(f"[Preview] Would remove link '{alias_path}'.") diff --git a/src/pkgmgr/actions/repository/delete.py b/src/pkgmgr/actions/repository/delete.py index 9930f9b..19caacd 100644 --- a/src/pkgmgr/actions/repository/delete.py +++ b/src/pkgmgr/actions/repository/delete.py @@ -3,22 +3,33 @@ import os from pkgmgr.core.repository.identifier import get_repo_identifier from pkgmgr.core.repository.dir import get_repo_dir + def delete_repos(selected_repos, repositories_base_dir, all_repos, preview=False): for repo in selected_repos: repo_identifier = get_repo_identifier(repo, all_repos) repo_dir = get_repo_dir(repositories_base_dir, repo) if os.path.exists(repo_dir): - confirm = input(f"Are you sure you want to delete directory '{repo_dir}' for {repo_identifier}? [y/N]: ").strip().lower() + confirm = ( + input( + f"Are you sure you want to delete directory '{repo_dir}' for {repo_identifier}? [y/N]: " + ) + .strip() + .lower() + ) if confirm == "y": if preview: - print(f"[Preview] Would delete directory '{repo_dir}' for {repo_identifier}.") + print( + f"[Preview] Would delete directory '{repo_dir}' for {repo_identifier}." + ) else: try: shutil.rmtree(repo_dir) - print(f"Deleted repository directory '{repo_dir}' for {repo_identifier}.") + print( + f"Deleted repository directory '{repo_dir}' for {repo_identifier}." + ) except Exception as e: print(f"Error deleting '{repo_dir}' for {repo_identifier}: {e}") else: print(f"Skipped deletion of '{repo_dir}' for {repo_identifier}.") else: - print(f"Repository directory '{repo_dir}' not found for {repo_identifier}.") \ No newline at end of file + print(f"Repository directory '{repo_dir}' not found for {repo_identifier}.") diff --git a/src/pkgmgr/actions/repository/list.py b/src/pkgmgr/actions/repository/list.py index 08393ab..f3c401f 100644 --- a/src/pkgmgr/actions/repository/list.py +++ b/src/pkgmgr/actions/repository/list.py @@ -233,9 +233,7 @@ def list_repositories( categories.append(str(repo["category"])) yaml_tags: List[str] = list(map(str, repo.get("tags", []))) - display_tags: List[str] = sorted( - set(yaml_tags + list(map(str, extra_tags))) - ) + display_tags: List[str] = sorted(set(yaml_tags + list(map(str, extra_tags)))) rows.append( { @@ -288,13 +286,7 @@ def list_repositories( status_padded = status.ljust(status_width) status_colored = _color_status(status_padded) - print( - f"{ident_col} " - f"{status_colored} " - f"{cat_col} " - f"{tag_col} " - f"{dir_col}" - ) + print(f"{ident_col} {status_colored} {cat_col} {tag_col} {dir_col}") # ------------------------------------------------------------------ # Detailed section (alias value red, same status coloring) diff --git a/src/pkgmgr/actions/update/manager.py b/src/pkgmgr/actions/update/manager.py index 016d6ea..d5ae220 100644 --- a/src/pkgmgr/actions/update/manager.py +++ b/src/pkgmgr/actions/update/manager.py @@ -55,12 +55,16 @@ class UpdateManager: code = exc.code if isinstance(exc.code, int) else str(exc.code) failures.append((identifier, f"pull failed (exit={code})")) if not quiet: - print(f"[Warning] update: pull failed for {identifier} (exit={code}). Continuing...") + print( + f"[Warning] update: pull failed for {identifier} (exit={code}). Continuing..." + ) continue except Exception as exc: failures.append((identifier, f"pull failed: {exc}")) if not quiet: - print(f"[Warning] update: pull failed for {identifier}: {exc}. Continuing...") + print( + f"[Warning] update: pull failed for {identifier}: {exc}. Continuing..." + ) continue try: @@ -82,12 +86,16 @@ class UpdateManager: code = exc.code if isinstance(exc.code, int) else str(exc.code) failures.append((identifier, f"install failed (exit={code})")) if not quiet: - print(f"[Warning] update: install failed for {identifier} (exit={code}). Continuing...") + print( + f"[Warning] update: install failed for {identifier} (exit={code}). Continuing..." + ) continue except Exception as exc: failures.append((identifier, f"install failed: {exc}")) if not quiet: - print(f"[Warning] update: install failed for {identifier}: {exc}. Continuing...") + print( + f"[Warning] update: install failed for {identifier}: {exc}. Continuing..." + ) continue if failures and not quiet: diff --git a/src/pkgmgr/actions/update/os_release.py b/src/pkgmgr/actions/update/os_release.py index 83d3a6b..175f172 100644 --- a/src/pkgmgr/actions/update/os_release.py +++ b/src/pkgmgr/actions/update/os_release.py @@ -31,6 +31,7 @@ class OSReleaseInfo: """ Minimal /etc/os-release representation for distro detection. """ + id: str = "" id_like: str = "" pretty_name: str = "" @@ -63,4 +64,6 @@ class OSReleaseInfo: def is_fedora_family(self) -> bool: ids = self.ids() - return bool(ids.intersection({"fedora", "rhel", "centos", "rocky", "almalinux"})) + return bool( + ids.intersection({"fedora", "rhel", "centos", "rocky", "almalinux"}) + ) diff --git a/src/pkgmgr/actions/update/system_updater.py b/src/pkgmgr/actions/update/system_updater.py index e215a29..5358f98 100644 --- a/src/pkgmgr/actions/update/system_updater.py +++ b/src/pkgmgr/actions/update/system_updater.py @@ -58,7 +58,9 @@ class SystemUpdater: run_command("sudo pacman -Syu --noconfirm", preview=preview) return - print("[Warning] Cannot update Arch system: missing required tools (sudo/yay/pacman).") + print( + "[Warning] Cannot update Arch system: missing required tools (sudo/yay/pacman)." + ) def _update_debian(self, *, preview: bool) -> None: from pkgmgr.core.command.run import run_command @@ -67,7 +69,9 @@ class SystemUpdater: apt_get = shutil.which("apt-get") if not (sudo and apt_get): - print("[Warning] Cannot update Debian/Ubuntu system: missing required tools (sudo/apt-get).") + print( + "[Warning] Cannot update Debian/Ubuntu system: missing required tools (sudo/apt-get)." + ) return env = "DEBIAN_FRONTEND=noninteractive" diff --git a/src/pkgmgr/cli/__init__.py b/src/pkgmgr/cli/__init__.py index 13b3337..5f7e7dd 100644 --- a/src/pkgmgr/cli/__init__.py +++ b/src/pkgmgr/cli/__init__.py @@ -29,6 +29,7 @@ For details on any command, run: \033[1mpkgmgr --help\033[0m """ + def main() -> None: """ Entry point for the pkgmgr CLI. @@ -41,9 +42,7 @@ def main() -> None: repositories_dir = os.path.expanduser( directories.get("repositories", "~/Repositories") ) - binaries_dir = os.path.expanduser( - directories.get("binaries", "~/.local/bin") - ) + binaries_dir = os.path.expanduser(directories.get("binaries", "~/.local/bin")) # Ensure the merged config actually contains the resolved directories config_merged.setdefault("directories", {}) diff --git a/src/pkgmgr/cli/commands/changelog.py b/src/pkgmgr/cli/commands/changelog.py index e06cef8..baa959f 100644 --- a/src/pkgmgr/cli/commands/changelog.py +++ b/src/pkgmgr/cli/commands/changelog.py @@ -135,9 +135,7 @@ def handle_changelog( target_tag=range_arg, ) if cur_tag is None: - print( - f"[WARN] Tag {range_arg!r} not found or not a SemVer tag." - ) + print(f"[WARN] Tag {range_arg!r} not found or not a SemVer tag.") print("[INFO] Falling back to full history.") from_ref = None to_ref = None diff --git a/src/pkgmgr/cli/commands/config.py b/src/pkgmgr/cli/commands/config.py index 93ab621..74efdf2 100644 --- a/src/pkgmgr/cli/commands/config.py +++ b/src/pkgmgr/cli/commands/config.py @@ -213,9 +213,7 @@ def handle_config(args, ctx: CLIContext) -> None: ) if key == mod_key: entry["ignore"] = args.set == "true" - print( - f"Set ignore for {key} to {entry['ignore']}" - ) + print(f"Set ignore for {key} to {entry['ignore']}") save_user_config(user_config, user_config_path) return diff --git a/src/pkgmgr/cli/commands/mirror.py b/src/pkgmgr/cli/commands/mirror.py index 3e97e0d..907ec08 100644 --- a/src/pkgmgr/cli/commands/mirror.py +++ b/src/pkgmgr/cli/commands/mirror.py @@ -4,7 +4,12 @@ from __future__ import annotations import sys from typing import Any, Dict, List -from pkgmgr.actions.mirror import diff_mirrors, list_mirrors, merge_mirrors, setup_mirrors +from pkgmgr.actions.mirror import ( + diff_mirrors, + list_mirrors, + merge_mirrors, + setup_mirrors, +) from pkgmgr.cli.context import CLIContext Repository = Dict[str, Any] @@ -56,11 +61,15 @@ def handle_mirror_command( preview = getattr(args, "preview", False) if source == target: - print("[ERROR] For 'mirror merge', source and target must differ (config vs file).") + print( + "[ERROR] For 'mirror merge', source and target must differ (config vs file)." + ) sys.exit(2) explicit_config_path = getattr(args, "config_path", None) - user_config_path = explicit_config_path or getattr(ctx, "user_config_path", None) + user_config_path = explicit_config_path or getattr( + ctx, "user_config_path", None + ) merge_mirrors( selected_repos=selected, diff --git a/src/pkgmgr/cli/commands/publish.py b/src/pkgmgr/cli/commands/publish.py index fa71b00..d3f0203 100644 --- a/src/pkgmgr/cli/commands/publish.py +++ b/src/pkgmgr/cli/commands/publish.py @@ -18,7 +18,9 @@ def handle_publish(args, ctx: CLIContext, selected: List[Repository]) -> None: for repo in selected: identifier = get_repo_identifier(repo, ctx.all_repositories) - repo_dir = repo.get("directory") or get_repo_dir(ctx.repositories_base_dir, repo) + repo_dir = repo.get("directory") or get_repo_dir( + ctx.repositories_base_dir, repo + ) if not os.path.isdir(repo_dir): print(f"[WARN] Skipping {identifier}: directory missing.") diff --git a/src/pkgmgr/cli/commands/release.py b/src/pkgmgr/cli/commands/release.py index 84a1a73..9f6b08f 100644 --- a/src/pkgmgr/cli/commands/release.py +++ b/src/pkgmgr/cli/commands/release.py @@ -36,9 +36,13 @@ def handle_release( identifier = get_repo_identifier(repo, ctx.all_repositories) try: - repo_dir = repo.get("directory") or get_repo_dir(ctx.repositories_base_dir, repo) + repo_dir = repo.get("directory") or get_repo_dir( + ctx.repositories_base_dir, repo + ) except Exception as exc: - print(f"[WARN] Skipping repository {identifier}: failed to resolve directory: {exc}") + print( + f"[WARN] Skipping repository {identifier}: failed to resolve directory: {exc}" + ) continue if not os.path.isdir(repo_dir): diff --git a/src/pkgmgr/cli/commands/repos.py b/src/pkgmgr/cli/commands/repos.py index 37f187e..f52467c 100644 --- a/src/pkgmgr/cli/commands/repos.py +++ b/src/pkgmgr/cli/commands/repos.py @@ -32,9 +32,8 @@ def _resolve_repository_directory(repository: Repository, ctx: CLIContext) -> st if repo_dir: return repo_dir - base_dir = ( - getattr(ctx, "repositories_base_dir", None) - or getattr(ctx, "repositories_dir", None) + base_dir = getattr(ctx, "repositories_base_dir", None) or getattr( + ctx, "repositories_dir", None ) if not base_dir: raise RuntimeError( diff --git a/src/pkgmgr/cli/commands/version.py b/src/pkgmgr/cli/commands/version.py index 66b8d8f..a388554 100644 --- a/src/pkgmgr/cli/commands/version.py +++ b/src/pkgmgr/cli/commands/version.py @@ -38,9 +38,9 @@ def _print_pkgmgr_self_version() -> None: # Common distribution/module naming variants. python_candidates = [ - "package-manager", # PyPI dist name in your project - "package_manager", # module-ish variant - "pkgmgr", # console/alias-ish + "package-manager", # PyPI dist name in your project + "package_manager", # module-ish variant + "pkgmgr", # console/alias-ish ] nix_candidates = [ "pkgmgr", diff --git a/src/pkgmgr/cli/parser/branch_cmd.py b/src/pkgmgr/cli/parser/branch_cmd.py index 1a181a1..f9ab028 100644 --- a/src/pkgmgr/cli/parser/branch_cmd.py +++ b/src/pkgmgr/cli/parser/branch_cmd.py @@ -33,8 +33,7 @@ def add_branch_subparsers( "name", nargs="?", help=( - "Name of the new branch (optional; will be asked interactively " - "if omitted)" + "Name of the new branch (optional; will be asked interactively if omitted)" ), ) branch_open.add_argument( @@ -54,8 +53,7 @@ def add_branch_subparsers( "name", nargs="?", help=( - "Name of the branch to close (optional; current branch is used " - "if omitted)" + "Name of the branch to close (optional; current branch is used if omitted)" ), ) branch_close.add_argument( @@ -84,8 +82,7 @@ def add_branch_subparsers( "name", nargs="?", help=( - "Name of the branch to drop (optional; current branch is used " - "if omitted)" + "Name of the branch to drop (optional; current branch is used if omitted)" ), ) branch_drop.add_argument( diff --git a/src/pkgmgr/cli/parser/mirror_cmd.py b/src/pkgmgr/cli/parser/mirror_cmd.py index 85d3648..b467570 100644 --- a/src/pkgmgr/cli/parser/mirror_cmd.py +++ b/src/pkgmgr/cli/parser/mirror_cmd.py @@ -20,7 +20,9 @@ def add_mirror_subparsers(subparsers: argparse._SubParsersAction) -> None: required=True, ) - mirror_list = mirror_subparsers.add_parser("list", help="List configured mirrors for repositories") + mirror_list = mirror_subparsers.add_parser( + "list", help="List configured mirrors for repositories" + ) add_identifier_arguments(mirror_list) mirror_list.add_argument( "--source", @@ -29,15 +31,21 @@ def add_mirror_subparsers(subparsers: argparse._SubParsersAction) -> None: help="Which mirror source to show.", ) - mirror_diff = mirror_subparsers.add_parser("diff", help="Show differences between config mirrors and MIRRORS file") + mirror_diff = mirror_subparsers.add_parser( + "diff", help="Show differences between config mirrors and MIRRORS file" + ) add_identifier_arguments(mirror_diff) mirror_merge = mirror_subparsers.add_parser( "merge", help="Merge mirrors between config and MIRRORS file (example: pkgmgr mirror merge config file --all)", ) - mirror_merge.add_argument("source", choices=["config", "file"], help="Source of mirrors.") - mirror_merge.add_argument("target", choices=["config", "file"], help="Target of mirrors.") + mirror_merge.add_argument( + "source", choices=["config", "file"], help="Source of mirrors." + ) + mirror_merge.add_argument( + "target", choices=["config", "file"], help="Target of mirrors." + ) add_identifier_arguments(mirror_merge) mirror_merge.add_argument( "--config-path", diff --git a/src/pkgmgr/cli/parser/navigation_cmd.py b/src/pkgmgr/cli/parser/navigation_cmd.py index 364a620..f449181 100644 --- a/src/pkgmgr/cli/parser/navigation_cmd.py +++ b/src/pkgmgr/cli/parser/navigation_cmd.py @@ -48,9 +48,6 @@ def add_navigation_subparsers( "--command", nargs=argparse.REMAINDER, dest="shell_command", - help=( - "The shell command (and its arguments) to execute in each " - "repository" - ), + help=("The shell command (and its arguments) to execute in each repository"), default=[], ) diff --git a/src/pkgmgr/cli/proxy.py b/src/pkgmgr/cli/proxy.py index a6d3469..d218c4b 100644 --- a/src/pkgmgr/cli/proxy.py +++ b/src/pkgmgr/cli/proxy.py @@ -53,10 +53,7 @@ def _add_proxy_identifier_arguments(parser: argparse.ArgumentParser) -> None: parser.add_argument( "identifiers", nargs="*", - help=( - "Identifier(s) for repositories. " - "Default: Repository of current folder." - ), + help=("Identifier(s) for repositories. Default: Repository of current folder."), ) parser.add_argument( "--all", @@ -118,12 +115,7 @@ def _proxy_has_explicit_selection(args: argparse.Namespace) -> bool: string_filter = getattr(args, "string", "") or "" # Proxy commands currently do not support --tag, so it is not checked here. - return bool( - use_all - or identifiers - or categories - or string_filter - ) + return bool(use_all or identifiers or categories or string_filter) def _select_repo_for_current_directory( @@ -204,9 +196,7 @@ def maybe_handle_proxy(args: argparse.Namespace, ctx: CLIContext) -> bool: If the top-level command is one of the proxy subcommands (git / docker / docker compose), handle it here and return True. """ - all_proxy_subcommands = { - sub for subs in PROXY_COMMANDS.values() for sub in subs - } + all_proxy_subcommands = {sub for subs in PROXY_COMMANDS.values() for sub in subs} if args.command not in all_proxy_subcommands: return False diff --git a/src/pkgmgr/cli/tools/paths.py b/src/pkgmgr/cli/tools/paths.py index 4394cbc..174e371 100644 --- a/src/pkgmgr/cli/tools/paths.py +++ b/src/pkgmgr/cli/tools/paths.py @@ -22,9 +22,8 @@ def resolve_repository_path(repository: Repository, ctx: CLIContext) -> str: if value: return value - base_dir = ( - getattr(ctx, "repositories_base_dir", None) - or getattr(ctx, "repositories_dir", None) + base_dir = getattr(ctx, "repositories_base_dir", None) or getattr( + ctx, "repositories_dir", None ) if not base_dir: raise RuntimeError( diff --git a/src/pkgmgr/cli/tools/vscode.py b/src/pkgmgr/cli/tools/vscode.py index c632b29..2f0065d 100644 --- a/src/pkgmgr/cli/tools/vscode.py +++ b/src/pkgmgr/cli/tools/vscode.py @@ -57,7 +57,9 @@ def _build_workspace_filename(identifiers: List[str]) -> str: return "_".join(sorted_identifiers) + ".code-workspace" -def _build_workspace_data(selected: List[Repository], ctx: CLIContext) -> Dict[str, Any]: +def _build_workspace_data( + selected: List[Repository], ctx: CLIContext +) -> Dict[str, Any]: folders = [{"path": resolve_repository_path(repo, ctx)} for repo in selected] return { "folders": folders, diff --git a/src/pkgmgr/core/command/alias.py b/src/pkgmgr/core/command/alias.py index 3c0996a..d03b132 100644 --- a/src/pkgmgr/core/command/alias.py +++ b/src/pkgmgr/core/command/alias.py @@ -2,10 +2,11 @@ import os import hashlib import re + def generate_alias(repo, bin_dir, existing_aliases): """ Generate an alias for a repository based on its repository name. - + Steps: 1. Keep only consonants from the repository name (letters from BCDFGHJKLMNPQRSTVWXYZ). 2. Collapse consecutive identical consonants. @@ -39,4 +40,4 @@ def generate_alias(repo, bin_dir, existing_aliases): while conflict(candidate3): candidate3 += "x" candidate3 = candidate3[:12] - return candidate3 \ No newline at end of file + return candidate3 diff --git a/src/pkgmgr/core/command/ink.py b/src/pkgmgr/core/command/ink.py index 91c8ddb..adb5dac 100644 --- a/src/pkgmgr/core/command/ink.py +++ b/src/pkgmgr/core/command/ink.py @@ -98,8 +98,7 @@ def create_ink( if alias_name == repo_identifier: if not quiet: print( - f"Alias '{alias_name}' equals identifier. " - "Skipping alias creation." + f"Alias '{alias_name}' equals identifier. Skipping alias creation." ) return diff --git a/src/pkgmgr/core/command/layer.py b/src/pkgmgr/core/command/layer.py index df3b33b..2b49c93 100644 --- a/src/pkgmgr/core/command/layer.py +++ b/src/pkgmgr/core/command/layer.py @@ -8,6 +8,7 @@ class CliLayer(str, Enum): """ CLI layer precedence (lower number = stronger layer). """ + OS_PACKAGES = "os-packages" NIX = "nix" PYTHON = "python" diff --git a/src/pkgmgr/core/command/resolve.py b/src/pkgmgr/core/command/resolve.py index 01dad82..e293c9b 100644 --- a/src/pkgmgr/core/command/resolve.py +++ b/src/pkgmgr/core/command/resolve.py @@ -34,11 +34,7 @@ def _nix_binary_candidates(home: str, names: List[str]) -> List[str]: """ Build possible Nix profile binary paths for a list of candidate names. """ - return [ - os.path.join(home, ".nix-profile", "bin", name) - for name in names - if name - ] + return [os.path.join(home, ".nix-profile", "bin", name) for name in names if name] def _path_binary_candidates(names: List[str]) -> List[str]: @@ -148,7 +144,8 @@ def resolve_command_for_repo( # c) Nix profile binaries nix_binaries = [ - path for path in _nix_binary_candidates(home, candidate_names) + path + for path in _nix_binary_candidates(home, candidate_names) if _is_executable(path) ] nix_binary = nix_binaries[0] if nix_binaries else None diff --git a/src/pkgmgr/core/config/load.py b/src/pkgmgr/core/config/load.py index 34571e0..986a6f3 100644 --- a/src/pkgmgr/core/config/load.py +++ b/src/pkgmgr/core/config/load.py @@ -51,6 +51,7 @@ Repo = Dict[str, Any] # Hilfsfunktionen # --------------------------------------------------------------------------- + def _deep_merge(base: Dict[str, Any], override: Dict[str, Any]) -> Dict[str, Any]: """ Recursively merge two dictionaries. @@ -58,11 +59,7 @@ def _deep_merge(base: Dict[str, Any], override: Dict[str, Any]) -> Dict[str, Any Values from `override` win over values in `base`. """ for key, value in override.items(): - if ( - key in base - and isinstance(base[key], dict) - and isinstance(value, dict) - ): + if key in base and isinstance(base[key], dict) and isinstance(value, dict): _deep_merge(base[key], value) else: base[key] = value @@ -93,9 +90,7 @@ def _merge_repo_lists( - Wenn category_name gesetzt ist, wird dieser in repo["category_files"] eingetragen. """ - index: Dict[Tuple[str, str, str], Repo] = { - _repo_key(r): r for r in base_list - } + index: Dict[Tuple[str, str, str], Repo] = {_repo_key(r): r for r in base_list} for src in new_list: key = _repo_key(src) @@ -233,10 +228,12 @@ def _load_defaults_from_package_or_project() -> Dict[str, Any]: return {"directories": {}, "repositories": []} + # --------------------------------------------------------------------------- # Hauptfunktion # --------------------------------------------------------------------------- + def load_config(user_config_path: str) -> Dict[str, Any]: """ Load and merge configuration for pkgmgr. @@ -289,8 +286,12 @@ def load_config(user_config_path: str) -> Dict[str, Any]: # repositories merged["repositories"] = [] - _merge_repo_lists(merged["repositories"], defaults["repositories"], category_name=None) - _merge_repo_lists(merged["repositories"], user_cfg["repositories"], category_name=None) + _merge_repo_lists( + merged["repositories"], defaults["repositories"], category_name=None + ) + _merge_repo_lists( + merged["repositories"], user_cfg["repositories"], category_name=None + ) # andere Top-Level-Keys (falls vorhanden) other_keys = (set(defaults.keys()) | set(user_cfg.keys())) - { diff --git a/src/pkgmgr/core/config/save.py b/src/pkgmgr/core/config/save.py index 0556b4b..d2dfc56 100644 --- a/src/pkgmgr/core/config/save.py +++ b/src/pkgmgr/core/config/save.py @@ -1,9 +1,10 @@ import yaml import os -def save_user_config(user_config,USER_CONFIG_PATH:str): + +def save_user_config(user_config, USER_CONFIG_PATH: str): """Save the user configuration to USER_CONFIG_PATH.""" os.makedirs(os.path.dirname(USER_CONFIG_PATH), exist_ok=True) - with open(USER_CONFIG_PATH, 'w') as f: + with open(USER_CONFIG_PATH, "w") as f: yaml.dump(user_config, f) - print(f"User configuration updated in {USER_CONFIG_PATH}.") \ No newline at end of file + print(f"User configuration updated in {USER_CONFIG_PATH}.") diff --git a/src/pkgmgr/core/credentials/providers/env.py b/src/pkgmgr/core/credentials/providers/env.py index 6e0a857..7f2a8b8 100644 --- a/src/pkgmgr/core/credentials/providers/env.py +++ b/src/pkgmgr/core/credentials/providers/env.py @@ -16,7 +16,9 @@ class EnvTokenProvider: source_name: str = "env" def get(self, request: TokenRequest) -> Optional[TokenResult]: - for key in env_var_candidates(request.provider_kind, request.host, request.owner): + for key in env_var_candidates( + request.provider_kind, request.host, request.owner + ): val = os.environ.get(key) if val: return TokenResult(token=val.strip(), source=self.source_name) diff --git a/src/pkgmgr/core/credentials/providers/gh.py b/src/pkgmgr/core/credentials/providers/gh.py index 7140542..7ad2973 100644 --- a/src/pkgmgr/core/credentials/providers/gh.py +++ b/src/pkgmgr/core/credentials/providers/gh.py @@ -15,6 +15,7 @@ class GhTokenProvider: This does NOT persist anything; it only reads what `gh` already knows. """ + source_name: str = "gh" def get(self, request: TokenRequest) -> Optional[TokenResult]: diff --git a/src/pkgmgr/core/credentials/providers/keyring.py b/src/pkgmgr/core/credentials/providers/keyring.py index 4a98ba3..0e3fd8f 100644 --- a/src/pkgmgr/core/credentials/providers/keyring.py +++ b/src/pkgmgr/core/credentials/providers/keyring.py @@ -21,9 +21,7 @@ def _import_keyring(): try: import keyring # type: ignore except Exception as exc: # noqa: BLE001 - raise KeyringUnavailableError( - "python-keyring is not installed." - ) from exc + raise KeyringUnavailableError("python-keyring is not installed.") from exc # Some environments have keyring installed but no usable backend. # We do a lightweight "backend sanity check" by attempting to read the backend. diff --git a/src/pkgmgr/core/credentials/resolver.py b/src/pkgmgr/core/credentials/resolver.py index ee45b4a..7d45690 100644 --- a/src/pkgmgr/core/credentials/resolver.py +++ b/src/pkgmgr/core/credentials/resolver.py @@ -9,7 +9,12 @@ from .providers.env import EnvTokenProvider from .providers.gh import GhTokenProvider from .providers.keyring import KeyringTokenProvider from .providers.prompt import PromptTokenProvider -from .types import KeyringUnavailableError, NoCredentialsError, TokenRequest, TokenResult +from .types import ( + KeyringUnavailableError, + NoCredentialsError, + TokenRequest, + TokenResult, +) from .validate import validate_token @@ -55,7 +60,10 @@ class TokenResolver: print(f" {msg}", file=sys.stderr) print(" Tokens will NOT be persisted securely.", file=sys.stderr) print("", file=sys.stderr) - print(" To enable secure token storage, install python-keyring:", file=sys.stderr) + print( + " To enable secure token storage, install python-keyring:", + file=sys.stderr, + ) print(" pip install keyring", file=sys.stderr) print("", file=sys.stderr) print(" Or install via system packages:", file=sys.stderr) diff --git a/src/pkgmgr/core/credentials/store_keys.py b/src/pkgmgr/core/credentials/store_keys.py index ff8bdff..e0510c2 100644 --- a/src/pkgmgr/core/credentials/store_keys.py +++ b/src/pkgmgr/core/credentials/store_keys.py @@ -13,7 +13,9 @@ class KeyringKey: username: str -def build_keyring_key(provider_kind: str, host: str, owner: Optional[str]) -> KeyringKey: +def build_keyring_key( + provider_kind: str, host: str, owner: Optional[str] +) -> KeyringKey: """Build a stable keyring key. - service: "pkgmgr:" @@ -21,11 +23,15 @@ def build_keyring_key(provider_kind: str, host: str, owner: Optional[str]) -> Ke """ provider_kind = str(provider_kind).strip().lower() host = str(host).strip() - owner_part = (str(owner).strip() if owner else "-") - return KeyringKey(service=f"pkgmgr:{provider_kind}", username=f"{host}|{owner_part}") + owner_part = str(owner).strip() if owner else "-" + return KeyringKey( + service=f"pkgmgr:{provider_kind}", username=f"{host}|{owner_part}" + ) -def env_var_candidates(provider_kind: str, host: str, owner: Optional[str]) -> list[str]: +def env_var_candidates( + provider_kind: str, host: str, owner: Optional[str] +) -> list[str]: """Return a list of environment variable names to try. Order is from most specific to most generic. @@ -44,7 +50,7 @@ def env_var_candidates(provider_kind: str, host: str, owner: Optional[str]) -> l candidates.append(f"PKGMGR_{kind}_TOKEN") candidates.append(f"PKGMGR_TOKEN_{kind}") candidates.append("PKGMGR_TOKEN") - + return candidates diff --git a/src/pkgmgr/core/git/commands/add_all.py b/src/pkgmgr/core/git/commands/add_all.py index 5061b51..602e02d 100644 --- a/src/pkgmgr/core/git/commands/add_all.py +++ b/src/pkgmgr/core/git/commands/add_all.py @@ -18,4 +18,6 @@ def add_all(*, cwd: str = ".", preview: bool = False) -> None: try: run(["add", "-A"], cwd=cwd, preview=preview) except GitRunError as exc: - raise GitAddAllError("Failed to stage all changes with `git add -A`.", cwd=cwd) from exc + raise GitAddAllError( + "Failed to stage all changes with `git add -A`.", cwd=cwd + ) from exc diff --git a/src/pkgmgr/core/git/commands/branch_move.py b/src/pkgmgr/core/git/commands/branch_move.py index 62180bc..ea019d3 100644 --- a/src/pkgmgr/core/git/commands/branch_move.py +++ b/src/pkgmgr/core/git/commands/branch_move.py @@ -18,4 +18,6 @@ def branch_move(branch: str, *, cwd: str = ".", preview: bool = False) -> None: try: run(["branch", "-M", branch], cwd=cwd, preview=preview) except GitRunError as exc: - raise GitBranchMoveError(f"Failed to move/rename current branch to {branch!r}.", cwd=cwd) from exc + raise GitBranchMoveError( + f"Failed to move/rename current branch to {branch!r}.", cwd=cwd + ) from exc diff --git a/src/pkgmgr/core/git/errors.py b/src/pkgmgr/core/git/errors.py index 54ef96a..f11a5b7 100644 --- a/src/pkgmgr/core/git/errors.py +++ b/src/pkgmgr/core/git/errors.py @@ -4,21 +4,26 @@ from __future__ import annotations class GitBaseError(RuntimeError): """Base error raised for Git related failures.""" + class GitRunError(GitBaseError): """Base error raised for Git related failures.""" + class GitNotRepositoryError(GitBaseError): """Raised when the current working directory is not a git repository.""" + class GitQueryError(GitRunError): """Base class for read-only git query failures.""" + class GitCommandError(GitRunError): """ Base class for state-changing git command failures. Use subclasses to provide stable error types for callers. """ + def __init__(self, message: str, *, cwd: str = ".") -> None: super().__init__(message) if cwd in locals(): diff --git a/src/pkgmgr/core/git/queries/get_config_value.py b/src/pkgmgr/core/git/queries/get_config_value.py index 79d35ed..3869c3b 100644 --- a/src/pkgmgr/core/git/queries/get_config_value.py +++ b/src/pkgmgr/core/git/queries/get_config_value.py @@ -16,6 +16,7 @@ def _is_missing_key_error(exc: GitRunError) -> bool: # 'git config --get' returns exit code 1 when the key is not set. return "exit code: 1" in msg + def get_config_value(key: str, *, cwd: str = ".") -> Optional[str]: """ Return a value from `git config --get `, or None if not set. diff --git a/src/pkgmgr/core/git/queries/get_current_branch.py b/src/pkgmgr/core/git/queries/get_current_branch.py index e9a8263..bca5a68 100644 --- a/src/pkgmgr/core/git/queries/get_current_branch.py +++ b/src/pkgmgr/core/git/queries/get_current_branch.py @@ -15,4 +15,4 @@ def get_current_branch(cwd: str = ".") -> Optional[str]: output = run(["rev-parse", "--abbrev-ref", "HEAD"], cwd=cwd) except GitRunError: return None - return output or None \ No newline at end of file + return output or None diff --git a/src/pkgmgr/core/git/queries/get_remote_head_commit.py b/src/pkgmgr/core/git/queries/get_remote_head_commit.py index c056b12..9051d64 100644 --- a/src/pkgmgr/core/git/queries/get_remote_head_commit.py +++ b/src/pkgmgr/core/git/queries/get_remote_head_commit.py @@ -30,4 +30,4 @@ def get_remote_head_commit( ) from exc # minimal parsing: first token is the hash - return (out.split()[0].strip() if out else "") + return out.split()[0].strip() if out else "" diff --git a/src/pkgmgr/core/git/queries/get_remote_push_urls.py b/src/pkgmgr/core/git/queries/get_remote_push_urls.py index 77b779c..629ac47 100644 --- a/src/pkgmgr/core/git/queries/get_remote_push_urls.py +++ b/src/pkgmgr/core/git/queries/get_remote_push_urls.py @@ -4,6 +4,7 @@ from typing import Set from ..run import run + def get_remote_push_urls(remote: str, cwd: str = ".") -> Set[str]: """ Return all push URLs configured for a remote. diff --git a/src/pkgmgr/core/git/run.py b/src/pkgmgr/core/git/run.py index e9844a6..8100e5b 100644 --- a/src/pkgmgr/core/git/run.py +++ b/src/pkgmgr/core/git/run.py @@ -44,9 +44,7 @@ def run( stderr = exc.stderr or "" if _is_not_repo_error(stderr): raise GitNotRepositoryError( - f"Not a git repository: {cwd!r}\n" - f"Command: {cmd_str}\n" - f"STDERR:\n{stderr}" + f"Not a git repository: {cwd!r}\nCommand: {cmd_str}\nSTDERR:\n{stderr}" ) from exc raise GitRunError( diff --git a/src/pkgmgr/core/repository/dir.py b/src/pkgmgr/core/repository/dir.py index 9520d2e..3fc84fe 100644 --- a/src/pkgmgr/core/repository/dir.py +++ b/src/pkgmgr/core/repository/dir.py @@ -34,7 +34,15 @@ def get_repo_dir(repositories_base_dir: str, repo: Dict[str, Any]) -> str: account = repo.get("account") repository = repo.get("repository") - missing = [k for k, v in [("provider", provider), ("account", account), ("repository", repository)] if not v] + missing = [ + k + for k, v in [ + ("provider", provider), + ("account", account), + ("repository", repository), + ] + if not v + ] if missing: print( "Error: repository entry is missing required keys.\n" diff --git a/src/pkgmgr/core/repository/identifier.py b/src/pkgmgr/core/repository/identifier.py index e18a5e8..adfc9a5 100644 --- a/src/pkgmgr/core/repository/identifier.py +++ b/src/pkgmgr/core/repository/identifier.py @@ -9,4 +9,4 @@ def get_repo_identifier(repo, all_repos): if count == 1: return repo_name else: - return f'{repo.get("provider")}/{repo.get("account")}/{repo.get("repository")}' \ No newline at end of file + return f"{repo.get('provider')}/{repo.get('account')}/{repo.get('repository')}" diff --git a/src/pkgmgr/core/repository/ignored.py b/src/pkgmgr/core/repository/ignored.py index 75f911b..6cdc515 100644 --- a/src/pkgmgr/core/repository/ignored.py +++ b/src/pkgmgr/core/repository/ignored.py @@ -1,3 +1,3 @@ def filter_ignored(repos): """Filter out repositories that have 'ignore' set to True.""" - return [r for r in repos if not r.get("ignore", False)] \ No newline at end of file + return [r for r in repos if not r.get("ignore", False)] diff --git a/src/pkgmgr/core/repository/paths.py b/src/pkgmgr/core/repository/paths.py index d6777ef..8c6e68b 100644 --- a/src/pkgmgr/core/repository/paths.py +++ b/src/pkgmgr/core/repository/paths.py @@ -109,7 +109,9 @@ def resolve_repo_paths(repo_dir: str) -> RepoPaths: ] ) if rpm_spec is None: - rpm_spec = _find_first_spec_in_dir(os.path.join(repo_dir, "packaging", "fedora")) + rpm_spec = _find_first_spec_in_dir( + os.path.join(repo_dir, "packaging", "fedora") + ) if rpm_spec is None: rpm_spec = _find_first_spec_in_dir(repo_dir) diff --git a/src/pkgmgr/core/repository/resolve.py b/src/pkgmgr/core/repository/resolve.py index 683a1df..ce7c5fc 100644 --- a/src/pkgmgr/core/repository/resolve.py +++ b/src/pkgmgr/core/repository/resolve.py @@ -1,5 +1,4 @@ - -def resolve_repos(identifiers:[], all_repos:[]): +def resolve_repos(identifiers: [], all_repos: []): """ Given a list of identifier strings, return a list of repository configs. The identifier can be: @@ -11,7 +10,9 @@ def resolve_repos(identifiers:[], all_repos:[]): for ident in identifiers: matches = [] for repo in all_repos: - full_id = f'{repo.get("provider")}/{repo.get("account")}/{repo.get("repository")}' + full_id = ( + f"{repo.get('provider')}/{repo.get('account')}/{repo.get('repository')}" + ) if ident == full_id: matches.append(repo) elif ident == repo.get("alias"): @@ -24,4 +25,4 @@ def resolve_repos(identifiers:[], all_repos:[]): print(f"Identifier '{ident}' did not match any repository in config.") else: selected.extend(matches) - return selected \ No newline at end of file + return selected diff --git a/src/pkgmgr/core/repository/verify.py b/src/pkgmgr/core/repository/verify.py index c93f156..7bcbd6a 100644 --- a/src/pkgmgr/core/repository/verify.py +++ b/src/pkgmgr/core/repository/verify.py @@ -66,18 +66,26 @@ def verify_repository(repo, repo_dir, mode="local", no_verification=False): if expected_commit: if not commit_hash: commit_check_passed = False - error_details.append(f"Expected commit: {expected_commit}, but could not determine current commit.") + error_details.append( + f"Expected commit: {expected_commit}, but could not determine current commit." + ) elif commit_hash != expected_commit: commit_check_passed = False - error_details.append(f"Expected commit: {expected_commit}, found: {commit_hash}") + error_details.append( + f"Expected commit: {expected_commit}, found: {commit_hash}" + ) if expected_gpg_keys: if not signing_key: gpg_check_passed = False - error_details.append(f"Expected one of GPG keys: {expected_gpg_keys}, but no signing key was found.") + error_details.append( + f"Expected one of GPG keys: {expected_gpg_keys}, but no signing key was found." + ) elif signing_key not in expected_gpg_keys: gpg_check_passed = False - error_details.append(f"Expected one of GPG keys: {expected_gpg_keys}, found: {signing_key}") + error_details.append( + f"Expected one of GPG keys: {expected_gpg_keys}, found: {signing_key}" + ) if expected_commit and expected_gpg_keys: verified_ok = commit_check_passed and gpg_check_passed diff --git a/src/pkgmgr/core/version/installed.py b/src/pkgmgr/core/version/installed.py index 72b3ecd..2bcb5dd 100644 --- a/src/pkgmgr/core/version/installed.py +++ b/src/pkgmgr/core/version/installed.py @@ -13,6 +13,7 @@ class InstalledVersion: """ Represents a resolved installed version and the matched name. """ + name: str version: str diff --git a/src/pkgmgr/core/version/semver.py b/src/pkgmgr/core/version/semver.py index d55a1b9..c27337c 100644 --- a/src/pkgmgr/core/version/semver.py +++ b/src/pkgmgr/core/version/semver.py @@ -43,10 +43,14 @@ class SemVer: minor = int(parts[1]) patch = int(parts[2]) except ValueError as exc: - raise ValueError(f"Semantic version components must be integers: {value!r}") from exc + raise ValueError( + f"Semantic version components must be integers: {value!r}" + ) from exc if major < 0 or minor < 0 or patch < 0: - raise ValueError(f"Semantic version components must be non-negative: {value!r}") + raise ValueError( + f"Semantic version components must be non-negative: {value!r}" + ) return cls(major=major, minor=minor, patch=patch) diff --git a/tests/e2e/test_branch_commands.py b/tests/e2e/test_branch_commands.py index b06711c..656efb4 100644 --- a/tests/e2e/test_branch_commands.py +++ b/tests/e2e/test_branch_commands.py @@ -37,9 +37,7 @@ class TestIntegrationBranchCommands(unittest.TestCase): `pkgmgr branch open feature/test --base develop` must forward the name and base branch to open_branch() with cwd=".". """ - self._run_pkgmgr( - ["branch", "open", "feature/test", "--base", "develop"] - ) + self._run_pkgmgr(["branch", "open", "feature/test", "--base", "develop"]) mock_open_branch.assert_called_once() _, kwargs = mock_open_branch.call_args @@ -74,9 +72,7 @@ class TestIntegrationBranchCommands(unittest.TestCase): `pkgmgr branch close feature/test --base develop` must forward the name and base branch to close_branch() with cwd=".". """ - self._run_pkgmgr( - ["branch", "close", "feature/test", "--base", "develop"] - ) + self._run_pkgmgr(["branch", "close", "feature/test", "--base", "develop"]) mock_close_branch.assert_called_once() _, kwargs = mock_close_branch.call_args diff --git a/tests/e2e/test_install_makefile_three_times.py b/tests/e2e/test_install_makefile_three_times.py index 70641fc..cfc33b5 100644 --- a/tests/e2e/test_install_makefile_three_times.py +++ b/tests/e2e/test_install_makefile_three_times.py @@ -3,15 +3,14 @@ import tempfile import unittest from pathlib import Path + class TestMakefileThreeTimes(unittest.TestCase): def test_make_install_three_times(self): with tempfile.TemporaryDirectory(prefix="makefile-3x-") as tmp: repo = Path(tmp) # Minimal Makefile with install target - (repo / "Makefile").write_text( - "install:\n\t@echo install >> install.log\n" - ) + (repo / "Makefile").write_text("install:\n\t@echo install >> install.log\n") for i in range(1, 4): print(f"\n=== RUN {i}/3 ===") diff --git a/tests/e2e/test_install_pkgmgr_shallow.py b/tests/e2e/test_install_pkgmgr_shallow.py index ef0c035..c6484bd 100644 --- a/tests/e2e/test_install_pkgmgr_shallow.py +++ b/tests/e2e/test_install_pkgmgr_shallow.py @@ -114,7 +114,9 @@ class TestIntegrationInstalPKGMGRShallow(unittest.TestCase): # Optional XDG override for a fully isolated environment os.environ.setdefault("XDG_CONFIG_HOME", os.path.join(temp_home, ".config")) os.environ.setdefault("XDG_CACHE_HOME", os.path.join(temp_home, ".cache")) - os.environ.setdefault("XDG_DATA_HOME", os.path.join(temp_home, ".local", "share")) + os.environ.setdefault( + "XDG_DATA_HOME", os.path.join(temp_home, ".local", "share") + ) # 🔧 IMPORTANT FIX: allow Git to access /src safely configure_git_safe_directory() diff --git a/tests/e2e/test_install_pkgmgr_three_times_nix.py b/tests/e2e/test_install_pkgmgr_three_times_nix.py index 40c0ec9..dd97b88 100644 --- a/tests/e2e/test_install_pkgmgr_three_times_nix.py +++ b/tests/e2e/test_install_pkgmgr_three_times_nix.py @@ -14,17 +14,16 @@ class TestPkgmgrInstallThreeTimesNix(unittest.TestCase): env["HOME"] = tmp # Ensure nix is found - env["PATH"] = "/nix/var/nix/profiles/default/bin:" + os.environ.get("PATH", "") + env["PATH"] = "/nix/var/nix/profiles/default/bin:" + os.environ.get( + "PATH", "" + ) # IMPORTANT: # nix run uses git+file:///src internally -> Git will reject /src if it's not a safe.directory. # Our test sets HOME to a temp dir, so we must provide a temp global gitconfig. gitconfig = tmp_path / ".gitconfig" gitconfig.write_text( - "[safe]\n" - "\tdirectory = /src\n" - "\tdirectory = /src/.git\n" - "\tdirectory = *\n" + "[safe]\n\tdirectory = /src\n\tdirectory = /src/.git\n\tdirectory = *\n" ) env["GIT_CONFIG_GLOBAL"] = str(gitconfig) diff --git a/tests/e2e/test_install_pkgmgr_three_times_venv.py b/tests/e2e/test_install_pkgmgr_three_times_venv.py index 1e83dec..745e513 100644 --- a/tests/e2e/test_install_pkgmgr_three_times_venv.py +++ b/tests/e2e/test_install_pkgmgr_three_times_venv.py @@ -16,10 +16,8 @@ class TestPkgmgrInstallThreeTimesVenv(unittest.TestCase): env["HOME"] = tmp # pkgmgr kommt aus dem Projekt-venv - env["PATH"] = ( - f"{Path.cwd() / '.venv' / 'bin'}:" - f"{bin_dir}:" - + os.environ.get("PATH", "") + env["PATH"] = f"{Path.cwd() / '.venv' / 'bin'}:{bin_dir}:" + os.environ.get( + "PATH", "" ) # nix explizit deaktivieren → Python/Venv-Pfad diff --git a/tests/e2e/test_make_commands.py b/tests/e2e/test_make_commands.py index 8ca8ef1..8234ba6 100644 --- a/tests/e2e/test_make_commands.py +++ b/tests/e2e/test_make_commands.py @@ -69,9 +69,7 @@ class TestIntegrationMakeCommands(unittest.TestCase): - '--preview' ensures that no destructive make commands are actually executed inside the container. """ - self._run_pkgmgr_make( - ["make", "install", "--preview", "pkgmgr"] - ) + self._run_pkgmgr_make(["make", "install", "--preview", "pkgmgr"]) if __name__ == "__main__": diff --git a/tests/e2e/test_nix_build_pkgmgr.py b/tests/e2e/test_nix_build_pkgmgr.py index 75db3be..5e6c07d 100644 --- a/tests/e2e/test_nix_build_pkgmgr.py +++ b/tests/e2e/test_nix_build_pkgmgr.py @@ -24,9 +24,7 @@ import unittest # Resolve project root (the repo where flake.nix lives, e.g. /src) -PROJECT_ROOT = os.path.abspath( - os.path.join(os.path.dirname(__file__), "..", "..") -) +PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) def _run_cmd(cmd: list[str]) -> subprocess.CompletedProcess: @@ -69,13 +67,18 @@ class TestNixBuildPkgmgrAllDistros(unittest.TestCase): _run_cmd(["id"]) # --- nix build .#pkgmgr -L --- - proc = _run_cmd([ - "nix", - "--option", "sandbox", "false", - "build", ".#pkgmgr", - "-L", - ]) - + proc = _run_cmd( + [ + "nix", + "--option", + "sandbox", + "false", + "build", + ".#pkgmgr", + "-L", + ] + ) + if proc.returncode != 0: raise AssertionError( "nix build .#pkgmgr -L failed inside the test container.\n" diff --git a/tests/e2e/test_release_commands.py b/tests/e2e/test_release_commands.py index 564861c..7c7f68c 100644 --- a/tests/e2e/test_release_commands.py +++ b/tests/e2e/test_release_commands.py @@ -164,5 +164,6 @@ class TestIntegrationReleaseCommand(unittest.TestCase): self.assertIn("usage:", output) self.assertIn("pkgmgr release", output) + if __name__ == "__main__": unittest.main() diff --git a/tests/e2e/test_tools_help.py b/tests/e2e/test_tools_help.py index 9365b71..68cb440 100644 --- a/tests/e2e/test_tools_help.py +++ b/tests/e2e/test_tools_help.py @@ -17,6 +17,7 @@ import sys import unittest from typing import List + def _run_main(argv: List[str]) -> None: """ Helper to run main.py with the given argv. @@ -62,4 +63,4 @@ class TestToolsHelp(unittest.TestCase): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/e2e/test_update_all_no_system.py b/tests/e2e/test_update_all_no_system.py index 75f58d6..ac654b5 100644 --- a/tests/e2e/test_update_all_no_system.py +++ b/tests/e2e/test_update_all_no_system.py @@ -30,10 +30,7 @@ from test_install_pkgmgr_shallow import ( def _make_temp_gitconfig_with_safe_dirs(home: Path) -> Path: gitconfig = home / ".gitconfig" gitconfig.write_text( - "[safe]\n" - "\tdirectory = /src\n" - "\tdirectory = /src/.git\n" - "\tdirectory = *\n" + "[safe]\n\tdirectory = /src\n\tdirectory = /src/.git\n\tdirectory = *\n" ) return gitconfig diff --git a/tests/e2e/test_update_pkgmgr_system.py b/tests/e2e/test_update_pkgmgr_system.py index 67ad80a..6aeb89e 100644 --- a/tests/e2e/test_update_pkgmgr_system.py +++ b/tests/e2e/test_update_pkgmgr_system.py @@ -29,10 +29,7 @@ from test_install_pkgmgr_shallow import ( def _make_temp_gitconfig_with_safe_dirs(home: Path) -> Path: gitconfig = home / ".gitconfig" gitconfig.write_text( - "[safe]\n" - "\tdirectory = /src\n" - "\tdirectory = /src/.git\n" - "\tdirectory = *\n" + "[safe]\n\tdirectory = /src\n\tdirectory = /src/.git\n\tdirectory = *\n" ) return gitconfig diff --git a/tests/e2e/test_version_commands.py b/tests/e2e/test_version_commands.py index 26e1974..d1eb09a 100644 --- a/tests/e2e/test_version_commands.py +++ b/tests/e2e/test_version_commands.py @@ -31,9 +31,7 @@ from typing import List from pkgmgr.core.config.load import load_config # Resolve project root (the repo where main.py lives, e.g. /src) -PROJECT_ROOT = os.path.abspath( - os.path.join(os.path.dirname(__file__), "..", "..") -) +PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) CONFIG_PATH = os.path.join(PROJECT_ROOT, "config", "config.yaml") diff --git a/tests/integration/test_branch_cli.py b/tests/integration/test_branch_cli.py index 2b1856a..e3984af 100644 --- a/tests/integration/test_branch_cli.py +++ b/tests/integration/test_branch_cli.py @@ -227,9 +227,7 @@ class TestBranchCLI(unittest.TestCase): Ensure that `pkgmgr branch drop --force` passes force=True. """ parser = self._create_parser() - args = parser.parse_args( - ["branch", "drop", "feature/tmp-branch", "--force"] - ) + args = parser.parse_args(["branch", "drop", "feature/tmp-branch", "--force"]) self.assertTrue(args.force) diff --git a/tests/integration/test_install_repos.py b/tests/integration/test_install_repos.py index 243fc52..28a9d03 100644 --- a/tests/integration/test_install_repos.py +++ b/tests/integration/test_install_repos.py @@ -83,7 +83,10 @@ class TestInstallReposIntegration(unittest.TestCase): selected_repos = [repo_system, repo_nix] all_repos = selected_repos - with tempfile.TemporaryDirectory() as tmp_base, tempfile.TemporaryDirectory() as tmp_bin: + with ( + tempfile.TemporaryDirectory() as tmp_base, + tempfile.TemporaryDirectory() as tmp_bin, + ): # Fake repo directories (what get_repo_dir will return) repo_system_dir = os.path.join(tmp_base, "repo-system") repo_nix_dir = os.path.join(tmp_base, "repo-nix") @@ -103,11 +106,12 @@ class TestInstallReposIntegration(unittest.TestCase): # Patch resolve_command_for_repo at the *pipeline* module level, # because InstallationPipeline imports it there. - with patch( - "pkgmgr.actions.install.pipeline.resolve_command_for_repo" - ) as mock_resolve, patch( - "pkgmgr.actions.install.os.path.exists" - ) as mock_exists_install: + with ( + patch( + "pkgmgr.actions.install.pipeline.resolve_command_for_repo" + ) as mock_resolve, + patch("pkgmgr.actions.install.os.path.exists") as mock_exists_install, + ): def fake_resolve(repo, repo_identifier: str, repo_dir: str): """ diff --git a/tests/integration/test_mirror_commands.py b/tests/integration/test_mirror_commands.py index b65ebcc..241ade9 100644 --- a/tests/integration/test_mirror_commands.py +++ b/tests/integration/test_mirror_commands.py @@ -29,7 +29,9 @@ from unittest.mock import MagicMock, PropertyMock, patch class TestIntegrationMirrorCommands(unittest.TestCase): """Integration tests for `pkgmgr mirror` commands.""" - def _run_pkgmgr(self, args: List[str], extra_env: Optional[Dict[str, str]] = None) -> str: + def _run_pkgmgr( + self, args: List[str], extra_env: Optional[Dict[str, str]] = None + ) -> str: """Execute pkgmgr with the given arguments and return captured output.""" original_argv = list(sys.argv) original_env = dict(os.environ) @@ -80,20 +82,65 @@ class TestIntegrationMirrorCommands(unittest.TestCase): with ExitStack() as stack: # build_context is imported directly in these modules: - stack.enter_context(_p("pkgmgr.actions.mirror.list_cmd.build_context", return_value=dummy_ctx)) - stack.enter_context(_p("pkgmgr.actions.mirror.diff_cmd.build_context", return_value=dummy_ctx)) - stack.enter_context(_p("pkgmgr.actions.mirror.merge_cmd.build_context", return_value=dummy_ctx)) - stack.enter_context(_p("pkgmgr.actions.mirror.setup_cmd.build_context", return_value=dummy_ctx)) - stack.enter_context(_p("pkgmgr.actions.mirror.remote_provision.build_context", return_value=dummy_ctx)) + stack.enter_context( + _p( + "pkgmgr.actions.mirror.list_cmd.build_context", + return_value=dummy_ctx, + ) + ) + stack.enter_context( + _p( + "pkgmgr.actions.mirror.diff_cmd.build_context", + return_value=dummy_ctx, + ) + ) + stack.enter_context( + _p( + "pkgmgr.actions.mirror.merge_cmd.build_context", + return_value=dummy_ctx, + ) + ) + stack.enter_context( + _p( + "pkgmgr.actions.mirror.setup_cmd.build_context", + return_value=dummy_ctx, + ) + ) + stack.enter_context( + _p( + "pkgmgr.actions.mirror.remote_provision.build_context", + return_value=dummy_ctx, + ) + ) # Deterministic remote probing (new refactor: probe_remote_reachable) - stack.enter_context(_p("pkgmgr.core.git.queries.probe_remote_reachable", return_value=True)) - stack.enter_context(_p("pkgmgr.actions.mirror.setup_cmd.probe_remote_reachable", return_value=True)) + stack.enter_context( + _p( + "pkgmgr.core.git.queries.probe_remote_reachable", + return_value=True, + ) + ) + stack.enter_context( + _p( + "pkgmgr.actions.mirror.setup_cmd.probe_remote_reachable", + return_value=True, + ) + ) # setup_cmd imports ensure_origin_remote directly: - stack.enter_context(_p("pkgmgr.actions.mirror.setup_cmd.ensure_origin_remote", return_value=None)) + stack.enter_context( + _p( + "pkgmgr.actions.mirror.setup_cmd.ensure_origin_remote", + return_value=None, + ) + ) # Extra safety: if any code calls git_remote.ensure_origin_remote directly - stack.enter_context(_p("pkgmgr.actions.mirror.git_remote.ensure_origin_remote", return_value=None)) + stack.enter_context( + _p( + "pkgmgr.actions.mirror.git_remote.ensure_origin_remote", + return_value=None, + ) + ) # remote provisioning: remote_provision imports ensure_remote_repo directly from core: stack.enter_context( @@ -135,8 +182,12 @@ class TestIntegrationMirrorCommands(unittest.TestCase): self.assertTrue(output.strip(), "Expected output from mirror diff") def test_mirror_merge_config_to_file_preview_all(self) -> None: - output = self._run_pkgmgr(["mirror", "merge", "config", "file", "--preview", "--all"]) - self.assertTrue(output.strip(), "Expected output from mirror merge (config -> file)") + output = self._run_pkgmgr( + ["mirror", "merge", "config", "file", "--preview", "--all"] + ) + self.assertTrue( + output.strip(), "Expected output from mirror merge (config -> file)" + ) def test_mirror_setup_preview_all(self) -> None: output = self._run_pkgmgr(["mirror", "setup", "--preview", "--all"]) @@ -148,7 +199,9 @@ class TestIntegrationMirrorCommands(unittest.TestCase): def test_mirror_provision_preview_all(self) -> None: output = self._run_pkgmgr(["mirror", "provision", "--preview", "--all"]) - self.assertTrue(output.strip(), "Expected output from mirror provision (preview)") + self.assertTrue( + output.strip(), "Expected output from mirror provision (preview)" + ) if __name__ == "__main__": diff --git a/tests/integration/test_nix_profile_list_json.py b/tests/integration/test_nix_profile_list_json.py index d29e51a..b0b580a 100644 --- a/tests/integration/test_nix_profile_list_json.py +++ b/tests/integration/test_nix_profile_list_json.py @@ -10,6 +10,7 @@ class FakeRunResult: """ Mimics your runner returning a structured result object. """ + returncode: int stdout: str stderr: str = "" @@ -19,6 +20,7 @@ class FakeRunner: """ Minimal runner stub: returns exactly what we configure. """ + def __init__(self, result): self._result = result @@ -37,26 +39,34 @@ class TestE2ENixProfileListJsonParsing(unittest.TestCase): def test_list_json_accepts_raw_string(self) -> None: from pkgmgr.actions.install.installers.nix.profile import NixProfileInspector - payload = {"elements": {"pkgmgr-1": {"attrPath": "packages.x86_64-linux.pkgmgr"}}} + payload = { + "elements": {"pkgmgr-1": {"attrPath": "packages.x86_64-linux.pkgmgr"}} + } raw = json.dumps(payload) runner = FakeRunner(raw) inspector = NixProfileInspector() data = inspector.list_json(ctx=None, runner=runner) - self.assertEqual(data["elements"]["pkgmgr-1"]["attrPath"], "packages.x86_64-linux.pkgmgr") + self.assertEqual( + data["elements"]["pkgmgr-1"]["attrPath"], "packages.x86_64-linux.pkgmgr" + ) def test_list_json_accepts_runresult_object(self) -> None: from pkgmgr.actions.install.installers.nix.profile import NixProfileInspector - payload = {"elements": {"pkgmgr-1": {"attrPath": "packages.x86_64-linux.pkgmgr"}}} + payload = { + "elements": {"pkgmgr-1": {"attrPath": "packages.x86_64-linux.pkgmgr"}} + } raw = json.dumps(payload) runner = FakeRunner(FakeRunResult(returncode=0, stdout=raw)) inspector = NixProfileInspector() data = inspector.list_json(ctx=None, runner=runner) - self.assertEqual(data["elements"]["pkgmgr-1"]["attrPath"], "packages.x86_64-linux.pkgmgr") + self.assertEqual( + data["elements"]["pkgmgr-1"]["attrPath"], "packages.x86_64-linux.pkgmgr" + ) if __name__ == "__main__": diff --git a/tests/integration/test_recursive_capabilities.py b/tests/integration/test_recursive_capabilities.py index 5844630..f4a4c9f 100644 --- a/tests/integration/test_recursive_capabilities.py +++ b/tests/integration/test_recursive_capabilities.py @@ -74,26 +74,28 @@ class TestRecursiveCapabilitiesIntegration(unittest.TestCase): patched_installers = [] for label, inst in installers: + def always_supports(self, ctx): return True def make_run(label_name: str): def _run(self, ctx): called_installers.append(label_name) + return _run inst.supports = always_supports.__get__(inst, inst.__class__) # type: ignore[assignment] inst.run = make_run(label).__get__(inst, inst.__class__) # type: ignore[assignment] patched_installers.append(inst) - with patch.object(install_mod, "INSTALLERS", patched_installers), patch.object( - install_mod, "get_repo_identifier", return_value="dummy-repo" - ), patch.object( - install_mod, "get_repo_dir", return_value=repo_dir - ), patch.object( - install_mod, "verify_repository", return_value=(True, [], None, None) - ), patch.object( - install_mod, "clone_repos" + with ( + patch.object(install_mod, "INSTALLERS", patched_installers), + patch.object(install_mod, "get_repo_identifier", return_value="dummy-repo"), + patch.object(install_mod, "get_repo_dir", return_value=repo_dir), + patch.object( + install_mod, "verify_repository", return_value=(True, [], None, None) + ), + patch.object(install_mod, "clone_repos"), ): install_repos( selected_repos=selected_repos, diff --git a/tests/integration/test_release_publish_hook.py b/tests/integration/test_release_publish_hook.py index 6187b13..9599aee 100644 --- a/tests/integration/test_release_publish_hook.py +++ b/tests/integration/test_release_publish_hook.py @@ -29,10 +29,12 @@ class TestIntegrationReleasePublishHook(unittest.TestCase): # Go through real parser to ensure CLI surface is wired correctly args = self._parse(["release", "patch"]) - with patch("pkgmgr.cli.commands.release.run_release") as m_release, patch( - "pkgmgr.cli.commands.release.run_publish" - ) as m_publish, patch( - "pkgmgr.cli.commands.release.sys.stdin.isatty", return_value=False + with ( + patch("pkgmgr.cli.commands.release.run_release") as m_release, + patch("pkgmgr.cli.commands.release.run_publish") as m_publish, + patch( + "pkgmgr.cli.commands.release.sys.stdin.isatty", return_value=False + ), ): handle_release(args=args, ctx=self._ctx(), selected=selected) @@ -53,9 +55,10 @@ class TestIntegrationReleasePublishHook(unittest.TestCase): args = self._parse(["release", "patch", "--no-publish"]) - with patch("pkgmgr.cli.commands.release.run_release") as m_release, patch( - "pkgmgr.cli.commands.release.run_publish" - ) as m_publish: + with ( + patch("pkgmgr.cli.commands.release.run_release") as m_release, + patch("pkgmgr.cli.commands.release.run_publish") as m_publish, + ): handle_release(args=args, ctx=self._ctx(), selected=selected) m_release.assert_called_once() diff --git a/tests/integration/test_repos_create_preview.py b/tests/integration/test_repos_create_preview.py index 8c74327..5ae7b5b 100644 --- a/tests/integration/test_repos_create_preview.py +++ b/tests/integration/test_repos_create_preview.py @@ -24,7 +24,10 @@ class TestIntegrationReposCreatePreview(unittest.TestCase): repositories_base_dir="/tmp/Repositories", binaries_dir="/tmp/bin", all_repositories=[], - config_merged={"directories": {"repositories": "/tmp/Repositories"}, "repositories": []}, + config_merged={ + "directories": {"repositories": "/tmp/Repositories"}, + "repositories": [], + }, user_config_path="/tmp/user.yml", ) diff --git a/tests/integration/test_repository_paths_exist.py b/tests/integration/test_repository_paths_exist.py index 7f95216..53e2dff 100644 --- a/tests/integration/test_repository_paths_exist.py +++ b/tests/integration/test_repository_paths_exist.py @@ -18,9 +18,13 @@ def _find_repo_root() -> Path: """ here = Path(__file__).resolve() for parent in here.parents: - if (parent / "pyproject.toml").is_file() and (parent / "src" / "pkgmgr").is_dir(): + if (parent / "pyproject.toml").is_file() and ( + parent / "src" / "pkgmgr" + ).is_dir(): return parent - raise RuntimeError("Could not determine repository root for pkgmgr integration test") + raise RuntimeError( + "Could not determine repository root for pkgmgr integration test" + ) class TestRepositoryPathsExist(unittest.TestCase): diff --git a/tests/integration/test_token_resolver_flow.py b/tests/integration/test_token_resolver_flow.py index b49826b..0e5afe9 100644 --- a/tests/integration/test_token_resolver_flow.py +++ b/tests/integration/test_token_resolver_flow.py @@ -25,7 +25,6 @@ class TestTokenResolverIntegration(unittest.TestCase): # 1) ENV: empty # ------------------------------------------------------------------ with patch.dict("os.environ", {}, clear=True): - # ------------------------------------------------------------------ # 2) GH CLI is available # ------------------------------------------------------------------ @@ -37,14 +36,12 @@ class TestTokenResolverIntegration(unittest.TestCase): "pkgmgr.core.credentials.providers.gh.subprocess.check_output", return_value="gh-invalid-token\n", ): - # ------------------------------------------------------------------ # 3) Keyring returns an existing (invalid) token # ------------------------------------------------------------------ with patch( "pkgmgr.core.credentials.providers.keyring._import_keyring" ) as mock_import_keyring: - mock_keyring = mock_import_keyring.return_value mock_keyring.get_password.return_value = "keyring-invalid-token" @@ -59,7 +56,6 @@ class TestTokenResolverIntegration(unittest.TestCase): "pkgmgr.core.credentials.providers.prompt.getpass", return_value="new-valid-token", ): - # ------------------------------------------------------------------ # 5) Validation logic: # - gh token invalid @@ -77,7 +73,6 @@ class TestTokenResolverIntegration(unittest.TestCase): "pkgmgr.core.credentials.resolver.validate_token", side_effect=validate_side_effect, ) as validate_mock: - result = resolver.get_token( provider_kind="github", host="github.com", diff --git a/tests/integration/test_update_silent_continues.py b/tests/integration/test_update_silent_continues.py index 0fd52ae..3dfa492 100644 --- a/tests/integration/test_update_silent_continues.py +++ b/tests/integration/test_update_silent_continues.py @@ -46,14 +46,22 @@ class TestUpdateSilentContinues(unittest.TestCase): def install_side_effect(selected_repos, *_args, **kwargs): repo = selected_repos[0] - install_calls.append((repo["repository"], kwargs.get("silent"), kwargs.get("emit_summary"))) + install_calls.append( + (repo["repository"], kwargs.get("silent"), kwargs.get("emit_summary")) + ) if repo["repository"] == "repo-b": raise SystemExit(3) return None # Patch at the exact import locations used inside UpdateManager.run() - with patch("pkgmgr.actions.repository.pull.pull_with_verification", side_effect=pull_side_effect), patch( - "pkgmgr.actions.install.install_repos", side_effect=install_side_effect + with ( + patch( + "pkgmgr.actions.repository.pull.pull_with_verification", + side_effect=pull_side_effect, + ), + patch( + "pkgmgr.actions.install.install_repos", side_effect=install_side_effect + ), ): # 1) silent=True: should NOT raise (even though failures happened) UpdateManager().run( @@ -73,7 +81,9 @@ class TestUpdateSilentContinues(unittest.TestCase): # Ensure it tried all pulls, and installs happened for B and C only. self.assertEqual(pull_calls, ["repo-a", "repo-b", "repo-c"]) - self.assertEqual([r for r, _silent, _emit in install_calls], ["repo-b", "repo-c"]) + self.assertEqual( + [r for r, _silent, _emit in install_calls], ["repo-b", "repo-c"] + ) # Ensure UpdateManager suppressed install summary spam by passing emit_summary=False. for _repo_name, _silent, emit_summary in install_calls: @@ -103,7 +113,9 @@ class TestUpdateSilentContinues(unittest.TestCase): # Still must have processed all repos (continue-on-failure behavior). self.assertEqual(pull_calls, ["repo-a", "repo-b", "repo-c"]) - self.assertEqual([r for r, _silent, _emit in install_calls], ["repo-b", "repo-c"]) + self.assertEqual( + [r for r, _silent, _emit in install_calls], ["repo-b", "repo-c"] + ) if __name__ == "__main__": diff --git a/tests/unit/pkgmgr/actions/branch/test_close_branch.py b/tests/unit/pkgmgr/actions/branch/test_close_branch.py index c1a0687..a764293 100644 --- a/tests/unit/pkgmgr/actions/branch/test_close_branch.py +++ b/tests/unit/pkgmgr/actions/branch/test_close_branch.py @@ -8,8 +8,13 @@ from pkgmgr.core.git.commands import GitDeleteRemoteBranchError class TestCloseBranch(unittest.TestCase): @patch("builtins.input", return_value="y") - @patch("pkgmgr.actions.branch.close_branch.get_current_branch", return_value="feature-x") - @patch("pkgmgr.actions.branch.close_branch.resolve_base_branch", return_value="main") + @patch( + "pkgmgr.actions.branch.close_branch.get_current_branch", + return_value="feature-x", + ) + @patch( + "pkgmgr.actions.branch.close_branch.resolve_base_branch", return_value="main" + ) @patch("pkgmgr.actions.branch.close_branch.fetch") @patch("pkgmgr.actions.branch.close_branch.checkout") @patch("pkgmgr.actions.branch.close_branch.pull") @@ -40,22 +45,36 @@ class TestCloseBranch(unittest.TestCase): delete_remote_branch.assert_called_once_with("origin", "feature-x", cwd=".") @patch("pkgmgr.actions.branch.close_branch.get_current_branch", return_value="main") - @patch("pkgmgr.actions.branch.close_branch.resolve_base_branch", return_value="main") + @patch( + "pkgmgr.actions.branch.close_branch.resolve_base_branch", return_value="main" + ) def test_refuses_to_close_base_branch(self, _resolve, _current) -> None: with self.assertRaises(RuntimeError): close_branch(None) @patch("builtins.input", return_value="n") - @patch("pkgmgr.actions.branch.close_branch.get_current_branch", return_value="feature-x") - @patch("pkgmgr.actions.branch.close_branch.resolve_base_branch", return_value="main") + @patch( + "pkgmgr.actions.branch.close_branch.get_current_branch", + return_value="feature-x", + ) + @patch( + "pkgmgr.actions.branch.close_branch.resolve_base_branch", return_value="main" + ) @patch("pkgmgr.actions.branch.close_branch.fetch") - def test_close_branch_aborts_on_no(self, fetch, _resolve, _current, _input_mock) -> None: + def test_close_branch_aborts_on_no( + self, fetch, _resolve, _current, _input_mock + ) -> None: close_branch(None, cwd=".") fetch.assert_not_called() @patch("builtins.input") - @patch("pkgmgr.actions.branch.close_branch.get_current_branch", return_value="feature-x") - @patch("pkgmgr.actions.branch.close_branch.resolve_base_branch", return_value="main") + @patch( + "pkgmgr.actions.branch.close_branch.get_current_branch", + return_value="feature-x", + ) + @patch( + "pkgmgr.actions.branch.close_branch.resolve_base_branch", return_value="main" + ) @patch("pkgmgr.actions.branch.close_branch.fetch") @patch("pkgmgr.actions.branch.close_branch.checkout") @patch("pkgmgr.actions.branch.close_branch.pull") @@ -90,14 +109,22 @@ class TestCloseBranch(unittest.TestCase): delete_local_branch.assert_called_once_with("feature-x", cwd=".", force=False) delete_remote_branch.assert_called_once_with("origin", "feature-x", cwd=".") - @patch("pkgmgr.actions.branch.close_branch.get_current_branch", side_effect=GitRunError("fail")) + @patch( + "pkgmgr.actions.branch.close_branch.get_current_branch", + side_effect=GitRunError("fail"), + ) def test_close_branch_errors_if_cannot_detect_branch(self, _current) -> None: with self.assertRaises(RuntimeError): close_branch(None) @patch("builtins.input", return_value="y") - @patch("pkgmgr.actions.branch.close_branch.get_current_branch", return_value="feature-x") - @patch("pkgmgr.actions.branch.close_branch.resolve_base_branch", return_value="main") + @patch( + "pkgmgr.actions.branch.close_branch.get_current_branch", + return_value="feature-x", + ) + @patch( + "pkgmgr.actions.branch.close_branch.resolve_base_branch", return_value="main" + ) @patch("pkgmgr.actions.branch.close_branch.fetch") @patch("pkgmgr.actions.branch.close_branch.checkout") @patch("pkgmgr.actions.branch.close_branch.pull") diff --git a/tests/unit/pkgmgr/actions/branch/test_drop_branch.py b/tests/unit/pkgmgr/actions/branch/test_drop_branch.py index facc505..5b7e3b8 100644 --- a/tests/unit/pkgmgr/actions/branch/test_drop_branch.py +++ b/tests/unit/pkgmgr/actions/branch/test_drop_branch.py @@ -8,11 +8,15 @@ from pkgmgr.core.git.commands import GitDeleteRemoteBranchError class TestDropBranch(unittest.TestCase): @patch("builtins.input", return_value="y") - @patch("pkgmgr.actions.branch.drop_branch.get_current_branch", return_value="feature-x") + @patch( + "pkgmgr.actions.branch.drop_branch.get_current_branch", return_value="feature-x" + ) @patch("pkgmgr.actions.branch.drop_branch.resolve_base_branch", return_value="main") @patch("pkgmgr.actions.branch.drop_branch.delete_local_branch") @patch("pkgmgr.actions.branch.drop_branch.delete_remote_branch") - def test_drop_branch_happy_path(self, delete_remote, delete_local, _resolve, _current, _input_mock) -> None: + def test_drop_branch_happy_path( + self, delete_remote, delete_local, _resolve, _current, _input_mock + ) -> None: drop_branch(None, cwd=".") delete_local.assert_called_once_with("feature-x", cwd=".", force=False) delete_remote.assert_called_once_with("origin", "feature-x", cwd=".") @@ -24,15 +28,21 @@ class TestDropBranch(unittest.TestCase): drop_branch(None) @patch("builtins.input", return_value="n") - @patch("pkgmgr.actions.branch.drop_branch.get_current_branch", return_value="feature-x") + @patch( + "pkgmgr.actions.branch.drop_branch.get_current_branch", return_value="feature-x" + ) @patch("pkgmgr.actions.branch.drop_branch.resolve_base_branch", return_value="main") @patch("pkgmgr.actions.branch.drop_branch.delete_local_branch") - def test_drop_branch_aborts_on_no(self, delete_local, _resolve, _current, _input_mock) -> None: + def test_drop_branch_aborts_on_no( + self, delete_local, _resolve, _current, _input_mock + ) -> None: drop_branch(None, cwd=".") delete_local.assert_not_called() @patch("builtins.input") - @patch("pkgmgr.actions.branch.drop_branch.get_current_branch", return_value="feature-x") + @patch( + "pkgmgr.actions.branch.drop_branch.get_current_branch", return_value="feature-x" + ) @patch("pkgmgr.actions.branch.drop_branch.resolve_base_branch", return_value="main") @patch("pkgmgr.actions.branch.drop_branch.delete_local_branch") @patch("pkgmgr.actions.branch.drop_branch.delete_remote_branch") @@ -50,13 +60,18 @@ class TestDropBranch(unittest.TestCase): delete_local.assert_called_once_with("feature-x", cwd=".", force=False) delete_remote.assert_called_once_with("origin", "feature-x", cwd=".") - @patch("pkgmgr.actions.branch.drop_branch.get_current_branch", side_effect=GitRunError("fail")) + @patch( + "pkgmgr.actions.branch.drop_branch.get_current_branch", + side_effect=GitRunError("fail"), + ) def test_drop_branch_errors_if_no_branch_detected(self, _current) -> None: with self.assertRaises(RuntimeError): drop_branch(None) @patch("builtins.input", return_value="y") - @patch("pkgmgr.actions.branch.drop_branch.get_current_branch", return_value="feature-x") + @patch( + "pkgmgr.actions.branch.drop_branch.get_current_branch", return_value="feature-x" + ) @patch("pkgmgr.actions.branch.drop_branch.resolve_base_branch", return_value="main") @patch("pkgmgr.actions.branch.drop_branch.delete_local_branch") @patch( diff --git a/tests/unit/pkgmgr/actions/install/installers/nix/test_conflicts_resolver.py b/tests/unit/pkgmgr/actions/install/installers/nix/test_conflicts_resolver.py index 830570f..86b0efa 100644 --- a/tests/unit/pkgmgr/actions/install/installers/nix/test_conflicts_resolver.py +++ b/tests/unit/pkgmgr/actions/install/installers/nix/test_conflicts_resolver.py @@ -15,24 +15,31 @@ class TestNixConflictResolver(unittest.TestCase): ctx = DummyCtx() install_cmd = "nix profile install /repo#default" - stderr = ''' + stderr = """ error: An existing package already provides the following file: /nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-pkgmgr/bin/pkgmgr - ''' + """ - runner = FakeRunner(mapping={ - "nix profile remove pkgmgr": FakeRunResult(0, "", ""), - }) + runner = FakeRunner( + mapping={ + "nix profile remove pkgmgr": FakeRunResult(0, "", ""), + } + ) retry = FakeRetry(results=[FakeRunResult(0, "", "")]) class FakeProfile: def find_remove_tokens_for_store_prefixes(self, ctx, runner, prefixes): return [] + def find_remove_tokens_for_output(self, ctx, runner, output): return ["pkgmgr"] - resolver = NixConflictResolver(runner=runner, retry=retry, profile=FakeProfile()) - ok = resolver.resolve(ctx, install_cmd, stdout="", stderr=stderr, output="pkgmgr", max_rounds=2) + resolver = NixConflictResolver( + runner=runner, retry=retry, profile=FakeProfile() + ) + ok = resolver.resolve( + ctx, install_cmd, stdout="", stderr=stderr, output="pkgmgr", max_rounds=2 + ) self.assertTrue(ok) self.assertIn("nix profile remove pkgmgr", [c[1] for c in runner.calls]) @@ -41,18 +48,25 @@ class TestNixConflictResolver(unittest.TestCase): install_cmd = "nix profile install /repo#default" stderr = "hint: try:\n nix profile remove 'pkgmgr-1'\n" - runner = FakeRunner(mapping={ - "nix profile remove pkgmgr-1": FakeRunResult(0, "", ""), - }) + runner = FakeRunner( + mapping={ + "nix profile remove pkgmgr-1": FakeRunResult(0, "", ""), + } + ) retry = FakeRetry(results=[FakeRunResult(0, "", "")]) class FakeProfile: def find_remove_tokens_for_store_prefixes(self, ctx, runner, prefixes): return [] + def find_remove_tokens_for_output(self, ctx, runner, output): return [] - resolver = NixConflictResolver(runner=runner, retry=retry, profile=FakeProfile()) - ok = resolver.resolve(ctx, install_cmd, stdout="", stderr=stderr, output="pkgmgr", max_rounds=2) + resolver = NixConflictResolver( + runner=runner, retry=retry, profile=FakeProfile() + ) + ok = resolver.resolve( + ctx, install_cmd, stdout="", stderr=stderr, output="pkgmgr", max_rounds=2 + ) self.assertTrue(ok) self.assertIn("nix profile remove pkgmgr-1", [c[1] for c in runner.calls]) diff --git a/tests/unit/pkgmgr/actions/install/installers/nix/test_inspector.py b/tests/unit/pkgmgr/actions/install/installers/nix/test_inspector.py index 1241461..4e5633e 100644 --- a/tests/unit/pkgmgr/actions/install/installers/nix/test_inspector.py +++ b/tests/unit/pkgmgr/actions/install/installers/nix/test_inspector.py @@ -9,32 +9,48 @@ from ._fakes import FakeRunResult, FakeRunner class TestNixProfileInspector(unittest.TestCase): def test_list_json_accepts_raw_string(self) -> None: - payload = {"elements": {"pkgmgr-1": {"attrPath": "packages.x86_64-linux.pkgmgr"}}} + payload = { + "elements": {"pkgmgr-1": {"attrPath": "packages.x86_64-linux.pkgmgr"}} + } raw = json.dumps(payload) runner = FakeRunner(default=raw) insp = NixProfileInspector() data = insp.list_json(ctx=None, runner=runner) - self.assertEqual(data["elements"]["pkgmgr-1"]["attrPath"], "packages.x86_64-linux.pkgmgr") + self.assertEqual( + data["elements"]["pkgmgr-1"]["attrPath"], "packages.x86_64-linux.pkgmgr" + ) def test_list_json_accepts_result_object(self) -> None: - payload = {"elements": {"pkgmgr-1": {"attrPath": "packages.x86_64-linux.pkgmgr"}}} + payload = { + "elements": {"pkgmgr-1": {"attrPath": "packages.x86_64-linux.pkgmgr"}} + } raw = json.dumps(payload) runner = FakeRunner(default=FakeRunResult(0, stdout=raw)) insp = NixProfileInspector() data = insp.list_json(ctx=None, runner=runner) - self.assertEqual(data["elements"]["pkgmgr-1"]["attrPath"], "packages.x86_64-linux.pkgmgr") + self.assertEqual( + data["elements"]["pkgmgr-1"]["attrPath"], "packages.x86_64-linux.pkgmgr" + ) def test_find_remove_tokens_for_output_includes_output_first(self) -> None: payload = { "elements": { - "pkgmgr-1": {"name": "pkgmgr-1", "attrPath": "packages.x86_64-linux.pkgmgr"}, - "default-1": {"name": "default-1", "attrPath": "packages.x86_64-linux.default"}, + "pkgmgr-1": { + "name": "pkgmgr-1", + "attrPath": "packages.x86_64-linux.pkgmgr", + }, + "default-1": { + "name": "default-1", + "attrPath": "packages.x86_64-linux.default", + }, } } raw = json.dumps(payload) runner = FakeRunner(default=FakeRunResult(0, stdout=raw)) insp = NixProfileInspector() - tokens = insp.find_remove_tokens_for_output(ctx=None, runner=runner, output="pkgmgr") + tokens = insp.find_remove_tokens_for_output( + ctx=None, runner=runner, output="pkgmgr" + ) self.assertEqual(tokens[0], "pkgmgr") self.assertIn("pkgmgr-1", tokens) @@ -44,7 +60,9 @@ class TestNixProfileInspector(unittest.TestCase): "pkgmgr-1": { "name": "pkgmgr-1", "attrPath": "packages.x86_64-linux.pkgmgr", - "storePaths": ["/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-pkgmgr"], + "storePaths": [ + "/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-pkgmgr" + ], }, "something": { "name": "other", @@ -57,6 +75,8 @@ class TestNixProfileInspector(unittest.TestCase): runner = FakeRunner(default=FakeRunResult(0, stdout=raw)) insp = NixProfileInspector() tokens = insp.find_remove_tokens_for_store_prefixes( - ctx=None, runner=runner, prefixes=["/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-pkgmgr"] + ctx=None, + runner=runner, + prefixes=["/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-pkgmgr"], ) self.assertIn("pkgmgr-1", tokens) diff --git a/tests/unit/pkgmgr/actions/install/installers/nix/test_installer_core.py b/tests/unit/pkgmgr/actions/install/installers/nix/test_installer_core.py index 6abe65a..a1a3361 100644 --- a/tests/unit/pkgmgr/actions/install/installers/nix/test_installer_core.py +++ b/tests/unit/pkgmgr/actions/install/installers/nix/test_installer_core.py @@ -8,7 +8,13 @@ from ._fakes import FakeRunResult class DummyCtx: - def __init__(self, identifier: str = "x", repo_dir: str = "/repo", quiet: bool = True, force_update: bool = False): + def __init__( + self, + identifier: str = "x", + repo_dir: str = "/repo", + quiet: bool = True, + force_update: bool = False, + ): self.identifier = identifier self.repo_dir = repo_dir self.quiet = quiet diff --git a/tests/unit/pkgmgr/actions/install/installers/nix/test_legacy.py b/tests/unit/pkgmgr/actions/install/installers/nix/test_legacy.py index ba68093..fb421dc 100644 --- a/tests/unit/pkgmgr/actions/install/installers/nix/test_legacy.py +++ b/tests/unit/pkgmgr/actions/install/installers/nix/test_legacy.py @@ -61,8 +61,12 @@ class TestNixFlakeInstaller(unittest.TestCase): shutil.rmtree(self._tmpdir, ignore_errors=True) @staticmethod - def _cp(code: int, stdout: str = "", stderr: str = "") -> subprocess.CompletedProcess: - return subprocess.CompletedProcess(args=["nix"], returncode=code, stdout=stdout, stderr=stderr) + def _cp( + code: int, stdout: str = "", stderr: str = "" + ) -> subprocess.CompletedProcess: + return subprocess.CompletedProcess( + args=["nix"], returncode=code, stdout=stdout, stderr=stderr + ) @staticmethod def _enable_nix_in_module(which_patch) -> None: @@ -99,11 +103,20 @@ class TestNixFlakeInstaller(unittest.TestCase): return self._cp(0) buf = io.StringIO() - with patch("pkgmgr.actions.install.installers.nix.installer.shutil.which") as which_mock, patch( - "pkgmgr.actions.install.installers.nix.installer.os.path.exists", return_value=True - ), patch( - "pkgmgr.actions.install.installers.nix.runner.subprocess.run", side_effect=fake_subprocess_run - ) as subproc_mock, redirect_stdout(buf): + with ( + patch( + "pkgmgr.actions.install.installers.nix.installer.shutil.which" + ) as which_mock, + patch( + "pkgmgr.actions.install.installers.nix.installer.os.path.exists", + return_value=True, + ), + patch( + "pkgmgr.actions.install.installers.nix.runner.subprocess.run", + side_effect=fake_subprocess_run, + ) as subproc_mock, + redirect_stdout(buf), + ): self._enable_nix_in_module(which_mock) self.assertTrue(installer.supports(ctx)) @@ -115,7 +128,7 @@ class TestNixFlakeInstaller(unittest.TestCase): install_cmds = self._install_cmds_from_calls(subproc_mock.call_args_list) self.assertEqual(install_cmds, [f"nix profile install {self.repo_dir}#default"]) - + def test_nix_flake_supports_respects_disable_env(self) -> None: """ PKGMGR_DISABLE_NIX_FLAKE_INSTALLER=1 must disable the installer, @@ -124,8 +137,14 @@ class TestNixFlakeInstaller(unittest.TestCase): ctx = DummyCtx(identifier="pkgmgr", repo_dir=self.repo_dir, quiet=False) installer = NixFlakeInstaller() - with patch("pkgmgr.actions.install.installers.nix.installer.shutil.which") as which_mock, patch( - "pkgmgr.actions.install.installers.nix.installer.os.path.exists", return_value=True + with ( + patch( + "pkgmgr.actions.install.installers.nix.installer.shutil.which" + ) as which_mock, + patch( + "pkgmgr.actions.install.installers.nix.installer.os.path.exists", + return_value=True, + ), ): self._enable_nix_in_module(which_mock) os.environ["PKGMGR_DISABLE_NIX_FLAKE_INSTALLER"] = "1" diff --git a/tests/unit/pkgmgr/actions/install/installers/nix/test_matcher.py b/tests/unit/pkgmgr/actions/install/installers/nix/test_matcher.py index 5070653..ca33925 100644 --- a/tests/unit/pkgmgr/actions/install/installers/nix/test_matcher.py +++ b/tests/unit/pkgmgr/actions/install/installers/nix/test_matcher.py @@ -3,7 +3,10 @@ from __future__ import annotations import unittest from pkgmgr.actions.install.installers.nix.profile.models import NixProfileEntry -from pkgmgr.actions.install.installers.nix.profile.matcher import entry_matches_output, entry_matches_store_path +from pkgmgr.actions.install.installers.nix.profile.matcher import ( + entry_matches_output, + entry_matches_store_path, +) class TestMatcher(unittest.TestCase): @@ -20,18 +23,32 @@ class TestMatcher(unittest.TestCase): self.assertTrue(entry_matches_output(self._e("pkgmgr", ""), "pkgmgr")) def test_matches_attrpath_hash(self) -> None: - self.assertTrue(entry_matches_output(self._e("", "github:me/repo#pkgmgr"), "pkgmgr")) + self.assertTrue( + entry_matches_output(self._e("", "github:me/repo#pkgmgr"), "pkgmgr") + ) def test_matches_attrpath_dot_suffix(self) -> None: - self.assertTrue(entry_matches_output(self._e("", "packages.x86_64-linux.pkgmgr"), "pkgmgr")) + self.assertTrue( + entry_matches_output(self._e("", "packages.x86_64-linux.pkgmgr"), "pkgmgr") + ) def test_matches_name_with_suffix_number(self) -> None: self.assertTrue(entry_matches_output(self._e("pkgmgr-1", ""), "pkgmgr")) def test_package_manager_special_case(self) -> None: - self.assertTrue(entry_matches_output(self._e("package-manager-2", ""), "pkgmgr")) + self.assertTrue( + entry_matches_output(self._e("package-manager-2", ""), "pkgmgr") + ) def test_store_path_match(self) -> None: entry = self._e("pkgmgr-1", "") - self.assertTrue(entry_matches_store_path(entry, "/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-pkgmgr")) - self.assertFalse(entry_matches_store_path(entry, "/nix/store/cccccccccccccccccccccccccccccccc-zzz")) + self.assertTrue( + entry_matches_store_path( + entry, "/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-pkgmgr" + ) + ) + self.assertFalse( + entry_matches_store_path( + entry, "/nix/store/cccccccccccccccccccccccccccccccc-zzz" + ) + ) diff --git a/tests/unit/pkgmgr/actions/install/installers/nix/test_nix_retry_403.py b/tests/unit/pkgmgr/actions/install/installers/nix/test_nix_retry_403.py index 973ce55..79c41f6 100644 --- a/tests/unit/pkgmgr/actions/install/installers/nix/test_nix_retry_403.py +++ b/tests/unit/pkgmgr/actions/install/installers/nix/test_nix_retry_403.py @@ -3,7 +3,10 @@ from __future__ import annotations import unittest from unittest.mock import patch -from pkgmgr.actions.install.installers.nix.retry import GitHubRateLimitRetry, RetryPolicy +from pkgmgr.actions.install.installers.nix.retry import ( + GitHubRateLimitRetry, + RetryPolicy, +) from pkgmgr.actions.install.installers.nix.types import RunResult @@ -46,8 +49,8 @@ class TestGitHub403Retry(unittest.TestCase): - Wait times follow Fibonacci(base=30) + jitter """ policy = RetryPolicy( - max_attempts=3, # attempts: 1,2,3 - base_delay_seconds=30, # fibonacci delays: 30, 30, 60 + max_attempts=3, # attempts: 1,2,3 + base_delay_seconds=30, # fibonacci delays: 30, 30, 60 jitter_seconds_min=0, jitter_seconds_max=60, ) @@ -57,9 +60,15 @@ class TestGitHub403Retry(unittest.TestCase): runner = FakeRunner(fail_count=2) # fail twice (403), then succeed # Make jitter deterministic and prevent real sleeping. - with patch("pkgmgr.actions.install.installers.nix.retry.random.randint", return_value=5) as jitter_mock, patch( - "pkgmgr.actions.install.installers.nix.retry.time.sleep" - ) as sleep_mock: + with ( + patch( + "pkgmgr.actions.install.installers.nix.retry.random.randint", + return_value=5, + ) as jitter_mock, + patch( + "pkgmgr.actions.install.installers.nix.retry.time.sleep" + ) as sleep_mock, + ): res = retry.run_with_retry(ctx, runner, "nix profile install /tmp#default") # Result should be success on 3rd attempt. @@ -90,15 +99,19 @@ class TestGitHub403Retry(unittest.TestCase): def run(self, ctx: DummyCtx, cmd: str, allow_failure: bool) -> RunResult: self.calls += 1 - return RunResult(returncode=1, stdout="", stderr="some other error (simulated)") + return RunResult( + returncode=1, stdout="", stderr="some other error (simulated)" + ) runner = Non403Runner() - with patch("pkgmgr.actions.install.installers.nix.retry.time.sleep") as sleep_mock: + with patch( + "pkgmgr.actions.install.installers.nix.retry.time.sleep" + ) as sleep_mock: res = retry.run_with_retry(ctx, runner, "nix profile install /tmp#default") self.assertEqual(res.returncode, 1) - self.assertEqual(runner.calls, 1) # no retries + self.assertEqual(runner.calls, 1) # no retries self.assertEqual(sleep_mock.call_count, 0) diff --git a/tests/unit/pkgmgr/actions/install/installers/nix/test_normalizer.py b/tests/unit/pkgmgr/actions/install/installers/nix/test_normalizer.py index f873774..7fffcb5 100644 --- a/tests/unit/pkgmgr/actions/install/installers/nix/test_normalizer.py +++ b/tests/unit/pkgmgr/actions/install/installers/nix/test_normalizer.py @@ -2,7 +2,10 @@ from __future__ import annotations import unittest -from pkgmgr.actions.install.installers.nix.profile.normalizer import coerce_index, normalize_elements +from pkgmgr.actions.install.installers.nix.profile.normalizer import ( + coerce_index, + normalize_elements, +) class TestNormalizer(unittest.TestCase): @@ -25,7 +28,9 @@ class TestNormalizer(unittest.TestCase): "pkgmgr-1": { "name": "pkgmgr-1", "attrPath": "packages.x86_64-linux.pkgmgr", - "storePaths": ["/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-pkgmgr"], + "storePaths": [ + "/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-pkgmgr" + ], }, "2": { "name": "foo", diff --git a/tests/unit/pkgmgr/actions/install/installers/nix/test_parser.py b/tests/unit/pkgmgr/actions/install/installers/nix/test_parser.py index a8b2f69..1ad9865 100644 --- a/tests/unit/pkgmgr/actions/install/installers/nix/test_parser.py +++ b/tests/unit/pkgmgr/actions/install/installers/nix/test_parser.py @@ -10,7 +10,9 @@ class TestParseProfileListJson(unittest.TestCase): def test_parses_valid_json(self) -> None: payload = {"elements": {"0": {"name": "pkgmgr"}}} raw = json.dumps(payload) - self.assertEqual(parse_profile_list_json(raw)["elements"]["0"]["name"], "pkgmgr") + self.assertEqual( + parse_profile_list_json(raw)["elements"]["0"]["name"], "pkgmgr" + ) def test_raises_systemexit_on_invalid_json(self) -> None: with self.assertRaises(SystemExit) as cm: diff --git a/tests/unit/pkgmgr/actions/install/installers/nix/test_profile_list_reader.py b/tests/unit/pkgmgr/actions/install/installers/nix/test_profile_list_reader.py index f961c1d..3529572 100644 --- a/tests/unit/pkgmgr/actions/install/installers/nix/test_profile_list_reader.py +++ b/tests/unit/pkgmgr/actions/install/installers/nix/test_profile_list_reader.py @@ -8,10 +8,10 @@ from ._fakes import FakeRunResult, FakeRunner class TestNixProfileListReader(unittest.TestCase): def test_entries_parses_indices_and_store_prefixes(self) -> None: - out = ''' + out = """ 0 something /nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-pkgmgr 1 something /nix/store/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb-foo - ''' + """ runner = FakeRunner(default=FakeRunResult(0, stdout=out)) reader = NixProfileListReader(runner=runner) entries = reader.entries(ctx=None) diff --git a/tests/unit/pkgmgr/actions/install/installers/nix/test_result.py b/tests/unit/pkgmgr/actions/install/installers/nix/test_result.py index 0fca68f..88f3401 100644 --- a/tests/unit/pkgmgr/actions/install/installers/nix/test_result.py +++ b/tests/unit/pkgmgr/actions/install/installers/nix/test_result.py @@ -15,15 +15,18 @@ class TestExtractStdoutText(unittest.TestCase): def test_accepts_object_with_stdout_str(self) -> None: class R: stdout = "ok" + self.assertEqual(extract_stdout_text(R()), "ok") def test_accepts_object_with_stdout_bytes(self) -> None: class R: stdout = b"ok" + self.assertEqual(extract_stdout_text(R()), "ok") def test_fallback_str(self) -> None: class R: def __str__(self) -> str: return "repr" + self.assertEqual(extract_stdout_text(R()), "repr") diff --git a/tests/unit/pkgmgr/actions/install/installers/nix/test_textparse.py b/tests/unit/pkgmgr/actions/install/installers/nix/test_textparse.py index e509362..dcfdc2b 100644 --- a/tests/unit/pkgmgr/actions/install/installers/nix/test_textparse.py +++ b/tests/unit/pkgmgr/actions/install/installers/nix/test_textparse.py @@ -8,23 +8,23 @@ from pkgmgr.actions.install.installers.nix.textparse import NixConflictTextParse class TestNixConflictTextParser(unittest.TestCase): def test_remove_tokens_parses_unquoted_and_quoted(self) -> None: t = NixConflictTextParser() - text = ''' + text = """ nix profile remove pkgmgr nix profile remove 'pkgmgr-1' nix profile remove "default-2" - ''' + """ tokens = t.remove_tokens(text) self.assertEqual(tokens, ["pkgmgr", "pkgmgr-1", "default-2"]) def test_existing_store_prefixes_extracts_existing_section_only(self) -> None: t = NixConflictTextParser() - text = ''' + text = """ error: An existing package already provides the following file: /nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-pkgmgr/bin/pkgmgr /nix/store/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb-pkgmgr/share/doc This is the conflicting file from the new package: /nix/store/cccccccccccccccccccccccccccccccc-pkgmgr/bin/pkgmgr - ''' + """ prefixes = t.existing_store_prefixes(text) self.assertEqual(len(prefixes), 2) self.assertTrue(prefixes[0].startswith("/nix/store/")) diff --git a/tests/unit/pkgmgr/actions/install/installers/os_packages/test_arch_pkgbuild.py b/tests/unit/pkgmgr/actions/install/installers/os_packages/test_arch_pkgbuild.py index 60ebdbf..6ae558f 100644 --- a/tests/unit/pkgmgr/actions/install/installers/os_packages/test_arch_pkgbuild.py +++ b/tests/unit/pkgmgr/actions/install/installers/os_packages/test_arch_pkgbuild.py @@ -5,7 +5,9 @@ import unittest from unittest.mock import patch from pkgmgr.actions.install.context import RepoContext -from pkgmgr.actions.install.installers.os_packages.arch_pkgbuild import ArchPkgbuildInstaller +from pkgmgr.actions.install.installers.os_packages.arch_pkgbuild import ( + ArchPkgbuildInstaller, +) class TestArchPkgbuildInstaller(unittest.TestCase): @@ -26,7 +28,10 @@ class TestArchPkgbuildInstaller(unittest.TestCase): ) self.installer = ArchPkgbuildInstaller() - @patch("pkgmgr.actions.install.installers.os_packages.arch_pkgbuild.os.geteuid", return_value=1000) + @patch( + "pkgmgr.actions.install.installers.os_packages.arch_pkgbuild.os.geteuid", + return_value=1000, + ) @patch("os.path.exists", return_value=True) @patch("shutil.which") def test_supports_true_when_tools_and_pkgbuild_exist( @@ -46,7 +51,10 @@ class TestArchPkgbuildInstaller(unittest.TestCase): self.assertIn("makepkg", calls) mock_exists.assert_called_with(os.path.join(self.ctx.repo_dir, "PKGBUILD")) - @patch("pkgmgr.actions.install.installers.os_packages.arch_pkgbuild.os.geteuid", return_value=0) + @patch( + "pkgmgr.actions.install.installers.os_packages.arch_pkgbuild.os.geteuid", + return_value=0, + ) @patch("os.path.exists", return_value=True) @patch("shutil.which") def test_supports_false_when_running_as_root( @@ -55,7 +63,10 @@ class TestArchPkgbuildInstaller(unittest.TestCase): mock_which.return_value = "/usr/bin/pacman" self.assertFalse(self.installer.supports(self.ctx)) - @patch("pkgmgr.actions.install.installers.os_packages.arch_pkgbuild.os.geteuid", return_value=1000) + @patch( + "pkgmgr.actions.install.installers.os_packages.arch_pkgbuild.os.geteuid", + return_value=1000, + ) @patch("os.path.exists", return_value=False) @patch("shutil.which") def test_supports_false_when_pkgbuild_missing( @@ -65,7 +76,10 @@ class TestArchPkgbuildInstaller(unittest.TestCase): self.assertFalse(self.installer.supports(self.ctx)) @patch("pkgmgr.actions.install.installers.os_packages.arch_pkgbuild.run_command") - @patch("pkgmgr.actions.install.installers.os_packages.arch_pkgbuild.os.geteuid", return_value=1000) + @patch( + "pkgmgr.actions.install.installers.os_packages.arch_pkgbuild.os.geteuid", + return_value=1000, + ) @patch("os.path.exists", return_value=True) @patch("shutil.which") def test_run_builds_and_installs_with_makepkg( diff --git a/tests/unit/pkgmgr/actions/install/installers/os_packages/test_debian_control.py b/tests/unit/pkgmgr/actions/install/installers/os_packages/test_debian_control.py index 3cca211..a6a07a8 100644 --- a/tests/unit/pkgmgr/actions/install/installers/os_packages/test_debian_control.py +++ b/tests/unit/pkgmgr/actions/install/installers/os_packages/test_debian_control.py @@ -43,9 +43,7 @@ class TestDebianControlInstaller(unittest.TestCase): """ self.assertFalse(self.installer.supports(self.ctx)) - @patch( - "pkgmgr.actions.install.installers.os_packages.debian_control.run_command" - ) + @patch("pkgmgr.actions.install.installers.os_packages.debian_control.run_command") @patch("glob.glob", return_value=["/tmp/package-manager_0.1.1_all.deb"]) @patch("os.path.exists", return_value=True) @patch("shutil.which") @@ -88,9 +86,7 @@ class TestDebianControlInstaller(unittest.TestCase): # 2) apt-get build-dep -y ./ (with or without trailing space) self.assertTrue( any( - "apt-get build-dep -y ./ " in cmd - or "apt-get build-dep -y ./" - in cmd + "apt-get build-dep -y ./ " in cmd or "apt-get build-dep -y ./" in cmd for cmd in cmds ) ) diff --git a/tests/unit/pkgmgr/actions/install/installers/test_makefile_installer.py b/tests/unit/pkgmgr/actions/install/installers/test_makefile_installer.py index 3290301..3f2e638 100644 --- a/tests/unit/pkgmgr/actions/install/installers/test_makefile_installer.py +++ b/tests/unit/pkgmgr/actions/install/installers/test_makefile_installer.py @@ -26,10 +26,10 @@ class TestMakefileInstaller(unittest.TestCase): ) self.installer = MakefileInstaller() -# @patch("os.path.exists", return_value=True) -# def test_supports_true_when_makefile_exists(self, mock_exists): -# self.assertTrue(self.installer.supports(self.ctx)) -# mock_exists.assert_called_with(os.path.join(self.ctx.repo_dir, "Makefile")) + # @patch("os.path.exists", return_value=True) + # def test_supports_true_when_makefile_exists(self, mock_exists): + # self.assertTrue(self.installer.supports(self.ctx)) + # mock_exists.assert_called_with(os.path.join(self.ctx.repo_dir, "Makefile")) @patch("os.path.exists", return_value=False) def test_supports_false_when_makefile_missing(self, mock_exists): diff --git a/tests/unit/pkgmgr/actions/install/test_capabilities.py b/tests/unit/pkgmgr/actions/install/test_capabilities.py index f264a14..44b59d3 100644 --- a/tests/unit/pkgmgr/actions/install/test_capabilities.py +++ b/tests/unit/pkgmgr/actions/install/test_capabilities.py @@ -152,7 +152,9 @@ class TestDetectCapabilities(unittest.TestCase): }, ) - with patch("pkgmgr.actions.install.capabilities.CAPABILITY_MATCHERS", [dummy1, dummy2]): + with patch( + "pkgmgr.actions.install.capabilities.CAPABILITY_MATCHERS", [dummy1, dummy2] + ): caps = detect_capabilities(self.ctx, layers) self.assertEqual( @@ -282,7 +284,9 @@ class TestResolveEffectiveCapabilities(unittest.TestCase): }, ) - with patch("pkgmgr.actions.install.capabilities.CAPABILITY_MATCHERS", [cap_only_make]): + with patch( + "pkgmgr.actions.install.capabilities.CAPABILITY_MATCHERS", [cap_only_make] + ): effective = resolve_effective_capabilities(self.ctx, layers) self.assertEqual(effective["makefile"], {"make-install"}) @@ -305,7 +309,9 @@ class TestResolveEffectiveCapabilities(unittest.TestCase): }, ) - with patch("pkgmgr.actions.install.capabilities.CAPABILITY_MATCHERS", [cap_only_nix]): + with patch( + "pkgmgr.actions.install.capabilities.CAPABILITY_MATCHERS", [cap_only_nix] + ): effective = resolve_effective_capabilities(self.ctx, layers) self.assertEqual(effective["makefile"], set()) diff --git a/tests/unit/pkgmgr/actions/install/test_context.py b/tests/unit/pkgmgr/actions/install/test_context.py index c28d129..af57607 100644 --- a/tests/unit/pkgmgr/actions/install/test_context.py +++ b/tests/unit/pkgmgr/actions/install/test_context.py @@ -33,4 +33,4 @@ class TestRepoContext(unittest.TestCase): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/unit/pkgmgr/actions/mirror/test_context.py b/tests/unit/pkgmgr/actions/mirror/test_context.py index 82e3476..cd17d8c 100644 --- a/tests/unit/pkgmgr/actions/mirror/test_context.py +++ b/tests/unit/pkgmgr/actions/mirror/test_context.py @@ -36,8 +36,12 @@ class TestMirrorContext(unittest.TestCase): self.assertEqual(ctx.identifier, "id") self.assertEqual(ctx.repo_dir, "/tmp/repo") - self.assertEqual(ctx.config_mirrors, {"origin": "git@github.com:alice/repo.git"}) - self.assertEqual(ctx.file_mirrors, {"backup": "ssh://git@backup/alice/repo.git"}) + self.assertEqual( + ctx.config_mirrors, {"origin": "git@github.com:alice/repo.git"} + ) + self.assertEqual( + ctx.file_mirrors, {"backup": "ssh://git@backup/alice/repo.git"} + ) self.assertEqual( ctx.resolved_mirrors, { diff --git a/tests/unit/pkgmgr/actions/mirror/test_diff_cmd.py b/tests/unit/pkgmgr/actions/mirror/test_diff_cmd.py index 2c237a9..12a75a2 100644 --- a/tests/unit/pkgmgr/actions/mirror/test_diff_cmd.py +++ b/tests/unit/pkgmgr/actions/mirror/test_diff_cmd.py @@ -17,7 +17,9 @@ class TestDiffCmd(unittest.TestCase): """ @patch("pkgmgr.actions.mirror.diff_cmd.build_context") - def test_diff_mirrors_reports_only_in_config_and_only_in_file(self, mock_build_context) -> None: + def test_diff_mirrors_reports_only_in_config_and_only_in_file( + self, mock_build_context + ) -> None: ctx = MagicMock() ctx.identifier = "id" ctx.repo_dir = "/tmp/repo" @@ -30,7 +32,9 @@ class TestDiffCmd(unittest.TestCase): buf = io.StringIO() with redirect_stdout(buf): - diff_mirrors(selected_repos=[{}], repositories_base_dir="/base", all_repos=[]) + diff_mirrors( + selected_repos=[{}], repositories_base_dir="/base", all_repos=[] + ) out = buf.getvalue() self.assertIn("[ONLY IN CONFIG] cfgonly: b", out) @@ -48,7 +52,9 @@ class TestDiffCmd(unittest.TestCase): buf = io.StringIO() with redirect_stdout(buf): - diff_mirrors(selected_repos=[{}], repositories_base_dir="/base", all_repos=[]) + diff_mirrors( + selected_repos=[{}], repositories_base_dir="/base", all_repos=[] + ) out = buf.getvalue() self.assertIn("[URL MISMATCH]", out) @@ -67,7 +73,9 @@ class TestDiffCmd(unittest.TestCase): buf = io.StringIO() with redirect_stdout(buf): - diff_mirrors(selected_repos=[{}], repositories_base_dir="/base", all_repos=[]) + diff_mirrors( + selected_repos=[{}], repositories_base_dir="/base", all_repos=[] + ) out = buf.getvalue() self.assertIn("[OK] Mirrors in config and MIRRORS file are in sync.", out) diff --git a/tests/unit/pkgmgr/actions/mirror/test_git_remote.py b/tests/unit/pkgmgr/actions/mirror/test_git_remote.py index cf3659d..4b85e88 100644 --- a/tests/unit/pkgmgr/actions/mirror/test_git_remote.py +++ b/tests/unit/pkgmgr/actions/mirror/test_git_remote.py @@ -28,7 +28,9 @@ class TestMirrorGitRemote(unittest.TestCase): # resolved_mirrors = config + file (file wins), so put origin in file. repo = {"provider": "github.com", "account": "alice", "repository": "repo"} ctx = self._ctx(file={"origin": "git@github.com:alice/repo.git"}) - self.assertEqual(determine_primary_remote_url(repo, ctx), "git@github.com:alice/repo.git") + self.assertEqual( + determine_primary_remote_url(repo, ctx), "git@github.com:alice/repo.git" + ) def test_determine_primary_falls_back_to_file_order(self) -> None: repo = {"provider": "github.com", "account": "alice", "repository": "repo"} @@ -43,9 +45,14 @@ class TestMirrorGitRemote(unittest.TestCase): def test_determine_primary_fallback_default(self) -> None: repo = {"provider": "github.com", "account": "alice", "repository": "repo"} ctx = self._ctx() - self.assertEqual(determine_primary_remote_url(repo, ctx), "git@github.com:alice/repo.git") + self.assertEqual( + determine_primary_remote_url(repo, ctx), "git@github.com:alice/repo.git" + ) - @patch("pkgmgr.actions.mirror.git_remote.list_remotes", return_value=["origin", "backup"]) + @patch( + "pkgmgr.actions.mirror.git_remote.list_remotes", + return_value=["origin", "backup"], + ) def test_has_origin_remote_true(self, _m_list) -> None: self.assertTrue(has_origin_remote("/tmp/repo")) diff --git a/tests/unit/pkgmgr/actions/mirror/test_git_remote_primary_push.py b/tests/unit/pkgmgr/actions/mirror/test_git_remote_primary_push.py index 3ac1fd9..1a27c55 100644 --- a/tests/unit/pkgmgr/actions/mirror/test_git_remote_primary_push.py +++ b/tests/unit/pkgmgr/actions/mirror/test_git_remote_primary_push.py @@ -23,15 +23,23 @@ class TestGitRemotePrimaryPush(unittest.TestCase): ) with patch("os.path.isdir", return_value=True): - with patch("pkgmgr.actions.mirror.git_remote.has_origin_remote", return_value=False), patch( - "pkgmgr.actions.mirror.git_remote.add_remote" - ) as m_add_remote, patch( - "pkgmgr.actions.mirror.git_remote.set_remote_url" - ) as m_set_remote_url, patch( - "pkgmgr.actions.mirror.git_remote.get_remote_push_urls", return_value=set() - ), patch( - "pkgmgr.actions.mirror.git_remote.add_remote_push_url" - ) as m_add_push: + with ( + patch( + "pkgmgr.actions.mirror.git_remote.has_origin_remote", + return_value=False, + ), + patch("pkgmgr.actions.mirror.git_remote.add_remote") as m_add_remote, + patch( + "pkgmgr.actions.mirror.git_remote.set_remote_url" + ) as m_set_remote_url, + patch( + "pkgmgr.actions.mirror.git_remote.get_remote_push_urls", + return_value=set(), + ), + patch( + "pkgmgr.actions.mirror.git_remote.add_remote_push_url" + ) as m_add_push, + ): ensure_origin_remote(repo, ctx, preview=False) # determine_primary_remote_url falls back to file order (primary first) diff --git a/tests/unit/pkgmgr/actions/mirror/test_io.py b/tests/unit/pkgmgr/actions/mirror/test_io.py index fb8c268..19b01c8 100644 --- a/tests/unit/pkgmgr/actions/mirror/test_io.py +++ b/tests/unit/pkgmgr/actions/mirror/test_io.py @@ -7,7 +7,11 @@ import os import tempfile import unittest -from pkgmgr.actions.mirror.io import load_config_mirrors, read_mirrors_file, write_mirrors_file +from pkgmgr.actions.mirror.io import ( + load_config_mirrors, + read_mirrors_file, + write_mirrors_file, +) class TestMirrorIO(unittest.TestCase): @@ -76,7 +80,10 @@ class TestMirrorIO(unittest.TestCase): self.assertEqual(mirrors["github.com2"], "https://github.com/alice/repo2") self.assertIn("git@git.veen.world", mirrors) - self.assertEqual(mirrors["git@git.veen.world"], "ssh://git@git.veen.world:2201/alice/repo3.git") + self.assertEqual( + mirrors["git@git.veen.world"], + "ssh://git@git.veen.world:2201/alice/repo3.git", + ) def test_read_mirrors_file_missing_returns_empty(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: @@ -96,7 +103,9 @@ class TestMirrorIO(unittest.TestCase): with open(p, "r", encoding="utf-8") as fh: content = fh.read() - self.assertEqual(content, "a ssh://a.example/repo.git\nb ssh://b.example/repo.git\n") + self.assertEqual( + content, "a ssh://a.example/repo.git\nb ssh://b.example/repo.git\n" + ) def test_write_mirrors_file_preview_does_not_create_file(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: diff --git a/tests/unit/pkgmgr/actions/mirror/test_list_cmd.py b/tests/unit/pkgmgr/actions/mirror/test_list_cmd.py index 3e41097..71f7d59 100644 --- a/tests/unit/pkgmgr/actions/mirror/test_list_cmd.py +++ b/tests/unit/pkgmgr/actions/mirror/test_list_cmd.py @@ -23,7 +23,9 @@ class TestListCmd(unittest.TestCase): ctx.repo_dir = "/tmp/repo" ctx.config_mirrors = {"origin": "a"} ctx.file_mirrors = {"backup": "b"} - type(ctx).resolved_mirrors = PropertyMock(return_value={"origin": "a", "backup": "b"}) + type(ctx).resolved_mirrors = PropertyMock( + return_value={"origin": "a", "backup": "b"} + ) mock_build_context.return_value = ctx buf = io.StringIO() @@ -49,7 +51,9 @@ class TestListCmd(unittest.TestCase): ctx.repo_dir = "/tmp/repo" ctx.config_mirrors = {"origin": "a"} ctx.file_mirrors = {"backup": "b"} - type(ctx).resolved_mirrors = PropertyMock(return_value={"origin": "a", "backup": "b"}) + type(ctx).resolved_mirrors = PropertyMock( + return_value={"origin": "a", "backup": "b"} + ) mock_build_context.return_value = ctx buf = io.StringIO() diff --git a/tests/unit/pkgmgr/actions/mirror/test_remote_provision.py b/tests/unit/pkgmgr/actions/mirror/test_remote_provision.py index 209873a..333162b 100644 --- a/tests/unit/pkgmgr/actions/mirror/test_remote_provision.py +++ b/tests/unit/pkgmgr/actions/mirror/test_remote_provision.py @@ -30,7 +30,9 @@ class TestRemoteProvision(unittest.TestCase): ctx.identifier = "repo-id" mock_build_context.return_value = ctx - mock_determine_primary.return_value = "ssh://git@git.veen.world:2201/alice/repo.git" + mock_determine_primary.return_value = ( + "ssh://git@git.veen.world:2201/alice/repo.git" + ) result = MagicMock() result.status = "created" diff --git a/tests/unit/pkgmgr/actions/mirror/test_setup_cmd.py b/tests/unit/pkgmgr/actions/mirror/test_setup_cmd.py index ad7cedf..bff6ab2 100644 --- a/tests/unit/pkgmgr/actions/mirror/test_setup_cmd.py +++ b/tests/unit/pkgmgr/actions/mirror/test_setup_cmd.py @@ -8,7 +8,9 @@ from pkgmgr.actions.mirror.types import RepoMirrorContext class TestMirrorSetupCmd(unittest.TestCase): - def _ctx(self, *, repo_dir: str = "/tmp/repo", resolved: dict[str, str] | None = None) -> RepoMirrorContext: + def _ctx( + self, *, repo_dir: str = "/tmp/repo", resolved: dict[str, str] | None = None + ) -> RepoMirrorContext: # resolved_mirrors is a @property combining config+file. Put it into file_mirrors. return RepoMirrorContext( identifier="repo", @@ -19,7 +21,9 @@ class TestMirrorSetupCmd(unittest.TestCase): @patch("pkgmgr.actions.mirror.setup_cmd.build_context") @patch("pkgmgr.actions.mirror.setup_cmd.ensure_origin_remote") - def test_setup_mirrors_local_calls_ensure_origin_remote(self, m_ensure, m_ctx) -> None: + def test_setup_mirrors_local_calls_ensure_origin_remote( + self, m_ensure, m_ctx + ) -> None: ctx = self._ctx(repo_dir="/tmp/repo", resolved={"primary": "git@x/y.git"}) m_ctx.return_value = ctx @@ -40,12 +44,16 @@ class TestMirrorSetupCmd(unittest.TestCase): self.assertEqual(args[0], repos[0]) self.assertIs(args[1], ctx) - self.assertEqual(kwargs.get("preview", args[2] if len(args) >= 3 else None), True) + self.assertEqual( + kwargs.get("preview", args[2] if len(args) >= 3 else None), True + ) @patch("pkgmgr.actions.mirror.setup_cmd.build_context") @patch("pkgmgr.actions.mirror.setup_cmd.determine_primary_remote_url") @patch("pkgmgr.actions.mirror.setup_cmd.probe_remote_reachable") - def test_setup_mirrors_remote_no_mirrors_probes_primary(self, m_probe, m_primary, m_ctx) -> None: + def test_setup_mirrors_remote_no_mirrors_probes_primary( + self, m_probe, m_primary, m_ctx + ) -> None: m_ctx.return_value = self._ctx(repo_dir="/tmp/repo", resolved={}) m_primary.return_value = "git@github.com:alice/repo.git" m_probe.return_value = True @@ -62,11 +70,15 @@ class TestMirrorSetupCmd(unittest.TestCase): ) m_primary.assert_called() - m_probe.assert_called_once_with("git@github.com:alice/repo.git", cwd="/tmp/repo") + m_probe.assert_called_once_with( + "git@github.com:alice/repo.git", cwd="/tmp/repo" + ) @patch("pkgmgr.actions.mirror.setup_cmd.build_context") @patch("pkgmgr.actions.mirror.setup_cmd.probe_remote_reachable") - def test_setup_mirrors_remote_with_mirrors_probes_each(self, m_probe, m_ctx) -> None: + def test_setup_mirrors_remote_with_mirrors_probes_each( + self, m_probe, m_ctx + ) -> None: m_ctx.return_value = self._ctx( repo_dir="/tmp/repo", resolved={ @@ -89,7 +101,9 @@ class TestMirrorSetupCmd(unittest.TestCase): self.assertEqual(m_probe.call_count, 2) m_probe.assert_any_call("git@github.com:alice/repo.git", cwd="/tmp/repo") - m_probe.assert_any_call("ssh://git@git.veen.world:2201/alice/repo.git", cwd="/tmp/repo") + m_probe.assert_any_call( + "ssh://git@git.veen.world:2201/alice/repo.git", cwd="/tmp/repo" + ) if __name__ == "__main__": diff --git a/tests/unit/pkgmgr/actions/mirror/test_url_utils.py b/tests/unit/pkgmgr/actions/mirror/test_url_utils.py index 556be61..2e74a06 100644 --- a/tests/unit/pkgmgr/actions/mirror/test_url_utils.py +++ b/tests/unit/pkgmgr/actions/mirror/test_url_utils.py @@ -5,7 +5,11 @@ from __future__ import annotations import unittest -from pkgmgr.actions.mirror.url_utils import hostport_from_git_url, normalize_provider_host, parse_repo_from_git_url +from pkgmgr.actions.mirror.url_utils import ( + hostport_from_git_url, + normalize_provider_host, + parse_repo_from_git_url, +) class TestUrlUtils(unittest.TestCase): @@ -14,7 +18,9 @@ class TestUrlUtils(unittest.TestCase): """ def test_hostport_from_git_url_ssh_url_with_port(self) -> None: - host, port = hostport_from_git_url("ssh://git@code.example.org:2201/alice/repo.git") + host, port = hostport_from_git_url( + "ssh://git@code.example.org:2201/alice/repo.git" + ) self.assertEqual(host, "code.example.org") self.assertEqual(port, "2201") @@ -34,7 +40,9 @@ class TestUrlUtils(unittest.TestCase): self.assertIsNone(port) def test_normalize_provider_host_strips_port_and_lowercases(self) -> None: - self.assertEqual(normalize_provider_host("GIT.VEEN.WORLD:2201"), "git.veen.world") + self.assertEqual( + normalize_provider_host("GIT.VEEN.WORLD:2201"), "git.veen.world" + ) def test_normalize_provider_host_ipv6_brackets(self) -> None: self.assertEqual(normalize_provider_host("[::1]"), "::1") @@ -43,7 +51,9 @@ class TestUrlUtils(unittest.TestCase): self.assertEqual(normalize_provider_host(""), "") def test_parse_repo_from_git_url_ssh_url(self) -> None: - host, owner, name = parse_repo_from_git_url("ssh://git@code.example.org:2201/alice/repo.git") + host, owner, name = parse_repo_from_git_url( + "ssh://git@code.example.org:2201/alice/repo.git" + ) self.assertEqual(host, "code.example.org") self.assertEqual(owner, "alice") self.assertEqual(name, "repo") diff --git a/tests/unit/pkgmgr/actions/publish/test_git_tags.py b/tests/unit/pkgmgr/actions/publish/test_git_tags.py index 445fce6..386dd49 100644 --- a/tests/unit/pkgmgr/actions/publish/test_git_tags.py +++ b/tests/unit/pkgmgr/actions/publish/test_git_tags.py @@ -9,6 +9,9 @@ class TestHeadSemverTags(unittest.TestCase): def test_no_tags(self, _mock_get_tags_at_ref) -> None: self.assertEqual(head_semver_tags(), []) - @patch("pkgmgr.actions.publish.git_tags.get_tags_at_ref", return_value=["v2.0.0", "nope", "v1.0.0", "v1.2.0"]) + @patch( + "pkgmgr.actions.publish.git_tags.get_tags_at_ref", + return_value=["v2.0.0", "nope", "v1.0.0", "v1.2.0"], + ) def test_filters_and_sorts_semver(self, _mock_get_tags_at_ref) -> None: self.assertEqual(head_semver_tags(), ["v1.0.0", "v1.2.0", "v2.0.0"]) diff --git a/tests/unit/pkgmgr/actions/publish/test_pypi_url.py b/tests/unit/pkgmgr/actions/publish/test_pypi_url.py index 376df3c..77b874f 100644 --- a/tests/unit/pkgmgr/actions/publish/test_pypi_url.py +++ b/tests/unit/pkgmgr/actions/publish/test_pypi_url.py @@ -1,4 +1,3 @@ - import unittest from pkgmgr.actions.publish.pypi_url import parse_pypi_project_url diff --git a/tests/unit/pkgmgr/actions/publish/test_workflow_preview.py b/tests/unit/pkgmgr/actions/publish/test_workflow_preview.py index cb367bb..9411839 100644 --- a/tests/unit/pkgmgr/actions/publish/test_workflow_preview.py +++ b/tests/unit/pkgmgr/actions/publish/test_workflow_preview.py @@ -1,4 +1,3 @@ - import unittest from unittest.mock import patch @@ -9,9 +8,7 @@ class TestPublishWorkflowPreview(unittest.TestCase): @patch("pkgmgr.actions.publish.workflow.read_mirrors_file") @patch("pkgmgr.actions.publish.workflow.head_semver_tags") def test_preview_does_not_build(self, mock_tags, mock_mirrors): - mock_mirrors.return_value = { - "pypi": "https://pypi.org/project/example/" - } + mock_mirrors.return_value = {"pypi": "https://pypi.org/project/example/"} mock_tags.return_value = ["v1.0.0"] publish( diff --git a/tests/unit/pkgmgr/actions/release/test_files.py b/tests/unit/pkgmgr/actions/release/test_files.py index 1c9496e..c2861c8 100644 --- a/tests/unit/pkgmgr/actions/release/test_files.py +++ b/tests/unit/pkgmgr/actions/release/test_files.py @@ -19,13 +19,16 @@ from pkgmgr.actions.release.files import ( class TestUpdatePyprojectVersion(unittest.TestCase): def test_update_pyproject_version_replaces_version_line(self) -> None: - original = textwrap.dedent( - """ + original = ( + textwrap.dedent( + """ [project] name = "example" version = "0.1.0" """ - ).strip() + "\n" + ).strip() + + "\n" + ) with tempfile.TemporaryDirectory() as tmpdir: path = os.path.join(tmpdir, "pyproject.toml") @@ -41,13 +44,16 @@ class TestUpdatePyprojectVersion(unittest.TestCase): self.assertNotIn('version = "0.1.0"', content) def test_update_pyproject_version_preview_does_not_write(self) -> None: - original = textwrap.dedent( - """ + original = ( + textwrap.dedent( + """ [project] name = "example" version = "0.1.0" """ - ).strip() + "\n" + ).strip() + + "\n" + ) with tempfile.TemporaryDirectory() as tmpdir: path = os.path.join(tmpdir, "pyproject.toml") @@ -63,12 +69,15 @@ class TestUpdatePyprojectVersion(unittest.TestCase): self.assertEqual(content, original) def test_update_pyproject_version_exits_when_no_version_line_found(self) -> None: - original = textwrap.dedent( - """ + original = ( + textwrap.dedent( + """ [project] name = "example" """ - ).strip() + "\n" + ).strip() + + "\n" + ) with tempfile.TemporaryDirectory() as tmpdir: path = os.path.join(tmpdir, "pyproject.toml") @@ -129,13 +138,16 @@ class TestUpdateFlakeVersion(unittest.TestCase): class TestUpdatePkgbuildVersion(unittest.TestCase): def test_update_pkgbuild_version_normal(self) -> None: - original = textwrap.dedent( - """ + original = ( + textwrap.dedent( + """ pkgname=example pkgver=0.1.0 pkgrel=5 """ - ).strip() + "\n" + ).strip() + + "\n" + ) with tempfile.TemporaryDirectory() as tmpdir: path = os.path.join(tmpdir, "PKGBUILD") @@ -152,13 +164,16 @@ class TestUpdatePkgbuildVersion(unittest.TestCase): self.assertNotIn("pkgver=0.1.0", content) def test_update_pkgbuild_version_preview(self) -> None: - original = textwrap.dedent( - """ + original = ( + textwrap.dedent( + """ pkgname=example pkgver=0.1.0 pkgrel=5 """ - ).strip() + "\n" + ).strip() + + "\n" + ) with tempfile.TemporaryDirectory() as tmpdir: path = os.path.join(tmpdir, "PKGBUILD") @@ -175,13 +190,16 @@ class TestUpdatePkgbuildVersion(unittest.TestCase): class TestUpdateSpecVersion(unittest.TestCase): def test_update_spec_version_normal(self) -> None: - original = textwrap.dedent( - """ + original = ( + textwrap.dedent( + """ Name: package-manager Version: 0.1.0 Release: 5%{?dist} """ - ).strip() + "\n" + ).strip() + + "\n" + ) with tempfile.TemporaryDirectory() as tmpdir: path = os.path.join(tmpdir, "package-manager.spec") @@ -199,13 +217,16 @@ class TestUpdateSpecVersion(unittest.TestCase): self.assertNotIn("Release: 5%{?dist}", content) def test_update_spec_version_preview(self) -> None: - original = textwrap.dedent( - """ + original = ( + textwrap.dedent( + """ Name: package-manager Version: 0.1.0 Release: 5%{?dist} """ - ).strip() + "\n" + ).strip() + + "\n" + ) with tempfile.TemporaryDirectory() as tmpdir: path = os.path.join(tmpdir, "package-manager.spec") @@ -328,8 +349,9 @@ class TestUpdateDebianChangelog(unittest.TestCase): class TestUpdateSpecChangelog(unittest.TestCase): def test_update_spec_changelog_inserts_stanza_after_changelog_marker(self) -> None: - original = textwrap.dedent( - """ + original = ( + textwrap.dedent( + """ Name: package-manager Version: 0.1.0 Release: 5%{?dist} @@ -341,7 +363,9 @@ class TestUpdateSpecChangelog(unittest.TestCase): * Mon Jan 01 2024 Old Maintainer - 0.1.0-1 - Old entry """ - ).strip() + "\n" + ).strip() + + "\n" + ) with tempfile.TemporaryDirectory() as tmpdir: path = os.path.join(tmpdir, "package-manager.spec") @@ -375,8 +399,9 @@ class TestUpdateSpecChangelog(unittest.TestCase): self.assertIn("Old Maintainer ", content) def test_update_spec_changelog_preview_does_not_write(self) -> None: - original = textwrap.dedent( - """ + original = ( + textwrap.dedent( + """ Name: package-manager Version: 0.1.0 Release: 5%{?dist} @@ -385,7 +410,9 @@ class TestUpdateSpecChangelog(unittest.TestCase): * Mon Jan 01 2024 Old Maintainer - 0.1.0-1 - Old entry """ - ).strip() + "\n" + ).strip() + + "\n" + ) with tempfile.TemporaryDirectory() as tmpdir: path = os.path.join(tmpdir, "package-manager.spec") diff --git a/tests/unit/pkgmgr/actions/release/test_git_ops_is_highest_version_tag.py b/tests/unit/pkgmgr/actions/release/test_git_ops_is_highest_version_tag.py index fab6ce1..983b5ee 100644 --- a/tests/unit/pkgmgr/actions/release/test_git_ops_is_highest_version_tag.py +++ b/tests/unit/pkgmgr/actions/release/test_git_ops_is_highest_version_tag.py @@ -23,18 +23,20 @@ class TestIsHighestVersionTag(unittest.TestCase): self.assertFalse(is_highest_version_tag("v1.0.0")) @patch("pkgmgr.actions.release.git_ops.list_tags") - def test_ignores_non_parseable_v_tags_for_semver_compare(self, mock_list_tags) -> None: + def test_ignores_non_parseable_v_tags_for_semver_compare( + self, mock_list_tags + ) -> None: mock_list_tags.return_value = ["v1.2.0", "v1.10.0", "v1.2.0-rc1", "vfoo"] self.assertTrue(is_highest_version_tag("v1.10.0")) self.assertFalse(is_highest_version_tag("v1.2.0")) @patch("pkgmgr.actions.release.git_ops.list_tags") - def test_current_tag_not_parseable_falls_back_to_lex_compare(self, mock_list_tags) -> None: + def test_current_tag_not_parseable_falls_back_to_lex_compare( + self, mock_list_tags + ) -> None: mock_list_tags.return_value = ["v1.9.0", "v1.10.0"] # prerelease must NOT outrank the final release self.assertFalse(is_highest_version_tag("v1.10.0-rc1")) self.assertFalse(is_highest_version_tag("v1.0.0-rc1")) - - diff --git a/tests/unit/pkgmgr/actions/release/test_workflow.py b/tests/unit/pkgmgr/actions/release/test_workflow.py index 015ca3f..dc14f3f 100644 --- a/tests/unit/pkgmgr/actions/release/test_workflow.py +++ b/tests/unit/pkgmgr/actions/release/test_workflow.py @@ -18,7 +18,9 @@ class TestWorkflowReleaseEntryPoint(unittest.TestCase): @patch("pkgmgr.actions.release.workflow._release_impl") @patch("pkgmgr.actions.release.workflow.sys.stdin.isatty", return_value=False) - def test_release_non_interactive_runs_real_without_confirmation(self, _mock_isatty, mock_impl) -> None: + def test_release_non_interactive_runs_real_without_confirmation( + self, _mock_isatty, mock_impl + ) -> None: release(preview=False, force=False, close=False) mock_impl.assert_called_once() @@ -35,9 +37,13 @@ class TestWorkflowReleaseEntryPoint(unittest.TestCase): self.assertTrue(kwargs["force"]) @patch("pkgmgr.actions.release.workflow._release_impl") - @patch("pkgmgr.actions.release.workflow.confirm_proceed_release", return_value=False) + @patch( + "pkgmgr.actions.release.workflow.confirm_proceed_release", return_value=False + ) @patch("pkgmgr.actions.release.workflow.sys.stdin.isatty", return_value=True) - def test_release_interactive_decline_runs_only_preview(self, _mock_isatty, _mock_confirm, mock_impl) -> None: + def test_release_interactive_decline_runs_only_preview( + self, _mock_isatty, _mock_confirm, mock_impl + ) -> None: release(preview=False, force=False, close=False) # interactive path: preview first, then decline => only one call @@ -47,7 +53,9 @@ class TestWorkflowReleaseEntryPoint(unittest.TestCase): @patch("pkgmgr.actions.release.workflow._release_impl") @patch("pkgmgr.actions.release.workflow.confirm_proceed_release", return_value=True) @patch("pkgmgr.actions.release.workflow.sys.stdin.isatty", return_value=True) - def test_release_interactive_accept_runs_preview_then_real(self, _mock_isatty, _mock_confirm, mock_impl) -> None: + def test_release_interactive_accept_runs_preview_then_real( + self, _mock_isatty, _mock_confirm, mock_impl + ) -> None: release(preview=False, force=False, close=False) self.assertEqual(mock_impl.call_count, 2) diff --git a/tests/unit/pkgmgr/actions/repository/create/test_scaffold_render_preview.py b/tests/unit/pkgmgr/actions/repository/create/test_scaffold_render_preview.py index 13719e5..524688d 100644 --- a/tests/unit/pkgmgr/actions/repository/create/test_scaffold_render_preview.py +++ b/tests/unit/pkgmgr/actions/repository/create/test_scaffold_render_preview.py @@ -40,4 +40,4 @@ class TestTemplateRendererPreview(unittest.TestCase): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tests/unit/pkgmgr/actions/repository/test_deinstall.py b/tests/unit/pkgmgr/actions/repository/test_deinstall.py index 36bb0aa..905ae62 100644 --- a/tests/unit/pkgmgr/actions/repository/test_deinstall.py +++ b/tests/unit/pkgmgr/actions/repository/test_deinstall.py @@ -6,17 +6,32 @@ from pkgmgr.actions.repository.deinstall import deinstall_repos class TestDeinstallRepos(unittest.TestCase): def test_preview_removes_nothing_but_runs_make_if_makefile_exists(self): - repo = {"provider": "github.com", "account": "alice", "repository": "demo", "alias": "demo"} + repo = { + "provider": "github.com", + "account": "alice", + "repository": "demo", + "alias": "demo", + } selected = [repo] - with patch("pkgmgr.actions.repository.deinstall.get_repo_identifier", return_value="demo"), \ - patch("pkgmgr.actions.repository.deinstall.get_repo_dir", return_value="/repos/github.com/alice/demo"), \ - patch("pkgmgr.actions.repository.deinstall.os.path.expanduser", return_value="/home/u/.local/bin"), \ - patch("pkgmgr.actions.repository.deinstall.os.path.exists") as mock_exists, \ - patch("pkgmgr.actions.repository.deinstall.os.remove") as mock_remove, \ - patch("pkgmgr.actions.repository.deinstall.run_command") as mock_run, \ - patch("builtins.input", return_value="y"): - + with ( + patch( + "pkgmgr.actions.repository.deinstall.get_repo_identifier", + return_value="demo", + ), + patch( + "pkgmgr.actions.repository.deinstall.get_repo_dir", + return_value="/repos/github.com/alice/demo", + ), + patch( + "pkgmgr.actions.repository.deinstall.os.path.expanduser", + return_value="/home/u/.local/bin", + ), + patch("pkgmgr.actions.repository.deinstall.os.path.exists") as mock_exists, + patch("pkgmgr.actions.repository.deinstall.os.remove") as mock_remove, + patch("pkgmgr.actions.repository.deinstall.run_command") as mock_run, + patch("builtins.input", return_value="y"), + ): # alias exists, Makefile exists def exists_side_effect(path): if path == "/home/u/.local/bin/demo": @@ -46,17 +61,32 @@ class TestDeinstallRepos(unittest.TestCase): ) def test_non_preview_removes_alias_when_confirmed(self): - repo = {"provider": "github.com", "account": "alice", "repository": "demo", "alias": "demo"} + repo = { + "provider": "github.com", + "account": "alice", + "repository": "demo", + "alias": "demo", + } selected = [repo] - with patch("pkgmgr.actions.repository.deinstall.get_repo_identifier", return_value="demo"), \ - patch("pkgmgr.actions.repository.deinstall.get_repo_dir", return_value="/repos/github.com/alice/demo"), \ - patch("pkgmgr.actions.repository.deinstall.os.path.expanduser", return_value="/home/u/.local/bin"), \ - patch("pkgmgr.actions.repository.deinstall.os.path.exists") as mock_exists, \ - patch("pkgmgr.actions.repository.deinstall.os.remove") as mock_remove, \ - patch("pkgmgr.actions.repository.deinstall.run_command") as mock_run, \ - patch("builtins.input", return_value="y"): - + with ( + patch( + "pkgmgr.actions.repository.deinstall.get_repo_identifier", + return_value="demo", + ), + patch( + "pkgmgr.actions.repository.deinstall.get_repo_dir", + return_value="/repos/github.com/alice/demo", + ), + patch( + "pkgmgr.actions.repository.deinstall.os.path.expanduser", + return_value="/home/u/.local/bin", + ), + patch("pkgmgr.actions.repository.deinstall.os.path.exists") as mock_exists, + patch("pkgmgr.actions.repository.deinstall.os.remove") as mock_remove, + patch("pkgmgr.actions.repository.deinstall.run_command") as mock_run, + patch("builtins.input", return_value="y"), + ): # alias exists, Makefile does NOT exist def exists_side_effect(path): if path == "/home/u/.local/bin/demo": diff --git a/tests/unit/pkgmgr/actions/test_changelog.py b/tests/unit/pkgmgr/actions/test_changelog.py index cbcc7e7..338ee19 100644 --- a/tests/unit/pkgmgr/actions/test_changelog.py +++ b/tests/unit/pkgmgr/actions/test_changelog.py @@ -10,7 +10,9 @@ from pkgmgr.cli.commands.changelog import _find_previous_and_current_tag class TestGenerateChangelog(unittest.TestCase): @patch("pkgmgr.actions.changelog.get_changelog") - def test_generate_changelog_default_range_no_merges(self, mock_get_changelog) -> None: + def test_generate_changelog_default_range_no_merges( + self, mock_get_changelog + ) -> None: mock_get_changelog.return_value = "abc123 (HEAD -> main) Initial commit" output = generate_changelog(cwd="/repo") @@ -43,7 +45,9 @@ class TestGenerateChangelog(unittest.TestCase): ) @patch("pkgmgr.actions.changelog.get_changelog") - def test_generate_changelog_giterror_returns_error_message(self, mock_get_changelog) -> None: + def test_generate_changelog_giterror_returns_error_message( + self, mock_get_changelog + ) -> None: mock_get_changelog.side_effect = GitChangelogQueryError("simulated git failure") result = generate_changelog(cwd="/repo", from_ref="v0.1.0", to_ref="v0.2.0") @@ -53,7 +57,9 @@ class TestGenerateChangelog(unittest.TestCase): self.assertIn("v0.1.0..v0.2.0", result) @patch("pkgmgr.actions.changelog.get_changelog") - def test_generate_changelog_empty_output_returns_info(self, mock_get_changelog) -> None: + def test_generate_changelog_empty_output_returns_info( + self, mock_get_changelog + ) -> None: mock_get_changelog.return_value = " \n " result = generate_changelog(cwd="/repo", from_ref=None, to_ref="HEAD") diff --git a/tests/unit/pkgmgr/cli/commands/test_publish.py b/tests/unit/pkgmgr/cli/commands/test_publish.py index ee2d4a9..476ba4d 100644 --- a/tests/unit/pkgmgr/cli/commands/test_publish.py +++ b/tests/unit/pkgmgr/cli/commands/test_publish.py @@ -1,4 +1,3 @@ - import unittest from unittest.mock import patch diff --git a/tests/unit/pkgmgr/cli/commands/test_release.py b/tests/unit/pkgmgr/cli/commands/test_release.py index 738ae66..0882c84 100644 --- a/tests/unit/pkgmgr/cli/commands/test_release.py +++ b/tests/unit/pkgmgr/cli/commands/test_release.py @@ -101,9 +101,7 @@ class TestReleaseCommand(unittest.TestCase): handle_release(args, ctx, selected) # We should have changed into the repo dir and then back. - mock_chdir.assert_has_calls( - [call("/repos/dummy"), call("/cwd")] - ) + mock_chdir.assert_has_calls([call("/repos/dummy"), call("/cwd")]) # And run_release should be invoked once with the expected parameters. mock_run_release.assert_called_once_with( diff --git a/tests/unit/pkgmgr/cli/commands/test_release_publish_hook.py b/tests/unit/pkgmgr/cli/commands/test_release_publish_hook.py index e2d1862..1d216ff 100644 --- a/tests/unit/pkgmgr/cli/commands/test_release_publish_hook.py +++ b/tests/unit/pkgmgr/cli/commands/test_release_publish_hook.py @@ -30,10 +30,12 @@ class TestCLIReleasePublishHook(unittest.TestCase): no_publish=False, ) - with patch("pkgmgr.cli.commands.release.run_release") as m_release, patch( - "pkgmgr.cli.commands.release.run_publish" - ) as m_publish, patch( - "pkgmgr.cli.commands.release.sys.stdin.isatty", return_value=False + with ( + patch("pkgmgr.cli.commands.release.run_release") as m_release, + patch("pkgmgr.cli.commands.release.run_publish") as m_publish, + patch( + "pkgmgr.cli.commands.release.sys.stdin.isatty", return_value=False + ), ): handle_release(args=args, ctx=self._ctx(), selected=[repo]) @@ -62,9 +64,10 @@ class TestCLIReleasePublishHook(unittest.TestCase): no_publish=True, ) - with patch("pkgmgr.cli.commands.release.run_release") as m_release, patch( - "pkgmgr.cli.commands.release.run_publish" - ) as m_publish: + with ( + patch("pkgmgr.cli.commands.release.run_release") as m_release, + patch("pkgmgr.cli.commands.release.run_publish") as m_publish, + ): handle_release(args=args, ctx=self._ctx(), selected=[repo]) m_release.assert_called_once() diff --git a/tests/unit/pkgmgr/cli/commands/test_repos.py b/tests/unit/pkgmgr/cli/commands/test_repos.py index 084fc2c..029f420 100644 --- a/tests/unit/pkgmgr/cli/commands/test_repos.py +++ b/tests/unit/pkgmgr/cli/commands/test_repos.py @@ -79,9 +79,10 @@ class TestReposCommand(unittest.TestCase): buf = io.StringIO() - with patch( - "pkgmgr.cli.commands.repos.get_repo_dir" - ) as mock_get_repo_dir, redirect_stdout(buf): + with ( + patch("pkgmgr.cli.commands.repos.get_repo_dir") as mock_get_repo_dir, + redirect_stdout(buf), + ): handle_repos_command(args, ctx, selected=repos) output = buf.getvalue().strip().splitlines() @@ -113,10 +114,13 @@ class TestReposCommand(unittest.TestCase): buf = io.StringIO() - with patch( - "pkgmgr.cli.commands.repos.get_repo_dir", - return_value="/resolved/from/get_repo_dir", - ) as mock_get_repo_dir, redirect_stdout(buf): + with ( + patch( + "pkgmgr.cli.commands.repos.get_repo_dir", + return_value="/resolved/from/get_repo_dir", + ) as mock_get_repo_dir, + redirect_stdout(buf), + ): handle_repos_command(args, ctx, selected=repos) output = buf.getvalue().strip().splitlines() @@ -168,12 +172,13 @@ class TestReposCommand(unittest.TestCase): shell_command=["echo", "hello"], ) - with patch( - "pkgmgr.cli.commands.repos.get_repo_dir", - return_value="/resolved/for/shell", - ) as mock_get_repo_dir, patch( - "pkgmgr.cli.commands.repos.run_command" - ) as mock_run_command: + with ( + patch( + "pkgmgr.cli.commands.repos.get_repo_dir", + return_value="/resolved/for/shell", + ) as mock_get_repo_dir, + patch("pkgmgr.cli.commands.repos.run_command") as mock_run_command, + ): buf = io.StringIO() with redirect_stdout(buf): handle_repos_command(args, ctx, selected=repos) @@ -185,7 +190,7 @@ class TestReposCommand(unittest.TestCase): mock_run_command.assert_called_once() called_args, called_kwargs = mock_run_command.call_args - self.assertEqual("echo hello", called_args[0]) # command string + self.assertEqual("echo hello", called_args[0]) # command string self.assertEqual("/resolved/for/shell", called_kwargs["cwd"]) self.assertFalse(called_kwargs["preview"]) diff --git a/tests/unit/pkgmgr/cli/test_cli.py b/tests/unit/pkgmgr/cli/test_cli.py index ad5d28f..4dac13f 100644 --- a/tests/unit/pkgmgr/cli/test_cli.py +++ b/tests/unit/pkgmgr/cli/test_cli.py @@ -115,13 +115,16 @@ class TestCliVersion(unittest.TestCase): """ Write a minimal PEP 621-style pyproject.toml into the temp directory. """ - content = textwrap.dedent( - f""" + content = ( + textwrap.dedent( + f""" [project] name = "pkgmgr-test" version = "{version}" """ - ).strip() + "\n" + ).strip() + + "\n" + ) path = os.path.join(self._tmp_dir.name, "pyproject.toml") with open(path, "w", encoding="utf-8") as f: diff --git a/tests/unit/pkgmgr/cli/test_handle_branch.py b/tests/unit/pkgmgr/cli/test_handle_branch.py index bed6da9..0e40a18 100644 --- a/tests/unit/pkgmgr/cli/test_handle_branch.py +++ b/tests/unit/pkgmgr/cli/test_handle_branch.py @@ -27,7 +27,9 @@ class TestCliBranch(unittest.TestCase): # ------------------------------------------------------------------ @patch("pkgmgr.cli.commands.branch.open_branch") - def test_handle_branch_open_forwards_args_to_open_branch(self, mock_open_branch) -> None: + def test_handle_branch_open_forwards_args_to_open_branch( + self, mock_open_branch + ) -> None: """ handle_branch('open') should call open_branch with name, base and cwd='.'. """ @@ -49,7 +51,9 @@ class TestCliBranch(unittest.TestCase): self.assertEqual(call_kwargs.get("cwd"), ".") @patch("pkgmgr.cli.commands.branch.open_branch") - def test_handle_branch_open_uses_default_base_when_not_set(self, mock_open_branch) -> None: + def test_handle_branch_open_uses_default_base_when_not_set( + self, mock_open_branch + ) -> None: """ If --base is not passed, argparse gives base='main' (default), and handle_branch should propagate that to open_branch. @@ -75,7 +79,9 @@ class TestCliBranch(unittest.TestCase): # ------------------------------------------------------------------ @patch("pkgmgr.cli.commands.branch.close_branch") - def test_handle_branch_close_forwards_args_to_close_branch(self, mock_close_branch) -> None: + def test_handle_branch_close_forwards_args_to_close_branch( + self, mock_close_branch + ) -> None: """ handle_branch('close') should call close_branch with name, base, cwd='.' and force=False by default. @@ -100,7 +106,9 @@ class TestCliBranch(unittest.TestCase): self.assertFalse(call_kwargs.get("force")) @patch("pkgmgr.cli.commands.branch.close_branch") - def test_handle_branch_close_uses_default_base_when_not_set(self, mock_close_branch) -> None: + def test_handle_branch_close_uses_default_base_when_not_set( + self, mock_close_branch + ) -> None: """ If --base is not passed for 'close', argparse gives base='main' (default), and handle_branch should propagate that to close_branch. @@ -153,7 +161,9 @@ class TestCliBranch(unittest.TestCase): # ------------------------------------------------------------------ @patch("pkgmgr.cli.commands.branch.drop_branch") - def test_handle_branch_drop_forwards_args_to_drop_branch(self, mock_drop_branch) -> None: + def test_handle_branch_drop_forwards_args_to_drop_branch( + self, mock_drop_branch + ) -> None: """ handle_branch('drop') should call drop_branch with name, base, cwd='.' and force=False by default. @@ -178,7 +188,9 @@ class TestCliBranch(unittest.TestCase): self.assertFalse(call_kwargs.get("force")) @patch("pkgmgr.cli.commands.branch.drop_branch") - def test_handle_branch_drop_uses_default_base_when_not_set(self, mock_drop_branch) -> None: + def test_handle_branch_drop_uses_default_base_when_not_set( + self, mock_drop_branch + ) -> None: """ If --base is not passed for 'drop', argparse gives base='main' (default), and handle_branch should propagate that to drop_branch. diff --git a/tests/unit/pkgmgr/cli/tools/test_paths.py b/tests/unit/pkgmgr/cli/tools/test_paths.py index 110c6b8..c60141c 100644 --- a/tests/unit/pkgmgr/cli/tools/test_paths.py +++ b/tests/unit/pkgmgr/cli/tools/test_paths.py @@ -20,7 +20,9 @@ class TestResolveRepositoryPath(unittest.TestCase): ctx = SimpleNamespace(repositories_base_dir="/base", repositories_dir="/base2") repo = {"provider": "github.com", "account": "acme", "repository": "demo"} - with patch("pkgmgr.cli.tools.paths.get_repo_dir", return_value="/computed/repo") as m: + with patch( + "pkgmgr.cli.tools.paths.get_repo_dir", return_value="/computed/repo" + ) as m: out = resolve_repository_path(repo, ctx) self.assertEqual(out, "/computed/repo") diff --git a/tests/unit/pkgmgr/cli/tools/test_vscode.py b/tests/unit/pkgmgr/cli/tools/test_vscode.py index b28bc1e..98f4d87 100644 --- a/tests/unit/pkgmgr/cli/tools/test_vscode.py +++ b/tests/unit/pkgmgr/cli/tools/test_vscode.py @@ -27,7 +27,9 @@ class TestOpenVSCodeWorkspace(unittest.TestCase): from pkgmgr.cli.tools.vscode import open_vscode_workspace ctx = SimpleNamespace(config_merged={}, all_repositories=[]) - selected: List[Repository] = [{"provider": "github.com", "account": "x", "repository": "y"}] + selected: List[Repository] = [ + {"provider": "github.com", "account": "x", "repository": "y"} + ] with patch("pkgmgr.cli.tools.vscode.shutil.which", return_value=None): with self.assertRaises(RuntimeError) as cm: @@ -42,11 +44,16 @@ class TestOpenVSCodeWorkspace(unittest.TestCase): config_merged={"directories": {"workspaces": "~/Workspaces"}}, all_repositories=[], ) - selected: List[Repository] = [{"provider": "github.com", "account": "x", "repository": "y"}] + selected: List[Repository] = [ + {"provider": "github.com", "account": "x", "repository": "y"} + ] - with patch("pkgmgr.cli.tools.vscode.shutil.which", return_value="/usr/bin/code"), patch( - "pkgmgr.cli.tools.vscode.get_repo_identifier", - return_value="github.com/x/y", + with ( + patch("pkgmgr.cli.tools.vscode.shutil.which", return_value="/usr/bin/code"), + patch( + "pkgmgr.cli.tools.vscode.get_repo_identifier", + return_value="github.com/x/y", + ), ): with self.assertRaises(RuntimeError) as cm: open_vscode_workspace(ctx, selected) @@ -68,18 +75,27 @@ class TestOpenVSCodeWorkspace(unittest.TestCase): repositories_base_dir=os.path.join(tmp, "Repos"), ) selected: List[Repository] = [ - {"provider": "github.com", "account": "kevin", "repository": "dotlinker"} + { + "provider": "github.com", + "account": "kevin", + "repository": "dotlinker", + } ] - with patch("pkgmgr.cli.tools.vscode.shutil.which", return_value="/usr/bin/code"), patch( - "pkgmgr.cli.tools.vscode.get_repo_identifier", - return_value="dotlinker", - ), patch( - "pkgmgr.cli.tools.vscode.resolve_repository_path", - return_value=repo_path, - ), patch( - "pkgmgr.cli.tools.vscode.run_command" - ) as run_cmd: + with ( + patch( + "pkgmgr.cli.tools.vscode.shutil.which", return_value="/usr/bin/code" + ), + patch( + "pkgmgr.cli.tools.vscode.get_repo_identifier", + return_value="dotlinker", + ), + patch( + "pkgmgr.cli.tools.vscode.resolve_repository_path", + return_value=repo_path, + ), + patch("pkgmgr.cli.tools.vscode.run_command") as run_cmd, + ): open_vscode_workspace(ctx, selected) workspace_file = os.path.join(workspaces_dir, "dotlinker.code-workspace") @@ -110,18 +126,27 @@ class TestOpenVSCodeWorkspace(unittest.TestCase): all_repositories=[], ) selected: List[Repository] = [ - {"provider": "github.com", "account": "kevin", "repository": "dotlinker"} + { + "provider": "github.com", + "account": "kevin", + "repository": "dotlinker", + } ] - with patch("pkgmgr.cli.tools.vscode.shutil.which", return_value="/usr/bin/code"), patch( - "pkgmgr.cli.tools.vscode.get_repo_identifier", - return_value="dotlinker", - ), patch( - "pkgmgr.cli.tools.vscode.resolve_repository_path", - return_value="/new/path", - ), patch( - "pkgmgr.cli.tools.vscode.run_command" - ) as run_cmd: + with ( + patch( + "pkgmgr.cli.tools.vscode.shutil.which", return_value="/usr/bin/code" + ), + patch( + "pkgmgr.cli.tools.vscode.get_repo_identifier", + return_value="dotlinker", + ), + patch( + "pkgmgr.cli.tools.vscode.resolve_repository_path", + return_value="/new/path", + ), + patch("pkgmgr.cli.tools.vscode.run_command") as run_cmd, + ): open_vscode_workspace(ctx, selected) with open(workspace_file, "r", encoding="utf-8") as f: diff --git a/tests/unit/pkgmgr/core/command/test_ink.py b/tests/unit/pkgmgr/core/command/test_ink.py index 13b7a71..49ec2e7 100644 --- a/tests/unit/pkgmgr/core/command/test_ink.py +++ b/tests/unit/pkgmgr/core/command/test_ink.py @@ -69,7 +69,10 @@ class TestCreateInk(unittest.TestCase): """ mock_get_repo_identifier.return_value = "mytool" - with tempfile.TemporaryDirectory() as repo_dir, tempfile.TemporaryDirectory() as bin_dir: + with ( + tempfile.TemporaryDirectory() as repo_dir, + tempfile.TemporaryDirectory() as bin_dir, + ): mock_get_repo_dir.return_value = repo_dir # Create a fake executable inside the repository. diff --git a/tests/unit/pkgmgr/core/command/test_resolve.py b/tests/unit/pkgmgr/core/command/test_resolve.py index 47cd539..dcf3967 100644 --- a/tests/unit/pkgmgr/core/command/test_resolve.py +++ b/tests/unit/pkgmgr/core/command/test_resolve.py @@ -155,13 +155,14 @@ class TestResolveCommandForRepo(unittest.TestCase): If no CLI is found via PATH or Nix, resolve_command_for_repo() should fall back to an executable main.sh in the repo root. """ - with tempfile.TemporaryDirectory() as tmpdir, patch( - "pkgmgr.core.command.resolve.shutil.which", return_value=None - ), patch( - "pkgmgr.core.command.resolve._nix_binary_candidates", return_value=[] - ), patch( - "pkgmgr.core.command.resolve._is_executable" - ) as mock_is_executable: + with ( + tempfile.TemporaryDirectory() as tmpdir, + patch("pkgmgr.core.command.resolve.shutil.which", return_value=None), + patch( + "pkgmgr.core.command.resolve._nix_binary_candidates", return_value=[] + ), + patch("pkgmgr.core.command.resolve._is_executable") as mock_is_executable, + ): main_sh = os.path.join(tmpdir, "main.sh") with open(main_sh, "w", encoding="utf-8") as f: f.write("#!/bin/sh\nexit 0\n") @@ -186,12 +187,13 @@ class TestResolveCommandForRepo(unittest.TestCase): but there is no CLI entry point or main.sh/main.py, the result should be None. """ - with tempfile.TemporaryDirectory() as tmpdir, patch( - "pkgmgr.core.command.resolve.shutil.which", return_value=None - ), patch( - "pkgmgr.core.command.resolve._nix_binary_candidates", return_value=[] - ), patch( - "pkgmgr.core.command.resolve._is_executable", return_value=False + with ( + tempfile.TemporaryDirectory() as tmpdir, + patch("pkgmgr.core.command.resolve.shutil.which", return_value=None), + patch( + "pkgmgr.core.command.resolve._nix_binary_candidates", return_value=[] + ), + patch("pkgmgr.core.command.resolve._is_executable", return_value=False), ): src_dir = os.path.join(tmpdir, "src", "mypkg") os.makedirs(src_dir, exist_ok=True) diff --git a/tests/unit/pkgmgr/core/command/test_run.py b/tests/unit/pkgmgr/core/command/test_run.py index 38b69d4..c2c381c 100644 --- a/tests/unit/pkgmgr/core/command/test_run.py +++ b/tests/unit/pkgmgr/core/command/test_run.py @@ -12,7 +12,11 @@ class TestRunCommand(unittest.TestCase): popen_mock.assert_not_called() def test_success_streams_and_returns_completed_process(self) -> None: - cmd = ["python3", "-c", "print('out'); import sys; print('err', file=sys.stderr)"] + cmd = [ + "python3", + "-c", + "print('out'); import sys; print('err', file=sys.stderr)", + ] with patch.object(run_mod.sys, "exit") as exit_mock: result = run_mod.run_command(cmd, allow_failure=False) @@ -23,7 +27,11 @@ class TestRunCommand(unittest.TestCase): exit_mock.assert_not_called() def test_failure_exits_when_not_allowed(self) -> None: - cmd = ["python3", "-c", "import sys; print('oops', file=sys.stderr); sys.exit(2)"] + cmd = [ + "python3", + "-c", + "import sys; print('oops', file=sys.stderr); sys.exit(2)", + ] with patch.object(run_mod.sys, "exit", side_effect=SystemExit(2)) as exit_mock: with self.assertRaises(SystemExit) as ctx: @@ -33,7 +41,11 @@ class TestRunCommand(unittest.TestCase): exit_mock.assert_called_once_with(2) def test_failure_does_not_exit_when_allowed(self) -> None: - cmd = ["python3", "-c", "import sys; print('oops', file=sys.stderr); sys.exit(3)"] + cmd = [ + "python3", + "-c", + "import sys; print('oops', file=sys.stderr); sys.exit(3)", + ] with patch.object(run_mod.sys, "exit") as exit_mock: result = run_mod.run_command(cmd, allow_failure=True) diff --git a/tests/unit/pkgmgr/core/git/queries/test_get_latest_signing_key.py b/tests/unit/pkgmgr/core/git/queries/test_get_latest_signing_key.py index 65650af..d7900e7 100644 --- a/tests/unit/pkgmgr/core/git/queries/test_get_latest_signing_key.py +++ b/tests/unit/pkgmgr/core/git/queries/test_get_latest_signing_key.py @@ -9,17 +9,26 @@ from pkgmgr.core.git.queries.get_latest_signing_key import ( class TestGetLatestSigningKey(unittest.TestCase): - @patch("pkgmgr.core.git.queries.get_latest_signing_key.run", return_value="ABCDEF1234567890\n") + @patch( + "pkgmgr.core.git.queries.get_latest_signing_key.run", + return_value="ABCDEF1234567890\n", + ) def test_strips_output(self, _mock_run) -> None: out = get_latest_signing_key(cwd="/tmp/repo") self.assertEqual(out, "ABCDEF1234567890") - @patch("pkgmgr.core.git.queries.get_latest_signing_key.run", side_effect=GitRunError("boom")) + @patch( + "pkgmgr.core.git.queries.get_latest_signing_key.run", + side_effect=GitRunError("boom"), + ) def test_wraps_git_run_error(self, _mock_run) -> None: with self.assertRaises(GitLatestSigningKeyQueryError): get_latest_signing_key(cwd="/tmp/repo") - @patch("pkgmgr.core.git.queries.get_latest_signing_key.run", side_effect=GitNotRepositoryError("no repo")) + @patch( + "pkgmgr.core.git.queries.get_latest_signing_key.run", + side_effect=GitNotRepositoryError("no repo"), + ) def test_does_not_catch_not_repository_error(self, _mock_run) -> None: with self.assertRaises(GitNotRepositoryError): get_latest_signing_key(cwd="/tmp/no-repo") diff --git a/tests/unit/pkgmgr/core/git/queries/test_get_remote_head_commit.py b/tests/unit/pkgmgr/core/git/queries/test_get_remote_head_commit.py index 81157da..520a801 100644 --- a/tests/unit/pkgmgr/core/git/queries/test_get_remote_head_commit.py +++ b/tests/unit/pkgmgr/core/git/queries/test_get_remote_head_commit.py @@ -9,7 +9,10 @@ from pkgmgr.core.git.queries.get_remote_head_commit import ( class TestGetRemoteHeadCommit(unittest.TestCase): - @patch("pkgmgr.core.git.queries.get_remote_head_commit.run", return_value="abc123\tHEAD\n") + @patch( + "pkgmgr.core.git.queries.get_remote_head_commit.run", + return_value="abc123\tHEAD\n", + ) def test_parses_first_token_as_hash(self, mock_run) -> None: out = get_remote_head_commit(cwd="/tmp/repo") self.assertEqual(out, "abc123") @@ -20,13 +23,19 @@ class TestGetRemoteHeadCommit(unittest.TestCase): out = get_remote_head_commit(cwd="/tmp/repo") self.assertEqual(out, "") - @patch("pkgmgr.core.git.queries.get_remote_head_commit.run", side_effect=GitRunError("boom")) + @patch( + "pkgmgr.core.git.queries.get_remote_head_commit.run", + side_effect=GitRunError("boom"), + ) def test_wraps_git_run_error(self, _mock_run) -> None: with self.assertRaises(GitRemoteHeadCommitQueryError) as ctx: get_remote_head_commit(cwd="/tmp/repo") self.assertIn("Failed to query remote head commit", str(ctx.exception)) - @patch("pkgmgr.core.git.queries.get_remote_head_commit.run", side_effect=GitNotRepositoryError("no repo")) + @patch( + "pkgmgr.core.git.queries.get_remote_head_commit.run", + side_effect=GitNotRepositoryError("no repo"), + ) def test_does_not_catch_not_repository_error(self, _mock_run) -> None: with self.assertRaises(GitNotRepositoryError): get_remote_head_commit(cwd="/tmp/no-repo") diff --git a/tests/unit/pkgmgr/core/git/queries/test_remote_check.py b/tests/unit/pkgmgr/core/git/queries/test_remote_check.py index ce36e55..17b13c0 100644 --- a/tests/unit/pkgmgr/core/git/queries/test_remote_check.py +++ b/tests/unit/pkgmgr/core/git/queries/test_remote_check.py @@ -26,7 +26,11 @@ class TestProbeRemoteReachable(unittest.TestCase): self.assertTrue(ok) mock_run.assert_called_once_with( - ["ls-remote", "--exit-code", "ssh://git@code.example.org:2201/alice/repo.git"], + [ + "ls-remote", + "--exit-code", + "ssh://git@code.example.org:2201/alice/repo.git", + ], cwd="/tmp/some-repo", ) @@ -41,7 +45,11 @@ class TestProbeRemoteReachable(unittest.TestCase): self.assertFalse(ok) mock_run.assert_called_once_with( - ["ls-remote", "--exit-code", "ssh://git@code.example.org:2201/alice/repo.git"], + [ + "ls-remote", + "--exit-code", + "ssh://git@code.example.org:2201/alice/repo.git", + ], cwd="/tmp/some-repo", ) diff --git a/tests/unit/pkgmgr/core/git/test_run.py b/tests/unit/pkgmgr/core/git/test_run.py index 15ec348..fce0f42 100644 --- a/tests/unit/pkgmgr/core/git/test_run.py +++ b/tests/unit/pkgmgr/core/git/test_run.py @@ -7,9 +7,10 @@ from pkgmgr.core.git.run import run class TestGitRun(unittest.TestCase): def test_preview_mode_prints_and_does_not_execute(self) -> None: - with patch("pkgmgr.core.git.run.subprocess.run") as mock_run, patch( - "builtins.print" - ) as mock_print: + with ( + patch("pkgmgr.core.git.run.subprocess.run") as mock_run, + patch("builtins.print") as mock_print, + ): out = run(["status"], cwd="/tmp/repo", preview=True) self.assertEqual(out, "") @@ -24,7 +25,9 @@ class TestGitRun(unittest.TestCase): completed.stderr = "" completed.returncode = 0 - with patch("pkgmgr.core.git.run.subprocess.run", return_value=completed) as mock_run: + with patch( + "pkgmgr.core.git.run.subprocess.run", return_value=completed + ) as mock_run: out = run(["rev-parse", "HEAD"], cwd="/repo", preview=False) self.assertEqual(out, "hello world") diff --git a/tests/unit/pkgmgr/core/repository/test_dir.py b/tests/unit/pkgmgr/core/repository/test_dir.py index e08f6bd..3c7ec6e 100644 --- a/tests/unit/pkgmgr/core/repository/test_dir.py +++ b/tests/unit/pkgmgr/core/repository/test_dir.py @@ -7,7 +7,10 @@ from pkgmgr.core.repository.dir import get_repo_dir class TestGetRepoDir(unittest.TestCase): def test_builds_path_with_expanded_base_dir(self): repo = {"provider": "github.com", "account": "alice", "repository": "demo"} - with patch("pkgmgr.core.repository.dir.os.path.expanduser", return_value="/home/u/repos"): + with patch( + "pkgmgr.core.repository.dir.os.path.expanduser", + return_value="/home/u/repos", + ): result = get_repo_dir("~/repos", repo) self.assertEqual(result, "/home/u/repos/github.com/alice/demo") diff --git a/tests/unit/pkgmgr/core/repository/test_ignored.py b/tests/unit/pkgmgr/core/repository/test_ignored.py index cb547ea..df16144 100644 --- a/tests/unit/pkgmgr/core/repository/test_ignored.py +++ b/tests/unit/pkgmgr/core/repository/test_ignored.py @@ -9,8 +9,18 @@ from pkgmgr.core.repository.ignored import filter_ignored class TestFilterIgnored(unittest.TestCase): def test_filter_ignored_removes_repos_with_ignore_true(self) -> None: repos = [ - {"provider": "github.com", "account": "user", "repository": "a", "ignore": True}, - {"provider": "github.com", "account": "user", "repository": "b", "ignore": False}, + { + "provider": "github.com", + "account": "user", + "repository": "a", + "ignore": True, + }, + { + "provider": "github.com", + "account": "user", + "repository": "b", + "ignore": False, + }, {"provider": "github.com", "account": "user", "repository": "c"}, ] diff --git a/tests/unit/pkgmgr/core/repository/test_selected.py b/tests/unit/pkgmgr/core/repository/test_selected.py index 905d047..079877f 100644 --- a/tests/unit/pkgmgr/core/repository/test_selected.py +++ b/tests/unit/pkgmgr/core/repository/test_selected.py @@ -96,7 +96,9 @@ class TestGetSelectedRepos(unittest.TestCase): selected = get_selected_repos(args, self.all_repos) # Beide Repos sollten erscheinen, weil include_ignored=True - self.assertEqual({r["repository"] for r in selected}, {"ignored-repo", "visible-repo"}) + self.assertEqual( + {r["repository"] for r in selected}, {"ignored-repo", "visible-repo"} + ) # ------------------------------------------------------------------ # 3) --all Modus – ignorierte Repos werden per Default entfernt diff --git a/tests/unit/pkgmgr/core/repository/test_verify_repository.py b/tests/unit/pkgmgr/core/repository/test_verify_repository.py index 045b470..dc2700b 100644 --- a/tests/unit/pkgmgr/core/repository/test_verify_repository.py +++ b/tests/unit/pkgmgr/core/repository/test_verify_repository.py @@ -10,9 +10,14 @@ from pkgmgr.core.repository.verify import verify_repository class TestVerifyRepository(unittest.TestCase): def test_no_verified_info_returns_ok_and_best_effort_values(self) -> None: repo = {"id": "demo"} # no "verified" - with patch("pkgmgr.core.repository.verify.get_head_commit", return_value="deadbeef"), patch( - "pkgmgr.core.repository.verify.get_latest_signing_key", - return_value="KEYID", + with ( + patch( + "pkgmgr.core.repository.verify.get_head_commit", return_value="deadbeef" + ), + patch( + "pkgmgr.core.repository.verify.get_latest_signing_key", + return_value="KEYID", + ), ): ok, errors, commit, key = verify_repository(repo, "/tmp/repo", mode="local") self.assertTrue(ok) @@ -22,12 +27,15 @@ class TestVerifyRepository(unittest.TestCase): def test_best_effort_swallows_query_errors_when_no_verified_info(self) -> None: repo = {"id": "demo"} - with patch( - "pkgmgr.core.repository.verify.get_head_commit", - return_value=None, - ), patch( - "pkgmgr.core.repository.verify.get_latest_signing_key", - side_effect=GitLatestSigningKeyQueryError("fail signing key"), + with ( + patch( + "pkgmgr.core.repository.verify.get_head_commit", + return_value=None, + ), + patch( + "pkgmgr.core.repository.verify.get_latest_signing_key", + side_effect=GitLatestSigningKeyQueryError("fail signing key"), + ), ): ok, errors, commit, key = verify_repository(repo, "/tmp/repo", mode="local") self.assertTrue(ok) @@ -37,9 +45,14 @@ class TestVerifyRepository(unittest.TestCase): def test_verified_commit_mismatch_fails(self) -> None: repo = {"verified": {"commit": "expected", "gpg_keys": None}} - with patch("pkgmgr.core.repository.verify.get_head_commit", return_value="actual"), patch( - "pkgmgr.core.repository.verify.get_latest_signing_key", - return_value="", + with ( + patch( + "pkgmgr.core.repository.verify.get_head_commit", return_value="actual" + ), + patch( + "pkgmgr.core.repository.verify.get_latest_signing_key", + return_value="", + ), ): ok, errors, commit, key = verify_repository(repo, "/tmp/repo", mode="local") @@ -50,9 +63,12 @@ class TestVerifyRepository(unittest.TestCase): def test_verified_gpg_key_missing_fails(self) -> None: repo = {"verified": {"commit": None, "gpg_keys": ["ABC"]}} - with patch("pkgmgr.core.repository.verify.get_head_commit", return_value=""), patch( - "pkgmgr.core.repository.verify.get_latest_signing_key", - return_value="", + with ( + patch("pkgmgr.core.repository.verify.get_head_commit", return_value=""), + patch( + "pkgmgr.core.repository.verify.get_latest_signing_key", + return_value="", + ), ): ok, errors, commit, key = verify_repository(repo, "/tmp/repo", mode="local") @@ -63,12 +79,15 @@ class TestVerifyRepository(unittest.TestCase): def test_strict_pull_collects_remote_error_message(self) -> None: repo = {"verified": {"commit": "expected", "gpg_keys": None}} - with patch( - "pkgmgr.core.repository.verify.get_remote_head_commit", - side_effect=GitRemoteHeadCommitQueryError("remote fail"), - ), patch( - "pkgmgr.core.repository.verify.get_latest_signing_key", - return_value="", + with ( + patch( + "pkgmgr.core.repository.verify.get_remote_head_commit", + side_effect=GitRemoteHeadCommitQueryError("remote fail"), + ), + patch( + "pkgmgr.core.repository.verify.get_latest_signing_key", + return_value="", + ), ): ok, errors, commit, key = verify_repository(repo, "/tmp/repo", mode="pull") diff --git a/tests/unit/pkgmgr/core/test_create_ink.py b/tests/unit/pkgmgr/core/test_create_ink.py index 6750bcf..0849135 100644 --- a/tests/unit/pkgmgr/core/test_create_ink.py +++ b/tests/unit/pkgmgr/core/test_create_ink.py @@ -19,9 +19,11 @@ class TestCreateInk(unittest.TestCase): mock_get_repo_identifier.return_value = "test-id" mock_get_repo_dir.return_value = "/repos/test-id" - with patch("pkgmgr.core.command.ink.os.makedirs") as mock_makedirs, \ - patch("pkgmgr.core.command.ink.os.symlink") as mock_symlink, \ - patch("pkgmgr.core.command.ink.os.chmod") as mock_chmod: + with ( + patch("pkgmgr.core.command.ink.os.makedirs") as mock_makedirs, + patch("pkgmgr.core.command.ink.os.symlink") as mock_symlink, + patch("pkgmgr.core.command.ink.os.chmod") as mock_chmod, + ): create_ink_module.create_ink( repo=repo, repositories_base_dir="/repos", @@ -45,9 +47,11 @@ class TestCreateInk(unittest.TestCase): mock_get_repo_identifier.return_value = "test-id" mock_get_repo_dir.return_value = "/repos/test-id" - with patch("pkgmgr.core.command.ink.os.makedirs") as mock_makedirs, \ - patch("pkgmgr.core.command.ink.os.symlink") as mock_symlink, \ - patch("pkgmgr.core.command.ink.os.chmod") as mock_chmod: + with ( + patch("pkgmgr.core.command.ink.os.makedirs") as mock_makedirs, + patch("pkgmgr.core.command.ink.os.symlink") as mock_symlink, + patch("pkgmgr.core.command.ink.os.chmod") as mock_chmod, + ): create_ink_module.create_ink( repo=repo, repositories_base_dir="/repos", @@ -74,12 +78,14 @@ class TestCreateInk(unittest.TestCase): mock_get_repo_identifier.return_value = "test-id" mock_get_repo_dir.return_value = "/repos/test-id" - with patch("pkgmgr.core.command.ink.os.makedirs") as mock_makedirs, \ - patch("pkgmgr.core.command.ink.os.symlink") as mock_symlink, \ - patch("pkgmgr.core.command.ink.os.chmod") as mock_chmod, \ - patch("pkgmgr.core.command.ink.os.path.exists", return_value=False), \ - patch("pkgmgr.core.command.ink.os.path.islink", return_value=False), \ - patch("pkgmgr.core.command.ink.os.path.realpath", side_effect=lambda p: p): + with ( + patch("pkgmgr.core.command.ink.os.makedirs") as mock_makedirs, + patch("pkgmgr.core.command.ink.os.symlink") as mock_symlink, + patch("pkgmgr.core.command.ink.os.chmod") as mock_chmod, + patch("pkgmgr.core.command.ink.os.path.exists", return_value=False), + patch("pkgmgr.core.command.ink.os.path.islink", return_value=False), + patch("pkgmgr.core.command.ink.os.path.realpath", side_effect=lambda p: p), + ): create_ink_module.create_ink( repo=repo, repositories_base_dir="/repos", @@ -93,5 +99,6 @@ class TestCreateInk(unittest.TestCase): mock_makedirs.assert_called_once() mock_chmod.assert_called_once() + if __name__ == "__main__": unittest.main()