ZaStoGram_desktop/Telegram/SourceFiles/tests/test_proxy_diagnostics.py

264 lines
10 KiB
Python

import re
from pathlib import Path
SOURCE_DIR = Path(__file__).resolve().parents[1]
ROOT = SOURCE_DIR.parents[1]
CMAKE = ROOT / "Telegram" / "CMakeLists.txt"
LANG = SOURCE_DIR.parent / "Resources" / "langs" / "lang.strings"
PROXY_DIR = SOURCE_DIR / "mtproto" / "proxy"
DIAGNOSTICS_H = PROXY_DIR / "diagnostics.h"
DIAGNOSTICS_CPP = PROXY_DIR / "diagnostics.cpp"
CONTROL_H = PROXY_DIR / "control_plane.h"
CONTROL_CPP = PROXY_DIR / "control_plane.cpp"
RUNTIME_CPP = SOURCE_DIR / "mtproto" / "runtime" / "runtime_environment.cpp"
INSTANCE_CPP = SOURCE_DIR / "mtproto" / "instance" / "mtp_instance.cpp"
ABSTRACT_CONNECTION_CPP = SOURCE_DIR / "mtproto" / "transport" / "connection_abstract.cpp"
ABSTRACT_SOCKET_CPP = SOURCE_DIR / "mtproto" / "transport" / "details" / "mtproto_abstract_socket.cpp"
RESOLVING_CPP = PROXY_DIR / "resolving_connection.cpp"
TCP_CPP = SOURCE_DIR / "mtproto" / "transport" / "connection_tcp.cpp"
HTTP_CPP = SOURCE_DIR / "mtproto" / "transport" / "connection_http.cpp"
PROXY_CHECK_CPP = PROXY_DIR / "check.cpp"
CONNECTION_BOX_CPP = SOURCE_DIR / "boxes" / "connection_box.cpp"
CONNECTION_BOX_H = SOURCE_DIR / "boxes" / "connection_box.h"
SETTINGS_MAIN_CPP = SOURCE_DIR / "settings" / "sections" / "settings_main.cpp"
def read(path):
assert path.exists(), f"missing expected source file: {path}"
return path.read_text(encoding="utf-8")
def proxy_event_report_fields():
header = read(DIAGNOSTICS_H)
body = header.split("struct ProxyEventReport {", 1)[1].split("};", 1)[0]
fields = []
for statement in body.split(";"):
declaration = statement.split("=", 1)[0].strip()
if not declaration:
continue
fields.append(declaration.rsplit(None, 1)[1])
return fields
def test_diagnostics_model_is_disk_only():
cmake = read(CMAKE)
header = read(DIAGNOSTICS_H)
source = read(DIAGNOSTICS_CPP)
runtime = read(RUNTIME_CPP)
assert "mtproto/proxy/diagnostics.cpp" in cmake
assert "mtproto/proxy/diagnostics.h" in cmake
assert "enum class ProxyDiagnosticsSource" in header
assert "enum class ProxyDiagnosticsPhase" in header
assert "struct ProxyDiagnosticsEvent" in header
assert "ProxyMtproxyTerminalReason mtproxyReason" in header
assert "ProxyConnectionAttempt attempt" in header
assert "WriteProxyDiagnosticsLine(" in header
assert "Logs::writeMtproxy(FormatProxyDiagnosticsEvent(event));" in runtime
assert "ProxyDiagnosticsEventsValue" not in header
assert "ProxyDiagnosticsSnapshot" not in header
assert "LoadProxyDiagnosticsTail(" not in header
assert "AddProxyDiagnosticsEvent" not in header
assert "rpl::variable" not in source
assert "Events.current()" not in source
def test_diagnostics_has_no_in_memory_event_stream():
source = read(DIAGNOSTICS_CPP)
assert "Events.force_assign" not in source
assert "Events.value()" not in source
assert "std::vector<ProxyDiagnosticsEvent>" not in source
def test_diagnostics_does_not_read_back_log_files():
source = read(DIAGNOSTICS_CPP)
assert "TailLines(" not in source
assert "SourceFromFileName(" not in source
assert "SeverityFromLine(" not in source
assert "removeFirst()" not in source
assert "result.erase(begin(result));" not in source
assert "result = result.mid(result.size() - maxLines);" not in source
assert "result.erase(result.begin(), result.end() - maxLines);" not in source
def test_diagnostics_redacts_secret_material():
source = read(DIAGNOSTICS_CPP)
assert "RedactProxyData" in source
assert "RedactMessage" in source
assert "password = u\"<redacted>\"_q" in source
assert "secret = u\"<redacted>\"_q" in source
assert "proxy.password" in source
assert "secret|password|pass" in source
assert "<redacted>" in source
def test_transport_paths_emit_diagnostics():
abstract_connection = read(ABSTRACT_CONNECTION_CPP)
abstract_socket = read(ABSTRACT_SOCKET_CPP)
resolving = read(RESOLVING_CPP)
tcp = read(TCP_CPP)
http = read(HTTP_CPP)
proxy_check = read(PROXY_CHECK_CPP)
assert "WriteProxyDiagnosticsLine" in abstract_connection
assert "WriteProxyDiagnosticsLine" in abstract_socket
assert "ProxyDiagnosticsPhase::Resolving" in resolving
assert "ProxyDiagnosticsPhase::TcpConnected" in tcp
assert "ProxyDiagnosticsPhase::ClientHelloSent" in tcp
assert "ProxyDiagnosticsPhase::ServerHelloOk" in tcp
assert "ProxyDiagnosticsPhase::TelegramCheck" in tcp
assert "ProxyDiagnosticsPhase::Connected" in tcp
assert "ProxyDiagnosticsPhase::Failed" in tcp
assert "ProxyDiagnosticsPhase::TelegramCheck" in http
assert "ProxyDiagnosticsPhase::ProxyCheckStarted" in proxy_check
assert "ProxyDiagnosticsPhase::ProxyCheckFinished" in proxy_check
def test_proxy_reporting_is_centralized():
header = read(DIAGNOSTICS_H)
diagnostics = read(DIAGNOSTICS_CPP)
control_header = read(CONTROL_H)
control_source = read(CONTROL_CPP)
runtime = read(RUNTIME_CPP)
instance = read(INSTANCE_CPP)
resolving = read(RESOLVING_CPP)
tcp = read(TCP_CPP)
http = read(HTTP_CPP)
proxy_check = read(PROXY_CHECK_CPP)
assert "struct ProxyEventReport" in header
assert "ProxyMtproxyTerminalReason mtproxyReason" in header
assert "ProxyConnectionAttempt attempt" in header
assert "void ReportProxyEvent(" in header
assert "void ReportProxyEvent(ProxyEventReport report);" not in header
assert "void WriteProxyDiagnosticsLine(ProxyDiagnosticsEvent event);" not in header
assert "class ProxyControlPlane final" in control_header
assert "ProxyControlPlane::FactFromReport(" in control_source
assert "StatusPhaseFromDiagnostics" not in diagnostics
assert "SourceForProxy" in diagnostics
assert '#include "mtproto/proxy/proxy_services.h"' in runtime
assert "proxyServices().control().submitFact(report);" in runtime
assert "setProxyConnectionStatus" not in diagnostics
assert "runtime->diagnostics().reportProxyEvent" in diagnostics
assert "DefaultRuntimeEnvironment()" not in diagnostics
assert "WriteProxyDiagnosticsLine(runtime, {" in runtime
assert "report.mtproxyReason" in runtime
assert "report.attempt" in runtime
for transport in (resolving, tcp, http):
assert "ReportProxyEvent(_runtime, {" in transport
assert "SetProxyConnectionStatus" not in transport
assert "AddProxyDiagnosticsEvent" not in transport
assert "ReportProxyEvent(runtime, {" in proxy_check
assert "AddProxyDiagnosticsEvent" not in proxy_check
assert "selected proxy status changed" not in instance
assert "AddProxyDiagnosticsEvent" not in instance
def test_proxy_log_lines_include_cooldown_ms():
diagnostics = read(DIAGNOSTICS_CPP)
format_body = function_body(
diagnostics,
"QString FormatProxyDiagnosticsEvent(")
assert "safe.terminalUntil" in format_body
assert "cooldown_ms=%1" in format_body
assert format_body.index("MtproxyReasonText(") < (
format_body.index("cooldown_ms=%1"))
def test_proxy_event_report_designators_follow_declaration_order():
order = {
name: index
for index, name in enumerate(proxy_event_report_fields())
}
for path in (SOURCE_DIR / "mtproto").rglob("*.cpp"):
text = read(path)
for match in re.finditer(
r"ReportProxyEvent\([^;{}]*?,\s*\{(?P<body>.*?)\}\);",
text,
re.DOTALL):
designators = [
name
for name in re.findall(r"\.(\w+)\s*=", match.group("body"))
if name in order
]
indexes = [order[name] for name in designators]
assert indexes == sorted(indexes), (
f"{path.relative_to(ROOT)} has out-of-order "
f"ProxyEventReport designators: {designators}")
def test_proxy_logs_have_no_separate_settings_entry():
lang = read(LANG)
box = read(CONNECTION_BOX_CPP)
header = read(CONNECTION_BOX_H)
settings = read(SETTINGS_MAIN_CPP)
for key in (
"lng_proxy_logs_tab",
"lng_proxy_logs_filter_all",
"lng_proxy_logs_filter_mtproxy",
"lng_proxy_logs_filter_network",
"lng_proxy_logs_refresh",
"lng_proxy_logs_copy",
"lng_proxy_logs_open_folder",
"lng_proxy_logs_clear",
"lng_proxy_logs_search",
"lng_proxy_logs_empty",
"lng_proxy_logs_file_error",
):
assert f'"{key}' not in lang
assert "class ProxyLogsBox final : public Ui::BoxContent" not in box
assert "class ProxyLogsView" not in box
assert "ProxiesBoxController::CreateLogsBox()" not in box
assert "static object_ptr<Ui::BoxContent> CreateLogsBox();" not in header
assert 'id = u"main/proxy_logs"_q' not in settings
assert "tr::lng_proxy_logs_tab()" not in settings
assert "ProxyDiagnosticsEventsValue(" not in box
assert "LoadProxyDiagnosticsTail(" not in box
assert "_logsSearch" not in box
assert "_logsSearchQuery" not in box
def test_proxy_logs_ui_does_not_render_log_memory():
box = read(CONNECTION_BOX_CPP)
assert "ProxyDiagnosticsSnapshot" not in box
assert "_logsSnapshot" not in box
assert "_logsVisibleText" not in box
assert "QPointer<ProxyLogsView> _logsView" not in box
def function_body(text: str, signature: str) -> str:
start = text.index(signature)
brace = text.index(" {\n", start) + 1
depth = 0
for index in range(brace, len(text)):
char = text[index]
if char == "{":
depth += 1
elif char == "}":
depth -= 1
if depth == 0:
return text[brace + 1:index]
raise AssertionError(f"body not found for {signature}")
if __name__ == "__main__":
test_diagnostics_model_is_disk_only()
test_diagnostics_has_no_in_memory_event_stream()
test_diagnostics_does_not_read_back_log_files()
test_diagnostics_redacts_secret_material()
test_transport_paths_emit_diagnostics()
test_proxy_reporting_is_centralized()
test_proxy_log_lines_include_cooldown_ms()
test_proxy_logs_have_no_separate_settings_entry()
test_proxy_logs_ui_does_not_render_log_memory()