diff --git a/tests/test_image_reveal.py b/tests/test_image_reveal.py index 0f4ac8d..4b4b050 100644 --- a/tests/test_image_reveal.py +++ b/tests/test_image_reveal.py @@ -13,6 +13,7 @@ import sys import tempfile import types import unittest +from types import SimpleNamespace from pathlib import Path from unittest.mock import MagicMock, PropertyMock, patch @@ -289,6 +290,18 @@ class TestRevealInFileManager(unittest.TestCase): self.reveal() self.popen.assert_not_called() + def test_posix_opener_lookup_does_not_depend_on_the_host_path_rules(self): + # Python 3.13 on Windows no longer treats "/usr/bin/x" as absolute. The + # Linux and macOS openers are POSIX paths whatever host runs the code, + # so a host os.path with those rules must not reject them. + windows_313 = SimpleNamespace(isabs=lambda value: False) + with ( + patch.object(file_reveal.os, "path", windows_313), + self.which({"xdg-open": "/usr/bin/xdg-open", "rel": "bin/rel"}), + ): + self.assertEqual(file_reveal._executable("xdg-open"), "/usr/bin/xdg-open") + self.assertIsNone(file_reveal._executable("rel")) + def test_children_get_a_minimal_environment(self): inherited = { "PATH": "/venv/bin:/usr/bin", diff --git a/utils/file_reveal.py b/utils/file_reveal.py index a1aae94..61f5682 100644 --- a/utils/file_reveal.py +++ b/utils/file_reveal.py @@ -11,6 +11,7 @@ neither a file name nor ``PATH`` can change what runs or what it parses. import ipaddress import os +import posixpath import shutil # Only POSIX argv launches of a fixed opener (see _launch); Windows never @@ -227,9 +228,14 @@ class RevealGate: def _executable(name: str) -> str | None: - """Absolute path of *name* on the fixed SYSTEM_PATH, never a relative hit.""" + """Absolute path of *name* on the fixed SYSTEM_PATH, never a relative hit. + + Only the Linux and macOS openers are looked up here, so absoluteness follows + POSIX rules rather than the host's (Python 3.13 on Windows no longer counts + "/usr/bin/x" as absolute). + """ found = shutil.which(name, path=SYSTEM_PATH) - return found if found and os.path.isabs(found) else None + return found if found and posixpath.isabs(found) else None def _macos_open() -> str | None: