@@ -2068,6 +2068,14 @@ def setUp(self):
20682068 self .curdir = os .curdir
20692069 self .ext = ".EXE"
20702070
2071+ def to_text_type (self , s ):
2072+ '''
2073+ In this class we're testing with str, so convert s to a str
2074+ '''
2075+ if isinstance (s , bytes ):
2076+ return s .decode ()
2077+ return s
2078+
20712079 def test_basic (self ):
20722080 # Given an EXE in a directory, it should be returned.
20732081 rv = shutil .which (self .file , path = self .dir )
@@ -2255,9 +2263,9 @@ def test_empty_path_no_PATH(self):
22552263
22562264 @unittest .skipUnless (sys .platform == "win32" , 'test specific to Windows' )
22572265 def test_pathext (self ):
2258- ext = ".xyz"
2266+ ext = self . to_text_type ( ".xyz" )
22592267 temp_filexyz = tempfile .NamedTemporaryFile (dir = self .temp_dir ,
2260- prefix = "Tmp2" , suffix = ext )
2268+ prefix = self . to_text_type ( "Tmp2" ) , suffix = ext )
22612269 os .chmod (temp_filexyz .name , stat .S_IXUSR )
22622270 self .addCleanup (temp_filexyz .close )
22632271
@@ -2266,38 +2274,39 @@ def test_pathext(self):
22662274 program = os .path .splitext (program )[0 ]
22672275
22682276 with os_helper .EnvironmentVarGuard () as env :
2269- env ['PATHEXT' ] = ext
2277+ env ['PATHEXT' ] = ext if isinstance ( ext , str ) else ext . decode ()
22702278 rv = shutil .which (program , path = self .temp_dir )
22712279 self .assertEqual (rv , temp_filexyz .name )
22722280
22732281 # Issue 40592: See https://bugs.python.org/issue40592
22742282 @unittest .skipUnless (sys .platform == "win32" , 'test specific to Windows' )
22752283 def test_pathext_with_empty_str (self ):
2276- ext = ".xyz"
2284+ ext = self . to_text_type ( ".xyz" )
22772285 temp_filexyz = tempfile .NamedTemporaryFile (dir = self .temp_dir ,
2278- prefix = "Tmp2" , suffix = ext )
2286+ prefix = self . to_text_type ( "Tmp2" ) , suffix = ext )
22792287 self .addCleanup (temp_filexyz .close )
22802288
22812289 # strip path and extension
22822290 program = os .path .basename (temp_filexyz .name )
22832291 program = os .path .splitext (program )[0 ]
22842292
22852293 with os_helper .EnvironmentVarGuard () as env :
2286- env ['PATHEXT' ] = f"{ ext } ;" # note the ;
2294+ env ['PATHEXT' ] = f"{ ext if isinstance ( ext , str ) else ext . decode () } ;" # note the ;
22872295 rv = shutil .which (program , path = self .temp_dir )
22882296 self .assertEqual (rv , temp_filexyz .name )
22892297
22902298 # See GH-75586
22912299 @unittest .skipUnless (sys .platform == "win32" , 'test specific to Windows' )
22922300 def test_pathext_applied_on_files_in_path (self ):
22932301 with os_helper .EnvironmentVarGuard () as env :
2294- env ["PATH" ] = self .temp_dir
2302+ env ["PATH" ] = self .temp_dir if isinstance ( self . temp_dir , str ) else self . temp_dir . decode ()
22952303 env ["PATHEXT" ] = ".test"
22962304
2297- test_path = pathlib .Path (self .temp_dir ) / "test_program.test"
2298- test_path .touch (mode = 0o755 )
2305+ test_path = os .path .join (self .temp_dir , self .to_text_type ("test_program.test" ))
2306+ open (test_path , 'w' ).close ()
2307+ os .chmod (test_path , 0o755 )
22992308
2300- self .assertEqual (shutil .which ("test_program" ), str ( test_path ) )
2309+ self .assertEqual (shutil .which (self . to_text_type ( "test_program" )), test_path )
23012310
23022311 # See GH-75586
23032312 @unittest .skipUnless (sys .platform == "win32" , 'test specific to Windows' )
@@ -2313,16 +2322,69 @@ def test_win_path_needs_curdir(self):
23132322 self .assertFalse (shutil ._win_path_needs_curdir ('dontcare' , os .X_OK ))
23142323 need_curdir_mock .assert_called_once_with ('dontcare' )
23152324
2325+ # See GH-109590
2326+ @unittest .skipUnless (sys .platform == "win32" , 'test specific to Windows' )
2327+ def test_pathext_preferred_for_execute (self ):
2328+ with os_helper .EnvironmentVarGuard () as env :
2329+ env ["PATH" ] = self .temp_dir if isinstance (self .temp_dir , str ) else self .temp_dir .decode ()
2330+ env ["PATHEXT" ] = ".test"
2331+
2332+ exe = os .path .join (self .temp_dir , self .to_text_type ("test.exe" ))
2333+ open (exe , 'w' ).close ()
2334+ os .chmod (exe , 0o755 )
2335+
2336+ # default behavior allows a direct match if nothing in PATHEXT matches
2337+ self .assertEqual (shutil .which (self .to_text_type ("test.exe" )), exe )
2338+
2339+ dot_test = os .path .join (self .temp_dir , self .to_text_type ("test.exe.test" ))
2340+ open (dot_test , 'w' ).close ()
2341+ os .chmod (dot_test , 0o755 )
2342+
2343+ # now we have a PATHEXT match, so it take precedence
2344+ self .assertEqual (shutil .which (self .to_text_type ("test.exe" )), dot_test )
2345+
2346+ # but if we don't use os.X_OK we don't change the order based off PATHEXT
2347+ # and therefore get the direct match.
2348+ self .assertEqual (shutil .which (self .to_text_type ("test.exe" ), mode = os .F_OK ), exe )
2349+
2350+ # See GH-109590
2351+ @unittest .skipUnless (sys .platform == "win32" , 'test specific to Windows' )
2352+ def test_pathext_given_extension_preferred (self ):
2353+ with os_helper .EnvironmentVarGuard () as env :
2354+ env ["PATH" ] = self .temp_dir if isinstance (self .temp_dir , str ) else self .temp_dir .decode ()
2355+ env ["PATHEXT" ] = ".exe2;.exe"
2356+
2357+ exe = os .path .join (self .temp_dir , self .to_text_type ("test.exe" ))
2358+ open (exe , 'w' ).close ()
2359+ os .chmod (exe , 0o755 )
2360+
2361+ exe2 = os .path .join (self .temp_dir , self .to_text_type ("test.exe2" ))
2362+ open (exe2 , 'w' ).close ()
2363+ os .chmod (exe2 , 0o755 )
2364+
2365+ # even though .exe2 is preferred in PATHEXT, we matched directly to test.exe
2366+ self .assertEqual (shutil .which (self .to_text_type ("test.exe" )), exe )
2367+ self .assertEqual (shutil .which (self .to_text_type ("test" )), exe2 )
2368+
23162369
23172370class TestWhichBytes (TestWhich ):
23182371 def setUp (self ):
23192372 TestWhich .setUp (self )
23202373 self .dir = os .fsencode (self .dir )
23212374 self .file = os .fsencode (self .file )
23222375 self .temp_file .name = os .fsencode (self .temp_file .name )
2376+ self .temp_dir = os .fsencode (self .temp_dir )
23232377 self .curdir = os .fsencode (self .curdir )
23242378 self .ext = os .fsencode (self .ext )
23252379
2380+ def to_text_type (self , s ):
2381+ '''
2382+ In this class we're testing with bytes, so convert s to a bytes
2383+ '''
2384+ if isinstance (s , str ):
2385+ return s .encode ()
2386+ return s
2387+
23262388
23272389class TestMove (BaseTest , unittest .TestCase ):
23282390
0 commit comments