test_platform.py 20 KB

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