# 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"]] assert "kontrolleres da med toleranse, ikke bit for bit" in capsys.readouterr().out 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