Ein neuer Service auf Travis-ci.org listet fein säuberlich Github-Projekte eines Entwicklers auf, schickt den Code bei jedem Push durch deren Testsuites und gibt Rückmeldung, falls der Build bricht. In den zurückgelieferten Testergebnissen machen neugierige Perl-Skripte Zusatzinformationen sichtbar.
Open-Source-Projekte profitieren von agilen Entwicklungsverfahren mit Continuous Integration (CI). Jede Veränderung des Codes im öffentlichen Repository sollte automatisch den Lauf der Testsuite anstoßen, um die Entwicklergemeinde unmittelbar auf etwaige Probleme hinzuweisen.
Klick statt Klimmzug
Wer meinte, es erfordere doch einige Klimmzüge, um mit CI-Tools wie Jenkins [2] (ehemals Hudson, [3]) und den dafür notwendigen Konfigurationsdaten zu einer Dauertestumgebung zu gelangen, sah sich kürzlich eines Besseren belehrt: Auf der Seite Travis-ci.org klickt der Nutzer den Login-Button an, der zum Projekthoster Github verzweigt. Dort spürt der Service die Repositories des Users aufs und holt – ebenfalls per Knopfdruck – die Genehmigung ein, diese im Administrationsbereich zu manipulieren. Zurück bei Travis findet der verdutzte Entwickler eine Liste seiner Github-Projekte mit formschönen On/Off-Knöpfen (Abbildung 1).

Abbildung 1: Legt der Travis-User den Schalter für ein Projekt um, benachrichtigt Github den Travis-Service bei jedem Code-Update.
Ein Klick auf »ON« stellt den CI-Server auf das Projekt ein. Nun muss nur noch eine etwa dreizeilige YAML-Datei in die Projektwurzel (Abbildung 2) – und von nun an wirft Travis bei jedem Push ins Github-Repository die Testsuite an (Abbildung 3) und meldet per E-Mail, falls etwas zusammenbricht. Der Travis-Service unterstützt neuerdings sogar Perl, und zwar in drei verschiedenen Versionen (5.10, 5.12, 5,14), nachdem Java, Javascript (mit Node.js), Python, PHP, Ruby und sogar Clojure, Erlang, Groovy, Haskell und Scala schon länger im Programm sind.
Einfaches ist einfach
Im einfachsten Fall definiert die YAML-Datei ».travis.yml« , wie in Abbildung 2 gezeigt, nur die verwendete Sprache im Projekt (»language: perl« ) und Travis leitet daraus dann selbstständig per Konvention ab, wie die sprachtypischen Testsuites aufzurufen sind. In Perl geschieht dies üblicherweise mit dem Zweisatz »perl Makefile.PL; make test« , aber auch mit neueren »Build.PL« -Dateien kommt der Service klar. Nutzt ein Projekt aber von der Norm abweichende Testsuites, definiert der Entwickler einfach in ».travis.yml« , mit welchen Skripten diese aufzurufen sind.
Dabei läuft bei einem Perl-Projekt nicht nur die Testsuite ab, sondern der Travis-Service stellt auf einem seiner Worker-Systeme eine aufgeräumte Umgebung bereit, holt die in den Projektdateien angegebenen Perl-Module als Tarballs vom nächsten CPAN-Mirror, installiert sie in der definierten Reihenfolge und stößt erst dann die Testsuite an. All das erfordert keinerlei Konfiguration, der Service leitet diese Schritte und Aktionen allein von den mit »language: perl« festgelegten Standardkonventionen ab.
Laufen die Vorbereitungen inklusive der eigentlichen Testsuite problemlos durch, zeigt Travis dies im Ausgabefenster an (Abbildung 4) und markiert den Durchlauf in der Übersicht mit einem grünen Punkt. Falls im vorherigen Durchlauf ein Fehler aufgetreten war, geht im Erfolgsfall zudem eine E-Mail an den oder die Entwickler, ansonsten schweigt Travis.
Unmittelbarer Smoketest
Nun besteht für in die freie Wildbahn entlassene CPAN-Module bereits ein hervorragend funktionierendes Smoketest-Framework [4], das sich ungefragt Tarballs vom CPAN holt, entpackt, in unterschiedlichen Produktionsumgebungen installiert und austestet. Treten Fehler auf, schicken die Smoketester E-Mails an die von den Entwicklern auf dem CPAN hinterlegten Adressen. Ein agiler Ingenieur möchte jedoch möglichst nach jedem Check-in kontinuierliche Unit-Tests fahren, um Fehler rechtzeitig aufzuzeigen, nicht erst nach der Veröffentlichung des Tarball auf dem CPAN.

