weidner/archives/2013/08/

Qualitätssicherung mit Monotone VCS: mtn testresult

Es gibt verschiedene Möglichkeiten eine Qualitätssicherung mit dem Versionsverwaltungssystem monotone zu realisieren. Diese bauen auf der Beschränkung des Versionsgraphen auf, der im Produktivsystem verwendet wird. Das geht auf zwei Wegen:

Natürlich kann man die Verfahren mischen und kaskadieren, je nach dem bevorzugten Qualitätssicherungsprozess.

In diesem Artikel beschäftige ich mich mit der Anwendung von Test-Zertifikaten. Die Beschränkung auf zusätzliche Zweigzertifikate beschrieb ich bereits in einem anderen Artikel. Der Workflow ist fast der gleiche, mit der Ausnahme, dass statt mtn approve hier mtn testresult sowohl für pass als auch fail verwendet wird.

Generell funktioniert die Qualitätssicherung so, dass eine Instanz, die unabhängig vom Entwicklungsprozess ist, für bestimmte Revisionen Tests durchführt und die Ergebnisse dieser Tests mit zusätzlichen Zertifikaten dokumentiert. Bei der Aktualisierung der Produktivumgebung oder für die Freigabe eines Entwicklungsstandes werden dann nur Revisionen, die Zertifikate für bestandene Tests enthalten, berücksichtigt.

Die Tests sind frei wählbar und vom Projekt abhängig. Prinzipiell ist es möglich, die Testzertifikate auch ohne die Tests zu vergeben, aber das widerspräche dem Sinn der Qualitätssicherung.

Wichtig ist, dass man für die Tests eine Extra-Instanz und ein separates Zertifikat verwendet. Mit dem Testzertifikat werden keine Commits signiert sondern nur Testergebnisse.

Wie funktioniert es?

Laut Dokumentation von Monotone genügt es, im Verzeichnis _MTN eine Datei namens wanted-testresults abzulegen, die die Schlüssel der Gutachter enthält. Dabei handelt es sich nicht um die Schlüsselnamen sondern Hashes, die man zum Beispiel mit folgendem Befehl erhält:

$ mtn ls keys testkey \
| perl -n -e 's/^([0-9a-z]+)\s+testkey$/$1/ && print && exit' \
>> _MTN/wanted-testresults

Leider funktioniert die Default-Implementation des Hooks accept_testresult_change (old_results, new_results) zumindest in Version 1.0 von Monotone nicht wie gewünscht und in der Dokumentation beschrieben. Der folgende Workaround in der Datei _MTN/monotonerc hilft:

function HexDumpString(str,spacer)
   return (string.gsub(str,"(.)",
      function (c)
         return string.format("%02x%s",string.byte(c), spacer or "")
      end)
   )
end

function accept_testresult_change(old_results, new_results)
   -- Hex encode each of the key hashes to match those in 'wanted-testresults'
   local old_results_hex = {}
   for k, v in pairs(old_results) do
        local hexdump = HexDumpString(k)
        old_results_hex[HexDumpString(k)] = v
   end

   local new_results_hex = {}
   for k, v in pairs(new_results) do
        local hexdump = HexDumpString(k)
      new_results_hex[HexDumpString(k)] = v
   end

   local reqfile = io.open("_MTN/wanted-testresults", "r")
   if (reqfile == nil) then return true end
   local line = reqfile:read()
   local required = {}
   while (line ~= nil)
   do
      required[line] = true
      line = reqfile:read()
   end
   io.close(reqfile)
   for test, res in pairs(required)
   do
      if old_results_hex[test] == true and new_results_hex[test] ~= true
      then
         return false
      end
   end
   return true
end

Mit dieser Definition des Hooks und den Keyhashes der Gutachterschlüssel in _MTN/wanted-testresults aktualisiert der Befehl mtn update genau dann nicht, wenn bei der alten Revision ein Schlüssel mit einem positiven Testergebnis war und bei der neuen Revision der gleiche Schlüssel kein positives Testergebnis aufweist, so wie es in der Dokumentation beschrieben ist.

Bevor ich es vergesse; die Testresult-Zertifikate fügt man mit mtn testresult an eine Revision an:

mtn -k testkey testresult 99b505974f54aa4d0344b597362791590fd85028 pass
mtn -k testkey testresult a4bd7c92fca04dd92e10175fa6bd300ebe0531e4 fail

Das folgende Beispiel, bei dem der oben beschriebene Hook in der Datei monotonerc im aktuellen Verzeichnis liegt, soll die Verwendung verdeutlichen:

