Samstag, 22. Februar 2014

Die 90% Marke ist erreicht

Mit einer Abdeckung von 90.17% habe ich nun den Löwenanteil meiner Version test_calc_reduce.c (aus einer Kopie von sweph.c hervorgegangen) abgedeckt. Dafür werden insgesamt 1194 Testaufrufe der Swiss Ephemeris mit den verschiedensten Parametern ausgeführt, wie das folgende Kommando ergibt:
test_calc_reduce | perl -ne 'BEGIN { $count = 0;} $count += $1 if /(\d+) OK/; END { print qq(Total number of tests: $count\n); }'
Selbstverständlich sind alle API-Funktionsaufrufe enthalten - für die Planetenberechnungen auch mit einer grossen Bandbreite verschiedener Zeiten, verschiedener Flags, verschiedener Planeten. Um auch den Fall abzufangen, dass bei beschädigten Ephemeridenfiles korrekt ein Fehler gemeldet wird, habe ich sogar die Ephemeridendatei sepl_18.se1 in zwei automatischen Tests mit präparierten Versionen ausgetauscht.

Was nun übrig bleibt an nicht abgedecktem Code, betrifft

  • Ausnutzung von Pufferungen: Wenn ein in zeitlicher Nähe liegender Planetenstand zuvor schon einmal berechnet wurde, können Teile der Daten von der letzten Rechnung wiederverwendet werden. Die Zweige, die auf die Pufferung zugreifen, werden in meinen Tests nicht durchlaufen, da ich zwischen den Aufrufen stets die globale Struktur swed initialisiere. Ich könnte zwar in den Testfällen ein Flag clear_buffer vorsehen (das standardmässig gesetzt ist, aber abgeschaltet werden kann), um auch diese Codezweige zu testen. Aber ich will das gar nicht. Pufferungen werde ich nämlich ignorieren, entfernen und bei Bedarf hinterher einfügen, wenn es eine Performanceanalyse als nötig zeigt.
  • Extrem seltene Fehlersituationen: Beispielsweise ein ungültiger Koeffizient mitten im File sepl_18.se1.
  • Grundsätzlich nie durchlaufene Codestellen: Z.B. eine Codestrecke für den Fall baryzentrischer Moshierephemeriden, die aber gar nicht vorgesehen sind und daher weiter oben schon abgefangen werden. Oder eine Fehlermeldung, falls in einer Routine zur Sonnenberechnung nicht wirklich die Sonne
Bei diesem Stand der Testüberdeckung vom 22.2.2014 werde ich es nun im wesentlichen belassen (die Auswertungsfiles wurden mit dem Programm lcov aus den gcov-Resultaten erzeugt). Das Risiko, einige Randwerte beim Refaktorisieren falsch zu behandeln, ist zwar vorhanden, aber es lähmt mich nicht!

Mittwoch, 22. Januar 2014

Initialisierung ausschalten

Das Ausschalten der Initialisierung
    if (0) {
    swe_close( );
    const struct swe_data swed0 = {
      .ephe_path_is_set = false,
      .jpl_file_is_open = false,
      .fixfp = NULL,
      .ephepath =  SE_EPHE_PATH,
      .jplfnam = SE_FNAME_DFT,    /* JPL file name, default */
      .geopos_is_set = false,   /* geopos is set, for topocentric */
      .ayana_is_set = false     /* ayanamsa is set */
      };
    memcpy(&swed,&swed0,sizeof(struct swe_data));
    }
treibt die Abdeckung um ein halbes Prozent in die Höhe. Vermutlich ist es anderer Code, der in diesen Fällen durchlaufen wird. Die Tests bleiben aber alle auf OK, was beruhigend ist: Es gibt anscheinend keine weiteren verschleppten Abhängigkeiten mehr, die die Ergebnisse der Tests beeinflussen könnten.

Sonntag, 12. Januar 2014

82 Prozent

Ich habe nun den Testgenerator erweitert, so dass auch Testdaten für siderische Positionen generiert werden können. Ebenso kann eine topozentrische Position in der Fixture angegeben werden, so dass topozentrische Planetenberechnungen in der Fixture zugänglich sind. Für die Fixsterne und ihre Grössen, für die Ayanamsa-Namen, Planetennamen und die Zeitgleichung erlaube ich mir, die Tests und Erwartungen in test_test_calc_reduce.c hart zu codieren. Sie kommen im Testprotokoll immer am Schluss, nach den Tests aus der Fixture

Und so sieht das Protokoll nun aus.

ruediger@herschel:~/workspace/swepar/src$ test_calc_reduce
geocentric positions: 250 OK   0 NOT OK
helio/barycentric positions: 285 OK   0 NOT OK
barycentric Sun: 5 OK   0 NOT OK
astrometric positions J2000: 90 OK   0 NOT OK
topocentric positions: 15 OK   0 NOT OK
sidereal positions with Lahiri Ayanamsha: 25 OK   0 NOT OK
sidereal positions / Suryasiddhanta, strictly computed: 25 OK   0 NOT OK
individual ayanamsa: 25 OK   0 NOT OK
sidereal rectangular geocentric: 40 OK   0 NOT OK
Asteroid Urania: 5 OK   0 NOT OK
Hamburger Poseidon: 5 OK   0 NOT OK
different coordinate systems: 4 OK   0 NOT OK
Obliquity of ecliptic and nutation: 1 OK   0 NOT OK
Moshier, geocentric positions: 140 OK   0 NOT OK
Moshier, heliocentric positions: 100 OK   0 NOT OK
Provoking a "file not found" error: 1 OK   0 NOT OK
swe_calc_ut: 1 OK   0 NOT OK
New mode: Barycentric, directly from file: 1 OK   0 NOT OK
Fixed Stars: 7 OK   0 NOT OK
Get Ayanamsa: 6 OK   0 NOT OK
Get Planet Names: 3 OK   0 NOT OK
Equation of Time for J2000: 1 OK   0 NOT OK
Die damit erreichte Testabdeckung liegt bei 82 Prozent. In dem arg zerklüfteten gcov-File gibt es jetzt keine Hauptfunktion mehr, die nicht abgedeckt wäre. Die vielen kleinen roten Einsprengsel entstehen:
  • durch zahllose nicht durchlaufene Ausnahmesituationen (z.B. "Ephemeride nach Moshier darf nicht benutzt werden für baryzentrische Koordinaten"). Einiges davon hat mit nicht mehr unterstützten Optionen zu tun.
  • durch nicht ausgenutzte Puffer: Da ich zwischen den Tests normalerweise nun swe_close() aufrufe, wird der Code zum Wiederverwenden bereits berechneter Positionen nicht durchlaufen. Dies könnte ich noch mit einer weiteren kleinen Testserie erreichen.

Freitag, 10. Januar 2014

Der Nageltest für die Local History

Gleich nach meinem gestrigen Lobpreis auf die Local History konnte ich nun die Probe aufs Exempel machen. In der Konsole hatte ich versehentlich ein rm test_calc* ausgeführt, was mir ohne Sicherungen einige Backup-Arbeit beschert hätte. Nun konnte ich auf die Local History zurückgreifen: Sie erschien wieder, nachdem ich die Files test_calc_reduce.c und test_calc_reduce.h wieder leer angelegt hatte.

Nach einigen kleineren Macken konnte ich nun fünf Fixsterntests und vier Ayanamsa-Tests hinzufügen, was die Abdeckung zwar nicht weltbewegend hochgetrieben hat (sie liegt stieg von 68% auf 71% an), aber mit swe_fixstar() und swe_deltat() sind nun zwei prominente Funktionen mit im Rennen.

Die folgende Übersicht zeigt,

  • dass im Bereich der Berechnung von Fixsternpositionen und -grössen noch besonders viel Luft ist.
  • lunar_osc_elem könnte mit ein paar Lilith-Berechnungen besser ausgebeutet werden.
  • sweph, die zentrale Funktion für die Planetenberechnungen, ist erst zu drei Vierteln abgedeckt, obwohl sie mit den bestehenden Tests bereits über 6000 Mal aufgerufen wird.
  • Der Code, der die Anfrage aus dem Puffer bedient, wird nicht durchlaufen, da ich bislang keine zwei identische Positionsanfragen nacheinander stelle.
  • Ausserdem werden keine Asteroiden abgefragt, für die eines der ast-Subdirectories abgefragt werden müsste.
  • Die Abdeckung lässt sich deutlich steigern, wenn noch ein paar topozentrische Berechnungen dazukommen (vor allem durch swid_get_observer).
  • Die Zweige, die Planetenberechnungen mit Ayanamsa enthalten, werden noch nicht durchlaufen.

