internal/agent/imagecache/sparse_test.go
Ref: Size: 3.3 KiB History
package imagecache
import (
"bytes"
"os"
"path/filepath"
"syscall"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// allocatedBytes reports how much disk a file actually occupies, as opposed to
// the size it claims. st_blocks is in 512-byte units by POSIX definition,
// regardless of the filesystem's own block size.
func allocatedBytes(t *testing.T, path string) int64 {
t.Helper()
fi, err := os.Stat(path)
require.NoError(t, err)
st, ok := fi.Sys().(*syscall.Stat_t)
require.True(t, ok, "no syscall.Stat_t for %s", path)
return st.Blocks * 512
}
// requireSparseFS skips the calling test when the temp filesystem cannot hold
// holes at all. Without this probe a sparseness assertion is really an
// assertion about the machine it runs on.
func requireSparseFS(t *testing.T, dir string) {
t.Helper()
p := filepath.Join(dir, "sparse-probe")
f, err := os.Create(p)
require.NoError(t, err)
require.NoError(t, f.Truncate(8<<20))
require.NoError(t, f.Close())
alloc := allocatedBytes(t, p)
require.NoError(t, os.Remove(p))
if alloc > 1<<20 {
t.Skipf("filesystem under %s does not support sparse files (8 MiB hole allocated %d bytes)", dir, alloc)
}
}
// TestWriteSparse pins both halves of the contract at once: the file that comes
// out is byte-identical to the stream that went in AND its apparent size is
// exact, while the runs of zeros cost no disk.
func TestWriteSparse(t *testing.T) {
data := bytes.Repeat([]byte{0xAB}, blockSize)
tests := []struct {
name string
body []byte
sparse bool // must occupy far less disk than it claims
}{
{
name: "solid data only",
body: bytes.Repeat([]byte{0x5A}, 3*blockSize),
},
{
name: "hole between two data blocks",
body: concat(data, make([]byte, 8<<20), data),
sparse: true,
},
{
name: "leading hole",
body: concat(make([]byte, 8<<20), data),
sparse: true,
},
{
name: "trailing hole: apparent size must survive it",
body: concat(data, make([]byte, 8<<20)),
sparse: true,
},
{
name: "zero run spanning several read buffers",
body: concat(data, make([]byte, 3*bufSize), data),
sparse: true,
},
{
name: "sub-block tail",
body: concat(data, []byte("tail")),
},
{
name: "unaligned data after a hole",
body: concat(make([]byte, 8<<20), []byte("x")),
},
{
name: "empty stream",
body: nil,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
dir := t.TempDir()
if tc.sparse {
requireSparseFS(t, dir)
}
p := filepath.Join(dir, "out.raw")
f, err := os.Create(p)
require.NoError(t, err)
require.NoError(t, writeSparse(f, bytes.NewReader(tc.body)))
require.NoError(t, f.Close())
got, err := os.ReadFile(p)
require.NoError(t, err)
assert.True(t, bytes.Equal(tc.body, got), "decompressed bytes must be identical")
fi, err := os.Stat(p)
require.NoError(t, err)
assert.Equal(t, int64(len(tc.body)), fi.Size(), "apparent size must be exact")
if tc.sparse {
alloc := allocatedBytes(t, p)
assert.Less(t, alloc, fi.Size()/2,
"zero runs must be holes: %d bytes allocated for an apparent %d", alloc, fi.Size())
}
})
}
}
func concat(parts ...[]byte) []byte {
var out []byte
for _, p := range parts {
out = append(out, p...)
}
return out
}