scan: show each phase as N of M, with progress, elapsed time and ETA, also while it waits (closes #112)
check / check (push) Waiting to run
check / check (push) Waiting to run
Every scan display names its phase and its place among the five, as "[2/5] walk", with its progress, 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 redraws every 100 ms on a terminal and every 5 seconds in plain mode. On a terminal no line reaches the last column: a line too wide drops its rate, then its bar. After the terminal is made narrower than the widest line drawn, the display makes a new library object, whose erase fits. 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:
@@ -1586,6 +1586,69 @@ func TestScanPipeline(t *testing.T) {
|
||||
assertSmokeTreeGroups(t, dir, db)
|
||||
}
|
||||
|
||||
// TestScanProgressNamesEachPhase checks the plain progress lines of a
|
||||
// first scan, and of a rescan of one operand holding two of the
|
||||
// database's records. Each phase's first line names it with its place
|
||||
// among the five: load counts the records before reading them, the
|
||||
// content phase searches the database, and the walk estimates its total
|
||||
// from the records under its operand, or says its ETA is unknown when
|
||||
// there are none.
|
||||
//
|
||||
//nolint:paralleltest // captureStderr replaces the process-wide os.Stderr
|
||||
func TestScanProgressNamesEachPhase(t *testing.T) {
|
||||
stderr := captureStderr(t)
|
||||
dir := buildSmokeTree(t)
|
||||
db := openTestDB(t)
|
||||
|
||||
syncTree(t, db, dir)
|
||||
|
||||
first := stderr()
|
||||
|
||||
syncTree(t, db, filepath.Join(dir, "a"))
|
||||
|
||||
rescan := stderr()[len(first):]
|
||||
load := "[1/5] load: counting the records, elapsed 0s, eta unknown"
|
||||
content := "[5/5] content: searching the database, elapsed 0s, eta unknown"
|
||||
cases := map[string][]string{
|
||||
first: {
|
||||
load, "[2/5] walk: 0 files, 0 files/s, elapsed 0s, eta unknown",
|
||||
"[3/5] hash: [0/", "[4/5] update: [0/", content,
|
||||
},
|
||||
rescan: {
|
||||
load, "[2/5] walk: [0/~2] 0%, 0 files/s, elapsed 0s, eta unknown",
|
||||
"[3/5] hash: [0/", "[4/5] update: [0/", content,
|
||||
},
|
||||
}
|
||||
|
||||
for out, want := range cases {
|
||||
var got []string
|
||||
|
||||
for line := range strings.Lines(out) {
|
||||
label, _, _ := strings.Cut(line, ":")
|
||||
if len(got) == 0 || !strings.HasPrefix(got[len(got)-1], label) {
|
||||
got = append(got, line)
|
||||
}
|
||||
}
|
||||
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("first lines of the phases = %q, want %q", got, want)
|
||||
}
|
||||
|
||||
for i := range want {
|
||||
if !strings.HasPrefix(got[i], want[i]) {
|
||||
t.Errorf("phase %d starts %q, want %q", i+1, got[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The load phase's total is the count of the records.
|
||||
loaded := fmt.Sprintf("\n[1/5] load: [%d/%d] 100%%, ",
|
||||
smokeTreeFiles, smokeTreeFiles)
|
||||
if !strings.Contains(rescan, loaded) {
|
||||
t.Errorf("rescan stderr = %q, want %q", rescan, loaded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncScanUnchangedReuse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user