Skip files of an unreadable blob under restore --skip-errors (closes #218)
check / check (push) Successful in 12m34s
check / check (push) Successful in 12m34s
A blob that failed to download ended `snapshot restore` even with --skip-errors, after restoring whichever files came first. The download error now goes through the same per-file handling as any other restore error, once for every pending file that references the blob. With --skip-errors those files are reported as failed, the rest are restored, and the command still exits non-zero. Without the flag the restore still aborts; the error now also names one affected file and suggests --skip-errors. A cancelled restore still ends at once. The --skip-errors help text and README now limit the packing and storage caveat to snapshot creation. Model: opus-5-5
This commit was merged in pull request #243.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package vaultik //nolint:testpackage // sets ctx/cancel and inspects scratch files
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"path/filepath"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"sneak.berlin/go/vaultik/internal/log"
|
||||
"sneak.berlin/go/vaultik/internal/storage"
|
||||
@@ -157,3 +159,51 @@ func scratchEntries(t *testing.T, dir string) []string {
|
||||
|
||||
return matches
|
||||
}
|
||||
|
||||
// TestRestoreSkipErrorsCancelDuringBlobDownload cancels a SkipErrors
|
||||
// restore while a blob download is in progress. The download fails only
|
||||
// because of the cancel, so Restore must return context.Canceled without
|
||||
// reporting the file that needs the blob as failed.
|
||||
//
|
||||
//nolint:paralleltest // installs the global logger via log.Initialize
|
||||
func TestRestoreSkipErrorsCancelDuringBlobDownload(t *testing.T) {
|
||||
log.Initialize(log.Config{})
|
||||
|
||||
fs := afero.NewOsFs()
|
||||
tempDir := t.TempDir()
|
||||
|
||||
cfg, storer, snapshotID, srcPath := backupOneFile(context.Background(),
|
||||
t, fs, tempDir, "a.txt", []byte("hello vaultik"), 0o644)
|
||||
|
||||
gate := newBlockingBlobStorer(storer)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
var out bytes.Buffer
|
||||
|
||||
v := newRestoreVaultik(ctx, cfg, gate, fs)
|
||||
v.UI = ui.NewWithColor(&out, false)
|
||||
|
||||
restoreErr := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
restoreErr <- v.Restore(&RestoreOptions{
|
||||
SnapshotID: snapshotID,
|
||||
TargetDir: filepath.Join(tempDir, "restored"),
|
||||
SkipErrors: true,
|
||||
})
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-gate.entered:
|
||||
case <-time.After(30 * time.Second):
|
||||
t.Fatal("restore never reached the blob-download phase")
|
||||
}
|
||||
|
||||
cancel()
|
||||
|
||||
require.ErrorIs(t, <-restoreErr, context.Canceled)
|
||||
assert.NotContains(t, out.String(), srcPath,
|
||||
"the cancel was reported as a failed file")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user