Schon 2018 hatte ich mich sehr umfassend mit dem Thema Voyager 1 und Voyager 2 beschäftigt. Dabei ging es um eine „fixe“ Idee, dass man das Board System welches Fortran beruht auf Python umswitcht. Ich habe in diesem Beitrag von 2018 dargelegt, wie die einzelnen Komponenten aufgebaut sind, wie man eventuell hier Python zum Einsatz bringen könnte. Aber nachhaltig auch festgestellt, dass ein Umstieg auf Python eventuell sogar schädlicher wäre.

 

Retro Engineering am Beispiel Voyager 1 und Voyager 2 Mission

 

Betrachtet man die Meldung bezüglich des „falschen Befehls“ der an Voyager 2 versendet wurde, so hätte man das in einer entsprechenden Emulation eigentlich feststellen müssen, dass eben dieser Befehl klar falsch ist. Bisher bin ich immer davon ausgegangen, dass so etwas in einer Emulation auf einem Environment stattfindet. Denn wie bei allen uns bekannten Sprachen, die man entsprechend einsetzt, gibt es entsprechende Testframeworks, die entsprechende Emulationen ausführen.

Die Antenne von »Voyager 2« zeigt nicht mehr exakt zur Erde, der Kontakt ist dadurch unterbrochen. Der Grund: ein falscher Befehl. Doch im Oktober könnte die Kommunikation wieder aufgenommen werden.

Quelle: https://www.spiegel.de/wissenschaft/nasa-verliert-kontakt-zu-sonde-voyager-2-wegen-eines-falschen-befehls-a-2c41883b-2e28-4413-a40b-075c59c10ce1

Hier eine weitere Meldung:

Der Kontakt zu der Sonde war am 21. Juli 2023 abgebrochen, nachdem "eine Serie geplanter Befehle" an sie gesendet worden war. Die hatten dazu geführt, dass sich die Antenne um 2 Grad von der Erde wegdrehte, weshalb kein Kontakt mehr möglich war.

Quelle: https://www.golem.de/news/raumfahrt-nasa-empfaengt-signal-von-voyager-2-2308-176370.html

 

 

Fortran spielte in den frühen Jahren der Raumfahrt und insbesondere bei der Entwicklung von Raumsonden eine wichtige Rolle. Das Voyager-Programm wurde in den 1970er Jahren gestartet, als Fortran immer noch eine der vorherrschenden Programmiersprachen für wissenschaftliche und technische Anwendungen war. Und Fortan wird bis heute gerade auch im wissenschaftlichen Umfeld immer noch genutzt!

