25. Testsuite

Pepsi liefert unter tests/ eine End-to-End-Testsuite, die eine Live-Installation über SMTP ausübt und das beobachtbare Verhalten der gesamten Pipeline prüft, statt einer einzelnen Funktion isoliert. Sie ist bewusst eine Black-Box-Suite: Nachrichten werden mit dem gewöhnlichen mail-Befehl oder per rohem SMTP gesendet, und die Prüfungen lesen die Postfächer der Empfänger und die Tabelle pepsi.workqueue.

Dies ergänzt — ersetzt aber nicht — die Unit-Tests im Baum (cargo test --workspace), die die reine Logik (SRS, MIME-Herabstufung, DSN-Konstruktion, Sprachbewertung, DNS-Klassifizierung, …) ohne Netzwerk abdecken.

Zwei Dinge liegen dazwischen und werden von Integrationstests abgedeckt, die weder ein Netzwerk noch eine Datenbank benötigen. Die Migrations-Suiten (pepsi-setup/tests/import_*.rs und tests/cli_import.rs) lassen jeden MTA-Importer gegen einen Fixture-/etc-Baum laufen und führen dann das echte pepsi-setup-Binärprogramm Ende zu Ende aus, bis hin zu einem --wizard --import, das eine Konfiguration erzeugen muss, die Pepsis eigene Validierung besteht; siehe das Kapitel zum Erweitern für deren Aufbau. Die Dispatcher-Lebenszyklus-Suite (pepsi-test-stages) benötigt ein PostgreSQL und wird von make check ausgeführt.

25.1. make check: die Schranke ohne entfernte Konten

make check ist der Einstiegspunkt für alles, was keine echten Mail-Konten braucht. Der Reihe nach führt es aus:

  1. make check-telemetry-socket und make check-systemd-units — tests/check-telemetry-socket.sh und tests/check-systemd-units.sh, Konsistenzprüfungen über die systemd-Units und die Debian-Paketierung, die keine Installation benötigen und nie überspringen.

  2. cargo test --workspace --exclude pepsi-test-stages --locked — jeden datenbankfreien Test im Baum. (Ein blankes cargo test --workspace würde pepsi-test-stages einschließen und daher eine Datenbank benötigen; bevorzugen Sie make check, das jene Suite an eine Bedingung knüpft, statt an ihr zu scheitern.)

  3. tests/check-lifecycle.sh — die Dispatcher-Lebenszyklus-Suite, nach bestem Bemühen.

  4. tests/maildir-writer-suid.sh, tests/dot-forward-suid.sh und tests/whitelist-suid.sh — die Verträge der setuid-/setgid-Helper, nach bestem Bemühen.

  5. tests/interop-auth.sh — eine DKIM/ARC-Prüfung über Implementierungen hinweg, nach bestem Bemühen. Pepsis eigene Round-Trip-Tests signieren und verifizieren mit derselben mail-auth-Bibliothek und können daher keine Transformation erkennen, die darin durchgängig falsch ist; dieses Skript bewertet von Pepsi signierte Nachrichten (alle vier c=-Kombinationen, dazu eine manipulierte Kopie, die abgelehnt werden muss, dazu ein ARC-Siegel) mit zwei unabhängigen Implementierungen, ganz ohne DNS. dkimpy erhält die öffentlichen Schlüssel über seine dnsfunc (tests/interop-verify.py) und bewertet DKIM und die ARC-Kette; OpenDKIM liest sie im -t-Testmodus des opendkim-Filters aus einer TestPublicKeys-Datei und bewertet DKIM (es hat keinen ARC-Verifizierer; opendkim-testmsg ist nicht verwendbar, weil es nur gegen Live-DNS verifiziert). Jede DKIM-Signature wird bewertet, die RSA- und die Ed25519-Signatur — dkimpy braucht für Letztere PyNaCl. Jeder Verifizierer überspringt (SKIP) seine eigenen Prüfungen, wenn er fehlt, und das Skript überspringt als Ganzes, wenn beide fehlen. Unter Debian: apt install python3-dkim python3-nacl opendkim (der Filter liegt in /usr/sbin; setzen Sie OPENDKIM, wenn er anderswo liegt, und deaktivieren Sie den opendkim-Dienst, den das Paket aktiviert — der Test spricht nie mit ihm). PEPSI_INTEROP_REQUIRE=1 macht aus einem fehlenden Verifizierer einen Fehlschlag.

  6. cargo deny check — Advisories, Verbote, Lizenzen und Quellen. Dieses ist nicht nach bestem Bemühen: Es wird nur übersprungen, wenn cargo-deny wirklich nicht installiert ist, und ein echter Befund lässt das Ziel fehlschlagen. Lesen Sie deny.toml, bevor Sie der Schranke trauen: Seine ignore-Liste ist nicht leer.

  7. make docs-linkcheck — jeder interne Querverweis des Handbuchs muss sich in jeder Sprache auflösen (ein Sphinx-dummy-Build mit Warnungen als Fehlern). Wird nur übersprungen, wenn sphinx-build nicht installiert ist.

Die Thunderbird-S/MIME-Interoperabilitätsschranke ist bewusst nicht in make check — sie startet ein echtes (kopfloses, von Marionette gesteuertes) Thunderbird und kostet etwa fünfzehn Sekunden. Sie ist make interop-thunderbird, und make integrationtests hängt von ihr ab.

25.1.1. Kontinuierliche Integration

Mehrere der obigen Schritte überspringen, wenn ein Werkzeug oder eine Datenbank fehlt — auf dem Rechner eines Entwicklers der richtige Standard und für einen automatisierten Lauf genau falsch, wo ein übersprungenes, grün gemeldetes Gate wie ein bestandenes aussieht. contrib/ci/ enthält Jobs in dem Layout, das der CI-Worker von GNU Taler ausführt (dasselbe, das vendor/taler-rust/contrib/ci/ verwendet): contrib/ci/ci.sh <job> baut das Image aus contrib/ci/Containerfile mit podman und führt darin contrib/ci/jobs/<job>/job.sh mit eingehängtem Quellbaum aus; contrib/ci/run-all-jobs.sh führt jeden Job der Reihe nach aus. Die Jobs sind:

1-gates

Die Gates zur Übersetzungszeit: RUSTFLAGS="-D warnings" cargo build, cargo clippy … -D warnings und cargo fmt --check.

2-check

make check mit jedem Best-Effort-Gate scharf geschaltet. Das Image enthält dkimpy, PyNaCl, OpenDKIM, cargo-deny und Sphinx, der Job startet PostgreSQL, setzt PEPSI_INTEROP_REQUIRE=1 und schlägt fehl, wenn irgendeine Zeile des Protokolls ein SKIP meldet. make check läuft als unprivilegierter Benutzer auf einer privaten Kopie des Baums, so wie ein Entwickler es ausführt — das pepsi-setup run der Lifecycle-Suite würde sonst zu paketierten Systemkonten wechseln, die der Container nicht hat —, und die drei setuid/setgid-Suiten, die root benötigen, werden danach erneut als root ausgeführt; ihr Überspringen mit „not running as root“ im ersten Durchlauf ist das einzige akzeptierte Überspringen. CI_ALLOWED_SKIPS (ein erweiterter regulärer Ausdruck) benennt jedes weitere absichtlich akzeptierte Überspringen.

3-docs

make docs-all: jede Sprache des Handbuchs einschließlich ihres PDFs, also der Build, der an einem nicht deklarierten Unicode-Zeichen scheitert.

4-upgrade

