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