Abbildung 5: Der in Ruby geschriebene Service-Hook alarmiert die Travis-Website, falls ein Entwickler neuen Code ans Github-Projekt geschickt hat.
Es verblüfft zunächst, wie unmittelbar nach dem Kommando »git push« auf Github die Testsuite auf »travis-ci.org« zu laufen beginnt. Github bietet für diese verzögerungsfreie Kommunikation im Repository so genannte Service-Hooks an, die Anbieter wie Travis als Open-Source-Code in Ruby erstellen (Abbildung 5) und nach einer Review auf Github installieren. Von den paar Dutzend verfügbaren Service-Hooks darf der Eigentümer eines Entwicklungs-Repository einen oder auch mehrere aktivieren, Github führt den Code dann jedes Mal sofort aus, falls ein Projektmitarbeiter Code eincheckt.
Nummer sicher
Der Travis-Service macht es dem Repository-Eigentümer noch einfacher, denn er beantragt auf Github per “OAuth” Admin-Rechte für dessen Repository (Abbildung 6). Nun versichern die Travis-Tester zwar, damit keinen Unfug zu treiben, doch sicher ist das nicht, und so empfiehlt es sich, diese Rechte im Admin-Bereich des Repository kurz nach der Installation des Service-Hook wieder zu löschen (Abbildung 7). Das Anstoßen der Testsuite leidet dadurch nicht.

Abbildung 6: Travis-ci.org möchte Admin-Zugang zu allen Repositories des Users erhalten, um dort jeweils den Service-Hook zu installieren.

Abbildung 7: Nach der Installation des Service-Hook holt der vorsichtige User die Genehmigung wieder zurück.
Vom Lesen des “Getting Started”-Guide [5] bis zur permanent laufenden CI-Umgebung vergehen dank dieser ausgefeilten Integration selten mehr als fünf Minuten. Da der Travis-Service gebräuchliche Sprachkonventionen kennt, brauchen Entwickler normalerweise keine einzige zusätzliche Zeile Code zu schreiben.
Mieser Build, böses Karma
Aber auch jenen Usern, die zusätzliche Funktionen wünschen, macht es der Travis-Service einfach, denn alle Testergebnisse stehen über kompakte REST-URLs als Webservice zum Abruf bereit (Abbildungen 8 und 9). Die zurückkommenden, ebenfalls kompakten Json-Daten eignen sich für extravaganten interaktiven Browsercode, aber auch für neugierige Perl-Programme, die mit Hilfe des Json-Moduls vom CPAN in den Daten herumschnüffeln können.
Wer zum Beispiel wissen möchte, welche Entwickler am häufigsten Code einchecken, der die Testsuite zum Abbruch zwingt, um ihnen negative Karma-Punkte zuzuweisen, klopft ein Skript nach Listing 1 zusammen. Es holt die Testergebnisse der vergangenen Woche vom Travis-Service und ermittelt für jeden fehlgeschlagenen Lauf den für den eingecheckten Code verantwortlichen Entwickler.

