File size: 8,597 Bytes
58f00c9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a00abaf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
529b973
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
import pytest

from bot import sysinfo


def test_parse_cpu_max():
    assert sysinfo.parse_cpu_max("200000 100000") == 2.0
    assert sysinfo.parse_cpu_max("max 100000") is None
    assert sysinfo.parse_cpu_max("garbage") is None
    assert sysinfo.parse_cpu_max(None) is None


def test_parse_mem_limit():
    assert sysinfo.parse_mem_limit("17179869184") == 17179869184
    assert sysinfo.parse_mem_limit("max") is None
    assert sysinfo.parse_mem_limit("9223372036854771712") is None  # cgroup v1 "unlimited"
    assert sysinfo.parse_mem_limit(None) is None


def test_parse_stat_value():
    text = "usage_usec 12345\nuser_usec 1\ninactive_file 99"
    assert sysinfo.parse_stat_value(text, "usage_usec") == 12345
    assert sysinfo.parse_stat_value(text, "inactive_file") == 99
    assert sysinfo.parse_stat_value(text, "missing") is None


@pytest.fixture
def fake_cgroup(tmp_path, monkeypatch):
    (tmp_path / "cpu.max").write_text("200000 100000\n")
    (tmp_path / "memory.max").write_text("17179869184\n")
    (tmp_path / "memory.current").write_text("3000000000\n")
    (tmp_path / "memory.stat").write_text("anon 1\ninactive_file 1000000000\n")
    (tmp_path / "cpu.stat").write_text("usage_usec 1000000\n")
    monkeypatch.setattr(sysinfo, "CGROUP_ROOT", str(tmp_path))
    return tmp_path


def test_container_memory_uses_cgroup_and_ignores_page_cache(fake_cgroup):
    used, total = sysinfo.memory_usage()
    assert used == 2_000_000_000
    assert total <= 17179869184
    assert sysinfo.cpu_cores() == 2.0


async def test_container_cpu_percent_from_cgroup(fake_cgroup, monkeypatch):
    calls = {"n": 0}
    real = sysinfo._cgroup_cpu_usec

    def fake_usec():
        calls["n"] += 1
        return 1_000_000 if calls["n"] == 1 else 1_500_000  # 0.5 cpu-seconds over ~0.1s window

    monkeypatch.setattr(sysinfo, "_cgroup_cpu_usec", fake_usec)
    pct = await sysinfo.cpu_percent(interval=0.1)
    assert 0 < pct <= 100
    monkeypatch.setattr(sysinfo, "_cgroup_cpu_usec", real)


def test_falls_back_to_psutil_without_cgroup(tmp_path, monkeypatch):
    monkeypatch.setattr(sysinfo, "CGROUP_ROOT", str(tmp_path / "does-not-exist"))
    used, total = sysinfo.memory_usage()
    assert total > 0 and used >= 0
    assert sysinfo.cpu_cores() >= 1


async def test_format_status_and_stats(tmp_path):
    text = sysinfo.format_status(123.4, 1, "1.45.0")
    assert "123 ms" in text and "DC 1" in text and "v1.0.0" in text and "Uptime" in text
    assert "n/a" in sysinfo.format_status(None, None, "x")
    stats = await sysinfo.format_stats(3, 12, 4_500_000_000, 1, 2, str(tmp_path))
    for needle in ("Users:</b> 3", "Files renamed", "1 running · 2 waiting", "CPU", "RAM", "Disk", "Network"):
        assert needle in stats


# ---------------------------------------------------------------------------
# what makes transfers fast: cryptg, uvloop, parallel transfer
# ---------------------------------------------------------------------------


def test_telethon_encrypts_with_cryptg_here():
    # also a guard for the deployment: requirements.txt must keep installing cryptg
    assert sysinfo.crypto_backend() == "cryptg"


@pytest.mark.parametrize("cryptg, libssl_works, expected", [
    (object(), True, "cryptg"), (object(), False, "cryptg"), (None, True, "libssl"), (None, False, "python"),
])
def test_crypto_backend_follows_the_order_telethon_picks(monkeypatch, cryptg, libssl_works, expected):
    from telethon.crypto import aes

    monkeypatch.setattr(aes, "cryptg", cryptg)
    monkeypatch.setattr(aes.libssl, "encrypt_ige", (lambda *a: b"") if libssl_works else None)
    monkeypatch.setattr(aes.libssl, "decrypt_ige", (lambda *a: b"") if libssl_works else None)
    assert sysinfo.crypto_backend() == expected


async def test_loop_backend_sees_the_standard_loop():
    assert sysinfo.loop_backend() == "asyncio"


def test_loop_backend_outside_a_loop_and_on_uvloop():
    uvloop = pytest.importorskip("uvloop")
    assert sysinfo.loop_backend() == "none"
    loop = uvloop.new_event_loop()
    try:
        assert loop.run_until_complete(_loop_name()) == "uvloop"
    finally:
        loop.close()


async def _loop_name():
    return sysinfo.loop_backend()


