1import os 2import copy 3import pickle 4import platform 5import subprocess 6import sys 7import unittest 8from unittest import mock 9 10from test import support 11from test.support import os_helper 12 13FEDORA_OS_RELEASE = """\ 14NAME=Fedora 15VERSION="32 (Thirty Two)" 16ID=fedora 17VERSION_ID=32 18VERSION_CODENAME="" 19PLATFORM_ID="platform:f32" 20PRETTY_NAME="Fedora 32 (Thirty Two)" 21ANSI_COLOR="0;34" 22LOGO=fedora-logo-icon 23CPE_NAME="cpe:/o:fedoraproject:fedora:32" 24HOME_URL="https://fedoraproject.org/" 25DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f32/system-administrators-guide/" 26SUPPORT_URL="https://fedoraproject.org/wiki/Communicating_and_getting_help" 27BUG_REPORT_URL="https://bugzilla.redhat.com/" 28REDHAT_BUGZILLA_PRODUCT="Fedora" 29REDHAT_BUGZILLA_PRODUCT_VERSION=32 30REDHAT_SUPPORT_PRODUCT="Fedora" 31REDHAT_SUPPORT_PRODUCT_VERSION=32 32PRIVACY_POLICY_URL="https://fedoraproject.org/wiki/Legal:PrivacyPolicy" 33""" 34 35UBUNTU_OS_RELEASE = """\ 36NAME="Ubuntu" 37VERSION="20.04.1 LTS (Focal Fossa)" 38ID=ubuntu 39ID_LIKE=debian 40PRETTY_NAME="Ubuntu 20.04.1 LTS" 41VERSION_ID="20.04" 42HOME_URL="https://www.ubuntu.com/" 43SUPPORT_URL="https://help.ubuntu.com/" 44BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" 45PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" 46VERSION_CODENAME=focal 47UBUNTU_CODENAME=focal 48""" 49 50TEST_OS_RELEASE = r""" 51# test data 52ID_LIKE="egg spam viking" 53EMPTY= 54# comments and empty lines are ignored 55 56SINGLE_QUOTE='single' 57EMPTY_SINGLE='' 58DOUBLE_QUOTE="double" 59EMPTY_DOUBLE="" 60QUOTES="double\'s" 61SPECIALS="\$\`\\\'\"" 62# invalid lines 63=invalid 64= 65INVALID 66IN-VALID=value 67IN VALID=value 68""" 69 70 71class PlatformTest(unittest.TestCase): 72 def clear_caches(self): 73 platform._platform_cache.clear() 74 platform._sys_version_cache.clear() 75 platform._uname_cache = None 76 platform._os_release_cache = None 77 78 def test_architecture(self): 79 res = platform.architecture() 80 81 @os_helper.skip_unless_symlink 82 @support.requires_subprocess() 83 def test_architecture_via_symlink(self): # issue3762 84 with support.PythonSymlink() as py: 85 cmd = "-c", "import platform; print(platform.architecture())" 86 self.assertEqual(py.call_real(*cmd), py.call_link(*cmd)) 87 88 def test_platform(self): 89 for aliased in (False, True): 90 for terse in (False, True): 91 res = platform.platform(aliased, terse) 92 93 def test_system(self): 94 res = platform.system() 95 96 def test_node(self): 97 res = platform.node() 98 99 def test_release(self): 100 res = platform.release() 101 102 def test_version(self): 103 res = platform.version() 104 105 def test_machine(self): 106 res = platform.machine() 107 108 def test_processor(self): 109 res = platform.processor() 110 111 def setUp(self): 112 self.save_version = sys.version 113 self.save_git = sys._git 114 self.save_platform = sys.platform 115 116 def tearDown(self): 117 sys.version = self.save_version 118 sys._git = self.save_git 119 sys.platform = self.save_platform 120 121 def test_sys_version(self): 122 # Old test. 123 for input, output in ( 124 ('2.4.3 (#1, Jun 21 2006, 13:54:21) \n[GCC 3.3.4 (pre 3.3.5 20040809)]', 125 ('CPython', '2.4.3', '', '', '1', 'Jun 21 2006 13:54:21', 'GCC 3.3.4 (pre 3.3.5 20040809)')), 126 ('IronPython 1.0.60816 on .NET 2.0.50727.42', 127 ('IronPython', '1.0.60816', '', '', '', '', '.NET 2.0.50727.42')), 128 ('IronPython 1.0 (1.0.61005.1977) on .NET 2.0.50727.42', 129 ('IronPython', '1.0.0', '', '', '', '', '.NET 2.0.50727.42')), 130 ('2.4.3 (truncation, date, t) \n[GCC]', 131 ('CPython', '2.4.3', '', '', 'truncation', 'date t', 'GCC')), 132 ('2.4.3 (truncation, date, ) \n[GCC]', 133 ('CPython', '2.4.3', '', '', 'truncation', 'date', 'GCC')), 134 ('2.4.3 (truncation, date,) \n[GCC]', 135 ('CPython', '2.4.3', '', '', 'truncation', 'date', 'GCC')), 136 ('2.4.3 (truncation, date) \n[GCC]', 137 ('CPython', '2.4.3', '', '', 'truncation', 'date', 'GCC')), 138 ('2.4.3 (truncation, d) \n[GCC]', 139 ('CPython', '2.4.3', '', '', 'truncation', 'd', 'GCC')), 140 ('2.4.3 (truncation, ) \n[GCC]', 141 ('CPython', '2.4.3', '', '', 'truncation', '', 'GCC')), 142 ('2.4.3 (truncation,) \n[GCC]', 143 ('CPython', '2.4.3', '', '', 'truncation', '', 'GCC')), 144 ('2.4.3 (truncation) \n[GCC]', 145 ('CPython', '2.4.3', '', '', 'truncation', '', 'GCC')), 146 ): 147 # branch and revision are not "parsed", but fetched 148 # from sys._git. Ignore them 149 (name, version, branch, revision, buildno, builddate, compiler) \ 150 = platform._sys_version(input) 151 self.assertEqual( 152 (name, version, '', '', buildno, builddate, compiler), output) 153 154 # Tests for python_implementation(), python_version(), python_branch(), 155 # python_revision(), python_build(), and python_compiler(). 156 sys_versions = { 157 ("2.6.1 (r261:67515, Dec 6 2008, 15:26:00) \n[GCC 4.0.1 (Apple Computer, Inc. build 5370)]", 158 ('CPython', 'tags/r261', '67515'), self.save_platform) 159 : 160 ("CPython", "2.6.1", "tags/r261", "67515", 161 ('r261:67515', 'Dec 6 2008 15:26:00'), 162 'GCC 4.0.1 (Apple Computer, Inc. build 5370)'), 163 164 ("IronPython 2.0 (2.0.0.0) on .NET 2.0.50727.3053", None, "cli") 165 : 166 ("IronPython", "2.0.0", "", "", ("", ""), 167 ".NET 2.0.50727.3053"), 168 169 ("2.6.1 (IronPython 2.6.1 (2.6.10920.0) on .NET 2.0.50727.1433)", None, "cli") 170 : 171 ("IronPython", "2.6.1", "", "", ("", ""), 172 ".NET 2.0.50727.1433"), 173 174 ("2.7.4 (IronPython 2.7.4 (2.7.0.40) on Mono 4.0.30319.1 (32-bit))", None, "cli") 175 : 176 ("IronPython", "2.7.4", "", "", ("", ""), 177 "Mono 4.0.30319.1 (32-bit)"), 178 179 ("2.5 (trunk:6107, Mar 26 2009, 13:02:18) \n[Java HotSpot(TM) Client VM (\"Apple Computer, Inc.\")]", 180 ('Jython', 'trunk', '6107'), "java1.5.0_16") 181 : 182 ("Jython", "2.5.0", "trunk", "6107", 183 ('trunk:6107', 'Mar 26 2009'), "java1.5.0_16"), 184 185 ("2.5.2 (63378, Mar 26 2009, 18:03:29)\n[PyPy 1.0.0]", 186 ('PyPy', 'trunk', '63378'), self.save_platform) 187 : 188 ("PyPy", "2.5.2", "trunk", "63378", ('63378', 'Mar 26 2009'), 189 "") 190 } 191 for (version_tag, scm, sys_platform), info in \ 192 sys_versions.items(): 193 sys.version = version_tag 194 if scm is None: 195 if hasattr(sys, "_git"): 196 del sys._git 197 else: 198 sys._git = scm 199 if sys_platform is not None: 200 sys.platform = sys_platform 201 self.assertEqual(platform.python_implementation(), info[0]) 202 self.assertEqual(platform.python_version(), info[1]) 203 self.assertEqual(platform.python_branch(), info[2]) 204 self.assertEqual(platform.python_revision(), info[3]) 205 self.assertEqual(platform.python_build(), info[4]) 206 self.assertEqual(platform.python_compiler(), info[5]) 207 208 def test_system_alias(self): 209 res = platform.system_alias( 210 platform.system(), 211 platform.release(), 212 platform.version(), 213 ) 214 215 def test_uname(self): 216 res = platform.uname() 217 self.assertTrue(any(res)) 218 self.assertEqual(res[0], res.system) 219 self.assertEqual(res[-6], res.system) 220 self.assertEqual(res[1], res.node) 221 self.assertEqual(res[-5], res.node) 222 self.assertEqual(res[2], res.release) 223 self.assertEqual(res[-4], res.release) 224 self.assertEqual(res[3], res.version) 225 self.assertEqual(res[-3], res.version) 226 self.assertEqual(res[4], res.machine) 227 self.assertEqual(res[-2], res.machine) 228 self.assertEqual(res[5], res.processor) 229 self.assertEqual(res[-1], res.processor) 230 self.assertEqual(len(res), 6) 231 232 def test_uname_cast_to_tuple(self): 233 res = platform.uname() 234 expected = ( 235 res.system, res.node, res.release, res.version, res.machine, 236 res.processor, 237 ) 238 self.assertEqual(tuple(res), expected) 239 240 def test_uname_replace(self): 241 res = platform.uname() 242 new = res._replace( 243 system='system', node='node', release='release', 244 version='version', machine='machine') 245 self.assertEqual(new.system, 'system') 246 self.assertEqual(new.node, 'node') 247 self.assertEqual(new.release, 'release') 248 self.assertEqual(new.version, 'version') 249 self.assertEqual(new.machine, 'machine') 250 # processor cannot be replaced 251 self.assertEqual(new.processor, res.processor) 252 253 def test_uname_copy(self): 254 uname = platform.uname() 255 self.assertEqual(copy.copy(uname), uname) 256 self.assertEqual(copy.deepcopy(uname), uname) 257 258 def test_uname_pickle(self): 259 orig = platform.uname() 260 for proto in range(pickle.HIGHEST_PROTOCOL + 1): 261 with self.subTest(protocol=proto): 262 pickled = pickle.dumps(orig, proto) 263 restored = pickle.loads(pickled) 264 self.assertEqual(restored, orig) 265 266 def test_uname_slices(self): 267 res = platform.uname() 268 expected = tuple(res) 269 self.assertEqual(res[:], expected) 270 self.assertEqual(res[:5], expected[:5]) 271 272 def test_uname_fields(self): 273 self.assertIn('processor', platform.uname()._fields) 274 275 def test_uname_asdict(self): 276 res = platform.uname()._asdict() 277 self.assertEqual(len(res), 6) 278 self.assertIn('processor', res) 279 280 @unittest.skipIf(sys.platform in ['win32', 'OpenVMS'], "uname -p not used") 281 @support.requires_subprocess() 282 def test_uname_processor(self): 283 """ 284 On some systems, the processor must match the output 285 of 'uname -p'. See Issue 35967 for rationale. 286 """ 287 try: 288 proc_res = subprocess.check_output(['uname', '-p'], text=True).strip() 289 expect = platform._unknown_as_blank(proc_res) 290 except (OSError, subprocess.CalledProcessError): 291 expect = '' 292 self.assertEqual(platform.uname().processor, expect) 293 294 @unittest.skipUnless(sys.platform.startswith('win'), "windows only test") 295 def test_uname_win32_ARCHITEW6432(self): 296 # Issue 7860: make sure we get architecture from the correct variable 297 # on 64 bit Windows: if PROCESSOR_ARCHITEW6432 exists we should be 298 # using it, per 299 # http://blogs.msdn.com/david.wang/archive/2006/03/26/HOWTO-Detect-Process-Bitness.aspx 300 try: 301 with os_helper.EnvironmentVarGuard() as environ: 302 if 'PROCESSOR_ARCHITEW6432' in environ: 303 del environ['PROCESSOR_ARCHITEW6432'] 304 environ['PROCESSOR_ARCHITECTURE'] = 'foo' 305 platform._uname_cache = None 306 system, node, release, version, machine, processor = platform.uname() 307 self.assertEqual(machine, 'foo') 308 environ['PROCESSOR_ARCHITEW6432'] = 'bar' 309 platform._uname_cache = None 310 system, node, release, version, machine, processor = platform.uname() 311 self.assertEqual(machine, 'bar') 312 finally: 313 platform._uname_cache = None 314 315 def test_java_ver(self): 316 res = platform.java_ver() 317 if sys.platform == 'java': 318 self.assertTrue(all(res)) 319 320 def test_win32_ver(self): 321 res = platform.win32_ver() 322 323 def test_mac_ver(self): 324 res = platform.mac_ver() 325 326 if platform.uname().system == 'Darwin': 327 # We are on a macOS system, check that the right version 328 # information is returned 329 output = subprocess.check_output(['sw_vers'], text=True) 330 for line in output.splitlines(): 331 if line.startswith('ProductVersion:'): 332 real_ver = line.strip().split()[-1] 333 break 334 else: 335 self.fail(f"failed to parse sw_vers output: {output!r}") 336 337 result_list = res[0].split('.') 338 expect_list = real_ver.split('.') 339 len_diff = len(result_list) - len(expect_list) 340 # On Snow Leopard, sw_vers reports 10.6.0 as 10.6 341 if len_diff > 0: 342 expect_list.extend(['0'] * len_diff) 343 # For compatibility with older binaries, macOS 11.x may report 344 # itself as '10.16' rather than '11.x.y'. 345 if result_list != ['10', '16']: 346 self.assertEqual(result_list, expect_list) 347 348 # res[1] claims to contain 349 # (version, dev_stage, non_release_version) 350 # That information is no longer available 351 self.assertEqual(res[1], ('', '', '')) 352 353 if sys.byteorder == 'little': 354 self.assertIn(res[2], ('i386', 'x86_64', 'arm64')) 355 else: 356 self.assertEqual(res[2], 'PowerPC') 357 358 359 @unittest.skipUnless(sys.platform == 'darwin', "OSX only test") 360 def test_mac_ver_with_fork(self): 361 # Issue7895: platform.mac_ver() crashes when using fork without exec 362 # 363 # This test checks that the fix for that issue works. 364 # 365 pid = os.fork() 366 if pid == 0: 367 # child 368 info = platform.mac_ver() 369 os._exit(0) 370 371 else: 372 # parent 373 support.wait_process(pid, exitcode=0) 374 375 @unittest.skipIf(support.is_emscripten, "Does not apply to Emscripten") 376 def test_libc_ver(self): 377 # check that libc_ver(executable) doesn't raise an exception 378 if os.path.isdir(sys.executable) and \ 379 os.path.exists(sys.executable+'.exe'): 380 # Cygwin horror 381 executable = sys.executable + '.exe' 382 elif sys.platform == "win32" and not os.path.exists(sys.executable): 383 # App symlink appears to not exist, but we want the 384 # real executable here anyway 385 import _winapi 386 executable = _winapi.GetModuleFileName(0) 387 else: 388 executable = sys.executable 389 platform.libc_ver(executable) 390 391 filename = os_helper.TESTFN 392 self.addCleanup(os_helper.unlink, filename) 393 394 with mock.patch('os.confstr', create=True, return_value='mock 1.0'): 395 # test os.confstr() code path 396 self.assertEqual(platform.libc_ver(), ('mock', '1.0')) 397 398 # test the different regular expressions 399 for data, expected in ( 400 (b'__libc_init', ('libc', '')), 401 (b'GLIBC_2.9', ('glibc', '2.9')), 402 (b'libc.so.1.2.5', ('libc', '1.2.5')), 403 (b'libc_pthread.so.1.2.5', ('libc', '1.2.5_pthread')), 404 (b'', ('', '')), 405 ): 406 with open(filename, 'wb') as fp: 407 fp.write(b'[xxx%sxxx]' % data) 408 fp.flush() 409 410 # os.confstr() must not be used if executable is set 411 self.assertEqual(platform.libc_ver(executable=filename), 412 expected) 413 414 # binary containing multiple versions: get the most recent, 415 # make sure that 1.9 is seen as older than 1.23.4 416 chunksize = 16384 417 with open(filename, 'wb') as f: 418 # test match at chunk boundary 419 f.write(b'x'*(chunksize - 10)) 420 f.write(b'GLIBC_1.23.4\0GLIBC_1.9\0GLIBC_1.21\0') 421 self.assertEqual(platform.libc_ver(filename, chunksize=chunksize), 422 ('glibc', '1.23.4')) 423 424 @support.cpython_only 425 def test__comparable_version(self): 426 from platform import _comparable_version as V 427 self.assertEqual(V('1.2.3'), V('1.2.3')) 428 self.assertLess(V('1.2.3'), V('1.2.10')) 429 self.assertEqual(V('1.2.3.4'), V('1_2-3+4')) 430 self.assertLess(V('1.2spam'), V('1.2dev')) 431 self.assertLess(V('1.2dev'), V('1.2alpha')) 432 self.assertLess(V('1.2dev'), V('1.2a')) 433 self.assertLess(V('1.2alpha'), V('1.2beta')) 434 self.assertLess(V('1.2a'), V('1.2b')) 435 self.assertLess(V('1.2beta'), V('1.2c')) 436 self.assertLess(V('1.2b'), V('1.2c')) 437 self.assertLess(V('1.2c'), V('1.2RC')) 438 self.assertLess(V('1.2c'), V('1.2rc')) 439 self.assertLess(V('1.2RC'), V('1.2.0')) 440 self.assertLess(V('1.2rc'), V('1.2.0')) 441 self.assertLess(V('1.2.0'), V('1.2pl')) 442 self.assertLess(V('1.2.0'), V('1.2p')) 443 444 self.assertLess(V('1.5.1'), V('1.5.2b2')) 445 self.assertLess(V('3.10a'), V('161')) 446 self.assertEqual(V('8.02'), V('8.02')) 447 self.assertLess(V('3.4j'), V('1996.07.12')) 448 self.assertLess(V('3.1.1.6'), V('3.2.pl0')) 449 self.assertLess(V('2g6'), V('11g')) 450 self.assertLess(V('0.9'), V('2.2')) 451 self.assertLess(V('1.2'), V('1.2.1')) 452 self.assertLess(V('1.1'), V('1.2.2')) 453 self.assertLess(V('1.1'), V('1.2')) 454 self.assertLess(V('1.2.1'), V('1.2.2')) 455 self.assertLess(V('1.2'), V('1.2.2')) 456 self.assertLess(V('0.4'), V('0.4.0')) 457 self.assertLess(V('1.13++'), V('5.5.kw')) 458 self.assertLess(V('0.960923'), V('2.2beta29')) 459 460 461 def test_macos(self): 462 self.addCleanup(self.clear_caches) 463 464 uname = ('Darwin', 'hostname', '17.7.0', 465 ('Darwin Kernel Version 17.7.0: ' 466 'Thu Jun 21 22:53:14 PDT 2018; ' 467 'root:xnu-4570.71.2~1/RELEASE_X86_64'), 468 'x86_64', 'i386') 469 arch = ('64bit', '') 470 with mock.patch.object(platform, 'uname', return_value=uname), \ 471 mock.patch.object(platform, 'architecture', return_value=arch): 472 for mac_ver, expected_terse, expected in [ 473 # darwin: mac_ver() returns empty strings 474 (('', '', ''), 475 'Darwin-17.7.0', 476 'Darwin-17.7.0-x86_64-i386-64bit'), 477 # macOS: mac_ver() returns macOS version 478 (('10.13.6', ('', '', ''), 'x86_64'), 479 'macOS-10.13.6', 480 'macOS-10.13.6-x86_64-i386-64bit'), 481 ]: 482 with mock.patch.object(platform, 'mac_ver', 483 return_value=mac_ver): 484 self.clear_caches() 485 self.assertEqual(platform.platform(terse=1), expected_terse) 486 self.assertEqual(platform.platform(), expected) 487 488 def test_freedesktop_os_release(self): 489 self.addCleanup(self.clear_caches) 490 self.clear_caches() 491 492 if any(os.path.isfile(fn) for fn in platform._os_release_candidates): 493 info = platform.freedesktop_os_release() 494 self.assertIn("NAME", info) 495 self.assertIn("ID", info) 496 497 info["CPYTHON_TEST"] = "test" 498 self.assertNotIn( 499 "CPYTHON_TEST", 500 platform.freedesktop_os_release() 501 ) 502 else: 503 with self.assertRaises(OSError): 504 platform.freedesktop_os_release() 505 506 def test_parse_os_release(self): 507 info = platform._parse_os_release(FEDORA_OS_RELEASE.splitlines()) 508 self.assertEqual(info["NAME"], "Fedora") 509 self.assertEqual(info["ID"], "fedora") 510 self.assertNotIn("ID_LIKE", info) 511 self.assertEqual(info["VERSION_CODENAME"], "") 512 513 info = platform._parse_os_release(UBUNTU_OS_RELEASE.splitlines()) 514 self.assertEqual(info["NAME"], "Ubuntu") 515 self.assertEqual(info["ID"], "ubuntu") 516 self.assertEqual(info["ID_LIKE"], "debian") 517 self.assertEqual(info["VERSION_CODENAME"], "focal") 518 519 info = platform._parse_os_release(TEST_OS_RELEASE.splitlines()) 520 expected = { 521 "ID": "linux", 522 "NAME": "Linux", 523 "PRETTY_NAME": "Linux", 524 "ID_LIKE": "egg spam viking", 525 "EMPTY": "", 526 "DOUBLE_QUOTE": "double", 527 "EMPTY_DOUBLE": "", 528 "SINGLE_QUOTE": "single", 529 "EMPTY_SINGLE": "", 530 "QUOTES": "double's", 531 "SPECIALS": "$`\\'\"", 532 } 533 self.assertEqual(info, expected) 534 self.assertEqual(len(info["SPECIALS"]), 5) 535 536 537if __name__ == '__main__': 538 unittest.main() 539