1- import os
1+ import contextlib
22import copy
3+ import io
4+ import itertools
5+ import os
36import pickle
47import platform
58import subprocess
@@ -83,6 +86,38 @@ def clear_caches(self):
8386 platform ._uname_cache = None
8487 platform ._os_release_cache = None
8588
89+ def test_invalidate_caches (self ):
90+ self .clear_caches ()
91+
92+ self .assertDictEqual (platform ._platform_cache , {})
93+ self .assertDictEqual (platform ._sys_version_cache , {})
94+ self .assertIsNone (platform ._uname_cache )
95+ self .assertIsNone (platform ._os_release_cache )
96+
97+ # fill the cached entries (some have side effects on others)
98+ platform .platform () # for platform._platform_cache
99+ platform .python_implementation () # for platform._sys_version_cache
100+ platform .uname () # for platform._uname_cache
101+
102+ # check that the cache are filled
103+ self .assertNotEqual (platform ._platform_cache , {})
104+ self .assertNotEqual (platform ._sys_version_cache , {})
105+ self .assertIsNotNone (platform ._uname_cache )
106+
107+ try :
108+ platform .freedesktop_os_release ()
109+ except OSError :
110+ self .assertIsNone (platform ._os_release_cache )
111+ else :
112+ self .assertIsNotNone (platform ._os_release_cache )
113+
114+ with self .subTest ('clear platform caches' ):
115+ platform .invalidate_caches ()
116+ self .assertDictEqual (platform ._platform_cache , {})
117+ self .assertDictEqual (platform ._sys_version_cache , {})
118+ self .assertIsNone (platform ._uname_cache )
119+ self .assertIsNone (platform ._os_release_cache )
120+
86121 def test_architecture (self ):
87122 res = platform .architecture ()
88123
@@ -375,7 +410,7 @@ def test_win32_ver(self):
375410 for v in version .split ('.' ):
376411 int (v ) # should not fail
377412 if csd :
378- self .assertTrue (csd . startswith ( 'SP' ), msg = csd )
413+ self .assertStartsWith (csd , 'SP' )
379414 if ptype :
380415 if os .cpu_count () > 1 :
381416 self .assertIn ('Multiprocessor' , ptype )
@@ -490,8 +525,10 @@ def test_ios_ver(self):
490525 self .assertEqual (override .model , "Whiz" )
491526 self .assertTrue (override .is_simulator )
492527
493- @unittest .skipIf (support .is_emscripten , "Does not apply to Emscripten" )
494528 def test_libc_ver (self ):
529+ if support .is_emscripten :
530+ assert platform .libc_ver () == ("emscripten" , "4.0.12" )
531+ return
495532 # check that libc_ver(executable) doesn't raise an exception
496533 if os .path .isdir (sys .executable ) and \
497534 os .path .exists (sys .executable + '.exe' ):
@@ -519,6 +556,14 @@ def test_libc_ver(self):
519556 (b'GLIBC_2.9' , ('glibc' , '2.9' )),
520557 (b'libc.so.1.2.5' , ('libc' , '1.2.5' )),
521558 (b'libc_pthread.so.1.2.5' , ('libc' , '1.2.5_pthread' )),
559+ (b'/aports/main/musl/src/musl-1.2.5' , ('musl' , '1.2.5' )),
560+ # musl uses semver, but we accept some variations anyway:
561+ (b'/aports/main/musl/src/musl-12.5' , ('musl' , '12.5' )),
562+ (b'/aports/main/musl/src/musl-1.2.5.7' , ('musl' , '1.2.5.7' )),
563+ (b'libc.musl.so.1' , ('musl' , '1' )),
564+ (b'libc.musl-x86_64.so.1.2.5' , ('musl' , '1.2.5' )),
565+ (b'ld-musl.so.1' , ('musl' , '1' )),
566+ (b'ld-musl-x86_64.so.1.2.5' , ('musl' , '1.2.5' )),
522567 (b'' , ('' , '' )),
523568 ):
524569 with open (filename , 'wb' ) as fp :
@@ -530,14 +575,37 @@ def test_libc_ver(self):
530575 expected )
531576
532577 # binary containing multiple versions: get the most recent,
533- # make sure that 1.9 is seen as older than 1.23.4
534- chunksize = 16384
535- with open (filename , 'wb' ) as f :
536- # test match at chunk boundary
537- f .write (b'x' * (chunksize - 10 ))
538- f .write (b'GLIBC_1.23.4\0 GLIBC_1.9\0 GLIBC_1.21\0 ' )
539- self .assertEqual (platform .libc_ver (filename , chunksize = chunksize ),
540- ('glibc' , '1.23.4' ))
578+ # make sure that eg 1.9 is seen as older than 1.23.4, and that
579+ # the arguments don't count even if they are set.
580+ chunksize = 200
581+ for data , expected in (
582+ (b'GLIBC_1.23.4\0 GLIBC_1.9\0 GLIBC_1.21\0 ' , ('glibc' , '1.23.4' )),
583+ (b'libc.so.2.4\0 libc.so.9\0 libc.so.23.1\0 ' , ('libc' , '23.1' )),
584+ (b'musl-1.4.1\0 musl-2.1.1\0 musl-2.0.1\0 ' , ('musl' , '2.1.1' )),
585+ (
586+ b'libc.musl-x86_64.so.1.4.1\0 libc.musl-x86_64.so.2.1.1\0 libc.musl-x86_64.so.2.0.1' ,
587+ ('musl' , '2.1.1' ),
588+ ),
589+ (
590+ b'ld-musl-x86_64.so.1.4.1\0 ld-musl-x86_64.so.2.1.1\0 ld-musl-x86_64.so.2.0.1' ,
591+ ('musl' , '2.1.1' ),
592+ ),
593+ (b'no match here, so defaults are used' , ('test' , '100.1.0' )),
594+ ):
595+ with open (filename , 'wb' ) as f :
596+ # test match at chunk boundary
597+ f .write (b'x' * (chunksize - 10 ))
598+ f .write (data )
599+ self .assertEqual (
600+ expected ,
601+ platform .libc_ver (
602+ filename ,
603+ lib = 'test' ,
604+ version = '100.1.0' ,
605+ chunksize = chunksize ,
606+ ),
607+ )
608+
541609
542610 def test_android_ver (self ):
543611 res = platform .android_ver ()
@@ -690,5 +758,66 @@ def test_parse_os_release(self):
690758 self .assertEqual (len (info ["SPECIALS" ]), 5 )
691759
692760
761+ class CommandLineTest (unittest .TestCase ):
762+ def setUp (self ):
763+ platform .invalidate_caches ()
764+ self .addCleanup (platform .invalidate_caches )
765+
766+ def invoke_platform (self , * flags ):
767+ output = io .StringIO ()
768+ with contextlib .redirect_stdout (output ):
769+ platform ._main (args = flags )
770+ return output .getvalue ()
771+
772+ def test_unknown_flag (self ):
773+ with self .assertRaises (SystemExit ):
774+ output = io .StringIO ()
775+ # suppress argparse error message
776+ with contextlib .redirect_stderr (output ):
777+ _ = self .invoke_platform ('--unknown' )
778+ self .assertStartsWith (output , "usage: " )
779+
780+ def test_invocation (self ):
781+ flags = (
782+ "--terse" , "--nonaliased" , "terse" , "nonaliased"
783+ )
784+
785+ for r in range (len (flags ) + 1 ):
786+ for combination in itertools .combinations (flags , r ):
787+ self .invoke_platform (* combination )
788+
789+ def test_arg_parsing (self ):
790+ # For backwards compatibility, the `aliased` and `terse` parameters are
791+ # computed based on a combination of positional arguments and flags.
792+ #
793+ # Test that the arguments are correctly passed to the underlying
794+ # `platform.platform()` call.
795+ options = (
796+ (["--nonaliased" ], False , False ),
797+ (["nonaliased" ], False , False ),
798+ (["--terse" ], True , True ),
799+ (["terse" ], True , True ),
800+ (["nonaliased" , "terse" ], False , True ),
801+ (["--nonaliased" , "terse" ], False , True ),
802+ (["--terse" , "nonaliased" ], False , True ),
803+ )
804+
805+ for flags , aliased , terse in options :
806+ with self .subTest (flags = flags , aliased = aliased , terse = terse ):
807+ with mock .patch .object (platform , 'platform' ) as obj :
808+ self .invoke_platform (* flags )
809+ obj .assert_called_once_with (aliased , terse )
810+
811+ @support .force_not_colorized
812+ def test_help (self ):
813+ output = io .StringIO ()
814+
815+ with self .assertRaises (SystemExit ):
816+ with contextlib .redirect_stdout (output ):
817+ platform ._main (args = ["--help" ])
818+
819+ self .assertStartsWith (output .getvalue (), "usage:" )
820+
821+
693822if __name__ == '__main__' :
694823 unittest .main ()
0 commit comments