Ob die Linux-Installation nach einem Update noch wie gewünscht funktioniert, findet der sorgfältige Admin nicht erst im Laufe der Zeit heraus, sondern mittels einer Testsuite vor dem Ausrollen.
Wer als Admin regelmäßig die ihm anvertrauten Nutzer mit Updates der Linux-Distribution beglücken darf, vermeidet leidige Reklamationen, indem er grundlegende Funktionen testet. In diesem Artikel steht als Beispiel für das Verfahren exemplarisch die Prüfung eines eingehängten Druckers.
Unter Ubuntu erscheint unter dem Suchbegriff “Printers” ein Dialog mit allen angeschlossenen Druckern (Abbildung 1), per Hand ließe sich dort ein Drucker auswählen und dazu überreden, eine Testseite auszudrucken. Doch wie kann diese Aufgabe – und dem hier vorgestellten Muster folgend auch andere Tests – ein automatisch abgefeuertes Tool erledigen?
Erster Versuch
Das Linux Desktop Testing Project (LDTP, [2]) hat sich dem Testen von GUIs auf allerlei Plattformen inklusive Windows verschrieben. Es nutzt die Accessibility-Funktionen von Windowmanagern wie Gnome oder KDE und erlaubt es auch, sie fernzusteuern.
Allerdings befindet sich das Projekt in keinem guten Zustand, denn die Dokumentation ist lückenhaft. Selbst auf Nachfrage beim Entwickler konnte ich das GUI nicht dazu bewegen, angemessen zu reagieren, weil dabei entweder der LDTP-Daemon abstürzte oder anders als in der ohnehin spärlichen Dokumentation beschrieben reagierte. Sicher eine gute Idee, aber die Wartung müsste sich deutlich verbessern, wenn das Projekt von praktischem Nutzen sein soll.
Auf Kommando
Aus der Patsche halfen mir die Kommandozeilentools »lpstat« , »lpr« und »lpq« , die alle konfigurierten Drucker auflisten, ein Dokument drucken und die Druckerwarteschlange visualisieren können. In ein Perl-Skript 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 verschwindet. Zeigt die Ausgabe im Perl-üblichen TAP (Test Anything Protocol) dann durchgehend »ok« und kein »not ok« an, gilt der Test als bestanden und die frisch installierte Distro ist, was die Druckerei betrifft, funktionsfähig. Abbildung 2 zeigt die Ausgabe eines erfolgreichen Druckertests.
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. Operatoren des Testmoduls Test::More exportieren die Funktionen »ok« und »is« , die Vergleiche zwischen Ist- und Sollwert vornehmen. Und sie schlagen Alarm, falls ein unerwartetes Ergebnis auftritt.
Listing 1
lptest-default
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 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 3, dass auf meiner Ubuntu-Installation ein Multifunktionsdrucker namens MFC7420 konfiguriert ist, zudem eine Reihe von Labeldruckern der Marke Dymo, die ich vor drei Monaten in der Perl-Snapshot-Reihe vorgestellt habe [3]. Da der Distributionstest nur die Funktion des Default-Druckers prüft, ruft das Testskript in Listing 1 »lpstat -d« auf, schnappt sich die Ausgabe, die den Default-Drucker benennt, und verifiziert, ob eine Zeile mit einem Doppelpunkt herauskommt.
Standarddrucker
Findet »lpstat« einen Default-Drucker, ist dies schon einmal ein Zeichen, dass die neu installierte Distribution richtig konfiguriert ist. Für weitere Tests muss das Skript tatsächlich einen Druckauftrag abschicken. Wer kein Papier investieren will, sollte »lptest-default« mit der Option »–norealprint« 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
Die in Zeile 8 aufgerufene Funktion »GetOptions()« definiert hierzu den Kommandozeilen-Parameter »–realprint« , den Zeile 7 von Haus aus 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 tatsächlichen Druck deaktiviert.
Ans Eingemachte
Im Normalfall eines richtigen Drucktests schaut der Aufruf von »lpq_busy()« in Zeile 29 mit der ab Zeile 49 definierten Funktion über »lpq« nach, ob die Druckerschlange einen Auftrag enthält. Die Warteschlange ist bei einem neu installierten 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äre Datei, die »system()« mit dem Kommandozeilentool »lpr« an die Druckerschlange schickt. Der Aufruf von »lpr« sollte einen Exit-Code von »0 « liefern, was die Testsuiten-Funktion »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 und der Test damit endgültig erfolgreich abgeschlossen ist.
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.
Teil des Ganzen
Besteht der Systemtest aus einem oder mehreren solcher Testskripte, ist es üblich, diese mit einem Testrunner wie »prove« zu starten, der 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 Skripte, 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.
Gut verpackt
Wie zieht sich nun Münchhausen an den Haaren aus dem Sumpf, wie kommt 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 Red Hats ».rpm« – sowie das ».pkg« -Format von OS X beherrscht. Abbildung 5 zeigt das Schnüren eines Debian-Pakets »allmytests« . Da das Skript das CPAN-Modul Path::Tiny benötigt, das glücklicherweise bereits als »libpath-tiny-perl« im Debian-Repository existiert, bindet es »fpm« einfach mit der Option »-d« ein. Installiert der User per »sudo apt-get install« das »allmytests« -Paket vom Repository, holt der Paketmanager das abhängige Paket kurzerhand von dort und löst die Abhängigkeit elegant auf.
Ist der Perl-Core nicht Teil der Distribution, kommt er auf die gleiche Weise mit einer weiteren Option »-d« aufs System. Fehlt ein CPAN-Modul in der Distribution, hilft das in einer zurückliegenden Ausgabe bereits vorgestellte Carton-Modul [5], es mit der Testsuite zu bündeln. Mit einer stetig wachsenden Testsuite lässt sich auch bei flexiblen Änderungen Stabilität garantieren und Regression vermeiden.
Online PLUS
Im Screencast demonstriert Michael Schilli das Beispiel: https://www.linux-magazin.de/Ausgaben/2016/05/plus
Infos
- 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/
- Michael Schilli, “Ordnungshalber”: Linux-Magazin 12/15, S. 94, https://www.linux-magazin.de/Ausgaben/2015/12/Perl-Snapshot
- “Effing Package Management: fpm”: https://github.com/jordansissel/fpm/wiki
- Michael Schilli, “Aus dem Nähkästchen”: Linux-Magazin 01/16, S. 94, https://www.linux-magazin.de/Ausgaben/2016/01/Perl-Snapshot











