"""Eén taak tegelijk per app. Zonder vergrendeling kan de nachtelijke backup starten terwijl een instellingswijziging net `compose down` heeft gedaan en de bestanden herschrijft. Het archief bevat dan een halve stack, terwijl de melding erna 'Backup klaar' is. """ import sys import threading import time from pathlib import Path WORTEL = Path(__file__).resolve().parent.parent sys.path.insert(0, str(WORTEL / "server-up")) def test_tweede_taak_wacht_op_de_eerste(env): from core import jobs volgorde = [] eerste_bezig = threading.Event() laat_los = threading.Event() def taak_een(jid): volgorde.append("een-start") eerste_bezig.set() laat_los.wait(5) volgorde.append("een-klaar") jobs.finish(jid, "done") jobs.done(jobs.get_queue(jid)) def taak_twee(jid): volgorde.append("twee-start") jobs.finish(jid, "done") jobs.done(jobs.get_queue(jid)) jid1, _ = jobs.create("update:web") jid2, _ = jobs.create("backup:web") jobs.run(jobs.met_slot("web", taak_een), jid1) assert eerste_bezig.wait(5) jobs.run(jobs.met_slot("web", taak_twee), jid2) time.sleep(0.2) assert volgorde == ["een-start"], "de tweede taak begon terwijl de eerste liep" laat_los.set() einde = time.time() + 5 while time.time() < einde and "twee-start" not in volgorde: time.sleep(0.02) assert volgorde == ["een-start", "een-klaar", "twee-start"] # En de wachtende taak meldt dat ook in zijn eigen log. tekst = " ".join(r["text"] for r in jobs.stream(jid2)["lines"]) assert "wacht" in tekst def test_andere_app_hoeft_niet_te_wachten(env): """Twee verschillende apps mogen wél tegelijk.""" from core import jobs bezig = threading.Event() los = threading.Event() gedraaid = [] def blokkeer(jid): bezig.set() los.wait(5) jobs.finish(jid, "done") jobs.done(jobs.get_queue(jid)) def snel(jid): gedraaid.append(True) jobs.finish(jid, "done") jobs.done(jobs.get_queue(jid)) jid1, _ = jobs.create("update:web") jid2, _ = jobs.create("update:andere") jobs.run(jobs.met_slot("web", blokkeer), jid1) assert bezig.wait(5) jobs.run(jobs.met_slot("andere", snel), jid2) einde = time.time() + 5 while time.time() < einde and not gedraaid: time.sleep(0.02) assert gedraaid, "een taak op een andere app werd onnodig geblokkeerd" los.set() def test_slot_komt_vrij_na_een_fout(env): """Een taak die klapt mag de app niet voor altijd op slot zetten, en mag ook niet eeuwig op 'running' blijven staan met een interface die erop blijft pollen.""" from core import jobs def klapt(jid): raise RuntimeError("boem") jid, _ = jobs.create("update:web") t = threading.Thread(target=jobs.met_slot("web", klapt), args=(jid,)) t.start(); t.join(5) with jobs.stack_slot("web") as vrij: assert vrij, "het slot bleef hangen na een mislukte taak" d = jobs.stream(jid) assert d["status"] == "error" assert "boem" in " ".join(r["text"] for r in d["lines"]) def test_geplande_backup_slaat_een_bezette_app_over(env, monkeypatch): """Wachten heeft geen zin als er een lange update loopt; dan is overslaan met een melding beter dan een archief van een halve stack.""" import app as app_module from core import jobs (env["lib"] / "web").mkdir(parents=True, exist_ok=True) (env["lib"] / "web" / "docker-compose.yml").write_text( "services:\n web:\n image: nginx\n") env["core"].patch({"BACKUP_SCHEDULE": "daily"}) # Het tijdvenster zelf is elders getest; hier gaat het om de vergrendeling. monkeypatch.setattr(app_module.scheduler, "binnen_venster", lambda *a, **k: True) gemaakt = [] monkeypatch.setattr(app_module.backups, "create", lambda *a, **k: gemaakt.append(a) or {"file": "x.tar.gz"}) # Niet twee minuten wachten in een test; het gaat om de keuze die daarna # gemaakt wordt. De originele functie eerst vastleggen, anders roept de # vervanger zichzelf aan. origineel = jobs.stack_slot monkeypatch.setattr(app_module.jobs, "stack_slot", lambda naam, wacht=0.0: origineel(naam)) slot = jobs._stackslot("web") slot.acquire() try: app_module._geplande_backups() finally: slot.release() assert gemaakt == [], "er werd een backup gemaakt van een bezette app" # Zonder slot loopt hij gewoon. app_module._geplande_backups() assert gemaakt, "zonder bezetting hoort de backup gewoon te draaien"