Add manifest corruption detection test
Generates a ~1MB manifest (20000 files with random names), then: - Verifies truncated manifest causes check to fail - Runs 500 iterations of random single-byte corruption - Each iteration verifies check detects the corruption
This commit is contained in:
parent
c218fe56e9
commit
a20c3e5104
@ -3,6 +3,7 @@ package cli
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
@ -483,3 +484,74 @@ func TestGenerateAtomicWriteCleansUpOnError(t *testing.T) {
|
|||||||
tmpExists, _ := afero.Exists(baseFs, "/output.mf.tmp")
|
tmpExists, _ := afero.Exists(baseFs, "/output.mf.tmp")
|
||||||
assert.False(t, tmpExists, "temp file should be cleaned up after failed generation")
|
assert.False(t, tmpExists, "temp file should be cleaned up after failed generation")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCheckDetectsManifestCorruption(t *testing.T) {
|
||||||
|
fs := afero.NewMemMapFs()
|
||||||
|
rng := rand.New(rand.NewSource(42))
|
||||||
|
|
||||||
|
// Create many small files with random names to generate a ~1MB manifest
|
||||||
|
// Each manifest entry is roughly 50-60 bytes, so we need ~20000 files
|
||||||
|
require.NoError(t, fs.MkdirAll("/testdir", 0755))
|
||||||
|
|
||||||
|
numFiles := 20000
|
||||||
|
for i := 0; i < numFiles; i++ {
|
||||||
|
// Generate random filename
|
||||||
|
filename := fmt.Sprintf("/testdir/%08x%08x%08x.dat", rng.Uint32(), rng.Uint32(), rng.Uint32())
|
||||||
|
// Small random content
|
||||||
|
content := make([]byte, 16+rng.Intn(48))
|
||||||
|
rng.Read(content)
|
||||||
|
require.NoError(t, afero.WriteFile(fs, filename, content, 0644))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate manifest outside of testdir
|
||||||
|
opts := testOpts([]string{"mfer", "generate", "-q", "-o", "/manifest.mf", "/testdir"}, fs)
|
||||||
|
exitCode := RunWithOptions(opts)
|
||||||
|
require.Equal(t, 0, exitCode, "generate should succeed")
|
||||||
|
|
||||||
|
// Read the valid manifest and verify it's approximately 1MB
|
||||||
|
validManifest, err := afero.ReadFile(fs, "/manifest.mf")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, len(validManifest) >= 1024*1024, "manifest should be at least 1MB, got %d bytes", len(validManifest))
|
||||||
|
t.Logf("manifest size: %d bytes (%d files)", len(validManifest), numFiles)
|
||||||
|
|
||||||
|
// First corruption: truncate the manifest
|
||||||
|
require.NoError(t, afero.WriteFile(fs, "/manifest.mf", validManifest[:len(validManifest)/2], 0644))
|
||||||
|
|
||||||
|
// Check should fail with truncated manifest
|
||||||
|
opts = testOpts([]string{"mfer", "check", "-q", "--base", "/testdir", "/manifest.mf"}, fs)
|
||||||
|
exitCode = RunWithOptions(opts)
|
||||||
|
assert.Equal(t, 1, exitCode, "check should fail with truncated manifest")
|
||||||
|
|
||||||
|
// Verify check passes with valid manifest
|
||||||
|
require.NoError(t, afero.WriteFile(fs, "/manifest.mf", validManifest, 0644))
|
||||||
|
opts = testOpts([]string{"mfer", "check", "-q", "--base", "/testdir", "/manifest.mf"}, fs)
|
||||||
|
exitCode = RunWithOptions(opts)
|
||||||
|
require.Equal(t, 0, exitCode, "check should pass with valid manifest")
|
||||||
|
|
||||||
|
// Now do 500 random corruption iterations
|
||||||
|
for i := 0; i < 500; i++ {
|
||||||
|
// Corrupt: write a random byte at a random offset
|
||||||
|
corrupted := make([]byte, len(validManifest))
|
||||||
|
copy(corrupted, validManifest)
|
||||||
|
|
||||||
|
offset := rng.Intn(len(corrupted))
|
||||||
|
originalByte := corrupted[offset]
|
||||||
|
// Make sure we actually change the byte
|
||||||
|
newByte := byte(rng.Intn(256))
|
||||||
|
for newByte == originalByte {
|
||||||
|
newByte = byte(rng.Intn(256))
|
||||||
|
}
|
||||||
|
corrupted[offset] = newByte
|
||||||
|
|
||||||
|
require.NoError(t, afero.WriteFile(fs, "/manifest.mf", corrupted, 0644))
|
||||||
|
|
||||||
|
// Check should fail with corrupted manifest
|
||||||
|
opts = testOpts([]string{"mfer", "check", "-q", "--base", "/testdir", "/manifest.mf"}, fs)
|
||||||
|
exitCode = RunWithOptions(opts)
|
||||||
|
assert.Equal(t, 1, exitCode, "iteration %d: check should fail with corrupted manifest (offset %d, 0x%02x -> 0x%02x)",
|
||||||
|
i, offset, originalByte, newByte)
|
||||||
|
|
||||||
|
// Restore valid manifest for next iteration
|
||||||
|
require.NoError(t, afero.WriteFile(fs, "/manifest.mf", validManifest, 0644))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user