Run the disk-full restore test again #210

Merged
clawbot merged 1 commits from fix/207-disk-full-restore-test into next 2026-10-01 20:24:30 +02:00
+18 -21
View File
@@ -680,18 +680,10 @@ func faultScannerFactory(
// Scenario 5: the restore target runs out of space mid-file. Restore // Scenario 5: the restore target runs out of space mid-file. Restore
// must fail with an out-of-space error, and must not leave a truncated // must fail with an out-of-space error, and must not leave a truncated
// file at the target path presenting as a complete restore. Restore // file at the target path presenting as a complete restore.
// today writes each file straight to its final path and does not remove
// it when a write fails, so the truncated file survives; deleting it is
// tracked by https://git.eeqj.de/sneak/vaultik/issues/163. Skipped until
// that lands, so the destination assertion below is recorded rather than
// dropped.
// //
//nolint:paralleltest // installs the global logger via log.Initialize //nolint:paralleltest // installs the global logger via log.Initialize
func TestRestoreReportsDiskFull(t *testing.T) { func TestRestoreReportsDiskFull(t *testing.T) {
t.Skip("blocked on https://git.eeqj.de/sneak/vaultik/issues/163: " +
"a disk-full write leaves a truncated file at the target path " +
"instead of removing it")
log.Initialize(log.Config{}) log.Initialize(log.Config{})
osFS := afero.NewOsFs() osFS := afero.NewOsFs()
@@ -717,10 +709,10 @@ func TestRestoreReportsDiskFull(t *testing.T) {
id := fullFaultBackup(ctx, t, osFS, inner, cfg, repos, dataDir, dbPath, "diskfull") id := fullFaultBackup(ctx, t, osFS, inner, cfg, repos, dataDir, dbPath, "diskfull")
require.NoError(t, db.Close()) require.NoError(t, db.Close())
// Restore onto a filesystem that allows only a few bytes of file // Restore onto a target that allows only a few bytes of file content:
// content: enough to create files, far too little to hold them. // enough to create files, far too little to hold them.
budget := int64(8) budget := int64(8)
quota := &quotaFS{Fs: osFS, remaining: &budget} quota := &quotaFS{Fs: osFS, dir: restoreDir, remaining: &budget}
v := newReaderVaultik(ctx, cfg, inner, nil, quota) v := newReaderVaultik(ctx, cfg, inner, nil, quota)
err = v.Restore(&vaultik.RestoreOptions{SnapshotID: id, TargetDir: restoreDir}) err = v.Restore(&vaultik.RestoreOptions{SnapshotID: id, TargetDir: restoreDir})
@@ -754,21 +746,26 @@ func assertRestoredTree(
// budget is exhausted, mirroring a real ENOSPC. // budget is exhausted, mirroring a real ENOSPC.
var errNoSpace = errors.New("no space left on device") var errNoSpace = errors.New("no space left on device")
// quotaFS is an afero.Fs whose files may write only a fixed total number // quotaFS is an afero.Fs on which files opened under dir may write only
// of content bytes before failing, simulating a full restore target. It // a fixed total number of content bytes before failing, simulating a full
// wraps the interface so every method except Create delegates to the // restore target. Every other method, and every file outside dir, goes
// real filesystem; only file writes are capped. // straight to the real filesystem: restore also writes the decrypted
// metadata database under $TMPDIR through this filesystem, and capping
// that would fail the restore before it wrote anything to the target.
type quotaFS struct { type quotaFS struct {
afero.Fs afero.Fs
dir string
remaining *int64 remaining *int64
} }
//nolint:ireturn // afero.Fs.Create's signature requires returning afero.File. //nolint:ireturn // afero.Fs.OpenFile's signature requires returning afero.File.
func (q *quotaFS) Create(name string) (afero.File, error) { func (q *quotaFS) OpenFile(
f, err := q.Fs.Create(name) name string, flag int, perm os.FileMode,
if err != nil { ) (afero.File, error) {
return nil, err f, err := q.Fs.OpenFile(name, flag, perm)
if err != nil || !strings.HasPrefix(name, q.dir) {
return f, err
} }
return &quotaFile{File: f, remaining: q.remaining}, nil return &quotaFile{File: f, remaining: q.remaining}, nil