syncova-backup/packages/backupengine/chunker_test.go
Jerrit Fritzsche 610719c316
Some checks failed
CI / Backend (Go) (push) Failing after 3m7s
CI / Frontend (React/TypeScript) (push) Successful in 37s
CI / Sicherheitsprüfungen (push) Successful in 44s
Syncova Backups V1
Enterprise-Backup-, Recovery-, Verification-, Security- und
Monitoring-Plattform fuer Proxmox VE, Windows, Linux und Dateisysteme.

Der Leitsatz, der fast jede Entscheidung erklaert: Ein Backup gilt erst als
vertrauenswuerdig, wenn Integritaet geprueft und Wiederherstellbarkeit
nachgewiesen wurde. Deshalb steigt ein Wiederherstellungspunkt erst nach einem
tatsaechlich durchgefuehrten Restore-Test auf "recoverable", und Unbekanntes
geht in keine Bewertung als "gut" ein.

Umfang (Phasen 0-23):

- Repository Engine: inhaltsadressierte Bloecke, atomares Commit-Protokoll,
  Katalogaufbau allein aus den Manifesten — ohne Datenbank
- Backup Engine: inhaltsabhaengiges Chunking, Deduplizierung trotz
  Verschluesselung, zstd, AES-256-GCM, Streaming mit Gegendruck
- Agenten fuer Windows und Linux mit Auftragsabholung (Pull-Modell)
- Proxmox-Provider mit beiden Zugriffswegen auf die Sicherungsarchive
- Scheduler, Recovery Engine mit Pruefpunkt, Verification, Unveraenderlichkeit
- Weboberflaeche, Kennzahlen, Meldungen, Berichte, Security Center,
  Ransomware-Heuristik (meldet, handelt nie)
- Disaster Recovery, Haertung, Leistungsmessung, Chaos Testing
- Eingefrorene Vertraege fuer API, Migrationen, Backup-Format und Repository
- Auslieferungspaket fuer linux/amd64, linux/arm64 und windows/amd64

Nicht enthalten und als solches gekennzeichnet: Kapazitaetsprognose, Backup
Copy, Changed Block Tracking bei Proxmox, erweiterte Attribute und ACLs.

Gebaut, aber nie auf echter Hardware gefahren: der Windows-Dienst, die
systemd-Einheit und der verpflichtende Proxmox-Meilenstein — ob eine
wiederhergestellte VM startet, ist ungeprueft. Einzelheiten in CHANGELOG.md
und docs/release-candidate.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 09:10:54 +02:00

98 lines
3.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package backupengine
import (
"bytes"
"errors"
"io"
"testing"
)
// TestChunkerBufferMatchesSmallSources ist der Regressionstest zum Fund aus Phase 20.
//
// Ohne die bekannte Quellgroesse legt der Chunker seinen Lesepuffer stets in
// Hoechstblockgroesse an: vier Megabyte, auch fuer eine Datei von sechzehn
// Kilobyte. Bei vielen kleinen Dateien war das der beherrschende Aufwand — ein
// Messlauf ueber 4000 Dateien mit zusammen 62,5 MiB forderte 16 GiB Speicher an
// (genau 4000 × 4 MiB) und loeste 1447 Speicherbereinigungen aus.
//
// Nach der Behebung: 411 MiB und 90 Bereinigungen.
func TestChunkerBufferMatchesSmallSources(testInstance *testing.T) {
const smallSourceSize = 16 * 1024
smallSource := bytes.NewReader(make([]byte, smallSourceSize))
chunkerWithSize := NewChunker(smallSource, ChunkerOptions{ExpectedSize: smallSourceSize})
if bufferSize := len(chunkerWithSize.readBuffer); bufferSize != smallSourceSize {
testInstance.Errorf("der Lesepuffer ist %d Byte groß, erwartet wurden %d — bei vielen "+
"kleinen Dateien ist genau das der beherrschende Aufwand",
bufferSize, smallSourceSize)
}
// Ohne Angabe bleibt es bei der Hoechstblockgroesse: Eine unbekannte Quelle
// kann beliebig gross sein, und ein zu kleiner Puffer ergaebe unnoetig
// kleine Bloecke.
chunkerWithoutSize := NewChunker(bytes.NewReader(nil), ChunkerOptions{})
if bufferSize := len(chunkerWithoutSize.readBuffer); bufferSize != MaximumChunkSize {
testInstance.Errorf("ohne bekannte Größe ist der Puffer %d Byte groß, erwartet wurden %d",
bufferSize, MaximumChunkSize)
}
// Eine grosse Quelle bekommt weiterhin den vollen Puffer.
chunkerWithLargeSize := NewChunker(bytes.NewReader(nil),
ChunkerOptions{ExpectedSize: 100 * 1024 * 1024})
if bufferSize := len(chunkerWithLargeSize.readBuffer); bufferSize != MaximumChunkSize {
testInstance.Errorf("eine große Quelle bekam einen Puffer von %d Byte", bufferSize)
}
}
// TestSmallSourceStillChunksCorrectly prueft, dass der kleinere Puffer nichts bricht.
//
// Der Puffer bestimmt nur, wie viel auf einmal gelesen wird — nicht, wie
// zerlegt wird. Eine Quelle, die kleiner ist als ihre erwartete Groesse, muss
// weiterhin vollstaendig und richtig durchlaufen.
func TestSmallSourceStillChunksCorrectly(testInstance *testing.T) {
sourceData := buildDeterministicTestData(48 * 1024)
chunker := NewChunker(bytes.NewReader(sourceData), ChunkerOptions{
ExpectedSize: int64(len(sourceData)),
})
var collectedData []byte
for {
nextChunk, chunkError := chunker.Next()
if errors.Is(chunkError, io.EOF) {
break
}
if chunkError != nil {
testInstance.Fatalf("die Zerlegung schlug fehl: %v", chunkError)
}
collectedData = append(collectedData, nextChunk.Data...)
}
if !bytes.Equal(collectedData, sourceData) {
testInstance.Error("die zerlegten Daten weichen von der Quelle ab")
}
}
// buildDeterministicTestData erzeugt wiederholbare, inkompressible Testdaten.
func buildDeterministicTestData(sizeBytes int) []byte {
testData := make([]byte, sizeBytes)
pseudoRandomState := uint64(0x9E3779B97F4A7C15)
for byteIndex := range testData {
pseudoRandomState ^= pseudoRandomState << 13
pseudoRandomState ^= pseudoRandomState >> 7
pseudoRandomState ^= pseudoRandomState << 17
testData[byteIndex] = byte(pseudoRandomState)
}
return testData
}