summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorMalcolm Smith <smith@chaquo.com>2024-03-27 16:53:27 (GMT)
committerGitHub <noreply@github.com>2024-03-27 16:53:27 (GMT)
commit74c8568d07719529b874897598d8b3bc25ff0434 (patch)
tree43226bc05462471e4c05c7a160335aa3e752d2ea /Lib
parentce00de4c8cd39816f992e749c1074487d93abe9d (diff)
downloadcpython-74c8568d07719529b874897598d8b3bc25ff0434.zip
cpython-74c8568d07719529b874897598d8b3bc25ff0434.tar.gz
cpython-74c8568d07719529b874897598d8b3bc25ff0434.tar.bz2
gh-71042: Add `platform.android_ver` (#116674)
Diffstat (limited to 'Lib')
-rwxr-xr-xLib/platform.py46
-rw-r--r--Lib/test/pythoninfo.py3
-rw-r--r--Lib/test/support/__init__.py16
-rw-r--r--Lib/test/test_asyncio/test_base_events.py5
-rw-r--r--Lib/test/test_platform.py50
-rw-r--r--Lib/test/test_socket.py18
6 files changed, 123 insertions, 15 deletions
diff --git a/Lib/platform.py b/Lib/platform.py
index 2756f29..df1d987 100755
--- a/Lib/platform.py
+++ b/Lib/platform.py
@@ -542,6 +542,47 @@ def java_ver(release='', vendor='', vminfo=('', '', ''), osinfo=('', '', '')):
return release, vendor, vminfo, osinfo
+
+AndroidVer = collections.namedtuple(
+ "AndroidVer", "release api_level manufacturer model device is_emulator")
+
+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
+ 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):
+ # 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")
+
+ 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)
+ is_emulator = getprop("ro.kernel.qemu", "0") == "1"
+
+ return AndroidVer(
+ release, api_level, manufacturer, model, device, is_emulator)
+
+
### System name aliasing
def system_alias(system, release, version):
@@ -972,6 +1013,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 8143587..5612c55 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':
+ call_func(info_add, 'platform.android_ver', platform, 'android_ver')
+
def collect_locale(info_add):
import locale
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index a1c7987..3d78687 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -1801,18 +1801,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 # 100 us
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_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py
index 4cd872d..c14a0bb 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 @@ class BaseEventLoopWithSelectorTests(test_utils.TestCase):
self._test_create_connection_ip_addr(m_socket, False)
@patch_socket
+ @unittest.skipIf(
+ support.is_android and platform.android_ver().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
sock = m_socket.socket.return_value
diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py
index 9f8aeee..57f27b2 100644
--- a/Lib/test/test_platform.py
+++ b/Lib/test/test_platform.py
@@ -219,6 +219,19 @@ class PlatformTest(unittest.TestCase):
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):
@@ -458,6 +471,43 @@ class PlatformTest(unittest.TestCase):
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.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.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, "")
+ self.assertEqual(res.api_level, 0)
+ 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", 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 platform import _comparable_version as V
diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py
index a7e657f..661a859 100644
--- a/Lib/test/test_socket.py
+++ b/Lib/test/test_socket.py
@@ -209,7 +209,10 @@ HAVE_SOCKET_QIPCRTR = _have_socket_qipcrtr()
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()
@@ -1217,8 +1220,8 @@ class GeneralModuleTests(unittest.TestCase):
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 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)
# Try udp, but don't barf if it doesn't exist
@@ -1229,8 +1232,9 @@ class GeneralModuleTests(unittest.TestCase):
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 +1579,8 @@ class GeneralModuleTests(unittest.TestCase):
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)