@@ -2067,6 +2067,14 @@ def setUp(self):
20672067 self .curdir = os .curdir
20682068 self .ext = ".EXE"
20692069
2070+ def to_text_type (self , s ):
2071+ '''
2072+ In this class we're testing with str, so convert s to a str
2073+ '''
2074+ if isinstance (s , bytes ):
2075+ return s .decode ()
2076+ return s
2077+
20702078 def test_basic (self ):
20712079 # Given an EXE in a directory, it should be returned.
20722080 rv = shutil .which (self .file , path = self .dir )
@@ -2254,9 +2262,9 @@ def test_empty_path_no_PATH(self):
22542262
22552263 @unittest .skipUnless (sys .platform == "win32" , 'test specific to Windows' )
22562264 def test_pathext (self ):
2257- ext = ".xyz"
2265+ ext = self . to_text_type ( ".xyz" )
22582266 temp_filexyz = tempfile .NamedTemporaryFile (dir = self .temp_dir ,
2259- prefix = "Tmp2" , suffix = ext )
2267+ prefix = self . to_text_type ( "Tmp2" ) , suffix = ext )
22602268 os .chmod (temp_filexyz .name , stat .S_IXUSR )
22612269 self .addCleanup (temp_filexyz .close )
22622270
@@ -2265,38 +2273,39 @@ def test_pathext(self):
22652273 program = os .path .splitext (program )[0 ]
22662274
22672275 with os_helper .EnvironmentVarGuard () as env :
2268- env ['PATHEXT' ] = ext
2276+ env ['PATHEXT' ] = ext if isinstance ( ext , str ) else ext . decode ()
22692277 rv = shutil .which (program , path = self .temp_dir )
22702278 self .assertEqual (rv , temp_filexyz .name )
22712279
22722280 # Issue 40592: See https://bugs.python.org/issue40592
22732281 @unittest .skipUnless (sys .platform == "win32" , 'test specific to Windows' )
22742282 def test_pathext_with_empty_str (self ):
2275- ext = ".xyz"
2283+ ext = self . to_text_type ( ".xyz" )
22762284 temp_filexyz = tempfile .NamedTemporaryFile (dir = self .temp_dir ,
2277- prefix = "Tmp2" , suffix = ext )
2285+ prefix = self . to_text_type ( "Tmp2" ) , suffix = ext )
22782286 self .addCleanup (temp_filexyz .close )
22792287
22802288 # strip path and extension
22812289 program = os .path .basename (temp_filexyz .name )
22822290 program = os .path .splitext (program )[0 ]
22832291
22842292 with os_helper .EnvironmentVarGuard () as env :
2285- env ['PATHEXT' ] = f"{ ext } ;" # note the ;
2293+ env ['PATHEXT' ] = f"{ ext if isinstance ( ext , str ) else ext . decode () } ;" # note the ;
22862294 rv = shutil .which (program , path = self .temp_dir )
22872295 self .assertEqual (rv , temp_filexyz .name )
22882296
22892297 # See GH-75586
22902298 @unittest .skipUnless (sys .platform == "win32" , 'test specific to Windows' )
22912299 def test_pathext_applied_on_files_in_path (self ):
22922300 with os_helper .EnvironmentVarGuard () as env :
2293- env ["PATH" ] = self .temp_dir
2301+ env ["PATH" ] = self .temp_dir if isinstance ( self . temp_dir , str ) else self . temp_dir . decode ()
22942302 env ["PATHEXT" ] = ".test"
22952303
2296- test_path = pathlib .Path (self .temp_dir ) / "test_program.test"
2297- test_path .touch (mode = 0o755 )
2304+ test_path = os .path .join (self .temp_dir , self .to_text_type ("test_program.test" ))
2305+ open (test_path , 'w' ).close ()
2306+ os .chmod (test_path , 0o755 )
22982307
2299- self .assertEqual (shutil .which ("test_program" ), str ( test_path ) )
2308+ self .assertEqual (shutil .which (self . to_text_type ( "test_program" )), test_path )
23002309
23012310 # See GH-75586
23022311 @unittest .skipUnless (sys .platform == "win32" , 'test specific to Windows' )
@@ -2312,16 +2321,69 @@ def test_win_path_needs_curdir(self):
23122321 self .assertFalse (shutil ._win_path_needs_curdir ('dontcare' , os .X_OK ))
23132322 need_curdir_mock .assert_called_once_with ('dontcare' )
23142323
2324+ # See GH-109590
2325+ @unittest .skipUnless (sys .platform == "win32" , 'test specific to Windows' )
2326+ def test_pathext_preferred_for_execute (self ):
2327+ with os_helper .EnvironmentVarGuard () as env :
2328+ env ["PATH" ] = self .temp_dir if isinstance (self .temp_dir , str ) else self .temp_dir .decode ()
2329+ env ["PATHEXT" ] = ".test"
2330+
2331+ exe = os .path .join (self .temp_dir , self .to_text_type ("test.exe" ))
2332+ open (exe , 'w' ).close ()
2333+ os .chmod (exe , 0o755 )
2334+
2335+ # default behavior allows a direct match if nothing in PATHEXT matches
2336+ self .assertEqual (shutil .which (self .to_text_type ("test.exe" )), exe )
2337+
2338+ dot_test = os .path .join (self .temp_dir , self .to_text_type ("test.exe.test" ))
2339+ open (dot_test , 'w' ).close ()
2340+ os .chmod (dot_test , 0o755 )
2341+
2342+ # now we have a PATHEXT match, so it take precedence
2343+ self .assertEqual (shutil .which (self .to_text_type ("test.exe" )), dot_test )
2344+
2345+ # but if we don't use os.X_OK we don't change the order based off PATHEXT
2346+ # and therefore get the direct match.
2347+ self .assertEqual (shutil .which (self .to_text_type ("test.exe" ), mode = os .F_OK ), exe )
2348+
2349+ # See GH-109590
2350+ @unittest .skipUnless (sys .platform == "win32" , 'test specific to Windows' )
2351+ def test_pathext_given_extension_preferred (self ):
2352+ with os_helper .EnvironmentVarGuard () as env :
2353+ env ["PATH" ] = self .temp_dir if isinstance (self .temp_dir , str ) else self .temp_dir .decode ()
2354+ env ["PATHEXT" ] = ".exe2;.exe"
2355+
2356+ exe = os .path .join (self .temp_dir , self .to_text_type ("test.exe" ))
2357+ open (exe , 'w' ).close ()
2358+ os .chmod (exe , 0o755 )
2359+
2360+ exe2 = os .path .join (self .temp_dir , self .to_text_type ("test.exe2" ))
2361+ open (exe2 , 'w' ).close ()
2362+ os .chmod (exe2 , 0o755 )
2363+
2364+ # even though .exe2 is preferred in PATHEXT, we matched directly to test.exe
2365+ self .assertEqual (shutil .which (self .to_text_type ("test.exe" )), exe )
2366+ self .assertEqual (shutil .which (self .to_text_type ("test" )), exe2 )
2367+
23152368
23162369class TestWhichBytes (TestWhich ):
23172370 def setUp (self ):
23182371 TestWhich .setUp (self )
23192372 self .dir = os .fsencode (self .dir )
23202373 self .file = os .fsencode (self .file )
23212374 self .temp_file .name = os .fsencode (self .temp_file .name )
2375+ self .temp_dir = os .fsencode (self .temp_dir )
23222376 self .curdir = os .fsencode (self .curdir )
23232377 self .ext = os .fsencode (self .ext )
23242378
2379+ def to_text_type (self , s ):
2380+ '''
2381+ In this class we're testing with bytes, so convert s to a bytes
2382+ '''
2383+ if isinstance (s , str ):
2384+ return s .encode ()
2385+ return s
2386+
23252387
23262388class TestMove (BaseTest , unittest .TestCase ):
23272389
0 commit comments