Skip to content

Commit 8b40dff

Browse files
committed
gh-152680: Check if running in a container/VM in test.pythoninfo
Log also the "CI" and "container" environment variable.
1 parent ecdef17 commit 8b40dff

1 file changed

Lines changed: 86 additions & 52 deletions

File tree

Lib/test/pythoninfo.py

Lines changed: 86 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010

1111
MS_WINDOWS = (sys.platform == "win32")
12+
COMMAND_TIMEOUT = 60.0
1213

1314

1415
def normalize_text(text):
@@ -293,6 +294,7 @@ def format_groups(groups):
293294
"BUILDPYTHON",
294295
"CC",
295296
"CFLAGS",
297+
"CI",
296298
"COLUMNS",
297299
"COMPUTERNAME",
298300
"COMSPEC",
@@ -355,6 +357,9 @@ def format_groups(groups):
355357
"_PYTHON_SYSCONFIGDATA_PATH",
356358
"__PYVENV_LAUNCHER__",
357359

360+
# Lower case variables
361+
"container",
362+
358363
# Sanitizer options
359364
"ASAN_OPTIONS",
360365
"LSAN_OPTIONS",
@@ -434,19 +439,40 @@ def format_attr(attr, value):
434439
info_add('readline.library', 'GNU readline')
435440

436441

437-
def collect_gdb(info_add):
442+
def run_command(cmd, **kwargs):
438443
import subprocess
444+
timeout = COMMAND_TIMEOUT
439445

440446
try:
441-
proc = subprocess.Popen(["gdb", "-nx", "--version"],
447+
proc = subprocess.Popen(cmd,
442448
stdout=subprocess.PIPE,
443-
stderr=subprocess.PIPE,
444-
universal_newlines=True)
445-
version = proc.communicate()[0]
449+
stderr=subprocess.DEVNULL,
450+
text=True,
451+
**kwargs)
452+
# ignore stderr
453+
with proc:
454+
try:
455+
stdout = proc.communicate(timeout=timeout)[0]
456+
except:
457+
proc.kill()
458+
proc.communicate()
459+
raise
460+
446461
if proc.returncode:
447-
# ignore gdb failure: test_gdb will log the error
448-
return
462+
return ''
463+
464+
# Strip trailing spaces and newlines
465+
return stdout.rstrip()
449466
except OSError:
467+
return ''
468+
except subprocess.TimeoutExpired:
469+
print(f"ERROR: Command {' '.join(cmd)}: timeout!")
470+
return ''
471+
472+
473+
def collect_gdb(info_add):
474+
version = run_command(["gdb", "-nx", "--version"])
475+
if not version:
450476
return
451477

452478
# Only keep the first line
@@ -847,7 +873,6 @@ def collect_support_threading_helper(info_add):
847873

848874

849875
def collect_cc(info_add):
850-
import subprocess
851876
import sysconfig
852877

853878
CC = sysconfig.get_config_var('CC')
@@ -860,19 +885,13 @@ def collect_cc(info_add):
860885
except ImportError:
861886
args = CC.split()
862887
args.append('--version')
863-
try:
864-
proc = subprocess.Popen(args,
865-
stdout=subprocess.PIPE,
866-
stderr=subprocess.STDOUT,
867-
universal_newlines=True)
868-
except OSError:
888+
889+
stdout = run_command(args)
890+
if not stdout:
869891
# Cannot run the compiler, for example when Python has been
870892
# cross-compiled and installed on the target platform where the
871893
# compiler is missing.
872-
return
873-
874-
stdout = proc.communicate()[0]
875-
if proc.returncode:
894+
#
876895
# CC --version failed: ignore error
877896
return
878897

@@ -978,21 +997,11 @@ def collect_windows(info_add):
978997
call_func(info_add, 'windows.oem_code_page', _winapi, 'GetOEMCP')
979998

