a73x

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
}