indicator_test.go RawHistoryFolder

1package capture
2
3import (
4	"context"
5	"image"
6	"os"
7	"testing"
8
9	portsmocks "github.com/bnema/neferwl/internal/mocks/ports"
10	"github.com/bnema/neferwl/internal/ports"
11	"github.com/stretchr/testify/mock"
12	"github.com/stretchr/testify/require"
13)
14
15func indicatorScene() ports.Scene {
16	s := excludedTestScene()
17	s.Capture = nil
18	s.CaptureIndicators = []ports.CaptureIndicator{{Rect: ports.Rect{W: 10, H: 10}}, {Pill: true, Rect: ports.Rect{X: 1, Y: 1, W: 3, H: 3}}}
19	return s
20}
21
22// No capture scene ever carries the indicator: excluded scenes, plain scenes
23// and the off-screen child are derived without it.
24func TestCaptureScenesNeverCarryTheIndicator(t *testing.T) {
25	p := NewPipeline(context.Background(), make(chan ports.CaptureDone, 1))
26	defer p.Close(nil)
27	s := indicatorScene()
28	require.Nil(t, p.ExcludedScene(s).CaptureIndicators)
29	s.Capture = excludedTestScene().Capture
30	require.Nil(t, p.ExcludedScene(s).CaptureIndicators)
31	require.Nil(t, plain(s).CaptureIndicators)
32	require.Len(t, s.CaptureIndicators, 2, "the displayed scene keeps its indicator")
33	// Even an exclusion-less request is fine: it is drawn from the plain frame.
34	normal, excluded, _ := p.Split(s, []ports.CaptureRequest{{ID: 1}, {ID: 2, Exclude: true, Session: 7}})
35	require.Equal(t, []uint64{1}, ids(normal))
36	require.Equal(t, []uint64{2}, ids(excluded), "an Exclude request is drawn from ExcludedScene, never the displayed frame")
37}
38
39// The requests of the displayed frame are answered from a frame rendered
40// without the indicator before the displayed one.
41func TestSubmitPlainRendersWithoutIndicatorFirst(t *testing.T) {
42	replies := make(chan ports.CaptureDone, 1)
43	p := NewPipeline(context.Background(), replies)
44	r := portsmocks.NewMockRenderer(t)
45	frame := portsmocks.NewMockCaptureFrame(t)
46	frame.EXPECT().Done().Return((*os.File)(nil))
47	frame.EXPECT().Read(image.Rect(0, 0, 2, 2), mock.Anything, 8).Return(nil).Once()
48	var rendered []ports.Scene
49	r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(s ports.Scene, _ map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
50		rendered = append(rendered, s)
51		return nil, nil
52	}).Once()
53	r.EXPECT().BeginCapture().Return(frame, nil).Once()
54	r.EXPECT().EndCapture(frame).Return().Once()
55	q := pipelineRequest(t, 1)
56	reqs := []ports.CaptureRequest{q}
57	took, done, err := p.SubmitPlain(r, indicatorScene(), nil, reqs)
58	require.NoError(t, err)
59	require.Nil(t, done)
60	require.True(t, took)
61	require.True(t, Handed(reqs[0]))
62	require.Len(t, rendered, 1)
63	require.Nil(t, rendered[0].CaptureIndicators)
64	require.Zero(t, rendered[0].Seq)
65	require.Equal(t, uint64(1), awaitCapture(t, replies).ID)
66	p.Close(r)
67	frame.Calls = nil
68}
69
70// Without indicator, or without requests, nothing is rendered and nothing
71// allocates: the feature is free while no capture is shown.
72func TestSubmitPlainIdleCostsNothing(t *testing.T) {
73	p := NewPipeline(context.Background(), make(chan ports.CaptureDone, 1))
74	defer p.Close(nil)
75	r := portsmocks.NewMockRenderer(t)
76	s := excludedTestScene()
77	reqs := []ports.CaptureRequest{{ID: 1}}
78	if took, _, err := p.SubmitPlain(r, s, nil, reqs); took || err != nil {
79		t.Fatalf("took %v err %v without an indicator", took, err)
80	}
81	if took, _, err := p.SubmitPlain(r, indicatorScene(), nil, nil); took || err != nil {
82		t.Fatalf("took %v err %v without requests", took, err)
83	}
84	if n := testing.AllocsPerRun(100, func() { _, _, _ = p.SubmitPlain(r, s, nil, reqs) }); n != 0 {
85		t.Fatalf("%v allocations", n)
86	}
87}
88
89// A scene that carries the indicator is still gated: once the session is
90// protected its plain frame is never copied, and the requests fail with the
91// security error, closed, exactly once.
92func TestSubmitPlainRejectsProtectedEpoch(t *testing.T) {
93	locked := ports.SecurityState{Generation: 3, Protected: true}
94	replies := make(chan ports.CaptureDone, 2)
95	p := NewPipeline(context.Background(), replies)
96	security := portsmocks.NewMockSessionSecurity(t)
97	security.EXPECT().Snapshot().Return(locked).Maybe()
98	p.Security = security
99	r := portsmocks.NewMockRenderer(t)
100	r.EXPECT().Render(mock.Anything, mock.Anything).Return(nil, nil).Once()
101	q := pipelineRequest(t, 1)
102	file := q.Dst.File
103	s := indicatorScene()
104	s.Security = locked
105	took, _, err := p.SubmitPlain(r, s, nil, []ports.CaptureRequest{q})
106	require.NoError(t, err)
107	require.True(t, took)
108	p.Close(r)
109	require.Len(t, replies, 1)
110	require.ErrorIs(t, (<-replies).Err, ErrSecurityState)
111	r.AssertNotCalled(t, "BeginCapture")
112	_, statErr := file.Stat()
113	require.ErrorIs(t, statErr, os.ErrClosed)
114}

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