Welche Fortran Versionen gibt es?

  • Fortran I (1957): Die erste Version von Fortran wurde 1957 von IBM entwickelt. Sie wurde für den IBM 704 Computer entwickelt und war die erste höhere Programmiersprache überhaupt. Fortran I unterstützte die grundlegenden Konzepte der Programmierung, aber es gab noch keine Unterprogramme (keine Unterstützung für Unterprogramme wie Funktionen oder Subroutinen).
  • Fortran II (1958): Fortran II wurde 1958 veröffentlicht und fügte einige neue Funktionen hinzu, darunter Unterprogramme und die Möglichkeit, Unterprogramme in anderen Unterprogrammen aufzurufen. Es war eine verbesserte Version von Fortran I.
  • Fortran IV (1962): Fortran IV war eine weiter verbesserte Version von Fortran und wurde 1962 veröffentlicht. Es führte neue Funktionen wie logische Variablen, DO-Schleifen und FORMAT-Anweisungen ein, um die Ausgabe zu formatieren.
  • Fortran 66: Fortran 66, auch bekannt als Fortran IV oder Fortran 66 Standard, wurde 1966 eingeführt. Es war die erste offizielle Version der Sprache mit einem standardisierten Sprachumfang. Fortran 66 führte neue Funktionen wie das IF-THEN-ELSE-Konstrukt ein und standardisierte einige vorhandene Sprachkonstrukte.
  • Fortran 77: Fortran 77, auch bekannt als Fortran 1977 Standard, wurde 1977 veröffentlicht. Es war eine bedeutende Aktualisierung und erweiterte den Funktionsumfang von Fortran erheblich. Fortran 77 führte Zeichenkettenverarbeitung, dynamische Speicherzuweisung (mit der ALLOCATE-Anweisung) und verbesserte Array-Funktionen ein.
  • Fortran 90: Fortran 90 wurde 1991 veröffentlicht und brachte wichtige Erweiterungen in die Sprache. Es führte den freien Formatierungsstil ein, verbesserte die Array-Unterstützung (einschließlich dynamischer Arrays und Array-Schnittoperationen), fügte neue Sprachkonstrukte wie MODULES, POINTERs und DO WHILE-Schleifen hinzu und ermöglichte rekursive Unterprogramme.
  • Fortran 95: Fortran 95 wurde 1997 als geringfügige Aktualisierung von Fortran 90 veröffentlicht. Es behebt einige Unklarheiten und Fehler in Fortran 90 und fügt einige neue Funktionen hinzu, wie beispielsweise Verbesserungen bei der ALLOCATE-Anweisung und Unterstützung für den CHARACTER-Datentyp mit variabler Länge.
  • Fortran 2003, Fortran 2008, Fortran 2018: Diese Versionen sind weitere Erweiterungen von Fortran, die neue Funktionen und Verbesserungen in die Sprache einbringen, wie z. B. Verbesserungen bei der Behandlung von Zeichenketten, Verbesserungen bei der parallelen Programmierung (Fortran 2008) und viele andere Funktionen.

 

Testframeworks für Fortran

In der Welt von Fortran gibt es mehrere Testframeworks, die verwendet werden können, um Unit-Tests für Fortran-Code durchzuführen. Wobei man sagen muss das Voyger 2 in Teilen noch auf Fortran 77 beruht aber auch Fortran 90.

 

Hier sind einige beliebte Testframeworks, es gibt aber weitaus mehr unter anderem entwickelt durch NASA Mitarbeiter:

Testframework Details Weiterführende Informationen Alternative
FRUIT (Fortran Unit Tester): FRUIT ist eines der ältesten Testframeworks für Fortran und wird häufig verwendet, um Unit-Tests für Fortran-77-Code zu schreiben. Es unterstützt die Erstellung von Testsuiten und Assertions, um die Funktionalität von Fortran-Routinen zu überprüfen.

https://github.com/mortele/FRUIT

FUnit FUnit ist ein moderneres Testframework für Fortran, das speziell für die Erstellung von Unit-Tests in Fortran entwickelt wurde. Es unterstützt Fortran 90 und höher und bietet eine Vielzahl von Testfunktionen und Assertions.

https://fortranwiki.org/fortran/show/FUnit 

 

Fortran Unit Test (FUT): FUT ist ein weiteres Testframework für Fortran, das es ermöglicht, Unit-Tests für Fortran-Code zu schreiben. Es bietet Funktionen wie Assertions und die Möglichkeit, Testergebnisse zu protokollieren.

https://github.com/dongli/fortran-unit-test

 

PFUnit (Parallel Fortran Unit Test): PFUnit ist ein Testframework, das speziell für den Test paralleler Fortran-Code entwickelt wurde. Es ermöglicht die Durchführung von Tests für parallele Funktionen und Routinen.

https://github.com/Goddard-Fortran-Ecosystem/pFUnit

Fortran Test Framework (FTF): Das Fortran Test Framework ist ein einfaches und leichtgewichtiges Testframework für Fortran. Es unterstützt die Erstellung von Unit-Tests und Assertions.

 

https://github.com/agforero/FTFramework

Ein Flussdiagramm zur Beschreibung der Laufzeit:

Diagram

 

 

ForFUnit: ForFUnit ist ein weiteres Testframework für Fortran, das es ermöglicht, Unit-Tests für Fortran-Code zu schreiben. Es bietet Funktionen wie Assertions und die Möglichkeit, Testergebnisse zu protokollieren.

 

 

 

Fortuno
Benutzerfreundliches, flexibles und erweiterbares objektorientiertes Fortran-Unit-Testing-Framework zum Testen von seriellen, MPI-parallelisierten und Coarray-parallelisierten Anwendungen.

https://github.com/aradi/fortuno

 

 

 

Aufbau von Testszenarien in Fortran

In Fortran gibt es keine eingebauten Testframeworks oder Assertions wie in einigen modernen Programmiersprachen. Daher müssen Entwickler Testroutinen und Assertions oft manuell implementieren. Hier ist ein einfaches Beispiel, wie ihr eine Testroutine mit Assertions in Fortran erstellen könnt:

PROGRAM TestExample
  IMPLICIT NONE

  ! Function to be tested
  REAL FUNCTION Square(x)
    REAL, INTENT(IN) :: x
    Square = x * x
  END FUNCTION Square

  ! Test routine
  SUBROUTINE RunTests
    INTEGER :: numTests, i
    REAL :: input, result, expected

    ! Array of test cases: (input, expected result)
    REAL, DIMENSION(3, 2) :: testCases
    testCases = RESHAPE([1.0, 1.0, &
                         2.0, 4.0, &
                         3.5, 12.25], [3, 2])

    numTests = SIZE(testCases, 1)

    DO i = 1, numTests
      input = testCases(i, 1)
      expected = testCases(i, 2)
      result = Square(input)

      ! Assertion
      IF (result == expected) THEN
        WRITE(*, '(A, F6.2, A, F6.2)') 'Test ', input, ': Passed. Result = ', result
      ELSE
        WRITE(*, '(A, F6.2, A, F6.2, A, F6.2)') 'Test ', input, ': Failed. Expected = ', expected, ', Got = ', result
      END IF
    END DO
  END SUBROUTINE RunTests

  ! Run the tests
  CALL RunTests()

END PROGRAM TestExample

Aufbau einer Testsuite in Fortran

In Fortran gibt es keine eingebaute Testsuit-Struktur wie in einigen modernen Programmiersprachen. Daher müssen Entwickler Testsuiten manuell implementieren. Eine Testsuite in Fortran ist eine Sammlung von mehreren Testroutinen, die verschiedene Funktionen oder Teile des Fortran-Codes testen. Hier ist ein einfaches Beispiel, wie Sie eine Testsuite in Fortran erstellen können:

Angenommen, ihr habt eine Fortran-Datei my_functions.f90, die einige Funktionen enthält, die ihr testen möchtet. Ihr könnt eine separate Testdatei test_my_functions.f90 erstellen, die eure Testsuite enthält.

! Datei: my_functions.f90
MODULE MyFunctions
  IMPLICIT NONE

  CONTAINS

  ! Funktion 1: Berechne das Quadrat einer Zahl
  REAL FUNCTION Square(x)
    REAL, INTENT(IN) :: x
    Square = x * x
  END FUNCTION Square

  ! Funktion 2: Berechne die Fakultät einer ganzen Zahl
  INTEGER FUNCTION Factorial(n)
    INTEGER, INTENT(IN) :: n
    INTEGER :: i, result
    result = 1
    DO i = 1, n
      result = result * i
    END DO
    Factorial = result
  END FUNCTION Factorial

END MODULE MyFunctions

 

! Datei: test_my_functions.f90
PROGRAM TestSuite
  USE MyFunctions
  IMPLICIT NONE

  ! Testroutine für Square
  SUBROUTINE TestSquare
    REAL :: input, result, expected

    ! Testfälle: (input, erwartetes Ergebnis)
    REAL, DIMENSION(3, 2) :: testCases
    testCases = RESHAPE([1.0, 1.0, &
                         2.0, 4.0, &
                         3.5, 12.25], [3, 2])

    WRITE(*, '(A)') 'Testing Square'
    DO i = 1, SIZE(testCases, 1)
      input = testCases(i, 1)
      expected = testCases(i, 2)
      result = Square(input)

      ! Assertion
      IF (result == expected) THEN
        WRITE(*, '(A, F6.2, A, F6.2)') 'Test ', input, ': Passed. Result = ', result
      ELSE
        WRITE(*, '(A, F6.2, A, F6.2, A, F6.2)') 'Test ', input, ': Failed. Expected = ', expected, ', Got = ', result
      END IF
    END DO
  END SUBROUTINE TestSquare

  ! Testroutine für Factorial
  SUBROUTINE TestFactorial
    INTEGER :: input, result, expected

    ! Testfälle: (input, erwartetes Ergebnis)
    INTEGER, DIMENSION(3, 2) :: testCases
    testCases = RESHAPE([0, 1, &
                         3, 6, &
                         5, 120], [3, 2])

    WRITE(*, '(A)') 'Testing Factorial'
    DO i = 1, SIZE(testCases, 1)
      input = testCases(i, 1)
      expected = testCases(i, 2)
      result = Factorial(input)

      ! Assertion
      IF (result == expected) THEN
        WRITE(*, '(A, I3, A, I6)') 'Test ', input, ': Passed. Result = ', result
      ELSE
        WRITE(*, '(A, I3, A, I6, A, I6)') 'Test ', input, ': Failed. Expected = ', expected, ', Got = ', result
      END IF
    END DO
  END SUBROUTINE TestFactorial

  ! Hauptprogramm der Testsuite
  CALL TestSquare()
  CALL TestFactorial()

END PROGRAM TestSuite

 

Wie man den Fehler hätte verhindern können?

Grundsätzlich gehe ich davom aus das die NASA solche Testmethoden einsetzt und auch entsprechende Emulationen laufen hat? Trotzdem wundert es das man hier eben doch falsche Befehle versendet hat. Entweder ist es menschliches Versagen, oder die Befehle wurden nicht ausreichend geprüft?

Das Testen von falschen oder fehlerhaften Befehlen in Fortran kann schwierig sein, da der Fortran-Compiler normalerweise während der Kompilierung Fehler in ungültigen Befehlen erkennt und den Kompilationsprozess beendet. Dennoch könnt ihr in einigen Fällen Testfälle erstellen, um zu überprüfen, wie Ihr Code mit fehlerhaften Befehlen umgeht.

Hier sind einige Möglichkeiten, wie ihr falsche Befehle in Fortran testen könntet:

  1. Ungültige Variablennamen: Erstellt Testfälle mit ungültigen oder nicht deklarierten Variablennamen, um zu überprüfen, wie euer Code auf solche Fehler reagiert.
  2. Syntaxfehler: Erstellt Testfälle mit fehlerhaften Syntaxkonstruktionen, wie beispielsweise fehlenden Klammern, falschen Zeichenketten oder fehlenden Semikolons.
  3. Falsche Datentypen: Überprüft, wie euer Code auf ungültige Datentypen reagiert, z. B. wenn ihr versucht, eine Zeichenkette in eine ganze Zahl umzuwandeln oder umgekehrt.
  4. Ungültige Compilerdirektiven: Testet, wie euer Code auf ungültige oder nicht unterstützte Compilerdirektiven reagiert.
  5. Fehlerhafte Ein-/Ausgabeoperationen: Testet, wie euer Code auf fehlerhafte Ein-/Ausgabeoperationen reagiert, z. B. wenn ihr versucht, in eine geschlossene Datei zu schreiben oder von einer nicht vorhandenen Datei zu lesen.

Die folgenden Beispiele zeigen, wie einige der oben genannten Szenarien in einer Testumgebung aussehen könnten:

FUNCTION Divide(a, b)
  REAL, INTENT(IN) :: a, b
  REAL :: Divide

  IF (b == 0.0) THEN
    WRITE(*, '(A)') 'Error: Division by zero.'
    STOP
  END IF

  Divide = a / b
END FUNCTION Divide

SUBROUTINE TestNegativeDivision
  REAL :: result
  ! Negative Test: Division durch Null
  result = Divide(5.0, 0.0) ! Erwartung: Fehlermeldung "Error: Division by zero."
END SUBROUTINE TestNegativeDivision
FUNCTION GetStringLength(str)
  CHARACTER(*), INTENT(IN) :: str
  INTEGER :: GetStringLength
  GetStringLength = LEN_TRIM(str)
END FUNCTION GetStringLength

SUBROUTINE TestInvalidStringLength
  INTEGER :: length
  CHARACTER(5) :: myString = "Hello"

  ! Negative Test: Zuweisen eines zu langen Strings zu einer begrenzten Zeichenkette
  length = GetStringLength(myString) ! Erwartung: Compilerfehler oder Laufzeitfehler
END SUBROUTINE TestInvalidStringLength

Diese Beispiele zeigen, wie ihr Testfälle erstellen könnt, die fehlerhafte Befehle in eurem Fortran-Code auslösen, um sicherzustellen, dass euer Code angemessen auf solche Fehler reagiert.

Wie in jeder anderen Sprache sollte man auch die entsprecchenden Workflows einhalten. Es geht immer um den Test von Schwachstellen, daher ergeben sich auch immer die gleichen Szenarieren im Test.

Statische Code-Analyse: Statische Code-Analysetools durchsuchen euren Quellcode, um mögliche Schwachstellen und Fehler zu erkennen, ohne den Code tatsächlich auszuführen. Diese Tools können beispielsweise potenzielle Speicherzugriffsfehler, uninitialisierte Variablen oder unsichere Aufrufe von Funktionen erkennen. Einige gängige statische Analysetools für Fortran sind:

Dynamische Code-Analyse: Dynamische Code-Analysetools führen Ihren Code aus und überwachen sein Verhalten zur Laufzeit. Diese Tools können Laufzeitfehler, Speicherlecks und andere potenzielle Probleme erkennen. Beim Testen von Fortran ist es wichtig, Ihre Tests mit verschiedenen Eingabewerten und Grenzfallbedingungen durchzuführen. Mit selbst ist nur ein Tool bekannt welches Entsprechend ab Fortran 77 eingesetzt werden kann:

Code-Reviews: Peer-Reviews und Sicherheitsüberprüfungen sind ebenfalls wichtige Schritte, um potenzielle Schwachstellen im Code zu entdecken. Lassen Sie andere Entwickler Ihren Code überprüfen und Feedback geben, um mögliche Sicherheitslücken zu identifizieren.

Kann man diesen Testcode automatisieren?

Ja, ihr könnt den Testcode in Jenkins oder GitHub in einer Pipeline automatisieren, um eine kontinuierliche Integration und automatisierte Testabläufe zu ermöglichen. Beide Plattformen bieten Möglichkeiten, Skripte und Tests in einer Pipeline zu definieren und auszuführen, um sicherzustellen, dass euer Code kontinuierlich getestet wird. Es wundert mich das dieser fehler gerade der NASA passiert ist, denn einige der oben genannten Testframeworks sind eigene Entwicklungen von NASA Mitarbeitern.

Beispiel Jenkins:

In Jenkins könnt ihr eine Pipeline erstellen, um eure Fortran-Tests zu automatisieren.

Ein einfacher Jenkins-Pipeline-Code könnte wie folgt aussehen:

pipeline {
    agent any

    stages {
        stage('Build') {
            steps {
                // Hier den Code aus dem Repository klonen und den Fortran-Compiler ausführen, um den Code zu kompilieren
            }
        }
        stage('Test') {
            steps {
                sh 'gfortran -o test_my_functions test_my_functions.f90 my_functions.f90' // Kompilieren des Testcodes
                sh './test_my_functions' // Ausführen der Testsuite
            }
        }
    }
}

Beispiel Github Actions;

In GitHub können Sie eine ähnliche Pipeline in Form von GitHub Actions erstellen. GitHub Actions ermöglicht es Ihnen, den Testcode in einer YAML-Datei zu definieren, die im Repository gespeichert wird.

Ein einfacher GitHub Actions-Pipeline-Code könnte wie folgt aussehen:

name: Fortran CI

on: [push]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout repository
      uses: actions/checkout@v2

    - name: Install gfortran
      run: sudo apt-get update && sudo apt-get install gfortran

    - name: Compile and run tests
      run: |
        gfortran -o test_my_functions test_my_functions.f90 my_functions.f90
        ./test_my_functions