# -*- coding: utf-8 -*- """Tests fuer lib/tro_ids.py: ID-Datei lesen/schreiben, TROs ueber die Lage wiederfinden (doc/Konzept_Layout_Hardware_SCL.md, Abschnitt TRO-IDs).""" from __future__ import annotations from dataclasses import dataclass, field import pytest from tro_ids import ( IdEntry, TroIds, assign_ids, insert_entries, load_ids, other_sections, render_ids, render_new_entries, ) @dataclass class FakeTro: separators: list[str] type_name: str = "1Sep" label: str = "" tro_id: str = "" hosts: list[str] = field(default_factory=list) ANCHORS = { "0001": (0.0, 0.0), "0002": (1000.0, 0.0), "0003": (2000.0, 0.0), "0004": (2000.0, 500.0), } def _tros(*seps: list[str]) -> list[FakeTro]: return [FakeTro(separators=list(s)) for s in seps] def test_ohne_eintraege_wird_durchnummeriert(): tros = _tros(["0001"], ["0002"], ["0003"]) result = assign_ids(tros, ANCHORS, []) assert result.renumbered assert [t.tro_id for t in tros] == ["TRO01", "TRO02", "TRO03"] def test_wiederfinden_ueber_lage_auch_umbenannt(): # Neuer Export: andere TeileIds, gleiche Lage, andere Reihenfolge anchors = {"0101": (2000.0, 0.0), "0102": (0.0, 0.0), "0103": (1000.0, 30.0)} tros = _tros(["0101"], ["0102"], ["0103"]) entries = [IdEntry("TRO01", 0.0, 0.0), IdEntry("K0102_1", 1000.0, 0.0), IdEntry("TRO03", 2000.0, 0.0)] result = assign_ids(tros, anchors, entries) assert [t.tro_id for t in tros] == ["TRO03", "TRO01", "K0102_1"] assert not result.new and not result.missing def test_neue_tros_naechste_freie_nummer_weggefallene_nicht_wiederverwendet(): tros = _tros(["0001"], ["0003"]) entries = [IdEntry("TRO01", 0.0, 0.0), IdEntry("TRO02", 1000.0, 0.0)] result = assign_ids(tros, ANCHORS, entries) assert [t.tro_id for t in tros] == ["TRO01", "TRO03"] assert result.new == ["TRO03"] assert [e.tro_id for e in result.missing] == ["TRO02"] def test_ausserhalb_toleranz_gilt_als_neu(): tros = _tros(["0001"]) entries = [IdEntry("TRO01", 150.0, 0.0)] result = assign_ids(tros, ANCHORS, entries, tolerance=100.0) assert tros[0].tro_id == "TRO02" assert result.missing[0].tro_id == "TRO01" assert assign_ids(tros, ANCHORS, entries, tolerance=200.0).kept[0][0] == "TRO01" def test_zusammengelegte_tros_naechster_eintrag_gewinnt(): # zwei Separatoren, frueher zwei TROs, jetzt ein 2Sep-TRO tros = _tros(["0003", "0004"]) entries = [IdEntry("TRO05", 2000.0, 0.0), IdEntry("TRO06", 2000.0, 510.0)] result = assign_ids(tros, ANCHORS, entries) assert tros[0].tro_id == "TRO05" assert result.merged_into == {"TRO06": "TRO05"} def test_renumber_ignoriert_eintraege(): tros = _tros(["0001"], ["0002"]) result = assign_ids(tros, ANCHORS, [IdEntry("X", 1000.0, 0.0)], renumber=True) assert result.renumbered assert [t.tro_id for t in tros] == ["TRO01", "TRO02"] def test_datei_rundlauf_und_anhaengen(tmp_path): path = tmp_path / "anlage_tro_ids.ini" tros = _tros(["0001"], ["0002"]) assign_ids(tros, ANCHORS, []) path.write_text(render_ids(tros, ANCHORS, "anlage.csv") + "\n[separator]\n; spaeter\n", encoding="utf-8") entries = load_ids(path) assert [(e.tro_id, e.x, e.y) for e in entries] == [ ("TRO01", 0.0, 0.0), ("TRO02", 1000.0, 0.0)] tros = _tros(["0001"], ["0002"], ["0003"]) result = assign_ids(tros, ANCHORS, entries) insert_entries(path, render_new_entries(tros, ANCHORS, result.new, "anlage.csv")) assert [e.tro_id for e in load_ids(path)] == ["TRO01", "TRO02", "TRO03"] # neuer Eintrag steht in [tro], nicht im folgenden Abschnitt assert other_sections(path) == "[separator]\n; spaeter" assert TroIds.load(path).exists @pytest.mark.parametrize("inhalt, meldung", [ ("[tro]\nTRO01 = 1, 2\nTRO01 = 3, 4\n", "already exists"), ("[tro]\nSYS1 = 1, 2\n", "reserviert"), ("[tro]\nTRO-1 = 1, 2\n", "SCL-Bezeichner"), ("[tro]\nTRO01 = 1\n", "erwartet"), ("[andere]\n", "fehlt"), ]) def test_fehlerhafte_datei(tmp_path, inhalt, meldung): path = tmp_path / "x.ini" path.write_text(inhalt, encoding="utf-8") with pytest.raises(ValueError, match=meldung): load_ids(path)