Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3b2c657bfa | ||
|
|
e335ab05a1 | ||
|
|
75f963d6e2 | ||
|
|
94b998741f | ||
|
|
172c734866 |
10
CHANGELOG.md
10
CHANGELOG.md
@@ -1,3 +1,13 @@
|
|||||||
|
## [0.7.12] - 2025-12-09
|
||||||
|
|
||||||
|
* Fixed self refering alias during setup
|
||||||
|
|
||||||
|
|
||||||
|
## [0.7.11] - 2025-12-09
|
||||||
|
|
||||||
|
* test: fix installer unit tests for OS packages and Nix dev shell
|
||||||
|
|
||||||
|
|
||||||
## [0.7.10] - 2025-12-09
|
## [0.7.10] - 2025-12-09
|
||||||
|
|
||||||
* Fixed test_install_pkgmgr_shallow.py
|
* Fixed test_install_pkgmgr_shallow.py
|
||||||
|
|||||||
2
PKGBUILD
2
PKGBUILD
@@ -1,7 +1,7 @@
|
|||||||
# Maintainer: Kevin Veen-Birkenbach <info@veen.world>
|
# Maintainer: Kevin Veen-Birkenbach <info@veen.world>
|
||||||
|
|
||||||
pkgname=package-manager
|
pkgname=package-manager
|
||||||
pkgver=0.7.10
|
pkgver=0.7.12
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Local-flake wrapper for Kevin's package-manager (Nix-based)."
|
pkgdesc="Local-flake wrapper for Kevin's package-manager (Nix-based)."
|
||||||
arch=('any')
|
arch=('any')
|
||||||
|
|||||||
12
debian/changelog
vendored
12
debian/changelog
vendored
@@ -1,3 +1,15 @@
|
|||||||
|
package-manager (0.7.12-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Fixed self refering alias during setup
|
||||||
|
|
||||||
|
-- Kevin Veen-Birkenbach <kevin@veen.world> Tue, 09 Dec 2025 23:36:35 +0100
|
||||||
|
|
||||||
|
package-manager (0.7.11-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* test: fix installer unit tests for OS packages and Nix dev shell
|
||||||
|
|
||||||
|
-- Kevin Veen-Birkenbach <kevin@veen.world> Tue, 09 Dec 2025 23:16:46 +0100
|
||||||
|
|
||||||
package-manager (0.7.10-1) unstable; urgency=medium
|
package-manager (0.7.10-1) unstable; urgency=medium
|
||||||
|
|
||||||
* Fixed test_install_pkgmgr_shallow.py
|
* Fixed test_install_pkgmgr_shallow.py
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
rec {
|
rec {
|
||||||
pkgmgr = pyPkgs.buildPythonApplication {
|
pkgmgr = pyPkgs.buildPythonApplication {
|
||||||
pname = "package-manager";
|
pname = "package-manager";
|
||||||
version = "0.7.10";
|
version = "0.7.12";
|
||||||
|
|
||||||
# Use the git repo as source
|
# Use the git repo as source
|
||||||
src = ./.;
|
src = ./.;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
Name: package-manager
|
Name: package-manager
|
||||||
Version: 0.7.10
|
Version: 0.7.12
|
||||||
Release: 1%{?dist}
|
Release: 1%{?dist}
|
||||||
Summary: Wrapper that runs Kevin's package-manager via Nix flake
|
Summary: Wrapper that runs Kevin's package-manager via Nix flake
|
||||||
|
|
||||||
@@ -77,6 +77,12 @@ echo ">>> package-manager removed. Nix itself was not removed."
|
|||||||
/usr/lib/package-manager/
|
/usr/lib/package-manager/
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Tue Dec 09 2025 Kevin Veen-Birkenbach <kevin@veen.world> - 0.7.12-1
|
||||||
|
- Fixed self refering alias during setup
|
||||||
|
|
||||||
|
* Tue Dec 09 2025 Kevin Veen-Birkenbach <kevin@veen.world> - 0.7.11-1
|
||||||
|
- test: fix installer unit tests for OS packages and Nix dev shell
|
||||||
|
|
||||||
* Tue Dec 09 2025 Kevin Veen-Birkenbach <kevin@veen.world> - 0.7.10-1
|
* Tue Dec 09 2025 Kevin Veen-Birkenbach <kevin@veen.world> - 0.7.10-1
|
||||||
- Fixed test_install_pkgmgr_shallow.py
|
- Fixed test_install_pkgmgr_shallow.py
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,14 @@ from pkgmgr.core.repository.identifier import get_repo_identifier
|
|||||||
from pkgmgr.core.repository.dir import get_repo_dir
|
from pkgmgr.core.repository.dir import get_repo_dir
|
||||||
|
|
||||||
|
|
||||||
def create_ink(repo, repositories_base_dir, bin_dir, all_repos,
|
def create_ink(
|
||||||
quiet=False, preview=False):
|
repo,
|
||||||
|
repositories_base_dir,
|
||||||
|
bin_dir,
|
||||||
|
all_repos,
|
||||||
|
quiet: bool = False,
|
||||||
|
preview: bool = False,
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Create a symlink for the repository's command.
|
Create a symlink for the repository's command.
|
||||||
|
|
||||||
@@ -18,6 +24,11 @@ def create_ink(repo, repositories_base_dir, bin_dir, all_repos,
|
|||||||
Behavior:
|
Behavior:
|
||||||
- If repo["command"] is defined → create a symlink to it.
|
- If repo["command"] is defined → create a symlink to it.
|
||||||
- If repo["command"] is missing or None → do NOT create a link.
|
- If repo["command"] is missing or None → do NOT create a link.
|
||||||
|
|
||||||
|
Safety:
|
||||||
|
- If the resolved command path is identical to the final link target,
|
||||||
|
we skip symlink creation to avoid self-referential symlinks that
|
||||||
|
would break shell resolution ("too many levels of symbolic links").
|
||||||
"""
|
"""
|
||||||
|
|
||||||
repo_identifier = get_repo_identifier(repo, all_repos)
|
repo_identifier = get_repo_identifier(repo, all_repos)
|
||||||
@@ -31,6 +42,27 @@ def create_ink(repo, repositories_base_dir, bin_dir, all_repos,
|
|||||||
|
|
||||||
link_path = os.path.join(bin_dir, repo_identifier)
|
link_path = os.path.join(bin_dir, repo_identifier)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Safety guard: avoid self-referential symlinks
|
||||||
|
#
|
||||||
|
# Example of a broken situation we must avoid:
|
||||||
|
# - command = ~/.local/bin/package-manager
|
||||||
|
# - link_path = ~/.local/bin/package-manager
|
||||||
|
# - create_ink() removes the real binary and creates a symlink
|
||||||
|
# pointing to itself → zsh: too many levels of symbolic links
|
||||||
|
#
|
||||||
|
# If the resolved command already lives exactly at the target path,
|
||||||
|
# we treat it as "already installed" and skip any modification.
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
if os.path.abspath(command) == os.path.abspath(link_path):
|
||||||
|
if not quiet:
|
||||||
|
print(
|
||||||
|
f"[pkgmgr] Command for '{repo_identifier}' already lives at "
|
||||||
|
f"'{link_path}'. Skipping symlink creation to avoid a "
|
||||||
|
"self-referential link."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
if preview:
|
if preview:
|
||||||
print(f"[Preview] Would link {link_path} → {command}")
|
print(f"[Preview] Would link {link_path} → {command}")
|
||||||
return
|
return
|
||||||
@@ -65,7 +97,10 @@ def create_ink(repo, repositories_base_dir, bin_dir, all_repos,
|
|||||||
|
|
||||||
if alias_name == repo_identifier:
|
if alias_name == repo_identifier:
|
||||||
if not quiet:
|
if not quiet:
|
||||||
print(f"Alias '{alias_name}' equals identifier. Skipping alias creation.")
|
print(
|
||||||
|
f"Alias '{alias_name}' equals identifier. "
|
||||||
|
"Skipping alias creation."
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "package-manager"
|
name = "package-manager"
|
||||||
version = "0.7.10"
|
version = "0.7.12"
|
||||||
description = "Kevin's package-manager tool (pkgmgr)"
|
description = "Kevin's package-manager tool (pkgmgr)"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
|
|||||||
@@ -1,91 +0,0 @@
|
|||||||
"""
|
|
||||||
Integration test: install all configured repositories using
|
|
||||||
--clone-mode shallow (HTTPS shallow clone) and --no-verification.
|
|
||||||
|
|
||||||
This test is intended to be run inside the Docker container where:
|
|
||||||
- network access is available,
|
|
||||||
- the config/config.yaml is present,
|
|
||||||
- and it is safe to perform real git operations.
|
|
||||||
|
|
||||||
It passes if the command completes without raising an exception.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import runpy
|
|
||||||
import sys
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
from test_install_pkgmgr_shallow import (
|
|
||||||
nix_profile_list_debug,
|
|
||||||
remove_pkgmgr_from_nix_profile,
|
|
||||||
pkgmgr_help_debug,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestIntegrationInstallAllShallow(unittest.TestCase):
|
|
||||||
def _run_pkgmgr_install_all(self) -> None:
|
|
||||||
"""
|
|
||||||
Helper that runs the CLI command via main.py and provides
|
|
||||||
extra diagnostics if the command exits with a non-zero code.
|
|
||||||
"""
|
|
||||||
cmd_repr = "pkgmgr install --all --clone-mode shallow --no-verification"
|
|
||||||
original_argv = sys.argv
|
|
||||||
try:
|
|
||||||
sys.argv = [
|
|
||||||
"pkgmgr",
|
|
||||||
"install",
|
|
||||||
"--all",
|
|
||||||
"--clone-mode",
|
|
||||||
"shallow",
|
|
||||||
"--no-verification",
|
|
||||||
]
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Execute main.py as if it was called from CLI.
|
|
||||||
# This will run the full install pipeline inside the container.
|
|
||||||
runpy.run_module("main", run_name="__main__")
|
|
||||||
except SystemExit as exc:
|
|
||||||
# Convert SystemExit into a more helpful assertion with debug output.
|
|
||||||
exit_code = exc.code if isinstance(exc.code, int) else str(exc.code)
|
|
||||||
|
|
||||||
print("\n[TEST] pkgmgr install --all failed with SystemExit")
|
|
||||||
print(f"[TEST] Command : {cmd_repr}")
|
|
||||||
print(f"[TEST] Exit code: {exit_code}")
|
|
||||||
|
|
||||||
# Additional Nix profile debug on failure
|
|
||||||
nix_profile_list_debug("ON FAILURE (AFTER SystemExit)")
|
|
||||||
|
|
||||||
raise AssertionError(
|
|
||||||
f"{cmd_repr!r} failed with exit code {exit_code}. "
|
|
||||||
"Scroll up to see the full pkgmgr/make output inside the container."
|
|
||||||
) from exc
|
|
||||||
|
|
||||||
finally:
|
|
||||||
sys.argv = original_argv
|
|
||||||
|
|
||||||
def test_install_all_repositories_shallow(self) -> None:
|
|
||||||
"""
|
|
||||||
Run: pkgmgr install --all --clone-mode shallow --no-verification
|
|
||||||
|
|
||||||
This will perform real installations/clones inside the container.
|
|
||||||
The test succeeds if no exception is raised and `pkgmgr --help`
|
|
||||||
works in a fresh interactive bash session afterwards.
|
|
||||||
"""
|
|
||||||
# Debug before cleanup
|
|
||||||
nix_profile_list_debug("BEFORE CLEANUP")
|
|
||||||
|
|
||||||
# Cleanup: aggressively try to drop any pkgmgr/profile entries
|
|
||||||
remove_pkgmgr_from_nix_profile()
|
|
||||||
|
|
||||||
# Debug after cleanup
|
|
||||||
nix_profile_list_debug("AFTER CLEANUP")
|
|
||||||
|
|
||||||
# Run the actual install with extended diagnostics
|
|
||||||
self._run_pkgmgr_install_all()
|
|
||||||
|
|
||||||
# After successful installation: show `pkgmgr --help`
|
|
||||||
# via interactive bash (same as the pkgmgr-only test).
|
|
||||||
pkgmgr_help_debug()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
unittest.main()
|
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
# tests/unit/pkgmgr/installers/os_packages/test_debian_control.py
|
|
||||||
|
|
||||||
import os
|
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from pkgmgr.actions.repository.install.context import RepoContext
|
from pkgmgr.actions.repository.install.context import RepoContext
|
||||||
from pkgmgr.actions.repository.install.installers.os_packages.debian_control import DebianControlInstaller
|
from pkgmgr.actions.repository.install.installers.os_packages.debian_control import (
|
||||||
|
DebianControlInstaller,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestDebianControlInstaller(unittest.TestCase):
|
class TestDebianControlInstaller(unittest.TestCase):
|
||||||
@@ -29,14 +28,24 @@ class TestDebianControlInstaller(unittest.TestCase):
|
|||||||
@patch("os.path.exists", return_value=True)
|
@patch("os.path.exists", return_value=True)
|
||||||
@patch("shutil.which", return_value="/usr/bin/dpkg-buildpackage")
|
@patch("shutil.which", return_value="/usr/bin/dpkg-buildpackage")
|
||||||
def test_supports_true(self, mock_which, mock_exists):
|
def test_supports_true(self, mock_which, mock_exists):
|
||||||
|
"""
|
||||||
|
supports() should return True when dpkg-buildpackage is available
|
||||||
|
and a debian/control file exists in the repository.
|
||||||
|
"""
|
||||||
self.assertTrue(self.installer.supports(self.ctx))
|
self.assertTrue(self.installer.supports(self.ctx))
|
||||||
|
|
||||||
@patch("os.path.exists", return_value=True)
|
@patch("os.path.exists", return_value=True)
|
||||||
@patch("shutil.which", return_value=None)
|
@patch("shutil.which", return_value=None)
|
||||||
def test_supports_false_without_dpkg_buildpackage(self, mock_which, mock_exists):
|
def test_supports_false_without_dpkg_buildpackage(self, mock_which, mock_exists):
|
||||||
|
"""
|
||||||
|
supports() should return False when dpkg-buildpackage is not available,
|
||||||
|
even if a debian/control file exists.
|
||||||
|
"""
|
||||||
self.assertFalse(self.installer.supports(self.ctx))
|
self.assertFalse(self.installer.supports(self.ctx))
|
||||||
|
|
||||||
@patch("pkgmgr.actions.repository.install.installers.os_packages.debian_control.run_command")
|
@patch(
|
||||||
|
"pkgmgr.actions.repository.install.installers.os_packages.debian_control.run_command"
|
||||||
|
)
|
||||||
@patch("glob.glob", return_value=["/tmp/package-manager_0.1.1_all.deb"])
|
@patch("glob.glob", return_value=["/tmp/package-manager_0.1.1_all.deb"])
|
||||||
@patch("os.path.exists", return_value=True)
|
@patch("os.path.exists", return_value=True)
|
||||||
@patch("shutil.which")
|
@patch("shutil.which")
|
||||||
@@ -47,7 +56,19 @@ class TestDebianControlInstaller(unittest.TestCase):
|
|||||||
mock_glob,
|
mock_glob,
|
||||||
mock_run_command,
|
mock_run_command,
|
||||||
):
|
):
|
||||||
# dpkg-buildpackage + apt-get vorhanden
|
"""
|
||||||
|
run() should:
|
||||||
|
|
||||||
|
1. Install build dependencies (apt-get build-dep).
|
||||||
|
2. Build the package using dpkg-buildpackage -b -us -uc.
|
||||||
|
3. Discover built .deb files via glob.
|
||||||
|
4. Install the resulting .deb packages using a suitable tool:
|
||||||
|
- dpkg -i
|
||||||
|
- sudo dpkg -i
|
||||||
|
- or sudo apt-get install -y
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Simulate dpkg-buildpackage and apt-get being available.
|
||||||
def which_side_effect(name):
|
def which_side_effect(name):
|
||||||
if name == "dpkg-buildpackage":
|
if name == "dpkg-buildpackage":
|
||||||
return "/usr/bin/dpkg-buildpackage"
|
return "/usr/bin/dpkg-buildpackage"
|
||||||
@@ -64,16 +85,35 @@ class TestDebianControlInstaller(unittest.TestCase):
|
|||||||
# 1) apt-get update
|
# 1) apt-get update
|
||||||
self.assertTrue(any("apt-get update" in cmd for cmd in cmds))
|
self.assertTrue(any("apt-get update" in cmd for cmd in cmds))
|
||||||
|
|
||||||
# 2) apt-get build-dep ./
|
# 2) apt-get build-dep -y ./ (with or without trailing space)
|
||||||
self.assertTrue(any("apt-get build-dep -y ./ " in cmd or
|
self.assertTrue(
|
||||||
"apt-get build-dep -y ./"
|
any(
|
||||||
in cmd for cmd in cmds))
|
"apt-get build-dep -y ./ " in cmd
|
||||||
|
or "apt-get build-dep -y ./"
|
||||||
|
in cmd
|
||||||
|
for cmd in cmds
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# 3) dpkg-buildpackage -b -us -uc
|
# 3) dpkg-buildpackage -b -us -uc
|
||||||
self.assertTrue(any("dpkg-buildpackage -b -us -uc" in cmd for cmd in cmds))
|
self.assertTrue(any("dpkg-buildpackage -b -us -uc" in cmd for cmd in cmds))
|
||||||
|
|
||||||
# 4) dpkg -i ../*.deb
|
# 4) final installation of .deb packages:
|
||||||
self.assertTrue(any(cmd.startswith("sudo dpkg -i ") for cmd in cmds))
|
# accept dpkg -i, sudo dpkg -i, or sudo apt-get install -y
|
||||||
|
has_plain_dpkg_install = any(cmd.startswith("dpkg -i ") for cmd in cmds)
|
||||||
|
has_sudo_dpkg_install = any(cmd.startswith("sudo dpkg -i ") for cmd in cmds)
|
||||||
|
has_apt_install = any(
|
||||||
|
cmd.startswith("sudo apt-get install -y ") for cmd in cmds
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertTrue(
|
||||||
|
has_plain_dpkg_install or has_sudo_dpkg_install or has_apt_install,
|
||||||
|
msg=(
|
||||||
|
"Expected one of 'dpkg -i', 'sudo dpkg -i' or "
|
||||||
|
"'sudo apt-get install -y', but got commands: "
|
||||||
|
f"{cmds}"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# tests/unit/pkgmgr/installers/os_packages/test_rpm_spec.py
|
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from pkgmgr.actions.repository.install.context import RepoContext
|
from pkgmgr.actions.repository.install.context import RepoContext
|
||||||
from pkgmgr.actions.repository.install.installers.os_packages.rpm_spec import RpmSpecInstaller
|
from pkgmgr.actions.repository.install.installers.os_packages.rpm_spec import (
|
||||||
|
RpmSpecInstaller,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestRpmSpecInstaller(unittest.TestCase):
|
class TestRpmSpecInstaller(unittest.TestCase):
|
||||||
@@ -28,6 +28,13 @@ class TestRpmSpecInstaller(unittest.TestCase):
|
|||||||
@patch("glob.glob", return_value=["/tmp/repo/test.spec"])
|
@patch("glob.glob", return_value=["/tmp/repo/test.spec"])
|
||||||
@patch("shutil.which")
|
@patch("shutil.which")
|
||||||
def test_supports_true(self, mock_which, mock_glob):
|
def test_supports_true(self, mock_which, mock_glob):
|
||||||
|
"""
|
||||||
|
supports() should return True when:
|
||||||
|
- rpmbuild is available, and
|
||||||
|
- at least one of dnf/yum/yum-builddep is available, and
|
||||||
|
- a *.spec file is present in the repo.
|
||||||
|
"""
|
||||||
|
|
||||||
def which_side_effect(name):
|
def which_side_effect(name):
|
||||||
if name == "rpmbuild":
|
if name == "rpmbuild":
|
||||||
return "/usr/bin/rpmbuild"
|
return "/usr/bin/rpmbuild"
|
||||||
@@ -42,9 +49,14 @@ class TestRpmSpecInstaller(unittest.TestCase):
|
|||||||
@patch("glob.glob", return_value=[])
|
@patch("glob.glob", return_value=[])
|
||||||
@patch("shutil.which")
|
@patch("shutil.which")
|
||||||
def test_supports_false_missing_spec(self, mock_which, mock_glob):
|
def test_supports_false_missing_spec(self, mock_which, mock_glob):
|
||||||
|
"""
|
||||||
|
supports() should return False if no *.spec file is found,
|
||||||
|
even if rpmbuild is present.
|
||||||
|
"""
|
||||||
mock_which.return_value = "/usr/bin/rpmbuild"
|
mock_which.return_value = "/usr/bin/rpmbuild"
|
||||||
self.assertFalse(self.installer.supports(self.ctx))
|
self.assertFalse(self.installer.supports(self.ctx))
|
||||||
|
|
||||||
|
@patch.object(RpmSpecInstaller, "_prepare_source_tarball")
|
||||||
@patch("pkgmgr.actions.repository.install.installers.os_packages.rpm_spec.run_command")
|
@patch("pkgmgr.actions.repository.install.installers.os_packages.rpm_spec.run_command")
|
||||||
@patch("glob.glob")
|
@patch("glob.glob")
|
||||||
@patch("shutil.which")
|
@patch("shutil.which")
|
||||||
@@ -53,8 +65,20 @@ class TestRpmSpecInstaller(unittest.TestCase):
|
|||||||
mock_which,
|
mock_which,
|
||||||
mock_glob,
|
mock_glob,
|
||||||
mock_run_command,
|
mock_run_command,
|
||||||
|
mock_prepare_source_tarball,
|
||||||
):
|
):
|
||||||
# glob.glob wird zweimal benutzt: einmal für *.spec, einmal für gebaute RPMs
|
"""
|
||||||
|
run() should:
|
||||||
|
|
||||||
|
1. Determine the .spec file in the repo.
|
||||||
|
2. Call _prepare_source_tarball() once with ctx and spec path.
|
||||||
|
3. Install build dependencies via dnf/yum-builddep/yum.
|
||||||
|
4. Call rpmbuild -ba <spec>.
|
||||||
|
5. Find built RPMs via glob.
|
||||||
|
6. Install built RPMs via dnf/yum/rpm (here: dnf).
|
||||||
|
"""
|
||||||
|
|
||||||
|
# glob.glob is used twice: once for *.spec, once for built RPMs.
|
||||||
def glob_side_effect(pattern, recursive=False):
|
def glob_side_effect(pattern, recursive=False):
|
||||||
if pattern.endswith("*.spec"):
|
if pattern.endswith("*.spec"):
|
||||||
return ["/tmp/repo/package-manager.spec"]
|
return ["/tmp/repo/package-manager.spec"]
|
||||||
@@ -77,16 +101,23 @@ class TestRpmSpecInstaller(unittest.TestCase):
|
|||||||
|
|
||||||
self.installer.run(self.ctx)
|
self.installer.run(self.ctx)
|
||||||
|
|
||||||
|
# _prepare_source_tarball must have been called with the resolved spec path.
|
||||||
|
mock_prepare_source_tarball.assert_called_once_with(
|
||||||
|
self.ctx,
|
||||||
|
"/tmp/repo/package-manager.spec",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Collect all command strings passed to run_command.
|
||||||
cmds = [c[0][0] for c in mock_run_command.call_args_list]
|
cmds = [c[0][0] for c in mock_run_command.call_args_list]
|
||||||
|
|
||||||
# 1) builddep
|
# 1) build dependencies (dnf builddep)
|
||||||
self.assertTrue(any("builddep -y" in cmd for cmd in cmds))
|
self.assertTrue(any("builddep -y" in cmd for cmd in cmds))
|
||||||
|
|
||||||
# 2) rpmbuild -ba
|
# 2) rpmbuild -ba <spec>
|
||||||
self.assertTrue(any(cmd.startswith("rpmbuild -ba ") for cmd in cmds))
|
self.assertTrue(any(cmd.startswith("rpmbuild -ba ") for cmd in cmds))
|
||||||
|
|
||||||
# 3) rpm -i …
|
# 3) installation via dnf: "sudo dnf install -y <rpms>"
|
||||||
self.assertTrue(any(cmd.startswith("sudo rpm -i ") for cmd in cmds))
|
self.assertTrue(any(cmd.startswith("sudo dnf install -y ") for cmd in cmds))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -28,14 +28,27 @@ class TestNixFlakeInstaller(unittest.TestCase):
|
|||||||
@patch("shutil.which", return_value="/usr/bin/nix")
|
@patch("shutil.which", return_value="/usr/bin/nix")
|
||||||
@patch("os.path.exists", return_value=True)
|
@patch("os.path.exists", return_value=True)
|
||||||
def test_supports_true_when_nix_and_flake_exist(self, mock_exists, mock_which):
|
def test_supports_true_when_nix_and_flake_exist(self, mock_exists, mock_which):
|
||||||
self.assertTrue(self.installer.supports(self.ctx))
|
"""
|
||||||
|
supports() should return True when:
|
||||||
|
- nix is available,
|
||||||
|
- flake.nix exists in the repo,
|
||||||
|
- and we are not inside a Nix dev shell.
|
||||||
|
"""
|
||||||
|
with patch.dict(os.environ, {"IN_NIX_SHELL": ""}, clear=False):
|
||||||
|
self.assertTrue(self.installer.supports(self.ctx))
|
||||||
|
|
||||||
mock_which.assert_called_with("nix")
|
mock_which.assert_called_with("nix")
|
||||||
mock_exists.assert_called_with(os.path.join(self.ctx.repo_dir, "flake.nix"))
|
mock_exists.assert_called_with(os.path.join(self.ctx.repo_dir, "flake.nix"))
|
||||||
|
|
||||||
@patch("shutil.which", return_value=None)
|
@patch("shutil.which", return_value=None)
|
||||||
@patch("os.path.exists", return_value=True)
|
@patch("os.path.exists", return_value=True)
|
||||||
def test_supports_false_when_nix_missing(self, mock_exists, mock_which):
|
def test_supports_false_when_nix_missing(self, mock_exists, mock_which):
|
||||||
self.assertFalse(self.installer.supports(self.ctx))
|
"""
|
||||||
|
supports() should return False if nix is not available,
|
||||||
|
even if a flake.nix file exists.
|
||||||
|
"""
|
||||||
|
with patch.dict(os.environ, {"IN_NIX_SHELL": ""}, clear=False):
|
||||||
|
self.assertFalse(self.installer.supports(self.ctx))
|
||||||
|
|
||||||
@patch("os.path.exists", return_value=True)
|
@patch("os.path.exists", return_value=True)
|
||||||
@patch("shutil.which", return_value="/usr/bin/nix")
|
@patch("shutil.which", return_value="/usr/bin/nix")
|
||||||
@@ -47,10 +60,12 @@ class TestNixFlakeInstaller(unittest.TestCase):
|
|||||||
mock_exists,
|
mock_exists,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Ensure that run():
|
run() should:
|
||||||
- first tries to remove the old 'package-manager' profile entry
|
|
||||||
- then installs both 'pkgmgr' and 'default' outputs.
|
1. attempt to remove the old 'package-manager' profile entry, and
|
||||||
|
2. install both 'pkgmgr' and 'default' flake outputs.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
cmds = []
|
cmds = []
|
||||||
|
|
||||||
def side_effect(cmd, cwd=None, preview=False, *args, **kwargs):
|
def side_effect(cmd, cwd=None, preview=False, *args, **kwargs):
|
||||||
@@ -59,18 +74,24 @@ class TestNixFlakeInstaller(unittest.TestCase):
|
|||||||
|
|
||||||
mock_run_command.side_effect = side_effect
|
mock_run_command.side_effect = side_effect
|
||||||
|
|
||||||
self.installer.run(self.ctx)
|
# Simulate a normal environment (not inside nix develop, installer enabled).
|
||||||
|
with patch.dict(
|
||||||
|
os.environ,
|
||||||
|
{"IN_NIX_SHELL": "", "PKGMGR_DISABLE_NIX_FLAKE_INSTALLER": ""},
|
||||||
|
clear=False,
|
||||||
|
):
|
||||||
|
self.installer.run(self.ctx)
|
||||||
|
|
||||||
remove_cmd = f"nix profile remove {self.installer.PROFILE_NAME} || true"
|
remove_cmd = f"nix profile remove {self.installer.PROFILE_NAME} || true"
|
||||||
install_pkgmgr_cmd = f"nix profile install {self.ctx.repo_dir}#pkgmgr"
|
install_pkgmgr_cmd = f"nix profile install {self.ctx.repo_dir}#pkgmgr"
|
||||||
install_default_cmd = f"nix profile install {self.ctx.repo_dir}#default"
|
install_default_cmd = f"nix profile install {self.ctx.repo_dir}#default"
|
||||||
|
|
||||||
# Mindestens diese drei Kommandos müssen aufgerufen worden sein
|
# At least these three commands must have been issued.
|
||||||
self.assertIn(remove_cmd, cmds)
|
self.assertIn(remove_cmd, cmds)
|
||||||
self.assertIn(install_pkgmgr_cmd, cmds)
|
self.assertIn(install_pkgmgr_cmd, cmds)
|
||||||
self.assertIn(install_default_cmd, cmds)
|
self.assertIn(install_default_cmd, cmds)
|
||||||
|
|
||||||
# Optional: sicherstellen, dass der remove-Aufruf zuerst kam
|
# Optional: ensure the remove call came first.
|
||||||
self.assertEqual(cmds[0], remove_cmd)
|
self.assertEqual(cmds[0], remove_cmd)
|
||||||
|
|
||||||
@patch("shutil.which", return_value="/usr/bin/nix")
|
@patch("shutil.which", return_value="/usr/bin/nix")
|
||||||
@@ -90,8 +111,13 @@ class TestNixFlakeInstaller(unittest.TestCase):
|
|||||||
|
|
||||||
mock_run_command.side_effect = side_effect
|
mock_run_command.side_effect = side_effect
|
||||||
|
|
||||||
# Should not raise, SystemExit is swallowed internally.
|
with patch.dict(
|
||||||
self.installer._ensure_old_profile_removed(self.ctx)
|
os.environ,
|
||||||
|
{"IN_NIX_SHELL": "", "PKGMGR_DISABLE_NIX_FLAKE_INSTALLER": ""},
|
||||||
|
clear=False,
|
||||||
|
):
|
||||||
|
# Should not raise, SystemExit is swallowed internally.
|
||||||
|
self.installer._ensure_old_profile_removed(self.ctx)
|
||||||
|
|
||||||
remove_cmd = f"nix profile remove {self.installer.PROFILE_NAME} || true"
|
remove_cmd = f"nix profile remove {self.installer.PROFILE_NAME} || true"
|
||||||
mock_run_command.assert_called_with(
|
mock_run_command.assert_called_with(
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
# tests/unit/pkgmgr/installers/test_python_installer.py
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
@@ -28,18 +26,40 @@ class TestPythonInstaller(unittest.TestCase):
|
|||||||
|
|
||||||
@patch("os.path.exists", side_effect=lambda path: path.endswith("pyproject.toml"))
|
@patch("os.path.exists", side_effect=lambda path: path.endswith("pyproject.toml"))
|
||||||
def test_supports_true_when_pyproject_exists(self, mock_exists):
|
def test_supports_true_when_pyproject_exists(self, mock_exists):
|
||||||
self.assertTrue(self.installer.supports(self.ctx))
|
"""
|
||||||
|
supports() should return True when a pyproject.toml exists in the repo
|
||||||
|
and we are not inside a Nix dev shell.
|
||||||
|
"""
|
||||||
|
with patch.dict(os.environ, {"IN_NIX_SHELL": ""}, clear=False):
|
||||||
|
self.assertTrue(self.installer.supports(self.ctx))
|
||||||
|
|
||||||
@patch("os.path.exists", return_value=False)
|
@patch("os.path.exists", return_value=False)
|
||||||
def test_supports_false_when_no_pyproject(self, mock_exists):
|
def test_supports_false_when_no_pyproject(self, mock_exists):
|
||||||
self.assertFalse(self.installer.supports(self.ctx))
|
"""
|
||||||
|
supports() should return False when no pyproject.toml exists.
|
||||||
|
"""
|
||||||
|
with patch.dict(os.environ, {"IN_NIX_SHELL": ""}, clear=False):
|
||||||
|
self.assertFalse(self.installer.supports(self.ctx))
|
||||||
|
|
||||||
@patch("pkgmgr.actions.repository.install.installers.python.run_command")
|
@patch("pkgmgr.actions.repository.install.installers.python.run_command")
|
||||||
@patch("os.path.exists", side_effect=lambda path: path.endswith("pyproject.toml"))
|
@patch("os.path.exists", side_effect=lambda path: path.endswith("pyproject.toml"))
|
||||||
def test_run_installs_project_from_pyproject(self, mock_exists, mock_run_command):
|
def test_run_installs_project_from_pyproject(self, mock_exists, mock_run_command):
|
||||||
self.installer.run(self.ctx)
|
"""
|
||||||
|
run() should invoke pip to install the project from pyproject.toml
|
||||||
|
when we are not inside a Nix dev shell.
|
||||||
|
"""
|
||||||
|
# Simulate a normal environment (not inside nix develop).
|
||||||
|
with patch.dict(os.environ, {"IN_NIX_SHELL": ""}, clear=False):
|
||||||
|
self.installer.run(self.ctx)
|
||||||
|
|
||||||
|
# Ensure run_command was actually called.
|
||||||
|
mock_run_command.assert_called()
|
||||||
|
|
||||||
|
# Extract the command string.
|
||||||
cmd = mock_run_command.call_args[0][0]
|
cmd = mock_run_command.call_args[0][0]
|
||||||
self.assertIn("pip install .", cmd)
|
self.assertIn("pip install .", cmd)
|
||||||
|
|
||||||
|
# Ensure the working directory is the repo dir.
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
mock_run_command.call_args[1].get("cwd"),
|
mock_run_command.call_args[1].get("cwd"),
|
||||||
self.ctx.repo_dir,
|
self.ctx.repo_dir,
|
||||||
|
|||||||
0
tests/unit/pkgmgr/core/command/__init__.py
Normal file
0
tests/unit/pkgmgr/core/command/__init__.py
Normal file
108
tests/unit/pkgmgr/core/command/test_ink.py
Normal file
108
tests/unit/pkgmgr/core/command/test_ink.py
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from pkgmgr.core.command.ink import create_ink
|
||||||
|
|
||||||
|
|
||||||
|
class TestCreateInk(unittest.TestCase):
|
||||||
|
@patch("pkgmgr.core.command.ink.get_repo_dir")
|
||||||
|
@patch("pkgmgr.core.command.ink.get_repo_identifier")
|
||||||
|
def test_self_referential_command_skips_symlink(
|
||||||
|
self,
|
||||||
|
mock_get_repo_identifier,
|
||||||
|
mock_get_repo_dir,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
If the resolved command path is identical to the final link target,
|
||||||
|
create_ink() must NOT replace it with a self-referential symlink.
|
||||||
|
|
||||||
|
This simulates the situation where the command already lives at
|
||||||
|
~/.local/bin/<identifier> and we would otherwise create a symlink
|
||||||
|
pointing to itself.
|
||||||
|
"""
|
||||||
|
mock_get_repo_identifier.return_value = "package-manager"
|
||||||
|
mock_get_repo_dir.return_value = "/fake/repo"
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as bin_dir:
|
||||||
|
# Simulate an existing real binary at the final link location.
|
||||||
|
command_path = os.path.join(bin_dir, "package-manager")
|
||||||
|
with open(command_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write("#!/bin/sh\necho package-manager\n")
|
||||||
|
|
||||||
|
# Sanity check: not a symlink yet.
|
||||||
|
self.assertTrue(os.path.exists(command_path))
|
||||||
|
self.assertFalse(os.path.islink(command_path))
|
||||||
|
|
||||||
|
repo = {"command": command_path}
|
||||||
|
|
||||||
|
# This must NOT turn the file into a self-referential symlink.
|
||||||
|
create_ink(
|
||||||
|
repo=repo,
|
||||||
|
repositories_base_dir="/fake/base",
|
||||||
|
bin_dir=bin_dir,
|
||||||
|
all_repos=[],
|
||||||
|
quiet=True,
|
||||||
|
preview=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# After create_ink(), the file must still exist and must not be a symlink.
|
||||||
|
self.assertTrue(os.path.exists(command_path))
|
||||||
|
self.assertFalse(
|
||||||
|
os.path.islink(command_path),
|
||||||
|
"create_ink() must not create a self-referential symlink "
|
||||||
|
"when command == link_path",
|
||||||
|
)
|
||||||
|
|
||||||
|
@patch("pkgmgr.core.command.ink.get_repo_dir")
|
||||||
|
@patch("pkgmgr.core.command.ink.get_repo_identifier")
|
||||||
|
def test_create_symlink_for_normal_command(
|
||||||
|
self,
|
||||||
|
mock_get_repo_identifier,
|
||||||
|
mock_get_repo_dir,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
In the normal case (command path != link target), create_ink()
|
||||||
|
must create a symlink in bin_dir pointing to the given command,
|
||||||
|
and optionally an alias symlink when repo['alias'] is set.
|
||||||
|
"""
|
||||||
|
mock_get_repo_identifier.return_value = "mytool"
|
||||||
|
|
||||||
|
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.
|
||||||
|
command_path = os.path.join(repo_dir, "main.sh")
|
||||||
|
with open(command_path, "w", encoding="utf-8") as f:
|
||||||
|
f.write("#!/bin/sh\necho mytool\n")
|
||||||
|
os.chmod(command_path, 0o755)
|
||||||
|
|
||||||
|
repo = {
|
||||||
|
"command": command_path,
|
||||||
|
"alias": "mt",
|
||||||
|
}
|
||||||
|
|
||||||
|
create_ink(
|
||||||
|
repo=repo,
|
||||||
|
repositories_base_dir="/fake/base",
|
||||||
|
bin_dir=bin_dir,
|
||||||
|
all_repos=[],
|
||||||
|
quiet=True,
|
||||||
|
preview=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
link_path = os.path.join(bin_dir, "mytool")
|
||||||
|
alias_path = os.path.join(bin_dir, "mt")
|
||||||
|
|
||||||
|
# Main link must exist and point to the command.
|
||||||
|
self.assertTrue(os.path.islink(link_path))
|
||||||
|
self.assertEqual(os.readlink(link_path), command_path)
|
||||||
|
|
||||||
|
# Alias must exist and point to the main link.
|
||||||
|
self.assertTrue(os.path.islink(alias_path))
|
||||||
|
self.assertEqual(os.readlink(alias_path), link_path)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user