feat/data-dnd e05cb15

postring_test.go RawHistoryFolder

1package wayland
2
3import (
4	"context"
5	"sync"
6	"testing"
7	"time"
8
9	"github.com/bnema/neferwl/internal/adapters/logging"
10	"github.com/bnema/neferwl/internal/ports"
11)
12
13// Pushing a capture reply and applying it on the display goroutine allocates
14// nothing in steady state. The count includes the display goroutine:
15// AllocsPerRun reads the global malloc counter.
16func TestCapturedForwardAllocations(t *testing.T) {
17	if raceEnabled {
18		t.Skip("the race detector makes the purego wake callback allocate")
19	}
20	s, _, _, _, _, _ := presentServer(t)
21	done := ports.CaptureDone{ID: 77, Output: "HEADLESS-1", Time: time.Unix(1, 2)}
22	// Posts run in order: once the marker ran, the drain before it did. The
23	// marker is bound once and the channel reused, so waiting allocates nothing.
24	ran := make(chan struct{}, 1)
25	marker := func() { ran <- struct{}{} }
26	for range 4 {
27		s.capturedRing.Push(done)
28		s.display.Post(marker)
29		<-ran
30	}
31	allocs := testing.AllocsPerRun(200, func() {
32		s.capturedRing.Push(done)
33		s.display.Post(marker)
34		<-ran
35	})
36	if allocs != 0 {
37		t.Errorf("push + drain: %.1f allocs, want 0", allocs)
38	}
39}
40
41// A full ring blocks the push until the display goroutine drains it, and no
42// value is lost or reordered.
43func TestPostRingFullBlocksWithoutLoss(t *testing.T) {
44	s, _, _, _, _, _ := presentServer(t)
45	const limit, total = 4, 3*4 + 1
46	var got []int // display goroutine only
47	r := newPostRing(s.display, limit, func(v int) { got = append(got, v) })
48
49	// Stall the display goroutine so nothing drains.
50	release := make(chan struct{})
51	stalled := make(chan struct{})
52	s.display.Post(func() { close(stalled); <-release })
53	<-stalled
54
55	pushed := make(chan int, total)
56	var wg sync.WaitGroup
57	wg.Add(1)
58	go func() {
59		defer wg.Done()
60		for i := range total {
61			if !r.Push(i) {
62				t.Errorf("push %d refused", i)
63				return
64			}
65			pushed <- i
66		}
67	}()
68	for range limit {
69		select {
70		case <-pushed:
71		case <-time.After(2 * time.Second):
72			t.Fatal("pushes below the limit blocked")
73		}
74	}
75	select {
76	case i := <-pushed:
77		t.Fatalf("push %d returned on a full ring", i)
78	case <-time.After(50 * time.Millisecond):
79	}
80	close(release)
81	wg.Wait()
82	// The marker runs after every drain posted before it.
83	var final []int
84	ran := make(chan struct{})
85	for {
86		s.display.Post(func() {
87			if len(got) == total {
88				final = append(final, got...)
89			}
90			ran <- struct{}{}
91		})
92		<-ran
93		if final != nil {
94			break
95		}
96		time.Sleep(time.Millisecond)
97	}
98	for i, v := range final {
99		if v != i {
100			t.Fatalf("applied %v, want 0..%d in order", final, total-1)
101		}
102	}
103}
104
105// A push blocked on a full ring resumes once the display drains it.
106func TestPostRingBlockedPushResumes(t *testing.T) {
107	s, _, _, _, _, _ := presentServer(t)
108	r := newPostRing(s.display, 1, func(int) {})
109	release := make(chan struct{})
110	stalled := make(chan struct{})
111	s.display.Post(func() { close(stalled); <-release })
112	<-stalled
113	if !r.Push(1) {
114		t.Fatal("first push refused")
115	}
116	result := make(chan bool, 1)
117	go func() { result <- r.Push(2) }()
118	select {
119	case ok := <-result:
120		t.Fatalf("push on a full ring returned %v", ok)
121	case <-time.After(50 * time.Millisecond):
122	}
123	close(release)
124	select {
125	case ok := <-result:
126		if !ok {
127			t.Fatal("push refused while the display runs")
128		}
129	case <-time.After(2 * time.Second):
130		t.Fatal("blocked push never resumed")
131	}
132}
133
134// A push blocked on a full ring returns false once the display has stopped,
135// instead of waiting for a drain that never comes.
136func TestPostRingPushStopsWithDisplay(t *testing.T) {
137	s, err := New(Options{RuntimeDir: t.TempDir()}, Channels{}, logging.For(context.Background(), "wayland"))
138	if err != nil {
139		t.Fatal(err)
140	}
141	ctx, cancel := context.WithCancel(context.Background())
142	done := make(chan error, 1)
143	go func() { done <- s.Run(ctx) }()
144	cancel()
145	<-done
146	r := newPostRing(s.display, 1, func(int) {})
147	if !r.Push(1) { // room: queued, its drain is dropped by the stopped display
148		t.Fatal("first push refused")
149	}
150	result := make(chan bool, 1)
151	go func() { result <- r.Push(2) }()
152	select {
153	case ok := <-result:
154		if ok {
155			t.Fatal("push on a stopped display reported success")
156		}
157	case <-time.After(2 * time.Second):
158		t.Fatal("push blocked after the display stopped")
159	}
160}

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 23

Tags 10

↑↓ moveenter openesc close