fix: findings from the independent review
Scenario runner: stops when overridden base parameters leave the start out of balance (new field startlikevekt: krev, kalibrer, ubalanse); stops on non-finite results; says that models with a court queue drift slowly even without a shock and prints the drift before the shock; queue in months of inflow; never deletes files it did not write; stricter limits. Check: anchored on the note's own checksums for core and reference files, runs the scenario files too, exact comparison for the diagnosis, honest wording when parts were skipped or an extension is broken. Launcher: unknown field names in input are rejected with a suggestion; unwritable folders give a message, not a traceback. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
parent
2669759f5a
commit
776325c680
14 changed files with 1524 additions and 199 deletions
|
|
@ -70,10 +70,11 @@ def hjem(tmp_path: Path) -> Path:
|
|||
|
||||
@pytest.fixture
|
||||
def kopi(tmp_path: Path) -> Path:
|
||||
"""En kopi av modell/, fasit/ og eksempler/ som testen kan endre. Returnerer kopiens rot."""
|
||||
"""En kopi av modell/, fasit/ og eksempler/ som testen kan endre. Returnerer kopiens rot.
|
||||
|
||||
Kopien er ikke merket som arbeidskopi (ingen LES-MEG.md), som en klone av repoet.
|
||||
"""
|
||||
rot = tmp_path / "kopi"
|
||||
for navn in ("modell", "fasit", "eksempler"):
|
||||
shutil.copytree(ROT / navn, rot / navn, ignore=shutil.ignore_patterns("__pycache__"))
|
||||
for navn in ("scenario.py", "arbeidskopi.py"):
|
||||
(rot / "modell" / navn).unlink(missing_ok=True)
|
||||
return rot
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from __future__ import annotations
|
|||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import arbeidskopi as arbeidskopi_modul
|
||||
import pytest
|
||||
from conftest import EKSEMPLER, FASIT, ROT, kjor
|
||||
|
||||
|
|
@ -100,3 +101,28 @@ def test_endring_i_kopiens_kjerne_brukes(arbeidskopi: Path, tmp_path: Path) -> N
|
|||
plugin = kjor("scenario", str(KJEDE_S2), "--ut", str(tmp_path / "plugin"))
|
||||
assert plugin.returncode == 0, plugin.stderr
|
||||
assert (tmp_path / "plugin" / "kjede_s2.dat").read_bytes() == (FASIT / "data" / "kjede_s2.dat").read_bytes()
|
||||
|
||||
|
||||
def test_nekter_mappe_inne_i_pluginen(tmp_path: Path) -> None:
|
||||
mal = ROT / "nyttsted-test-arbeidskopi"
|
||||
assert not mal.exists()
|
||||
prosess = kjor("arbeidskopi", str(mal))
|
||||
assert prosess.returncode == 2, prosess.stdout
|
||||
assert "ligger inne i" in prosess.stderr
|
||||
assert not mal.exists()
|
||||
assert not (tmp_path / "x").exists()
|
||||
|
||||
|
||||
def test_lisenssetningen(arbeidskopi: Path) -> None:
|
||||
assert "Alt i pluginen er under MIT-lisensen (se `LICENSE`)." in (arbeidskopi / "LES-MEG.md").read_text(
|
||||
encoding="utf-8")
|
||||
|
||||
|
||||
def test_kommandoene_skrives_for_windows(tmp_path: Path) -> None:
|
||||
mal = tmp_path / "Kari Nordmann" / "rettsstat-modell"
|
||||
tekst = arbeidskopi_modul.les_meg(mal, [], windows=True)
|
||||
assert f'python "{mal / "modell" / "kjor.py"}" scenario' in tekst
|
||||
assert "python3" not in tekst
|
||||
assert "'" not in tekst.split("## Slik kjører du kopien", 1)[1].split("## Slik kontrollerer", 1)[0]
|
||||
posix = arbeidskopi_modul.les_meg(mal, [], windows=False)
|
||||
assert f"python3 '{mal / 'modell' / 'kjor.py'}' scenario" in posix
|
||||
|
|
|
|||
|
|
@ -3,16 +3,21 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import kjor as kjor_modul
|
||||
import pytest
|
||||
from conftest import DOMSTOL, EKSEMPLER, FASIT, MODELL, NORGE, ROT, fasit, kjor, lag_venv_omvei
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
UTEN_SITE = [sys.executable, "-S"] # samme Python uten site-packages: numpy og scipy mangler
|
||||
|
||||
|
||||
|
|
@ -119,6 +124,74 @@ def test_ingen_pycache_i_kjernen(kopi: Path) -> None:
|
|||
assert not (kopi / "modell" / "kjerne" / "__pycache__").exists()
|
||||
|
||||
|
||||
# ---------------- ukjente feltnavn (funn 5) ----------------
|
||||
|
||||
def skriv_variant(tmp_path: Path, endre: Callable[[dict[str, Any]], None], navn: str = "variant.json") -> Path:
|
||||
"""En kopi av eksempel_domstol.json med en endring."""
|
||||
data = json.loads(DOMSTOL.read_text(encoding="utf-8"))
|
||||
endre(data)
|
||||
fil = tmp_path / navn
|
||||
fil.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
|
||||
return fil
|
||||
|
||||
|
||||
def skrivefeil_i_ledd(data: dict[str, Any]) -> None:
|
||||
domstol = data["ledd"][2]
|
||||
domstol["prognose_innstrømning"] = domstol.pop("prognose_innstromning")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("kommando", ["diagnose", "forlop"])
|
||||
def test_skrivefeil_i_ledd_avvises(kommando: str, tmp_path: Path) -> None:
|
||||
fil = skriv_variant(tmp_path, skrivefeil_i_ledd)
|
||||
prosess = kjor(kommando, str(fil))
|
||||
assert prosess.returncode == 2
|
||||
assert prosess.stdout == ""
|
||||
assert prosess.stderr.startswith(
|
||||
'Feil i inndata, ledd 3 (Domstol): feltet "prognose_innstrømning" finnes ikke. '
|
||||
'Mente du "prognose_innstromning"? Gyldige felt: navn, innstromning, kapasitet, restanse, maal_tid,')
|
||||
|
||||
|
||||
def test_skrivefeil_i_styring_avvises(tmp_path: Path) -> None:
|
||||
def endre(data: dict[str, Any]) -> None:
|
||||
data["styring"]["reaktivitett"] = data["styring"].pop("reaktivitet")
|
||||
|
||||
prosess = kjor("forlop", str(skriv_variant(tmp_path, endre)))
|
||||
assert prosess.returncode == 2
|
||||
assert prosess.stderr == ('Feil i inndata, styring: feltet "reaktivitett" finnes ikke. Mente du "reaktivitet"? '
|
||||
"Gyldige felt: forsinkelse, reaktivitet, merknad, kilde, kilder.\n")
|
||||
|
||||
|
||||
def test_merknadsfelt_og_ukjente_toppfelt_er_tillatt(tmp_path: Path) -> None:
|
||||
def endre(data: dict[str, Any]) -> None:
|
||||
data["ledd"][2].update(merknad="fri tekst", kilde="SSB", kilder=["a", "b"])
|
||||
data["styring"]["merknad"] = "regime A"
|
||||
data["noe_annet"] = {"fritt": True}
|
||||
|
||||
prosess = kjor("diagnose", str(skriv_variant(tmp_path, endre)))
|
||||
assert prosess.returncode == 0, prosess.stderr
|
||||
assert prosess.stdout == fasit("diagnose_eksempel_domstol.txt")
|
||||
|
||||
|
||||
def test_feltene_leses_fra_kjernen_i_modellen(kopi: Path, tmp_path: Path) -> None:
|
||||
diagnose = kopi / "modell" / "kjerne" / "diagnose.py"
|
||||
tekst = diagnose.read_text(encoding="utf-8")
|
||||
assert '"innstromning_for_okning")' in tekst
|
||||
diagnose.write_text(tekst.replace('"innstromning_for_okning")', '"innstromning_for_okning", "nytt_felt")', 1),
|
||||
encoding="utf-8")
|
||||
fil = skriv_variant(tmp_path, lambda data: data["ledd"][0].update(nytt_felt=1))
|
||||
assert kjor("diagnose", str(fil)).returncode == 2
|
||||
prosess = kjor("--modell", str(kopi), "diagnose", str(fil))
|
||||
assert prosess.returncode == 0, prosess.stderr
|
||||
|
||||
|
||||
def test_feil_form_overlates_til_kjernen(tmp_path: Path) -> None:
|
||||
fil = tmp_path / "feil.json"
|
||||
fil.write_text('{"ledd": [1, {"navn": "A"}]}', encoding="utf-8")
|
||||
prosess = kjor("diagnose", str(fil))
|
||||
assert prosess.returncode == 2
|
||||
assert prosess.stderr == "Feil i inndata, ledd 1: hvert ledd må være et objekt med felt og verdier.\n"
|
||||
|
||||
|
||||
# ---------------- --data og valg av Python ----------------
|
||||
|
||||
@pytest.mark.parametrize("verdi", [None, "", " ", "${CLAUDE_PLUGIN_DATA}", "/a/${CLAUDE_PLUGIN_DATA}/b"])
|
||||
|
|
@ -229,6 +302,18 @@ def test_velg_tolk(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
|||
assert kjor_modul.velg_tolk(tmp_path).hvor == "mangler"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("system", "start", "sitert"), [
|
||||
("nt", "python ", '"/data med mellomrom"'), ("posix", "python3 ", "'/data med mellomrom'"),
|
||||
])
|
||||
def test_foreslatt_kommando_per_system(system: str, start: str, sitert: str) -> None:
|
||||
valg = kjor_modul.Valg(argv=[], data=Path("/data med mellomrom"), data_oppgitt=True, modell=MODELL,
|
||||
kommando="forlop", args=[])
|
||||
tekst = kjor_modul.kommando_tekst(valg, "miljo", "--opprett", system=system)
|
||||
assert tekst.startswith(start)
|
||||
assert f"--data {sitert} miljo --opprett" in tekst
|
||||
assert kjor_modul.python_navn(system) == start.strip()
|
||||
|
||||
|
||||
# ---------------- miljo --opprett (uten å installere) ----------------
|
||||
|
||||
def test_opprett_proever_laaste_versjoner_forst(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
|
|
@ -302,6 +387,7 @@ def test_modell_i_arbeidskopi(kopi: Path, undermappe: str) -> None:
|
|||
|
||||
@pytest.mark.parametrize("kommando", ["scenario", "arbeidskopi"])
|
||||
def test_modul_som_ikke_finnes_ennaa(kopi: Path, kommando: str) -> None:
|
||||
(kopi / "modell" / f"{kommando}.py").unlink()
|
||||
prosess = kjor("--modell", str(kopi), kommando, "x")
|
||||
assert prosess.returncode == 2
|
||||
assert f"Kommandoen «{kommando}» er ikke på plass i denne modellen ennå" in prosess.stderr
|
||||
|
|
@ -359,6 +445,29 @@ def test_notatet_nekter_ut_mappe_i_modellen() -> None:
|
|||
assert not (MODELL / "kjerne" / "data").exists()
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.name == "nt" or os.geteuid() == 0, reason="skrivebeskyttelse virker ikke for root")
|
||||
def test_notatet_i_mappe_som_ikke_kan_skrives(tmp_path: Path) -> None:
|
||||
sperret = tmp_path / "sperret"
|
||||
sperret.mkdir()
|
||||
sperret.chmod(0o555)
|
||||
try:
|
||||
prosess = kjor("notatet", "--ut", str(sperret / "ut"))
|
||||
finally:
|
||||
sperret.chmod(0o755)
|
||||
assert prosess.returncode == 2
|
||||
assert prosess.stderr.startswith(f"Kan ikke skrive i {(sperret / 'ut').resolve()}: ")
|
||||
assert prosess.stderr.endswith(". Velg en annen mappe med --ut.\n")
|
||||
assert "Traceback" not in prosess.stderr
|
||||
|
||||
|
||||
def test_notatet_naar_sim_stopper(kopi: Path, tmp_path: Path) -> None:
|
||||
(kopi / "modell" / "kjerne" / "sim.py").write_text("print('start')\nraise SystemExit(5)\n", encoding="utf-8")
|
||||
prosess = kjor("--modell", str(kopi), "notatet", "--ut", str(tmp_path / "ut"))
|
||||
assert prosess.returncode == 2
|
||||
assert "Kjøringen av sim.py stoppet underveis (exit-kode 5)" in prosess.stderr
|
||||
assert "start\n" in prosess.stdout
|
||||
|
||||
|
||||
@pytest.mark.treg
|
||||
def test_notatet_gjenskaper_datafilene(tmp_path: Path) -> None:
|
||||
ut = tmp_path / "ut"
|
||||
|
|
|
|||
|
|
@ -3,12 +3,15 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import kontroll
|
||||
import pytest
|
||||
from conftest import kjor
|
||||
from conftest import FASIT, kjor
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
|
@ -70,23 +73,53 @@ def test_dat_ulik_form(naa: str) -> None:
|
|||
|
||||
# ---------------- kontrollen som helhet ----------------
|
||||
|
||||
FALSK_SCENARIO = """\
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main(argv):
|
||||
fil, ut = Path(argv[0]), Path(argv[2])
|
||||
ut.mkdir(parents=True, exist_ok=True)
|
||||
fasit = Path(__file__).resolve().parent.parent / "fasit" / "data" / (fil.stem + ".dat")
|
||||
shutil.copyfile(fasit, ut / (fil.stem + ".dat"))
|
||||
return 0
|
||||
"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def lett_kopi(kopi: Path) -> Path:
|
||||
"""En kopi der scenario.py er byttet mot en som kopierer fasit, så kontroll 6 går fort.
|
||||
|
||||
Brukes av testene som gjelder andre kontroller; scenariokjøreren selv prøves på pluginen og i egne tester.
|
||||
"""
|
||||
(kopi / "modell" / "scenario.py").write_text(FALSK_SCENARIO, encoding="utf-8")
|
||||
return kopi
|
||||
|
||||
|
||||
def test_rask_kontroll_av_pluginen(hjem: Path) -> None:
|
||||
prosess = kjor("kontroll", "--rask", hjem=hjem)
|
||||
assert prosess.returncode == 0, prosess.stdout + prosess.stderr
|
||||
assert "1. Kjernen: uendret fra notatet (3 filer," in prosess.stdout
|
||||
assert "1. Fasit: uendret fra notatet (27 filer)." in prosess.stdout
|
||||
assert "2. diagnose: lik notatet for eksempel_domstol og norge_2025." in prosess.stdout
|
||||
assert "3. forlop: lik notatet for eksempel_domstol og norge_2025." in prosess.stdout
|
||||
assert "4. Full kjøring: hoppet over (--rask)." in prosess.stdout
|
||||
assert ("6. Scenariokjøreren: 13 av 13 av notatets datafiler gjenskapt byte for byte fra "
|
||||
"eksempler/scenarioer/notatet/ (mpc11 bare i full kjøring).") in prosess.stdout
|
||||
assert "Nivå (uten full kjøring, --rask): bit for bit som notatet." in prosess.stdout
|
||||
|
||||
|
||||
def test_kontroll_uten_numpy_gjor_det_den_kan(hjem: Path) -> None:
|
||||
def test_kontroll_uten_numpy_skryter_ikke(hjem: Path) -> None:
|
||||
prosess = kjor("kontroll", python=[sys.executable, "-S"], hjem=hjem)
|
||||
assert prosess.returncode == 3
|
||||
assert "2. diagnose: lik notatet" in prosess.stdout
|
||||
assert "3. forlop: ikke kjørt, numpy og scipy mangler." in prosess.stdout
|
||||
assert "4. Full kjøring: ikke kjørt, numpy og scipy mangler." in prosess.stdout
|
||||
assert "Nivå (bare det som er kjørt): bit for bit som notatet." in prosess.stdout
|
||||
assert "6. Scenariokjøreren: ikke kjørt, numpy og scipy mangler." in prosess.stdout
|
||||
assert ("Nivå: ingen avvik i det som ble kjørt (Kjernen, Fasit, diagnose). Ikke kontrollert: forlop, full "
|
||||
"kjøring av sim.py, utvidelsenes grensetilfeller, scenariokjøreren.") in prosess.stdout
|
||||
assert "bit for bit som notatet" not in prosess.stdout
|
||||
|
||||
|
||||
def endre_diagnose(kopi: Path) -> None:
|
||||
|
|
@ -97,32 +130,91 @@ def endre_diagnose(kopi: Path) -> None:
|
|||
fil.write_text(tekst.replace("UPS_STAR = 0.95", "UPS_STAR = 0.90"), encoding="utf-8")
|
||||
|
||||
|
||||
def test_endret_kjerne_i_pluginen_er_en_feil(kopi: Path, hjem: Path) -> None:
|
||||
endre_diagnose(kopi)
|
||||
prosess = kjor("--modell", str(kopi), "kontroll", "--rask", hjem=hjem)
|
||||
def test_endret_kjerne_utenfor_arbeidskopi_er_noytral(lett_kopi: Path, hjem: Path) -> None:
|
||||
endre_diagnose(lett_kopi)
|
||||
prosess = kjor("--modell", str(lett_kopi), "kontroll", "--rask", hjem=hjem)
|
||||
assert prosess.returncode == 1
|
||||
assert "diagnose.py er endret" in prosess.stdout
|
||||
assert "2. diagnose: endret (se under)." in prosess.stdout
|
||||
assert "- Kapasitet: (0,80 x 134,3 + 273/24) / 0,95 = 125,1 saker/mnd, det vil si 19 % mer." in prosess.stdout
|
||||
assert "Nivå (uten full kjøring, --rask): endret: Kjernen, diagnose, forlop." in prosess.stdout
|
||||
assert "Pluginens modell skal regne som notatet, så dette er en feil." in prosess.stdout
|
||||
assert "Er dette pluginen slik den er installert, er det en feil" in prosess.stdout
|
||||
assert "Er modellen endret med vilje (for eksempel i en klone av repoet), er det en opplysning" in prosess.stdout
|
||||
assert "så dette er en feil." not in prosess.stdout
|
||||
|
||||
|
||||
def test_endret_kjerne_i_arbeidskopi_er_en_opplysning(kopi: Path, hjem: Path) -> None:
|
||||
endre_diagnose(kopi)
|
||||
(kopi / "LES-MEG.md").write_text("Arbeidskopi\n", encoding="utf-8")
|
||||
prosess = kjor("--modell", str(kopi), "kontroll", "--rask", hjem=hjem)
|
||||
def test_endret_kjerne_i_arbeidskopi_er_en_opplysning(lett_kopi: Path, hjem: Path) -> None:
|
||||
endre_diagnose(lett_kopi)
|
||||
(lett_kopi / "LES-MEG.md").write_text("Arbeidskopi\n", encoding="utf-8")
|
||||
prosess = kjor("--modell", str(lett_kopi), "kontroll", "--rask", hjem=hjem)
|
||||
assert prosess.returncode == 1
|
||||
assert "(arbeidskopi)" in prosess.stdout
|
||||
assert "det er en opplysning, ikke en feil" in prosess.stdout
|
||||
assert "+ Kapasitet: (0,80 x 134,3 + 273/24) / 0,90" in prosess.stdout
|
||||
|
||||
|
||||
def test_manglende_fasit(kopi: Path, hjem: Path) -> None:
|
||||
(kopi / "fasit" / "diagnose_norge_2025.txt").unlink()
|
||||
prosess = kjor("--modell", str(kopi), "kontroll", "--rask", hjem=hjem)
|
||||
def test_endret_kjerne_med_ny_fasit_og_kilde_er_fortsatt_endret(lett_kopi: Path, hjem: Path) -> None:
|
||||
"""Funn 3: kontrollen ankres i notatet, ikke i KILDE.json og fasit/ i samme mappe."""
|
||||
endre_diagnose(lett_kopi)
|
||||
kjerne = lett_kopi / "modell" / "kjerne"
|
||||
for eksempel in ("eksempel_domstol", "norge_2025"):
|
||||
ny = subprocess.run([sys.executable, str(kjerne / "diagnose.py"), str(lett_kopi / "eksempler" /
|
||||
f"{eksempel}.json")], capture_output=True, text=True, encoding="utf-8", check=True)
|
||||
(lett_kopi / "fasit" / f"diagnose_{eksempel}.txt").write_text(ny.stdout, encoding="utf-8")
|
||||
kilde = json.loads((kjerne / "KILDE.json").read_text(encoding="utf-8"))
|
||||
kilde["filer"]["diagnose.py"]["sha256"] = hashlib.sha256((kjerne / "diagnose.py").read_bytes()).hexdigest()
|
||||
(kjerne / "KILDE.json").write_text(json.dumps(kilde), encoding="utf-8")
|
||||
prosess = kjor("--modell", str(lett_kopi), "kontroll", "--rask", hjem=hjem)
|
||||
assert prosess.returncode == 1
|
||||
assert "norge_2025: fant ikke" in prosess.stdout
|
||||
assert "1. Kjernen: endret fra notatet" in prosess.stdout
|
||||
assert "diagnose.py er endret" in prosess.stdout
|
||||
assert "1. Fasit: fasit er endret i forhold til notatet (2 av 27 filer)." in prosess.stdout
|
||||
assert "fasit/diagnose_eksempel_domstol.txt: har ikke notatets sha256" in prosess.stdout
|
||||
assert "2. diagnose: endret (se under)." in prosess.stdout
|
||||
assert "er endret i forhold til notatet, så sammenligningen sier ingenting om notatet" in prosess.stdout
|
||||
assert "bit for bit" not in prosess.stdout.split("Nivå")[-1]
|
||||
|
||||
|
||||
def test_manglende_fasit(lett_kopi: Path, hjem: Path) -> None:
|
||||
(lett_kopi / "fasit" / "diagnose_norge_2025.txt").unlink()
|
||||
prosess = kjor("--modell", str(lett_kopi), "kontroll", "--rask", hjem=hjem)
|
||||
assert prosess.returncode == 1
|
||||
assert "fasit/diagnose_norge_2025.txt: mangler" in prosess.stdout
|
||||
assert "norge_2025: fant ikke fasit/diagnose_norge_2025.txt" in prosess.stdout
|
||||
|
||||
|
||||
def test_diagnose_krever_eksakt_likhet(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Funn 10: én enhet i siste siffer i diagnosen er endret, ikke innenfor toleranse."""
|
||||
fasit = tmp_path / "fasit"
|
||||
fasit.mkdir()
|
||||
for navn in ("eksempel_domstol", "norge_2025"):
|
||||
tekst = (FASIT / f"diagnose_{navn}.txt").read_text(encoding="utf-8")
|
||||
(fasit / f"diagnose_{navn}.txt").write_text(tekst.replace("1,28", "1,29", 1), encoding="utf-8")
|
||||
monkeypatch.setattr(kontroll, "FASIT", fasit)
|
||||
assert kontroll.kontroll_utskrift("diagnose", tmp_path, set(), toleranse=False).status == kontroll.ENDRET
|
||||
assert kontroll.kontroll_utskrift("diagnose", tmp_path, set(), toleranse=True).status == kontroll.TOLERANSE
|
||||
|
||||
|
||||
def test_scenariokjoreren_som_ikke_gjenskaper_notatet(kopi: Path, hjem: Path) -> None:
|
||||
"""Funn 4: en endring i det scenariokjøreren regner, blir oppdaget av kontroll --rask."""
|
||||
fil = kopi / "eksempler" / "scenarioer" / "notatet" / "p3.json"
|
||||
data = json.loads(fil.read_text(encoding="utf-8"))
|
||||
data["styring"]["reaktivitet"] = 4
|
||||
fil.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
|
||||
prosess = kjor("--modell", str(kopi), "kontroll", "--rask", hjem=hjem)
|
||||
assert prosess.returncode == 1, prosess.stdout + prosess.stderr
|
||||
assert "6. Scenariokjøreren: 12 av 13 av notatets datafiler gjenskapt byte for byte" in prosess.stdout
|
||||
assert "p3.dat (endret): største absolutte avvik per kolonne:" in prosess.stdout
|
||||
assert "endret: Scenariokjøreren." in prosess.stdout
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fjern", ["modell/scenario.py", "eksempler/scenarioer/notatet/endo_ko.json"])
|
||||
def test_scenariokjoreren_som_mangler_sies_fra(lett_kopi: Path, hjem: Path, fjern: str) -> None:
|
||||
(lett_kopi / fjern).unlink()
|
||||
prosess = kjor("--modell", str(lett_kopi), "kontroll", "--rask", hjem=hjem)
|
||||
assert prosess.returncode == 1, prosess.stdout + prosess.stderr
|
||||
assert "så den er ikke kontrollert." in prosess.stdout
|
||||
assert "endret: Scenariokjøreren." in prosess.stdout
|
||||
|
||||
|
||||
def lag_utvidelser(kopi: Path, filer: dict[str, str]) -> None:
|
||||
|
|
@ -130,36 +222,41 @@ def lag_utvidelser(kopi: Path, filer: dict[str, str]) -> None:
|
|||
mappe = kopi / "modell" / "utvidelser"
|
||||
mappe.mkdir(exist_ok=True)
|
||||
for gammel in mappe.glob("*.py"):
|
||||
gammel.unlink()
|
||||
if gammel.name != "__init__.py":
|
||||
gammel.unlink()
|
||||
for navn, innhold in filer.items():
|
||||
(mappe / f"{navn}.py").write_text(innhold, encoding="utf-8")
|
||||
|
||||
|
||||
def test_utvidelse_som_gjenskaper_kjernen(kopi: Path, hjem: Path) -> None:
|
||||
lag_utvidelser(kopi, {
|
||||
def test_utvidelse_som_gjenskaper_kjernen(lett_kopi: Path, hjem: Path) -> None:
|
||||
lag_utvidelser(lett_kopi, {
|
||||
"eksakt": "import sim\n\ndef grensetilfelle():\n return [('p3 med trappetrinn', 0.0)]\n",
|
||||
"_hjelper": "raise RuntimeError('filer som begynner med _ skal ikke kjøres')\n",
|
||||
})
|
||||
prosess = kjor("--modell", str(kopi), "kontroll", "--rask", hjem=hjem)
|
||||
prosess = kjor("--modell", str(lett_kopi), "kontroll", "--rask", hjem=hjem)
|
||||
assert prosess.returncode == 0, prosess.stdout + prosess.stderr
|
||||
assert "5. Utvidelser: grensetilfellene gjenskaper kjernen eksakt (eksakt)." in prosess.stdout
|
||||
|
||||
|
||||
def test_utvidelser_som_ikke_gjenskaper_kjernen(kopi: Path, hjem: Path) -> None:
|
||||
lag_utvidelser(kopi, {
|
||||
"avvik": "def grensetilfelle():\n return [('p3 med trappetrinn', 1e-12), ('p6', 0.0)]\n",
|
||||
def test_utvidelser_som_ikke_gjenskaper_kjernen(lett_kopi: Path, hjem: Path) -> None:
|
||||
lag_utvidelser(lett_kopi, {
|
||||
"avvik": "def grensetilfelle():\n return [('avvik: trapp lik notatets sjokk', 1e-12), ('p6', 0.0)]\n",
|
||||
"tom": "def grensetilfelle():\n return []\n",
|
||||
"odelagt": "import sim\nraise RuntimeError('i stykker')\n",
|
||||
})
|
||||
prosess = kjor("--modell", str(kopi), "kontroll", "--rask", hjem=hjem)
|
||||
(lett_kopi / "LES-MEG.md").write_text("Arbeidskopi\n", encoding="utf-8")
|
||||
prosess = kjor("--modell", str(lett_kopi), "kontroll", "--rask", hjem=hjem)
|
||||
assert prosess.returncode == 1, prosess.stdout + prosess.stderr
|
||||
assert "5. Utvidelser: grensetilfellet gjenskaper ikke kjernen eksakt (avvik, odelagt, tom; se under)." in (
|
||||
prosess.stdout)
|
||||
assert "avvik: p3 med trappetrinn: avvik 1,0e-12, skal være eksakt 0" in prosess.stdout
|
||||
assert ("5. Utvidelser: avvik, odelagt, tom gjenskaper ikke kjernen i grensetilfellet. Det er en feil i "
|
||||
"utvidelsen, og resultatene den gir, kan ikke brukes (se under).") in prosess.stdout
|
||||
assert " avvik: trapp lik notatets sjokk: avvik 1,0e-12, skal være eksakt 0" in prosess.stdout
|
||||
assert "avvik: avvik:" not in prosess.stdout
|
||||
assert "tom: grensetilfelle() ga ingen tilfeller" in prosess.stdout
|
||||
assert "odelagt: grensetilfelle() kunne ikke kjøres" in prosess.stdout
|
||||
assert "RuntimeError: i stykker" in prosess.stdout
|
||||
assert "Nivå (uten full kjøring, --rask): endret: Utvidelser." in prosess.stdout
|
||||
assert "har en feil. Resultatene den gir, kan ikke brukes før den er rettet." in prosess.stdout
|
||||
assert "opplysning" not in prosess.stdout
|
||||
|
||||
|
||||
@pytest.mark.treg
|
||||
|
|
@ -167,4 +264,5 @@ def test_full_kontroll(hjem: Path) -> None:
|
|||
prosess = kjor("kontroll", hjem=hjem)
|
||||
assert prosess.returncode == 0, prosess.stdout + prosess.stderr
|
||||
assert "4. Full kjøring: 22 av 22 datafiler like byte for byte med notatet; utskriften lik." in prosess.stdout
|
||||
assert "6. Scenariokjøreren: 14 av 14 av notatets datafiler gjenskapt byte for byte" in prosess.stdout
|
||||
assert "\nNivå: bit for bit som notatet.\n" in prosess.stdout
|
||||
|
|
|
|||
|
|
@ -302,3 +302,271 @@ def test_alle_eksempelscenarioer_er_gyldige() -> None:
|
|||
assert sc.kjoringer, fil.name
|
||||
if fil.parent == NYE:
|
||||
assert sc.beskrivelse, f"{fil.name} mangler beskrivelse"
|
||||
|
||||
|
||||
# ---------------- startlikevekten (gjennomgang E, funn 1) ----------------
|
||||
|
||||
def test_endret_grunnparameter_stopper_med_to_veier_ut(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
|
||||
fil = skriv(tmp_path, {"modell": "simulate_chain", "parametere": {"P": {"gamma": 0.15}}, "sjokk": {"faktor": 1.0}})
|
||||
kode, _, err = kjor_fil(fil, tmp_path / "ut", capsys)
|
||||
assert kode == 2
|
||||
for t in ("startlikevekt: startpunktet er ikke en likevekt", "pådraget Omega0 (0,12)",
|
||||
"ville drevet bort fra 1,00 også uten sjokk", "passer ikke lenger til strømmen",
|
||||
'"startlikevekt": "kalibrer"', "(P.Omega0, CH0.Z, CH0.m)", '"startlikevekt": "ubalanse"'):
|
||||
assert t in err, err
|
||||
assert not (tmp_path / "ut").exists()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("p", "modell"), [({"Lam0": 0.6}, "simulate"), ({"delta": 0.03}, "simulate"),
|
||||
({"T0": 1.2}, "simulate"), ({"eta": 2.0}, "simulate"),
|
||||
({"Omega0": 0.13}, "simulate"), ({"Kstar": 1.1}, "simulate"),
|
||||
({"Tprop": 0.9}, "simulate"), ({"Tmax": 0.9}, "simulate"),
|
||||
({"gamma": 0.1}, "simulate_soc"), ({"Lam0": 0.7}, "simulate_chain2")])
|
||||
def test_hver_grunnparameter_i_likevekten_sjekkes(p: dict[str, float], modell: str, tmp_path: Path,
|
||||
capsys: pytest.CaptureFixture[str]) -> None:
|
||||
fil = skriv(tmp_path, {"modell": modell, "parametere": {"P": p}})
|
||||
kode, _, err = kjor_fil(fil, tmp_path / "ut", capsys)
|
||||
assert kode == 2
|
||||
assert "startpunktet er ikke en likevekt" in err, err
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("modell", "par"), [("simulate", {"P": {"ksat": 1.5}}),
|
||||
("simulate_soc", {"SOC": {"eps": 0.5}}),
|
||||
("simulate_chain", {"CH0": {"Z": 0.12}}),
|
||||
("simulate_chain", {"CH0": {"varsigma": 0.02}})])
|
||||
def test_parametere_kjernen_selv_rekalibrerer_stopper_ikke(
|
||||
modell: str, par: dict[str, object], tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
|
||||
fil = skriv(tmp_path, {"modell": modell, "parametere": par})
|
||||
kode, _, err = kjor_fil(fil, tmp_path / "ut", capsys)
|
||||
assert kode == 0, err
|
||||
|
||||
|
||||
def test_kalibrer_gir_ro_for_sjokket(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
|
||||
fil = skriv(tmp_path, {"navn": "k", "modell": "simulate", "startlikevekt": "kalibrer",
|
||||
"parametere": {"P": {"Lam0": 0.6, "gamma": 0.2}}, "sjokk": {"faktor": 1.0}})
|
||||
kode, ut, err = kjor_fil(fil, tmp_path / "ut", capsys)
|
||||
assert kode == 0, err
|
||||
assert "Referansetilstanden er kalibrert på nytt" in ut
|
||||
assert "P.Omega0 0,14 (var 0,12)" in ut
|
||||
assert "tilliten er 0,60 der" in ut
|
||||
rader = (tmp_path / "ut" / "k.csv").read_text(encoding="utf-8").splitlines()[1:]
|
||||
assert all(abs(float(r.split(",")[1]) - 1.0) < 1e-9 for r in rader) # K står i ro
|
||||
data = json.loads((tmp_path / "ut" / "resultat.json").read_text(encoding="utf-8"))
|
||||
kalibrert = data["kjoringer"][0]["startlikevekt"]["kalibrert"]
|
||||
assert kalibrert["P.Omega0"]["for"] == 0.12
|
||||
assert data["kjoringer"][0]["brukt"]["parametere"]["P"]["Omega0"] == kalibrert["P.Omega0"]["etter"]
|
||||
|
||||
|
||||
def test_kalibrer_kjeden_regner_om_z_og_m(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
|
||||
fil = skriv(tmp_path, {"modell": "simulate_chain", "startlikevekt": "kalibrer",
|
||||
"parametere": {"P": {"gamma": 0.15}}})
|
||||
kode, _, err = kjor_fil(fil, tmp_path / "ut", capsys)
|
||||
assert kode == 0, err
|
||||
brukt = json.loads((tmp_path / "ut" / "resultat.json").read_text(encoding="utf-8"))["kjoringer"][0]["brukt"]
|
||||
j0 = 0.15 * 0.8 * 1.0
|
||||
assert brukt["parametere"]["CH0"]["Z"] == j0 / 0.95 # sim.py:151
|
||||
assert brukt["parametere"]["CH0"]["m"] == 3.0 - j0 / (j0 / 0.95 * (1 / 3.0 + 0.01)) # sim.py:155
|
||||
assert brukt["parametere"]["P"]["Omega0"] == pytest.approx(j0 + 0.02, abs=1e-15)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("p", [{"Kstar": 1.1}, {"Omega0": 0.13}, {"Tprop": 0.9}])
|
||||
def test_kalibrer_retter_ikke_det_som_er_satt_eller_ligger_fast(
|
||||
p: dict[str, float], tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
|
||||
fil = skriv(tmp_path, {"modell": "simulate", "startlikevekt": "kalibrer", "parametere": {"P": p}})
|
||||
kode, _, err = kjor_fil(fil, tmp_path / "ut", capsys)
|
||||
assert kode == 2
|
||||
assert '"kalibrer" kan ikke gjøre starten til en likevekt' in err
|
||||
|
||||
|
||||
def test_ubalanse_kjorer_og_viser_driften(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
|
||||
fil = skriv(tmp_path, {"modell": "simulate", "startlikevekt": "ubalanse", "parametere": {"P": {"Lam0": 0.6}},
|
||||
"sjokk": {"faktor": 1.0}})
|
||||
kode, ut, err = kjor_fil(fil, tmp_path / "ut", capsys)
|
||||
assert kode == 0, err
|
||||
assert "1,00 er nivået i referanselikevekten" not in ut
|
||||
assert 'startpunktet er ikke en likevekt ("startlikevekt": "ubalanse"): pådraget Omega0' in ut
|
||||
assert "Fram til måned 12 har K gått fra 1,000 til 1,0" in ut
|
||||
data = json.loads((tmp_path / "ut" / "resultat.json").read_text(encoding="utf-8"))
|
||||
assert data["kjoringer"][0]["startlikevekt"]["ubalanse"]
|
||||
|
||||
|
||||
def test_ubalanse_med_sjokk_viser_driften_til_sjokkmaaneden(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
|
||||
fil = skriv(tmp_path, {"modell": "simulate_chain", "startlikevekt": "ubalanse",
|
||||
"parametere": {"P": {"gamma": 0.15}}, "sjokk": {"fra": 18}})
|
||||
kode, ut, _ = kjor_fil(fil, tmp_path / "ut", capsys)
|
||||
assert kode == 0
|
||||
assert "Fram til måned 18 har K gått fra 1,000 til" in ut
|
||||
assert "saksbehandlingstiden (mnd) fra 15,7 til" in ut
|
||||
|
||||
|
||||
@pytest.mark.parametrize("valg", ["krev", "kalibrer", "ubalanse"])
|
||||
def test_simulate_endo_med_annen_strom_ved_start_stopper_alltid(
|
||||
valg: str, tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
|
||||
fil = skriv(tmp_path, {"modell": "simulate_endo", "regel": "koord", "startlikevekt": valg,
|
||||
"parametere": {"P": {"gamma": 0.11}}})
|
||||
kode, _, err = kjor_fil(fil, tmp_path / "ut", capsys)
|
||||
assert kode == 2
|
||||
assert "sim.py:588" in err
|
||||
assert "endring i kjernen" in err
|
||||
|
||||
|
||||
def test_eta_i_kjeden_kan_ikke_kalibreres(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
|
||||
fil = skriv(tmp_path, {"modell": "simulate_chain", "startlikevekt": "kalibrer", "parametere": {"P": {"eta": 2.0}}})
|
||||
kode, _, err = kjor_fil(fil, tmp_path / "ut", capsys)
|
||||
assert kode == 2
|
||||
assert "uten eta" in err
|
||||
|
||||
|
||||
# ---------------- funn 2, 7, 8, 12, 13, 14 og kapasitet i simulate_chain2 ----------------
|
||||
|
||||
def test_kjoring_som_eksploderer_stopper_for_noe_skrives(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
|
||||
fil = skriv(tmp_path, {"modell": "utvidelse:paadrag_tidsserie",
|
||||
"utvidelse": {"punkter": [[12, 1e308], [13, 1e308]]}})
|
||||
kode, ut, err = kjor_fil(fil, tmp_path / "ut", capsys)
|
||||
assert kode == 2
|
||||
assert ut == ""
|
||||
assert "ikke er endelige tall" in err
|
||||
assert "Ingenting er skrevet" in err
|
||||
assert not (tmp_path / "ut").exists()
|
||||
|
||||
|
||||
def test_absurd_k_gir_merknad(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
|
||||
fil = skriv(tmp_path, {"modell": "simulate", "sjokk": {"faktor": 1e6}})
|
||||
kode, ut, _ = kjor_fil(fil, tmp_path / "ut", capsys)
|
||||
assert kode == 0
|
||||
assert "## Merknader (kjøringen er ikke stoppet)" in ut
|
||||
assert "utenfor området kjernen selv regner på" in ut
|
||||
|
||||
|
||||
def test_notatets_vippekjoring_gir_ingen_merknad(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
|
||||
assert kjor_fil(NOTATET / "vippe.json", tmp_path / "ut", capsys)[1].count("Merknader") == 0
|
||||
|
||||
|
||||
def test_mappe_som_ikke_kan_skrives(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
|
||||
fil = skriv(tmp_path, {"modell": "simulate"})
|
||||
laast = tmp_path / "laast"
|
||||
laast.mkdir()
|
||||
laast.chmod(0o500)
|
||||
try:
|
||||
kode, _, err = kjor_fil(fil, laast / "ut", capsys)
|
||||
finally:
|
||||
laast.chmod(0o700)
|
||||
assert kode == 2
|
||||
assert err.startswith(f"Kan ikke skrive i {laast / 'ut'}: ")
|
||||
assert err.rstrip().endswith("Velg en annen mappe med --ut.")
|
||||
assert "Traceback" not in err
|
||||
|
||||
|
||||
def test_koen_har_enhet(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
|
||||
kode, ut, _ = kjor_fil(NOTATET / "kjede_s2.json", tmp_path / "ut", capsys)
|
||||
assert kode == 0
|
||||
assert "Kø i domstolen ved slutt (mnd innstrømning før sjokket)" in ut
|
||||
assert "Køen er målt i måneder av innstrømningen til domstolen før sjokket" in ut
|
||||
data = json.loads((tmp_path / "ut" / "resultat.json").read_text(encoding="utf-8"))
|
||||
assert data["kjoringer"][0]["nokkeltall"]["Q_slutt_mnd"] == pytest.approx(56.4, abs=0.05) # Qend 5,64 / 0,1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("periode", "tekst"), [(0.07, "minst 1"), (1.5, "helt antall måneder")])
|
||||
def test_budsjettsyklus_er_hele_maaneder(periode: float, tekst: str, tmp_path: Path,
|
||||
capsys: pytest.CaptureFixture[str]) -> None:
|
||||
fil = skriv(tmp_path, {"modell": "simulate_endo", "regel": "koord", "parametere": {"ENDO": {"period": periode}}})
|
||||
kode, _, err = kjor_fil(fil, tmp_path / "ut", capsys)
|
||||
assert kode == 2
|
||||
assert "parametere.ENDO.period" in err
|
||||
assert tekst in err
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("data", "tekst"), [
|
||||
({"modell": "simulate", "slutt": 0.04, "styring": {"forsinkelse": 0}}, "slutt: må være et tall minst 1"),
|
||||
({"modell": "simulate", "slutt": 2}, "slutt: horisonten (2 mnd) er kortere enn forsinkelsen"),
|
||||
({"modell": "simulate", "sveip": {"parameter": "slutt", "verdier": [2]}}, "slutt: horisonten (2 mnd)"),
|
||||
])
|
||||
def test_for_kort_horisont_navngir_slutt(data: dict[str, object], tekst: str, tmp_path: Path,
|
||||
capsys: pytest.CaptureFixture[str]) -> None:
|
||||
kode, _, err = kjor_fil(skriv(tmp_path, data), tmp_path / "ut", capsys)
|
||||
assert kode == 2
|
||||
assert tekst in err, err
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("data", "tekst"), [
|
||||
({"modell": "simulate_chain", "slutt": 6}, "Sjokket kommer i måned 12, ikke før slutt (6)"),
|
||||
({"modell": "simulate_soc", "slutt": 30}, "Sjokket varer til måned 36, etter slutt (30)"),
|
||||
])
|
||||
def test_sjokk_utenfor_horisonten_gir_merknad(data: dict[str, object], tekst: str, tmp_path: Path,
|
||||
capsys: pytest.CaptureFixture[str]) -> None:
|
||||
kode, ut, _ = kjor_fil(skriv(tmp_path, data), tmp_path / "ut", capsys)
|
||||
assert kode == 0
|
||||
assert tekst in ut
|
||||
|
||||
|
||||
@pytest.mark.parametrize("innhold", [{"kjoringer": [{"fil": "viktig.docx"}]},
|
||||
{"scenario": "x", "scenariofil_sha256": "", "kjerne_sha256": {},
|
||||
"kjoringer": [{"fil": "viktig.docx"}, {"fil": "../utenfor.csv"}]}])
|
||||
def test_sletter_aldri_andres_filer(innhold: dict[str, object], tmp_path: Path,
|
||||
capsys: pytest.CaptureFixture[str]) -> None:
|
||||
ut = tmp_path / "ut"
|
||||
ut.mkdir()
|
||||
(ut / "resultat.json").write_text(json.dumps(innhold), encoding="utf-8")
|
||||
(ut / "viktig.docx").write_text("x", encoding="utf-8")
|
||||
(tmp_path / "utenfor.csv").write_text("x", encoding="utf-8")
|
||||
kjor_fil(skriv(tmp_path, {"navn": "n", "modell": "simulate"}), ut, capsys)
|
||||
assert (ut / "viktig.docx").is_file()
|
||||
assert (tmp_path / "utenfor.csv").is_file()
|
||||
|
||||
|
||||
def test_kapasitet_i_simulate_chain2_vises(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
|
||||
fil = skriv(tmp_path, {"modell": "simulate_chain2", "tiltak": {"kapasitet": {"faktor": 1.34},
|
||||
"andel_utenom": {"andel": 0.2}}})
|
||||
kode, ut, _ = kjor_fil(fil, tmp_path / "ut", capsys)
|
||||
assert kode == 0
|
||||
tall = json.loads((tmp_path / "ut" / "resultat.json").read_text(encoding="utf-8"))["kjoringer"][0]["nokkeltall"]
|
||||
assert tall["Z_slutt"] == pytest.approx(1.34)
|
||||
assert tall["utenom_slutt_pst"] == pytest.approx(20.0)
|
||||
assert "Kapasitet, ved slutt (1,00 = ved start)" in ut
|
||||
|
||||
|
||||
def test_bruk_nevner_riktig_python() -> None:
|
||||
assert scenario.BRUK.startswith(f"Bruk: {scenario.PYTHON} modell/kjor.py scenario")
|
||||
|
||||
|
||||
# ---------------- modeller med kø driver sakte før sjokket ----------------
|
||||
|
||||
def test_modell_med_ko_viser_driften_for_sjokket(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
|
||||
kode, ut, _ = kjor_fil(NOTATET / "kjede_s2.json", tmp_path / "ut", capsys)
|
||||
assert kode == 0
|
||||
assert "1,00 er nivået ved start for kriminaliteten K og politiets innsats T (notatets referansetilstand)" in ut
|
||||
assert "nesten, men ikke helt, en likevekt" in ut
|
||||
assert "referanselikevekten" not in ut
|
||||
assert "i ro" not in ut
|
||||
assert "Fram til måned 12 har K gått fra 1,000 til 1,007, innsatsen T fra 1,00 til 1,02" in ut
|
||||
|
||||
|
||||
def test_modell_med_ko_uten_sjokk_sier_at_den_kryper(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
|
||||
kode, ut, _ = kjor_fil(NYE / "uten_sjokk_kjede.json", tmp_path / "ut", capsys)
|
||||
assert kode == 0
|
||||
assert "Fram til måned" not in ut
|
||||
assert 'kryper modellen med kø sakte oppover av seg selv; en kjøring uten sjokk ("sjokk": {"faktor": 1})' in ut
|
||||
data = json.loads((tmp_path / "ut" / "resultat.json").read_text(encoding="utf-8"))
|
||||
tall = data["kjoringer"][0]["nokkeltall"]
|
||||
assert round(tall["K_slutt"], 3) == 1.018
|
||||
assert round(tall["tauD_slutt"], 1) == 4.6
|
||||
|
||||
|
||||
@pytest.mark.parametrize("modell", ["simulate_chain2", "simulate_endo", "utvidelse:paadrag_tidsserie"])
|
||||
def test_alle_modeller_med_ko_far_driftlinjen(modell: str, tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
|
||||
data: dict[str, object] = {"modell": modell}
|
||||
if modell == "simulate_endo":
|
||||
data["regel"] = "koord"
|
||||
kode, ut, _ = kjor_fil(skriv(tmp_path, data), tmp_path / "ut", capsys)
|
||||
assert kode == 0
|
||||
assert "nesten, men ikke helt, en likevekt" in ut
|
||||
assert "Fram til måned 12 har K gått fra 1,000 til" in ut
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fil", ["p3", "vippe"])
|
||||
def test_modell_uten_ko_har_ingen_driftlinje(fil: str, tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
|
||||
kode, ut, _ = kjor_fil(NOTATET / f"{fil}.json", tmp_path / "ut", capsys)
|
||||
assert kode == 0
|
||||
assert "Slik leses tallene: 1,00 er nivået i referanselikevekten før sjokket" in ut
|
||||
assert "Fram til måned" not in ut
|
||||
assert "nesten, men ikke helt" not in ut
|
||||
|
|
|
|||
|
|
@ -156,3 +156,42 @@ def test_malen_kjorer_med_scenariofil(modell_med_mal: Path, tmp_path: Path) -> N
|
|||
assert prosess.returncode == 0, prosess.stderr
|
||||
assert (tmp_path / "en" / "en.dat").read_bytes() == (FASIT / "data" / "p3.dat").read_bytes()
|
||||
assert (tmp_path / "annen" / "annen.dat").read_bytes() != (FASIT / "data" / "p3.dat").read_bytes()
|
||||
|
||||
|
||||
# ---------------- uavhengig av utvidelsens egen beregning: gjennom kjøreren mot fasit ----------------
|
||||
|
||||
NOTATFILER = [(navn, fil) for navn in UTVIDELSER
|
||||
for fil in getattr(scenario.last_utvidelse(navn), "GJENSKAPER_NOTATET", ())]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("navn", UTVIDELSER)
|
||||
def test_utvidelsen_oppgir_notatfiler(navn: str) -> None:
|
||||
filer = getattr(scenario.last_utvidelse(navn), "GJENSKAPER_NOTATET", None)
|
||||
if filer is None:
|
||||
pytest.skip(f"{navn} oppgir ikke GJENSKAPER_NOTATET; grensetilfellet er bare sjekket med egen beregning")
|
||||
assert filer, f"{navn}: GJENSKAPER_NOTATET er tom"
|
||||
for fil in filer:
|
||||
assert (ROT / "eksempler" / "scenarioer" / "notatet" / f"{fil}.json").is_file(), fil
|
||||
assert (FASIT / "data" / f"{fil}.dat").is_file(), fil
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("navn", "fil"), NOTATFILER)
|
||||
def test_standardverdiene_gjenskaper_notatfilene_gjennom_kjoreren(
|
||||
navn: str, fil: str, tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None:
|
||||
data = json.loads((ROT / "eksempler" / "scenarioer" / "notatet" / f"{fil}.json").read_text(encoding="utf-8"))
|
||||
data["modell"] = f"utvidelse:{navn}"
|
||||
kode, err = kjor_fil(data, tmp_path, capsys)
|
||||
assert kode == 0, err
|
||||
assert (tmp_path / "ut" / f"{fil}.dat").read_bytes() == (FASIT / "data" / f"{fil}.dat").read_bytes()
|
||||
|
||||
|
||||
def test_utvidelsen_stoppes_ved_ubalanse_og_kan_kalibreres(tmp_path: Path,
|
||||
capsys: pytest.CaptureFixture[str]) -> None:
|
||||
data: dict[str, object] = {"navn": "u", "modell": "utvidelse:paadrag_tidsserie",
|
||||
"parametere": {"P": {"gamma": 0.15}}}
|
||||
kode, err = kjor_fil(data, tmp_path, capsys)
|
||||
assert kode == 2
|
||||
assert "startpunktet er ikke en likevekt" in err
|
||||
data["startlikevekt"] = "kalibrer"
|
||||
kode, err = kjor_fil(data, tmp_path, capsys)
|
||||
assert kode == 0, err
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue