| import os |
| import copy |
| import pickle |
| import platform |
| import subprocess |
| import sys |
| import unittest |
| from unittest import mock |
| |
| from test import support |
| from test.support import os_helper |
| |
| FEDORA_OS_RELEASE = """\ |
| NAME=Fedora |
| VERSION="32 (Thirty Two)" |
| ID=fedora |
| VERSION_ID=32 |
| VERSION_CODENAME="" |
| PLATFORM_ID="platform:f32" |
| PRETTY_NAME="Fedora 32 (Thirty Two)" |
| ANSI_COLOR="0;34" |
| LOGO=fedora-logo-icon |
| CPE_NAME="cpe:/o:fedoraproject:fedora:32" |
| HOME_URL="https://fedoraproject.org/" |
| DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora/f32/system-administrators-guide/" |
| SUPPORT_URL="https://fedoraproject.org/wiki/Communicating_and_getting_help" |
| BUG_REPORT_URL="https://bugzilla.redhat.com/" |
| REDHAT_BUGZILLA_PRODUCT="Fedora" |
| REDHAT_BUGZILLA_PRODUCT_VERSION=32 |
| REDHAT_SUPPORT_PRODUCT="Fedora" |
| REDHAT_SUPPORT_PRODUCT_VERSION=32 |
| PRIVACY_POLICY_URL="https://fedoraproject.org/wiki/Legal:PrivacyPolicy" |
| """ |
| |
| UBUNTU_OS_RELEASE = """\ |
| NAME="Ubuntu" |
| VERSION="20.04.1 LTS (Focal Fossa)" |
| ID=ubuntu |
| ID_LIKE=debian |
| PRETTY_NAME="Ubuntu 20.04.1 LTS" |
| VERSION_ID="20.04" |
| HOME_URL="https://www.ubuntu.com/" |
| SUPPORT_URL="https://help.ubuntu.com/" |
| BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" |
| PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" |
| VERSION_CODENAME=focal |
| UBUNTU_CODENAME=focal |
| """ |
| |
| TEST_OS_RELEASE = r""" |
| # test data |
| ID_LIKE="egg spam viking" |
| EMPTY= |
| # comments and empty lines are ignored |
| |
| SINGLE_QUOTE='single' |
| EMPTY_SINGLE='' |
| DOUBLE_QUOTE="double" |
| EMPTY_DOUBLE="" |
| QUOTES="double\'s" |
| SPECIALS="\$\`\\\'\"" |
| # invalid lines |
| =invalid |
| = |
| INVALID |
| IN-VALID=value |
| IN VALID=value |
| """ |
| |
| |
| class PlatformTest(unittest.TestCase): |
| def clear_caches(self): |
| platform._platform_cache.clear() |
| platform._sys_version_cache.clear() |
| platform._uname_cache = None |
| platform._os_release_cache = None |
| |
| def test_architecture(self): |
| res = platform.architecture() |
| |
| @os_helper.skip_unless_symlink |
| @support.requires_subprocess() |
| def test_architecture_via_symlink(self): # issue3762 |
| with support.PythonSymlink() as py: |
| cmd = "-c", "import platform; print(platform.architecture())" |
| self.assertEqual(py.call_real(*cmd), py.call_link(*cmd)) |
| |
| def test_platform(self): |
| for aliased in (False, True): |
| for terse in (False, True): |
| res = platform.platform(aliased, terse) |
| |
| def test_system(self): |
| res = platform.system() |
| |
| def test_node(self): |
| res = platform.node() |
| |
| def test_release(self): |
| res = platform.release() |
| |
| def test_version(self): |
| res = platform.version() |
| |
| def test_machine(self): |
| res = platform.machine() |
| |
| def test_processor(self): |
| res = platform.processor() |
| |
| def setUp(self): |
| self.save_version = sys.version |
| self.save_git = sys._git |
| self.save_platform = sys.platform |
| |
| def tearDown(self): |
| sys.version = self.save_version |
| sys._git = self.save_git |
| sys.platform = self.save_platform |
| |
| def test_sys_version(self): |
| # Old test. |
| for input, output in ( |
| ('2.4.3 (#1, Jun 21 2006, 13:54:21) \n[GCC 3.3.4 (pre 3.3.5 20040809)]', |
| ('CPython', '2.4.3', '', '', '1', 'Jun 21 2006 13:54:21', 'GCC 3.3.4 (pre 3.3.5 20040809)')), |
| ('IronPython 1.0.60816 on .NET 2.0.50727.42', |
| ('IronPython', '1.0.60816', '', '', '', '', '.NET 2.0.50727.42')), |
| ('IronPython 1.0 (1.0.61005.1977) on .NET 2.0.50727.42', |
| ('IronPython', '1.0.0', '', '', '', '', '.NET 2.0.50727.42')), |
| ('2.4.3 (truncation, date, t) \n[GCC]', |
| ('CPython', '2.4.3', '', '', 'truncation', 'date t', 'GCC')), |
| ('2.4.3 (truncation, date, ) \n[GCC]', |
| ('CPython', '2.4.3', '', '', 'truncation', 'date', 'GCC')), |
| ('2.4.3 (truncation, date,) \n[GCC]', |
| ('CPython', '2.4.3', '', '', 'truncation', 'date', 'GCC')), |
| ('2.4.3 (truncation, date) \n[GCC]', |
| ('CPython', '2.4.3', '', '', 'truncation', 'date', 'GCC')), |
| ('2.4.3 (truncation, d) \n[GCC]', |
| ('CPython', '2.4.3', '', '', 'truncation', 'd', 'GCC')), |
| ('2.4.3 (truncation, ) \n[GCC]', |
| ('CPython', '2.4.3', '', '', 'truncation', '', 'GCC')), |
| ('2.4.3 (truncation,) \n[GCC]', |
| ('CPython', '2.4.3', '', '', 'truncation', '', 'GCC')), |
| ('2.4.3 (truncation) \n[GCC]', |
| ('CPython', '2.4.3', '', '', 'truncation', '', 'GCC')), |
| ): |
| # branch and revision are not "parsed", but fetched |
| # from sys._git. Ignore them |
| (name, version, branch, revision, buildno, builddate, compiler) \ |
| = platform._sys_version(input) |
| self.assertEqual( |
| (name, version, '', '', buildno, builddate, compiler), output) |
| |
| # Tests for python_implementation(), python_version(), python_branch(), |
| # python_revision(), python_build(), and python_compiler(). |
| sys_versions = { |
| ("2.6.1 (r261:67515, Dec 6 2008, 15:26:00) \n[GCC 4.0.1 (Apple Computer, Inc. build 5370)]", |
| ('CPython', 'tags/r261', '67515'), self.save_platform) |
| : |
| ("CPython", "2.6.1", "tags/r261", "67515", |
| ('r261:67515', 'Dec 6 2008 15:26:00'), |
| 'GCC 4.0.1 (Apple Computer, Inc. build 5370)'), |
| |
| ("IronPython 2.0 (2.0.0.0) on .NET 2.0.50727.3053", None, "cli") |
| : |
| ("IronPython", "2.0.0", "", "", ("", ""), |
| ".NET 2.0.50727.3053"), |
| |
| ("2.6.1 (IronPython 2.6.1 (2.6.10920.0) on .NET 2.0.50727.1433)", None, "cli") |
| : |
| ("IronPython", "2.6.1", "", "", ("", ""), |
| ".NET 2.0.50727.1433"), |
| |
| ("2.7.4 (IronPython 2.7.4 (2.7.0.40) on Mono 4.0.30319.1 (32-bit))", None, "cli") |
| : |
| ("IronPython", "2.7.4", "", "", ("", ""), |
| "Mono 4.0.30319.1 (32-bit)"), |
| |
| ("2.5 (trunk:6107, Mar 26 2009, 13:02:18) \n[Java HotSpot(TM) Client VM (\"Apple Computer, Inc.\")]", |
| ('Jython', 'trunk', '6107'), "java1.5.0_16") |
| : |
| ("Jython", "2.5.0", "trunk", "6107", |
| ('trunk:6107', 'Mar 26 2009'), "java1.5.0_16"), |
| |
| ("2.5.2 (63378, Mar 26 2009, 18:03:29)\n[PyPy 1.0.0]", |
| ('PyPy', 'trunk', '63378'), self.save_platform) |
| : |
| ("PyPy", "2.5.2", "trunk", "63378", ('63378', 'Mar 26 2009'), |
| "") |
| } |
| for (version_tag, scm, sys_platform), info in \ |
| sys_versions.items(): |
| sys.version = version_tag |
| if scm is None: |
| if hasattr(sys, "_git"): |
| del sys._git |
| else: |
| sys._git = scm |
| if sys_platform is not None: |
| sys.platform = sys_platform |
| self.assertEqual(platform.python_implementation(), info[0]) |
| self.assertEqual(platform.python_version(), info[1]) |
| self.assertEqual(platform.python_branch(), info[2]) |
| self.assertEqual(platform.python_revision(), info[3]) |
| self.assertEqual(platform.python_build(), info[4]) |
| self.assertEqual(platform.python_compiler(), info[5]) |
| |
| def test_system_alias(self): |
| res = platform.system_alias( |
| platform.system(), |
| platform.release(), |
| platform.version(), |
| ) |
| |
| def test_uname(self): |
| res = platform.uname() |
| self.assertTrue(any(res)) |
| self.assertEqual(res[0], res.system) |
| self.assertEqual(res[-6], res.system) |
| self.assertEqual(res[1], res.node) |
| self.assertEqual(res[-5], res.node) |
| self.assertEqual(res[2], res.release) |
| self.assertEqual(res[-4], res.release) |
| self.assertEqual(res[3], res.version) |
| self.assertEqual(res[-3], res.version) |
| self.assertEqual(res[4], res.machine) |
| self.assertEqual(res[-2], res.machine) |
| self.assertEqual(res[5], res.processor) |
| self.assertEqual(res[-1], res.processor) |
| self.assertEqual(len(res), 6) |
| |
| def test_uname_cast_to_tuple(self): |
| res = platform.uname() |
| expected = ( |
| res.system, res.node, res.release, res.version, res.machine, |
| res.processor, |
| ) |
| self.assertEqual(tuple(res), expected) |
| |
| def test_uname_replace(self): |
| res = platform.uname() |
| new = res._replace( |
| system='system', node='node', release='release', |
| version='version', machine='machine') |
| self.assertEqual(new.system, 'system') |
| self.assertEqual(new.node, 'node') |
| self.assertEqual(new.release, 'release') |
| self.assertEqual(new.version, 'version') |
| self.assertEqual(new.machine, 'machine') |
| # processor cannot be replaced |
| self.assertEqual(new.processor, res.processor) |
| |
| def test_uname_copy(self): |
| uname = platform.uname() |
| self.assertEqual(copy.copy(uname), uname) |
| self.assertEqual(copy.deepcopy(uname), uname) |
| |
| def test_uname_pickle(self): |
| orig = platform.uname() |
| for proto in range(pickle.HIGHEST_PROTOCOL + 1): |
| with self.subTest(protocol=proto): |
| pickled = pickle.dumps(orig, proto) |
| restored = pickle.loads(pickled) |
| self.assertEqual(restored, orig) |
| |
| def test_uname_slices(self): |
| res = platform.uname() |
| expected = tuple(res) |
| self.assertEqual(res[:], expected) |
| self.assertEqual(res[:5], expected[:5]) |
| |
| def test_uname_fields(self): |
| self.assertIn('processor', platform.uname()._fields) |
| |
| def test_uname_asdict(self): |
| res = platform.uname()._asdict() |
| self.assertEqual(len(res), 6) |
| self.assertIn('processor', res) |
| |
| @unittest.skipIf(sys.platform in ['win32', 'OpenVMS'], "uname -p not used") |
| @support.requires_subprocess() |
| def test_uname_processor(self): |
| """ |
| On some systems, the processor must match the output |
| of 'uname -p'. See Issue 35967 for rationale. |
| """ |
| try: |
| proc_res = subprocess.check_output(['uname', '-p'], text=True).strip() |
| expect = platform._unknown_as_blank(proc_res) |
| except (OSError, subprocess.CalledProcessError): |
| expect = '' |
| self.assertEqual(platform.uname().processor, expect) |
| |
| @unittest.skipUnless(sys.platform.startswith('win'), "windows only test") |
| def test_uname_win32_ARCHITEW6432(self): |
| # Issue 7860: make sure we get architecture from the correct variable |
| # on 64 bit Windows: if PROCESSOR_ARCHITEW6432 exists we should be |
| # using it, per |
| # http://blogs.msdn.com/david.wang/archive/2006/03/26/HOWTO-Detect-Process-Bitness.aspx |
| try: |
| with os_helper.EnvironmentVarGuard() as environ: |
| if 'PROCESSOR_ARCHITEW6432' in environ: |
| del environ['PROCESSOR_ARCHITEW6432'] |
| environ['PROCESSOR_ARCHITECTURE'] = 'foo' |
| platform._uname_cache = None |
| system, node, release, version, machine, processor = platform.uname() |
| self.assertEqual(machine, 'foo') |
| environ['PROCESSOR_ARCHITEW6432'] = 'bar' |
| platform._uname_cache = None |
| system, node, release, version, machine, processor = platform.uname() |
| self.assertEqual(machine, 'bar') |
| finally: |
| platform._uname_cache = None |
| |
| def test_java_ver(self): |
| res = platform.java_ver() |
| if sys.platform == 'java': |
| self.assertTrue(all(res)) |
| |
| def test_win32_ver(self): |
| res = platform.win32_ver() |
| |
| def test_mac_ver(self): |
| res = platform.mac_ver() |
| |
| if platform.uname().system == 'Darwin': |
| # We are on a macOS system, check that the right version |
| # information is returned |
| output = subprocess.check_output(['sw_vers'], text=True) |
| for line in output.splitlines(): |
| if line.startswith('ProductVersion:'): |
| real_ver = line.strip().split()[-1] |
| break |
| else: |
| self.fail(f"failed to parse sw_vers output: {output!r}") |
| |
| result_list = res[0].split('.') |
| expect_list = real_ver.split('.') |
| len_diff = len(result_list) - len(expect_list) |
| # On Snow Leopard, sw_vers reports 10.6.0 as 10.6 |
| if len_diff > 0: |
| expect_list.extend(['0'] * len_diff) |
| # For compatibility with older binaries, macOS 11.x may report |
| # itself as '10.16' rather than '11.x.y'. |
| if result_list != ['10', '16']: |
| self.assertEqual(result_list, expect_list) |
| |
| # res[1] claims to contain |
| # (version, dev_stage, non_release_version) |
| # That information is no longer available |
| self.assertEqual(res[1], ('', '', '')) |
| |
| if sys.byteorder == 'little': |
| self.assertIn(res[2], ('i386', 'x86_64', 'arm64')) |
| else: |
| self.assertEqual(res[2], 'PowerPC') |
| |
| |
| @unittest.skipUnless(sys.platform == 'darwin', "OSX only test") |
| def test_mac_ver_with_fork(self): |
| # Issue7895: platform.mac_ver() crashes when using fork without exec |
| # |
| # This test checks that the fix for that issue works. |
| # |
| pid = os.fork() |
| if pid == 0: |
| # child |
| info = platform.mac_ver() |
| os._exit(0) |
| |
| else: |
| # parent |
| support.wait_process(pid, exitcode=0) |
| |
| @unittest.skipIf(support.is_emscripten, "Does not apply to Emscripten") |
| def test_libc_ver(self): |
| # check that libc_ver(executable) doesn't raise an exception |
| if os.path.isdir(sys.executable) and \ |
| os.path.exists(sys.executable+'.exe'): |
| # Cygwin horror |
| executable = sys.executable + '.exe' |
| elif sys.platform == "win32" and not os.path.exists(sys.executable): |
| # App symlink appears to not exist, but we want the |
| # real executable here anyway |
| import _winapi |
| executable = _winapi.GetModuleFileName(0) |
| else: |
| executable = sys.executable |
| platform.libc_ver(executable) |
| |
| filename = os_helper.TESTFN |
| self.addCleanup(os_helper.unlink, filename) |
| |
| with mock.patch('os.confstr', create=True, return_value='mock 1.0'): |
| # test os.confstr() code path |
| self.assertEqual(platform.libc_ver(), ('mock', '1.0')) |
| |
| # test the different regular expressions |
| for data, expected in ( |
| (b'__libc_init', ('libc', '')), |
| (b'GLIBC_2.9', ('glibc', '2.9')), |
| (b'libc.so.1.2.5', ('libc', '1.2.5')), |
| (b'libc_pthread.so.1.2.5', ('libc', '1.2.5_pthread')), |
| (b'', ('', '')), |
| ): |
| with open(filename, 'wb') as fp: |
| fp.write(b'[xxx%sxxx]' % data) |
| fp.flush() |
| |
| # os.confstr() must not be used if executable is set |
| self.assertEqual(platform.libc_ver(executable=filename), |
| expected) |
| |
| # binary containing multiple versions: get the most recent, |
| # make sure that 1.9 is seen as older than 1.23.4 |
| chunksize = 16384 |
| with open(filename, 'wb') as f: |
| # test match at chunk boundary |
| f.write(b'x'*(chunksize - 10)) |
| f.write(b'GLIBC_1.23.4\0GLIBC_1.9\0GLIBC_1.21\0') |
| self.assertEqual(platform.libc_ver(filename, chunksize=chunksize), |
| ('glibc', '1.23.4')) |
| |
| @support.cpython_only |
| def test__comparable_version(self): |
| from platform import _comparable_version as V |
| self.assertEqual(V('1.2.3'), V('1.2.3')) |
| self.assertLess(V('1.2.3'), V('1.2.10')) |
| self.assertEqual(V('1.2.3.4'), V('1_2-3+4')) |
| self.assertLess(V('1.2spam'), V('1.2dev')) |
| self.assertLess(V('1.2dev'), V('1.2alpha')) |
| self.assertLess(V('1.2dev'), V('1.2a')) |
| self.assertLess(V('1.2alpha'), V('1.2beta')) |
| self.assertLess(V('1.2a'), V('1.2b')) |
| self.assertLess(V('1.2beta'), V('1.2c')) |
| self.assertLess(V('1.2b'), V('1.2c')) |
| self.assertLess(V('1.2c'), V('1.2RC')) |
| self.assertLess(V('1.2c'), V('1.2rc')) |
| self.assertLess(V('1.2RC'), V('1.2.0')) |
| self.assertLess(V('1.2rc'), V('1.2.0')) |
| self.assertLess(V('1.2.0'), V('1.2pl')) |
| self.assertLess(V('1.2.0'), V('1.2p')) |
| |
| self.assertLess(V('1.5.1'), V('1.5.2b2')) |
| self.assertLess(V('3.10a'), V('161')) |
| self.assertEqual(V('8.02'), V('8.02')) |
| self.assertLess(V('3.4j'), V('1996.07.12')) |
| self.assertLess(V('3.1.1.6'), V('3.2.pl0')) |
| self.assertLess(V('2g6'), V('11g')) |
| self.assertLess(V('0.9'), V('2.2')) |
| self.assertLess(V('1.2'), V('1.2.1')) |
| self.assertLess(V('1.1'), V('1.2.2')) |
| self.assertLess(V('1.1'), V('1.2')) |
| self.assertLess(V('1.2.1'), V('1.2.2')) |
| self.assertLess(V('1.2'), V('1.2.2')) |
| self.assertLess(V('0.4'), V('0.4.0')) |
| self.assertLess(V('1.13++'), V('5.5.kw')) |
| self.assertLess(V('0.960923'), V('2.2beta29')) |
| |
| |
| def test_macos(self): |
| self.addCleanup(self.clear_caches) |
| |
| uname = ('Darwin', 'hostname', '17.7.0', |
| ('Darwin Kernel Version 17.7.0: ' |
| 'Thu Jun 21 22:53:14 PDT 2018; ' |
| 'root:xnu-4570.71.2~1/RELEASE_X86_64'), |
| 'x86_64', 'i386') |
| arch = ('64bit', '') |
| with mock.patch.object(platform, 'uname', return_value=uname), \ |
| mock.patch.object(platform, 'architecture', return_value=arch): |
| for mac_ver, expected_terse, expected in [ |
| # darwin: mac_ver() returns empty strings |
| (('', '', ''), |
| 'Darwin-17.7.0', |
| 'Darwin-17.7.0-x86_64-i386-64bit'), |
| # macOS: mac_ver() returns macOS version |
| (('10.13.6', ('', '', ''), 'x86_64'), |
| 'macOS-10.13.6', |
| 'macOS-10.13.6-x86_64-i386-64bit'), |
| ]: |
| with mock.patch.object(platform, 'mac_ver', |
| return_value=mac_ver): |
| self.clear_caches() |
| self.assertEqual(platform.platform(terse=1), expected_terse) |
| self.assertEqual(platform.platform(), expected) |
| |
| def test_freedesktop_os_release(self): |
| self.addCleanup(self.clear_caches) |
| self.clear_caches() |
| |
| if any(os.path.isfile(fn) for fn in platform._os_release_candidates): |
| info = platform.freedesktop_os_release() |
| self.assertIn("NAME", info) |
| self.assertIn("ID", info) |
| |
| info["CPYTHON_TEST"] = "test" |
| self.assertNotIn( |
| "CPYTHON_TEST", |
| platform.freedesktop_os_release() |
| ) |
| else: |
| with self.assertRaises(OSError): |
| platform.freedesktop_os_release() |
| |
| def test_parse_os_release(self): |
| info = platform._parse_os_release(FEDORA_OS_RELEASE.splitlines()) |
| self.assertEqual(info["NAME"], "Fedora") |
| self.assertEqual(info["ID"], "fedora") |
| self.assertNotIn("ID_LIKE", info) |
| self.assertEqual(info["VERSION_CODENAME"], "") |
| |
| info = platform._parse_os_release(UBUNTU_OS_RELEASE.splitlines()) |
| self.assertEqual(info["NAME"], "Ubuntu") |
| self.assertEqual(info["ID"], "ubuntu") |
| self.assertEqual(info["ID_LIKE"], "debian") |
| self.assertEqual(info["VERSION_CODENAME"], "focal") |
| |
| info = platform._parse_os_release(TEST_OS_RELEASE.splitlines()) |
| expected = { |
| "ID": "linux", |
| "NAME": "Linux", |
| "PRETTY_NAME": "Linux", |
| "ID_LIKE": "egg spam viking", |
| "EMPTY": "", |
| "DOUBLE_QUOTE": "double", |
| "EMPTY_DOUBLE": "", |
| "SINGLE_QUOTE": "single", |
| "EMPTY_SINGLE": "", |
| "QUOTES": "double's", |
| "SPECIALS": "$`\\'\"", |
| } |
| self.assertEqual(info, expected) |
| self.assertEqual(len(info["SPECIALS"]), 5) |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |