render_timer_test.go RawHistoryFolder

1package daemon
2
3import (
4	"testing"
5	"time"
6
7	"github.com/stretchr/testify/require"
8
9	"github.com/bnema/vev/internal/domain"
10	portsmocks "github.com/bnema/vev/internal/ports/mocks"
11)
12
13func TestTimerLaneDetachRemovesCompleteToken(t *testing.T) {
14	lane := timerLane{generation: 1}
15	timer := portsmocks.NewMockTimer(t)
16	token := lane.publishLocked(1, timer)
17	require.NotNil(t, token)
18	detached := lane.detachLocked()
19	require.Same(t, token, detached)
20	require.Nil(t, lane.token)
21	select {
22	case <-token.cancel:
23	default:
24		t.Fatal("detach did not cancel token")
25	}
26	require.Nil(t, lane.publishLocked(0, timer), "stale generation reacquired detached lane")
27}
28
29func TestRenderDeadlineCallbackCanInvalidateAgainWithoutJoiningItself(t *testing.T) {
30	clock := newCoordinatorMockClock(t, 2)
31	ac := &attachedClient{}
32	wakeReturned := make(chan struct{})
33	var rc *renderCoordinator
34	rc = newRenderCoordinator(renderCoordinatorOptions{
35		clock: clock.clock,
36		wake: func(renderWake) {
37			require.True(t, rc.invalidateForAttachment(ac, renderInvalidation{class: invalidateUrgent, producer: "reentrant-wake-test"}))
38			close(wakeReturned)
39		},
40	})
41	rc.attach(ac)
42	t.Cleanup(func() {
43		rc.beginSessionTeardown().finish()
44		rc.waitForTimerWorkers()
45	})
46	require.True(t, rc.invalidateForAttachment(ac, renderInvalidation{class: invalidateUrgent, producer: "initial-wake-test"}))
47	awaitCoordinatorScheduledTimer(t, clock).ch <- time.Time{}
48	awaitTestCompletion(t, wakeReturned, "timer callback deadlocked while publishing its next invalidation")
49}
50
51func TestRenderTimerCallbacksCanReplaceTheirOwnLane(t *testing.T) {
52	cases := []struct {
53		name string
54		run  func(*testing.T, *renderCoordinator, *coordinatorMockClock, *attachedClient)
55	}{
56		{
57			name: "normal wake invalidates", run: func(t *testing.T, rc *renderCoordinator, clock *coordinatorMockClock, ac *attachedClient) {
58				woke := make(chan struct{})
59				rc.opts.wake = func(renderWake) {
60					rc.invalidateForAttachment(ac, renderInvalidation{class: invalidateUrgent, producer: "table-normal"})
61					close(woke)
62				}
63				rc.invalidateForAttachment(ac, renderInvalidation{class: invalidateUrgent, producer: "table-normal"})
64				awaitCoordinatorScheduledTimer(t, clock).ch <- time.Time{}
65				awaitTestCompletion(t, woke, "normal callback did not reenter invalidation")
66			},
67		},
68		{
69			name: "resize callback schedules resize", run: func(t *testing.T, rc *renderCoordinator, clock *coordinatorMockClock, ac *attachedClient) {
70				lease := rc.attachmentLease(ac)
71				rc.scheduleResizeForLease(domain.Size{Cols: 80, Rows: 24}, ac, lease, func(uint64) {
72					rc.scheduleResizeForLease(domain.Size{Cols: 81, Rows: 24}, ac, lease, func(uint64) {})
73				})
74				timer := awaitCoordinatorScheduledTimer(t, clock)
75				done := captureResizeCallbackDone(t, rc)
76				timer.ch <- time.Time{}
77				_ = awaitTestValue(t, clock.timers, "resize callback did not schedule replacement")
78				awaitTestCompletion(t, done, "resize callback did not complete")
79			},
80		},
81		{
82			name: "retry callback schedules retry", run: func(t *testing.T, rc *renderCoordinator, clock *coordinatorMockClock, ac *attachedClient) {
83				lease := rc.attachmentLease(ac)
84				epoch := rc.recordResizeRequestForLease(domain.Size{Cols: 80, Rows: 24}, ac, lease)
85				require.True(t, rc.resizeCurrentForLease(epoch, ac, lease, true))
86				rc.scheduleResizeRetryForLease(epoch, ac, lease, func() { rc.scheduleResizeRetryForLease(epoch, ac, lease, func() {}) })
87				awaitCoordinatorScheduledTimer(t, clock).ch <- time.Time{}
88				_ = awaitTestValue(t, clock.timers, "retry callback did not schedule replacement")
89			},
90		},
91		{
92			name: "sync force transitions sync generation", run: func(t *testing.T, rc *renderCoordinator, clock *coordinatorMockClock, _ *attachedClient) {
93				p := &pane{}
94				rc.noteSyncBegin(p, 1, func() { rc.noteSyncBegin(p, 2) })
95				awaitCoordinatorScheduledTimer(t, clock).ch <- time.Time{}
96				_ = awaitTestValue(t, clock.timers, "sync force did not schedule replacement")
97			},
98		},
99	}
100	for _, tc := range cases {
101		t.Run(tc.name, func(t *testing.T) {
102			clock := newCoordinatorMockClock(t, 4)
103			rc, ac := newRenderCoordinator(renderCoordinatorOptions{clock: clock.clock}), &attachedClient{}
104			rc.attach(ac)
105			tc.run(t, rc, clock, ac)
106			rc.beginSessionTeardown().finish()
107			rc.waitForTimerWorkers()
108		})
109	}
110}
111
112func TestRenderTimerScheduleRacesTerminalTeardown(t *testing.T) {
113	for range 32 {
114		clock := newCoordinatorMockClock(t, 2)
115		rc, ac := newRenderCoordinator(renderCoordinatorOptions{clock: clock.clock}), &attachedClient{}
116		rc.attach(ac)
117		start := make(chan struct{})
118		scheduled := make(chan struct{})
119		go func() {
120			<-start
121			rc.invalidateForAttachment(ac, renderInvalidation{class: invalidateUrgent, producer: "race"})
122			close(scheduled)
123		}()
124		close(start)
125		cleanup := rc.beginSessionTeardown()
126		awaitTestCompletion(t, scheduled, "schedule raced terminal teardown")
127		cleanup.finish()
128		rc.waitForTimerWorkers()
129		rc.mu.Lock()
130		require.True(t, rc.torndown)
131		require.Nil(t, rc.normalLane.token)
132		rc.mu.Unlock()
133	}
134}
135
136func TestTimerSupervisorCancelsNeverFiringWorker(t *testing.T) {
137	var lane timerLane
138	generation, _ := lane.replaceLocked()
139	token := lane.publishLocked(generation, portsmocks.NewMockTimer(t))
140	var supervisor timerSupervisor
141	supervisor.startLocked(token, nil, func() { t.Fatal("cancelled worker fired") })
142	lane.detachLocked()
143	supervisor.wait()
144}

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 12

Tags 11

↑↓ moveenter openesc close