diff options
| author | user <user@clank> | 2026-08-14 19:32:02 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-14 19:32:02 +0200 |
| commit | 6a6799bfc0eb8662df12b636e4fe537b84a5d484 (patch) | |
| tree | 887b96ef94def60fad351cf41070a86be94a7f56 /tests | |
| parent | sl0ppty: saving the config reloads it (diff) | |
| download | slosh-6a6799bfc0eb8662df12b636e4fe537b84a5d484.tar.gz slosh-6a6799bfc0eb8662df12b636e4fe537b84a5d484.tar.xz slosh-6a6799bfc0eb8662df12b636e4fe537b84a5d484.zip | |
sl0ppty: one save, one reload
A double "config reloaded" was reported, and the immediate cause was me saving
the file twice in a row — but looking properly turned up a real one underneath.
One save is not always one inotify event. Watching a directory for
CLOSE_WRITE, MOVED_TO and CREATE, an editor that *creates* the file rather than
rewriting it produces CREATE and then CLOSE_WRITE, measured, and each one was
reloading the config and announcing it. Rewriting in place gives one event, and
so does the rename-over that sed and vim do, which is why it took a specific
kind of editor to see it.
Events now arm a deadline rather than reloading, and the reload happens once
the file has been quiet for eighty milliseconds. That collapses the double, and
also means an editor that writes in chunks is read after it has finished rather
than halfway through — which should make the mid-save parse error, the thing
the fail-open behaviour exists for, rarer than it was.
The test counts the toasts, because toasts stack: a config that reloaded twice
says so twice, which is exactly what was seen on screen. I checked it against
the un-debounced build first and it reported two, so it is testing the thing it
claims to.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/live_reload.py | 38 |
1 files changed, 38 insertions, 0 deletions
diff --git a/tests/live_reload.py b/tests/live_reload.py index 9d1ab2c..db1fff8 100644 --- a/tests/live_reload.py +++ b/tests/live_reload.py @@ -120,6 +120,43 @@ def test_watch(): os.unlink(f) +def toast_count(session, text): + """How many times `text` appears on screen. Toasts stack, so a config that + reloaded twice announces itself twice.""" + out = subprocess.run([BIN, "-s", session, "cmd", "snapshot"], + capture_output=True, text=True).stdout + try: + rows = json.loads(out)["text"] + except Exception: + return -1 + return sum(1 for r in rows if text in r) + + +def test_one_save_is_one_reload(): + """An editor that creates the file rather than rewriting it produces two + inotify events -- CREATE then CLOSE_WRITE -- and reloading on each would + parse the config twice and say so twice for a single edit.""" + name = "t" + uuid.uuid4().hex[:8] + cfg = f"/tmp/{name}.kdl" + write(cfg, "#00ff00") + p = start(name, cfg) + try: + # Recreate it the way an editor that unlinks first would. + os.unlink(cfg) + write(cfg, "#ff0000") + check("the recreated file is picked up", + wait_for_colour(name, "#ff0000") == "#ff0000", + str(frame_colour(name))) + time.sleep(0.3) # bounded: let any second reload land if it is coming + n = toast_count(name, "config reloaded") + check("and announced exactly once, not once per inotify event", + n == 1, f"{n} toasts") + finally: + stop(name, p) + if os.path.exists(cfg): + os.unlink(cfg) + + def test_no_reload(): name = "t" + uuid.uuid4().hex[:8] cfg = f"/tmp/{name}.kdl" @@ -147,6 +184,7 @@ def test_no_reload(): if __name__ == "__main__": test_watch() + test_one_save_is_one_reload() test_no_reload() print() print(f"{'FAILED' if fails else 'all green'} ({fails} failures)") |
