runtime_residency_test.go RawHistoryFolder

1package ui
2
3import (
4	"testing"
5	"time"
6
7	"github.com/bnema/dumber/internal/application/usecase"
8	"github.com/stretchr/testify/require"
9)
10
11func TestResidencyTimeoutFromMillis(t *testing.T) {
12	require.Equal(t, time.Duration(0), ResidencyTimeoutFromMillis(0))
13	require.Equal(t, time.Duration(0), ResidencyTimeoutFromMillis(-5))
14	require.Equal(t, 60*time.Second, ResidencyTimeoutFromMillis(60000))
15	require.Equal(t, 300000*time.Millisecond, ResidencyTimeoutFromMillis(300000))
16	require.Equal(t, 300000*time.Millisecond, ResidencyTimeoutFromMillis(999999))
17}
18
19func TestResidencyTimeoutLatchAndRestartRequired(t *testing.T) {
20	app := &App{}
21	require.Equal(t, time.Duration(0), app.LatchedResidencyTimeout())
22
23	app.SetResidencyTimeout(60 * time.Second)
24	require.Equal(t, 60*time.Second, app.LatchedResidencyTimeout())
25	require.False(t, app.ResidencyTimeoutRestartRequired(60000))
26	require.True(t, app.ResidencyTimeoutRestartRequired(0))
27	require.True(t, app.ResidencyTimeoutRestartRequired(120000))
28
29	// A nil app never panics and reports no change.
30	var nilApp *App
31	nilApp.SetResidencyTimeout(time.Minute)
32	require.Equal(t, time.Duration(0), nilApp.LatchedResidencyTimeout())
33	require.False(t, nilApp.ResidencyTimeoutRestartRequired(60000))
34}
35
36type residencyFakeApp struct {
37	holds    int
38	releases int
39	quits    int
40}
41
42func (f *residencyFakeApp) Hold()    { f.holds++ }
43func (f *residencyFakeApp) Release() { f.releases++ }
44func (f *residencyFakeApp) Quit()    { f.quits++ }
45
46type residencyFakeScheduler struct {
47	scheduled   []time.Duration
48	generations []uint64
49	cancels     int
50}
51
52func (f *residencyFakeScheduler) schedule(d time.Duration, generation uint64) {
53	f.scheduled = append(f.scheduled, d)
54	f.generations = append(f.generations, generation)
55}
56
57func (f *residencyFakeScheduler) cancel() { f.cancels++ }
58
59func newResidencyTestController(timeout time.Duration) (*ResidencyController, *residencyFakeApp, *residencyFakeScheduler, *int) {
60	now := time.Now()
61	clock := func() time.Time { return now }
62	app := &residencyFakeApp{}
63	sched := &residencyFakeScheduler{}
64	quits := 0
65	ctl := NewResidencyController(timeout, clock, sched.schedule, sched.cancel, func() { quits++ })
66	return ctl, app, sched, &quits
67}
68
69func TestResidencyControllerDisabledPreservesQuit(t *testing.T) {
70	ctl, app, sched, quits := newResidencyTestController(0)
71	require.False(t, ctl.Enabled())
72	ctl.AcquireHold(app)
73	require.Equal(t, 0, app.holds, "disabled residency takes no hold")
74	ctl.ReleaseHold(app)
75	require.Equal(t, 0, app.releases)
76
77	ctl.SetWindowCount(1)
78	decision := ctl.SetWindowCount(0)
79	require.Equal(t, usecase.ResidencyQuit, decision.Action)
80	require.Equal(t, 1, *quits)
81	require.Empty(t, sched.scheduled)
82}
83
84func TestResidencyControllerHoldMatchesReleaseOnce(t *testing.T) {
85	ctl, app, _, quits := newResidencyTestController(time.Minute)
86	require.True(t, ctl.Enabled())
87	ctl.AcquireHold(app)
88	ctl.AcquireHold(app)
89	require.Equal(t, 1, app.holds, "exactly one hold")
90	ctl.ReleaseHold(app)
91	ctl.ReleaseHold(app)
92	require.Equal(t, 1, app.releases, "exactly one release")
93	require.Equal(t, 0, *quits, "hold cycling never quits")
94}
95
96func TestResidencyControllerLastWindowArmsAndExpiryQuits(t *testing.T) {
97	ctl, _, sched, quits := newResidencyTestController(time.Minute)
98	ctl.SetWindowCount(1)
99	decision := ctl.SetWindowCount(0)
100	require.Equal(t, usecase.ResidencyArm, decision.Action)
101	require.NotEmpty(t, sched.scheduled)
102	armed := sched.generations[len(sched.generations)-1]
103	ctl.OnTimerFired(armed)
104	require.Equal(t, 1, *quits)
105}
106
107func TestResidencyControllerReopenCancelsTimer(t *testing.T) {
108	ctl, _, sched, _ := newResidencyTestController(time.Minute)
109	ctl.SetWindowCount(1)
110	ctl.SetWindowCount(0)
111	require.NotEmpty(t, sched.scheduled)
112	armed := sched.generations[len(sched.generations)-1]
113	cancels := sched.cancels
114	ctl.SetWindowCount(1)
115	require.Greater(t, sched.cancels, cancels, "reopen cancels the armed deadline")
116	ctl.OnTimerFired(armed)
117}
118
119func TestResidencyControllerExplicitQuitBypassesDeadline(t *testing.T) {
120	ctl, _, sched, quits := newResidencyTestController(time.Minute)
121	ctl.SetWindowCount(1)
122	ctl.SetWindowCount(0)
123	require.NotEmpty(t, sched.scheduled)
124	armed := sched.generations[len(sched.generations)-1]
125	ctl.NoteExplicitQuit()
126	require.Equal(t, 0, *quits, "noting quit never invokes quit itself")
127	ctl.OnTimerFired(armed)
128	require.Equal(t, 0, *quits, "deadline never fires after explicit quit")
129}
130
131func TestResidencyControllerNilSafe(t *testing.T) {
132	var ctl *ResidencyController
133	require.False(t, ctl.Enabled())
134	require.Equal(t, usecase.ResidencyQuit, ctl.SetWindowCount(0).Action)
135	require.Equal(t, usecase.ResidencyQuit, ctl.Evaluate().Action)
136	ctl.NoteExplicitQuit()
137	ctl.OnTimerFired(7)
138	ctl.AcquireHold(nil)
139	ctl.ReleaseHold(nil)
140	ctl.SetQuiescentFunc(nil)
141	ctl.SetDispatchToGTK(nil)
142	ctl.DispatchToGTK(nil)
143	ctl.MaybeQuit()
144	require.Equal(t, usecase.ResidencyNone, ctl.SetNativeBusy("x", true).Action)
145}
146
147func TestResidencyControllerDefersQuitUntilSettled(t *testing.T) {
148	ctl, _, sched, quits := newResidencyTestController(time.Minute)
149	quiescent := true
150	ctl.SetQuiescentFunc(func() bool { return quiescent })
151	ctl.SetWindowCount(1)
152	decision := ctl.SetWindowCount(0)
153	require.Equal(t, usecase.ResidencyArm, decision.Action)
154	armed := sched.generations[len(sched.generations)-1]
155
156	quiescent = false
157	ctl.OnTimerFired(armed)
158	require.Equal(t, 0, *quits, "expiry defers while native work is outstanding")
159
160	quiescent = true
161	ctl.MaybeQuit()
162	require.Equal(t, 1, *quits, "settle recheck commits the deferred quit")
163	ctl.MaybeQuit()
164	require.Equal(t, 1, *quits, "quit commits exactly once")
165}
166
167func TestResidencyControllerNativeBusyDrivesPolicy(t *testing.T) {
168	ctl, _, sched, quits := newResidencyTestController(time.Minute)
169	ctl.SetWindowCount(1)
170
171	decision := ctl.SetNativeBusy("cef-activity", true)
172	require.Equal(t, usecase.ResidencyNone, decision.Action)
173	decision = ctl.SetWindowCount(0)
174	require.Equal(t, usecase.ResidencyNone, decision.Action, "close with native work outstanding arms nothing")
175	require.Empty(t, sched.scheduled)
176
177	decision = ctl.SetNativeBusy("cef-activity", false)
178	require.Equal(t, usecase.ResidencyArm, decision.Action, "settle re-arms idle")
179	require.NotEmpty(t, sched.scheduled)
180	require.Equal(t, 0, *quits)
181}
182
183func TestResidencyControllerDuplicateBusyIsSilent(t *testing.T) {
184	ctl, _, sched, _ := newResidencyTestController(time.Minute)
185	ctl.SetNativeBusy("downloads", true)
186	decision := ctl.SetNativeBusy("downloads", true)
187	require.Equal(t, usecase.ResidencyNone, decision.Action)
188	require.Empty(t, sched.scheduled)
189}
190
191func TestResidencyControllerDispatchRoutesCallbacks(t *testing.T) {
192	ctl, _, _, quits := newResidencyTestController(time.Minute)
193	var routed []string
194	ctl.SetDispatchToGTK(func(fn func()) {
195		routed = append(routed, "gtk")
196		fn()
197	})
198	ctl.DispatchToGTK(func() { routed = append(routed, "cb") })
199	require.Equal(t, []string{"gtk", "cb"}, routed)
200	require.Equal(t, 0, *quits)
201}
202
203func TestResidencyControllerSealsBeforeCommit(t *testing.T) {
204	ctl, _, sched, quits := newResidencyTestController(time.Minute)
205	sealed := 0
206	unsealed := 0
207	ctl.SetSealFunc(func() { sealed++ })
208	ctl.SetUnsealFunc(func() { unsealed++ })
209	ctl.SetQuiescentFunc(func() bool { return true })
210	ctl.SetWindowCount(1)
211	ctl.SetWindowCount(0)
212	armed := sched.generations[len(sched.generations)-1]
213	// Quiescent expiry seals admission, then commits the quit.
214	ctl.OnTimerFired(armed)
215	require.Equal(t, 1, sealed)
216	require.Equal(t, 0, unsealed)
217	require.Equal(t, 1, *quits)
218}
219
220func TestResidencyControllerReopenInvalidatesDeferredQuit(t *testing.T) {
221	ctl, _, sched, quits := newResidencyTestController(time.Minute)
222	quiescent := true
223	unsealed := 0
224	ctl.SetUnsealFunc(func() { unsealed++ })
225	ctl.SetQuiescentFunc(func() bool { return quiescent })
226	ctl.SetWindowCount(1)
227	ctl.SetWindowCount(0)
228	armed := sched.generations[len(sched.generations)-1]
229	quiescent = false
230	ctl.OnTimerFired(armed)
231	require.Equal(t, 0, *quits, "quit defers while unsettled")
232	// A reopened window invalidates the deferral.
233	ctl.SetWindowCount(1)
234	quiescent = true
235	ctl.MaybeQuit()
236	require.Equal(t, 0, *quits, "obsolete quit must not run after reopen")
237	require.Equal(t, 1, unsealed, "invalidation reopens admission")
238}
239
240func TestResidencyControllerDuplicateEmptyStaysArmed(t *testing.T) {
241	ctl, _, sched, quits := newResidencyTestController(time.Minute)
242	ctl.SetWindowCount(1)
243	first := ctl.SetWindowCount(0)
244	require.Equal(t, usecase.ResidencyArm, first.Action)
245	require.Len(t, sched.scheduled, 1)
246	// A repeated empty update (e.g. tab-empty after window removal)
247	// stays on the armed deadline instead of re-arming.
248	again := ctl.SetWindowCount(0)
249	require.Equal(t, usecase.ResidencyNone, again.Action)
250	require.Len(t, sched.scheduled, 1, "no duplicate timer")
251	require.Equal(t, 0, *quits)
252}

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 8

Tags 42

↑↓ moveenter openesc close