executet 'ruff format --check .'
Some checks failed
Mark stable commit / test-unit (push) Has been cancelled
Mark stable commit / test-integration (push) Has been cancelled
Mark stable commit / test-env-virtual (push) Has been cancelled
Mark stable commit / test-env-nix (push) Has been cancelled
Mark stable commit / test-e2e (push) Has been cancelled
Mark stable commit / test-virgin-user (push) Has been cancelled
Mark stable commit / test-virgin-root (push) Has been cancelled
Mark stable commit / lint-shell (push) Has been cancelled
Mark stable commit / lint-python (push) Has been cancelled
Mark stable commit / mark-stable (push) Has been cancelled

This commit is contained in:
Kevin Veen-Birkenbach
2025-12-18 14:04:44 +01:00
parent 763f02a9a4
commit f4339a746a
155 changed files with 1327 additions and 636 deletions

View File

@@ -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")

View File

@@ -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(

View File

@@ -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])

View File

@@ -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)

View File

@@ -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

View File

@@ -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"

View File

@@ -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"
)
)

View File

@@ -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)

View File

@@ -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",

View File

@@ -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:

View File

@@ -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)

View File

@@ -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")

View File

@@ -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/"))

View File

@@ -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(

View File

@@ -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
)
)

View File

@@ -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):

View File

@@ -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())

View File

@@ -33,4 +33,4 @@ class TestRepoContext(unittest.TestCase):
if __name__ == "__main__":
unittest.main()
unittest.main()

View File

@@ -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,
{

View File

@@ -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)

View File

@@ -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"))

View File

@@ -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)

View File

@@ -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:

View File

@@ -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()

View File

@@ -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"

View File

@@ -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__":

View File

@@ -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")

View File

@@ -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"])

View File

@@ -1,4 +1,3 @@
import unittest
from pkgmgr.actions.publish.pypi_url import parse_pypi_project_url

View File

@@ -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(

View File

@@ -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 <old@example.com> - 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 <old@example.com>", 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 <old@example.com> - 0.1.0-1
- Old entry
"""
).strip() + "\n"
).strip()
+ "\n"
)
with tempfile.TemporaryDirectory() as tmpdir:
path = os.path.join(tmpdir, "package-manager.spec")

View File

@@ -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"))

View File

@@ -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)

View File

@@ -40,4 +40,4 @@ class TestTemplateRendererPreview(unittest.TestCase):
if __name__ == "__main__":
unittest.main()
unittest.main()

View File

@@ -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":

View File

@@ -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")

View File

@@ -1,4 +1,3 @@
import unittest
from unittest.mock import patch

View File

@@ -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(

View File

@@ -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()

View File

@@ -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"])

View File

@@ -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:

View File

@@ -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.

View File

@@ -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")

View File

@@ -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:

View File

@@ -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.

View File

@@ -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)

View File

@@ -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)

View File

@@ -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")

View File

@@ -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")

View File

@@ -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",
)

View File

@@ -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")

View File

@@ -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")

View File

@@ -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"},
]

View File

@@ -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

View File

@@ -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")

View File

@@ -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()