Der Schema-Upgrade-Test, tests/check-upgrade.sh, gegen das vorherige Release-Tag (ohne Wirkung, solange es keines gibt): Er baut pepsi-setup und pepsi-status dieses Releases, installiert dessen Schema und lädt dessen eingefrorene Fixture (tests/upgrade/), führt mit diesem Baum das Upgrade samt Sicherung durch und prüft, dass jede Zeile erhalten bleibt, die Sicherung sich wiederherstellen lässt, der Dispatcher dieses Baums die alte Warteschlange abarbeitet und das alte Release das aktualisierte Schema mit Exit-Status 78 ablehnt. Das Skript läuft auch außerhalb der CI, wenn man ihm die Programme und das SQL-Verzeichnis eines vorherigen Releases gibt.

Die Rust-Toolchain im Image ist festgenagelt (RUST_TOOLCHAIN im Containerfile), statt stable zu folgen, sodass ein neuer clippy-Lint einen unbeteiligten Commit nicht rot färben kann; heben Sie sie bewusst an. Die Jobs bauen nach target-ci/, damit sie das target/ eines Entwicklers nicht ungültig machen. Führen Sie einen Job lokal von der Wurzel des Baums aus, mit initialisiertem vendor/taler-rust-Submodul, z. B. contrib/ci/ci.sh 2-check.

25.1.2. Die Lebenszyklus-Suite und ihre Datenbank

pepsi-test-stages startet das echte pepsi-dispatch-Binärprogramm gegen ein echtes PostgreSQL und prüft die Zustände der pepsi.workqueue-Datensätze, benötigt also einen erreichbaren Server. Es hat zwei Datenbankmodi, gewählt durch PEPSI_TEST_DATABASE:

nicht gesetzt — der Standardfall und der Entwicklungsablauf

Jeder Test legt seine eigene Wegwerf-Datenbank mit CREATE an und entfernt sie mit DROP, sodass die Suite parallel läuft. Das tut ein blankes cargo test -p pepsi-test-stages; die verbindende Rolle muss CREATE DATABASE / DROP DATABASE ausführen können.

gesetzt (z. B. pepsicheck)

Alle Tests teilen sich diese eine bereits vorhandene Datenbank — nie angelegt, nie gelöscht, ihr Inhalt bleibt erhalten — und setzen beim Aufbau allein pepsi.workqueue zurück. Die Suite muss dann mit --test-threads=1 laufen.

tests/check-lifecycle.sh ist der zweite Modus, automatisiert: Es sucht nach einer pepsicheck-Datenbank (und legt sie an, wenn sie fehlt), setzt das Schema mit pepsi-setup run --reset zurück — nach einem vorangehenden blanken run, damit eine frisch angelegte Datenbank das _v-Schema hat, aus dem der Lösch-Schritt löscht — und führt die Suite seriell dagegen aus, wobei die Datenbank stehen bleibt. Funktionieren weder die Suche noch das Anlegen, überspringt es: Exit 0, was weder ein Bestehen noch ein Fehlschlag ist.

Vier Umgebungsvariablen justieren das alles:

PEPSI_TEST_DATABASE

Wählt den geteilten Modus und benennt die Datenbank. Nicht gesetzt bedeutet Wegwerf-Modus.

PEPSI_TEST_PGHOST

Das Socket-Verzeichnis für die lokale, peer-authentifizierte Verbindung. Standardwert /var/run/postgresql.

PEPSI_TEST_PGUSER

Die Login-/Peer-Rolle, als die verbunden wird. Standardwert $USER.

PEPSI_TEST_DISPATCH_LOG

Auf 1 setzen, um die eigenen Logs des gestarteten Dispatchers zu sehen, die die Suite standardmäßig stummschaltet.

(tests/check-lifecycle.sh liest zusätzlich PEPSI_CHECK_DB, um die von ihm verwaltete geteilte Datenbank umzubenennen, Standardwert pepsicheck.)

Ein einzelner Test oder ein einzelnes Modul ist cargo test -p <crate> <name::filter>, z. B. cargo test -p pepsi-common srs::.

25.1.2.1. Die Tests der Privilegiengrenzen

Sechs Testdateien prüfen die Datenbankrechte aus Die Privilegientrennung gegen einen echten Server — service_role_grants, secure_link_grants, admin_grants, whitelist_grants, setup_task sowie die Rechtefälle in keystore und keydisc_lifecycle. Jede führt das produktive provision_roles aus und wechselt dann per SET ROLE in jedes Konto, um PostgreSQL zu fragen, was es tun darf.

Sie werden übersprungen (mit einer SKIPPED-Zeile auf stderr, sichtbar mit --nocapture), wenn irgendeine geprüfte Rolle ein Superuser ist, weil ein Superuser jedes Recht umgeht und sich über die Grenze nichts lernen lässt. Auf einem Entwicklungsrechner ist das der Normalfall: Das Konto, das die Tests ausführt, ist pepsi, das zugleich die eigene Rolle der Pipeline und meist auch der Superuser des Clusters ist. Um sie auszuführen, lassen Sie die Suite laufen, während pepsi vorübergehend kein Superuser ist, aber in die anderen Rollen wechseln kann — eingerichtet aus einer zweiten Superuser-Sitzung —, und stellen Sie es danach wieder her:

$ psql -U some_superuser -c "ALTER ROLE pepsi NOSUPERUSER CREATEDB CREATEROLE" \
    -c 'GRANT "pepsi-ingress", "pepsi-httpd", "pepsi-telemetry", "pepsi-whitelist",
              "pepsi-crypto", "pepsi-keydisc", "pepsi-config"
        TO pepsi WITH INHERIT FALSE, SET TRUE'
$ cargo test -p pepsi-test-stages --test service_role_grants -- --nocapture
$ psql -U some_superuser \
    -c 'REVOKE "pepsi-ingress", "pepsi-httpd", "pepsi-telemetry", "pepsi-whitelist",
               "pepsi-crypto", "pepsi-keydisc", "pepsi-config" FROM pepsi' \
    -c "ALTER ROLE pepsi SUPERUSER"

INHERIT FALSE ist wichtig: Damit kann pepsi jede Rolle annehmen, ohne ihre Privilegien zu erwerben, sodass die Prüfungen an pepsi selbst aussagekräftig bleiben.

Die End-to-End-Tests der Verwaltungs-API (admin_api) enthalten einen weiteren rollenabhängigen Fall: Der erfolgreiche CONFIG_DB-Schreibvorgang verbindet sich mit options[role]=pepsi-config und wird daher übersprungen (wieder mit einer SKIPPED-Zeile), wenn die Testrolle nicht SET ROLE "pepsi-config" ausführen kann. Dasselbe Recht wie oben lässt ihn laufen. Keiner der Ablauftests dort — Leerlauf- und absoluter Ablauf von Sitzungen, expires_at und disabled von Tokens, der pepsi-httpd prune-Job — wartet, bis ein Zeitfenster verstrichen ist: Jeder datiert den Zeitstempel zurück, den die Datenbank mit now() vergleicht, was die durchsetzende Abfrage nicht vom Verstreichen der Zeit unterscheiden kann.

25.2. Auch die Dokumentations-Builds sind Prüfungen

make docs führt sie alle für die Quellsprache aus und ist das eine Dokumentationsziel in der allgemeinen Qualitätsschranke. Es ist ein Alias für make docs-en; make docs-de und make docs-fr bauen dieselben Artefakte aus den übersetzten Katalogen, und make docs-all baut jede Sprache. Vier getrennte Builds stecken darin, und dass einer durchläuft, sagt wenig über die anderen:

make docs-html-en

