v0.8.0 446d1e3

security_test.go RawHistoryFolder

1package capture
2
3import (
4	"context"
5	"errors"
6	"fmt"
7	"image"
8	"os"
9	"sync/atomic"
10	"testing"
11
12	portsmocks "github.com/bnema/neferwl/internal/mocks/ports"
13	"github.com/bnema/neferwl/internal/ports"
14	"github.com/stretchr/testify/mock"
15	"github.com/stretchr/testify/require"
16)
17
18func TestSubmitScopedRejectsChangedAndProtectedEpoch(t *testing.T) {
19	for _, state := range []ports.SecurityState{{Generation: 4}, {Generation: 3, Protected: true}} {
20		t.Run(fmt.Sprintf("generation-%d-protected-%v", state.Generation, state.Protected), func(t *testing.T) {
21			replies := make(chan ports.CaptureDone, 2)
22			p := NewPipeline(context.Background(), replies)
23			security := portsmocks.NewMockSessionSecurity(t)
24			security.EXPECT().Snapshot().Return(state)
25			p.Security = security
26			r := portsmocks.NewMockRenderer(t)
27			q := pipelineRequest(t, 1)
28			file := q.Dst.File
29			p.SubmitScoped(ports.SecurityState{Generation: 3}, r, []ports.CaptureRequest{q})
30			p.Close(r)
31			require.Len(t, replies, 1)
32			require.ErrorIs(t, (<-replies).Err, ErrSecurityState)
33			r.AssertNotCalled(t, "BeginCapture")
34			_, err := file.Stat()
35			require.ErrorIs(t, err, os.ErrClosed)
36		})
37	}
38}
39
40func TestSubmitHiddenEngageDuringRenderRejectsCopyRetainsReads(t *testing.T) {
41	replies := make(chan ports.CaptureDone, 4)
42	p := NewPipeline(context.Background(), replies)
43	security := portsmocks.NewMockSessionSecurity(t)
44	var engaged atomic.Bool
45	security.EXPECT().Snapshot().RunAndReturn(func() ports.SecurityState {
46		if engaged.Load() {
47			return ports.SecurityState{Generation: 1, Protected: true}
48		}
49		return ports.SecurityState{}
50	})
51	p.Security = security
52	child := portsmocks.NewMockRenderer(t)
53	f, w, err := os.Pipe()
54	require.NoError(t, err)
55	t.Cleanup(func() { f.Close(); w.Close() })
56	child.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(ports.Scene, map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
57		engaged.Store(true)
58		return f, nil
59	}).Once()
60	child.EXPECT().Close().Return().Once()
61	p.EnableOffscreen(func(int, int) (ports.Renderer, error) { return child, nil })
62	s := hiddenScene()
63	s.CaptureScene.Windows = []ports.SceneWindow{{ID: 5}}
64	surfaces := map[ports.WindowID]ports.SurfaceContent{5: {ID: 5, Seq: 8}}
65	reqs := []ports.CaptureRequest{hiddenRequest(t, 1, image.Rect(0, 0, 200, 100)), hiddenRequest(t, 2, image.Rect(0, 0, 200, 100))}
66	files := []*os.File{reqs[0].Dst.File, reqs[1].Dst.File}
67	p.SubmitHidden(s, surfaces, reqs)
68	child.AssertNotCalled(t, "BeginCapture")
69	require.True(t, p.HiddenReading(), "rejected native capture still owns GPU reads")
70	seen, reads, limited := p.CapHiddenSeen(map[ports.WindowID]uint64{5: 9})
71	require.True(t, limited)
72	require.Equal(t, uint64(8), seen[5])
73	require.Equal(t, uint64(8), reads[5])
74	for range 2 {
75		reply := awaitCapture(t, replies)
76		require.ErrorIs(t, reply.Err, ErrSecurityState)
77	}
78	for _, file := range files {
79		_, err := file.Stat()
80		require.ErrorIs(t, err, os.ErrClosed)
81	}
82	require.Empty(t, replies, "exactly one failure reply for each request credit")
83	_, err = f.Stat()
84	require.NoError(t, err, "render fence stays owned until signalling")
85	_, err = w.Write([]byte{1})
86	require.NoError(t, err)
87	require.False(t, p.HiddenReading())
88	_, err = f.Stat()
89	require.True(t, errors.Is(err, os.ErrClosed), "signalled child fence closes")
90	p.Close(nil)
91	require.Empty(t, replies, "closing rejected pipeline creates no duplicate reply")
92}

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