From ead6dcbd8eda802ca45d52c98d190927852b8078 Mon Sep 17 00:00:00 2001 From: Malcolm Smith Date: Tue, 12 Mar 2024 19:13:08 +0000 Subject: [PATCH 1/9] Add `platform.android_ver` --- Doc/library/platform.rst | 31 +++++++++++++ Doc/library/sys.rst | 4 +- Lib/platform.py | 44 ++++++++++++++++++ Lib/test/pythoninfo.py | 3 ++ Lib/test/test_asyncio/test_base_events.py | 5 +++ Lib/test/test_platform.py | 54 +++++++++++++++++++++++ Lib/test/test_socket.py | 13 +++--- 7 files changed, 147 insertions(+), 7 deletions(-) diff --git a/Doc/library/platform.rst b/Doc/library/platform.rst index 4bc3956449b930..f57533777723f5 100644 --- a/Doc/library/platform.rst +++ b/Doc/library/platform.rst @@ -301,3 +301,34 @@ Linux Platforms return ids .. versionadded:: 3.10 + + +Android Platform +---------------- + +.. function:: android_ver(release="", api_level=0, min_api_level=0, \ + manufacturer="", model="", device="") + + Get Android device information. Returns a :func:`~collections.namedtuple` + with the following attributes. Values which cannot be determined are set to + the defaults given as parameters. + + * ``release`` - Android version of the device, as a string (e.g. ``"14"``) + * ``api_level`` - API level of the device, as an integer (e.g. ``34``) + * ``min_api_level`` - Minimum API level this build of Python can run on, as + an integer (e.g. ``23``). + * ``manufacturer`` - `manufacturer + `__ + of the device, as a string (e.g. ``"Google"``) + * ``model`` - `model name + `__ of the + device, as a string (e.g. ``"Pixel 7"``) + * ``device`` - `device name + `__ of + the device, as a string (e.g. ``"panther"``) + + Which one of ``model`` and ``device`` is more likely to be unique, and which + one is more likely to resemble the marketing name, varies between different + manufacturers. + + .. versionadded:: 3.13 diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index 087a3454c33272..19d6856efe5d09 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -753,7 +753,9 @@ always available. .. function:: getandroidapilevel() - Return the build time API version of Android as an integer. + Return the build-time API level of Android as an integer. This represents the + minimum version of Android this build of Python can run on. For runtime + version information, see :func:`platform.android_ver`. .. availability:: Android. diff --git a/Lib/platform.py b/Lib/platform.py index 2756f298f9676f..0e1dc250134610 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -542,6 +542,45 @@ def java_ver(release='', vendor='', vminfo=('', '', ''), osinfo=('', '', '')): return release, vendor, vminfo, osinfo + +AndroidVer = collections.namedtuple( + "AndroidVer", + "release api_level min_api_level manufacturer model device") + +def android_ver(release="", api_level=0, min_api_level=0, + manufacturer="", model="", device=""): + if sys.platform == "android": + min_api_level = sys.getandroidapilevel() + try: + from ctypes import CDLL, c_char_p, create_string_buffer + except ImportError: + pass + else: + # An NDK developer confirmed that this is an officially-supported + # API (https://stackoverflow.com/a/28416743). Use `getattr` to avoid + # private name mangling. + system_property_get = getattr(CDLL("libc.so"), "__system_property_get") + system_property_get.argtypes = (c_char_p, c_char_p) + + def getprop(name, default): + PROP_VALUE_MAX = 92 # From sys/system_properties.h + buffer = create_string_buffer(PROP_VALUE_MAX) + length = system_property_get(name.encode("UTF-8"), buffer) + if length == 0: + return default + else: + return buffer.value.decode("UTF-8", "backslashreplace") + + release = getprop("ro.build.version.release", release) + api_level = int(getprop("ro.build.version.sdk", api_level)) + manufacturer = getprop("ro.product.manufacturer", manufacturer) + model = getprop("ro.product.model", model) + device = getprop("ro.product.device", device) + + return AndroidVer( + release, api_level, min_api_level, manufacturer, model, device) + + ### System name aliasing def system_alias(system, release, version): @@ -972,6 +1011,11 @@ def uname(): system = 'Windows' release = 'Vista' + # On Android, return the name and version of the OS rather than the kernel. + if sys.platform == 'android': + system = 'Android' + release = android_ver().release + vals = system, node, release, version, machine # Replace 'unknown' values with the more portable '' _uname_cache = uname_result(*map(_unknown_as_blank, vals)) diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 814358746d6d8a..406630b8edbcca 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -179,6 +179,9 @@ def collect_platform(info_add): info_add(f'platform.freedesktop_os_release[{key}]', os_release[key]) + if sys.platform == 'android': + info_add('platform.android_ver', platform.android_ver()) + def collect_locale(info_add): import locale diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index 4cd872d3a5b2d8..66717bc7bb9ff0 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -3,6 +3,7 @@ import concurrent.futures import errno import math +import platform import socket import sys import threading @@ -1430,6 +1431,10 @@ def test_create_connection_no_inet_pton(self, m_socket): self._test_create_connection_ip_addr(m_socket, False) @patch_socket + @unittest.skipIf( + support.is_android and platform.android_ver().api_level < 23, + "Issue #26936: this fails on Android before API level 23" + ) def test_create_connection_service_name(self, m_socket): m_socket.getaddrinfo = socket.getaddrinfo sock = m_socket.socket.return_value diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index bbd0c06efed2c9..da34053e4b18d3 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -219,6 +219,19 @@ def test_uname(self): self.assertEqual(res[-1], res.processor) self.assertEqual(len(res), 6) + if os.name == "posix": + uname = os.uname() + self.assertEqual(res.node, uname.nodename) + self.assertEqual(res.version, uname.version) + self.assertEqual(res.machine, uname.machine) + + if sys.platform == "android": + self.assertEqual(res.system, "Android") + self.assertEqual(res.release, platform.android_ver().release) + else: + self.assertEqual(res.system, uname.sysname) + self.assertEqual(res.release, uname.release) + @unittest.skipUnless(sys.platform.startswith('win'), "windows only test") def test_uname_win32_without_wmi(self): def raises_oserror(*a): @@ -430,6 +443,47 @@ def test_libc_ver(self): self.assertEqual(platform.libc_ver(filename, chunksize=chunksize), ('glibc', '1.23.4')) + def test_android_ver(self): + res = platform.android_ver() + self.assertIsInstance(res, tuple) + self.assertEqual(res, (res.release, res.api_level, res.min_api_level, + res.manufacturer, res.model, res.device)) + + if sys.platform == "android": + for name in ["release", "manufacturer", "model", "device"]: + with self.subTest(name): + value = getattr(res, name) + self.assertIsInstance(value, str) + self.assertGreater(len(value), 0) + + for name in ["api_level", "min_api_level"]: + with self.subTest(name): + value = getattr(res, name) + self.assertIsInstance(value, int) + self.assertGreater(value, 0) + + self.assertEqual(res.min_api_level, sys.getandroidapilevel()) + self.assertGreaterEqual(res.api_level, res.min_api_level) + + # When not running on Android, it should return the default values. + else: + self.assertEqual(res.release, "") + self.assertEqual(res.api_level, 0) + self.assertEqual(res.min_api_level, 0) + self.assertEqual(res.manufacturer, "") + self.assertEqual(res.model, "") + self.assertEqual(res.device, "") + + # Default values may also be overridden using parameters. + res = platform.android_ver("alpha", 1, 2, "bravo", "charlie", "delta") + self.assertEqual(res.release, "alpha") + self.assertEqual(res.api_level, 1) + self.assertEqual(res.min_api_level, 2) + self.assertEqual(res.manufacturer, "bravo") + self.assertEqual(res.model, "charlie") + self.assertEqual(res.device, "delta") + + @support.cpython_only def test__comparable_version(self): from platform import _comparable_version as V diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index a7e657f5718524..94bdb33b7e2afc 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -1217,8 +1217,8 @@ def testGetServBy(self): else: raise OSError # Try same call with optional protocol omitted - # Issue #26936: Android getservbyname() was broken before API 23. - if (not support.is_android) or sys.getandroidapilevel() >= 23: + # Issue #26936: this fails on Android before API level 23. + if not (support.is_android and platform.android_ver().api_level < 23): port2 = socket.getservbyname(service) eq(port, port2) # Try udp, but don't barf if it doesn't exist @@ -1229,8 +1229,9 @@ def testGetServBy(self): else: eq(udpport, port) # Now make sure the lookup by port returns the same service name - # Issue #26936: Android getservbyport() is broken. - if not support.is_android: + # Issue #26936: when the protocol is omitted, this fails on Android + # before API level 28. + if not (support.is_android and platform.android_ver().api_level < 28): eq(socket.getservbyport(port2), service) eq(socket.getservbyport(port, 'tcp'), service) if udpport is not None: @@ -1575,8 +1576,8 @@ def testGetaddrinfo(self): socket.getaddrinfo('::1', 80) # port can be a string service name such as "http", a numeric # port number or None - # Issue #26936: Android getaddrinfo() was broken before API level 23. - if (not support.is_android) or sys.getandroidapilevel() >= 23: + # Issue #26936: this fails on Android before API level 23. + if not (support.is_android and platform.android_ver().api_level < 23): socket.getaddrinfo(HOST, "http") socket.getaddrinfo(HOST, 80) socket.getaddrinfo(HOST, None) From 8934a4e1de6f6893660e8ffa8cca6fd24c967938 Mon Sep 17 00:00:00 2001 From: Malcolm Smith Date: Tue, 12 Mar 2024 20:09:47 +0000 Subject: [PATCH 2/9] Documentation fixes --- Doc/library/platform.rst | 38 ++++++++++++------- ...4-03-12-19-32-17.gh-issue-71042.oI0Ron.rst | 2 + 2 files changed, 27 insertions(+), 13 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2024-03-12-19-32-17.gh-issue-71042.oI0Ron.rst diff --git a/Doc/library/platform.rst b/Doc/library/platform.rst index f57533777723f5..fa9c915a2218ac 100644 --- a/Doc/library/platform.rst +++ b/Doc/library/platform.rst @@ -142,12 +142,19 @@ Cross Platform Returns the system's release, e.g. ``'2.2.0'`` or ``'NT'``. An empty string is returned if the value cannot be determined. + .. versionchanged:: 3.13 + On Android, this now returns the Android version rather than the Linux + kernel version. + .. function:: system() Returns the system/OS name, such as ``'Linux'``, ``'Darwin'``, ``'Java'``, ``'Windows'``. An empty string is returned if the value cannot be determined. + .. versionchanged:: 3.13 + On Android, this now returns ``'Android'`` rather than ``'Linux'``. + .. function:: system_alias(system, release, version) @@ -313,22 +320,27 @@ Android Platform with the following attributes. Values which cannot be determined are set to the defaults given as parameters. - * ``release`` - Android version of the device, as a string (e.g. ``"14"``) - * ``api_level`` - API level of the device, as an integer (e.g. ``34``) + * ``release`` - Android version, as a string (e.g. ``"14"``) + + * ``api_level`` - API level, as an integer (e.g. ``34``) + * ``min_api_level`` - Minimum API level this build of Python can run on, as - an integer (e.g. ``23``). - * ``manufacturer`` - `manufacturer - `__ - of the device, as a string (e.g. ``"Google"``) + an integer (e.g. ``21``) + + * ``manufacturer`` - `manufacturer name + `__ + (e.g. ``"Google"``) + * ``model`` - `model name - `__ of the - device, as a string (e.g. ``"Pixel 7"``) + `__ + (e.g. ``"Pixel 7"``) + * ``device`` - `device name - `__ of - the device, as a string (e.g. ``"panther"``) + `__ + (e.g. ``"panther"``) - Which one of ``model`` and ``device`` is more likely to be unique, and which - one is more likely to resemble the marketing name, varies between different - manufacturers. + Which one of ``model`` and ``device`` is more likely to distinguish different + device variants, and which one is more likely to resemble the marketing name, + varies between different manufacturers. .. versionadded:: 3.13 diff --git a/Misc/NEWS.d/next/Library/2024-03-12-19-32-17.gh-issue-71042.oI0Ron.rst b/Misc/NEWS.d/next/Library/2024-03-12-19-32-17.gh-issue-71042.oI0Ron.rst new file mode 100644 index 00000000000000..3641cbb9b2fc1a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2024-03-12-19-32-17.gh-issue-71042.oI0Ron.rst @@ -0,0 +1,2 @@ +Add :func:`platform.android_ver`, which provides device and OS information +on Android. From 4dca7c439c3c8c6c66e9b11cf5b866030aa6ff04 Mon Sep 17 00:00:00 2001 From: Malcolm Smith Date: Wed, 13 Mar 2024 14:13:20 +0000 Subject: [PATCH 3/9] Skip UDPLITE tests before API level 29 --- Lib/test/test_socket.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 94bdb33b7e2afc..4ae4638910596f 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -209,7 +209,10 @@ def socket_setdefaulttimeout(timeout): HAVE_SOCKET_VSOCK = _have_socket_vsock() -HAVE_SOCKET_UDPLITE = hasattr(socket, "IPPROTO_UDPLITE") +# Older Android versions block UDPLITE with SELinux. +HAVE_SOCKET_UDPLITE = ( + hasattr(socket, "IPPROTO_UDPLITE") + and not (support.is_android and platform.android_ver().api_level < 29)) HAVE_SOCKET_BLUETOOTH = _have_socket_bluetooth() From 4108dfc6a6236dd124b626b2a11c2cbd90984ac2 Mon Sep 17 00:00:00 2001 From: Malcolm Smith Date: Mon, 25 Mar 2024 18:02:52 +0000 Subject: [PATCH 4/9] Clarify model and device name documentation --- Doc/library/platform.rst | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/Doc/library/platform.rst b/Doc/library/platform.rst index fa9c915a2218ac..fdda19859a6bd6 100644 --- a/Doc/library/platform.rst +++ b/Doc/library/platform.rst @@ -142,19 +142,12 @@ Cross Platform Returns the system's release, e.g. ``'2.2.0'`` or ``'NT'``. An empty string is returned if the value cannot be determined. - .. versionchanged:: 3.13 - On Android, this now returns the Android version rather than the Linux - kernel version. - .. function:: system() Returns the system/OS name, such as ``'Linux'``, ``'Darwin'``, ``'Java'``, ``'Windows'``. An empty string is returned if the value cannot be determined. - .. versionchanged:: 3.13 - On Android, this now returns ``'Android'`` rather than ``'Linux'``. - .. function:: system_alias(system, release, version) @@ -329,18 +322,16 @@ Android Platform * ``manufacturer`` - `manufacturer name `__ - (e.g. ``"Google"``) * ``model`` - `model name - `__ - (e.g. ``"Pixel 7"``) + `__ – + typically the marketing name or model number * ``device`` - `device name - `__ - (e.g. ``"panther"``) + `__ – + typically the model number or a codename - Which one of ``model`` and ``device`` is more likely to distinguish different - device variants, and which one is more likely to resemble the marketing name, - varies between different manufacturers. + For a list of known model and device names, see `here + `__. .. versionadded:: 3.13 From 646441bb531d10d232062e5e9f4dab3445783126 Mon Sep 17 00:00:00 2001 From: Malcolm Smith Date: Mon, 25 Mar 2024 18:26:26 +0000 Subject: [PATCH 5/9] Remove min_api_level field --- Doc/library/platform.rst | 5 +---- Lib/platform.py | 10 +++------- Lib/test/test_platform.py | 16 ++++------------ 3 files changed, 8 insertions(+), 23 deletions(-) diff --git a/Doc/library/platform.rst b/Doc/library/platform.rst index fdda19859a6bd6..ffd46bd2df3524 100644 --- a/Doc/library/platform.rst +++ b/Doc/library/platform.rst @@ -306,7 +306,7 @@ Linux Platforms Android Platform ---------------- -.. function:: android_ver(release="", api_level=0, min_api_level=0, \ +.. function:: android_ver(release="", api_level=0, \ manufacturer="", model="", device="") Get Android device information. Returns a :func:`~collections.namedtuple` @@ -317,9 +317,6 @@ Android Platform * ``api_level`` - API level, as an integer (e.g. ``34``) - * ``min_api_level`` - Minimum API level this build of Python can run on, as - an integer (e.g. ``21``) - * ``manufacturer`` - `manufacturer name `__ diff --git a/Lib/platform.py b/Lib/platform.py index 0e1dc250134610..9b124a1f9cc75d 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -544,13 +544,10 @@ def java_ver(release='', vendor='', vminfo=('', '', ''), osinfo=('', '', '')): AndroidVer = collections.namedtuple( - "AndroidVer", - "release api_level min_api_level manufacturer model device") + "AndroidVer", "release api_level manufacturer model device") -def android_ver(release="", api_level=0, min_api_level=0, - manufacturer="", model="", device=""): +def android_ver(release="", api_level=0, manufacturer="", model="", device=""): if sys.platform == "android": - min_api_level = sys.getandroidapilevel() try: from ctypes import CDLL, c_char_p, create_string_buffer except ImportError: @@ -577,8 +574,7 @@ def getprop(name, default): model = getprop("ro.product.model", model) device = getprop("ro.product.device", device) - return AndroidVer( - release, api_level, min_api_level, manufacturer, model, device) + return AndroidVer(release, api_level, manufacturer, model, device) ### System name aliasing diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index da34053e4b18d3..839330aedabbe7 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -446,7 +446,7 @@ def test_libc_ver(self): def test_android_ver(self): res = platform.android_ver() self.assertIsInstance(res, tuple) - self.assertEqual(res, (res.release, res.api_level, res.min_api_level, + self.assertEqual(res, (res.release, res.api_level, res.manufacturer, res.model, res.device)) if sys.platform == "android": @@ -456,29 +456,21 @@ def test_android_ver(self): self.assertIsInstance(value, str) self.assertGreater(len(value), 0) - for name in ["api_level", "min_api_level"]: - with self.subTest(name): - value = getattr(res, name) - self.assertIsInstance(value, int) - self.assertGreater(value, 0) - - self.assertEqual(res.min_api_level, sys.getandroidapilevel()) - self.assertGreaterEqual(res.api_level, res.min_api_level) + self.assertIsInstance(res.api_level, int) + self.assertGreaterEqual(res.api_level, sys.getandroidapilevel()) # When not running on Android, it should return the default values. else: self.assertEqual(res.release, "") self.assertEqual(res.api_level, 0) - self.assertEqual(res.min_api_level, 0) self.assertEqual(res.manufacturer, "") self.assertEqual(res.model, "") self.assertEqual(res.device, "") # Default values may also be overridden using parameters. - res = platform.android_ver("alpha", 1, 2, "bravo", "charlie", "delta") + res = platform.android_ver("alpha", 1, "bravo", "charlie", "delta") self.assertEqual(res.release, "alpha") self.assertEqual(res.api_level, 1) - self.assertEqual(res.min_api_level, 2) self.assertEqual(res.manufacturer, "bravo") self.assertEqual(res.model, "charlie") self.assertEqual(res.device, "delta") From 4f65e8e620f4d5e95269ee100f991827f886c9d4 Mon Sep 17 00:00:00 2001 From: Malcolm Smith Date: Tue, 26 Mar 2024 18:00:37 +0000 Subject: [PATCH 6/9] Add is_emulator field --- Doc/library/platform.rst | 7 +++++-- Lib/platform.py | 14 ++++++++++---- Lib/test/support/__init__.py | 16 ++++++++-------- Lib/test/test_platform.py | 14 +++++++++----- 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/Doc/library/platform.rst b/Doc/library/platform.rst index ffd46bd2df3524..b2e8c767549971 100644 --- a/Doc/library/platform.rst +++ b/Doc/library/platform.rst @@ -306,8 +306,8 @@ Linux Platforms Android Platform ---------------- -.. function:: android_ver(release="", api_level=0, \ - manufacturer="", model="", device="") +.. function:: android_ver(release="", api_level=0, manufacturer="", \ + model="", device="", is_emulator=False) Get Android device information. Returns a :func:`~collections.namedtuple` with the following attributes. Values which cannot be determined are set to @@ -328,6 +328,9 @@ Android Platform `__ – typically the model number or a codename + * ``is_emulator`` - ``True`` if the device is an emulator; ``False`` if it's + a physical device + For a list of known model and device names, see `here `__. diff --git a/Lib/platform.py b/Lib/platform.py index 9b124a1f9cc75d..df1d987036455f 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -544,9 +544,10 @@ def java_ver(release='', vendor='', vminfo=('', '', ''), osinfo=('', '', '')): AndroidVer = collections.namedtuple( - "AndroidVer", "release api_level manufacturer model device") + "AndroidVer", "release api_level manufacturer model device is_emulator") -def android_ver(release="", api_level=0, manufacturer="", model="", device=""): +def android_ver(release="", api_level=0, manufacturer="", model="", device="", + is_emulator=False): if sys.platform == "android": try: from ctypes import CDLL, c_char_p, create_string_buffer @@ -560,10 +561,13 @@ def android_ver(release="", api_level=0, manufacturer="", model="", device=""): system_property_get.argtypes = (c_char_p, c_char_p) def getprop(name, default): - PROP_VALUE_MAX = 92 # From sys/system_properties.h + # https://android.googlesource.com/platform/bionic/+/refs/tags/android-5.0.0_r1/libc/include/sys/system_properties.h#39 + PROP_VALUE_MAX = 92 buffer = create_string_buffer(PROP_VALUE_MAX) length = system_property_get(name.encode("UTF-8"), buffer) if length == 0: + # This API doesn’t distinguish between an empty property and + # a missing one. return default else: return buffer.value.decode("UTF-8", "backslashreplace") @@ -573,8 +577,10 @@ def getprop(name, default): manufacturer = getprop("ro.product.manufacturer", manufacturer) model = getprop("ro.product.model", model) device = getprop("ro.product.device", device) + is_emulator = getprop("ro.kernel.qemu", "0") == "1" - return AndroidVer(release, api_level, manufacturer, model, device) + return AndroidVer( + release, api_level, manufacturer, model, device, is_emulator) ### System name aliasing diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index af43446c26120e..22292403cf2753 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -1791,18 +1791,18 @@ def missing_compiler_executable(cmd_names=[]): return cmd[0] -_is_android_emulator = None +_old_android_emulator = None def setswitchinterval(interval): # Setting a very low gil interval on the Android emulator causes python # to hang (issue #26939). - minimum_interval = 1e-5 + minimum_interval = 1e-4 if is_android and interval < minimum_interval: - global _is_android_emulator - if _is_android_emulator is None: - import subprocess - _is_android_emulator = (subprocess.check_output( - ['getprop', 'ro.kernel.qemu']).strip() == b'1') - if _is_android_emulator: + global _old_android_emulator + if _old_android_emulator is None: + import platform + av = platform.android_ver() + _old_android_emulator = av.is_emulator and av.api_level < 24 + if _old_android_emulator: interval = minimum_interval return sys.setswitchinterval(interval) diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index 839330aedabbe7..80805fcc7ab200 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -446,19 +446,21 @@ def test_libc_ver(self): def test_android_ver(self): res = platform.android_ver() self.assertIsInstance(res, tuple) - self.assertEqual(res, (res.release, res.api_level, - res.manufacturer, res.model, res.device)) + self.assertEqual(res, (res.release, res.api_level, res.manufacturer, + res.model, res.device, res.is_emulator)) if sys.platform == "android": for name in ["release", "manufacturer", "model", "device"]: with self.subTest(name): value = getattr(res, name) self.assertIsInstance(value, str) - self.assertGreater(len(value), 0) + self.assertNotEqual(value, "") self.assertIsInstance(res.api_level, int) self.assertGreaterEqual(res.api_level, sys.getandroidapilevel()) + self.assertIsInstance(res.is_emulator, bool) + # When not running on Android, it should return the default values. else: self.assertEqual(res.release, "") @@ -466,15 +468,17 @@ def test_android_ver(self): self.assertEqual(res.manufacturer, "") self.assertEqual(res.model, "") self.assertEqual(res.device, "") + self.assertEqual(res.is_emulator, False) # Default values may also be overridden using parameters. - res = platform.android_ver("alpha", 1, "bravo", "charlie", "delta") + res = platform.android_ver( + "alpha", 1, "bravo", "charlie", "delta", True) self.assertEqual(res.release, "alpha") self.assertEqual(res.api_level, 1) self.assertEqual(res.manufacturer, "bravo") self.assertEqual(res.model, "charlie") self.assertEqual(res.device, "delta") - + self.assertEqual(res.is_emulator, True) @support.cpython_only def test__comparable_version(self): From 9fd7f4488f8623589f69b702c7c3c1b62fa6ef83 Mon Sep 17 00:00:00 2001 From: Malcolm Smith Date: Tue, 26 Mar 2024 18:31:50 +0000 Subject: [PATCH 7/9] Apply suggestions from code review Co-authored-by: Victor Stinner --- Lib/test/pythoninfo.py | 2 +- Lib/test/test_asyncio/test_base_events.py | 2 +- Lib/test/test_socket.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 406630b8edbcca..5612c55746a516 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -180,7 +180,7 @@ def collect_platform(info_add): os_release[key]) if sys.platform == 'android': - info_add('platform.android_ver', platform.android_ver()) + call_func(info_add, 'platform.android_ver', platform, 'android_ver') def collect_locale(info_add): diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index 66717bc7bb9ff0..c14a0bb180d79b 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -1433,7 +1433,7 @@ def test_create_connection_no_inet_pton(self, m_socket): @patch_socket @unittest.skipIf( support.is_android and platform.android_ver().api_level < 23, - "Issue #26936: this fails on Android before API level 23" + "Issue gh-71123: this fails on Android before API level 23" ) def test_create_connection_service_name(self, m_socket): m_socket.getaddrinfo = socket.getaddrinfo diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index 4ae4638910596f..661a859b0d0601 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -1220,7 +1220,7 @@ def testGetServBy(self): else: raise OSError # Try same call with optional protocol omitted - # Issue #26936: this fails on Android before API level 23. + # Issue gh-71123: this fails on Android before API level 23. if not (support.is_android and platform.android_ver().api_level < 23): port2 = socket.getservbyname(service) eq(port, port2) From b64f1f5e34b55995c83d442ff9d2946e9abccdb3 Mon Sep 17 00:00:00 2001 From: Malcolm Smith Date: Wed, 27 Mar 2024 15:44:04 +0000 Subject: [PATCH 8/9] Apply suggestions from code review Co-authored-by: Victor Stinner --- Doc/library/platform.rst | 8 ++++---- Lib/test/support/__init__.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/library/platform.rst b/Doc/library/platform.rst index b2e8c767549971..3f02722dd11028 100644 --- a/Doc/library/platform.rst +++ b/Doc/library/platform.rst @@ -317,21 +317,21 @@ Android Platform * ``api_level`` - API level, as an integer (e.g. ``34``) - * ``manufacturer`` - `manufacturer name + * ``manufacturer`` - `Manufacturer name `__ - * ``model`` - `model name + * ``model`` - `Model name `__ – typically the marketing name or model number - * ``device`` - `device name + * ``device`` - `Device name `__ – typically the model number or a codename * ``is_emulator`` - ``True`` if the device is an emulator; ``False`` if it's a physical device - For a list of known model and device names, see `here + See the `list of known model and device names `__. .. versionadded:: 3.13 diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 22292403cf2753..aafa9a2f246b5f 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -1795,7 +1795,7 @@ def missing_compiler_executable(cmd_names=[]): def setswitchinterval(interval): # Setting a very low gil interval on the Android emulator causes python # to hang (issue #26939). - minimum_interval = 1e-4 + minimum_interval = 1e-4 # 100 us if is_android and interval < minimum_interval: global _old_android_emulator if _old_android_emulator is None: From 53e6511a562b00d6233c402c1814111dab10fe2a Mon Sep 17 00:00:00 2001 From: Malcolm Smith Date: Wed, 27 Mar 2024 15:58:03 +0000 Subject: [PATCH 9/9] Clarify distinction between platform.android_ver().api_level and sys.getandroidapilevel() --- Doc/library/platform.rst | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Doc/library/platform.rst b/Doc/library/platform.rst index 3f02722dd11028..6af9168d15749f 100644 --- a/Doc/library/platform.rst +++ b/Doc/library/platform.rst @@ -313,25 +313,27 @@ Android Platform with the following attributes. Values which cannot be determined are set to the defaults given as parameters. - * ``release`` - Android version, as a string (e.g. ``"14"``) + * ``release`` - Android version, as a string (e.g. ``"14"``). - * ``api_level`` - API level, as an integer (e.g. ``34``) + * ``api_level`` - API level of the running device, as an integer (e.g. ``34`` + for Android 14). To get the API level which Python was built against, see + :func:`sys.getandroidapilevel`. * ``manufacturer`` - `Manufacturer name - `__ + `__. * ``model`` - `Model name `__ – - typically the marketing name or model number + typically the marketing name or model number. * ``device`` - `Device name `__ – - typically the model number or a codename + typically the model number or a codename. * ``is_emulator`` - ``True`` if the device is an emulator; ``False`` if it's - a physical device + a physical device. - See the `list of known model and device names + Google maintains a `list of known model and device names `__. .. versionadded:: 3.13