Donnerstag, 9. Januar 2014

Local History

Hätte ich die Local History von Eclipse schon 2012 gekannt, dann hätte ich nicht folgende Frage im Forum der Clean Code Developer abgesetzt:

Delta-Backup für alle Änderungen an Textdateien

Genau die Umkehrung des üblichen Master/Slave-Verhältnisses leistet die Local History, die für mich ohne weiteres Zutun von Eclipse verwaltet wird. Alles, was ich brauche, kann ich damit machen: Versionen vergleichen (und zwar jeden beliebigen Stand, ohne dass ich Commits einchecken und mit einem Text versehen muss), und bei Bedarf auch alte Versionen wieder zurückholen.

Wenn ich will, kann ich sogar einzelne Deltas aus früheren Versionen wieder holen. Alles sehr komfortabel und nützlich.

Mittwoch, 8. Januar 2014

Über die Benutzung vorgesehener Funktionen. Coverage jetzt 68%

Stand gestern: In der bash ausgeführt, lieferte das Programm test_calc_reduce unsinnige Testergebnisse, während es in Eclipse mit dem Pfad Run as->Local C/C++ Application korrekt ausgeführt wurde. Wie ist dieser Unterschied zu erklären?

Zwischen den Tests hatte ich ja die Struktur swed explizit initialisiert.

    const struct swe_data swed0 = {
      .ephe_path_is_set = false,
      .jpl_file_is_open = false,
      .fixfp = NULL,
      .ephepath =  SE_EPHE_PATH,
      .jplfnam = SE_FNAME_DFT,    /* JPL file name, default */
      .geopos_is_set = false,   /* geopos is set, for topocentric */
      .ayana_is_set = false     /* ayanamsa is set */
      };
    memcpy(&swed,&swed0,sizeof(struct swe_data));
Indem ich nun dieser Initialisierung noch einen Aufruf von swe_close() voranstellte, funktionierte das Programm mit beiden Aufrufmethoden wieder gleich. Es lohnt sich vermutlich nicht, der genauen Ursache nachzugehen, warum es zu diesem Fehlverhalten kam. Denn selbst wenn ich es vergessen haben sollte, ein globales Datum zu löschen: Warum verhielt sich das Programm dann beim Aufruf aus Eclipse anders als beim Aufruf aus der Konsole?

Anderes Thema: Ich habe einmal den Coverage Analyzer gcov angeworfen und festgestellt, dass ich mit der Testabdeckung für sweph.c (also mein test_calc_reduce.c) schon ganz gut liege:

Die vertikale Seitenleiste in der Abdeckungs-Quelltextanzeige zeigt, dass es vor allem im unteren Teil des Files noch viel Rot gibt: da sind einige Funktionen, die noch gar nicht aufgerufen werden. Das topozentrische Koordinatensystem habe ich noch mit keinem Testfall angefordert. Die vielen kleinen Splitter zwischendurch sind Ausnahmefälle wie: Die Fehlermeldung bei der Chironberechnung für ein Datum kleiner als CHIRON_START. Oder ein File, das behauptet, Big Endian zu sein, aber es in Wahrheit nicht ist, weil schon seine eigene Grösse in falscher Codierung abgespeichert ist. Hier werde ich irgendwo einen Stopp machen. Ich könnte natürlich in einem Test ein präpariertes File in den Ephemeridenpfad schieben, das dort das entsprechende korrekte File ersetzt. So könnte ich auch viele dieser Splitter noch "erschlagen".

Eine 100%-Abdeckung bekomme ich voraussichtlich nicht hin. Aber ab 95% könnte ich mit gutem Gefühl damit beginnen, auch aggressivere Umbaumassnahmen in meinem "Branch" test_calc_reduce.c vorzunehmen. Und mir dabei ziemlich sicher zu sein, dass die Kernfunktionen der Swiss Ephemeris erhalten bleiben.

