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