Die Pruefsumme wurde beim Ausliefern mitgerechnet und das Ergebnis weggeworfen -- ein gekipptes Bit wurde erkannt und verschwiegen. Jetzt: Logeintrag mit Datei-ID, Speicherschluessel, erwarteter und gelesener Summe, dazu panic(http.ErrAbortHandler), den chis Recoverer ausdruecklich durchreicht. Damit das wirkt, werden die letzten 32 KiB zurueckgehalten. Sonst waere die Datei beim Erkennen laengst vollstaendig beim Empfaenger und der Abbruch kaeme zu spaet. Erst wenn die Summe stimmt, darf der Schluss hinterher; sonst bekommt die Gegenstelle weniger Bytes als angekuendigt und wertet das als Fehler statt als Datei. scripts/pruefsumme-selbsttest.py, 14 Pruefungen: kippt ein Bit auf der Platte (nicht im Programm), in der Mitte der Datei, damit es nicht schon an der Laenge auffaellt; zwei Groessen ueber und unter den 32 KiB; und zuerst die Gegenprobe, dass eine heile Datei durchkommt. **Dabei gefunden:** Der neue Logeintrag blieb aus, obwohl der Abbruch funktionierte. zerolog.Ctx liefert einen *abgeschalteten* Logger, wenn im Kontext keiner steckt -- und Aether legte nie einen hinein. Alle 34 log.Ctx-Aufrufe im Baum schrieben ins Nichts, darunter die Warnung ueber verwaiste Blobs beim Zurueckrollen einer Fassung. Behoben zweifach: zerolog.DefaultContextLogger beim Start (deckt auch Hintergrundarbeit ab) und ein Logger mit request_id im Anfragekontext. Zwei Tests halten es fest. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
101 lines
2.8 KiB
Go
101 lines
2.8 KiB
Go
package files
|
||
|
||
import (
|
||
"bytes"
|
||
"io"
|
||
"testing"
|
||
)
|
||
|
||
// Der tailWriter ist die Stelle, an der ein beschädigter Download noch
|
||
// aufzuhalten ist. Was er falsch macht, merkt man sonst erst an einer Datei,
|
||
// die heil aussieht und keine ist.
|
||
|
||
func TestTailWriterHaeltDenSchlussZurueck(t *testing.T) {
|
||
var raus bytes.Buffer
|
||
tw := &tailWriter{w: &raus, keep: 4}
|
||
|
||
if _, err := tw.Write([]byte("0123456789")); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if got := raus.String(); got != "012345" {
|
||
t.Fatalf("durchgereicht %q, erwartet %q", got, "012345")
|
||
}
|
||
if err := tw.Flush(); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if got := raus.String(); got != "0123456789" {
|
||
t.Fatalf("nach Flush %q", got)
|
||
}
|
||
}
|
||
|
||
// Der Fall, auf den es ankommt: Ohne Flush darf der Empfänger die Datei
|
||
// **nicht** vollständig haben.
|
||
func TestOhneFlushBleibtEsUnvollstaendig(t *testing.T) {
|
||
var raus bytes.Buffer
|
||
tw := &tailWriter{w: &raus, keep: 4}
|
||
io.Copy(tw, bytes.NewReader([]byte("0123456789")))
|
||
if raus.Len() != 6 {
|
||
t.Fatalf("%d Bytes draußen, erwartet 6", raus.Len())
|
||
}
|
||
}
|
||
|
||
// Eine Datei, die kleiner ist als der zurückgehaltene Schluss, darf ohne
|
||
// Flush gar nichts hinauslassen.
|
||
func TestKleineDateiBleibtGanzZurueck(t *testing.T) {
|
||
var raus bytes.Buffer
|
||
tw := &tailWriter{w: &raus, keep: 32}
|
||
tw.Write([]byte("kurz"))
|
||
if raus.Len() != 0 {
|
||
t.Fatalf("%d Bytes zu früh draußen", raus.Len())
|
||
}
|
||
tw.Flush()
|
||
if raus.String() != "kurz" {
|
||
t.Fatalf("nach Flush %q", raus.String())
|
||
}
|
||
}
|
||
|
||
// Viele kleine Schreibvorgänge dürfen sich nicht anders verhalten als einer
|
||
// großer – io.Copy zerlegt in Häppchen.
|
||
func TestHaeppchenweiseGleichesErgebnis(t *testing.T) {
|
||
var raus bytes.Buffer
|
||
tw := &tailWriter{w: &raus, keep: 5}
|
||
for _, c := range []byte("abcdefghijklmnop") {
|
||
if _, err := tw.Write([]byte{c}); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
}
|
||
if raus.String() != "abcdefghijk" {
|
||
t.Fatalf("durchgereicht %q", raus.String())
|
||
}
|
||
tw.Flush()
|
||
if raus.String() != "abcdefghijklmnop" {
|
||
t.Fatalf("nach Flush %q", raus.String())
|
||
}
|
||
}
|
||
|
||
// Der Zwischenspeicher darf nicht mitwachsen: Bei einer 5-GB-Datei läge sonst
|
||
// alles im Arbeitsspeicher.
|
||
func TestZwischenspeicherWaechstNicht(t *testing.T) {
|
||
tw := &tailWriter{w: io.Discard, keep: 1024}
|
||
block := make([]byte, 32*1024)
|
||
for range 100 {
|
||
tw.Write(block)
|
||
if len(tw.buf) > len(block)+tw.keep {
|
||
t.Fatalf("Zwischenspeicher auf %d Bytes gewachsen", len(tw.buf))
|
||
}
|
||
}
|
||
}
|
||
|
||
// Bricht die Gegenstelle weg, muss der Fehler nach oben durchkommen – sonst
|
||
// liefe io.Copy weiter und läse die ganze Datei ins Nichts.
|
||
type kaputterWriter struct{}
|
||
|
||
func (kaputterWriter) Write([]byte) (int, error) { return 0, io.ErrClosedPipe }
|
||
|
||
func TestSchreibfehlerKommtDurch(t *testing.T) {
|
||
tw := &tailWriter{w: kaputterWriter{}, keep: 2}
|
||
if _, err := tw.Write([]byte("abcdef")); err == nil {
|
||
t.Fatal("Schreibfehler wurde verschluckt")
|
||
}
|
||
}
|