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 names its phase and its place among the five, as "[2/5] walk", with its progress, rate, elapsed time and ETA. load counts the records first; the walk estimates its total from the records under the operands; the content phase adds up the sizes of the matching groups and says it is searching until the first record returns. Each display is drawn from its own goroutine: several times a second on a terminal, every 5 seconds in plain mode. On a terminal a phase with an exact total draws its line before the library's bar when there is room, and any other a spinner before the line. A line too wide drops its rate, then its bar; it is cut only when the phase, progress, elapsed time and ETA alone do not fit. Judgement call: load and the content checks draw the bar too, once their totals are known. Judgement call: the bar is 10 columns wide, not the width the line leaves. 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