Sphinx-HTML. Fängt kaputte Querverweise, unbekannte Rollen und fehlerhafte Tabellen ab.

make man

Die Handbuchseiten der Abschnitte 1/5 über docs/rst2man.py. Fängt Titel-Über-/Unterstriche ab, die nicht zur Titellänge passen — ein docutils-Fehler, den der HTML-Build toleriert. Nur in der Quellsprache: Die roff-Seiten werden direkt aus docs/*.rst gerendert und laufen nie durch Sphinx, haben also keinen Übersetzungskatalog.

make info

Das GNU-Info-Handbuch, über den Texinfo-Builder von Sphinx und makeinfo. Nur in der Quellsprache, aus demselben Grund, aus dem install-info ein pepsi.info in einem Info-dir-Index registriert.

make docs-pdf-en

LaTeX. Der am leichtesten vergessene und der am härtesten scheiternde. pdflatex hat für ein beliebiges Unicode-Zeichen keine Glyphe und degradiert nicht, wenn es ihm begegnet: Es bricht mit „Unicode character … not set up for use with LaTeX“ ab und erzeugt überhaupt kein PDF. HTML und man rendern dasselbe Zeichen klaglos, sodass ein neuer Pfeil, ein Rahmenzeichen oder ein griechischer Buchstabe in der Prosa beide besteht und nur diesen Build bricht.

Jedes im Handbuch verwendete Nicht-ASCII-Zeichen muss in latex_elements["preamble"] in docs/manual/conf.py deklariert werden. Der von den ASCII-Art-Pipeline-Diagrammen benutzte Rahmenzeichensatz wird absichtlich auf reines ASCII abgebildet: Diese Diagramme stehen in code-block:: text, sodass die Ersetzung in einer Verbatim-Umgebung expandiert wird, in der kein Mathematikmodus verfügbar ist.

Der zweite Fehlermodus ist eine Tabelle, die nicht auf eine Seite passt. Sphinx rendert eine list-table erst oberhalb einer Zeilenzahlschwelle in LaTeX‘ longtable — die über Seiten hinweg umbricht und den Kopf wiederholt —; darunter wird die Tabelle zu einem tabular, das überhaupt nicht umbrechen kann. Die Heuristik zählt Zeilen, aber was eine Seite überläuft, ist die Höhe, und die beiden gehen bei einer breiten Tabelle mit umbrechenden Zellen weit auseinander: Die 113-zeilige RFC-Übersichtstabelle bricht anstandslos um, während die 17-zeilige, siebenspaltige Vergleichstabelle in Einführung es nicht tut und 397 pt unten über die Seite hinausläuft.

Die Abhilfe besteht darin, es ausdrücklich zu sagen, statt sich auf die Heuristik zu verlassen:

.. list-table::
   :header-rows: 1
   :widths: 20 14 14 14 14 14 14
   :class: longtable

Anders als ein fehlendes Zeichen lässt dies den Build nicht scheitern — es ist eine Overfull \vbox-Warnung und ein PDF mit Inhalt außerhalb der Seite —, durchsuchen Sie die Log-Datei also nach Overfull \vbox und behandeln Sie alles jenseits von ein, zwei Punkt als echten Mangel. Überschüsse unterhalb eines Punktes sind gewöhnliche typografische Rundung.

Ignorieren Sie beim Lesen der Ausgabe die Hyper reference … undefined-Warnungen der ersten Durchläufe — latexmk löst Vorwärtsreferenzen in späteren auf. Der letzte Durchlauf, in docs/manual/_build/pdf/en/latex/pepsi.log, ist derjenige, der sauber sein muss.

Die Übersetzungen werden auf dieselbe Weise geprüft, und die Prüfung ist nicht überflüssig: Ein Zeichen, das ein Übersetzer einführt — ein Guillemet, ein deutsches Anführungszeichen, ein geschütztes Leerzeichen —, lässt denselben pdflatex-Build auf dieselbe Weise scheitern, und kein rein englischer Lauf würde ihm je begegnen. make docs-all ist daher eher eine Freigabeschranke als eine Schranke je Commit; das PDF jeder Sprache landet in docs/manual/_build/pdf/<lang>/latex/pepsi.pdf, und ein deutscher oder französischer Build braucht zusätzlich die installierte babel-Unterstützung dieser Sprache (Debian: texlive-lang-german, texlive-lang-french).

Die Übersetzungen aktuell zu halten besorgt make update-po: Es extrahiert die Nachrichtenvorlagen neu und führt sie in docs/manual/locale/<lang>/LC_MESSAGES zusammen, sodass geänderte Absätze als fuzzy markiert und neue unübersetzt zurückkommen. Nur die .po-Dateien stehen unter Versionskontrolle; Sphinx kompiliert sie zur Build-Zeit.

25.3. Topologie

Die Suite steuert drei echte Mail-Konten auf drei Hosts, beschrieben in einer kleinen INI-Datei, tests/test-accounts.ini. Diese Datei gehört nicht zum Quellbaum: Legen Sie sie aus der Vorlage mit cp tests/test-accounts.ini.sample tests/test-accounts.ini an und ersetzen Sie deren Platzhalter-Konten durch Hosts, die Sie kontrollieren:

Rolle

Konto (ssh-Ziel = E-Mail)

Funktion

ADMIN

root@<mx-host>

Betreibt Pepsi (ingress, dispatch, httpd), PostgreSQL und systemd; außerdem den Smarthost, über den die bediente Domain weiterleitet.

ALICE / BOB

alice@<served> / bob@<served>

Lokale Konten auf der bedienten Domain. Ihr MTA leitet über ADMIN weiter und wird von ihm als vertrauenswürdig eingestuft (MYNETWORKS), sodass ihre Mail lokalen Ursprungs ist (der ausgehende Pfad).

CAROL

test@<other>

Ein Konto auf einem unabhängigen Host. Ihre Mail erreicht ADMINs öffentlichen MX auf Port 25 als fremder MTA — der eingehende Pfad.

DAVE

dave@<mx-host> (optional)

Ein lokaler Unix-Benutzer auf dem ADMIN-Host, der Mail per direkter lokaler Zustellung in sein Maildir empfängt (der Lokale-Zustellung-Pfad; keine Weiterleitung). Anders als die anderen wird er nie direkt per ssh angesprochen — sein Postfach wird als root über die ADMIN-Verbindung gelesen. Nur 07-maildir-test.sh benötigt ihn; 01-deploy.sh richtet die Benutzer dave/dave-broken und die Gruppe pepsi-maildir ein, wenn er gesetzt ist.

Jeder Host wird über passwortloses ssh erreicht; der Harness installiert nichts. ALICE/BOB verwenden eine mbox (/var/mail/<user>); CAROL und DAVE verwenden ein Maildir. Die Postfach-Helper sind formatunabhängig und finden eine Nachricht über ein laufspezifisches Token in beiden Layouts.

Bemerkung

Die beiden anderen Hosts haben eigene Annahmerichtlinien, und die greifen lange vor Pepsi. In der Korrektheits-Suite, die eine Handvoll Nachrichten verschickt, sind sie unsichtbar; in den Benchmarks, die Hunderttausende verschicken, entscheiden sie. Zwei davon besonders:

  • Der MX von CAROL ist die empfangende Seite jedes ausgehenden Tests. Ist er selbst eine Pepsi-Installation, dann sind [pepsi-ingress] CONN_RATE_PER_SECOND und CONN_RATE_BURST dort standardmäßig 1 und 1 — eine neue Verbindung pro Sekunde aus einem bestimmten Quellbereich —, sodass ein Relay, das pro Nachricht eine Verbindung öffnet, mit 421 4.7.0 Too many connections from your address beantwortet wird und zurückstellt. Es geht nichts verloren; gemessen wird aber auch nichts. Erhöhe sie auf diesem Host, bevor du irgendeiner ausgehenden Zahl glaubst.

  • Sind sie erhöht, bleibt die eigene Geschwindigkeit dieses Hosts. Ist der CAROL-Host deutlich kleiner als der getestete MTA, ist jede ausgehende Messung die Zahl dieses Hosts, nicht die von Pepsi; das Kapitel Performance sagt, wie solche Zahlen zu lesen sind.

  • Der MTA von ALICE/BOB ist der Smarthost, an dem der Relay-Schwanz endet, und Postfix‘ message_size_limit liegt standardmäßig bei zehn Millionen Bytes. Deshalb können die größeren Größen des Stage-Durchlaufs den Smarthost-Pfad überhaupt nicht passieren — was der Durchlauf als das eigene 552 des nächsten Hops meldet, anstatt zu raten.

Dieselbe Datei enthält ein Konto, das kein Mailkonto ist, in einem [demobank]-Abschnitt:

[demobank]
URL  = https://bank.demo.taler.net
USER = pepsi-ci
PASS = pepsi-pass

Dies ist ein Bankkonto bei der öffentlichen GNU-Taler-Demo-Bank, deren Währung (KUDOS) Spielgeld ist. 12-wallet-topup-test.sh benötigt es, weil eine manuelle Wallet-Aufladung dem Benutzer nur mitteilt, wohin er Geld überweisen soll — dem Wallet wird nichts gutgeschrieben, bis diese Banküberweisung tatsächlich stattfindet. Der Harness spielt daher die Bank des Benutzers: Er meldet sich an und führt genau die Überweisung aus, um die die per E-Mail versandten Anweisungen bitten; nur so kann der Test prüfen, dass die Abhebung wirklich abgeschlossen wird, und nicht bloß, dass Anweisungen verschickt wurden. Registrieren Sie ein Konto unter https://bank.demo.taler.net/ (ein neues beginnt mit KUDOS:100 und darf bis KUDOS:500 ins Soll gehen — etwa 120 Durchläufe dieses Falls); DEMOBANK_USER, DEMOBANK_PASS und DEMOBANK_URL in der Umgebung haben Vorrang vor der Datei. Kein anderes Skript verwendet es, und wenn der Abschnitt fehlt, wird nur diese eine Gutschriftprüfung übersprungen.

25.4. Die Skripte

tests/lib.sh

Gemeinsame Bibliothek, die von jedem nummerierten Skript gesourct wird. Sie parst die Konten-INI, stellt die von der gemeinsamen Vorprüfung verwendeten ssh-/Abhängigkeits-/Datenbank-/Dienst-Proben, die Ergebniszähler und die farbige ok/no/skip-Ausgabe, die SQL- und Reset-Helper des ADMIN-Hosts, die Postfach-Sende-/Empfangs-Helper, die Taler-Wallet-Helper, die RFC-3464-DSN-Prüfung und — für die erweiterten Skripte — send_smtp (einen Roh-SMTP-Sender, der die ESMTP- und DSN-Parameter setzen kann, die der mail-Befehl nicht kann) und hop_caps (eine EHLO-Fähigkeitsprobe) bereit. Sie ist für sich allein nicht ausführbar.

01-deploy.sh

Richtet eine vollständige Pepsi-Installation auf dem ADMIN-Host aus einem sauberen Checkout ein: Dienstbenutzer, PostgreSQL-Rolle/-Datenbank, die Konfigurationsdatei (einschließlich der vollständigen Stage-Pipeline), pepsi-setup (TLS, Schema, DKIM-Schlüssel, DNS-Einträge), den Demo-Zahlungs-Händler und die systemd-Units. Es verifiziert nur die erforderlichen Werkzeuge und bricht mit einer genauen Liste ab, falls welche fehlen — es installiert nie OS-Abhängigkeiten.

02-pipeline-test.sh

Die zentrale Happy-Path-Suite (T1–T6): die Pay-to-Send-Anti-Spam-Stage (unbezahlt → Bounce, bezahlt → zugestellt, zentral aus dem Taler-Wallet des Runners bezahlt), der Sprachfilter (auf die schwarze Liste gesetztes Französisch → Bounce), der ausgehende Signier-/Relay-Pfad (DKIM + ARC beim Empfänger verifiziert), das Lernen der weißen Liste mit SRS-Umschlag-Umschreibung und ein zweiter lokaler Benutzer.

03-dsn-test.sh

RFC-3461-DSN-Erweiterungen und Bounce-Varianten, mit send_smtp: NOTIFY=NEVER unterdrückt die DSN (D1); ENVID/ORCPT werden zurückgespiegelt (D2); RET=HDRS gibt nur Header zurück (D3); NOTIFY=SUCCESS mit ORIGINATE_SUCCESS_DSN liefert eine positive DSN (D4); ein NXDOMAIN-Empfänger routet zum Standard-Bounce (D5); ein unerreichbares Relay erzeugt eine DELAY-DSN (D6); und ein Null-Absender-Bounce an eine SRS0=…-Adresse wird von Ingress zurück zum ursprünglichen Absender rückwärtsdekodiert (D7).

04-settings-test.sh

Die Überschreibungsschicht pro Adresse: ein pepsi-settings-Datensatz ändert das Ergebnis eines Empfängers, aber nicht das eines anderen (S1); ein Konto bearbeitet seine eigenen Überschreibungen per E-Mail und wird bestätigt (S2); eine Bearbeitung an einem Nicht-EDITABLE_STAGES-Abschnitt wird abgelehnt und speichert nichts (S3); und ein DATA mit divergierenden Überschreibungen pro Empfänger wird in separate Datensätze der Warteschlange aufgeteilt (S4).

05-mime-test.sh

8BITMIME-/SMTPUTF8-Behandlung: ein 8-Bit-Nachrichtentext und ein UTF-8-Subject überstehen das Relay unversehrt, wenn der Zustell-Hop die Erweiterung anbietet, werden herabgestuft (Quoted-Printable/Base64, RFC 2047), wenn er es nicht tut, und eine Nicht-ASCII-Empfängeradresse, die der Hop nicht darstellen kann, schlägt dauerhaft fehl. Das Skript probt zuerst das EHLO des Hops und nimmt den passenden Zweig.

06-cli-test.sh

Die Operator-CLIs, direkt über ssh ausgeführt: pepsi-config (get/dump/pathsub), pepsi-whitelist (add/list/remove und die dkim/signature-Flags), pepsi-settings (set/get/list/unset/remove), pepsi-tlsrpt (report --dry-run / prune) und der pepsi-httpd-/metrics-Endpunkt.

07-maildir-test.sh

Direkte (lokale Maildir-)Zustellung über die pepsi-stage-relay-to-maildir-Stage, die den setuid-root pepsi-helper-maildir-writer antreibt. Eine Nachricht an den lokalen DAVE-Benutzer landet in dessen Maildir mit den vorangestellten Return-Path/Delivered-To/Received-Headern (L1); eine einzelne Nachricht an einen lokalen und einen entfernten Empfänger wird aufgeteilt, wobei dave lokal zugestellt und alice weitergeleitet wird (L2); und eine Nachricht an ein absichtlich nicht zustellbares Konto (dave-broken, kein nutzbares Home) gibt nach MAX_LIFETIME auf und gibt eine Failure-DSN an den Absender zurück (L3). Weil [stage-local] nach den eingehenden Filtern sitzt (… → anti-spam → local → srs), wird der Absender zuerst auf die weiße Liste gesetzt, um die Pay-to-Send-Schranke zu passieren. Das gesamte Skript überspringt sauber, wenn DAVE nicht gesetzt ist, die Installation keine [stage-local] hat oder die Zielbenutzer fehlen.

11-lmtp-test.sh

Lokale LMTP-Zustellung (pepsi-stage-relay-to-lmtp) gegen ein echtes Dovecot auf dem ADMIN-Host, sodass das Sieve-Skript des Empfängers läuft, während der MDA die Mail ablegt: Eine Nachricht, deren Subject den Sieve-Auslöser trägt, wird per fileinto :create in einen Pepsi-Ordner abgelegt (S1, was beweist, dass Sieve lief, und nicht bloß, dass LMTP zugestellt hat), und eine Nachricht an einen unbekannten Benutzer wird von Dovecot je Empfänger abgelehnt und weiter an NEXT_STAGE geroutet — die Stage baut nie selbst eine DSN (S2). Das Skript installiert und konfiguriert Dovecot selbst, idempotent, und verdrahtet einen Wegwerf-Abschnitt [stage-lmtp] in pepsi.conf; es treibt den worker der Stage direkt an, statt LMTP in die laufende Pipeline zu setzen, sodass es kein anderes Skript stört. Es überspringt sauber, wenn Dovecot nicht installiert werden kann oder das Stage-Binärprogramm fehlt.

12-wallet-topup-test.sh

Die By-E-Mail-Wallet-Self-Service-Hälfte von pepsi-stage-auto-pay (der [stage-wallet]-Abschnitt): Ein lokales Konto füllt sein eigenes benutzerspezifisches GNU-Taler-Wallet auf, indem es pepsi-wallet@<served-domain> einen Subject:-Befehl sendet. Alle drei Methoden werden End-to-End gegen den Demo-Exchange (KUDOS) ausgeübt, mit dem finanzierten lokalen taler-wallet-cli als echtem Gegenüber: withdraw (manuelle Bank-Überweisungsanweisungen, die Überweisung wird anschließend tatsächlich vom [demobank]-Konto aus ausgeführt), pull (eine taler://pay-pull/-URI, die der Harness begleicht) und push (der Harness pusht; der Benutzer akzeptiert die taler://pay-push/-URI). Jeder Fall prüft, dass die Steuernachricht verbraucht wird und das Wallet-Guthaben wächst.

13-auto-pay-test.sh

Die forderungszahlende Hälfte: pepsi-stage-auto-pay begleicht eine zurückkehrende Pay-to-Send-Forderung aus dem eigenen Wallet des Absenders. Die Forderung wird echt erzeugt — ein lokaler Benutzer sendet an einen mit Bezahlschranke versehenen lokalen Empfänger (DAVE), das Ausgehende wird internet-relayt (und stempelt eine echte Pepsi-Origin), läuft in den eingehenden MX dieses Hosts zurück, wo pepsi-stage-anti-spam es zurückhält und dem Absender eine echte Forderung mailt. Diese Forderung wird durch den Auto-Pay-worker wiedergespielt. Die beiden Fälle verwenden separate Absender (also separate benutzerspezifische Wallets/Bestellungen): BOB``s Wallet bleibt leer, sodass Auto-Pay die Forderung weiterleitet und nichts berechnet (A1); ``ALICE``s Wallet wird **im Voraus vorfinanziert** (sodass die langsame Abhebung außerhalb des kurzen 30-Sekunden-Zahlungsfensters der Bezahlschranke liegt), sodass Auto-Pay die Forderung darin begleicht — die Bestellung kippt auf bezahlt, das Wallet wird belastet, und die zurückgehaltene Nachricht wird freigegeben und an ``DAVE zugestellt (A2).

14-multi-recipient-test.sh

Empfänger-Fan-out — der Fehlerfall, in dem eine an mehrere Personen adressierte Nachricht nur einen Teil von ihnen erreicht. Beide Fälle prüfen die positive Zustellung an jeden vorgesehenen Empfänger. T1 sendet einen Umschlag an zwei entfernte Empfänger und verlangt, dass beide ankommen — ein Relay, das rcpt_to.first() nimmt und dann den Datensatz beendet, gibt nichts weiter. T2 deckt das ~/.forward-Fan-out ab: ein Umschlag, der an einen lokalen Benutzer mit Weiterleitung (DAVE) und an einen zweiten einfachen Empfänger adressiert ist, wobei die weitergeleitete Kopie und der Durchleitungsempfänger jeweils genau einmal ankommen müssen — eine Stage, die den Datenbank-Datensatz serverseitig reduziert, ohne die Nachricht im Speicher zu ändern, lässt einen fusionierten Nachfolger mit der veralteten Empfängerliste arbeiten. T2 benötigt das optionale DAVE-Konto und eine installierte ~/.forward-Stage.

15-debian-deploy-test.sh

Das paketierte Artefakt und der Assistent — der echte Installationsweg des Operators, während 01-deploy.sh aus einem Checkout baut und die Konfiguration von Hand schreibt. Es baut das .deb lokal, installiert es auf dem ADMIN-Host (sodass das postinst die Konten, Rollen und dpkg-statoverride-Bits anlegt) und führt dann pepsi-setup --wizard --answers für eine Reihe von Szenarien aus, jeweils gefolgt von pepsi-setup run und echter Probe-Mail durch die Pipeline, die der Assistent erzeugt hat. Es schreibt den installierten Host neu, tut also nichts außer seinen Plan auszugeben, solange PEPSI_DEPLOY_CONFIRM=yes nicht gesetzt ist.

16-whitelist-import-test.sh

Das setuid-pepsi-whitelist und sein Postfach-Importer, gegen die laufende Installation — alles an diesem Programm, was es erst gibt, wenn es installiert ist. Es prüft die Installation selbst (Modus 4755 mit Eigentümer pepsi-whitelist, den Scan-Helper ohne Privilegien-Bits, die auf die Tabelle beschränkte Datenbankrolle, die Hoster-Liste); die Namensraum-Regel, indem es zwei Wegwerf-Konten anlegt und bestätigt, dass eines <login>/… und sonst nichts verwenden darf, dass list die geteilte weiße Liste des Operators nicht offenlegt und dass -c abgelehnt wird; den Import selbst, der genau die Empfänger der Mail ergeben muss, die dieser Benutzer gesendet hat, und empfangene Mail ignorieren muss; die Wildcard-Entscheidung, bei der eine stark frequentierte Domain zu einem einzigen *@domain-Datensatz zusammenfällt, während gmail.com mit ebenso vielen Korrespondenten das nicht tut; die Privilegienabgabe, indem ein Helper untergeschoben wird, der seine eigenen Zugangsdaten meldet, und geprüft wird, dass die uid des Aufrufers in allen drei ID-Slots auftaucht; --user, das den eigenen Namensraum eines anderen Kontos befüllt; und schließlich, dass pepsi-stage-check-whitelist den benutzerspezifischen Namen konsultiert, zu dem sein {localpart}-Platzhalter expandiert. Einzelne Prüfungen überspringen, wenn der Installation die Funktion fehlt.

17-crypto-test.sh

Ende-zu-Ende-Kryptographie: die Teile von pepsi-stage-encrypt, pepsi-stage-decrypt und pepsi-keys, die es erst gibt, wenn die Binärprogramme setuid installiert sind und der Schlüsselspeicher einen Schlüsselverschlüsselungsschlüssel hält. Es prüft die Installation und die Verwahrungsgrenze (4750 pepsi-crypto:pepsi; die Rolle pepsi kann crypto_identity.private_wrapped nicht lesen), die Identitätserzeugung für beide Protokolle und die Interoperabilität statt bloßer Selbstkonsistenz: Pepsis Chiffrat wird von einem unveränderten gpg gelesen, und das Chiffrat von gpg wird von Pepsi gelesen. Es deckt außerdem die fail-open-Urteile für eingehende Mail ab, den gefälschten X-Pepsi-Crypto-Header und dass ENCRYPT = required einen Bounce erzeugt, statt je Klartext zu senden.

18-wkd-test.sh

Das Spiegelbild von 17: wie andere unsere Schlüssel finden. pepsi-httpd bedient das Web Key Directory für jede akzeptierte Domain aus dem Schlüsselspeicher und beachtet dabei das published-Flag jeder Identität — die Richtliniendatei, den direkten und den fortgeschrittenen Pfad, dass sich die zurückgegebenen Bytes in ein unverändertes gpg importieren lassen, dass gpg --locate-external-key sie über echtes DNS und TLS findet und dass eine unveröffentlichte Identität und eine Domain, die wir nicht bedienen, beide 404 ergeben.

19-secure-link-test.sh

Das ferne Ende von [stage-encrypt] ON_NO_KEY = secure-link: Der Klartext wird von der Leitung genommen, dem Empfänger wird ein Link und dem Absender eine PIN gemailt. Es deckt das Routing ab, die PIN, die an den Absender geht, das Offenlegungsverhalten des Portals, die Sperre nach MAX_ATTEMPTS, das erfolgreiche Lesen, die Lesebestätigung und dass die Datenbank allein Chiffrat und keine PIN hält.

20-admin-api-test.sh

Die administrative API über den UNIX-Socket-Listener mit ADMIN = yes, den 01-deploy.sh konfiguriert: dass alle drei Mechanismen authentifizieren (SO_PEERCRED, ein Bearer-Token, eine Passwort-Sitzung), dass ein Token auf seine Geltungsbereiche beschränkt ist und ein fehlender Geltungsbereich ein 403 ergibt, dass jeder nur lesende Endpunkt JSON antwortet und dass GET /api/v1/config kein Geheimnis offenlegt.

21-online-setup-test.sh

Dass die zwei Front-Ends eines Setup-Interviews — das Terminal-pepsi-setup --wizard und das im Browser unter /api/v1/setup — nicht auseinanderdriften: dieselben Fragen, Bezeichner, Arten, dieselbe Reihenfolge, dieselben Standardwerte und Bedingungen; Antworten, die unverändert hin und zurück gehen; dieselben Werte, die von beiden abgelehnt werden; und dieselben Antworten, die zu derselben Konfiguration gerendert werden. Der letzte Fall schreibt die installierte Konfiguration neu (und stellt sie danach wieder her) und ist an PEPSI_DEPLOY_CONFIRM=yes gebunden; die nur lesenden laufen immer.

22-list-api-test.sh

Das Kompatibilitätstor der Mailinglisten und das eine Skript hier, das nicht unseres ist. Es führt die eigene Suite von mailmanclient 3.3.5 (344 Doctest-Anweisungen in using.rst und 36 Testfunktionen) und die mailman_api_tests von Postorius 1.3.13 (30 Dateien, 248 Funktionen) gegen den installierten Server aus, wobei nur die Vorrichtung ausgetauscht ist, die GNU Mailman Core startet – diese zwei Ersatzstücke liegen in contrib/compat/, versioniert neben den Festschreibungen. Davor prüft es die Dinge, deren Scheitern sonst als 250 rote Python-Tracebacks erscheinen würde: dass die API auf dem Loopback-Listener mit LIST_API = yes antwortet und ohne die Zugangsdaten verweigert, dass dieselben Pfade auf dem öffentlichen Listener ein Byte-für-Byte gleiches 404 sind, dass der nur für Tests gedachte Reset-Haken funktioniert, und ein Rauchtest des Ressourcenbaums samt der 400-gegen-404-Asymmetrie auf /config und der Aufteilung dezimal gegen hexadezimal bei member_id zwischen /3.0/ und /3.1/. Eine Suite, die nicht installiert werden kann, wird als erklärtes Übergehen gemeldet und lässt den Lauf dennoch scheitern: ein stillschweigend übergangenes Tor ist die andere Art, auf die dieser Test lügen kann.

24-list-post-test.sh … 27-list-archive-test.sh

Die vier Hälften des Mailinglisten-Subsystems über echte Post: eine Listennachricht, die jedes Mitglied mit seiner eigenen Ein-Klick-Abmelde-URI erreicht; -join, -leave und -confirm, die die Abonnementabläufe antreiben; ein Bounce, der einem Mitglied zugeordnet, bewertet und in eine Warnung und dann eine Entfernung verwandelt wird; und das Archiv, das aufnimmt, threadet, durchsucht und löscht, was gesendet wurde.

Skript 24 trägt außerdem die Sammelnachrichtenphase: Nachrichten wachsen an, eine Ausgabe geht in jedem Format an die Mitglieder, die dieses Format verlangt haben, Band und Nummer sind das, was die Regel sagt, und ein Mitglied, das an diesem Morgen die Sammelnachrichten abgeschaltet hat, erhält dennoch die letzte Ausgabe. Es ist eine Phase dieses Skripts und kein neues, weil die Installationssuite schon lang ist.

28-list-web-test.sh

Die öffentlichen Mailinglistenseiten über HTTP: dass jede Route auf einem Listener ohne LISTS = yes ein Byte-für-Byte gleiches 404 ist; dass eine beworbene Liste im Verzeichnis steht und eine nicht beworbene nicht und weiterhin über ihre URL erreichbar ist; dass das Anmeldeformular genau ein anstehendes Token ausgibt, von sich aus niemanden anmeldet, ein gültiges Token wiederverwendet statt ein zweites auszugeben und für eine bekannte und eine unbekannte Adresse gleich antwortet; dass ein Bestätigungs-GET nichts abschließt und nur das POST es tut; dass eine Nachricht unter ihrer HyperKitty-URL und über die Suche zu finden ist, mit ihrem Text maskiert und nicht gerendert; dass ein privates Archiv ein 404 und kein 403 ist; und dass das Ein-Klick-Ziel nach RFC 8058 das Mitglied mit einem einzigen POST entfernt, selbst wenn unsubscription_policy moderate ist, idempotent ist und bei einem manipulierten Token niemanden entfernt.

Die letzte Phase behandelt die Mitgliederebene und die Verwalteroberfläche: ein Konto registrieren und genau ein Bestätigungstoken zugesandt bekommen (eine zweite Registrierung verwendet es wieder statt ein zweites auszugeben), bestätigen und ein Passwort setzen, von der Kommandozeile zum Verwalter gemacht werden, sich anmelden, und dann der Test, auf den es am meisten ankommt – ein Verwalter ändert eine Einstellung im Browser und die REST-API liest denselben Wert zurück, und das ist es, was belegt, dass die zwei Oberflächen einen Datensatz schreiben. Es prüft außerdem, dass ein Fremder, der die Konsole anfordert, dieselbe Antwort erhält wie für eine nicht vorhandene Liste, und dass eine Einstellungssendung ohne CSRF-Token abgewiesen wird.

Neben den nummerierten Skripten deckt tests/whitelist-suid.sh denselben setuid-Vertrag ohne SSH oder eine Installation ab: Als root auf einem beliebigen Host mit lokalem PostgreSQL ausgeführt, legt es eine Wegwerf-Datenbank an, installiert das Binärprogramm setuid in ein suid-beachtendes Verzeichnis und führt dieselben Namensraum-/Privilegienabgabe-Prüfungen aus. make check führt es (und die anderen *-suid.sh-Skripte) nach bestem Bemühen aus — sie überspringen, statt fehlzuschlagen, wenn sie kein root, keine Datenbank oder kein suid-fähiges Dateisystem bekommen können.

29-list-import-test.sh

Das Migrationswerkzeug. Eine echte config.pck in Protokoll 0 wird importiert, und die Umwandlungen, die keine Kopien sind, werden über die Datenbank geprüft und nicht über den Rückgabewert des Konverters – der Zusammenfall von archive_policy, die DMARC-Vorrangregel, die neunzig Tage Karenzzeit, das abschließende Leerzeichen des subject_prefix, das DisableMime-Bit, das ein Klartext-Sammelnachrichtenmitglied erzeugt, und ein wegen Bounces abgeschaltetes Mitglied, das abgeschaltet ankommt. Der Import endet mit einem Fehlerstatus, weil die Testdaten absichtlich eine Sperre tragen, die niemand übersetzen kann, und ein zweiter Lauf ändert nichts und sagt das.

Die mittlere Phase ist die wertvollste: pepsi-archive export, gefolgt von import in eine zweite Liste, mit Vergleich der Nachrichtenzahl, jeder Message-ID und der Threadzahl. Das prüft die beiden Hälften gegeneinander und nicht gegen unsere Erwartungen, und ein erneuter Import fügt nichts hinzu – die Wiederaufnahme hängt am Inhalt und nicht an einem Zeitstempel.

Die letzte Phase ist die Umstellungssendung: das Histogramm je Domain, ein ratenbegrenzter Lauf und ein zweiter Lauf, der überspringt, was der erste eingeladen hat.

Was es nicht tut, ist ein echtes upstream Mailman 3 aufzusetzen, um über REST zu importieren; das braucht eine Mailman-Installation auf dem Zielrechner. Die Lücke ist im Skript benannt und nicht dem Entdecken überlassen.

30-secretary-test.sh

Confirm-to-Send (pepsi-stage-secretary). Die Installation, die 01-deploy.sh aufbaut, hat keine Secretary in ihrer eingehenden Kette, daher fügt das Skript für seine eigene Laufzeit eine nach check-whitelist ein und stellt die Konfiguration beim Beenden wieder her. Es prüft, dass ein unbekannter Absender zurückgehalten wird und eine Challenge mit Null-Absender erhält (X1); dass eine automatische Antwort auf die Challenge nichts freigibt und niemanden auf die Whitelist setzt (X2); dass eine echte Antwort die zurückgehaltene Nachricht zustellt und den Absender auf die Whitelist setzt (X3), dessen nächste Nachricht dann ohne zweite Challenge durchgeht (X4); dass eine gebouncte Challenge die zurückgehaltene Mail sofort in den Timeout-Pfad schickt (X5); und dass ohne Antwort die zurückgehaltene Mail nach HOLD_TIME gebounct wird, wobei eine verspätete Antwort auf das abgelaufene Cookie nichts bewirkt (X6).

25.5. Ausführen

# One-time: deploy this checkout onto the MX host (over SSH):
tests/01-deploy.sh tests/test-accounts.ini

# Then, repeatedly, from the test runner:
tests/02-pipeline-test.sh tests/test-accounts.ini
tests/03-dsn-test.sh      tests/test-accounts.ini
tests/04-settings-test.sh tests/test-accounts.ini
tests/05-mime-test.sh     tests/test-accounts.ini
tests/06-cli-test.sh      tests/test-accounts.ini
tests/07-maildir-test.sh  tests/test-accounts.ini   # needs the optional DAVE account
tests/11-lmtp-test.sh     tests/test-accounts.ini   # deploys Dovecot on the MX host
tests/12-wallet-topup-test.sh tests/test-accounts.ini   # needs taler-wallet-cli on the MX host
tests/13-auto-pay-test.sh     tests/test-accounts.ini   # needs DAVE + taler-wallet-cli
tests/14-multi-recipient-test.sh tests/test-accounts.ini   # T2 needs DAVE + a ~/.forward stage
tests/16-whitelist-import-test.sh tests/test-accounts.ini   # needs root on the MX host
tests/17-crypto-test.sh   tests/test-accounts.ini   # needs the setuid crypto stages + gpg
tests/18-wkd-test.sh      tests/test-accounts.ini   # needs pepsi-httpd + gpg
tests/19-secure-link-test.sh  tests/test-accounts.ini
tests/20-admin-api-test.sh    tests/test-accounts.ini   # needs the ADMIN = yes listener
tests/22-list-api-test.sh     tests/test-accounts.ini   # needs the LIST_API = yes listener + PyPI
tests/24-list-post-test.sh    tests/test-accounts.ini
tests/25-list-command-test.sh tests/test-accounts.ini
tests/26-list-bounce-test.sh  tests/test-accounts.ini   # needs the DAVE account for the owner notices
tests/27-list-archive-test.sh tests/test-accounts.ini
tests/28-list-web-test.sh     tests/test-accounts.ini   # needs the LISTS = yes listener
tests/29-list-import-test.sh  tests/test-accounts.ini   # the importers and the cutover
tests/30-secretary-test.sh    tests/test-accounts.ini   # splices in a secretary stage

# These two rewrite the deployed host, so they only plan unless confirmed:
PEPSI_DEPLOY_CONFIRM=yes tests/15-debian-deploy-test.sh tests/test-accounts.ini
PEPSI_DEPLOY_CONFIRM=yes tests/21-online-setup-test.sh  tests/test-accounts.ini

make integrationtests ACCOUNTS=tests/test-accounts.ini führt den ganzen Satz aus: Das INTEGRATION_SH des Ziels ist tests/[0-9][0-9]-*.sh abzüglich der tests/[0-9][0-9]-*-bench.sh-Skripte (BENCH_SH, die ihr eigenes Ziel make benchmarks haben — siehe Benchmark-Suite), sodass ein Skript dadurch aufgenommen wird, dass es existiert, und nicht dadurch, dass es irgendwo aufgeführt ist — einschließlich derer, die dieses Kapitel nicht erwähnt. Es hält beim ersten fehlschlagenden Skript an und hängt von interop-thunderbird ab, sodass die S/MIME-Interoperabilitätsschranke zuerst läuft. ACCOUNTS hat den Standardwert tests/test-accounts.ini.

Jedes Skript verwaltet sich selbst: Es führt eine Vorprüfung durch (ssh-Erreichbarkeit mit dem echten Fehler bei Misserfolg, erforderliche entfernte Werkzeuge, eine Datenbankprobe und der Zustand der drei Dienste mit dem Journal-Auszug jedes ausgefallenen), stellt eine kurze PAYMENT_DEADLINE und ein zügiges POLL_INTERVAL für schnellen Durchlauf sicher und setzt die weiße Liste/Warteschlange/Einstellungen zurück, von denen es abhängt. Eine fehlende Annahme lässt die Vorprüfung mit einer umsetzbaren Meldung scheitern; eine nicht ankommende Nachricht löst einen Diagnose-Dump aus (die Live-Warteschlange plus das Dispatcher-Journal). Stellgrößen wie PAYMENT_DEADLINE_SECS, DELIVER_TIMEOUT und WITHDRAW_AMOUNT sind Überschreibungen aus der Umgebung. Jedes Skript endet nur dann mit 0, wenn kein Fall fehlschlug (Übersprünge sind erlaubt).

25.6. Übersprünge und bedingte Fälle

Ein Fall überspringt (statt fehlzuschlagen), wenn eine Vorbedingung fehlt, die er nicht erfüllen kann, stets mit einer einzeiligen Begründung. Die häufigen:

  • Kein nutzbares Taler-Wallet. Der bezahlte Teil von 02 überspringt, wenn das lokale taler-wallet-cli fehlt oder mit dem Demo-Exchange inkompatibel ist. Alle Zahlungen erfolgen zentral aus dem Wallet des Runners (im Produktivbetrieb würde jeder Absender für seine eigene Nachricht bezahlen).

  • Der Installation fehlt eine Funktion. 03/04 überspringen die Fälle, die Pipeline-Funktionen benötigen, die die Installation nicht konfiguriert (ORIGINATE_SUCCESS_DSN, ein [stage-internet]-BOUNCE_STAGE, das [stage-delaytest]-Blackhole-Relay, die [stage-edit-settings]-Stage) — führen Sie 01-deploy.sh erneut aus, um sie zu aktivieren. 06 überspringt den pepsi-tlsrpt-Fall, sofern kein [pepsi-tlsrpt]-Abschnitt konfiguriert ist. 07 überspringt vollständig, wenn das optionale DAVE-Konto nicht gesetzt ist, die Installation keine [stage-local] hat oder die Zielbenutzer dave/dave-broken auf dem ADMIN-Host fehlen. Der T2-Fall von 14 überspringt, wenn DAVE nicht gesetzt ist oder die Installation keine ~/.forward-Stage hat.

  • Hop-Fähigkeit. 05 nimmt seinen Durchleitungs-, Herabstufungs- oder Nicht-darstellbare-Adresse-Zweig je nachdem, ob der Zustell-Hop 8BITMIME / SMTPUTF8 anbietet; die Fälle, die verlangen, dass dem Hop eine Erweiterung fehlt, überspringen, wenn er sie anbietet. Diese Seite deckt unabhängig davon rfc2231_downgrade_lifecycle aus pepsi-test-stages ab, der über die echte Smarthost-Stage an einen Mock-Server weiterleitet, der keine der beiden Erweiterungen anbietet.

  • Wallet / Auto-Pay nicht installiert. 12/13 überspringen vollständig, wenn die Abschnitte [stage-wallet]/[stage-pay], taler-wallet-cli oder das pepsi-wallets-Konto fehlen (führen Sie 01-deploy.sh erneut aus). Der withdraw-Gutschriftteil von 12 überspringt ohne ein [demobank]-Konto (siehe oben); seine pull/push-Fälle überspringen ohne nutzbares lokales Wallet. 13 überspringt, wenn DAVE nicht gesetzt ist, die echte Forderung nicht erzeugt wird (mit Bezahlschranke versehener Hop oder ausgefallener Händler) oder — für A2 — das benutzerspezifische Wallet nicht finanziert werden kann.

25.7. Die Parser fuzzen

Drei Oberflächen parsen Bytes, die ein Angreifer gewählt hat — der MIME-Durchlauf (pepsi_crypto::classify, der bei jeder eingehenden Nachricht vor jeder Authentifizierung läuft), unser eigener handgeschriebener S/MIME-CMS-Öffner und der OpenPGP-Leser einschließlich seines Pfads für komprimierte Pakete —, dazu der Parser für die eingehende Authentifizierung und die Parser des Mailinglisten-Subsystems für Bounces, Beiträge, mboxes und importierte Mailman-Konfigurationen. Keiner von ihnen darf panicken, abbrechen oder davonlaufen, und diese Zusage wird auf zwei Wegen gehalten.

make fuzz führt jeweils ein libFuzzer-Ziel aus:

make fuzz                                  # FUZZ_TARGET=parse, 300 s
make fuzz FUZZ_TARGET=cms FUZZ_TIME=3600
make fuzz FUZZ_TARGET=pgp FUZZ_TIME=0      # until interrupted

Die acht Ziele unter fuzz/fuzz_targets/ sind parse (der Parser für die eingehende Authentifizierung), classify (der MIME-Durchlauf), cms (der CMS-Öffner), pgp (der OpenPGP-Leser), bounceparse (die RFC-3464-Extraktion und die heuristischen Bounce-Erkenner), mimebuild (der Parse-Modify-Rebuild-Pfad der Posting-Stage der Mailingliste, der neben der Abwesenheit von Panics auch den Hin-und-Rückweg prüft), pickle21 (der Leser für Mailman-2.1-config.pck, der prüft, dass nie etwas konstruiert wird) und mboxsplit (der mbox-Zerleger, den der Archiv-Importer verwendet). FUZZ_TIME begrenzt einen Lauf in Sekunden (0 heißt: laufen, bis unterbrochen wird), und FUZZ_FLAGS reicht alles Weitere an libFuzzer durch. Die Saat-Korpora liegen in fuzz/corpus/<target>/, das nicht versioniert ist: PEPSI_CRYPTO_WRITE_FUZZ_CORPUS=1 cargo test -p pepsi-crypto --test sweep schreibt die Saaten, führen Sie es also in einem frischen Checkout einmal vor dem Fuzzing aus; Funde landen in fuzz/artifacts/<target>/.

Für eine Kampagne auf vielen Kernen startet fuzz/campaign.sh mehrere Ziele gleichzeitig unter AddressSanitizer, jedes im Fork-Modus von libFuzzer mit seinem eigenen Anteil an den Kernen, und hält pro Ziel ein Korpusverzeichnis, auf dem aufeinanderfolgende Läufe aufbauen:

cd fuzz && for t in parse classify cms pgp; do
    cargo +nightly fuzz build -O --debug-assertions $t; done
fuzz/campaign.sh p1 3600 "parse:24 classify:80 cms:90 pgp:54"

Die erste solche Kampagne — acht Stunden auf 256 Kernen, etwa vier Milliarden Ausführungen — fand keinen Absturz, kein Speicherleck und kein Hängen.

fuzz/ ist ein eigener Cargo-Workspace, vom Wurzel-Workspace ausgeschlossen, weil libfuzzer-sys die Nightly-Toolchain und eine Sanitizer-Laufzeit braucht — die stabilen Qualitätsschranken bauen ihn also nie, und make fuzz ist ein bewusstes, teures Opt-in-Ziel. Ohne cargo-fuzz oder eine Nightly-Toolchain überspringt es mit dem Installationshinweis und endet mit 0, sodass es harmlos ist, es auf einer Maschine mit nur Stable auszuführen.

Die immer aktive Hälfte ist stabil und braucht kein Nightly: pepsi-crypto/tests/sweep.rs ist ein deterministischer Mutationsdurchlauf über dieselben drei Krypto-Oberflächen, Eingabe für Eingabe, und parse_verify_never_panics in pepsi-common deckt die vierte ab. Beide laufen unter einfachem cargo test — also unter make check —, sodass eine Regression, die eine Fuzzing-Sitzung fände, meist schon von der gewöhnlichen Schranke gefangen wird.

25.8. Beschränkungen

Tests dürfen niemals echte Mail an Drittanbieter-MX-Hosts senden: Die Suite verwendet nur die eigenen Domains der Operatoren und reservierte/.invalid-Namen (z. B. leitet der DELAY-Test über einen Empfänger in blackhole.invalid an eine nicht routbare 192.0.2.0/24-TEST-NET-1-Adresse weiter). Die Entwickler-uid kann die privilegierten Ports 25/53 nicht binden, sodass die privilegierten Listener nur auf dem installierten Host ausgeübt werden.