From 0a4a70ea9d500f919b59d9187d949798792a7192 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 24 Jun 2026 22:45:17 +0200 Subject: [PATCH 1/3] gh-151929: Get boot identifier and uptime in test.pythoninfo --- Lib/test/pythoninfo.py | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 7f735d75b318e7f..566c224a86b60bf 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -1040,6 +1040,51 @@ def collect_libregrtest_utils(info_add): info_add('libregrtests.build_info', ' '.join(utils.get_build_info())) +def linux_get_uptime(): + # Use CLOCK_BOOTTIME if available + import time + try: + return time.clock_gettime(time.CLOCK_BOOTTIME) + except (AttributeError, OSError): + pass + + # Otherwise, parse the first member of /proc/uptime + try: + with open("/proc/uptime", encoding="utf-8") as fp: + line = fp.readline() + except OSError: + return + + try: + parts = line.split() + if not parts: + return + return float(parts[0]) + except ValueError: + return + + +def collect_linux(info_add): + try: + with open("/proc/sys/kernel/random/boot_id", encoding="utf-8") as fp: + boot_id = fp.readline().rstrip() + except OSError: + pass + else: + info_add('linux.boot_id', boot_id) + + uptime = linux_get_uptime() + if uptime is not None: + # truncate microseconds + uptime = int(uptime) + try: + import datetime + uptime = str(datetime.timedelta(seconds=uptime)) + except ImportError: + uptime = f'{uptime} sec' + info_add('linux.uptime', uptime) + + def collect_info(info): error = False info_add = info.add @@ -1081,6 +1126,7 @@ def collect_info(info): collect_zlib, collect_zstd, collect_libregrtest_utils, + collect_linux, # Collecting from tests should be last as they have side effects. collect_test_socket, From 5c3e84237c6a4f281974d2d5d0207dc02e137f03 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 24 Jun 2026 23:54:34 +0200 Subject: [PATCH 2/3] Read also /etc/machine-id --- Lib/test/pythoninfo.py | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 566c224a86b60bf..067e218f797364c 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -16,6 +16,15 @@ def normalize_text(text): return text.strip() +def read_first_line(filename): + # Get the first line of a text file and strip trailing spaces + try: + with open(filename, encoding="utf-8") as fp: + return fp.readline().rstrip() + except OSError: + return '' + + class PythonInfo: def __init__(self): self.info = {} @@ -1015,14 +1024,9 @@ def collect_fips(info_add): if _hashlib is not None: call_func(info_add, 'fips.openssl_fips_mode', _hashlib, 'get_fips_mode') - try: - with open("/proc/sys/crypto/fips_enabled", encoding="utf-8") as fp: - line = fp.readline().rstrip() - - if line: - info_add('fips.linux_crypto_fips_enabled', line) - except OSError: - pass + fips_enabled = read_first_line("/proc/sys/crypto/fips_enabled") + if fips_enabled: + info_add('fips.linux_crypto_fips_enabled', fips_enabled) def collect_tempfile(info_add): @@ -1049,14 +1053,11 @@ def linux_get_uptime(): pass # Otherwise, parse the first member of /proc/uptime - try: - with open("/proc/uptime", encoding="utf-8") as fp: - line = fp.readline() - except OSError: + uptime = read_first_line("/proc/uptime") + if not uptime: return - try: - parts = line.split() + parts = uptime.split() if not parts: return return float(parts[0]) @@ -1065,14 +1066,15 @@ def linux_get_uptime(): def collect_linux(info_add): - try: - with open("/proc/sys/kernel/random/boot_id", encoding="utf-8") as fp: - boot_id = fp.readline().rstrip() - except OSError: - pass - else: + boot_id = read_first_line("/proc/sys/kernel/random/boot_id") + if boot_id: info_add('linux.boot_id', boot_id) + # https://www.freedesktop.org/software/systemd/man/latest/machine-id.html + machine_id = read_first_line("/etc/machine-id") + if machine_id: + info_add('linux.machine_id', machine_id) + uptime = linux_get_uptime() if uptime is not None: # truncate microseconds From 30a21cbc2370e850ac95eaa7c0cb20ef63cc8e6d Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 24 Jun 2026 23:58:20 +0200 Subject: [PATCH 3/3] GHA: Run test.pythoninfo on the "Cross build Linux" job --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 43f8e0c010ed1bc..d9a956a6bf53038 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -601,6 +601,9 @@ jobs: run: ./configure --prefix="$BUILD_DIR/cross-python" --with-build-python="$BUILD_DIR/host-python/bin/python3" - name: Install cross Python run: make -j8 install + - name: Display build info + run: | + "$BUILD_DIR/cross-python/bin/python3" -m test.pythoninfo - name: Run test subset with host build run: | "$BUILD_DIR/cross-python/bin/python3" -m test test_sysconfig test_site test_embed