980999
# windows.version_caption: "wmic os get Caption,Version /value" command
981-
import subprocess
982-
try:
983-
# When wmic.exe output is redirected to a pipe,
984-
# it uses the OEM code page
985-
proc = subprocess.Popen(["wmic", "os", "get", "Caption,Version", "/value"],
986-
stdout=subprocess.PIPE,
987-
stderr=subprocess.PIPE,
988-
encoding="oem",
989-
text=True)
990-
output, stderr = proc.communicate()
991-
if proc.returncode:
992-
output = ""
993-
except OSError:
994-
pass
995-
else:
1000+
output = run_command(["wmic", "os", "get", "Caption,Version", "/value"],
1001+
# When wmic.exe output is redirected to a pipe,
1002+
# it uses the OEM code page
1003+
encoding="oem")
1004+
if output:
9961005
for line in output.splitlines():
9971006
line = line.strip()
9981007
if line.startswith('Caption='):
@@ -1005,23 +1014,11 @@ def collect_windows(info_add):
10051014
info_add('windows.version', line)
10061015

10071016
# windows.ver: "ver" command
1008-
try:
1009-
proc = subprocess.Popen(["ver"], shell=True,
1010-
stdout=subprocess.PIPE,
1011-
stderr=subprocess.PIPE,
1012-
text=True)
1013-
output = proc.communicate()[0]
1014-
if proc.returncode == 0xc0000142:
1015-
return
1016-
if proc.returncode:
1017-
output = ""
1018-
except OSError:
1019-
return
1020-
else:
1021-
output = output.strip()
1022-
line = output.splitlines()[0]
1023-
if line:
1024-
info_add('windows.ver', line)
1017+
output = run_command(["ver"], shell=True)
1018+
if output:
1019+
first_line = output.splitlines()[0]
1020+
if first_line:
1021+
info_add('windows.ver', first_line)
10251022

10261023
# windows.developer_mode: get AllowDevelopmentWithoutDevLicense registry
10271024
value = winreg_query(r"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows"
@@ -1132,7 +1129,40 @@ def get_machine_id():
11321129
return None
11331130

11341131

1135-
def collect_linux(info_add):
1132+
def detect_virt():
1133+
# Run systemd-detect-virt command
1134+
virt = run_command(["systemd-detect-virt"])
1135+
if virt and virt != "none":
1136+
return virt
1137+
1138+
# Check if the process in running in a container
1139+
import os.path
1140+
if os.path.exists('/.dockerenv'):
1141+
return 'docker'
1142+
if os.path.exists('/run/.containerenv'):
1143+
return 'podman'
1144+
1145+
container = read_first_line('/run/systemd/container')
1146+
if container:
1147+
return container
1148+
1149+
# Other ways to check if running in a container:
1150+
# * Parse /proc/1/mounts or /proc/1/mountinfo (check "/" filesystem).
1151+
# * Parse /proc/1/cgroup.
1152+
# * Parse the first line of /proc/1/sched (check process name is different
1153+
# than "init" and "systemd").
1154+
# * Check / inode.
1155+
# * On systems using SELinux (Fedora/CentOS/RHEL), check for "container_t"
1156+
# label, for example of /proc/1/attr/current.
1157+
# * Check for "container" variable in /proc/1/environ
1158+
# (only root can read this file).
1159+
# * Check for "container" environment variable.
1160+
# * Set a specific env var when creating the container image.
1161+
# * Run virt-what, need to install the script, and must be run as root.
1162+
# * Check for "GITHUB_ACTIONS" environmant variable (GitHub Action).
1163+
1164+
1165+
def collect_system(info_add):
11361166
boot_id = read_first_line("/proc/sys/kernel/random/boot_id")
11371167
if boot_id:
11381168
info_add('system.boot_id', boot_id)
@@ -1152,6 +1182,10 @@ def collect_linux(info_add):
11521182
uptime = f'{uptime} sec'
11531183
info_add('system.uptime', uptime)
11541184

1185+
virt = detect_virt()
1186+
if virt:
1187+
info_add('system.virt', virt)
1188+
11551189

11561190
def collect_info(info):
11571191
error = False
@@ -1194,7 +1228,7 @@ def collect_info(info):
11941228
collect_zlib,
11951229
collect_zstd,
11961230
collect_libregrtest_utils,
1197-
collect_linux,
1231+
collect_system,
11981232

11991233
# Collecting from tests should be last as they have side effects.
12001234
collect_test_socket,

0 commit comments

Comments
 (0)