"""Tests fuer den Anzeigetreiber -- Zeilenaufbau und die festen Hinweistexte. python3 -m unittest test_display """ import unittest import config import migra from display import Display from transport import FakeTransport def _lesbar(frame): """Telegramm als lesbarer Text; Steuerzeichen werden zu '.'.""" return "".join(chr(b) if 32 <= b < 127 else "." for b in frame) def zellen(frames): """Aus gesendeten Telegrammen zurueckrechnen, was wo auf der Anzeige steht. Seit jedes Zeichen einzeln positioniert wird (config.CELL_WIDTH), steht der Text nicht mehr am Stueck im Telegramm -- zwischen je zwei Zeichen liegt eine Cursor-Sequenz. Ein Test auf "AB " im Rohtelegramm ginge deshalb ins Leere. Diese Funktion liest die Cursor-Sequenzen mit und liefert {(x, y): zeichen}, also das, was die Anzeige am Ende wirklich zeigt. Aufbau je Zeichen: ESC 'C' xxx yyy SEP Beim Gradzeichen schiebt sich ein Zeichensatzwechsel dazwischen (ESC 'z' nn), das Zeichen ist dann das erste nach dem naechsten SEP. """ out = {} for frame in frames: i = 0 while i < len(frame): if frame[i] == migra.ESC and i + 7 < len(frame) and frame[i + 1:i + 2] == b"C": x = int(frame[i + 2:i + 5]) y = int(frame[i + 5:i + 8]) j = frame.find(bytes([migra.SEP]), i + 8) if j >= 0 and j + 1 < len(frame): out[(x, y)] = chr(frame[j + 1]) i += 8 else: i += 1 return out def zeile(frames, index): """Den Text einer Anzeigezeile aus den Telegrammen rekonstruieren.""" y = config.LINE_Y[index] treffer = [(x, ch) for (x, yy), ch in zellen(frames).items() if yy == y] return "".join(ch for _, ch in sorted(treffer)) def zentriert(text): """Erwartungswert: Text so, wie _emit_line ihn auf die Zeile legt.""" text = text[:config.CHARS_PER_LINE] rest = config.CHARS_PER_LINE - len(text) links = rest // 2 if config.CENTER_LINES else 0 return " " * links + text + " " * (rest - links) class TestFesteTexte(unittest.TestCase): """Die Hinweiszeilen muessen in die Anzeige passen. Am Geraet aufgefallen: "MONTIERUNG" hat 10 Zeichen, die Anzeige fasst 9 -- angezeigt wurde "MONTIERUN". _emit_line schneidet stillschweigend ab, also faellt so etwas sonst erst auf der Anzeige auf. """ def test_hinweiszeilen_passen_in_die_zeilenbreite(self): for name in ("NOLINK_LINES", "NOWIFI_LINES"): for zeile in getattr(config, name): self.assertLessEqual( len(zeile), config.CHARS_PER_LINE, "config.%s: %r hat %d Zeichen, die Anzeige fasst %d" % (name, zeile, len(zeile), config.CHARS_PER_LINE)) def test_es_gibt_nicht_mehr_hinweiszeilen_als_zeilen(self): for name in ("NOLINK_LINES", "NOWIFI_LINES"): self.assertLessEqual(len(getattr(config, name)), len(config.LINE_Y)) class TestZeilenaufbau(unittest.TestCase): def setUp(self): self.t = FakeTransport(echo=False) self.d = Display(self.t, response=True) def _alle(self): return self.t.frames def test_zeile_wird_auf_volle_breite_aufgefuellt(self): """Ohne Auffuellen blieben Reste der vorigen Anzeige stehen. Gleichzeitig die Regressionsprobe fuer str.ljust, das MicroPython nicht kennt (am Geraet aufgefallen). """ self.d.show_lines("AB", "CD") self.assertEqual(zeile(self._alle(), 0), zentriert("AB")) self.assertEqual(zeile(self._alle(), 1), zentriert("CD")) # Es wird immer die volle Breite geschrieben, damit nichts stehen bleibt. self.assertEqual(len(zeile(self._alle(), 0)), config.CHARS_PER_LINE) def test_kuerzere_zeile_wird_mittig_gesetzt(self): """Zentriert heisst: links und rechts gleich viel Luft (Nutzerwunsch).""" self.d.show_lines("+38°47'") # 7 Zeichen in 10 Zellen text = zeile(self._alle(), 0) links = len(text) - len(text.lstrip()) rechts = len(text) - len(text.rstrip()) self.assertLessEqual(abs(links - rechts), 1) def test_jedes_zeichen_sitzt_auf_seiner_zelle(self): """Der Auto-Vorschub der Sperrschrift ist 7 px und liesse nur 9 Zeichen zu; die Matrix ist aber 6 px breit. Deshalb wird jedes Zeichen selbst gesetzt -- x muss genau LINE_X + i*CELL_WIDTH sein (am Geraet gemessen).""" self.d.show_lines("0123456789") gesetzt = zellen(self._alle()) y = config.LINE_Y[0] for i in range(config.CHARS_PER_LINE): self.assertIn((config.LINE_X + i * config.CELL_WIDTH, y), gesetzt) def test_zeile_passt_in_die_anzeigenbreite(self): """Auch die letzte Zelle darf nicht ueber den rechten Rand hinausragen -- sonst quittiert die Anzeige mit Fehlercode 4 (ausserhalb).""" rechts = config.LINE_X + config.CHARS_PER_LINE * config.CELL_WIDTH self.assertLessEqual(rechts, config.DISPLAY_WIDTH) self.assertGreaterEqual(config.LINE_X, 0) def test_zu_lange_zeile_wird_abgeschnitten(self): """Zu breit aufgefuellt bricht das Fuell-Leerzeichen um und beschaedigt die andere Zeile -- deshalb hart abschneiden.""" lang = "X" * (config.CHARS_PER_LINE + 5) self.d.show_lines(lang) self.assertEqual(zeile(self._alle(), 0), "X" * config.CHARS_PER_LINE) def test_ohne_zentrierung_steht_der_text_links(self): """CENTER_LINES = False muss wieder linksbuendig setzen.""" alt = config.CENTER_LINES try: config.CENTER_LINES = False self.d.show_lines("AB") self.assertEqual(zeile(self._alle(), 0), "AB" + " " * (config.CHARS_PER_LINE - 2)) finally: config.CENTER_LINES = alt def test_oberste_zeile_wird_zuletzt_gezeichnet(self): """Schutz der Home-Zelle: ein Zeichensatzwechsel zerstoert (0,0), die oberste Zeile muss danach kommen und sie ueberschreiben.""" self.d.show_lines("OBEN", "UNTEN") frames = self._alle() self.assertEqual(len(frames), 2) # Erst das Telegramm der unteren Zeile, dann das der oberen. self.assertEqual(zeile(frames[:1], 1).strip(), "UNTEN") self.assertEqual(zeile(frames[1:], 0).strip(), "OBEN") def test_telegramme_bleiben_unter_der_groessengrenze(self): """Mit Einzelpositionierung kosten beide Zeilen zusammen 224 der erlaubten 230 Bytes -- deshalb ein Telegramm je Zeile. Der Test haelt fest, dass die Reserve nicht wieder verloren geht.""" self.d.show_lines("18h36m56s", "+38" + config.DEGREE_MARKER + "47'01\"", clear=True) for frame in self._alle(): self.assertLessEqual(len(frame), migra.MAX_DATA_UNIT) def test_clear_erzeugt_genau_ein_vollbild_loeschen(self): """clear=True darf nur einmal loeschen -- sonst loescht das zweite Telegramm die gerade geschriebene Zeile wieder weg.""" fuellen = bytes([migra.ESC]) + b"F" + config.BACKGROUND.encode("ascii") self.d.show_lines("A", "B", clear=True) mit = self.t.frames[:] self.assertEqual(sum(f.count(fuellen) for f in mit), 1) self.t.frames.clear() self.d.show_lines("A", "B", clear=False) self.assertEqual(sum(f.count(fuellen) for f in self.t.frames), 0) def test_mehr_zeilen_als_die_anzeige_hat(self): with self.assertRaises(ValueError): self.d.show_lines(*(["X"] * (len(config.LINE_Y) + 1))) if __name__ == "__main__": unittest.main()