scan: show each phase as N of M, with progress, elapsed time and ETA, also while it waits (closes #112)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
Every scan display now starts with its phase and its place among the five, as "[2/5] walk", and shows its progress, rate, elapsed time and ETA. On a terminal hash, update and the content reads keep the library's bar, which fills what the rest of the line leaves; load, the walk and the content checks are a spinner whose text is cut to the terminal's width. load counts the records first. The content phase adds up the sizes of the matching groups, without the join and sort, and says it is searching until the first record returns. The walk's total is estimated from the records under the operands. Each display is drawn from its own goroutine: several times a second on a terminal, every 5 seconds in plain mode. Judgement call: load and the content checks stay spinners once their totals are known. Model: opus-5-5
This commit is contained in:
+69
-7
@@ -309,8 +309,10 @@ func TestApplyChangesRoundTrip(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
err := applyChanges(t.Context(), db, recs, nil,
|
||||
newProgress("update", 2))
|
||||
prog := newProgress(progressParams{label: "update", total: 4})
|
||||
defer prog.finish()
|
||||
|
||||
err := applyChanges(t.Context(), db, recs, nil, prog)
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges: %v", err)
|
||||
}
|
||||
@@ -329,7 +331,7 @@ func TestApplyChangesRoundTrip(t *testing.T) {
|
||||
}
|
||||
|
||||
err = applyChanges(t.Context(), db, []scanRec{upd},
|
||||
[]string{"/a/tab\tnew\nline"}, newProgress("update", 2))
|
||||
[]string{"/a/tab\tnew\nline"}, prog)
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges: %v", err)
|
||||
}
|
||||
@@ -357,8 +359,10 @@ func TestApplyChangesBatching(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
err := applyChanges(t.Context(), db, recs, nil,
|
||||
newProgress("update", int64(n)))
|
||||
prog := newProgress(progressParams{label: "update", total: int64(2 * n)})
|
||||
defer prog.finish()
|
||||
|
||||
err := applyChanges(t.Context(), db, recs, nil, prog)
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges: %v", err)
|
||||
}
|
||||
@@ -372,8 +376,7 @@ func TestApplyChangesBatching(t *testing.T) {
|
||||
deletes = append(deletes, r.path)
|
||||
}
|
||||
|
||||
err = applyChanges(t.Context(), db, nil, deletes,
|
||||
newProgress("update", int64(n)))
|
||||
err = applyChanges(t.Context(), db, nil, deletes, prog)
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges deletes: %v", err)
|
||||
}
|
||||
@@ -382,3 +385,62 @@ func TestApplyChangesBatching(t *testing.T) {
|
||||
t.Fatalf("records = %d, want 0", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
// TestCountContentCandidates checks that countContentCandidates counts
|
||||
// the records the content phase's search returns: those of each group
|
||||
// of two or more records of headTailMin or more with a head, where at
|
||||
// least one has no content hash.
|
||||
func TestCountContentCandidates(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db := openTestDB(t)
|
||||
|
||||
// Each group's records: size, head, and content, one per record.
|
||||
groups := []struct {
|
||||
size int64
|
||||
head string
|
||||
contents []string
|
||||
}{
|
||||
{headTailMin, "h", []string{"", "c", "c"}}, // returned
|
||||
{headTailMin + 1, "h", []string{"", ""}}, // returned
|
||||
{headTailMin + 2, "h", []string{"c", "c"}}, // all hashed
|
||||
{headTailMin + 3, "h", []string{""}}, // one record
|
||||
{headTailMin - 1, "h", []string{"", ""}}, // too small
|
||||
{headTailMin + 4, "", []string{"", ""}}, // no head
|
||||
}
|
||||
|
||||
var recs []scanRec
|
||||
|
||||
for _, g := range groups {
|
||||
for _, content := range g.contents {
|
||||
recs = append(recs, scanRec{
|
||||
size: g.size, mtime: time.Unix(1, 0), head: g.head,
|
||||
tail: "t", content: content,
|
||||
path: fmt.Sprintf("/r/%d", len(recs)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
err := applyChanges(t.Context(), db, recs, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges: %v", err)
|
||||
}
|
||||
|
||||
got, err := countContentCandidates(t.Context(), db)
|
||||
if err != nil {
|
||||
t.Fatalf("countContentCandidates: %v", err)
|
||||
}
|
||||
|
||||
var returned int64
|
||||
|
||||
err = loadContentCandidates(t.Context(), db,
|
||||
func(scanRec, bool) { returned++ })
|
||||
if err != nil {
|
||||
t.Fatalf("loadContentCandidates: %v", err)
|
||||
}
|
||||
|
||||
if got != 5 || returned != 5 {
|
||||
t.Errorf("count = %d, records returned = %d, want 5 and 5",
|
||||
got, returned)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user