Files
libretro/tests/netguard/sitecustomize.py
T
Abdessamad Derraz 964812f977 chore: drop review tags from comments
Comments, docstrings and test texts ended on tags that name private review notes no clone holds. The tags go, the facts stay. Test methods named after a finding number are named after what they hold. test_prose_style refuses a tag that comes back. install.py changes in comments only: its pin and the launcher index follow.
2026-11-05 12:19:12 +01:00

136 lines
4.9 KiB
Python

"""The machine running the installer tests, kept out of them.
Python imports this module at start in every process a test starts with
tests/netguard on PYTHONPATH (enable), and the test module
calls guard() for its own process. Two things the host has never reach a
test: a connection off the loopback, which is refused (OSError, as a network
that is down) and written to RETROBIOS_TEST_NETLOG, and the cards and drives
mounted under /media, /run/media, /mnt and /Volumes, which read as folders
no drive is mounted on. A test module passed on a machine with no network
and no card, and failed with an AmberELEC card in the reader, while 261
connections to GitHub left the module in a run.
"""
from __future__ import annotations
import errno
import os
import socket
import sys
LOOPBACK = frozenset({"127.0.0.1", "::1", "localhost", "0.0.0.0", ""})
MOUNT_ROOTS = ("/media", "/run/media", "/mnt", "/Volumes")
NETLOG = "RETROBIOS_TEST_NETLOG"
# What guard() replaced, for unguard().
_SAVED: dict = {}
def _refuse(host: object) -> None:
where = os.environ.get(NETLOG)
# A name under .invalid never resolves (RFC 2606): a test names one to
# fail on purpose, refused here before its DNS query leaves.
if where and not str(host).endswith(".invalid"):
with open(where, "a", encoding="utf-8") as log:
log.write(f"{os.getpid()} {host} {' '.join(sys.argv)[:300]}\n")
raise OSError(errno.ENETUNREACH, f"a test makes no request to {host}")
def guard() -> None:
"""Refuse every connection off the loopback, hide the host's mounts."""
if _SAVED:
return
_SAVED.update(connect=socket.socket.__dict__.get("connect"),
connect_ex=socket.socket.__dict__.get("connect_ex"),
getaddrinfo=socket.getaddrinfo, ismount=os.path.ismount)
connect = socket.socket.connect
connect_ex = socket.socket.connect_ex
getaddrinfo = socket.getaddrinfo
def host_of(address: object) -> object:
return address[0] if isinstance(address, tuple) and address else None
def checked(sock: socket.socket, address: object) -> None:
if sock.family in (socket.AF_INET, socket.AF_INET6) and \
host_of(address) not in LOOPBACK:
_refuse(host_of(address))
def guarded_connect(self: socket.socket, address: object) -> None:
checked(self, address)
return connect(self, address)
def guarded_connect_ex(self: socket.socket, address: object) -> int:
checked(self, address)
return connect_ex(self, address)
def guarded_getaddrinfo(host: object, *args: object, **kwargs: object) -> list:
if host is not None and str(host) not in LOOPBACK:
_refuse(host)
return getaddrinfo(host, *args, **kwargs)
socket.socket.connect = guarded_connect # type: ignore[method-assign]
socket.socket.connect_ex = guarded_connect_ex # type: ignore[method-assign]
socket.getaddrinfo = guarded_getaddrinfo # type: ignore[assignment]
ismount = os.path.ismount
def guarded_ismount(path: object) -> bool:
text = os.fspath(path) if not isinstance(path, str) else path
if isinstance(text, bytes):
text = text.decode(errors="replace")
if any(text == root or text.startswith(root + "/") for root in MOUNT_ROOTS):
return False
return ismount(path)
os.path.ismount = guarded_ismount # type: ignore[assignment]
def unguard() -> None:
"""The process as it was before guard()."""
if not _SAVED:
return
for name in ("connect", "connect_ex"):
if _SAVED[name] is None:
delattr(socket.socket, name)
else:
setattr(socket.socket, name, _SAVED[name])
socket.getaddrinfo = _SAVED["getaddrinfo"]
os.path.ismount = _SAVED["ismount"]
_SAVED.clear()
def enable(log: str) -> dict:
"""Guard this process and every Python it starts, the refusals written
to log. Returns the variables it changed, for disable()."""
before = {name: os.environ.get(name) for name in ("PYTHONPATH", NETLOG)}
here = os.path.dirname(os.path.abspath(__file__))
paths = [here, *[p for p in os.environ.get("PYTHONPATH", "").split(os.pathsep) if p]]
os.environ["PYTHONPATH"] = os.pathsep.join(dict.fromkeys(paths))
os.environ[NETLOG] = log
with open(log, "w", encoding="utf-8"):
pass
guard()
return before
def disable(before: dict) -> None:
"""This process and the Python it starts as before enable()."""
unguard()
for name, value in before.items():
if value is None:
os.environ.pop(name, None)
else:
os.environ[name] = value
def refused(log: str) -> list[str]:
"""The connections refused since enable(log), one line each."""
try:
with open(log, encoding="utf-8") as text:
return [line.strip() for line in text if line.strip()]
except OSError:
return []
if os.environ.get(NETLOG):
guard()