Update brach ohne Meldung ab
Beim Sprung von 1.5.0 auf 1.10.1 endete scripts/update.sh mitten in der Ausgabe. Kein Fehler, keine Zeile, einfach zurueck zur Eingabeaufforderung. Ursache war eine abgeschnittene Leitung: git log --oneline "HEAD..$ZIEL" | head -15 | sed ... head schliesst die Leitung nach 15 Zeilen, git log schreibt weiter und bekommt SIGPIPE (141). Mit "set -o pipefail" gilt die ganze Kette als gescheitert, "set -e" beendet das Skript - stumm. Zeitabhaengig, deshalb monatelang unauffaellig: Solange git log fertig wurde, bevor head zumachte, ging es gut. Die Schwelle liegt exakt bei 15 Commits, und v1.5.0..v1.10.0 waren die ersten 16. Behoben, indem der Erzeuger selbst begrenzt (git log -15) statt die Leitung zu kappen. Dieselbe Behandlung fuer die beiden Tag-Listen. Dieselbe Falle steckte in der Selbstpruefung von sicherung.sh: openssl enc -d ... | head -c 5 | grep -q "PGDMP" Beide Leser schliessen frueh. Bei kleiner Datenbank steht alles im Puffer und es faellt nicht auf; mit wachsender Datenbank kippt es - und dann meldet das Skript eine INTAKTE Sicherung als kaputt und verweigert das Update. Jetzt liest dd eine feste Menge und endet regulaer. Damit so etwas nicht wieder stumm passiert: - Eine ERR-Falle nennt Zeilennummer und Exitcode und erklaert bei 141, dass es SIGPIPE ist und ein Fehler im Skript, nicht beim Benutzer. - Ein Test verbietet "| head" in Skripten mit pipefail und nennt die Zeile. Gegenprobe gemacht: Er schlaegt an. - Bei mehr als 15 Commits steht "... und N weitere" darunter, statt sie wortlos wegzulassen. Nachgestellt: der Ablauf von 1.5.0 auf 1.10.1 laeuft durch.
This commit is contained in:
@@ -85,3 +85,50 @@ describe.skipIf(!mitGit)('Betriebsskripte', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Abschneidende Leitungen in Skripten mit "pipefail".
|
||||
*
|
||||
* Anlass ist ein Abbruch im Betrieb: Beim Sprung von v1.5.0 auf v1.10.1
|
||||
* endete update.sh mitten in der Ausgabe, ohne jede Meldung. Ursache war
|
||||
*
|
||||
* git log --oneline "HEAD..$ZIEL" | head -15 | sed ...
|
||||
*
|
||||
* "head" schliesst die Leitung nach 15 Zeilen, "git log" schreibt weiter
|
||||
* und bekommt SIGPIPE (Exitcode 141). Mit "set -o pipefail" gilt die
|
||||
* ganze Kette als gescheitert, "set -e" beendet das Skript - stumm.
|
||||
*
|
||||
* Es ist zeitabhaengig: Solange der Erzeuger fertig wird, bevor "head"
|
||||
* zumacht, faellt nichts auf. Deshalb lief es monatelang gut und riss
|
||||
* genau dann, als zum ersten Mal mehr als 15 Commits dazwischenlagen.
|
||||
*
|
||||
* Dieselbe Falle steckte in der Selbstpruefung der Sicherung, wo sie
|
||||
* eine intakte Sicherung als kaputt gemeldet haette, sobald die
|
||||
* Datenbank gross genug ist.
|
||||
*
|
||||
* Der Test verbietet das Muster deshalb ganz. Wer eine Ausgabe kuerzen
|
||||
* will, nimmt "git log -15", "sed -n '1,15p'" oder einen Zaehler in awk -
|
||||
* alles drei liest bis zum Ende und schliesst nichts zu frueh.
|
||||
*/
|
||||
describe('Skripte mit pipefail', () => {
|
||||
const skripte = ['scripts/update.sh', 'scripts/sicherung.sh', 'scripts/wiederherstellung.sh']
|
||||
|
||||
it.each(skripte)('%s schneidet keine Leitung mit head ab', (pfad) => {
|
||||
const inhalt = readFileSync(pfad, 'utf8')
|
||||
if (!/set -[a-z]*o pipefail|set -o pipefail/.test(inhalt)) return
|
||||
|
||||
const treffer = inhalt
|
||||
.split('\n')
|
||||
.map((zeile, nummer) => ({ zeile, nummer: nummer + 1 }))
|
||||
// Kommentarzeilen erklaeren das Muster gerade - sie sind erlaubt.
|
||||
.filter(({ zeile }) => !zeile.trim().startsWith('#'))
|
||||
.filter(({ zeile }) => !zeile.includes('printf'))
|
||||
.filter(({ zeile }) => /\|\s*head\b/.test(zeile))
|
||||
|
||||
expect(
|
||||
treffer.map((t) => `Zeile ${t.nummer}: ${t.zeile.trim()}`),
|
||||
'Abschneidende Leitung: der Erzeuger bekommt SIGPIPE, pipefail reicht ' +
|
||||
'es weiter, set -e beendet das Skript stumm.',
|
||||
).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user