Skip to content

gh-109590: Fix incorrect resolution of paths without extensions in shutil.which on win32. #109797

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 164 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
164 commits
Select commit Hold shift + click to select a range
41cb299
gh-109590 - Fix shutil.which to more closely match CMD on win32
csm10495 Sep 24, 2023
d505afd
📜🤖 Added by blurb_it.
blurb-it[bot] Sep 24, 2023
287a3fa
GH-109373: Store metadata required for pystats comparison in the JSON…
mdboom Sep 15, 2023
99d114e
gh-109096: Deprecate `http.server.CGIHTTPRequestHandler` (#109387)
gpshead Sep 15, 2023
f9cabbf
gh-106213: Make Emscripten trampolines work with JSPI (GH-106219)
hoodmane Sep 15, 2023
a4284f9
GH-83417: Allow `venv` to add a `.gitignore` file to environments via…
brettcannon Sep 15, 2023
691fd9c
gh-109474: Update two Unix packaging URLs (#109307)
partev Sep 16, 2023
b7ff008
gh-109414: Add some basic information about venvs in the introduction…
vsajip Sep 16, 2023
d152179
gh-109451: Fix wrong format specifier in logging documentation (GH-10…
AlberLC Sep 16, 2023
0174f87
gh-108303: Move all certificates to `Lib/test/certdata/` (#109489)
sobolevn Sep 16, 2023
b14d608
gh-108511: Add C API functions which do not silently ignore errors (G…
serhiy-storchaka Sep 17, 2023
be22f09
gh-109408: Remove Ubuntu unit tests from Azure Pipelines (#109452)
hugovk Sep 17, 2023
ea912a6
gh-109413: Improve mypy config for libregrtest (#109518)
AlexWaygood Sep 18, 2023
657f884
Fix extraneous backslashes in hashlib docs (#109468)
asottile Sep 18, 2023
5e484bf
Fix a typo in c-analyzer (#109213)
Son-Dongwoo Sep 18, 2023
0722ddf
Docs: getopt is deprecated in Python 3.13 (#109438)
hugovk Sep 18, 2023
7c24e26
gh-109408: Azure Pipelines: test 3.12 branch (#109453)
hugovk Sep 18, 2023
3394750
gh-108303: Fix and move `badsyntax_pep3120.py` (#109513)
sobolevn Sep 18, 2023
0fe458e
gh-108843: fix ast.unparse for f-string with many quotes (#108981)
hauntsaninja Sep 18, 2023
ea21924
gh-109371: Fix monitoring with instruction events set (gh-109385)
gaogaotiantian Sep 18, 2023
aad8670
gh-109508: Fix libregrtest formatting of getcwd() (#109537)
vstinner Sep 18, 2023
6d4c96a
gh-109496: Detect Py_DECREF() after dealloc in debug mode (#109539)
vstinner Sep 18, 2023
ebfa90b
gh-102757: fix function signature mismatch for `functools.reduce` bet…
XuehaiPan Sep 18, 2023
6d6ceb9
Fix error handling in _PySys_UpdateConfig() (GH-109524)
serhiy-storchaka Sep 18, 2023
be83a3a
gh-109546: Add more tests for formatting floats and fractions (GH-109…
serhiy-storchaka Sep 19, 2023
3eb65fb
gh-109469: Silence compiler warnings on string comparisons in _testca…
serhiy-storchaka Sep 19, 2023
c0a0a8a
gh-109125: Run mypy on `Tools/wasm` (#109126)
sobolevn Sep 19, 2023
20b9e40
no-issue: Fix typo TestContentTyopeHeader to TestContentTypeHeader (g…
jenner9212 Sep 19, 2023
a116a04
gh-109435: Add Doc/library/cmdline.rst (#109436)
vstinner Sep 19, 2023
7921882
gh-109485: Further improve `test_future_stmt` tests (#109486)
sobolevn Sep 19, 2023
24c6ae9
Misc itertool recipe improvements, mostly docstrings and comments (gh…
rhettinger Sep 19, 2023
9e4d048
gh-109566: Run GHA and buildbot tests with --fail-rerun (#109567)
vstinner Sep 19, 2023
416dda9
gh-109496: Skip test_capi.test_decref_freed_object() on ASAN (#109573)
vstinner Sep 19, 2023
867e79e
gh-108724: Add PyMutex and _PyParkingLot APIs (gh-109344)
colesbury Sep 19, 2023
cd452fb
gh-108724: Fix _PySemaphore compile error on WASM (gh-109583)
colesbury Sep 19, 2023
856df53
gh-109580: Skip test_perf_profiler on ASAN build (#109584)
vstinner Sep 19, 2023
fad1dac
gh-76785: Use Pending Calls When Releasing Cross-Interpreter Data (gh…
ericsnowcurrently Sep 19, 2023
fd59071
gh-109033: Return filename with os.utime errors (#109034)
rpigott Sep 19, 2023
67622e5
gh-109109: Expose retrieving certificate chains in SSL module (#109113)
matiuszka Sep 20, 2023
1ab8940
gh-90108: Disable LTO on _freeze_module and _testembed (#109581)
vstinner Sep 20, 2023
b7fe6da
gh-103053: Skip test_freeze_simple_script() on PGO build (#109591)
vstinner Sep 20, 2023
cd91e0b
gh-109543: Remove unnecessary hasattr check (#109544)
JelleZijlstra Sep 20, 2023
f9b55bd
fixes gh-109559: Update `unicodedata` for Unicode 15.1.0 (GH-109560)
SnoopJ Sep 20, 2023
7d7587c
gh-109559: Update unicodedata checksums for 15.1.0. (#109597)
benjaminp Sep 20, 2023
b7c7d8c
gh-108973: Fix asyncio test_subprocess_consistent_callbacks() (#109431)
vstinner Sep 20, 2023
e2f97a8
gh-109054: Document configure variables (#109224)
vstinner Sep 20, 2023
7611b2b
gh-109390: add dump_symtable utility under #if 0 (#109391)
carljm Sep 20, 2023
3f7755f
Fix typos in docs and comments (#109619)
afuetterer Sep 20, 2023
1606625
gh-109408: Move Windows builds from Azure Pipelines PR to GitHub Acti…
hugovk Sep 20, 2023
1d9f0f3
gh-109627: duplicated smalll exit blocks need to be assigned jump tar…
iritkatriel Sep 20, 2023
949922e
GH-109209: Bump the minimum Sphinx version to 4.2 (#109210)
AA-Turner Sep 21, 2023
4806a15
gh-109625: Move _ready_to_import() from test_import to support.import…
sobolevn Sep 21, 2023
14103aa
gh-109613: _pystat_fromstructstat() checks for exceptions (#109618)
vstinner Sep 21, 2023
4d30c4f
gh-74481: Add missing debug function docs and constants to msvcrt (GH…
aisk Sep 21, 2023
ff83b9d
GH-109190: Copyedit 3.12 What's New: PEP 709 (#109656)
AA-Turner Sep 21, 2023
382fff8
GH-109190: Copyedit 3.12 What's New: Typing PEPs (#109659)
AA-Turner Sep 21, 2023
08cdf0e
gh-108303: Move all math files to `Lib/test/mathdata/` (#109512)
sobolevn Sep 21, 2023
03328a3
GH-109190: Copyedit 3.12 What's New: Improved Error Messages (#109654)
AA-Turner Sep 21, 2023
01638c0
GH-109190: Copyedit 3.12 What's New: PEP 701 (#109655)
AA-Turner Sep 21, 2023
c31a6a2
gh-108303: Update test_fractions for new Lib/test/mathdata/ (#109686)
vstinner Sep 21, 2023
cd529b4
gh-109693: Remove pycore_atomic_funcs.h (#109694)
colesbury Sep 21, 2023
78d3ba2
gh-109582: test_fork_signal_handling should wait for event (#109605)
sorcio Sep 21, 2023
a6fc8f3
gh-108948: Skip test_tarfile.test_modes() on EFTYPE error (#109697)
vstinner Sep 21, 2023
c91677e
gh-104469: Disallow using Py_LIMITED_API with Py_BUILD_CORE (#109690)
vstinner Sep 21, 2023
8e0eaa7
gh-108996: fix and enable test_msvcrt (#109226)
aisk Sep 22, 2023
9e38e69
gh-109702: Increase concurrent_futures deadlock timeout (#109703)
vstinner Sep 22, 2023
4ba8474
gh-109566: Fix typo in PCbuild/rt.bat (#109701)
vstinner Sep 22, 2023
034b950
GH-109190: Copyedit 3.12 What's New: Consistently show module names …
AA-Turner Sep 22, 2023
b26e23f
GH-109190: Copyedit 3.12 What's New: tokenize (#109663)
AA-Turner Sep 22, 2023
1a67122
GH-109190: Copyedit 3.12 What's New: calendar (#109662)
AA-Turner Sep 22, 2023
53fda70
gh-106584: Fix exit code for unittest in Python 3.12 (#106588)
EliseevEgor Sep 22, 2023
9682424
GH-109190: Copyedit 3.12 What's New: bytecode (LOAD_METHOD) (#109665)
AA-Turner Sep 22, 2023
1a54072
GH-109190: Copyedit 3.12 What's New: PEP 684 (#109657)
AA-Turner Sep 22, 2023
9a05c03
gh-105829: Fix concurrent.futures.ProcessPoolExecutor deadlock (#108513)
elfstrom Sep 22, 2023
c7d4b0b
GH-109190: Copyedit 3.12 What's New: Other Language Changes (#109660)
AA-Turner Sep 22, 2023
8862990
Remove outdated docstring from the `quantify` itertools recipe (#109726)
ambv Sep 22, 2023
ba232d5
gh-109709: Fix asyncio test_stdin_broken_pipe() (#109710)
vstinner Sep 22, 2023
b099d56
GH-109190: Copyedit 3.12 What's New: PEP 669 (#109658)
AA-Turner Sep 22, 2023
588f1d1
GH-109190: Copyedit 3.12 What's New: asyncio (#109661)
AA-Turner Sep 22, 2023
118a20f
gh-109723: Disable Py_BUILD_CORE in _testcapi (#109727)
vstinner Sep 22, 2023
ff9ecae
ACKS: Fix ordering; Correct Itamar Oren's surname; Add Adam Turner (…
AA-Turner Sep 22, 2023
7ae8f6b
gh-109164: Replace `getopt` with `argparse` in pdb (#109165)
gaogaotiantian Sep 22, 2023
4c4a2f5
gh-109719: Fix missing jump target labels when compiler reorders cold…
iritkatriel Sep 22, 2023
65a74e3
gh-109596: Ensure repeated rules in the grammar are not allowed and f…
pablogsal Sep 22, 2023
2e0277d
Docs: Update Donghee Na's name (#109743)
hugovk Sep 22, 2023
df173c4
gh-109721: Guard `_testinternalcapi` imports in tests (GH-109722)
sobolevn Sep 22, 2023
199ad02
GH-107265: Add missing deoptimizations for ENTER_EXECUTOR's original …
gaogaotiantian Sep 22, 2023
0066283
gh-109706: Fix multiprocessing test_nested_startmethod() (#109707)
vstinner Sep 22, 2023
f954304
Fix indentation in 3.13 What's New (#109769)
JelleZijlstra Sep 23, 2023
a87a43b
GH-95913: Add the release date for Python 3.11 (#109750)
AA-Turner Sep 23, 2023
74e0150
gh-109505: Remove unnecessary `hasattr` checks from `test_asyncio` (#…
sobolevn Sep 23, 2023
f1cffd7
gh-100228: Document the os.fork threads DeprecationWarning. (#109767)
gpshead Sep 23, 2023
ca1edfe
gh-109634: Use :samp: role (GH-109635)
serhiy-storchaka Sep 23, 2023
58bb3df
gh-109611: Add convenient C API function _PyFile_Flush() (GH-109612)
serhiy-storchaka Sep 23, 2023
309ff27
gh-109521: Fix obscure cases handling in PyImport_GetImporter() (GH-1…
serhiy-storchaka Sep 23, 2023
973823d
gh-109653: `typing.py`: improve import time by creating soft-deprecat…
AlexWaygood Sep 23, 2023
9087a14
gh-109653: Improve `enum` import time by avoiding import of `functool…
AlexWaygood Sep 23, 2023
a113eea
gh-101100: Fix sphinx warnings in `Doc/library/xml.etree.elementtree.…
sobolevn Sep 24, 2023
1b27fe3
gh-109653: Remove unused imports in the `Lib/` directory (#109803)
AlexWaygood Sep 24, 2023
0cac4f0
GH-109190: Copyedit 3.12 What's New: Use the ``:file:`` role (#109756)
AA-Turner Sep 24, 2023
bb9da70
gh-109653: Avoid a top-level import of `types` in `functools` (#109804)
AlexWaygood Sep 24, 2023
2c0fa66
Sync whatsnew with the edit I made in the 3.12 backport PR. (#109807)
gpshead Sep 24, 2023
d030ecf
GH-109190: Copyedit 3.12 What's New: Increase the prominence of the s…
AA-Turner Sep 25, 2023
58ed026
GH-109190: Copyedit 3.12 What's New: Update the ``imp`` porting guida…
AA-Turner Sep 25, 2023
4b65aa2
gh-101100: Fix sphinx warnings in `Doc/library/__future__.rst` (#109814)
sobolevn Sep 25, 2023
4ede466
gh-104469: Convert _testcapi/vectorcall_limited.c to use AC (#109691)
vstinner Sep 25, 2023
0de2bc3
GH-109190: Copyedit 3.12 What's New: Prefer GitHub issues links (#109…
AA-Turner Sep 25, 2023
bc90587
GH-109190: Copyedit 3.12 What's New: Trivia (#109760)
AA-Turner Sep 25, 2023
0b852ff
GH-109190: Copyedit 3.12 What's New: Use the present tense (#109754)
AA-Turner Sep 25, 2023
1908ddb
no-issue: Capitalise 'PhotoImage' (gh-108958)
Son-Dongwoo Sep 25, 2023
a663082
GH-109190: Copyedit 3.12 What's New: Improve the C-API deprecations s…
AA-Turner Sep 25, 2023
a4a3be1
gh-109833: Fix asyncio test_wait_for() (#109834)
vstinner Sep 25, 2023
9f395fc
gh-109276: Enhance libregrtest results (#109828)
vstinner Sep 25, 2023
e905fda
GH-109190: Copyedit 3.12 What's New: Synchronise C API deprecations w…
AA-Turner Sep 25, 2023
82ab866
gh-109276: regrtest re-runs "env changed" tests (#109831)
vstinner Sep 25, 2023
8fb8db6
GH-109190: Copyedit 3.12 What's New: Sort Other Language Changes (#10…
AA-Turner Sep 25, 2023
4113684
gh-109723: Fix build of _testclinic_limited on WASM (#109842)
vstinner Sep 25, 2023
9045142
Code: Update Donghee Na's name (#109744)
hugovk Sep 25, 2023
f3d62b3
gh-109795: `_thread.start_new_thread`: allocate thread bootstate usin…
chgnrdv Sep 25, 2023
00310f6
gh-89363: Skip threading test_is_alive_after_fork() if ASAN (#109835)
vstinner Sep 25, 2023
9334268
gh-109599: Add types.CapsuleType (#109600)
pitrou Sep 25, 2023
a262c6e
gh-109823: Adjust labels in compiler when removing an empty basic blo…
iritkatriel Sep 25, 2023
fe9f2a8
gh-109748: Fix venv test_zippath_from_non_installed_posix() (#109872)
vstinner Sep 25, 2023
b3ee6d0
gh-88233: zipfile: refactor _strip_extra (#102084)
jaraco Sep 25, 2023
f93446f
gh-109401: Fix threading barrier test_default_timeout() (#109875)
vstinner Sep 26, 2023
8e7d778
gh-109739: regrtest disables load tracker if refleak (#109871)
vstinner Sep 26, 2023
4ddd6fa
gh-109370: Fix unexpected traceback output in test_concurrent_futures…
serhiy-storchaka Sep 26, 2023
c8a95e2
gh-101100: Fix Sphinx warnings in `Doc/library/weakref.rst` (#109881)
sobolevn Sep 26, 2023
b65bed6
gh-109631: Allow interruption of short repeated regex matches (GH-109…
serhiy-storchaka Sep 26, 2023
a9ed4b2
gh-109832: concurrent.futures test_deadlock restores sys.stderr (#109…
vstinner Sep 26, 2023
80619be
gh-109593: Fix reentrancy issue in multiprocessing resource_tracker (…
pitrou Sep 26, 2023
dc0c068
no-issue: Fix a typo in the parameter name of random.expovariate. (gh…
lohaswinner Sep 26, 2023
5a1b96a
More informative docstrings in the random module (gh-109745)
rhettinger Sep 26, 2023
a3c4161
gh-109566, regrtest: Add --fast-ci and --slow-ci options (#109570)
vstinner Sep 26, 2023
7de1cc0
GH-109187: Improve symlink loop handling in `pathlib.Path.resolve()` …
barneygale Sep 26, 2023
fdd4e67
gh-109566: regrtest reexecutes the process (#109909)
vstinner Sep 26, 2023
3c55b15
Remove concurrent.futures deadcode: process_result_item() (#109906)
vstinner Sep 26, 2023
5c99caf
gh-109276, gh-109508: Fix libregrtest stdout (#109903)
vstinner Sep 26, 2023
925b25e
gh-109845: Make test_ftplib more stable under load (GH-109912)
serhiy-storchaka Sep 26, 2023
aef7403
Fix argument ordering of embuilder command documented in `Tools/wasm/…
OmniTroid Sep 26, 2023
5ca9552
GH-109190: Copyedit 3.12 What's New: Deprecations (#109766)
AA-Turner Sep 26, 2023
b46c07a
gh-109566: Fix regrtest code adding Python options (#109926)
vstinner Sep 26, 2023
0933e14
gh-107888: Fix test_mmap.test_access_parameter() on macOS 14 (#109928)
vstinner Sep 26, 2023
6608db3
gh-109098: Fuzz re module instead of internal sre (#109911)
ammaraskar Sep 26, 2023
8ab8b6e
gh-101100: Fix Sphinx warnings in Doc/using/configure.rst (#109931)
vstinner Sep 27, 2023
14530dd
Remove loop from docstring for asyncio.streams.open_connection (#108528)
tgbugs Sep 27, 2023
d31993e
GH-109190: Copyedit 3.12 What's New: Deprecations (``os`` fix) (#109…
AA-Turner Sep 27, 2023
a6dd426
gh-109615: Fix test_tools.test_freeze SRCDIR (#109935)
vstinner Sep 27, 2023
cb42e83
gh-109565: Fix concurrent.futures test_future_times_out() (#109949)
vstinner Sep 27, 2023
f13088c
gh-109566: Fix regrtest Python options for WASM/WASI (#109954)
vstinner Sep 27, 2023
387cdc7
gh-109615: Fix support test_copy_python_src_ignore() (#109958)
vstinner Sep 27, 2023
64fcb92
gh-109923: set line number on the POP_TOP that follows a RETURN_GENER…
iritkatriel Sep 27, 2023
467a22b
gh-101100: Fix sphinx warnings in `library/devmode.rst` (#109963)
sobolevn Sep 27, 2023
5e4b590
gh-109566: regrtest doesn't enable --rerun if --python is used (#109969)
vstinner Sep 27, 2023
c722559
GH-109190: Copyedit 3.12 What's New: Release highlights (#109770)
AA-Turner Sep 27, 2023
9e0dbb9
gh-109740: Use 't' in `--disable-gil` SOABI (#109922)
colesbury Sep 27, 2023
c98e6ba
gh-109615: Fix support test_copy_python_src_ignore() on WASM (#109970)
vstinner Sep 27, 2023
e5576c0
gh-109461: Update logging module lock to use context manager (#109462)
dcollison Sep 27, 2023
d2bd511
Enhance TypedDict docs around required/optional keys (#109547)
JelleZijlstra Sep 27, 2023
93da346
gh-109793: Allow Switching Interpreters During Finalization (gh-109794)
ericsnowcurrently Sep 27, 2023
bd9e1cb
gh-109955 : Update state transition comments for asyncio.Task (#109910)
kristjanvalur Sep 27, 2023
e3cdf36
gh-104909: Split some more insts into ops (#109943)
gvanrossum Sep 27, 2023
9dd1e2f
Only return PATHEXT matches on win32 if X_OK is in mode
csm10495 Sep 28, 2023
10e3394
Merge branch 'python:main' into shutil_which_no_extv2
csm10495 Sep 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions Lib/shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -1554,8 +1554,16 @@ def which(cmd, mode=os.F_OK | os.X_OK, path=None):
if use_bytes:
pathext = [os.fsencode(ext) for ext in pathext]

# Always try checking the originally given cmd, if it doesn't match, try pathext
files = [cmd] + [cmd + ext for ext in pathext]
files = ([cmd] + [cmd + ext for ext in pathext])

# gh-109590. If we are looking for an executable, we need to look
# for a PATHEXT match. The first cmd is the direct match
# (e.g. python.exe instead of python)
# Check that direct match first if and only if the extension is in PATHEXT
if mode & os.X_OK and not any(
[os.path.splitext(files[0])[1].upper() == ext.upper() for ext in pathext]
):
files = files[1:]
else:
# On other platforms you don't have things like PATHEXT to tell you
# what file suffixes are executable, so just pass on cmd as-is.
Expand Down
75 changes: 65 additions & 10 deletions Lib/test/test_shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -2067,6 +2067,14 @@ def setUp(self):
self.curdir = os.curdir
self.ext = ".EXE"

def to_text_type(self, s):
'''
In this class we're testing with str, so convert s to a str
'''
if isinstance(s, bytes):
return s.decode()
return s

def test_basic(self):
# Given an EXE in a directory, it should be returned.
rv = shutil.which(self.file, path=self.dir)
Expand Down Expand Up @@ -2254,9 +2262,9 @@ def test_empty_path_no_PATH(self):

@unittest.skipUnless(sys.platform == "win32", 'test specific to Windows')
def test_pathext(self):
ext = ".xyz"
ext = self.to_text_type(".xyz")
temp_filexyz = tempfile.NamedTemporaryFile(dir=self.temp_dir,
prefix="Tmp2", suffix=ext)
prefix=self.to_text_type("Tmp2"), suffix=ext)
os.chmod(temp_filexyz.name, stat.S_IXUSR)
self.addCleanup(temp_filexyz.close)

Expand All @@ -2265,38 +2273,39 @@ def test_pathext(self):
program = os.path.splitext(program)[0]

with os_helper.EnvironmentVarGuard() as env:
env['PATHEXT'] = ext
env['PATHEXT'] = ext if isinstance(ext, str) else ext.decode()
rv = shutil.which(program, path=self.temp_dir)
self.assertEqual(rv, temp_filexyz.name)

# Issue 40592: See https://bugs.python.org/issue40592
@unittest.skipUnless(sys.platform == "win32", 'test specific to Windows')
def test_pathext_with_empty_str(self):
ext = ".xyz"
ext = self.to_text_type(".xyz")
temp_filexyz = tempfile.NamedTemporaryFile(dir=self.temp_dir,
prefix="Tmp2", suffix=ext)
prefix=self.to_text_type("Tmp2"), suffix=ext)
self.addCleanup(temp_filexyz.close)

# strip path and extension
program = os.path.basename(temp_filexyz.name)
program = os.path.splitext(program)[0]

with os_helper.EnvironmentVarGuard() as env:
env['PATHEXT'] = f"{ext};" # note the ;
env['PATHEXT'] = f"{ext if isinstance(ext, str) else ext.decode()};" # note the ;
rv = shutil.which(program, path=self.temp_dir)
self.assertEqual(rv, temp_filexyz.name)

# See GH-75586
@unittest.skipUnless(sys.platform == "win32", 'test specific to Windows')
def test_pathext_applied_on_files_in_path(self):
with os_helper.EnvironmentVarGuard() as env:
env["PATH"] = self.temp_dir
env["PATH"] = self.temp_dir if isinstance(self.temp_dir, str) else self.temp_dir.decode()
env["PATHEXT"] = ".test"

test_path = pathlib.Path(self.temp_dir) / "test_program.test"
test_path.touch(mode=0o755)
test_path = os.path.join(self.temp_dir, self.to_text_type("test_program.test"))
open(test_path, 'w').close()
os.chmod(test_path, 0o755)

self.assertEqual(shutil.which("test_program"), str(test_path))
self.assertEqual(shutil.which(self.to_text_type("test_program")), test_path)

# See GH-75586
@unittest.skipUnless(sys.platform == "win32", 'test specific to Windows')
Expand All @@ -2312,16 +2321,62 @@ def test_win_path_needs_curdir(self):
self.assertFalse(shutil._win_path_needs_curdir('dontcare', os.X_OK))
need_curdir_mock.assert_called_once_with('dontcare')

# See GH-109590
@unittest.skipUnless(sys.platform == "win32", 'test specific to Windows')
def test_pathext_enforced_for_execute(self):
with os_helper.EnvironmentVarGuard() as env:
env["PATH"] = self.temp_dir if isinstance(self.temp_dir, str) else self.temp_dir.decode()
env["PATHEXT"] = ".test"

exe = os.path.join(self.temp_dir, self.to_text_type("test.exe"))
open(exe, 'w').close()
os.chmod(exe, 0o755)

# default does not match since .exe is not in PATHEXT
self.assertIsNone(shutil.which(self.to_text_type("test.exe")))

# but if we don't use os.X_OK we're ok not matching PATHEXT
self.assertEqual(shutil.which(self.to_text_type("test.exe"), mode=os.F_OK), exe)

# See GH-109590
@unittest.skipUnless(sys.platform == "win32", 'test specific to Windows')
def test_pathext_given_extension_preferred(self):
with os_helper.EnvironmentVarGuard() as env:
env["PATH"] = self.temp_dir if isinstance(self.temp_dir, str) else self.temp_dir.decode()
env["PATHEXT"] = ".exe2;.exe"

exe = os.path.join(self.temp_dir, self.to_text_type("test.exe"))
open(exe, 'w').close()
os.chmod(exe, 0o755)

exe2 = os.path.join(self.temp_dir, self.to_text_type("test.exe2"))
open(exe2, 'w').close()
os.chmod(exe2, 0o755)

# even though .exe2 is preferred in PATHEXT, we matched directly to test.exe
self.assertEqual(shutil.which(self.to_text_type("test.exe")), exe)

self.assertEqual(shutil.which(self.to_text_type("test")), exe2)


class TestWhichBytes(TestWhich):
def setUp(self):
TestWhich.setUp(self)
self.dir = os.fsencode(self.dir)
self.file = os.fsencode(self.file)
self.temp_file.name = os.fsencode(self.temp_file.name)
self.temp_dir = os.fsencode(self.temp_dir)
self.curdir = os.fsencode(self.curdir)
self.ext = os.fsencode(self.ext)

def to_text_type(self, s):
'''
In this class we're testing with bytes, so convert s to a bytes
'''
if isinstance(s, str):
return s.encode()
return s


class TestMove(BaseTest, unittest.TestCase):

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:func:`shutil.which` will only match extension-less files on win32 if '.' is in ``PATHEXT``.