Files
vaultik/internal/vaultik/uncompressed_size_test.go
T
sneak 1ead24d193
check / check (push) Waiting to run
Refuse a backup destination an unplugged volume left empty (closes #239)
Every backup writes vaultik-destination.txt at the destination root.
While the local index holds snapshots, a destination without it is
not present: snapshot create refuses before writing, prune and
snapshot purge fail, and snapshot list and snapshot remove act as for
a destination that cannot be listed. snapshot create checks before it
drops incomplete snapshots, snapshot remove before it looks on the
destination. remote nuke deletes the marker last. The file:// backend
never creates its destination directory; that protects a directory on
a removable volume, not a mount point.

Tests back up into directories that exist; fixtures for a written
destination carry the marker.

Judgement call: the marker is rewritten on every backup.
Deviation: for the complexity limit, two blocks moved, unchanged, into
snapshotNamesToCreate and confirmRemoveSnapshot.

Model: opus-5-5
2026-10-14 02:37:52 +00:00

86 lines
2.7 KiB
Go

package vaultik_test
import (
"bytes"
"context"
"path/filepath"
"testing"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"sneak.berlin/go/vaultik/internal/database"
"sneak.berlin/go/vaultik/internal/log"
"sneak.berlin/go/vaultik/internal/storage"
"sneak.berlin/go/vaultik/internal/vaultik"
)
// TestUncompressedSizeOfChangedFileStaysFixed covers
// https://git.eeqj.de/sneak/vaultik/issues/360. A backup that changes a
// file leaves the earlier snapshot's UNCOMPRESSED SIZE as it was when that
// snapshot's backup completed. The file's row in the local index is shared
// by both snapshots and rewritten by the second backup.
func TestUncompressedSizeOfChangedFileStaysFixed(t *testing.T) {
log.Initialize(log.Config{})
t.Parallel()
fs := afero.NewOsFs()
tempDir := t.TempDir()
dataDir := filepath.Join(tempDir, "src")
dbPath := filepath.Join(tempDir, "index.sqlite")
changedPath := filepath.Join(dataDir, "changed.bin")
ctx := context.Background()
files := writeFaultSourceTree(t, fs, dataDir)
cfg := changedFileConfig(dataDir, dbPath)
appendRandomBytes(t, fs, files, changedPath, appendedFileSize)
store, err := storage.NewFileStorer(t.TempDir())
require.NoError(t, err)
db, err := database.New(ctx, dbPath)
require.NoError(t, err)
t.Cleanup(func() { _ = db.Close() })
repos := database.NewRepositories(db)
v := newBackupVaultik(ctx, cfg, store, repos, db, fs)
require.NoError(t, backUp(v, "first"))
// With nothing to compare against, sizeOfNewChunks totals every
// distinct chunk of the files.
firstID := localSnapshotID(ctx, t, repos, "first")
firstSize := sizeOfNewChunks(t, files, nil)
require.Equal(t, firstSize, listedUncompressedSizes(t, v)[firstID],
"the first snapshot must count the chunks of every file it holds")
appendRandomBytes(t, fs, files, changedPath, faultChunkSize)
require.NoError(t, backUp(v, "second"))
secondID := localSnapshotID(ctx, t, repos, "second")
sizes := listedUncompressedSizes(t, v)
assert.Equal(t, firstSize, sizes[firstID],
"a later backup must not change the first snapshot's figure")
assert.Equal(t, sizeOfNewChunks(t, files, nil), sizes[secondID],
"the second snapshot must count the changed file's chunks")
}
// listedUncompressedSizes runs `snapshot list --json` and returns each
// listed snapshot's uncompressed_size by snapshot ID.
func listedUncompressedSizes(t *testing.T, v *vaultik.Vaultik) map[string]int64 {
t.Helper()
var out bytes.Buffer
v.Stdout = &out
require.NoError(t, v.ListSnapshots(true))
sizes := make(map[string]int64)
for _, row := range decodeListJSON(t, out.String()) {
sizes[row.ID] = row.UncompressedSize
}
return sizes
}