Abbildung 9: Abfrageresultate: Die letzten Builds mit ihren Ergebnissen kommen auf die Abfrage per REST-URL zurück.
Listing 1
karma
01 #!/usr/local/bin/perl -w
02 use strict;
03 use JSON qw( from_json );
04 use DateTime;
05 use DateTime::Format::Strptime;
06 use Log::Log4perl qw(:easy);
07 use LWP::Simple qw( get );
08
09 Log::Log4perl->easy_init($DEBUG);
10
11 my $github_api_url =
12 "https://api.github.com/repos";
13 my $travis_api_url =
14 "http://travis-ci.org";
15
16 my( $repo ) = @ARGV;
17 die "usage: $0 name/repo" if
18 !defined $repo;
19
20 my $build_json = get(
21 "$travis_api_url/$repo/builds.json" );
22 my $build_data = from_json( $build_json );
23
24 my $f = DateTime::Format::Strptime->new(
25 pattern => "%Y-%m-%dT%H:%M:%SZ",
26 time_zone => "America/Los_Angeles",
27 );
28
29 my $last_week_dt = DateTime->today(
30 time_zone => "local" )->
31 add( weeks => -1 );
32
33 my %build_breakers = ();
34
35 for my $build ( @$build_data ) {
36
37 if( $build->{ result } == 0 ) {
38 DEBUG "Build $build->{ commit } ok";
39 next;
40 }
41
42 my $build_dt = $f->parse_datetime(
43 $build->{ started_at } );
44
45 if( $build_dt < $last_week_dt ) {
46 DEBUG "Ignoring old build $build_dt";
47 }
48
49 my $github_request =
50 "$github_api_url/$repo/commits/" .
51 "$build->{ commit }";
52
53 DEBUG "Fetching $github_request";
54
55 my $github_json = get(
56 "$github_api_url/$repo/commits" .
57 "/$build->{ commit }" );
58 my $github_data =
59 from_json( $github_json );
60
61 my $committer = $github_data->
62 { commit }->{ committer }->{ email };
63
64 DEBUG "$committer broke build ",
65 "$build->{ commit }";
66 $build_breakers{ $committer }++;
67 }
68
69 for my $build_breaker (
70 sort keys %build_breakers ) {
71 print "$build_breaker: ",
72 "-$build_breakers{ $build_breaker }\n";
73 }
Das CPAN-Modul »JSON« exportiert auf Anfrage in Zeile 3 die Funktion »from_json()« , die später einen vom REST-Server kommenden Json-String in eine Perl-interne Datenstruktur umformt. Da das Skript einige Zeit läuft, sorgt »Log::Log4perl« im »DEBUG« -Modus (Zeile 9) dafür, dass auf der Standard-Error-Ausgabe aktuelle Skript-Aktionen wie das Einholen der REST-Anfragen erscheinen.
Von Travis nach Github
Der Travis-Service weiß allerdings nur die ID eines Commits zu nennen, eben den für das Revision-Control-System »git« typischen SHA1-Hash im Hexformat. Um nun festzustellen, welcher Entwickler den Code denn auf Github eingestellt hat, fragt Listing 1 unter der URL des Github-API (Zeile 11) nach den Metadaten des Commits und bekommt dort in einem weiteren Json-String unter anderem auch die E-Mail-Adresse des verantwortlichen Entwicklers heraus. Beide REST-APIs gleichen sich übrigens wie ein Ei dem anderen, was wohl eine Folge der zugrunde liegenden Ruby-Implementierung ist.
Da sich das Skript aus Zeitgründen nur für Commits der letzten Woche interessiert, verwirft es Einträge aus den Json-Daten mit älteren Commit-Zeitstempeln. In den Json-Daten liegt das Datum der auslösenden Commits im Format YYYY-MM-DDTHH::MM::SS vor, doch der Formatparser »DateTime::Format::Strptime« vom CPAN wandelt es jeweils in ein »DateTime« -Objekt um, damit Datumsvergleiche später leichter sind. So definiert Zeile 29 ein »DateTime« -Objekt mit dem Datum eines Tages, der genau eine Woche zurückliegt. Um festzustellen, ob ein Commit mehr als eine Woche zurückliegt, muss Zeile 45 dann nur noch zwei »DateTime« -Objekte mit dem überladenen »<« -Operator vergleichen.
Liegt ein Commit-Zeitstempel im aktuell untersuchten Bereich, schnappt sich Zeile 49 die Commit-ID, baut daraus eine URL für das ebenfalls für jedermann offene Github-API zusammen, und die aus dem CPAN-Modul »LWP::Simple« exportierte Funktion »get()« schickt den HTTP-Request ab. Zurück kommt ein Json-String, der in Perl nach der Umwandlung als Hash vorliegt.
Unter dem Schlüssel »commit« findet sich ein Hash mit einer weiteren Datenstruktur unter dem Eintrag »committer« . Dieser Hash enthält ein Feld »email« , das die E-Mail-Adresse des armen Sünders offenbart. Die Zeile 66 füllt diesen Wert in einen Hash, der die versaubeutelten Commits pro E-Mail-Adresse auflistet. Die For-Schleife ab Zeile 69 iteriert dann alphabetisch über die Entwickler und die »print()« -Funktion im Schleifenrumpf gibt zur Entwickler-E-Mail jeweils die negative Karma-Punktezahl aus.
Von Facebook-Release-Ingenieuren ist bekannt [6], dass sie an schusselige Entwickler negative Karma-Punkte vergeben und in Verruf geratene Kollegen dann bei neuen Releases kritisch beäugen. Im Einzelfall lässt sich das Release-Team angeblich mit edlen alkoholischen Getränken beruhigen, doch Wiederholungstäter finden peinliche Kommentare in ihren jährlichen Beurteilungen.
Online PLUS
In einem Screencast demonstriert Michael Schilli das Beispiel: https://www.linux-magazin.de/plus/2012/06
Infos
- Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2012/06/Perl
- Jenkins: http://jenkins-ci.org/
- “Am Ball bleiben”, Continuous Integration mit Hudson: Linux-Magazin 07/2010 S.106
- Smoketest-Framework:http://www.cpantesters.org
- “Getting Started”: http://about.travis-ci.org/docs/user/getting-started/
- Ryan Paul, “Exclusive: a behind-the-scenes look at Facebook release engineering”: http://arstechnica.com/business/news/2012/04/exclusive-a-behind-the-scenes-look-at-facebook-release-engineering.ars











