File size: 3,448 Bytes
7e83939
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5cb8ae8
 
 
 
7e83939
 
 
 
 
5cb8ae8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7e83939
 
5cb8ae8
 
 
7e83939
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
import datetime

from minecraft_stack import _server_performance


def test_tick_lag_and_movement_are_distinct():
    now = datetime.datetime(2026, 9, 27, 16, 30, 0)
    lines = [
        "[2026-09-27 16:24:00] minecraft> [Server thread/WARN]: Can't keep up! Is the server overloaded? Running 9000ms or 180 ticks behind",
        "[2026-09-27 16:27:00] minecraft> [Server thread/WARN]: IffyBinkie6134 moved too quickly!",
        "[2026-09-27 16:29:00] minecraft> [Server thread/WARN]: Can't keep up! Is the server overloaded? Running 321ms or 6 ticks behind",
        "[2026-09-27 16:29:10] minecraft> [Server thread/WARN]: IffyBinkie6134 moved wrongly!",
    ]
    result = _server_performance(lines, now)
    assert result == {
        "window_seconds": 300,
        "tick_lag_events": 1,
        "max_reported_tick_delay_ms": 321,
        "movement_warnings": 2,
        "movement_warning_samples": [
            "[2026-09-27 16:27:00] minecraft> [Server thread/WARN]: IffyBinkie6134 moved too quickly!",
            "[2026-09-27 16:29:10] minecraft> [Server thread/WARN]: IffyBinkie6134 moved wrongly!",
        ],
        "last_tick_lag_at": "2026-09-27 16:29:00",
    }
    assert _server_performance(lines[:1], now)["max_reported_tick_delay_ms"] is None


def test_movement_samples_name_the_entity_behind_the_burst():
    # Measured: the counter went 0 -> 107 in under a minute and then stopped. A
    # count cannot say whether one entity joining caused it or the whole server is
    # degrading, so the entity has to be in the payload.
    now = datetime.datetime(2026, 9, 29, 10, 30, 0)
    lines = [
        f"[2026-09-29 10:29:{second:02d}] minecraft> [Server thread/WARN]: Naldo_Bot moved too quickly! 12.5,0.0,-8.25"
        for second in range(0, 40)
    ]
    result = _server_performance(lines, now)
    assert result["movement_warnings"] == 40
    # The burst carries a distinct timestamp per entry, so the samples must
    # collapse on the message: one entity, one problem, one sample.
    assert result["movement_warning_samples"] == [
        "[2026-09-29 10:29:00] minecraft> [Server thread/WARN]: Naldo_Bot moved too quickly! 12.5,0.0,-8.25"
    ], "a burst from one entity must not fill the payload with copies"
    assert "Naldo_Bot" in result["movement_warning_samples"][0]


def test_movement_samples_are_bounded():
    now = datetime.datetime(2026, 9, 29, 10, 30, 0)
    lines = [
        f"[2026-09-29 10:29:{second:02d}] minecraft> [Server thread/WARN]: Bot{second} moved wrongly!"
        for second in range(0, 20)
    ]
    result = _server_performance(lines, now)
    assert result["movement_warnings"] == 20
    assert len(result["movement_warning_samples"]) == 5, "the payload must stay small"
    assert all(len(sample) <= 200 for sample in result["movement_warning_samples"])


def test_samples_are_empty_when_nothing_moved_wrongly():
    now = datetime.datetime(2026, 9, 29, 10, 30, 0)
    result = _server_performance(
        ["[2026-09-29 10:29:00] minecraft> [Server thread/INFO]: Done (1.2s)!"], now
    )
    assert result["movement_warnings"] == 0
    assert result["movement_warning_samples"] == []


if __name__ == "__main__":
    test_tick_lag_and_movement_are_distinct()
    test_movement_samples_name_the_entity_behind_the_burst()
    test_movement_samples_are_bounded()
    test_samples_are_empty_when_nothing_moved_wrongly()
    print("PASS bounded vanilla tick-lag and movement diagnostics")