Fix every finding surfaced by the canonical .golangci.yml with golangci-lint v2.12.2 (refs #61), behavior-preserving throughout: - err113: dynamic errors replaced with package-level sentinels and %w wrapping; direct comparisons converted to errors.Is - goprintffuncname: printf-style helpers renamed with an f suffix (ui.Writer message methods, cli.ReportErrorf, database.Fatalf, vaultik stdoutf) and all call sites updated - revive: stuttering type names renamed (blob.Handler, blob.WithReader, blob.ChunkPosition, storage.URL, storage.Info), doc comments added, unused parameters blanked, package comments added - contextcheck/noctx: ctx threaded through blob.Packer (AddChunk/Flush/FinalizeBlob/PackChunks) and scanner call sites; context-aware exec and sql variants used - funlen/cyclop/gocognit/nestif/dupl: oversized or duplicated functions split into focused helpers across production and test code - paralleltest/tparallel/thelper/usetesting/testpackage: tests parallelized where safe (global log.Initialize kept in the serial phase), helpers marked, t.TempDir adopted, external test packages where only exported API is used - gosec: integer conversions clamped or justified, header timeouts added, remaining findings suppressed with per-site justifications - mnd/goconst/lll/wsl_v5/nlreturn/noinlineerr/errcheck and other mechanical findings fixed directly Remove the deprecated log.LogOptions alias (callers migrated to log.Options). make check is green.
150 lines
3.4 KiB
Go
150 lines
3.4 KiB
Go
package chunker_test
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"testing"
|
|
|
|
"sneak.berlin/go/vaultik/internal/chunker"
|
|
)
|
|
|
|
func TestChunkerSmallFileSingleChunk(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := chunker.NewChunker(1024 * 1024) // 1MB average
|
|
data := bytes.Repeat([]byte("hello"), 100) // 500 bytes
|
|
|
|
chunks, err := c.ChunkReader(bytes.NewReader(data))
|
|
if err != nil {
|
|
t.Fatalf("chunking failed: %v", err)
|
|
}
|
|
|
|
if len(chunks) != 1 {
|
|
t.Errorf("expected 1 chunk, got %d", len(chunks))
|
|
}
|
|
|
|
if chunks[0].Size != int64(len(data)) {
|
|
t.Errorf("expected chunk size %d, got %d", len(data), chunks[0].Size)
|
|
}
|
|
}
|
|
|
|
func TestChunkerLargeFileMultipleChunks(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := chunker.NewChunker(256 * 1024) // 256KB average chunk size
|
|
|
|
// Generate 2MB of random data
|
|
data := make([]byte, 2*1024*1024)
|
|
|
|
_, err := rand.Read(data)
|
|
if err != nil {
|
|
t.Fatalf("failed to generate random data: %v", err)
|
|
}
|
|
|
|
chunks, err := c.ChunkReader(bytes.NewReader(data))
|
|
if err != nil {
|
|
t.Fatalf("chunking failed: %v", err)
|
|
}
|
|
|
|
// Should produce multiple chunks - with FastCDC we expect around 8
|
|
// chunks for 2MB with 256KB average
|
|
if len(chunks) < 4 || len(chunks) > 16 {
|
|
t.Errorf("expected 4-16 chunks, got %d", len(chunks))
|
|
}
|
|
|
|
// Verify chunks reconstruct original data
|
|
var reconstructed []byte
|
|
for _, chunk := range chunks {
|
|
reconstructed = append(reconstructed, chunk.Data...)
|
|
}
|
|
|
|
if !bytes.Equal(data, reconstructed) {
|
|
t.Error("reconstructed data doesn't match original")
|
|
}
|
|
|
|
// Verify offsets
|
|
var expectedOffset int64
|
|
|
|
for i, chunk := range chunks {
|
|
if chunk.Offset != expectedOffset {
|
|
t.Errorf("chunk %d: expected offset %d, got %d",
|
|
i, expectedOffset, chunk.Offset)
|
|
}
|
|
|
|
expectedOffset += chunk.Size
|
|
}
|
|
}
|
|
|
|
func TestChunkerDeterministic(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
chunker1 := chunker.NewChunker(256 * 1024)
|
|
chunker2 := chunker.NewChunker(256 * 1024)
|
|
|
|
// Use deterministic data
|
|
data := bytes.Repeat([]byte("abcdefghijklmnopqrstuvwxyz"), 20000) // ~520KB
|
|
|
|
chunks1, err := chunker1.ChunkReader(bytes.NewReader(data))
|
|
if err != nil {
|
|
t.Fatalf("chunking failed: %v", err)
|
|
}
|
|
|
|
chunks2, err := chunker2.ChunkReader(bytes.NewReader(data))
|
|
if err != nil {
|
|
t.Fatalf("chunking failed: %v", err)
|
|
}
|
|
|
|
// Should produce same chunks
|
|
if len(chunks1) != len(chunks2) {
|
|
t.Fatalf("different number of chunks: %d vs %d",
|
|
len(chunks1), len(chunks2))
|
|
}
|
|
|
|
for i := range chunks1 {
|
|
if chunks1[i].Hash != chunks2[i].Hash {
|
|
t.Errorf("chunk %d: different hashes", i)
|
|
}
|
|
|
|
if chunks1[i].Size != chunks2[i].Size {
|
|
t.Errorf("chunk %d: different sizes", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestChunkBoundaries(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
c := chunker.NewChunker(256 * 1024) // 256KB average
|
|
|
|
// FastCDC uses avg/4 for min and avg*4 for max
|
|
avgSize := int64(256 * 1024)
|
|
minSize := avgSize / 4
|
|
maxSize := avgSize * 4
|
|
|
|
// Test that minimum chunk size is respected
|
|
data := make([]byte, minSize+1024)
|
|
|
|
_, err := rand.Read(data)
|
|
if err != nil {
|
|
t.Fatalf("failed to generate random data: %v", err)
|
|
}
|
|
|
|
chunks, err := c.ChunkReader(bytes.NewReader(data))
|
|
if err != nil {
|
|
t.Fatalf("chunking failed: %v", err)
|
|
}
|
|
|
|
for i, chunk := range chunks {
|
|
// Last chunk can be smaller than minimum
|
|
if i < len(chunks)-1 && chunk.Size < minSize {
|
|
t.Errorf("chunk %d size %d is below minimum %d",
|
|
i, chunk.Size, minSize)
|
|
}
|
|
|
|
if chunk.Size > maxSize {
|
|
t.Errorf("chunk %d size %d exceeds maximum %d",
|
|
i, chunk.Size, maxSize)
|
|
}
|
|
}
|
|
}
|