Status
======

Vergleich mit dem Testskript
----------------------------

Einige der Testfälle des Testskripts werden noch nicht vom Unittest Framework
abgedeckt, teilweise konkret, teilweise konzeptionell.  Und zwar sind das:

- Tests für computers/* Objekte

  Es gibt bisher einen Testfall für eins dieser Objekte im TestComputers.py
  Modul.  Dieses muss so ausgebaut werden, dass es mit dem Computertypen
  parametrisiert ist (s. TestContainers.py für ein Beispiel der Technik), dann
  können Unterklassen für jeden Computertypen definiert werden.

  Außerdem muss der Umgang mit verschiedenen Optionen implementiert werden
  (Posix und Kerberos, s. TestSharesShare.py für ein Beispiel dazu), für die
  dann ebenfalls Unterklassen definiert werden können.  Hierbei ist zu
  beachten, dass einige Kombinationen aus Hosttyp und Optionen ungültig sind
  und entweder ausgelassen werden oder auf erwartete Fehler getestet werden
  (s. TestGroups für ein Beispiel hierfür).

  Momentan fehlen im Computer Testfall zudem die Locking-Tests, also die
  Tests, ob es verboten ist, ein Objekt mit z.B. der selben Mac-Adresse
  anzulegen (s. z.B. TestGroups.py dazu).

  Desweiteren testet das Testskript Computerobjekte sowohl mit einem
  Netzwerkparameter als auch mit direkter Angabe von IP, DHCP Dienst und DNS
  Zonen; hierfür sollte eine weitere Basisklasse definiert werden (analog zur
  bestehenden in TestComputers.py), für die dann wiederum Unterklassen
  definiert werden müssen. (Alternativ können die vorher definierten
  Unterklassen darüber parametrisiert werden, von welcher der beiden
  Unterklassen sie erben; Metaclass Hacking ist Geschmackssache :-)

  Zum Ende muss entschieden werden, welche Kombinationen aus Hosttyp und
  Optionen für die simple und welche für die "extended" Testsuite verwandt
  werden (s. hierfür TestUsers.py als Beispiel).

- Mehr Kombinationen aus Optionen bei Benutzern

  In TestUsers.py gibt es eine Reihe von Unterklassen des Benutzer-Testfalls,
  die eine feste Kombination aus Optionen festlegen.  Hier fehlen noch diverse
  Kombinationen (momentan sind es: a) keine Optionen, b) alle Optionen, c)
  eine Option, d) alle bis auf eine Option).  Alternativ zu Unterklassen
  könnten die Kombinationen algorithmisch erzeugt und die Basisklasse darüber
  parametrisiert werden (s. dazu TestModuleList.py als Beispiel).

- Schemaerweiterungen und Custom Attributes

  Es fehlt bisher eine einfache Möglichkeit, Schemaerweiterungen innerhalb der
  Tests in den laufenden LDAP Server einzuspeisen; diese müsste implementiert
  werden.  Danach können für die betroffenen Klassen Unterklassen erzeugt
  werden, die die `setUp' Methode überladen und die Custom Attributes in die
  Properties einfügen.

  Das Ganze gehört natürlich in die "extended" Testsuiten; evtl. könnten die
  notwendige Schemaänderungen vom CLI Frontend vorgenommen und zum Testende
  wieder rückgängig gemacht werden.

- Rekursives Löschen

  Es ist im Framework möglich, Unterbäume rekursiv zu löschen, indem der
  `remove' Methode das Argument `recursive = True' übergeben wird.

  Diese Möglichkeit kann genutzt werden, um rekursives Löschen zu testen,
  indem die Container-Tests so erweitert werden, dass ihnen eine
  untergeordnete Klasse von Objekten mit übergeben wird.  Nach dem Erzeugen
  wird ein Objekt dieses Typs unterhalb des Containers angelegt, der Container
  danach rekursiv gelöscht und auf Nicht-Existenz beider Objekte geprüft.

  Hierfür sollte vermutlich die `runTest' Methode überschrieben werden, um den
  veränderten Testablauf widerzuspiegeln (s. z.B. TestSettingsDefault.py als
  Beispiel für das Überschreiben der Testmethode, TestDnsAlias.py als Beispiel
  für das Erzeugen von (Unter-)Objekten).  Die hierfür erzeugten Tests können
  als neues Testfallmodul implementiert werden, das TestContainers importiert.


Sonstige nützliche Erweiterungen
--------------------------------

- Neue Benutzerattribute

  Für Benutzerobjekte gibt es einige Optionen, die (soweit ich das sehe) im
  Testskript nicht getestet werden, wie Groupware oder PKI.  Das TestUser
  Modul sollte um diese Optionen und deren Properties erweitert werden.

- Ändern von Optionen

  Es gibt Optionen, die nach dem Erzeugen eines Objekts diesem zugefügt oder
  von ihm entfernt werden können, wie z.B. die Groupware Option.  Um dies
  testen zu können, sollte es eine Eigenschaft `modifyOptions' geben, die sich
  zu `createOptions' verhält wie `modifyProperties' zu `createProperties'.

  Zusätzlich müssten dann auch nach dem Ändern von Objekten dessen Optionen
  (bzw. Objektklassen) geprüft werden.

- Multi-IPs

  Die Multi-IP Erweiterung besteht, wie ich sie verstehe, in der Änderung,
  dass IPs nun multi-values sind; um sie auszunutzen und zu testen, müssen IP
  Adressen einfach nach dem multi-value Protokoll für Properties festgelegt
  werden (s. GenericCase für Dokumentation zu diesem Protokoll).

- Automatisches Abschalten des Listener/Notifier Mechanismus

  Je nach Anzahl der Testfälle oder der übergebenen Optionen sollte das CLI
  Frontend eventuell den Listener/Notifier Mechanismus für die Dauer der Tests
  deaktivieren (und danach reaktivieren).  Hierfür müssen einfach die
  richtigen Shell-Befehle ausgeführt werden (was im Frontend und nicht in den
  Tests geschehen sollte); es könnte eine eigene Option dafür eingeführt
  werden, oder es könnte automatisch geschehen, sobald eine der "langen"
  Optionen (-a, -A, -x) gewählt wurde.

- Dediziertes Verzeichnis für Testfälle

  Das CLI Frontend findet die Tests momentan dadurch, dass es Dateien namens
  "Test*.py" im Verzeichnis "." sucht.  Besser wäre eventuell, ein eigenes
  Verzeichnis für die Testfall Module zu erzeugen (z.B. unterhalb von
  /usr/lib/python), und es sollten auch .pyo oder .pyc Dateien beachtet werden
  (v.a. .pyo Dateien; die sind mit Optimierung erzeugt worden, was wir zum
  Laden der Univention Admin Module ebenfalls machen müssen).


Fehlersuche
===========

Ein Fehler in einem der Testfälle kann z.B. zu folgendem Output führen:

,----
| testing module computers/trustaccount ... FAIL
| 
| ======================================================================
| FAIL: testing module computers/trustaccount
| ----------------------------------------------------------------------
| Traceback (most recent call last):
|   File "/root/GenericTest.py", line 473, in runTest
|     self.testCreate()
|   File "/root/GenericTest.py", line 491, in testCreate
|     self.hookAfterCreated(self.dn)
|   File "/root/TestComputerTrustAccount.py", line 64, in hookAfterCreated
|     self.__checkPassword(dn, self.createProperties['password'])
|   File "/root/TestComputerTrustAccount.py", line 61, in __checkPassword
|     raise PropertyInvalidError(self, dn, prop, nt1, lm2)
| PropertyInvalidError: Incorrect property password (Machine Password) of object testtrustaccount at DN cn=testtrustaccount,cn=computers,dc=ucs,dc=local (module computers/trustaccount). Expected: "0587176CF315F6A943BC69773103006A" Actual: "51D494924B82637F417EAF50CFAC29C3"
| 
| ----------------------------------------------------------------------
| Ran 1 test in 4.688s
| 
| FAILED (failures=1)
`----

Die erste Zeile gibt uns die Beschreibung des Tests (insbes. das
Handler-Modul, das getestet wird) und die Information, dass der Test
fehlgeschlagen ist.  `FAIL' meint hier, dass eine Kondition explizit geprüft
wurde und die Prüfung fehlgeschlagen ist.  `ERROR' würde bedeuten, dass ein
"unerwarteter Fehler" aufgetreten ist, was normalerweise eine Exception ist,
die -nicht- von AssertionError erbt, also z.B. ein TypeError, wenn ein Fehler
im Testfall vorliegt.  `ok' würde bedeuten, dass der Test erfolgreich war.

Unter der Liste der durchlaufenen Tests (und unter der doppelten Linie) werden
dann die Fehler zusammengefasst.  Die erste Zeile zeigt dabei wiederum (für
jeden Fehler) wie er fehlgeschlagen ist (also `FAIL' oder `ERROR') und die
Beschreibung des Tests.  Nach der einfachen Linie folgen dann der Traceback,
der erzeugt wurde und dessen Fehlermeldung.

Zum Ende wird zusammengefasst, wie viele Testfälle in welcher Zeit durchlaufen
wurden und wie viele davon auf welche Weise fehlgeschlagen sind.

Der Traceback soll natürlich der Fehleranalyse dienen; er zeigt an, welche
Funktionen von welcher Stelle aus aufgerufen wurden, bis es zum Fehler kam.
Der Fehler findet sich hier unterhalb von `hookAfterCreated', d.h. einer der
zusätzlich eingefügten Tests am neu erstellten Objekt ist fehlgeschlagen.
Aufgetreten ist der Fehler dann in der `__checkPassword' Methode -- dort habe
ich ihn für dieses Beispiel eingebaut :-)

Die Fehlermeldung nach dem Traceback verrät uns, welcher Fehler aufgetreten
ist (`PropertyInvalidError', d.h. eine Property hatte einen unerwarteten
Wert); in diesem Fall gibt er dazu aus, welche Property geprüft wurde
("password"), was sie beinhalten soll ("Machine Password"), wie das Objekt
heißt, das geprüft wurde ("testtrustaccount"), wo das Objekt im DIT angelegt
wurde ("cn=testtrustaccount,cn=computers,dc=ucs,dc=local"), zu welchem Modul
das Objekt gehört ("computers/trustaccount"), und welche Werte erwartet wurden
bzw. tatsächlich vorgefunden wurden.

--------------------

Hier ein weiteres, deutlich längeres Beispiel, entnommen aus einer Reihe von
User-Tests:

,----
| testing module users/user(KERBEROS,MAIL,PERSON,POSIX,SAMBA) ... ok
| testing module users/user(MAIL,PERSON,POSIX,SAMBA) ... ok
| testing module users/user(KERBEROS,PERSON,POSIX,SAMBA) ... ok
| testing module users/user(KERBEROS,MAIL,POSIX,SAMBA) ... ok
| testing module users/user(KERBEROS,MAIL,PERSON,SAMBA) ... ok
| testing module users/user(KERBEROS,MAIL,PERSON,POSIX) ... ok
| testing module users/user(MAIL) ... FAIL
| testing module users/user(PERSON) ... FAIL
| testing module users/user(SAMBA) ... FAIL
| testing module users/user() ... ok
| 
| ======================================================================
| FAIL: testing module users/user(MAIL)
| ----------------------------------------------------------------------
| Traceback (most recent call last):
|   File "/root/GenericTest.py", line 473, in runTest
|     self.testCreate()
|   File "/root/GenericTest.py", line 486, in testCreate
|     self._checkProcess(proc, 'create')
|   File "/root/GenericTest.py", line 200, in _checkProcess
|     proc.check(msg, self)
|   File "/root/BaseTest.py", line 361, in check
|     raise ProcessFailedError(self, message, test)
| ProcessFailedError: Failed to create object (module users/user)
| subprocess /usr/sbin/univention-admin failed (3):
| E: invalid Options: Need one of Posix Account, Samba Account or Personal Information in options to create user.
| 
| 
| ======================================================================
| FAIL: testing module users/user(PERSON)
| ----------------------------------------------------------------------
| Traceback (most recent call last):
|   File "/root/GenericTest.py", line 473, in runTest
|     self.testCreate()
|   File "/root/GenericTest.py", line 486, in testCreate
|     self._checkProcess(proc, 'create')
|   File "/root/GenericTest.py", line 200, in _checkProcess
|     proc.check(msg, self)
|   File "/root/BaseTest.py", line 361, in check
|     raise ProcessFailedError(self, message, test)
| ProcessFailedError: Failed to create object (module users/user)
| subprocess /usr/sbin/univention-admin failed (3):
| Traceback (most recent call last):
|   File "/usr/share/univention-admin-tools/univention-cli-server", line 227, in doit
|     output = univention.admincli.admin.doit(arglist)
|   File "/usr/lib/python2.6/site-packages/univention/admincli/admin.py", line 704, in doit
|     dn=object.create()
|   File "modules/univention/admin/handlers/__init__.py", line 288, in create
|   File "modules/univention/admin/handlers/__init__.py", line 538, in _create
|   File "modules/univention/admin/handlers/users/user.py", line 2176, in _ldap_modlist
|   File "modules/univention/admin/handlers/users/user.py", line 1624, in hasChanged
|   File "modules/univention/admin/handlers/users/user.py", line 1327, in __pwd_is_disabled
| AttributeError: 'NoneType' object has no attribute 'startswith'
| 
| 
| ======================================================================
| FAIL: testing module users/user(SAMBA)
| ----------------------------------------------------------------------
| Traceback (most recent call last):
|   File "/root/GenericTest.py", line 473, in runTest
|     self.testCreate()
|   File "/root/GenericTest.py", line 486, in testCreate
|     self._checkProcess(proc, 'create')
|   File "/root/GenericTest.py", line 200, in _checkProcess
|     proc.check(msg, self)
|   File "/root/BaseTest.py", line 361, in check
|     raise ProcessFailedError(self, message, test)
| ProcessFailedError: Failed to create object (module users/user)
| subprocess /usr/sbin/univention-admin failed (3):
| E: Object exists
| 
| 
| ----------------------------------------------------------------------
| Ran 10 tests in 101.676s
| 
| FAILED (failures=3)
`----

Der erste Traceback zeigt, dass das Anlegen (`testCreate') des Objekts
fehlgeschlagen ist, da der Unterprozess einen Fehler erzeugt hat
(`ProcessFailedError').  Es wird angegeben, welches Programm aufgerufen wurde,
mit welchem Fehlercode es beendet wurde und welche Ausgaben es erzeugt hat, in
diesem Fall die Information, dass die übergeben Optionen ungültig waren.

Der zweite Traceback zeigt einen Fehler, der direkt im Univention Admin
aufgetreten ist, was bedeutet, dass dieser durch Optionen an den CLI Admin zum
Absturz gebracht werden konnte.  Der Traceback und der genaue Testfall sollten
verwandt werden, um einen Bug gegen den Univention Admin zu öffnen (ist in
diesem Fall geschehen).

Der dritte Traceback zeigt die unspektakuläre "Object exists" Fehlermeldung
des CLI Admin; so ein Fehler an dieser Stelle weist darauf hin, dass es sich
um einen Folgefehler handelt, d.h. entweder das Benutzerobjekt oder eins
seiner Locks wurden bei einem der vorherigen Fehler nicht ordnungsgemäß
entfernt.

--------------------

Als letztes Beispiel hier die Ausgabe einer Suche nach übriggebliebenen Locks:

,----
| searching remaining locks ... FAIL
| 
| ======================================================================
| FAIL: searching remaining locks
| ----------------------------------------------------------------------
| Traceback (most recent call last):
|   File "/root/TestLocks.py", line 53, in testLocks
|     raise LeftoverLocks(dns, self)
| LeftoverLocks: There were leftover locks in:
| cn=00:12:25:34:44:32,cn=mac,cn=temporary,cn=univention,dc=ucs,dc=local
| 
| ----------------------------------------------------------------------
| Ran 1 test in 0.002s
| 
| FAILED (failures=1)
`----

Der Traceback verrät uns, dass ein Lock übriggeblieben ist, und an welcher DN
es zu finden ist.

Um alle verbliebenen Locks automatisch zu entfernen, reicht es, dem CLI
Frontend die `--cleanup' Option zu übergeben.  Die Ausgabe wird übrigens auf
Stdout ausgegeben und kann somit direkt in eine Datei umgeleitet werden.
