1import unittest 2import sys 3import os 4import subprocess 5import shutil 6from copy import copy 7 8from test.support import ( 9 captured_stdout, PythonSymlink, requires_subprocess, is_wasi 10) 11from test.support.import_helper import import_module 12from test.support.os_helper import (TESTFN, unlink, skip_unless_symlink, 13 change_cwd) 14from test.support.warnings_helper import check_warnings 15 16import sysconfig 17from sysconfig import (get_paths, get_platform, get_config_vars, 18 get_path, get_path_names, _INSTALL_SCHEMES, 19 get_default_scheme, get_scheme_names, get_config_var, 20 _expand_vars, _get_preferred_schemes, _main) 21import _osx_support 22 23 24HAS_USER_BASE = sysconfig._HAS_USER_BASE 25 26 27class TestSysConfig(unittest.TestCase): 28 29 def setUp(self): 30 super(TestSysConfig, self).setUp() 31 self.sys_path = sys.path[:] 32 # patching os.uname 33 if hasattr(os, 'uname'): 34 self.uname = os.uname 35 self._uname = os.uname() 36 else: 37 self.uname = None 38 self._set_uname(('',)*5) 39 os.uname = self._get_uname 40 # saving the environment 41 self.name = os.name 42 self.platform = sys.platform 43 self.version = sys.version 44 self.sep = os.sep 45 self.join = os.path.join 46 self.isabs = os.path.isabs 47 self.splitdrive = os.path.splitdrive 48 self._config_vars = sysconfig._CONFIG_VARS, copy(sysconfig._CONFIG_VARS) 49 self._added_envvars = [] 50 self._changed_envvars = [] 51 for var in ('MACOSX_DEPLOYMENT_TARGET', 'PATH'): 52 if var in os.environ: 53 self._changed_envvars.append((var, os.environ[var])) 54 else: 55 self._added_envvars.append(var) 56 57 def tearDown(self): 58 sys.path[:] = self.sys_path 59 self._cleanup_testfn() 60 if self.uname is not None: 61 os.uname = self.uname 62 else: 63 del os.uname 64 os.name = self.name 65 sys.platform = self.platform 66 sys.version = self.version 67 os.sep = self.sep 68 os.path.join = self.join 69 os.path.isabs = self.isabs 70 os.path.splitdrive = self.splitdrive 71 sysconfig._CONFIG_VARS = self._config_vars[0] 72 sysconfig._CONFIG_VARS.clear() 73 sysconfig._CONFIG_VARS.update(self._config_vars[1]) 74 for var, value in self._changed_envvars: 75 os.environ[var] = value 76 for var in self._added_envvars: 77 os.environ.pop(var, None) 78 79 super(TestSysConfig, self).tearDown() 80 81 def _set_uname(self, uname): 82 self._uname = os.uname_result(uname) 83 84 def _get_uname(self): 85 return self._uname 86 87 def _cleanup_testfn(self): 88 path = TESTFN 89 if os.path.isfile(path): 90 os.remove(path) 91 elif os.path.isdir(path): 92 shutil.rmtree(path) 93 94 def test_get_path_names(self): 95 self.assertEqual(get_path_names(), sysconfig._SCHEME_KEYS) 96 97 def test_get_paths(self): 98 scheme = get_paths() 99 default_scheme = get_default_scheme() 100 wanted = _expand_vars(default_scheme, None) 101 wanted = sorted(wanted.items()) 102 scheme = sorted(scheme.items()) 103 self.assertEqual(scheme, wanted) 104 105 def test_get_path(self): 106 config_vars = get_config_vars() 107 if os.name == 'nt': 108 # On Windows, we replace the native platlibdir name with the 109 # default so that POSIX schemes resolve correctly 110 config_vars = config_vars | {'platlibdir': 'lib'} 111 for scheme in _INSTALL_SCHEMES: 112 for name in _INSTALL_SCHEMES[scheme]: 113 expected = _INSTALL_SCHEMES[scheme][name].format(**config_vars) 114 self.assertEqual( 115 os.path.normpath(get_path(name, scheme)), 116 os.path.normpath(expected), 117 ) 118 119 def test_get_default_scheme(self): 120 self.assertIn(get_default_scheme(), _INSTALL_SCHEMES) 121 122 def test_get_preferred_schemes(self): 123 expected_schemes = {'prefix', 'home', 'user'} 124 125 # Windows. 126 os.name = 'nt' 127 schemes = _get_preferred_schemes() 128 self.assertIsInstance(schemes, dict) 129 self.assertEqual(set(schemes), expected_schemes) 130 131 # Mac and Linux, shared library build. 132 os.name = 'posix' 133 schemes = _get_preferred_schemes() 134 self.assertIsInstance(schemes, dict) 135 self.assertEqual(set(schemes), expected_schemes) 136 137 # Mac, framework build. 138 os.name = 'posix' 139 sys.platform = 'darwin' 140 sys._framework = True 141 self.assertIsInstance(schemes, dict) 142 self.assertEqual(set(schemes), expected_schemes) 143 144 def test_posix_venv_scheme(self): 145 # The following directories were hardcoded in the venv module 146 # before bpo-45413, here we assert the posix_venv scheme does not regress 147 binpath = 'bin' 148 incpath = 'include' 149 libpath = os.path.join('lib', 150 'python%d.%d' % sys.version_info[:2], 151 'site-packages') 152 153 # Resolve the paths in prefix 154 binpath = os.path.join(sys.prefix, binpath) 155 incpath = os.path.join(sys.prefix, incpath) 156 libpath = os.path.join(sys.prefix, libpath) 157 158 self.assertEqual(binpath, sysconfig.get_path('scripts', scheme='posix_venv')) 159 self.assertEqual(libpath, sysconfig.get_path('purelib', scheme='posix_venv')) 160 161 # The include directory on POSIX isn't exactly the same as before, 162 # but it is "within" 163 sysconfig_includedir = sysconfig.get_path('include', scheme='posix_venv') 164 self.assertTrue(sysconfig_includedir.startswith(incpath + os.sep)) 165 166 def test_nt_venv_scheme(self): 167 # The following directories were hardcoded in the venv module 168 # before bpo-45413, here we assert the posix_venv scheme does not regress 169 binpath = 'Scripts' 170 incpath = 'Include' 171 libpath = os.path.join('Lib', 'site-packages') 172 173 # Resolve the paths in prefix 174 binpath = os.path.join(sys.prefix, binpath) 175 incpath = os.path.join(sys.prefix, incpath) 176 libpath = os.path.join(sys.prefix, libpath) 177 178 self.assertEqual(binpath, sysconfig.get_path('scripts', scheme='nt_venv')) 179 self.assertEqual(incpath, sysconfig.get_path('include', scheme='nt_venv')) 180 self.assertEqual(libpath, sysconfig.get_path('purelib', scheme='nt_venv')) 181 182 def test_venv_scheme(self): 183 if sys.platform == 'win32': 184 self.assertEqual( 185 sysconfig.get_path('scripts', scheme='venv'), 186 sysconfig.get_path('scripts', scheme='nt_venv') 187 ) 188 self.assertEqual( 189 sysconfig.get_path('include', scheme='venv'), 190 sysconfig.get_path('include', scheme='nt_venv') 191 ) 192 self.assertEqual( 193 sysconfig.get_path('purelib', scheme='venv'), 194 sysconfig.get_path('purelib', scheme='nt_venv') 195 ) 196 else: 197 self.assertEqual( 198 sysconfig.get_path('scripts', scheme='venv'), 199 sysconfig.get_path('scripts', scheme='posix_venv') 200 ) 201 self.assertEqual( 202 sysconfig.get_path('include', scheme='venv'), 203 sysconfig.get_path('include', scheme='posix_venv') 204 ) 205 self.assertEqual( 206 sysconfig.get_path('purelib', scheme='venv'), 207 sysconfig.get_path('purelib', scheme='posix_venv') 208 ) 209 210 def test_get_config_vars(self): 211 cvars = get_config_vars() 212 self.assertIsInstance(cvars, dict) 213 self.assertTrue(cvars) 214 215 def test_get_platform(self): 216 # windows XP, 32bits 217 os.name = 'nt' 218 sys.version = ('2.4.4 (#71, Oct 18 2006, 08:34:43) ' 219 '[MSC v.1310 32 bit (Intel)]') 220 sys.platform = 'win32' 221 self.assertEqual(get_platform(), 'win32') 222 223 # windows XP, amd64 224 os.name = 'nt' 225 sys.version = ('2.4.4 (#71, Oct 18 2006, 08:34:43) ' 226 '[MSC v.1310 32 bit (Amd64)]') 227 sys.platform = 'win32' 228 self.assertEqual(get_platform(), 'win-amd64') 229 230 # macbook 231 os.name = 'posix' 232 sys.version = ('2.5 (r25:51918, Sep 19 2006, 08:49:13) ' 233 '\n[GCC 4.0.1 (Apple Computer, Inc. build 5341)]') 234 sys.platform = 'darwin' 235 self._set_uname(('Darwin', 'macziade', '8.11.1', 236 ('Darwin Kernel Version 8.11.1: ' 237 'Wed Oct 10 18:23:28 PDT 2007; ' 238 'root:xnu-792.25.20~1/RELEASE_I386'), 'PowerPC')) 239 _osx_support._remove_original_values(get_config_vars()) 240 get_config_vars()['MACOSX_DEPLOYMENT_TARGET'] = '10.3' 241 242 get_config_vars()['CFLAGS'] = ('-fno-strict-aliasing -DNDEBUG -g ' 243 '-fwrapv -O3 -Wall -Wstrict-prototypes') 244 245 maxint = sys.maxsize 246 try: 247 sys.maxsize = 2147483647 248 self.assertEqual(get_platform(), 'macosx-10.3-ppc') 249 sys.maxsize = 9223372036854775807 250 self.assertEqual(get_platform(), 'macosx-10.3-ppc64') 251 finally: 252 sys.maxsize = maxint 253 254 self._set_uname(('Darwin', 'macziade', '8.11.1', 255 ('Darwin Kernel Version 8.11.1: ' 256 'Wed Oct 10 18:23:28 PDT 2007; ' 257 'root:xnu-792.25.20~1/RELEASE_I386'), 'i386')) 258 _osx_support._remove_original_values(get_config_vars()) 259 get_config_vars()['MACOSX_DEPLOYMENT_TARGET'] = '10.3' 260 261 get_config_vars()['CFLAGS'] = ('-fno-strict-aliasing -DNDEBUG -g ' 262 '-fwrapv -O3 -Wall -Wstrict-prototypes') 263 maxint = sys.maxsize 264 try: 265 sys.maxsize = 2147483647 266 self.assertEqual(get_platform(), 'macosx-10.3-i386') 267 sys.maxsize = 9223372036854775807 268 self.assertEqual(get_platform(), 'macosx-10.3-x86_64') 269 finally: 270 sys.maxsize = maxint 271 272 # macbook with fat binaries (fat, universal or fat64) 273 _osx_support._remove_original_values(get_config_vars()) 274 get_config_vars()['MACOSX_DEPLOYMENT_TARGET'] = '10.4' 275 get_config_vars()['CFLAGS'] = ('-arch ppc -arch i386 -isysroot ' 276 '/Developer/SDKs/MacOSX10.4u.sdk ' 277 '-fno-strict-aliasing -fno-common ' 278 '-dynamic -DNDEBUG -g -O3') 279 280 self.assertEqual(get_platform(), 'macosx-10.4-fat') 281 282 _osx_support._remove_original_values(get_config_vars()) 283 get_config_vars()['CFLAGS'] = ('-arch x86_64 -arch i386 -isysroot ' 284 '/Developer/SDKs/MacOSX10.4u.sdk ' 285 '-fno-strict-aliasing -fno-common ' 286 '-dynamic -DNDEBUG -g -O3') 287 288 self.assertEqual(get_platform(), 'macosx-10.4-intel') 289 290 _osx_support._remove_original_values(get_config_vars()) 291 get_config_vars()['CFLAGS'] = ('-arch x86_64 -arch ppc -arch i386 -isysroot ' 292 '/Developer/SDKs/MacOSX10.4u.sdk ' 293 '-fno-strict-aliasing -fno-common ' 294 '-dynamic -DNDEBUG -g -O3') 295 self.assertEqual(get_platform(), 'macosx-10.4-fat3') 296 297 _osx_support._remove_original_values(get_config_vars()) 298 get_config_vars()['CFLAGS'] = ('-arch ppc64 -arch x86_64 -arch ppc -arch i386 -isysroot ' 299 '/Developer/SDKs/MacOSX10.4u.sdk ' 300 '-fno-strict-aliasing -fno-common ' 301 '-dynamic -DNDEBUG -g -O3') 302 self.assertEqual(get_platform(), 'macosx-10.4-universal') 303 304 _osx_support._remove_original_values(get_config_vars()) 305 get_config_vars()['CFLAGS'] = ('-arch x86_64 -arch ppc64 -isysroot ' 306 '/Developer/SDKs/MacOSX10.4u.sdk ' 307 '-fno-strict-aliasing -fno-common ' 308 '-dynamic -DNDEBUG -g -O3') 309 310 self.assertEqual(get_platform(), 'macosx-10.4-fat64') 311 312 for arch in ('ppc', 'i386', 'x86_64', 'ppc64'): 313 _osx_support._remove_original_values(get_config_vars()) 314 get_config_vars()['CFLAGS'] = ('-arch %s -isysroot ' 315 '/Developer/SDKs/MacOSX10.4u.sdk ' 316 '-fno-strict-aliasing -fno-common ' 317 '-dynamic -DNDEBUG -g -O3' % arch) 318 319 self.assertEqual(get_platform(), 'macosx-10.4-%s' % arch) 320 321 # linux debian sarge 322 os.name = 'posix' 323 sys.version = ('2.3.5 (#1, Jul 4 2007, 17:28:59) ' 324 '\n[GCC 4.1.2 20061115 (prerelease) (Debian 4.1.1-21)]') 325 sys.platform = 'linux2' 326 self._set_uname(('Linux', 'aglae', '2.6.21.1dedibox-r7', 327 '#1 Mon Apr 30 17:25:38 CEST 2007', 'i686')) 328 329 self.assertEqual(get_platform(), 'linux-i686') 330 331 # XXX more platforms to tests here 332 333 @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds") 334 def test_get_config_h_filename(self): 335 config_h = sysconfig.get_config_h_filename() 336 self.assertTrue(os.path.isfile(config_h), config_h) 337 338 def test_get_scheme_names(self): 339 wanted = ['nt', 'posix_home', 'posix_prefix', 'posix_venv', 'nt_venv', 'venv'] 340 if HAS_USER_BASE: 341 wanted.extend(['nt_user', 'osx_framework_user', 'posix_user']) 342 self.assertEqual(get_scheme_names(), tuple(sorted(wanted))) 343 344 @skip_unless_symlink 345 @requires_subprocess() 346 def test_symlink(self): # Issue 7880 347 with PythonSymlink() as py: 348 cmd = "-c", "import sysconfig; print(sysconfig.get_platform())" 349 self.assertEqual(py.call_real(*cmd), py.call_link(*cmd)) 350 351 def test_user_similar(self): 352 # Issue #8759: make sure the posix scheme for the users 353 # is similar to the global posix_prefix one 354 base = get_config_var('base') 355 if HAS_USER_BASE: 356 user = get_config_var('userbase') 357 # the global scheme mirrors the distinction between prefix and 358 # exec-prefix but not the user scheme, so we have to adapt the paths 359 # before comparing (issue #9100) 360 adapt = sys.base_prefix != sys.base_exec_prefix 361 for name in ('stdlib', 'platstdlib', 'purelib', 'platlib'): 362 global_path = get_path(name, 'posix_prefix') 363 if adapt: 364 global_path = global_path.replace(sys.exec_prefix, sys.base_prefix) 365 base = base.replace(sys.exec_prefix, sys.base_prefix) 366 elif sys.base_prefix != sys.prefix: 367 # virtual environment? Likewise, we have to adapt the paths 368 # before comparing 369 global_path = global_path.replace(sys.base_prefix, sys.prefix) 370 base = base.replace(sys.base_prefix, sys.prefix) 371 if HAS_USER_BASE: 372 user_path = get_path(name, 'posix_user') 373 expected = os.path.normpath(global_path.replace(base, user, 1)) 374 # bpo-44860: platlib of posix_user doesn't use sys.platlibdir, 375 # whereas posix_prefix does. 376 if name == 'platlib': 377 # Replace "/lib64/python3.11/site-packages" suffix 378 # with "/lib/python3.11/site-packages". 379 py_version_short = sysconfig.get_python_version() 380 suffix = f'python{py_version_short}/site-packages' 381 expected = expected.replace(f'/{sys.platlibdir}/{suffix}', 382 f'/lib/{suffix}') 383 self.assertEqual(user_path, expected) 384 385 def test_main(self): 386 # just making sure _main() runs and returns things in the stdout 387 with captured_stdout() as output: 388 _main() 389 self.assertTrue(len(output.getvalue().split('\n')) > 0) 390 391 @unittest.skipIf(sys.platform == "win32", "Does not apply to Windows") 392 def test_ldshared_value(self): 393 ldflags = sysconfig.get_config_var('LDFLAGS') 394 ldshared = sysconfig.get_config_var('LDSHARED') 395 396 self.assertIn(ldflags, ldshared) 397 398 @unittest.skipUnless(sys.platform == "darwin", "test only relevant on MacOSX") 399 @requires_subprocess() 400 def test_platform_in_subprocess(self): 401 my_platform = sysconfig.get_platform() 402 403 # Test without MACOSX_DEPLOYMENT_TARGET in the environment 404 405 env = os.environ.copy() 406 if 'MACOSX_DEPLOYMENT_TARGET' in env: 407 del env['MACOSX_DEPLOYMENT_TARGET'] 408 409 p = subprocess.Popen([ 410 sys.executable, '-c', 411 'import sysconfig; print(sysconfig.get_platform())', 412 ], 413 stdout=subprocess.PIPE, 414 stderr=subprocess.DEVNULL, 415 env=env) 416 test_platform = p.communicate()[0].strip() 417 test_platform = test_platform.decode('utf-8') 418 status = p.wait() 419 420 self.assertEqual(status, 0) 421 self.assertEqual(my_platform, test_platform) 422 423 # Test with MACOSX_DEPLOYMENT_TARGET in the environment, and 424 # using a value that is unlikely to be the default one. 425 env = os.environ.copy() 426 env['MACOSX_DEPLOYMENT_TARGET'] = '10.1' 427 428 p = subprocess.Popen([ 429 sys.executable, '-c', 430 'import sysconfig; print(sysconfig.get_platform())', 431 ], 432 stdout=subprocess.PIPE, 433 stderr=subprocess.DEVNULL, 434 env=env) 435 test_platform = p.communicate()[0].strip() 436 test_platform = test_platform.decode('utf-8') 437 status = p.wait() 438 439 self.assertEqual(status, 0) 440 self.assertEqual(my_platform, test_platform) 441 442 @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds") 443 def test_srcdir(self): 444 # See Issues #15322, #15364. 445 srcdir = sysconfig.get_config_var('srcdir') 446 447 self.assertTrue(os.path.isabs(srcdir), srcdir) 448 self.assertTrue(os.path.isdir(srcdir), srcdir) 449 450 if sysconfig._PYTHON_BUILD: 451 # The python executable has not been installed so srcdir 452 # should be a full source checkout. 453 Python_h = os.path.join(srcdir, 'Include', 'Python.h') 454 self.assertTrue(os.path.exists(Python_h), Python_h) 455 # <srcdir>/PC/pyconfig.h always exists even if unused on POSIX. 456 pyconfig_h = os.path.join(srcdir, 'PC', 'pyconfig.h') 457 self.assertTrue(os.path.exists(pyconfig_h), pyconfig_h) 458 pyconfig_h_in = os.path.join(srcdir, 'pyconfig.h.in') 459 self.assertTrue(os.path.exists(pyconfig_h_in), pyconfig_h_in) 460 elif os.name == 'posix': 461 makefile_dir = os.path.dirname(sysconfig.get_makefile_filename()) 462 # Issue #19340: srcdir has been realpath'ed already 463 makefile_dir = os.path.realpath(makefile_dir) 464 self.assertEqual(makefile_dir, srcdir) 465 466 def test_srcdir_independent_of_cwd(self): 467 # srcdir should be independent of the current working directory 468 # See Issues #15322, #15364. 469 srcdir = sysconfig.get_config_var('srcdir') 470 with change_cwd(os.pardir): 471 srcdir2 = sysconfig.get_config_var('srcdir') 472 self.assertEqual(srcdir, srcdir2) 473 474 @unittest.skipIf(sysconfig.get_config_var('EXT_SUFFIX') is None, 475 'EXT_SUFFIX required for this test') 476 def test_EXT_SUFFIX_in_vars(self): 477 import _imp 478 if not _imp.extension_suffixes(): 479 self.skipTest("stub loader has no suffixes") 480 vars = sysconfig.get_config_vars() 481 self.assertEqual(vars['EXT_SUFFIX'], _imp.extension_suffixes()[0]) 482 483 @unittest.skipUnless(sys.platform == 'linux' and 484 hasattr(sys.implementation, '_multiarch'), 485 'multiarch-specific test') 486 def test_triplet_in_ext_suffix(self): 487 ctypes = import_module('ctypes') 488 import platform, re 489 machine = platform.machine() 490 suffix = sysconfig.get_config_var('EXT_SUFFIX') 491 if re.match('(aarch64|arm|mips|ppc|powerpc|s390|sparc)', machine): 492 self.assertTrue('linux' in suffix, suffix) 493 if re.match('(i[3-6]86|x86_64)$', machine): 494 if ctypes.sizeof(ctypes.c_char_p()) == 4: 495 expected_suffixes = 'i386-linux-gnu.so', 'x86_64-linux-gnux32.so', 'i386-linux-musl.so' 496 else: # 8 byte pointer size 497 expected_suffixes = 'x86_64-linux-gnu.so', 'x86_64-linux-musl.so' 498 self.assertTrue(suffix.endswith(expected_suffixes), 499 f'unexpected suffix {suffix!r}') 500 501 @unittest.skipUnless(sys.platform == 'darwin', 'OS X-specific test') 502 def test_osx_ext_suffix(self): 503 suffix = sysconfig.get_config_var('EXT_SUFFIX') 504 self.assertTrue(suffix.endswith('-darwin.so'), suffix) 505 506class MakefileTests(unittest.TestCase): 507 508 @unittest.skipIf(sys.platform.startswith('win'), 509 'Test is not Windows compatible') 510 @unittest.skipIf(is_wasi, "Incompatible with WASI mapdir and OOT builds") 511 def test_get_makefile_filename(self): 512 makefile = sysconfig.get_makefile_filename() 513 self.assertTrue(os.path.isfile(makefile), makefile) 514 515 def test_parse_makefile(self): 516 self.addCleanup(unlink, TESTFN) 517 with open(TESTFN, "w") as makefile: 518 print("var1=a$(VAR2)", file=makefile) 519 print("VAR2=b$(var3)", file=makefile) 520 print("var3=42", file=makefile) 521 print("var4=$/invalid", file=makefile) 522 print("var5=dollar$$5", file=makefile) 523 print("var6=${var3}/lib/python3.5/config-$(VAR2)$(var5)" 524 "-x86_64-linux-gnu", file=makefile) 525 vars = sysconfig._parse_makefile(TESTFN) 526 self.assertEqual(vars, { 527 'var1': 'ab42', 528 'VAR2': 'b42', 529 'var3': 42, 530 'var4': '$/invalid', 531 'var5': 'dollar$5', 532 'var6': '42/lib/python3.5/config-b42dollar$5-x86_64-linux-gnu', 533 }) 534 535 536if __name__ == "__main__": 537 unittest.main() 538