detached ee9ce2c

test(app): wait for the maintainer to be idle instead of sleeping

commit
ee9ce2c9fec92017ea44f6e1212e73b9c7fe8ae1
author
brice · · 2026-10-10 11:16 UTC
parent
fcf895d
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +17 -6
A internal/app/export_test.go +11 -0
@@ -0,0 +1,11 @@
1+// SPDX-License-Identifier: AGPL-3.0-or-later
2+
3+package app
4+
5+// Idle reports whether nothing is pending or running: tests wait for it
6+// instead of sleeping.
7+func (m *Maintainer) Idle() bool {
8+ m.mu.Lock()
9+ defer m.mu.Unlock()
10+ return len(m.entries) == 0
11+}
@@ -0,0 +1,11 @@
1+// SPDX-License-Identifier: AGPL-3.0-or-later
2+
3+package app
4+
5+// Idle reports whether nothing is pending or running: tests wait for it
6+// instead of sleeping.
7+func (m *Maintainer) Idle() bool {
8+ m.mu.Lock()
9+ defer m.mu.Unlock()
10+ return len(m.entries) == 0
11+}
M internal/app/maintenance_test.go +6 -6
@@ -5,6 +5,7 @@ package app_test
55 import (
66 "context"
77 "errors"
8+ "runtime"
89 "sync"
910 "sync/atomic"
1011 "testing"
@@ -37,16 +38,16 @@ func TestMaintainerDebounces(t *testing.T) {
3738 runs.Add(1)
3839 return nil
3940 })
40- m := app.NewMaintainer(ctx, git, 50*time.Millisecond)
41+ m := app.NewMaintainer(ctx, git, 200*time.Millisecond)
4142 defer m.Close(ctx)
4243
4344 for range 10 {
4445 m.Schedule(ctx, n)
4546 }
46- time.Sleep(10 * time.Millisecond)
4747 assert.Equal(t, int32(0), runs.Load(), "waits for the delay")
4848 waitFor(t, func() bool { return runs.Load() == 1 }, "one run")
49- time.Sleep(150 * time.Millisecond)
49+ // Idle: nothing is pending or running, so no second run is coming.
50+ waitFor(t, m.Idle, "the maintainer to be idle")
5051 assert.Equal(t, int32(1), runs.Load(), "a burst makes one run")
5152
5253 m.Schedule(ctx, n)
@@ -86,13 +87,12 @@ func TestMaintainerOneRunAtATimePerRepo(t *testing.T) {
8687 m.Schedule(ctx, n)
8788 m.Schedule(ctx, other)
8889 waitFor(t, otherRan.Load, "another repository is not blocked")
89- time.Sleep(60 * time.Millisecond)
9090 assert.Equal(t, int32(1), runs.Load(), "the second run waits for the first")
9191
9292 release <- struct{}{} // finish first run
9393 waitFor(t, func() bool { return runs.Load() == 2 }, "queued run")
9494 release <- struct{}{}
95- time.Sleep(60 * time.Millisecond)
95+ waitFor(t, m.Idle, "the maintainer to be idle")
9696 assert.Equal(t, int32(2), runs.Load(), "exactly one queued run")
9797 assert.Equal(t, int32(1), maxConcurrent.Load())
9898 }
@@ -163,7 +163,7 @@ func TestMaintainerConcurrentSchedule(t *testing.T) {
163163 defer wg.Done()
164164 for range 50 {
165165 m.Schedule(ctx, name(t, string(rune('a'+i%3))))
166- time.Sleep(100 * time.Microsecond)
166+ runtime.Gosched()
167167 }
168168 }()
169169 }
@@ -5,6 +5,7 @@ package app_test
5 import (5 import (
6 "context"6 "context"
7 "errors"7 "errors"
8+ "runtime"
8 "sync"9 "sync"
9 "sync/atomic"10 "sync/atomic"
10 "testing"11 "testing"
@@ -37,16 +38,16 @@ func TestMaintainerDebounces(t *testing.T) {
37 runs.Add(1)38 runs.Add(1)
38 return nil39 return nil
39 })40 })
40- m := app.NewMaintainer(ctx, git, 50*time.Millisecond)41+ m := app.NewMaintainer(ctx, git, 200*time.Millisecond)
41 defer m.Close(ctx)42 defer m.Close(ctx)
42 43
43 for range 10 {44 for range 10 {
44 m.Schedule(ctx, n)45 m.Schedule(ctx, n)
45 }46 }
46- time.Sleep(10 * time.Millisecond)
47 assert.Equal(t, int32(0), runs.Load(), "waits for the delay")47 assert.Equal(t, int32(0), runs.Load(), "waits for the delay")
48 waitFor(t, func() bool { return runs.Load() == 1 }, "one run")48 waitFor(t, func() bool { return runs.Load() == 1 }, "one run")
49- time.Sleep(150 * time.Millisecond)49+ // Idle: nothing is pending or running, so no second run is coming.
50+ waitFor(t, m.Idle, "the maintainer to be idle")
50 assert.Equal(t, int32(1), runs.Load(), "a burst makes one run")51 assert.Equal(t, int32(1), runs.Load(), "a burst makes one run")
51 52
52 m.Schedule(ctx, n)53 m.Schedule(ctx, n)
@@ -86,13 +87,12 @@ func TestMaintainerOneRunAtATimePerRepo(t *testing.T) {
86 m.Schedule(ctx, n)87 m.Schedule(ctx, n)
87 m.Schedule(ctx, other)88 m.Schedule(ctx, other)
88 waitFor(t, otherRan.Load, "another repository is not blocked")89 waitFor(t, otherRan.Load, "another repository is not blocked")
89- time.Sleep(60 * time.Millisecond)
90 assert.Equal(t, int32(1), runs.Load(), "the second run waits for the first")90 assert.Equal(t, int32(1), runs.Load(), "the second run waits for the first")
91 91
92 release <- struct{}{} // finish first run92 release <- struct{}{} // finish first run
93 waitFor(t, func() bool { return runs.Load() == 2 }, "queued run")93 waitFor(t, func() bool { return runs.Load() == 2 }, "queued run")
94 release <- struct{}{}94 release <- struct{}{}
95- time.Sleep(60 * time.Millisecond)95+ waitFor(t, m.Idle, "the maintainer to be idle")
96 assert.Equal(t, int32(2), runs.Load(), "exactly one queued run")96 assert.Equal(t, int32(2), runs.Load(), "exactly one queued run")
97 assert.Equal(t, int32(1), maxConcurrent.Load())97 assert.Equal(t, int32(1), maxConcurrent.Load())
98 }98 }
@@ -163,7 +163,7 @@ func TestMaintainerConcurrentSchedule(t *testing.T) {
163 defer wg.Done()163 defer wg.Done()
164 for range 50 {164 for range 50 {
165 m.Schedule(ctx, name(t, string(rune('a'+i%3))))165 m.Schedule(ctx, name(t, string(rune('a'+i%3))))
166- time.Sleep(100 * time.Microsecond)166+ runtime.Gosched()
167 }167 }
168 }()168 }()
169 }169 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 3

Tags 44

↑↓ moveenter openesc close