# Copyright (c) 2026 Kjell Tore Guttormsen. MIT License, see LICENSE. """kontroll.py: sammenligningene, dommene og nivået.""" from __future__ import annotations import sys from typing import TYPE_CHECKING import kontroll import pytest from conftest import kjor if TYPE_CHECKING: from pathlib import Path # ---------------- sammenligningene ---------------- @pytest.mark.parametrize(("tall", "forventet"), [("19", 1.0), ("1,28", 0.01), ("7.21", 0.01), ("0.00000", 1e-5), ("1.11e-15", 1e-17), ("-3.0E+02", 10.0)]) def test_enhet_i_siste_siffer(tall: str, forventet: float) -> None: assert kontroll.enhet(tall) == pytest.approx(forventet) def test_tekst_lik() -> None: assert kontroll.sammenlign_tekst("a 1,23\n", "a 1,23\n").status == kontroll.LIK def test_tekst_innenfor_toleranse() -> None: resultat = kontroll.sammenlign_tekst("utnyttelse 1,23 og 7.21\n", "utnyttelse 1,24 og 7.20\n") assert resultat.status == kontroll.TOLERANSE assert resultat.avvik == pytest.approx(0.01) assert "-utnyttelse 1,23 og 7.21" in resultat.detaljer assert "+utnyttelse 1,24 og 7.20" in resultat.detaljer @pytest.mark.parametrize(("fasit", "naa"), [("a 1,23\n", "a 1,25\n"), ("a 1,23\n", "b 1,23\n"), ("a 1,23\n", "a 1,23\nb\n"), ("19 %\n", "21 %\n")]) def test_tekst_endret(fasit: str, naa: str) -> None: resultat = kontroll.sammenlign_tekst(fasit, naa) assert resultat.status == kontroll.ENDRET assert resultat.detaljer DAT = "t K Q\n0.00000 1.00000 0.29100\n0.50000 1.10000 0.30000\n" def test_dat_lik() -> None: assert kontroll.sammenlign_dat(DAT, DAT).status == kontroll.LIK def test_dat_innenfor_toleranse() -> None: resultat = kontroll.sammenlign_dat(DAT, DAT.replace("1.10000", "1.10001")) assert resultat.status == kontroll.TOLERANSE assert resultat.detaljer == ["største absolutte avvik per kolonne: K 1,0e-05"] def test_dat_endret_viser_avvik_per_kolonne() -> None: resultat = kontroll.sammenlign_dat(DAT, DAT.replace("1.10000", "1.10030").replace("0.30000", "0.30200")) assert resultat.status == kontroll.ENDRET assert resultat.detaljer == ["største absolutte avvik per kolonne: K 3,0e-04, Q 2,0e-03"] assert resultat.avvik == pytest.approx(0.002) @pytest.mark.parametrize("naa", ["t K X\n0.00000 1.00000 0.29100\n0.50000 1.10000 0.30000\n", "t K Q\n0.00000 1.00000 0.29100\n"]) def test_dat_ulik_form(naa: str) -> None: assert kontroll.sammenlign_dat(DAT, naa).status == kontroll.ENDRET # ---------------- kontrollen som helhet ---------------- 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 "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 "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: 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 def endre_diagnose(kopi: Path) -> None: """Endre målutnyttelsen i kopiens diagnose.py fra 0,95 til 0,90.""" fil = kopi / "modell" / "kjerne" / "diagnose.py" tekst = fil.read_text(encoding="utf-8") assert "UPS_STAR = 0.95" in tekst 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) 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 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) 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) assert prosess.returncode == 1 assert "norge_2025: fant ikke" in prosess.stdout def lag_utvidelser(kopi: Path, filer: dict[str, str]) -> None: """Legg utvidelser i kopiens modell/utvidelser/ (og fjern dem som lå der).""" mappe = kopi / "modell" / "utvidelser" mappe.mkdir(exist_ok=True) for gammel in mappe.glob("*.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, { "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) 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", "tom": "def grensetilfelle():\n return []\n", "odelagt": "import sim\nraise RuntimeError('i stykker')\n", }) prosess = kjor("--modell", str(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 "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 @pytest.mark.treg 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 "\nNivå: bit for bit som notatet.\n" in prosess.stdout