aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_reload.py
blob: 20b367329c63c13adaf91f2559ab48105d0025ca (plain) (blame)
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
#!/usr/bin/env python3
"""What a config reload actually changes, and when it is noticed at all.

Two questions worth having answers to in one place: does saving the file get
seen (including when there was no file when the session started), and which
settings take effect without a restart. The second is nearly all of them, and
"nearly" is the part worth pinning — a setting that silently needs a restart
is indistinguishable from one that does not work.
"""
import os
import subprocess
import sys
import tempfile
import time

from harness import BIN, Session, check, report

SH = ["/bin/sh", "-c", 'printf "\\033]2;p\\007"; read x']


def conf(text=""):
    f = tempfile.NamedTemporaryFile("w", suffix=".kdl", delete=False)
    f.write(text)
    f.close()
    return f.name


def frame_fg(s):
    return [r.get("fg") for r in s.snapshot().data["styles"] if r["y"] == 2][:1]


def reload_with(s, path, text):
    with open(path, "w") as f:
        f.write(text)
    s.api("reload")
    s.settle(30)


def test_the_things_that_take_effect_immediately():
    path = conf()
    with Session(SH, cols=80, rows=16, config=path) as s:
        s.settle(30)

        reload_with(s, path, 'theme { frame_focus "#00ff00" }\n')
        check("theme colours", frame_fg(s) == ["#00ff00"], str(frame_fg(s)))

        reload_with(s, path, "gap 3\n")
        check("geometry", s.pane()["x"] == 6, str(s.pane()))

        reload_with(s, path, "status_bar false\n")
        check("chrome on and off", "+" not in s.snapshot().line(1),
              repr(s.snapshot().line(1)))

        reload_with(s, path, 'close_mark "@"\n')
        check("the marks in a frame", "@" in s.snapshot().line(2),
              repr(s.snapshot().line(2)))

        reload_with(s, path, "dim_unfocused 0\n")
        s.key("\\\\")
        s.settle(30)
        unfocused = [p for p in s.panes() if not p["focused"]][0]
        run = s.snapshot().style_at(unfocused["content_x"], unfocused["content_y"])
        check("shader policy", (run or {}).get("fg") is None, str(run))

        reload_with(s, path, 'keys { prefix "ctrl+b" }\n')
        # A new tab rather than a split: by this point the panes are too narrow
        # to split, and a test that fails on geometry while claiming to be
        # about the prefix is worse than no test.
        before = len(s.tabs())
        s.send(r"\x02c")
        s.settle(30)
        check("the leader key itself", len(s.tabs()) == before + 1,
              str(s.tabs()))
    os.unlink(path)


def test_the_shell_is_consulted_when_a_pane_is_made():
    """`shell` used to be read by nobody: main.c took $SHELL directly, so the
    setting existed, parsed, rendered — and did nothing. Now it is resolved per
    pane, which also means editing it applies to the next pane you open."""
    path = conf('shell "/usr/bin/zsh"\n')
    with Session(SH, cols=70, rows=14, config=path) as s:
        s.settle(30)
        # The session was given an explicit command, so its own pane runs that;
        # a *new* pane is where the setting shows up.
        s.key("\\\\")
        s.settle(60)
        check("a second pane opened", len(s.panes()) == 2, str(s.panes()))
    os.unlink(path)


def test_a_config_written_after_the_session_started_is_noticed():
    """The watch is on the directory, so a file that appears later is caught —
    but a directory that does not exist cannot be watched at all, and then
    nothing is ever noticed for the life of the session. That was the bug: no
    config at launch meant no hot reload, ever, even after writing one."""
    home = tempfile.mkdtemp(prefix="sl0ppty-home-")
    path = os.path.join(home, ".config", "sl0ppty", "config.kdl")
    check("the directory really is missing", not os.path.isdir(os.path.dirname(path)))

    env = dict(os.environ, SL0PPTY_CONFIG=path)
    name = "reloadtest-%d" % os.getpid()
    subprocess.run([BIN, "-s", name, "--", "/bin/sh", "-c", "read x"],
                   env=env, capture_output=True, timeout=5)
    try:
        deadline = time.time() + 3
        got = None
        while time.time() < deadline:
            with open(path, "w") as f:
                f.write('theme { frame_focus "#00ff00" }\n')
            time.sleep(0.4)
            out = subprocess.run([BIN, "-s", name, "cmd",
                                  '{"cmd":"snapshot","format":"json"}'],
                                 capture_output=True, text=True, env=env).stdout
            if '"#00ff00"' in out:
                got = True
                break
        check("writing a config later is picked up without a restart", got,
              "the session never saw the new file")
    finally:
        subprocess.run([BIN, "-s", name, "cmd", '{"cmd":"quit"}'],
                       capture_output=True, env=env)


for name, fn in sorted(list(globals().items())):
    if name.startswith("test_"):
        fn()
sys.exit(report())