Ob eine neue Linux-Installation alle gewünschten Funktionen bringt, findet der sorgfältige Admin nicht erst im Laufe der Zeit heraus, sondern gleich nach der Inbetriebnahme mittels einer Testsuite.
Im Rahmen des Schwerpunkts "Eigenes Repository" bat mich die Redaktion des Linux-Magazins diesen Monat um ein Testverfahren, um auf einer neu installierten Distro die Funktion eingehängter Drucker zu prüfen. Auf Ubuntu kommt unter dem Suchbegriff "Printers" dazu ein Dialog mit allen angeschlossenen Druckern hoch (Abbildung 1) und per Hand lässt sich dort einfach ein Drucker auswählen und dazu überreden, eine Testseite auszudrucken. Doch wie kann dies ein automatisch abgefeuertes Tool erledigen?
Das Linux Desktop Testing Project "LDTP" hat sich dem Testen von GUIs auf allerlei Plattformen inklusive Windows verschrieben, nutzt die Accessibility-Funktionen von Window-Managern wie Gnome oder KDE und erlaubt deren Fernsteuerung. Allerdings befindet sich das Projekt in keinem guten Zustand, denn die Dokumentation ist lückenhaft und selbst auf Nachfrage beim Entwickler konnte ich die GUI nicht dazu bewegen, entsprechend zu reagieren, weil entweder der LDTP-Dämons abstürzte oder anders als in der spärlichen Dokumentation beschrieben reagierte. Sicher eine gute Idee, aber die Wartung müsste sich deutlich verbessern, wenn das Projekt denn von praktischem Nutzen sein soll.
Abbildung 1: Alle installierten Drucker im Printers-Dialog |
Aus der Patsche halfen mir die Kommandozeilen-Tools lpstat
, lpr
und lpq
,
die alle konfigurierten Drucker auflisten, ein Dokument drucken, sowie die
Druckerwarteschlange visualisieren können. In ein Perlskript verpackt finden
sie den Standarddrucker, lassen ihn ein Testdokument drucken und prüfen, ob
der Auftrag erst in der Warteschlange erscheint und dann von dort nach Erledigung
gelöscht wird. Zeigt die Ausgabe im Perl-üblichen TAP ("Test Anything Protocol") dann
durchgehend "ok" und kein "not ok", gilt der Test als bestanden und die
frisch installierte Distro ist funktionsfähig. Abbildung 2 zeigt die Ausgabe
eines erfolgreichen Druckertests.
Abbildung 2: Die Testsuite findet den Default-Drucker, druckt dort ein Dokument und prüft die Warteschlange auf Erfolg. |
Das Testskript in Listing 1 ruft hierzu über Perls Backquote-Mechanismus die
Kommandozeilentools auf und speichert deren Ausgabe in Variablen zur späteren
Überprüfung mittels regulärer Ausdrücke. Die des Test-Moduls Test::More exportieren
die Funktionen ok
und is
, die Vergleiche zwischen Ist- und Sollwert vornehmen
und schlagen Alarm falls ein unerwartetes Ergebnis auftritt. Die Funktion ok()
prüft hierzu, ob der erste ihr hereingereichte Parameter einen wahren Wert enthält,
während is()
den gesehenen Wert (erster Parameter) mit dem erwarteten Wert
(zweiter Parameter) vergleicht.
Von der Kommandozeile aus aufgerufen zeigt lpstat
in Abbildung 2, dass auf
meiner Ubuntu-Installation ein Multifunktionsdrucker namens "MFC7420" konfiguriert
ist, sowie eine Reihe von Labeldruckern der Marke "Dymo", die ich vor
drei Monaten in der Perl-Snapshot-Reihe vorgestellt habe ([3]). Da der Distrotest
nur die Funktion des Defaultdruckers prüft, ruft das Testskript in Listing 1
lpstat -d
auf, schnappt sich die Ausgabe, die den Defaultdrucker benennt,
und verifiziert, ob eine Zeile mit einem Doppelpunkt herauskommt.
Abbildung 3: Das Kommando lpstat listet alle konfigurierten Drucker auf. |
Findet lpstat
einen Defaultdrucker, ist dies schon einmal ein Zeichen, dass die
neu installierte Distro richtig konfiguriert ist. Für weitere Tests muss das Skript
tatsächlich einen Druckauftrag abschicken, wer nicht unnötig Papier verschwenden
will, sollte lptest-default
mit der Option --noprint
aufrufen, dann überspringt
das Testskript den mit SKIP
markierten Bereich ab Zeile 24. In diesem Fall
zeigt die Ausgabe die eingeschränkte Testabdeckung:
$ ./lptest-default --norealprint ok 1 - found default printer MFC7420 ok 2 # skip printing disabled 1..2
01 #!/usr/local/bin/perl -w 02 use strict; 03 use Test::More; 04 use Getopt::Long; 05 use Path::Tiny; 06 07 my $realprint = 1; 08 GetOptions( "realprint!" => \$realprint ); 09 10 my $lpstat = "lpstat"; 11 my $lpr = "lpr"; 12 my $lpq = "lpq"; 13 14 my( $default_printer ) = 15 ( `$lpstat -d` =~ /: (.*)/ ); 16 17 if( !defined $default_printer ) { 18 die "Cannot find default printer"; 19 } 20 21 ok 1, 22 "found default printer $default_printer"; 23 24 SKIP: { 25 if( !$realprint ) { 26 skip "printing disabled", 1; 27 } 28 29 ok !lpq_busy(), "lpq empty"; 30 31 my $temp = Path::Tiny->tempfile; 32 $temp->spew( "This is a test." ); 33 34 my $rc = system $lpr, "-P", 35 $default_printer, $temp->absolute; 36 is $rc, 0, "printing with $lpr"; 37 38 ok lpq_busy(), "lpq busy"; 39 40 while( lpq_busy() ) { 41 sleep 1; 42 } 43 44 ok !lpq_busy(), "lpq empty"; 45 } 46 47 done_testing; 48 49 sub lpq_busy { 50 my $queue = `$lpq`; 51 52 return $queue =~ /active/; 53 }
Die in Zeile 8 aufgerufene Funktion Getoptions()
definiert hierzu einen
Kommandozeilenparameter --realprint
den Zeile 7 von Haus auf 1 setzt, also
ohne Zutun des Users den Echtdruck ausführt. Das Ausrufezeichen am Ende der
Option "realprint!"
weist Getoptions()
an, dass die Verneinung mit
--norealprint
auf der Kommandozeile die Variable $realprint
im Skript
auf einen falschen Wert setzt und damit den Echtdruck deaktiviert.
Im Normalfall eines richtigen Drucktests schaut der Aufruf von lpq_busy()
in Zeile 29 mittels der ab Zeile 49 definierten Funktion über lpq
nach, ob
die Druckerschlange einen Auftrag enthält. Die Warteschlange ist bei einem
neuinstallierten System leer, also meldet der Test in Zeile 29 Erfolg, falls
lpq_busy()
einen falschen Wert zurückliefert. Anschließend schreibt die
Funktion spew()
des CPAN-Moduls Path::Tiny einen Textstring in eine
temporärer Datei, die system()
mit dem Kommandozeilentool lpr
an
die Druckerschlange schickt. Der Aufruf von lpr
sollte einen Exit-Code
von 0
liefern, was die Testsuitenfunktion is
verifiziert.
Kehrt lpr
zurück, steht der Auftrag bereits in der Druckerschlange und ein
nachfolgender Aufruf von lpq
zeigt ihn dort an. Die while
-Schleife ab
Zeile 40 untersucht nun im Sekundentakt, ob der Auftrag irgendwann aus der
Schlange verschwindet, was ein untrügliches Zeichen dafür ist, dass er
tatsächlich auf dem Drucker gelandet ist und der Test damit endgültig erfolgreich
abgeschlossen wurde. Nach diesen fünf Tests beendet done_testing()
in Zeile
47 den Reigen und meldet der steuernden Testsuite mit dem Textstring "1..5"
,
dass auch nichts zwischendurch abgestürzt ist.
Abbildung 4: Der Testrunner C |
Besteht der Systemtest aus einer oder mehreren solcher Testskripts, ist es
üblich, diese mit einem Testrunner wie prove
zu starten,
das der Perl-Distribution beiliegt. Dieses Tool schluckt die Ausgabe einzelner Tests
und gibt im Erfolgsfall nur eine Zusammenfassung des Ergebnisses aus. Im Fehlerfall
kommen hingegen weitere Details hoch, die helfen, die Ursache einzukreisen.
Abbildung 4 zeigt die Ausgabe im Erfolgsfall, und es bietet sich an, nicht nur
ein sondern gleich mehrere Skripts, eventuell mit Hilfe eines Glob-Zeichens
wie in
$ prove "/var/tests/*"
ablaufen zu lassen. Wichtig ist, dass alles automatisch läuft und die Einzelschritte keine manuellen Eingriffe erfordern.
Abbildung 5: Der Paket-Tausendsassa fpm schnürt aus der Testsuite ein Debianpaket. |
Wie zieht sich nun Münchhausen an den Haaren aus dem Sumpf, wie
kommt nun die Testsuite anfangs auf das System mit der zu testenden Installation?
Es bietet sich an,
das Testskript in ein Paket im Format der verwendeten Distribution zu verschnüren,
es im Repository abzulegen, und bei Bedarf mittels des Paketmanagers von dort zu
installieren. Am einfachsten geht das mit dem Paketschnürer fpm
([4]), der
Debians .deb
und RedHats .rpm
, sowie das .pkg
-Format von OSX beherrscht.
Abbildung 5 zeigt das Schnüren eines Debian-Pakets allmytests
.
Da das Skript das CPAN-Modul Path::Tiny benötigt, das jedoch glücklicherweise bereits
als libpath-tiny-perl
im Debian-Repo existiert,
bindet es fpm
einfach mittels der Option -d
ein.
Installiert der User dann mittels sudo apt-get install
das allmytests
-Paket vom Repo, holt der Paketmanager das abhängige Paket
kurzerhand ebenfalls von dort und löst die Abhängigkeit damit elegant auf.
Ist der Perl-Core noch nicht Teil der Distribution, kommt er auf die gleiche Weise
mit einer weiteren Option -d
aufs System. Ist ein verwendetes CPAN-Modul
noch nicht Teil der Distribution, hilft das in einer zurückliegenden Ausgabe
des Perl-Snapshots vorgestellte Carton-Modul ([5]), es mit der Testsuite zu
bündeln. Mit einer stetig wachsenden Testsuite lassen sich so auch bei flexiblen
Änderungen Stabilität garantieren und Regressionen vermeiden.
Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2016/05/Perl
LDTP ("Linux Desktop Test Project"): https://ldtp.freedesktop.org/wiki/
TODO Michael Schilli, "Dymo", Linux-Magazin, 2015
"Effing Package Management: fpm", https://github.com/jordansissel/fpm/wiki
TODO Michael Schilli, "Pro Tricks", Linux-Magazin, 2015