public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
* [rpms/python-menuinst] rawhide: Update to 2.5.0 upstream release
@ 2026-06-07 21:13 Packit
  0 siblings, 0 replies; only message in thread
From: Packit @ 2026-06-07 21:13 UTC (permalink / raw)
  To: git-commits

            A new commit has been pushed.

            Repo   : rpms/python-menuinst
            Branch : rawhide
            Commit : ed647c1bafc1761dc80473f79865205f528bc204
            Author : Packit <hello@packit.dev>
            Date   : 2026-06-05T22:02:54-06:00
            Stats  : +503/-2 in 5 file(s)
            URL    : https://src.fedoraproject.org/rpms/python-menuinst/c/ed647c1bafc1761dc80473f79865205f528bc204?branch=rawhide

            Log:
            Update to 2.5.0 upstream release

- Add upstream patch to fix tests
- Resolves: rhbz#2484360

Commit authored by Packit automation (https://packit.dev/)

---
diff --git a/.gitignore b/.gitignore
index cd1e9aa..32420d2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 /menuinst-2.0.0.tar.gz
 /menuinst-2.3.1.tar.gz
 /menuinst-2.4.2.tar.gz
+/menuinst-2.5.0.tar.gz

diff --git a/497.patch b/497.patch
new file mode 100644
index 0000000..81075b5
--- /dev/null
+++ b/497.patch
@@ -0,0 +1,495 @@
+From abbebb028fed2dd544be20652074af72b6728649 Mon Sep 17 00:00:00 2001
+From: Robin <randersson@anaconda.com>
+Date: Fri, 5 Jun 2026 09:59:31 -0400
+Subject: [PATCH 1/4] Gracefully handle permission errors with menuinst.toml
+
+---
+ menuinst/api.py        | 17 ++++++++++--
+ tests/test_api.py      |  4 +--
+ tests/test_metadata.py | 62 ++++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 79 insertions(+), 4 deletions(-)
+
+diff --git a/menuinst/api.py b/menuinst/api.py
+index 5481a58f..211d1cc0 100644
+--- a/menuinst/api.py
++++ b/menuinst/api.py
+@@ -62,7 +62,14 @@ def record_shortcuts(
+     for path in paths:
+         shortcuts.append({"source": source, "path": str(path)})
+ 
+-    write_menuinst_toml(prefix, data)
++    try:
++        write_menuinst_toml(prefix, data)
++    except PermissionError:
++        log.debug(
++            "Cannot write menuinst.toml to %s (permission denied). "
++            "Shortcut tracking will not be available for this prefix.",
++            prefix / "Menu",
++        )
+ 
+ 
+ def remove_shortcut_records(prefix: Path, source: str) -> None:
+@@ -86,7 +93,13 @@ def remove_shortcut_records(prefix: Path, source: str) -> None:
+         return  # Nothing was removed
+ 
+     data["shortcuts"] = filtered
+-    write_menuinst_toml(prefix, data)
++    try:
++        write_menuinst_toml(prefix, data)
++    except PermissionError:
++        log.debug(
++            "Cannot update menuinst.toml at %s (permission denied).",
++            prefix / "Menu",
++        )
+ 
+ 
+ def _load(
+diff --git a/tests/test_api.py b/tests/test_api.py
+index fc2e6c9c..0ed595d9 100644
+--- a/tests/test_api.py
++++ b/tests/test_api.py
+@@ -63,7 +63,7 @@ def check_output_from_shortcut(
+     tmp_base_path = mkdtemp()
+     delete_files.append(tmp_base_path)
+     (Path(tmp_base_path) / ".nonadmin").touch()
+-    paths = install(abs_json_path, base_prefix=tmp_base_path)
++    paths = install(abs_json_path, target_prefix=sys.prefix, base_prefix=tmp_base_path)
+     try:
+         if action == "run_shortcut":
+             if PLATFORM == "win":
+@@ -114,7 +114,7 @@ def check_output_from_shortcut(
+         if paths:
+             delete_files += list(paths)
+         if remove_after:
+-            remove(abs_json_path, base_prefix=tmp_base_path)
++            remove(abs_json_path, target_prefix=sys.prefix, base_prefix=tmp_base_path)
+         if PLATFORM == "osx" and action in ("open_file", "open_url"):
+             _lsregister(
+                 "-kill",
+diff --git a/tests/test_metadata.py b/tests/test_metadata.py
+index 10ee7ef4..9b44365f 100644
+--- a/tests/test_metadata.py
++++ b/tests/test_metadata.py
+@@ -3,6 +3,9 @@
+ from __future__ import annotations
+ 
+ import json
++import logging
++import os
++import sys
+ from typing import TYPE_CHECKING
+ 
+ import pytest
+@@ -155,6 +158,65 @@ def test_distribution_name_only_written_to_base_prefix(self, tmp_path: Path) ->
+         assert "distribution_name" not in data
+         assert len(data["shortcuts"]) == 1
+ 
++    @pytest.mark.skipif(sys.platform == "win32", reason="chmod doesn't work on Windows")
++    def test_record_shortcuts_handles_permission_error(
++        self, tmp_path: Path, caplog
++    ) -> None:
++        """record_shortcuts() should not raise when prefix is read-only."""
++        # Create a read-only directory
++        readonly_prefix = tmp_path / "readonly"
++        readonly_prefix.mkdir()
++        os.chmod(readonly_prefix, 0o555)
++
++        try:
++            with caplog.at_level(logging.DEBUG):
++                # This should NOT raise PermissionError
++                record_shortcuts(
++                    prefix=readonly_prefix,
++                    base_prefix=readonly_prefix,
++                    source="test.json",
++                    paths=[tmp_path / "fake" / "path" / "shortcut.desktop"],
++                )
++
++            # Should log a debug message about permission denied
++            assert "permission denied" in caplog.text.lower()
++        finally:
++            # Restore permissions for cleanup
++            os.chmod(readonly_prefix, 0o755)
++
++    @pytest.mark.skipif(sys.platform == "win32", reason="chmod doesn't work on Windows")
++    def test_remove_shortcut_records_handles_permission_error(
++        self, tmp_path: Path, caplog
++    ) -> None:
++        """remove_shortcut_records() should not raise when prefix is read-only."""
++        # Create a prefix with a menuinst.toml
++        prefix = tmp_path / "prefix"
++        prefix.mkdir()
++        menu_dir = prefix / "Menu"
++        menu_dir.mkdir()
++
++        # Write initial TOML data
++        write_menuinst_toml(
++            prefix,
++            {"shortcuts": [{"source": "test.json", "path": "/fake/path"}]},
++        )
++
++        # Make the directory read-only
++        os.chmod(menu_dir, 0o555)
++        os.chmod(prefix, 0o555)
++
++        try:
++            with caplog.at_level(logging.DEBUG):
++                # This should NOT raise PermissionError
++                remove_shortcut_records(prefix, "test.json")
++
++            # Should log a debug message about permission denied
++            assert "permission denied" in caplog.text.lower()
++        finally:
++            # Restore permissions for cleanup
++            os.chmod(prefix, 0o755)
++            os.chmod(menu_dir, 0o755)
++
+ 
+ class TestInstallAdapter:
+     """Tests for _install_adapter recording correct source filename."""
+
+From 9001da642cd5866d11a776b4ae1575e3d00fb832 Mon Sep 17 00:00:00 2001
+From: Robin <randersson@anaconda.com>
+Date: Fri, 5 Jun 2026 11:59:07 -0400
+Subject: [PATCH 2/4] Update tests
+
+---
+ tests/test_api.py      | 49 ++++++++++++++++++++++++++++++------------
+ tests/test_metadata.py |  4 +---
+ 2 files changed, 36 insertions(+), 17 deletions(-)
+
+diff --git a/tests/test_api.py b/tests/test_api.py
+index 0ed595d9..17ea419b 100644
+--- a/tests/test_api.py
++++ b/tests/test_api.py
+@@ -60,10 +60,29 @@ def check_output_from_shortcut(
+         abs_json_path = tmp.name
+         delete_files.append(abs_json_path)
+ 
+-    tmp_base_path = mkdtemp()
+-    delete_files.append(tmp_base_path)
+-    (Path(tmp_base_path) / ".nonadmin").touch()
+-    paths = install(abs_json_path, target_prefix=sys.prefix, base_prefix=tmp_base_path)
++    # Windows file/URL association tests need sys.prefix because symlinked Python
++    # doesn't work through the file association handler (registry lookup -> cmd -> batch).
++    # These tests only run on CI where sys.prefix is a writable conda environment.
++    use_real_prefix = PLATFORM == "win" and action in ("open_file", "open_url")
++
++    if use_real_prefix:
++        tmp_base_path = sys.prefix
++    else:
++        tmp_base_path = mkdtemp()
++        delete_files.append(tmp_base_path)
++        (Path(tmp_base_path) / ".nonadmin").touch()
++        # conda-meta makes conda treat this as a valid environment for activation
++        (Path(tmp_base_path) / "conda-meta").mkdir(exist_ok=True)
++        # Symlink to real Python so {{ PYTHON }} placeholder works
++        if PLATFORM == "win":
++            python_path = Path(tmp_base_path) / "python.exe"
++        else:
++            bin_dir = Path(tmp_base_path) / "bin"
++            bin_dir.mkdir(exist_ok=True)
++            python_path = bin_dir / "python"
++        python_path.symlink_to(sys.executable)
++
++    paths = install(abs_json_path, target_prefix=tmp_base_path, base_prefix=tmp_base_path)
+     try:
+         if action == "run_shortcut":
+             if PLATFORM == "win":
+@@ -114,7 +133,7 @@ def check_output_from_shortcut(
+         if paths:
+             delete_files += list(paths)
+         if remove_after:
+-            remove(abs_json_path, target_prefix=sys.prefix, base_prefix=tmp_base_path)
++            remove(abs_json_path, target_prefix=tmp_base_path, base_prefix=tmp_base_path)
+         if PLATFORM == "osx" and action in ("open_file", "open_url"):
+             _lsregister(
+                 "-kill",
+@@ -273,7 +292,7 @@ def test_precommands(delete_files):
+ 
+ @pytest.mark.skipif(PLATFORM != "osx", reason="macOS only")
+ def test_entitlements(delete_files):
+-    json_path, paths, *_ = check_output_from_shortcut(
++    json_path, paths, tmp_base_path, _ = check_output_from_shortcut(
+         delete_files, "entitlements.json", remove_after=False, expected_output="entitlements"
+     )
+     # verify signature
+@@ -300,12 +319,12 @@ def test_entitlements(delete_files):
+     else:
+         raise AssertionError("Didn't find Entitlements.plist")
+ 
+-    remove(json_path)
++    remove(json_path, target_prefix=tmp_base_path, base_prefix=tmp_base_path)
+ 
+ 
+ @pytest.mark.skipif(PLATFORM != "osx", reason="macOS only")
+ def test_no_entitlements_no_signature(delete_files):
+-    json_path, paths, *_ = check_output_from_shortcut(
++    json_path, paths, tmp_base_path, _ = check_output_from_shortcut(
+         delete_files, "sys-prefix.json", remove_after=False, expected_output=sys.prefix
+     )
+     app_dir = next(p for p in paths if p.name.endswith(".app"))
+@@ -316,12 +335,12 @@ def test_no_entitlements_no_signature(delete_files):
+         subprocess.check_call(["/usr/bin/codesign", "--verbose", "--verify", str(app_dir)])
+     with pytest.raises(subprocess.CalledProcessError):
+         subprocess.check_call(["/usr/bin/codesign", "--verbose", "--verify", str(launcher)])
+-    remove(json_path)
++    remove(json_path, target_prefix=tmp_base_path, base_prefix=tmp_base_path)
+ 
+ 
+ @pytest.mark.skipif(PLATFORM != "osx", reason="macOS only")
+ def test_info_plist(delete_files):
+-    json_path, paths, *_ = check_output_from_shortcut(
++    json_path, paths, tmp_base_path, _ = check_output_from_shortcut(
+         delete_files, "entitlements.json", remove_after=False, expected_output="entitlements"
+     )
+     metadata = json.loads(json_path.read_text())
+@@ -354,7 +373,7 @@ def test_info_plist(delete_files):
+     assert missing_items == []
+     assert incorrect_items == {}
+ 
+-    remove(json_path)
++    remove(json_path, target_prefix=tmp_base_path, base_prefix=tmp_base_path)
+ 
+ 
+ @pytest.mark.skipif(PLATFORM != "osx", reason="macOS only")
+@@ -381,14 +400,14 @@ def test_info_plist_duplicate():
+ 
+ @pytest.mark.skipif(PLATFORM != "osx", reason="macOS only")
+ def test_osx_symlinks(delete_files):
+-    json_path, paths, _, output = check_output_from_shortcut(
++    json_path, paths, tmp_base_path, output = check_output_from_shortcut(
+         delete_files, "osx_symlinks.json", remove_after=False
+     )
+     app_dir = next(p for p in paths if p.name.endswith(".app"))
+     symlinked_python = app_dir / "Contents" / "Resources" / "python"
+     assert output.strip() == str(symlinked_python)
+     assert symlinked_python.resolve() == (Path(DEFAULT_PREFIX) / "bin" / "python").resolve()
+-    remove(json_path)
++    remove(json_path, target_prefix=tmp_base_path, base_prefix=tmp_base_path)
+ 
+ 
+ def _dump_ls_services():
+@@ -423,7 +442,9 @@ def test_file_type_association_no_event_handler(delete_files, request):
+         file_to_open=test_file,
+         remove_after=False,
+     )
+-    request.addfinalizer(lambda: remove(abs_json_path, base_prefix=tmp_base_path))
++    request.addfinalizer(
++        lambda: remove(abs_json_path, target_prefix=tmp_base_path, base_prefix=tmp_base_path)
++    )
+     app_dir = next(p for p in paths if p.name.endswith(".app"))
+     info = app_dir / "Contents" / "Info.plist"
+     plist = plistlib.loads(info.read_bytes())
+diff --git a/tests/test_metadata.py b/tests/test_metadata.py
+index 9b44365f..f47bd301 100644
+--- a/tests/test_metadata.py
++++ b/tests/test_metadata.py
+@@ -159,9 +159,7 @@ def test_distribution_name_only_written_to_base_prefix(self, tmp_path: Path) ->
+         assert len(data["shortcuts"]) == 1
+ 
+     @pytest.mark.skipif(sys.platform == "win32", reason="chmod doesn't work on Windows")
+-    def test_record_shortcuts_handles_permission_error(
+-        self, tmp_path: Path, caplog
+-    ) -> None:
++    def test_record_shortcuts_handles_permission_error(self, tmp_path: Path, caplog) -> None:
+         """record_shortcuts() should not raise when prefix is read-only."""
+         # Create a read-only directory
+         readonly_prefix = tmp_path / "readonly"
+
+From 66dd735f561bbc89bfc6d2633f6da856531ccb59 Mon Sep 17 00:00:00 2001
+From: Robin <randersson@anaconda.com>
+Date: Fri, 5 Jun 2026 12:11:03 -0400
+Subject: [PATCH 3/4] Add news
+
+---
+ news/497-improve-tests | 19 +++++++++++++++++++
+ 1 file changed, 19 insertions(+)
+ create mode 100644 news/497-improve-tests
+
+diff --git a/news/497-improve-tests b/news/497-improve-tests
+new file mode 100644
+index 00000000..23b95cf3
+--- /dev/null
++++ b/news/497-improve-tests
+@@ -0,0 +1,19 @@
++### Enhancements
++
++* <news item>
++
++### Bug fixes
++
++* Gracefully handle permission errors when writing `menuinst.toml` in read-only environments. (#496)
++
++### Deprecations
++
++* <news item>
++
++### Docs
++
++* <news item>
++
++### Other
++
++* Refactor some tests to use temporary directories instead of using `sys.prefix`. (#496)
+
+From 30e7f963651b08e5cbc4ba2b25c9d3e55afd5c9a Mon Sep 17 00:00:00 2001
+From: Robin <randersson@anaconda.com>
+Date: Fri, 5 Jun 2026 14:32:33 -0400
+Subject: [PATCH 4/4] Review fixes
+
+---
+ tests/test_api.py      |  3 +-
+ tests/test_metadata.py | 83 ++++++++++++++++++++++--------------------
+ 2 files changed, 45 insertions(+), 41 deletions(-)
+
+diff --git a/tests/test_api.py b/tests/test_api.py
+index 17ea419b..ec149e18 100644
+--- a/tests/test_api.py
++++ b/tests/test_api.py
+@@ -73,7 +73,8 @@ def check_output_from_shortcut(
+         (Path(tmp_base_path) / ".nonadmin").touch()
+         # conda-meta makes conda treat this as a valid environment for activation
+         (Path(tmp_base_path) / "conda-meta").mkdir(exist_ok=True)
+-        # Symlink to real Python so {{ PYTHON }} placeholder works
++        # Shortcuts use {{ PYTHON }} which resolves to prefix/python.exe or prefix/bin/python.
++        # We symlink to sys.executable; copying doesn't work (can't find runtime libraries).
+         if PLATFORM == "win":
+             python_path = Path(tmp_base_path) / "python.exe"
+         else:
+diff --git a/tests/test_metadata.py b/tests/test_metadata.py
+index f47bd301..00209d84 100644
+--- a/tests/test_metadata.py
++++ b/tests/test_metadata.py
+@@ -5,14 +5,13 @@
+ import json
+ import logging
+ import os
++import subprocess
+ import sys
++from contextlib import contextmanager
+ from typing import TYPE_CHECKING
+ 
+ import pytest
+ 
+-if TYPE_CHECKING:
+-    from pathlib import Path
+-
+ from menuinst.api import (
+     _install_adapter,
+     record_shortcuts,
+@@ -22,11 +21,31 @@
+ from menuinst.platforms import Menu
+ from menuinst.utils import MENUINST_TOML_SCHEMA_VERSION, parse_schemaver, read_menuinst_toml
+ 
++if TYPE_CHECKING:
++    from pathlib import Path
++
+ # Placeholder distribution names for tests
+ DIST_NAME = "Something"
+ DIST_NAME_ALT = "SomethingElse"
+ 
+ 
++@contextmanager
++def make_readonly(path: "Path"):
++    """Make a path read-only, restoring permissions on exit."""
++    if sys.platform == "win32":
++        subprocess.run(["icacls", str(path), "/deny", f"{os.getlogin()}:(W)"], check=True)
++        try:
++            yield
++        finally:
++            subprocess.run(["icacls", str(path), "/remove:d", os.getlogin()], check=True)
++    else:
++        os.chmod(path, 0o555)
++        try:
++            yield
++        finally:
++            os.chmod(path, 0o755)
++
++
+ class TestGetDistributionName:
+     """Tests for Menu._get_distribution_name() resolution order."""
+ 
+@@ -158,62 +177,46 @@ def test_distribution_name_only_written_to_base_prefix(self, tmp_path: Path) ->
+         assert "distribution_name" not in data
+         assert len(data["shortcuts"]) == 1
+ 
+-    @pytest.mark.skipif(sys.platform == "win32", reason="chmod doesn't work on Windows")
+     def test_record_shortcuts_handles_permission_error(self, tmp_path: Path, caplog) -> None:
+         """record_shortcuts() should not raise when prefix is read-only."""
+-        # Create a read-only directory
+-        readonly_prefix = tmp_path / "readonly"
+-        readonly_prefix.mkdir()
+-        os.chmod(readonly_prefix, 0o555)
++        prefix = tmp_path / "readonly"
++        prefix.mkdir()
++        menu_dir = prefix / "Menu"
++        menu_dir.mkdir()
+ 
+-        try:
+-            with caplog.at_level(logging.DEBUG):
+-                # This should NOT raise PermissionError
+-                record_shortcuts(
+-                    prefix=readonly_prefix,
+-                    base_prefix=readonly_prefix,
+-                    source="test.json",
+-                    paths=[tmp_path / "fake" / "path" / "shortcut.desktop"],
+-                )
+-
+-            # Should log a debug message about permission denied
+-            assert "permission denied" in caplog.text.lower()
+-        finally:
+-            # Restore permissions for cleanup
+-            os.chmod(readonly_prefix, 0o755)
++        # Pre-create empty TOML so the test is consistent with test_remove_* below
++        write_menuinst_toml(prefix, {})
++
++        with make_readonly(menu_dir), make_readonly(prefix), caplog.at_level(logging.DEBUG):
++            # This should NOT raise PermissionError
++            record_shortcuts(
++                prefix=prefix,
++                base_prefix=prefix,
++                source="test.json",
++                paths=[tmp_path / "fake" / "path" / "shortcut.desktop"],
++            )
++
++        assert "permission denied" in caplog.text.lower()
+ 
+-    @pytest.mark.skipif(sys.platform == "win32", reason="chmod doesn't work on Windows")
+     def test_remove_shortcut_records_handles_permission_error(
+         self, tmp_path: Path, caplog
+     ) -> None:
+         """remove_shortcut_records() should not raise when prefix is read-only."""
+-        # Create a prefix with a menuinst.toml
+         prefix = tmp_path / "prefix"
+         prefix.mkdir()
+         menu_dir = prefix / "Menu"
+         menu_dir.mkdir()
+ 
+-        # Write initial TOML data
+         write_menuinst_toml(
+             prefix,
+             {"shortcuts": [{"source": "test.json", "path": "/fake/path"}]},
+         )
+ 
+-        # Make the directory read-only
+-        os.chmod(menu_dir, 0o555)
+-        os.chmod(prefix, 0o555)
++        with make_readonly(menu_dir), make_readonly(prefix), caplog.at_level(logging.DEBUG):
++            # This should NOT raise PermissionError
++            remove_shortcut_records(prefix, "test.json")
+ 
+-        try:
+-            with caplog.at_level(logging.DEBUG):
+-                # This should NOT raise PermissionError
+-                remove_shortcut_records(prefix, "test.json")
+-
+-            # Should log a debug message about permission denied
+-            assert "permission denied" in caplog.text.lower()
+-        finally:
+-            # Restore permissions for cleanup
+-            os.chmod(prefix, 0o755)
+-            os.chmod(menu_dir, 0o755)
++        assert "permission denied" in caplog.text.lower()
+ 
+ 
+ class TestInstallAdapter:

diff --git a/README.packit b/README.packit
new file mode 100644
index 0000000..4e76cbe
--- /dev/null
+++ b/README.packit
@@ -0,0 +1,3 @@
+This repository is maintained by packit.
+https://packit.dev/
+The file was generated using packit 1.16.0.post1.dev8+g8a0482385.

diff --git a/python-menuinst.spec b/python-menuinst.spec
index 88e6a05..0b3f3d3 100644
--- a/python-menuinst.spec
+++ b/python-menuinst.spec
@@ -3,13 +3,15 @@
 %bcond bootstrap 0
 
 Name:           python-%{srcname}
-Version:        2.4.2
+Version:        2.5.0
 Release:        %autorelease
 Summary:        Cross platform menu item installation
 
 License:        BSD-3-Clause
 URL:            https://github.com/conda/menuinst
 Source:         %{url}/archive/%{version}/%{srcname}-%{version}.tar.gz
+# Upstream patch to fix tests
+Patch:          https://github.com/conda/menuinst/pull/497.patch
 
 BuildArch:      noarch
 %if %{without bootstrap}

diff --git a/sources b/sources
index 96afd57..d5455e8 100644
--- a/sources
+++ b/sources
@@ -1 +1 @@
-SHA512 (menuinst-2.4.2.tar.gz) = 3385f2a5cfcd47fe2d9170a28ca33e12f9e92ea884f48327dc5ff631322abcb3c0b547550cb95fc52ca2e51ae52cadbcadaac03aec58dc3e1e5f18b7dcf19199
+SHA512 (menuinst-2.5.0.tar.gz) = aa50d1b956d57cd988c97b36ebef34d2e122980f7e807e67b27b1431576d76cc9c62a48c803bb00889d185600c526db8ad06a4be2201663c7b1a2339af3f5864

^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-06-07 21:13 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-06-07 21:13 [rpms/python-menuinst] rawhide: Update to 2.5.0 upstream release Packit

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox