Files
vaultik/internal/vaultik/restore_plan_test.go
T
clawbot 66ea7f5f45
check / check (push) Waiting to run
Free a cached blob once no file still to restore needs it (closes #324)
A restore of selected paths, or one where files failed under
--skip-errors, kept the blobs those files share in the temporary cache
until it ended: the sweeper freed a blob only once every file in the
snapshot database that uses it had been restored, and files outside the
selection or that failed never are.

The sweeper now asks the restore plan, through pendingFileUsesBlob,
whether any pending file still uses a cached blob. The plan holds only
this run's files and drops each one once it is restored or has failed.

The new test backs up 16 KiB files so it stays quick; the locality
backup helper takes the file size for that.

Model: opus-5-5
2026-10-14 01:47:48 +02:00

89 lines
2.5 KiB
Go

package vaultik //nolint:testpackage // inspects unexported restore plan internals
import (
"context"
"math"
"testing"
"github.com/stretchr/testify/require"
"sneak.berlin/go/vaultik/internal/database"
"sneak.berlin/go/vaultik/internal/types"
)
// TestPickNextDownloadReturnsNilUUIDFile proves a genuine pending file
// carrying the nil UUID is picked for download rather than mistaken for
// "nothing left" — the bug that could abandon every remaining file.
func TestPickNextDownloadReturnsNilUUIDFile(t *testing.T) {
t.Parallel()
var nilID types.FileID // zero value is the nil UUID
plan := &restorePlan{
fileBlobs: map[types.FileID]map[string]struct{}{
nilID: {"blobhash": {}},
},
}
id, ok := plan.pickNextDownload()
require.True(t, ok,
"pickNextDownload treated a pending nil-UUID file as nothing to do")
require.True(t, id.IsZero(), "expected the nil-UUID file to be picked")
}
// TestPickNextDownloadEmptyPlan confirms the second return value is false
// only when no file needs a download.
func TestPickNextDownloadEmptyPlan(t *testing.T) {
t.Parallel()
plan := &restorePlan{
fileBlobs: map[types.FileID]map[string]struct{}{},
}
_, ok := plan.pickNextDownload()
require.False(t, ok, "pickNextDownload reported work on an empty plan")
}
// TestRunRestoreLoopFailsOnAbandonedFiles proves the loop returns an
// error rather than silent success when files remain pending after it
// can make no further progress.
func TestRunRestoreLoopFailsOnAbandonedFiles(t *testing.T) {
t.Parallel()
ctx := context.Background()
db, err := database.NewTestDB()
require.NoError(t, err)
t.Cleanup(func() { _ = db.Close() })
repos := database.NewRepositories(db)
cache, err := newBlobDiskCache(math.MaxInt64)
require.NoError(t, err)
t.Cleanup(func() { _ = cache.Close() })
// A file that is still pending but whose uncached-blob set is empty
// and which was never queued as ready: the loop can neither restore
// nor download it. This is the abandonment the guard must catch.
var stuck types.FileID
plan := &restorePlan{
fileBlobs: map[types.FileID]map[string]struct{}{stuck: {}},
blobFiles: map[string]map[types.FileID]struct{}{},
cached: map[string]struct{}{},
}
v := &Vaultik{ctx: ctx}
session := &restoreSession{
v: v,
ctx: ctx,
repos: repos,
sweeper: newRestoreSweeper(plan, cache, 1),
result: &RestoreResult{},
}
err = v.runRestoreLoop(session, plan, map[types.FileID]*database.File{}, 0)
require.ErrorIs(t, err, errRestoreIncomplete)
}