From a51ac154e2eca13e8a4de051f4b8074fd4aa5593 Mon Sep 17 00:00:00 2001 From: Roland Date: Wed, 21 Oct 2026 08:04:21 -0600 Subject: [PATCH] which() prefers the executable found in $PATH which() kept looking after shutil.which found a name in $PATH, so a later fallback path (e.g. '/usr/bin/git' in which('git', '/usr/bin/git')) always replaced it. Its docstring says $PATH comes first; stop at the first match. Co-Authored-By: Claude Opus 5.5 --- wrolpi/cmd.py | 1 + wrolpi/test/test_cmd.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/wrolpi/cmd.py b/wrolpi/cmd.py index c024ebee..13c313f2 100644 --- a/wrolpi/cmd.py +++ b/wrolpi/cmd.py @@ -41,6 +41,7 @@ def which(*possible_paths: str, warn: bool = False) -> Optional[Path]: if path and (path := Path(path).absolute()) and is_executable(path): # `path` was a name or relative path. found = path + break if not found and warn and not PYTEST and not DOCKERIZED: logger.warning(f'Cannot find executable {possible_paths[0]}') diff --git a/wrolpi/test/test_cmd.py b/wrolpi/test/test_cmd.py index 2a0dd235..96fe77d6 100644 --- a/wrolpi/test/test_cmd.py +++ b/wrolpi/test/test_cmd.py @@ -26,6 +26,20 @@ def test_which(test_directory): assert cmd.which('asdf', str(foo)) == foo +def test_which_prefers_path(test_directory, monkeypatch): + """A name found in $PATH wins over a fallback path, even when the fallback exists too.""" + on_path, fallback = test_directory / 'bin', test_directory / 'fallback' + for directory in (on_path, fallback): + directory.mkdir() + (directory / 'tool').touch() + os.chmod(directory / 'tool', 0o777) + monkeypatch.setenv('PATH', str(on_path)) + + assert cmd.which('tool', str(fallback / 'tool')) == on_path / 'tool' + # The fallback is used when the name is not in $PATH. + assert cmd.which('missing', str(fallback / 'tool')) == fallback / 'tool' + + TEST_SCRIPT = '''#!/bin/sh echo stdout echo stderr >&2