Inhaltsverzeichnis

Testen

Lyx bringt kein Test-Framework mit — und braucht keins. Ein Test ist ein Programm, das etwas prüft und PASS/FAIL druckt. Das klingt karg, hat aber einen Vorteil: Ein Test ist genau so schwer zu verstehen wie ein gewöhnliches Programm, und er läuft ohne Zusatzwerkzeug überall, wo der Compiler läuft.

Der Compiler selbst wird so getestet — über 600 Testskripte im aurum-Repository, dazu eine Liste von 505 lauffähigen .lyx-Tests.

Fehlersuche · Compiler-Parameter · Standard Library


Ein Test in Lyx

Die zu prüfende Unit:

unit tst.rechner;

pub fn Addiere(a: int64, b: int64): int64 { return a + b; }

pub fn Teile(a: int64, b: int64, out_rest: int64): int64 {
  if (b == 0) { return 0 - 1; }
  poke64(out_rest, a - (a / b) * b);
  return a / b;
}

Der Test dazu — hier eigenständig, damit er ohne die Nachbardatei übersetzt:

import std.io;
import std.alloc;

// Im Guide steht die gepruefte Unit daneben; hier sind ihre beiden
// Funktionen mit eingerueckt, damit das Beispiel fuer sich uebersetzt.
fn Addiere(a: int64, b: int64): int64 { return a + b; }

fn Teile(a: int64, b: int64, out_rest: int64): int64 {
  if (b == 0) { return 0 - 1; }
  poke64(out_rest, a - (a / b) * b);
  return a / b;
}

var g_pass: int64;
var g_fail: int64;

fn Pruefe(name: pchar, ist: int64, soll: int64): void {
  if (ist == soll) {
    g_pass := g_pass + 1;
    PrintLn(StrConcat("PASS: ", name));
  } else {
    g_fail := g_fail + 1;
    PrintLn(StrConcat(StrConcat("FAIL: ", name), StrConcat(" — erwartet ",
      StrConcat(IntToStr(soll), StrConcat(", erhalten ", IntToStr(ist))))));
  }
}

fn main(): int64 {
  g_pass := 0;
  g_fail := 0;

  Pruefe("Addiere: gewoehnlich", Addiere(2, 3), 5);
  Pruefe("Addiere: Null", Addiere(0, 0), 0);
  Pruefe("Addiere: negativ", Addiere(-2, 5), 3);

  var rest: int64 := alloc(8);
  Pruefe("Teile: Ergebnis", Teile(7, 2, rest), 3);
  Pruefe("Teile: Rest", peek64(rest), 1);
  Pruefe("Teile: durch Null meldet -1", Teile(7, 0, rest), -1);
  free(rest, 8);

  PrintLn(StrConcat(StrConcat(IntToStr(g_pass), " PASS, "),
                    StrConcat(IntToStr(g_fail), " FAIL")));
  if (g_fail > 0) { return 1; }
  return 0;
}

PASS: Addiere: gewoehnlich
PASS: Addiere: Null
PASS: Addiere: negativ
PASS: Teile: Ergebnis
PASS: Teile: Rest
PASS: Teile: durch Null meldet -1
6 PASS, 0 FAIL

Übersetzt und ausgeführt mit lyxc 1.1.3I; Exit-Code 0.

Drei Dinge daran sind Absicht:


Testskripte drumherum

Wo die Prüfung nicht im Programm stehen kann — Compilerfehler, Ausgabe eines Werkzeugs, Dateigrößen — nimmt man ein Shell-Skript. Das Muster aus dem Compiler-Repository:

#!/bin/bash
# #1670 — ClassName(): Klassenname zur Laufzeit.
set -u
cd "$(dirname "$0")/.."
LYXC=${LYXC:-./lyxc}
TMP=$(mktemp -d); trap 'rm -rf "$TMP"' EXIT
PASS=0; FAIL=0
ok()  { echo "PASS: $1"; PASS=$((PASS+1)); }
bad() { echo "FAIL: $1"; FAIL=$((FAIL+1)); }
pruefe() { if [ "$2" = "$3" ]; then ok "$1"; else bad "$1 (erwartet '$3', erhalten '$2')"; fi; }

cat > "$TMP/t.lyx" <<'EOF'
… Testprogramm …
EOF

if ! "$LYXC" --std-path=. "$TMP/t.lyx" -o "$TMP/t" > "$TMP/t.log" 2>&1; then
  bad "uebersetzt"; grep -E "error" "$TMP/t.log" | head -3
else
  ok "uebersetzt"
  timeout 60 "$TMP/t" > "$TMP/t.out" 2>&1
  pruefe "dynamischer Name" "$(grep '^dyn=' "$TMP/t.out" | cut -d= -f2)" "TAbl"
fi

Bewährte Punkte daraus:


Was ein guter Test prüft

Ein Test, der nur den einfachen Fall abdeckt, ist auch grün, wenn die Umsetzung falsch ist. Beispiel aus dem Bestand: ClassName() auf einer direkt deklarierten Variablen wäre auch mit einer rein statischen Antwort grün — der eigentliche Punkt ist die Variable vom Basistyp, die die tatsächliche Klasse melden muss.

Daraus die Regel: Prüfe das, was kaputtgehen kann, nicht das, was offensichtlich geht.


Vor dem Test: übersetzen lassen

–compile-unit übersetzt eine Unit für sich allein — bis in den Codegen (#1587). Das ist die billigste Prüfung überhaupt und findet alles, was die Semantikprüfung sieht:

lyxc meineunit.lyx --compile-unit -o meineunit.lyu

Seit lyxc 1.1.x meldet die Prüfung deutlich mehr als früher: unbekannte Felder über Unit-Grenzen, falsche Stelligkeiten, doppelte Mitglieder, Vorwärtsverweise. Eine grüne Übersetzung sagt heute mehr aus als vor einem Jahr.


Testreihen organisieren

Im Compiler-Repository hängen die Tests an make-Zielen:

Ziel Inhalt
make test Die Skripte aus tests/*.sh
make test-lyx Die Liste tests/suite-full.txt (505 Einträge)
make test-known-red Die bekannt roten aus tests/known-red.txt — wird rot, wenn einer davon wieder grün ist

Der letzte Punkt ist der interessante: Ein bekannt roter Test, der repariert wurde, meldet sich selbst — sonst veraltet die Liste still.

Ebenfalls aus dem Bestand gelernt: Die Erfolgskonvention war uneinheitlich (manche Tests liefern 0, manche 42, eine Familie druckt „ALL PASS„ und endet mit 1). Der Runner urteilt deshalb nach der Ausgabe: eine FAIL-Zeile ist rot, PASS/OK: ohne FAIL ist grün, ein Absturz (rc ≥ 128) ist immer rot. Wer eine eigene Reihe aufbaut, legt die Konvention besser von Anfang an fest.


Beispiele in der Doku prüfen

Für dieses Wiki gibt es doku-pruefer.py: Es zieht jeden <code lyx>-Block mit fn main( heraus, übersetzt ihn und hält zusätzlich jede Aufruf-Stelligkeit gegen die echte pub fn-Zeile der Standardbibliothek. Dasselbe Verfahren taugt für jede Dokumentation, die Codebeispiele enthält — ein Beispiel, das nie übersetzt wurde, ist eine Behauptung.


Fallstricke

Letzte Aktualisierung: 2026-08-19 — Beispiel mit lyxc 1.1.3I übersetzt und ausgeführt; Skriptmuster und Suite-Mechanik aus dem aurum-Repository erhoben.