(root)/
Python-3.11.7/
Lib/
test/
test_platform.py
       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  
      10  from test import support
      11  from test.support import os_helper
      12  
      13  FEDORA_OS_RELEASE = """\
      14  NAME=Fedora
      15  VERSION="32 (Thirty Two)"
      16  ID=fedora
      17  VERSION_ID=32
      18  VERSION_CODENAME=""
      19  PLATFORM_ID="platform:f32"
      20  PRETTY_NAME="Fedora 32 (Thirty Two)"
      21  ANSI_COLOR="0;34"
      22  LOGO=fedora-logo-icon
      23  CPE_NAME="cpe:/o:fedoraproject:fedora:32"
      24  HOME_URL="https://fedoraproject.org/"
      25  DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f32/system-administrators-guide/"
      26  SUPPORT_URL="https://fedoraproject.org/wiki/Communicating_and_getting_help"
      27  BUG_REPORT_URL="https://bugzilla.redhat.com/"
      28  REDHAT_BUGZILLA_PRODUCT="Fedora"
      29  REDHAT_BUGZILLA_PRODUCT_VERSION=32
      30  REDHAT_SUPPORT_PRODUCT="Fedora"
      31  REDHAT_SUPPORT_PRODUCT_VERSION=32
      32  PRIVACY_POLICY_URL="https://fedoraproject.org/wiki/Legal:PrivacyPolicy"
      33  """
      34  
      35  UBUNTU_OS_RELEASE = """\
      36  NAME="Ubuntu"
      37  VERSION="20.04.1 LTS (Focal Fossa)"
      38  ID=ubuntu
      39  ID_LIKE=debian
      40  PRETTY_NAME="Ubuntu 20.04.1 LTS"
      41  VERSION_ID="20.04"
      42  HOME_URL="https://www.ubuntu.com/"
      43  SUPPORT_URL="https://help.ubuntu.com/"
      44  BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
      45  PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
      46  VERSION_CODENAME=focal
      47  UBUNTU_CODENAME=focal
      48  """
      49  
      50  TEST_OS_RELEASE = r"""
      51  # test data
      52  ID_LIKE="egg spam viking"
      53  EMPTY=
      54  # comments and empty lines are ignored
      55  
      56  SINGLE_QUOTE='single'
      57  EMPTY_SINGLE=''
      58  DOUBLE_QUOTE="double"
      59  EMPTY_DOUBLE=""
      60  QUOTES="double\'s"
      61  SPECIALS="\$\`\\\'\""
      62  # invalid lines
      63  =invalid
      64  =
      65  INVALID
      66  IN-VALID=value
      67  IN VALID=value
      68  """
      69  
      70  
      71  class ESC[4;38;5;81mPlatformTest(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
      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  
     537  if __name__ == '__main__':
     538      unittest.main()