$ mtn --keydir keys genkey testkey1
$ mtn --keydir keys genkey testkey2
$ mtn --db test.mtn db init
$ mtn --db test.mtn --keydir keys --branch testbranch setup testbranch1
$ cd testbranch1
$ echo abc > testfile
$ mtn add testfile
mtn: füge 'testfile' dem Arbeitsbereich-Manifest hinzu
$ mtn commit -m Initial -k testkey1
mtn: beginne Einpflegen auf Zweig 'testbranch'
mtn: Revision 99b505974f54aa4d0344b597362791590fd85028 eingepflegt
$ mtn -k testkey2 testresult 99b505974f54aa4d0344b597362791590fd85028 pass
$ cd ..
$ mtn --db test.mtn --keydir keys --branch testbranch co testbranch2
$ cd testbranch1
$ echo def >> testfile
$ mtn commit -m Second -k testkey1
mtn: beginne Einpflegen auf Zweig 'testbranch'
mtn: Revision a4bd7c92fca04dd92e10175fa6bd300ebe0531e4 eingepflegt
$ mtn -k testkey2 testresult a4bd7c92fca04dd92e10175fa6bd300ebe0531e4 fail
$ cd ../testbranch2
$ mtn ls keys
| perl -n -e s/^([0-9a-z]+)\s+testkey2$/$1/ && print && exit
> _MTN/wanted-testresults
$ cp ../monotonerc _MTN
$ mtn update
mtn: aktualisiere vorwärts auf dem Zweig 'testbranch'
mtn: bereits aktualisiert auf 99b505974f54aa4d0344b597362791590fd85028
$ mtn status
----------------------------------------------------------------------
Revision:   446e6d19dea54502e23dbc27465492fe94e40f5b
Elternrev.: 99b505974f54aa4d0344b597362791590fd85028
Autor:      ???
Datum:      04.01.2013 21:17:48
Zweig:      testbranch

*** DIESE REVISION WIRD EINEN NEUEN KOPF ERZEUGEN ***

Veränderungen ggü. Elternrev.
99b505974f54aa4d0344b597362791590fd85028

keine Änderungen

Das bedeutet, Monotone erkennt, dass die Revision im Arbeitsverzeichnis nicht die aktuelle ist, aktualisiert aber nicht auf die neueste Version, da diese im Testzertifikat 0 (fail) stehen hat, während die alte dort 1 (pass) stehen hatte. Das erkennt man auch gut bei der Auflistung der Zertifikate:

$ mtn ls certs 99b505974f54aa4d0344b597362791590fd85028 
--------------------------------------------------------------------------------
Schlüssel : testkey1 (8b50e0b2d1...)
Signatur  : in Ordnung
Name      : author
Wert      : testkey1
--------------------------------------------------------------------------------
Schlüssel : testkey1 (8b50e0b2d1...)
Signatur  : in Ordnung
Name      : branch
Wert      : testbranch
--------------------------------------------------------------------------------
Schlüssel : testkey1 (8b50e0b2d1...)
Signatur  : in Ordnung
Name      : changelog
Wert      : Initial
--------------------------------------------------------------------------------
Schlüssel : testkey1 (8b50e0b2d1...)
Signatur  : in Ordnung
Name      : date
Wert      : 2013-01-04T19:43:43
--------------------------------------------------------------------------------
Schlüssel : testkey2 (75325ca467...)
Signatur  : in Ordnung
Name      : testresult
Wert      : 1

$ mtn ls certs a4bd7c92fca04dd92e10175fa6bd300ebe0531e4 
--------------------------------------------------------------------------------
Schlüssel : testkey1 (8b50e0b2d1...)
Signatur  : in Ordnung
Name      : author
Wert      : testkey1
--------------------------------------------------------------------------------
Schlüssel : testkey1 (8b50e0b2d1...)
Signatur  : in Ordnung
Name      : branch
Wert      : testbranch
--------------------------------------------------------------------------------
Schlüssel : testkey1 (8b50e0b2d1...)
Signatur  : in Ordnung
Name      : changelog
Wert      : Second
--------------------------------------------------------------------------------
Schlüssel : testkey1 (8b50e0b2d1...)
Signatur  : in Ordnung
Name      : date
Wert      : 2013-01-04T19:43:44
--------------------------------------------------------------------------------
Schlüssel : testkey2 (75325ca467...)
Signatur  : in Ordnung
Name      : testresult
Wert      : 0
Posted 2013-08-30
Tags: