main f2d166b

capture_bounds_test.go RawHistoryFolder

1package headless
2
3import (
4	"context"
5	"image"
6	"os"
7	"testing"
8	"time"
9
10	"github.com/bnema/neferwl/internal/adapters/outputkit/capture"
11	portsmocks "github.com/bnema/neferwl/internal/mocks/ports"
12	"github.com/bnema/neferwl/internal/ports"
13	"github.com/stretchr/testify/mock"
14)
15
16func TestScreenshotReadbackFailureDoesNotWriteBlackPNG(t *testing.T) {
17	r := portsmocks.NewMockRenderer(t)
18	r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil).Once()
19	scenes := make(chan ports.Scene, 1)
20	scenes <- ports.Scene{Seq: 1, Scale: 1}
21	dir := t.TempDir()
22	ctx, cancel := context.WithCancel(context.Background())
23	defer cancel()
24	attempted := make(chan struct{})
25	// Synchronize on the actual readback attempt without assuming timing.
26	r.EXPECT().Pixels().Run(func() { close(attempted) }).Return(nil).Once()
27	r.EXPECT().Close().Return().Once()
28	done := make(chan error, 1)
29	go func() {
30		done <- Run(ctx, Options{Width: 2, Height: 2, ScreenshotDir: dir, NewRenderer: func(int, int) (ports.Renderer, error) { return r, nil }}, scenes, nil, nil, nil, nil)
31	}()
32	select {
33	case <-attempted:
34	case <-time.After(time.Second):
35		t.Fatal("no readback attempt")
36	}
37	select {
38	case err := <-done:
39		t.Fatalf("readback unavailable stopped output: %v", err)
40	default:
41	}
42	cancel()
43	if err := <-done; err != nil {
44		t.Fatal(err)
45	}
46	files, err := os.ReadDir(dir)
47	if err != nil || len(files) != 0 {
48		t.Fatalf("failed screenshot wrote files: %v (%v)", files, err)
49	}
50}
51
52func TestCaptureBatchBoundedAndRecycledWhileIdle(t *testing.T) {
53	r, _ := recordingRenderer(t, nil)
54	frame := portsmocks.NewMockCaptureFrame(t)
55	frame.EXPECT().Done().Return(nil)
56	frame.EXPECT().Read(mock.Anything, mock.Anything, 8).Return(nil).Times(capture.MaxRequests)
57	recycled := make(chan struct{})
58	r.EXPECT().BeginCapture().Return(frame, nil).Once()
59	r.EXPECT().EndCapture(frame).Run(func(ports.CaptureFrame) { close(recycled) }).Return().Once()
60	ctx, cancel := context.WithCancel(context.Background())
61	defer cancel()
62	incoming := make(chan ports.CaptureRequest)
63	scenes := make(chan ports.Scene)
64	replies := make(chan ports.CaptureDone, capture.MaxRequests+1)
65	done := make(chan error, 1)
66	go func() {
67		done <- Run(ctx, Options{Width: 2, Height: 2, NewRenderer: func(int, int) (ports.Renderer, error) { return r, nil }, Captured: replies}, scenes, nil, nil, nil, incoming)
68	}()
69	for i := range capture.MaxRequests + 1 {
70		f, err := os.CreateTemp(t.TempDir(), "capture")
71		if err != nil {
72			t.Fatal(err)
73		}
74		t.Cleanup(func() { _ = f.Close() })
75		if err := f.Truncate(16); err != nil {
76			t.Fatal(err)
77		}
78		select {
79		case incoming <- ports.CaptureRequest{ID: uint64(i), Region: image.Rect(0, 0, 2, 2), Width: 2, Height: 2, Stride: 8, Format: 1, Dst: ports.SHMBuffer{File: f}}:
80		case <-time.After(time.Second):
81			t.Fatal("capture admission blocked")
82		}
83	}
84	select {
85	case rejected := <-replies:
86		if rejected.ID != capture.MaxRequests || rejected.Err == nil {
87			t.Fatalf("batch overflow %+v", rejected)
88		}
89	case <-time.After(time.Second):
90		t.Fatal("batch overflow not answered")
91	}
92	scenes <- ports.Scene{Seq: 1, Scale: 1}
93	for range capture.MaxRequests {
94		select {
95		case result := <-replies:
96			if result.Err != nil {
97				t.Fatalf("capture %+v", result)
98			}
99		case <-time.After(time.Second):
100			t.Fatal("accepted capture not answered")
101		}
102	}
103	select {
104	case <-recycled:
105	case <-time.After(time.Second):
106		t.Fatal("idle output did not return readback lease")
107	}
108	cancel()
109	if err := <-done; err != nil {
110		t.Fatal(err)
111	}
112	frame.Calls = nil
113}

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 25

Tags 10

↑↓ moveenter openesc close