dynamisk-rettsstat/tests/test_kjor.py
Kjell Tore Guttormsen 13f6c28dc0
fix: findings from the Cowork test, released as v0.1.1
The operator tested v0.1.0 in Cowork on 2026-11-11. README now says what
was measured there and what was not.

- miljo: no longer claims that kontroll compares "with tolerance, not bit
  for bit" when numpy or scipy differ from the note's versions. kontroll
  always compares byte for byte first, and numpy 2.5.3 gave bit for bit.
- kontroll --alt: show every differing line. The cut-off line now names
  the option instead of only counting the lines left out.
- skill simuler: the question before a run must be answerable with yes or
  no. In the test it became a choice between variants and the run stalled.
- skill forbedre: offer the working copy as a zip when the user cannot
  open the folder it was made in, as in Cowork.

263 tests pass; full kontroll: bit for bit with the note.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-11 20:24:58 +01:00

520 lines
22 KiB
Python

# Copyright (c) 2026 Kjell Tore Guttormsen. MIT License, see LICENSE.
"""kjor.py: kommandoer, exit-koder, --data, --modell og valg av Python (uten å installere noe)."""
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
# ---------------- bruk og exit-koder ----------------
def test_uten_kommando_gir_bruk_og_kode_2() -> None:
prosess = kjor()
assert prosess.returncode == 2
assert prosess.stderr.startswith("Bruk: python3 kjor.py")
@pytest.mark.parametrize("valg", ["--hjelp", "-h", "--help"])
def test_hjelp_gir_kode_0(valg: str) -> None:
prosess = kjor(valg)
assert prosess.returncode == 0
for kommando in kjor_modul.KOMMANDOER:
assert f" {kommando} " in prosess.stdout
def test_ukjent_kommando() -> None:
prosess = kjor("simuler", "x.json")
assert prosess.returncode == 2
assert "Ukjent kommando «simuler»" in prosess.stderr
assert "diagnose, forlop, scenario, notatet, kontroll, miljo, arbeidskopi" in prosess.stderr
@pytest.mark.parametrize("args", [["--tull", "diagnose", "x"], ["--data"], ["--data", "mappe"], ["--modell"]])
def test_feil_i_globale_valg(args: list[str]) -> None:
prosess = kjor(*args)
assert prosess.returncode == 2
assert "Skriv «python3 kjor.py --hjelp» for bruk." in prosess.stderr
@pytest.mark.parametrize("args", [["diagnose"], ["diagnose", "a", "b"], ["forlop"], ["forlop", "a", "b", "c"],
["miljo", "--tull"], ["notatet", "--ut"], ["notatet", "x"],
["kontroll", "--tull"]])
def test_feil_antall_argumenter(args: list[str]) -> None:
prosess = kjor(*args)
assert prosess.returncode == 2, prosess.stdout + prosess.stderr
# ---------------- diagnose og forlop ----------------
@pytest.mark.parametrize(("fil", "navn"), [(DOMSTOL, "eksempel_domstol"), (NORGE, "norge_2025")])
def test_diagnose_er_lik_fasit(fil: Path, navn: str) -> None:
prosess = kjor("diagnose", str(fil))
assert prosess.returncode == 0
assert prosess.stdout == fasit(f"diagnose_{navn}.txt")
assert prosess.stderr == ""
@pytest.mark.parametrize(("fil", "navn"), [(DOMSTOL, "eksempel_domstol"), (NORGE, "norge_2025")])
def test_forlop_er_lik_fasit(fil: Path, navn: str) -> None:
prosess = kjor("forlop", str(fil))
assert prosess.returncode == 0, prosess.stderr
assert prosess.stdout == fasit(f"forlop_{navn}.txt")
def test_diagnose_virker_uten_numpy(hjem: Path) -> None:
prosess = kjor("diagnose", str(DOMSTOL), python=UTEN_SITE, hjem=hjem)
assert prosess.returncode == 0
assert prosess.stdout == fasit("diagnose_eksempel_domstol.txt")
@pytest.mark.parametrize("kommando", ["diagnose", "forlop"])
def test_manglende_fil_gir_kode_2(kommando: str, tmp_path: Path) -> None:
fil = tmp_path / "finnes_ikke.json"
prosess = kjor(kommando, str(fil))
assert prosess.returncode == 2
assert prosess.stderr == f"Fant ikke fila {fil}.\n"
def test_kjernens_sys_exit_gir_kode_2_og_meldingen_uendret(tmp_path: Path) -> None:
fil = tmp_path / "tom.json"
fil.write_text('{"ledd": []}', encoding="utf-8")
for kommando in ("diagnose", "forlop"):
prosess = kjor(kommando, str(fil))
assert prosess.returncode == 2
assert prosess.stderr == 'Feil i inndata: fila må ha en liste "ledd" med minst ett ledd.\n'
assert prosess.stdout == ""
def test_forlop_med_ukjent_ledd() -> None:
prosess = kjor("forlop", str(DOMSTOL), "Tingrett")
assert prosess.returncode == 2
assert prosess.stderr == (
'Fant ikke leddet "Tingrett". Leddene i fila: Politi, Påtale, Domstol, Kriminalomsorg.\n'
)
def test_forlop_med_valgt_ledd() -> None:
prosess = kjor("forlop", str(NORGE), "Tingrett, meddomsrett")
assert prosess.returncode == 0, prosess.stderr
assert "Forløp over tid for leddet du valgte: Tingrett, meddomsrett" in prosess.stdout
def test_ingen_pycache_i_kjernen(kopi: Path) -> None:
miljo = {k: v for k, v in os.environ.items() if k != "PYTHONDONTWRITEBYTECODE"}
prosess = subprocess.run(
[sys.executable, str(kopi / "modell" / "kjor.py"), "forlop", str(DOMSTOL)],
capture_output=True, text=True, encoding="utf-8", env=miljo, check=False,
)
assert prosess.returncode == 0, prosess.stderr
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"])
def test_datamappe_ikke_oppgitt(verdi: str | None) -> None:
assert kjor_modul.datamappe(verdi) == (Path.home() / ".dynamisk-rettsstat", False)
def test_datamappe_oppgitt(tmp_path: Path) -> None:
assert kjor_modul.datamappe(str(tmp_path / "d")) == ((tmp_path / "d").resolve(), True)
assert kjor_modul.datamappe("~/x") == ((Path.home() / "x").resolve(), True)
@pytest.mark.parametrize("verdi", ["", "${CLAUDE_PLUGIN_DATA}"])
def test_data_uten_verdi_bruker_standardmappa(verdi: str, hjem: Path) -> None:
prosess = kjor("--data", verdi, "miljo", hjem=hjem)
assert prosess.returncode == 0
assert f"Egen mappe for numpy og scipy: {hjem / '.dynamisk-rettsstat' / 'venv'}" in prosess.stdout
assert not (hjem / ".dynamisk-rettsstat").exists()
def test_data_med_likhetstegn(tmp_path: Path, hjem: Path) -> None:
prosess = kjor(f"--data={tmp_path / 'd'}", "miljo", hjem=hjem)
assert prosess.returncode == 0
assert f"Egen mappe for numpy og scipy: {(tmp_path / 'd').resolve() / 'venv'}" in prosess.stdout
def test_miljo_viser_versjonene(hjem: Path) -> None:
prosess = kjor("miljo", hjem=hjem)
assert prosess.returncode == 0
assert "numpy: 2.5.4 (notatet: 2.5.4)" in prosess.stdout
assert "scipy: 1.18.1 (notatet: 1.18.1)" in prosess.stdout
assert "regner med Python som kjører nå" in prosess.stdout
assert "kontroll kan gi bit for bit" in prosess.stdout
assert "finnes ikke" in prosess.stdout
def test_miljo_uten_numpy(hjem: Path) -> None:
prosess = kjor("miljo", python=UTEN_SITE, hjem=hjem)
assert prosess.returncode == 0
assert "numpy: mangler" in prosess.stdout
assert "kan ikke regne nå: numpy, scipy mangler" in prosess.stdout
def test_uten_numpy_og_uten_venv_gir_kode_3(tmp_path: Path, hjem: Path) -> None:
data = tmp_path / "data"
prosess = kjor("--data", str(data), "forlop", str(DOMSTOL), python=UTEN_SITE, hjem=hjem)
assert prosess.returncode == 3
assert "«forlop» trenger numpy (2.0 eller nyere) og scipy. Mangler: numpy, scipy." in prosess.stderr
assert f"--data {data.resolve()} miljo --opprett" in prosess.stderr
assert "Ingenting installeres uten at du ber om det." in prosess.stderr
assert not data.exists()
@pytest.mark.parametrize("kommando", [["notatet"], ["forlop", str(DOMSTOL)]])
def test_uten_numpy_i_standardmappa_gir_kode_3(kommando: list[str], hjem: Path) -> None:
prosess = kjor(*kommando, python=UTEN_SITE, hjem=hjem)
assert prosess.returncode == 3
assert f"Fant ingen egen mappe med numpy og scipy i {hjem / '.dynamisk-rettsstat' / 'venv'}" in prosess.stderr
assert not (hjem / ".dynamisk-rettsstat").exists()
def test_venv_i_datamappa_brukes(tmp_path: Path, hjem: Path) -> None:
data = tmp_path / "data"
lag_venv_omvei(data, sys.executable)
prosess = kjor("--data", str(data), "forlop", str(DOMSTOL), python=UTEN_SITE, hjem=hjem)
assert prosess.returncode == 0, prosess.stderr
assert prosess.stdout == fasit("forlop_eksempel_domstol.txt")
def test_venv_i_datamappa_gir_videre_exit_koden(tmp_path: Path, hjem: Path) -> None:
data = tmp_path / "data"
lag_venv_omvei(data, sys.executable)
prosess = kjor("--data", str(data), "forlop", str(DOMSTOL), "Ukjent", python=UTEN_SITE, hjem=hjem)
assert prosess.returncode == 2
assert prosess.stderr.startswith('Fant ikke leddet "Ukjent".')
def test_venv_uten_numpy_gir_kode_3_uten_lokke(tmp_path: Path, hjem: Path) -> None:
data = tmp_path / "data"
lag_venv_omvei(data, sys.executable, "-S")
prosess = kjor("--data", str(data), "forlop", str(DOMSTOL), python=UTEN_SITE, hjem=hjem)
assert prosess.returncode == 3
assert "mangler dem også" in prosess.stderr
def beskrivelse(*, numpy: str | None, scipy: str | None, trapezoid: bool = True) -> kjor_modul.Beskrivelse:
"""En oppdiktet beskrivelse av en Python-tolk."""
return kjor_modul.Beskrivelse(python="3.9.6", sti="/x/python", numpy=numpy, trapezoid=trapezoid, scipy=scipy)
def test_mangler_i_ord() -> None:
assert kjor_modul.mangler(beskrivelse(numpy="2.5.4", scipy="1.18.1")) == []
assert kjor_modul.mangler(beskrivelse(numpy=None, scipy=None)) == ["numpy", "scipy"]
assert kjor_modul.mangler(beskrivelse(numpy="1.26.4", scipy="1.13.0", trapezoid=False)) == [
"numpy 2.0 eller nyere (har 1.26.4)"]
def test_velg_tolk(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv(kjor_modul.VENV_MERKE, raising=False)
monkeypatch.setattr(kjor_modul, "beskriv_denne", lambda: beskrivelse(numpy="2.5.4", scipy="1.18.1"))
assert kjor_modul.velg_tolk(tmp_path).hvor == "denne"
monkeypatch.setattr(kjor_modul, "beskriv_denne", lambda: beskrivelse(numpy="2.4.2", scipy=None))
valg = kjor_modul.velg_tolk(tmp_path)
assert (valg.hvor, valg.mangler) == ("mangler", ["scipy"])
tolk = lag_venv_omvei(tmp_path, sys.executable)
assert kjor_modul.velg_tolk(tmp_path) == kjor_modul.Tolk("venv", tolk, ["scipy"])
monkeypatch.setenv(kjor_modul.VENV_MERKE, "1")
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:
lag_venv_omvei(tmp_path, sys.executable)
kommandoer: list[list[str]] = []
def falsk(kommando: list[str]) -> int:
kommandoer.append(kommando)
return 0
monkeypatch.setattr(kjor_modul, "kjor_prosess", falsk)
monkeypatch.setattr(kjor_modul, "rapport_miljo", lambda _valg: None)
assert kjor_modul.main(["--data", str(tmp_path), "miljo", "--opprett"]) == 0
assert len(kommandoer) == 1
assert kommandoer[0][-2:] == ["-r", str(MODELL / "requirements.txt")]
def test_opprett_faller_tilbake_og_sier_toleranse(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str],
) -> None:
lag_venv_omvei(tmp_path, sys.executable)
kommandoer: list[list[str]] = []
def falsk(kommando: list[str]) -> int:
kommandoer.append(kommando)
return 1 if "-r" in kommando else 0
monkeypatch.setattr(kjor_modul, "kjor_prosess", falsk)
monkeypatch.setattr(kjor_modul, "rapport_miljo", lambda _valg: None)
assert kjor_modul.main(["--data", str(tmp_path), "miljo", "--opprett"]) == 0
assert [k[-2:] for k in kommandoer] == [["-r", str(MODELL / "requirements.txt")], ["numpy>=2.0", "scipy"]]
ut = capsys.readouterr().out
assert "lot seg ikke installere, så nyere versjoner er installert" in ut
assert "bit for bit eller innenfor toleranse" in ut
# Measured in Cowork 2026-11-11: numpy 2.5.3 (not the pinned 2.5.4) still gave bit for bit,
# so the message must not promise a tolerance comparison.
assert "ikke bit for bit" not in ut
def test_opprett_som_feiler_gir_kode_3(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
lag_venv_omvei(tmp_path, sys.executable)
monkeypatch.setattr(kjor_modul, "kjor_prosess", lambda _kommando: 1)
assert kjor_modul.main(["--data", str(tmp_path), "miljo", "--opprett"]) == 3
def test_opprett_lager_venv_med_denne_python(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
kommandoer: list[list[str]] = []
def falsk(kommando: list[str]) -> int:
kommandoer.append(kommando)
return 0 # later som om venv ble laget, men lager ingenting
monkeypatch.setattr(kjor_modul, "kjor_prosess", falsk)
data = tmp_path / "ny"
assert kjor_modul.main(["--data", str(data), "miljo", "--opprett"]) == 3
assert kommandoer == [[sys.executable, "-m", "venv", str(data.resolve() / "venv")]]
# ---------------- --modell, scenario og arbeidskopi ----------------
def test_modell_som_ikke_finnes(tmp_path: Path) -> None:
prosess = kjor("--modell", str(tmp_path), "diagnose", str(DOMSTOL))
assert prosess.returncode == 2
assert f"Fant ingen modell i {tmp_path.resolve()}" in prosess.stderr
@pytest.mark.parametrize("undermappe", ["", "modell"])
def test_modell_i_arbeidskopi(kopi: Path, undermappe: str) -> None:
diagnose = kopi / "modell" / "kjerne" / "diagnose.py"
diagnose.write_text(diagnose.read_text(encoding="utf-8").replace(
'print("Trinn (c): utnyttelse', 'print("ARBEIDSKOPI")\n print("Trinn (c): utnyttelse'), encoding="utf-8")
prosess = kjor("--modell", str(kopi / undermappe), "diagnose", str(DOMSTOL))
assert prosess.returncode == 0, prosess.stderr
assert prosess.stdout == "ARBEIDSKOPI\n" + fasit("diagnose_eksempel_domstol.txt")
@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
FALSK_MODUL = """\
import sys
import sim
def main(argv):
if argv and argv[0] == "stopp":
sys.exit("Feil i scenariofila: feltet «tull» finnes ikke.")
if argv and argv[0] == "kode":
return 1
print(argv, sim.DT)
return 0
"""
@pytest.mark.parametrize("kommando", ["scenario", "arbeidskopi"])
def test_modul_faar_argumentene_og_kjernen(kopi: Path, kommando: str) -> None:
(kopi / "modell" / f"{kommando}.py").write_text(FALSK_MODUL, encoding="utf-8")
prosess = kjor("--modell", str(kopi), kommando, "fil.json", "--ut", "mappe")
assert prosess.returncode == 0, prosess.stderr
assert prosess.stdout == "['fil.json', '--ut', 'mappe'] 0.05\n"
prosess = kjor("--modell", str(kopi), kommando, "stopp")
assert prosess.returncode == 2
assert prosess.stderr == "Feil i scenariofila: feltet «tull» finnes ikke.\n"
assert kjor("--modell", str(kopi), kommando, "kode").returncode == 1
def test_modul_uten_main(kopi: Path) -> None:
(kopi / "modell" / "scenario.py").write_text("X = 1\n", encoding="utf-8")
prosess = kjor("--modell", str(kopi), "scenario", "x")
assert prosess.returncode == 2
assert "mangler funksjonen main(argv)" in prosess.stderr
def test_uventet_feil_i_modul_gir_kode_2(kopi: Path) -> None:
(kopi / "modell" / "scenario.py").write_text("def main(argv):\n return 1 / 0\n", encoding="utf-8")
prosess = kjor("--modell", str(kopi), "scenario", "x")
assert prosess.returncode == 2
assert "ZeroDivisionError" in prosess.stderr
assert "Uventet feil i scenario.py (se over)." in prosess.stderr
# ---------------- notatet ----------------
def test_notatet_nekter_ut_mappe_i_modellen() -> None:
prosess = kjor("notatet", "--ut", str(MODELL / "kjerne"))
assert prosess.returncode == 2
assert "ligger inne i modell-mappa" in prosess.stderr
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"
prosess = kjor("notatet", "--ut", str(ut))
assert prosess.returncode == 0, prosess.stderr
navn = sorted(f.name for f in (FASIT / "data").glob("*.dat"))
assert sorted(f.name for f in (ut / "data").glob("*.dat")) == navn
for fil in navn:
assert (ut / "data" / fil).read_bytes() == (FASIT / "data" / fil).read_bytes(), fil
assert (ut / "sim_utskrift.txt").read_text(encoding="utf-8") == fasit("sim_utskrift.txt")
# ---------------- Python 3.9 ----------------
def python39() -> str | None:
"""En Python 3.9 på maskinen, hvis det finnes en."""
for kandidat in ("/usr/bin/python3", shutil.which("python3.9")):
if not kandidat or not Path(kandidat).exists():
continue
try:
versjon = subprocess.run([kandidat, "-c", "import sys; print(sys.version_info[:2])"],
capture_output=True, text=True, check=False, timeout=60).stdout.strip()
except (OSError, subprocess.TimeoutExpired):
continue
if versjon == "(3, 9)":
return kandidat
return None
PY39 = python39()
@pytest.mark.skipif(PY39 is None, reason="fant ingen Python 3.9 på maskinen")
def test_kjor_og_kontroll_virker_paa_python_39(hjem: Path) -> None:
assert PY39 is not None
prosess = kjor("diagnose", str(EKSEMPLER / "norge_2025.json"), python=[PY39], hjem=hjem)
assert prosess.returncode == 0, prosess.stderr
assert prosess.stdout == fasit("diagnose_norge_2025.txt")
assert kjor("miljo", python=[PY39], hjem=hjem).returncode == 0
har_regnemiljo = subprocess.run([PY39, "-c", "import numpy, scipy.optimize; numpy.trapezoid"],
capture_output=True, check=False).returncode == 0
prosess = kjor("kontroll", "--rask", python=[PY39], hjem=hjem, cwd=ROT)
assert "1. Kjernen: uendret fra notatet" in prosess.stdout
assert "2. diagnose: lik notatet" in prosess.stdout
assert prosess.returncode == (0 if har_regnemiljo else 3), prosess.stdout + prosess.stderr