|
| 1 | +import os |
| 2 | +import platform |
| 3 | +import subprocess |
| 4 | +import sys |
| 5 | +import sysconfig |
| 6 | +import tempfile |
| 7 | +import unittest |
| 8 | +from unittest import mock |
| 9 | + |
| 10 | +from test import support |
| 11 | + |
| 12 | +class PlatformTest(unittest.TestCase): |
| 13 | + def clear_caches(self): |
| 14 | + platform._platform_cache.clear() |
| 15 | + platform._sys_version_cache.clear() |
| 16 | + platform._uname_cache = None |
| 17 | + |
| 18 | + def test_architecture(self): |
| 19 | + res = platform.architecture() |
| 20 | + |
| 21 | + @support.skip_unless_symlink |
| 22 | + def test_architecture_via_symlink(self): # issue3762 |
| 23 | + # On Windows, the EXE needs to know where pythonXY.dll and *.pyd is at |
| 24 | + # so we add the directory to the path, PYTHONHOME and PYTHONPATH. |
| 25 | + env = None |
| 26 | + if sys.platform == "win32": |
| 27 | + env = {k.upper(): os.environ[k] for k in os.environ} |
| 28 | + env["PATH"] = "{};{}".format( |
| 29 | + os.path.dirname(sys.executable), env.get("PATH", "")) |
| 30 | + env["PYTHONHOME"] = os.path.dirname(sys.executable) |
| 31 | + if sysconfig.is_python_build(True): |
| 32 | + env["PYTHONPATH"] = os.path.dirname(os.__file__) |
| 33 | + |
| 34 | + def get(python, env=None): |
| 35 | + cmd = [python, '-c', |
| 36 | + 'import platform; print(platform.architecture())'] |
| 37 | + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, |
| 38 | + stderr=subprocess.PIPE, env=env) |
| 39 | + r = p.communicate() |
| 40 | + if p.returncode: |
| 41 | + print(repr(r[0])) |
| 42 | + print(repr(r[1]), file=sys.stderr) |
| 43 | + self.fail('unexpected return code: {0} (0x{0:08X})' |
| 44 | + .format(p.returncode)) |
| 45 | + return r |
| 46 | + |
| 47 | + real = os.path.realpath(sys.executable) |
| 48 | + link = os.path.abspath(support.TESTFN) |
| 49 | + os.symlink(real, link) |
| 50 | + try: |
| 51 | + self.assertEqual(get(real), get(link, env=env)) |
| 52 | + finally: |
| 53 | + os.remove(link) |
| 54 | + |
| 55 | + def test_platform(self): |
| 56 | + for aliased in (False, True): |
| 57 | + for terse in (False, True): |
| 58 | + res = platform.platform(aliased, terse) |
| 59 | + |
| 60 | + def test_system(self): |
| 61 | + res = platform.system() |
| 62 | + |
| 63 | + def test_node(self): |
| 64 | + res = platform.node() |
| 65 | + |
| 66 | + def test_release(self): |
| 67 | + res = platform.release() |
| 68 | + |
| 69 | + def test_version(self): |
| 70 | + res = platform.version() |
| 71 | + |
| 72 | + def test_machine(self): |
| 73 | + res = platform.machine() |
| 74 | + |
| 75 | + def test_processor(self): |
| 76 | + res = platform.processor() |
| 77 | + |
| 78 | + def setUp(self): |
| 79 | + self.save_version = sys.version |
| 80 | + self.save_git = sys._git |
| 81 | + self.save_platform = sys.platform |
| 82 | + |
| 83 | + def tearDown(self): |
| 84 | + sys.version = self.save_version |
| 85 | + sys._git = self.save_git |
| 86 | + sys.platform = self.save_platform |
| 87 | + |
| 88 | + def test_sys_version(self): |
| 89 | + # Old test. |
| 90 | + for input, output in ( |
| 91 | + ('2.4.3 (#1, Jun 21 2006, 13:54:21) \n[GCC 3.3.4 (pre 3.3.5 20040809)]', |
| 92 | + ('CPython', '2.4.3', '', '', '1', 'Jun 21 2006 13:54:21', 'GCC 3.3.4 (pre 3.3.5 20040809)')), |
| 93 | + ('IronPython 1.0.60816 on .NET 2.0.50727.42', |
| 94 | + ('IronPython', '1.0.60816', '', '', '', '', '.NET 2.0.50727.42')), |
| 95 | + ('IronPython 1.0 (1.0.61005.1977) on .NET 2.0.50727.42', |
| 96 | + ('IronPython', '1.0.0', '', '', '', '', '.NET 2.0.50727.42')), |
| 97 | + ('2.4.3 (truncation, date, t) \n[GCC]', |
| 98 | + ('CPython', '2.4.3', '', '', 'truncation', 'date t', 'GCC')), |
| 99 | + ('2.4.3 (truncation, date, ) \n[GCC]', |
| 100 | + ('CPython', '2.4.3', '', '', 'truncation', 'date', 'GCC')), |
| 101 | + ('2.4.3 (truncation, date,) \n[GCC]', |
| 102 | + ('CPython', '2.4.3', '', '', 'truncation', 'date', 'GCC')), |
| 103 | + ('2.4.3 (truncation, date) \n[GCC]', |
| 104 | + ('CPython', '2.4.3', '', '', 'truncation', 'date', 'GCC')), |
| 105 | + ('2.4.3 (truncation, d) \n[GCC]', |
| 106 | + ('CPython', '2.4.3', '', '', 'truncation', 'd', 'GCC')), |
| 107 | + ('2.4.3 (truncation, ) \n[GCC]', |
| 108 | + ('CPython', '2.4.3', '', '', 'truncation', '', 'GCC')), |
| 109 | + ('2.4.3 (truncation,) \n[GCC]', |
| 110 | + ('CPython', '2.4.3', '', '', 'truncation', '', 'GCC')), |
| 111 | + ('2.4.3 (truncation) \n[GCC]', |
| 112 | + ('CPython', '2.4.3', '', '', 'truncation', '', 'GCC')), |
| 113 | + ): |
| 114 | + # branch and revision are not "parsed", but fetched |
| 115 | + # from sys._git. Ignore them |
| 116 | + (name, version, branch, revision, buildno, builddate, compiler) \ |
| 117 | + = platform._sys_version(input) |
| 118 | + self.assertEqual( |
| 119 | + (name, version, '', '', buildno, builddate, compiler), output) |
| 120 | + |
| 121 | + # Tests for python_implementation(), python_version(), python_branch(), |
| 122 | + # python_revision(), python_build(), and python_compiler(). |
| 123 | + sys_versions = { |
| 124 | + ("2.6.1 (r261:67515, Dec 6 2008, 15:26:00) \n[GCC 4.0.1 (Apple Computer, Inc. build 5370)]", |
| 125 | + ('CPython', 'tags/r261', '67515'), self.save_platform) |
| 126 | + : |
| 127 | + ("CPython", "2.6.1", "tags/r261", "67515", |
| 128 | + ('r261:67515', 'Dec 6 2008 15:26:00'), |
| 129 | + 'GCC 4.0.1 (Apple Computer, Inc. build 5370)'), |
| 130 | + |
| 131 | + ("IronPython 2.0 (2.0.0.0) on .NET 2.0.50727.3053", None, "cli") |
| 132 | + : |
| 133 | + ("IronPython", "2.0.0", "", "", ("", ""), |
| 134 | + ".NET 2.0.50727.3053"), |
| 135 | + |
| 136 | + ("2.6.1 (IronPython 2.6.1 (2.6.10920.0) on .NET 2.0.50727.1433)", None, "cli") |
| 137 | + : |
| 138 | + ("IronPython", "2.6.1", "", "", ("", ""), |
| 139 | + ".NET 2.0.50727.1433"), |
| 140 | + |
| 141 | + ("2.7.4 (IronPython 2.7.4 (2.7.0.40) on Mono 4.0.30319.1 (32-bit))", None, "cli") |
| 142 | + : |
| 143 | + ("IronPython", "2.7.4", "", "", ("", ""), |
| 144 | + "Mono 4.0.30319.1 (32-bit)"), |
| 145 | + |
| 146 | + ("2.5 (trunk:6107, Mar 26 2009, 13:02:18) \n[Java HotSpot(TM) Client VM (\"Apple Computer, Inc.\")]", |
| 147 | + ('Jython', 'trunk', '6107'), "java1.5.0_16") |
| 148 | + : |
| 149 | + ("Jython", "2.5.0", "trunk", "6107", |
| 150 | + ('trunk:6107', 'Mar 26 2009'), "java1.5.0_16"), |
| 151 | + |
| 152 | + ("2.5.2 (63378, Mar 26 2009, 18:03:29)\n[PyPy 1.0.0]", |
| 153 | + ('PyPy', 'trunk', '63378'), self.save_platform) |
| 154 | + : |
| 155 | + ("PyPy", "2.5.2", "trunk", "63378", ('63378', 'Mar 26 2009'), |
| 156 | + "") |
| 157 | + } |
| 158 | + for (version_tag, scm, sys_platform), info in \ |
| 159 | + sys_versions.items(): |
| 160 | + sys.version = version_tag |
| 161 | + if scm is None: |
| 162 | + if hasattr(sys, "_git"): |
| 163 | + del sys._git |
| 164 | + else: |
| 165 | + sys._git = scm |
| 166 | + if sys_platform is not None: |
| 167 | + sys.platform = sys_platform |
| 168 | + self.assertEqual(platform.python_implementation(), info[0]) |
| 169 | + self.assertEqual(platform.python_version(), info[1]) |
| 170 | + self.assertEqual(platform.python_branch(), info[2]) |
| 171 | + self.assertEqual(platform.python_revision(), info[3]) |
| 172 | + self.assertEqual(platform.python_build(), info[4]) |
| 173 | + self.assertEqual(platform.python_compiler(), info[5]) |
| 174 | + |
| 175 | + def test_system_alias(self): |
| 176 | + res = platform.system_alias( |
| 177 | + platform.system(), |
| 178 | + platform.release(), |
| 179 | + platform.version(), |
| 180 | + ) |
| 181 | + |
| 182 | + def test_uname(self): |
| 183 | + res = platform.uname() |
| 184 | + self.assertTrue(any(res)) |
| 185 | + self.assertEqual(res[0], res.system) |
| 186 | + self.assertEqual(res[1], res.node) |
| 187 | + self.assertEqual(res[2], res.release) |
| 188 | + self.assertEqual(res[3], res.version) |
| 189 | + self.assertEqual(res[4], res.machine) |
| 190 | + self.assertEqual(res[5], res.processor) |
| 191 | + |
| 192 | + @unittest.skipUnless(sys.platform.startswith('win'), "windows only test") |
| 193 | + def test_uname_win32_ARCHITEW6432(self): |
| 194 | + # Issue 7860: make sure we get architecture from the correct variable |
| 195 | + # on 64 bit Windows: if PROCESSOR_ARCHITEW6432 exists we should be |
| 196 | + # using it, per |
| 197 | + # http://blogs.msdn.com/david.wang/archive/2006/03/26/HOWTO-Detect-Process-Bitness.aspx |
| 198 | + try: |
| 199 | + with support.EnvironmentVarGuard() as environ: |
| 200 | + if 'PROCESSOR_ARCHITEW6432' in environ: |
| 201 | + del environ['PROCESSOR_ARCHITEW6432'] |
| 202 | + environ['PROCESSOR_ARCHITECTURE'] = 'foo' |
| 203 | + platform._uname_cache = None |
| 204 | + system, node, release, version, machine, processor = platform.uname() |
| 205 | + self.assertEqual(machine, 'foo') |
| 206 | + environ['PROCESSOR_ARCHITEW6432'] = 'bar' |
| 207 | + platform._uname_cache = None |
| 208 | + system, node, release, version, machine, processor = platform.uname() |
| 209 | + self.assertEqual(machine, 'bar') |
| 210 | + finally: |
| 211 | + platform._uname_cache = None |
| 212 | + |
| 213 | + def test_java_ver(self): |
| 214 | + res = platform.java_ver() |
| 215 | + if sys.platform == 'java': |
| 216 | + self.assertTrue(all(res)) |
| 217 | + |
| 218 | + def test_win32_ver(self): |
| 219 | + res = platform.win32_ver() |
| 220 | + |
| 221 | + def test_mac_ver(self): |
| 222 | + res = platform.mac_ver() |
| 223 | + |
| 224 | + if platform.uname().system == 'Darwin': |
| 225 | + # We are on a macOS system, check that the right version |
| 226 | + # information is returned |
| 227 | + output = subprocess.check_output(['sw_vers'], text=True) |
| 228 | + for line in output.splitlines(): |
| 229 | + if line.startswith('ProductVersion:'): |
| 230 | + real_ver = line.strip().split()[-1] |
| 231 | + break |
| 232 | + else: |
| 233 | + self.fail(f"failed to parse sw_vers output: {output!r}") |
| 234 | + |
| 235 | + result_list = res[0].split('.') |
| 236 | + expect_list = real_ver.split('.') |
| 237 | + len_diff = len(result_list) - len(expect_list) |
| 238 | + # On Snow Leopard, sw_vers reports 10.6.0 as 10.6 |
| 239 | + if len_diff > 0: |
| 240 | + expect_list.extend(['0'] * len_diff) |
| 241 | + self.assertEqual(result_list, expect_list) |
| 242 | + |
| 243 | + # res[1] claims to contain |
| 244 | + # (version, dev_stage, non_release_version) |
| 245 | + # That information is no longer available |
| 246 | + self.assertEqual(res[1], ('', '', '')) |
| 247 | + |
| 248 | + if sys.byteorder == 'little': |
| 249 | + self.assertIn(res[2], ('i386', 'x86_64')) |
| 250 | + else: |
| 251 | + self.assertEqual(res[2], 'PowerPC') |
| 252 | + |
| 253 | + |
| 254 | + @unittest.skipUnless(sys.platform == 'darwin', "OSX only test") |
| 255 | + def test_mac_ver_with_fork(self): |
| 256 | + # Issue7895: platform.mac_ver() crashes when using fork without exec |
| 257 | + # |
| 258 | + # This test checks that the fix for that issue works. |
| 259 | + # |
| 260 | + pid = os.fork() |
| 261 | + if pid == 0: |
| 262 | + # child |
| 263 | + info = platform.mac_ver() |
| 264 | + os._exit(0) |
| 265 | + |
| 266 | + else: |
| 267 | + # parent |
| 268 | + cpid, sts = os.waitpid(pid, 0) |
| 269 | + self.assertEqual(cpid, pid) |
| 270 | + self.assertEqual(sts, 0) |
| 271 | + |
| 272 | + def test_libc_ver(self): |
| 273 | + # check that libc_ver(executable) doesn't raise an exception |
| 274 | + if os.path.isdir(sys.executable) and \ |
| 275 | + os.path.exists(sys.executable+'.exe'): |
| 276 | + # Cygwin horror |
| 277 | + executable = sys.executable + '.exe' |
| 278 | + else: |
| 279 | + executable = sys.executable |
| 280 | + platform.libc_ver(executable) |
| 281 | + |
| 282 | + filename = support.TESTFN |
| 283 | + self.addCleanup(support.unlink, filename) |
| 284 | + |
| 285 | + with mock.patch('os.confstr', create=True, return_value='mock 1.0'): |
| 286 | + # test os.confstr() code path |
| 287 | + self.assertEqual(platform.libc_ver(), ('mock', '1.0')) |
| 288 | + |
| 289 | + # test the different regular expressions |
| 290 | + for data, expected in ( |
| 291 | + (b'__libc_init', ('libc', '')), |
| 292 | + (b'GLIBC_2.9', ('glibc', '2.9')), |
| 293 | + (b'libc.so.1.2.5', ('libc', '1.2.5')), |
| 294 | + (b'libc_pthread.so.1.2.5', ('libc', '1.2.5_pthread')), |
| 295 | + (b'', ('', '')), |
| 296 | + ): |
| 297 | + with open(filename, 'wb') as fp: |
| 298 | + fp.write(b'[xxx%sxxx]' % data) |
| 299 | + fp.flush() |
| 300 | + |
| 301 | + # os.confstr() must not be used if executable is set |
| 302 | + self.assertEqual(platform.libc_ver(executable=filename), |
| 303 | + expected) |
| 304 | + |
| 305 | + # binary containing multiple versions: get the most recent, |
| 306 | + # make sure that 1.9 is seen as older than 1.23.4 |
| 307 | + chunksize = 16384 |
| 308 | + with open(filename, 'wb') as f: |
| 309 | + # test match at chunk boundary |
| 310 | + f.write(b'x'*(chunksize - 10)) |
| 311 | + f.write(b'GLIBC_1.23.4\0GLIBC_1.9\0GLIBC_1.21\0') |
| 312 | + self.assertEqual(platform.libc_ver(filename, chunksize=chunksize), |
| 313 | + ('glibc', '1.23.4')) |
| 314 | + |
| 315 | + @support.cpython_only |
| 316 | + def test__comparable_version(self): |
| 317 | + from platform import _comparable_version as V |
| 318 | + self.assertEqual(V('1.2.3'), V('1.2.3')) |
| 319 | + self.assertLess(V('1.2.3'), V('1.2.10')) |
| 320 | + self.assertEqual(V('1.2.3.4'), V('1_2-3+4')) |
| 321 | + self.assertLess(V('1.2spam'), V('1.2dev')) |
| 322 | + self.assertLess(V('1.2dev'), V('1.2alpha')) |
| 323 | + self.assertLess(V('1.2dev'), V('1.2a')) |
| 324 | + self.assertLess(V('1.2alpha'), V('1.2beta')) |
| 325 | + self.assertLess(V('1.2a'), V('1.2b')) |
| 326 | + self.assertLess(V('1.2beta'), V('1.2c')) |
| 327 | + self.assertLess(V('1.2b'), V('1.2c')) |
| 328 | + self.assertLess(V('1.2c'), V('1.2RC')) |
| 329 | + self.assertLess(V('1.2c'), V('1.2rc')) |
| 330 | + self.assertLess(V('1.2RC'), V('1.2.0')) |
| 331 | + self.assertLess(V('1.2rc'), V('1.2.0')) |
| 332 | + self.assertLess(V('1.2.0'), V('1.2pl')) |
| 333 | + self.assertLess(V('1.2.0'), V('1.2p')) |
| 334 | + |
| 335 | + self.assertLess(V('1.5.1'), V('1.5.2b2')) |
| 336 | + self.assertLess(V('3.10a'), V('161')) |
| 337 | + self.assertEqual(V('8.02'), V('8.02')) |
| 338 | + self.assertLess(V('3.4j'), V('1996.07.12')) |
| 339 | + self.assertLess(V('3.1.1.6'), V('3.2.pl0')) |
| 340 | + self.assertLess(V('2g6'), V('11g')) |
| 341 | + self.assertLess(V('0.9'), V('2.2')) |
| 342 | + self.assertLess(V('1.2'), V('1.2.1')) |
| 343 | + self.assertLess(V('1.1'), V('1.2.2')) |
| 344 | + self.assertLess(V('1.1'), V('1.2')) |
| 345 | + self.assertLess(V('1.2.1'), V('1.2.2')) |
| 346 | + self.assertLess(V('1.2'), V('1.2.2')) |
| 347 | + self.assertLess(V('0.4'), V('0.4.0')) |
| 348 | + self.assertLess(V('1.13++'), V('5.5.kw')) |
| 349 | + self.assertLess(V('0.960923'), V('2.2beta29')) |
| 350 | + |
| 351 | + |
| 352 | + def test_macos(self): |
| 353 | + self.addCleanup(self.clear_caches) |
| 354 | + |
| 355 | + uname = ('Darwin', 'hostname', '17.7.0', |
| 356 | + ('Darwin Kernel Version 17.7.0: ' |
| 357 | + 'Thu Jun 21 22:53:14 PDT 2018; ' |
| 358 | + 'root:xnu-4570.71.2~1/RELEASE_X86_64'), |
| 359 | + 'x86_64', 'i386') |
| 360 | + arch = ('64bit', '') |
| 361 | + with mock.patch.object(platform, 'uname', return_value=uname), \ |
| 362 | + mock.patch.object(platform, 'architecture', return_value=arch): |
| 363 | + for mac_ver, expected_terse, expected in [ |
| 364 | + # darwin: mac_ver() returns empty strings |
| 365 | + (('', '', ''), |
| 366 | + 'Darwin-17.7.0', |
| 367 | + 'Darwin-17.7.0-x86_64-i386-64bit'), |
| 368 | + # macOS: mac_ver() returns macOS version |
| 369 | + (('10.13.6', ('', '', ''), 'x86_64'), |
| 370 | + 'macOS-10.13.6', |
| 371 | + 'macOS-10.13.6-x86_64-i386-64bit'), |
| 372 | + ]: |
| 373 | + with mock.patch.object(platform, 'mac_ver', |
| 374 | + return_value=mac_ver): |
| 375 | + self.clear_caches() |
| 376 | + self.assertEqual(platform.platform(terse=1), expected_terse) |
| 377 | + self.assertEqual(platform.platform(), expected) |
| 378 | + |
| 379 | + |
| 380 | +if __name__ == '__main__': |
| 381 | + unittest.main() |
0 commit comments