Dienstag, 7. Januar 2014

Statische Variablen

Gestern abend räsonierte ich noch über rätselhafte Fixsterne. Das Rätsel der zwei verschiedenen Positionen desselben Fixsterns (Aldebaran) war schnell gelöst.

Es ist so, wie ich gestern schon schrieb:

Das kann nur bedeuten, dass es noch irgendeinen anderen, nicht in swed enthaltenen, Zustand im Programm gibt. Das werde ich morgen weiter analysieren.
Und dieser verborgene Zustand bestand aus zwei in swe_fixstar deklarierten statischen Variablen:
  static char slast_stardata[AS_MAXCH];
  static char slast_starname[AS_MAXCH];
die zur Pufferung des zuletzt gelesenen Satzes im Fixsternkatalog verwendet wurden.

Das erklärt, dass der innere Zustand des Programms beim zweiten, identischen Aufruf ein anderer war als beim ersten Aufruf. Nun waren die beiden statischen Variablen - noch vom ersten Aufruf - gefüllt.

Wenn es aber nur eine Pufferung war - warum ergab der zweite Aufruf dann andere Werte als der erste?

Der Grund ist, dass ich durch das Löschen der swed (z.B. mittels eines swe_close()-Aufrufs, oder - wie in meinem Testprogramm: explizit) auch die Information swed.is_old_starfile = TRUE zerstört hatte, die beim ersten Aufruf ermittelt worden war (in meinem Ephemeridenverzeichnis befand sich nämlich nur das "alte" File fixstars.cat - was aber ja nicht verboten ist).

  if (swed.fixfp == NULL) {
    if ((swed.fixfp = swi_fopen(SEI_FILE_FIXSTAR, SE_STARFILE, swed.ephepath, serr)) == NULL) {
      swed.is_old_starfile = TRUE;
      if ((swed.fixfp = swi_fopen(SEI_FILE_FIXSTAR, SE_STARFILE_OLD, swed.ephepath, NULL)) == NULL) {
        ...
Wenn der Eintrag im Puffer gefunden wurde, springt das Programm direkt zum Label found. Nach Erreichen der Sprungmarke gibt es aber noch eine Stelle, in der mit den Koordinaten abhängig vom Flag swed.is_old_starfile unterschiedlich weitergerechnet wird. Das erklärt's:
  /* speed in ra and de, degrees per century */
  if (swed.is_old_starfile == TRUE) {
    ra_pm = ra_pm * 15 / 3600.0;
    de_pm = de_pm / 3600.0;
  } else {
    ra_pm = ra_pm / 10.0 / 3600.0;
    de_pm = de_pm / 10.0 / 3600.0;
    parall /= 1000.0;
  }
Nach Ausbau dieser Pufferung und Entfernung der beiden statischen Variablen slast_stardata und slast_starname liefen die beiden Fixsterntests nun beide mit OK durch. Insbesondere müssen die beiden sukzessiven Aufrufe nun auch dieselben Ergebnisse gebracht haben.

Nachdem dies erledigt war, habe ich auch das neuere File sefstars.txt ins Ephemeridenverzeichnis hinzugefügt. Im Grunde ist fixstars.cat ja ein alter Zopf, den ich irgendwann kappen kann.

Wie ich ja früher auch schon den Compileparameter TRACE eliminiert habe.

Das Tool nm, das das Symbolverzeichnis eines Objektfiles auflistet, zeigt, dass es noch zwei weitere statische Variablen in sweph.c gibt: epheflag_sv und nutflag:

ruediger@herschel:~/workspace/swepar/src$ nm -aC sweph.o | grep ' [bB] ' 
00000000 b .bss
00000204 b epheflag_sv.3938
00000200 b nutflag.5350
00000000 b slast_stardata.5374
00000100 b slast_starname.5375
Um diese werde ich mich morgen kümmern.

Verwirrend kommt nun hinzu, dass der Start des Programms test_calc_reduce in der Bash nicht mehr funktioniert, wohl aber der Start von Eclipse aus mittels Run As -> Local C/C++ Application