Keep the progress line of a multi-path snapshot within 100% (closes #271)
check / check (push) Waiting to run
check / check (push) Waiting to run
One progress reporter spans every path of a snapshot. Its counts of files and bytes processed run across all the paths, while the totals they were divided by were reset to each path's own, so a second path smaller than the first showed more than 100% and an ETA of `unknown`. The totals now add up over the paths scanned so far. The rate behind the ETA is measured from the bytes processed since the current path's scan phase ended, so a later path's scan phase does not lower it. SetTotalSize is renamed AddTotalSize because it now adds. The issue says the ETA went negative; it was computed negative and printed as `unknown`. Model: opus-5-5
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
package snapshot_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
"sneak.berlin/go/vaultik/internal/database"
|
||||
"sneak.berlin/go/vaultik/internal/snapshot"
|
||||
)
|
||||
|
||||
// TestProgressPercentWithSecondPathSmaller backs up two paths with one
|
||||
// scanner, as a snapshot with two paths does, the second path smaller
|
||||
// than the first. The progress line divides the bytes processed by the
|
||||
// total size, so both must count both paths to stay within 100%.
|
||||
func TestProgressPercentWithSecondPathSmaller(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fs := afero.NewMemMapFs()
|
||||
files := map[string]string{
|
||||
"/large/one.txt": strings.Repeat("1", 4000),
|
||||
"/large/two.txt": strings.Repeat("2", 4000),
|
||||
"/small/three.txt": strings.Repeat("3", 1000),
|
||||
}
|
||||
|
||||
for path, content := range files {
|
||||
err := fs.MkdirAll(filepath.Dir(path), 0755)
|
||||
if err != nil {
|
||||
t.Fatalf("mkdir: %v", err)
|
||||
}
|
||||
|
||||
err = afero.WriteFile(fs, path, []byte(content), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("write %s: %v", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
db, err := database.NewTestDB()
|
||||
if err != nil {
|
||||
t.Fatalf("create test db: %v", err)
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
cerr := db.Close()
|
||||
if cerr != nil {
|
||||
t.Errorf("close db: %v", cerr)
|
||||
}
|
||||
})
|
||||
|
||||
repos := database.NewRepositories(db)
|
||||
|
||||
scanner := snapshot.NewScanner(snapshot.ScannerConfig{
|
||||
FS: fs,
|
||||
ChunkSize: int64(1024 * 16),
|
||||
Repositories: repos,
|
||||
MaxBlobSize: int64(1024 * 1024),
|
||||
CompressionLevel: 3,
|
||||
AgeRecipients: []string{testAgePublicKey},
|
||||
EnableProgress: true,
|
||||
})
|
||||
|
||||
// Never started, but Stop releases its tickers and signal handler.
|
||||
progress := scanner.GetProgress()
|
||||
defer progress.Stop()
|
||||
|
||||
ctx := context.Background()
|
||||
snapshotID := "test-snapshot-progress"
|
||||
createTestSnapshotRecord(ctx, t, repos, snapshotID)
|
||||
|
||||
for _, path := range []string{"/large", "/small"} {
|
||||
_, err := scanner.Scan(ctx, path, snapshotID)
|
||||
if err != nil {
|
||||
t.Fatalf("scanning %s: %v", path, err)
|
||||
}
|
||||
}
|
||||
|
||||
stats := progress.GetStats()
|
||||
|
||||
// Directories count toward the total size but produce no chunks, so
|
||||
// the percentage ends just under 100%.
|
||||
percent := float64(stats.BytesProcessed.Load()) /
|
||||
float64(stats.TotalSize.Load()) * 100
|
||||
if percent > 100 {
|
||||
t.Errorf("progress after both paths is %.1f%%, want at most 100%%",
|
||||
percent)
|
||||
}
|
||||
|
||||
if stats.FilesProcessed.Load() != stats.TotalFiles.Load() {
|
||||
t.Errorf("progress after both paths is %d of %d files, want all",
|
||||
stats.FilesProcessed.Load(), stats.TotalFiles.Load())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user