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 }