def test_the_bot_really_starts_on_uvloop():
    from bot import main as bot_main

    pytest.importorskip("uvloop")
    seen = []

    async def probe():
        seen.append(sysinfo.loop_backend())
        return 0

    assert bot_main._run(probe) == 0 and seen == ["uvloop"]


def test_speed_summary_and_checks_mark_what_is_off(monkeypatch):
    monkeypatch.setattr(sysinfo, "crypto_backend", lambda: "cryptg")
    monkeypatch.setattr(sysinfo, "loop_backend", lambda: "uvloop")
    assert sysinfo.speed_summary(True, 8) == "cryptg ✅ · uvloop ✅ · parallel ×8 ✅"
    assert all(fine for _short, _detail, fine in sysinfo.speed_checks(True, 8))

    monkeypatch.setattr(sysinfo, "crypto_backend", lambda: "python")
    monkeypatch.setattr(sysinfo, "loop_backend", lambda: "asyncio")
    assert sysinfo.speed_summary(False, 8) == "slow Python crypto ⚠️ · no uvloop ⚠️ · parallel off ⚠️"
    details = [detail for _short, detail, _fine in sysinfo.speed_checks(False, 8)]
    assert "cryptg is missing" in details[0] and "uvloop is not running" in details[1] and "FAST_TRANSFER=0" in details[2]

    monkeypatch.setattr(sysinfo, "crypto_backend", lambda: "libssl")
    assert sysinfo.speed_summary(True, 16).startswith("libssl ✅") and "parallel ×16 ✅" in sysinfo.speed_summary(True, 16)


def test_the_speed_section_shows_the_stack_the_counts_and_the_last_failure(monkeypatch):
    from bot.fasttransfer import TransferStats

    monkeypatch.setattr(sysinfo, "crypto_backend", lambda: "cryptg")
    monkeypatch.setattr(sysinfo, "loop_backend", lambda: "uvloop")
    stats = TransferStats(fast_down=12, fast_up=9, small_up=3)
    text = sysinfo.format_speed(True, 8, stats)
    assert "⚡ <b>Speed</b>" in text and "Encryption: cryptg ✅" in text and "Event loop: uvloop ✅" in text
    assert "Parallel transfer: on, 8 connections ✅" in text
    assert "<b>Downloads:</b> 12 parallel · 0 standard" in text
    assert "<b>Uploads:</b> 9 parallel · 3 up to 10 MB · 0 standard" in text
    assert "failed" not in text and "⚠️" not in text

    stats.standard_down, stats.standard_up = 2, 1
    stats.fell_back("download", ValueError("<bad> & worse"))
    text = sysinfo.format_speed(False, 8, stats)
    assert "off (FAST_TRANSFER=0) ⚠️" in text and "2 standard" in text
    assert "failed 1×" in text and "<code>download: ValueError: &lt;bad&gt; &amp; worse</code>" in text  # HTML-safe


def test_format_status_adds_the_speed_section_only_when_given_one():
    plain = sysinfo.format_status(10, 1, "1.45.0")
    assert "Speed" not in plain and plain.endswith("Telethon 1.45.0")
    with_speed = sysinfo.format_status(10, 1, "1.45.0", "⚡ <b>Speed</b>\nline")
    assert with_speed.startswith(plain) and with_speed.endswith("\n\n⚡ <b>Speed</b>\nline")


def test_the_speed_summary_and_section_say_whether_streaming_is_on(monkeypatch):
    from bot.fasttransfer import TransferStats

    monkeypatch.setattr(sysinfo, "crypto_backend", lambda: "cryptg")
    monkeypatch.setattr(sysinfo, "loop_backend", lambda: "uvloop")
    assert sysinfo.speed_summary(True, 8, True) == "cryptg ✅ · uvloop ✅ · parallel ×8 ✅ · streaming ✅"
    assert sysinfo.speed_summary(True, 8, False).endswith("parallel ×8 ✅ · no streaming ⚠️")
    assert sysinfo.speed_summary(False, 8, True).endswith("parallel off ⚠️ · no streaming ⚠️")  # streaming needs the parallel transfer
    assert sysinfo.speed_summary(True, 8) == "cryptg ✅ · uvloop ✅ · parallel ×8 ✅"  # nothing added when it is not asked for

    stats = TransferStats(streamed=7)
    text = sysinfo.format_speed(True, 8, stats, True)
    assert "⬇⬆ Streaming rename: on: big files are uploaded while they download ✅" in text and "<b>Streamed renames:</b> 7" in text
    assert "Streaming failed" not in text and "⚠️" not in text
    assert "Streaming rename" not in sysinfo.format_speed(True, 8, stats) and "Streamed renames" not in sysinfo.format_speed(True, 8, stats)

    stats.stream_failed(ValueError("<x>"))
    text = sysinfo.format_speed(True, 8, stats, False)
    assert "off (STREAM_RENAME=0) ⚠️" in text
    assert "⚠️ Streaming failed 1× and those files were saved first and then uploaded. Last: <code>ValueError: &lt;x&gt;</code>" in text
    assert "The parallel transfer failed" not in text  # a streaming failure is not a parallel-transfer failure