feat/hints a471fa4

security_review_test.go RawHistoryFolder

1package core
2
3import (
4	"context"
5	"errors"
6	"sync/atomic"
7	"testing"
8	"time"
9
10	portsmocks "github.com/bnema/neferwl/internal/mocks/ports"
11	"github.com/bnema/neferwl/internal/ports"
12)
13
14func reviewReceive[T any](t *testing.T, ch <-chan T) T {
15	t.Helper()
16	select {
17	case v := <-ch:
18		return v
19	case <-time.After(time.Second):
20		t.Fatal("timed out")
21		var z T
22		return z
23	}
24}
25
26func TestInputOutputSwitchBackpressureKeepsAdmittedEpoch(t *testing.T) {
27	c, _, _ := hiddenCaptureCommands(t)
28	c.addScreen(ports.OutputInfo{Name: "B", Width: 200, Height: 100})
29	var state atomic.Value
30	state.Store(ports.SecurityState{})
31	gate := portsmocks.NewMockSessionSecurity(t)
32	var calls atomic.Int32
33	gate.EXPECT().Snapshot().RunAndReturn(func() ports.SecurityState {
34		v := state.Load().(ports.SecurityState)
35		if calls.Add(1) == 2 {
36			// The epoch changes right after the check that admits the command.
37			state.Store(ports.SecurityState{Generation: 2})
38		}
39		return v
40	})
41	c.opts.Security = gate
42	c.sent.outputs = ports.SetOutputs{Outputs: c.layout(), Focused: c.cur().name(), Off: c.offOutputs()}
43	commands := make(chan ports.ClientCommand, 8)
44	c.ch.Commands = commands
45	err := c.handleInput(context.Background(), ports.PointerMotion{X: 350, Y: 30})
46	cmd := reviewReceive(t, commands).(ports.SecurityCommand)
47	if cmd.State != (ports.SecurityState{}) {
48		t.Fatalf("old handler adopted new epoch: %+v", cmd)
49	}
50	if !errors.Is(err, errSecurityChanged) {
51		t.Fatalf("handler not aborted: %v", err)
52	}
53	if c.security != (ports.SecurityState{}) {
54		t.Fatal("publish adopted generation mid-input")
55	}
56	select {
57	case cmd := <-commands:
58		t.Fatalf("remaining old input command: %+v", cmd)
59	default:
60	}
61}
62
63func TestExactEpochAdmissionPreservesProducerModifierState(t *testing.T) {
64	c, _, _ := hiddenCaptureCommands(t)
65	state := ports.SecurityState{Generation: 2}
66	gate := portsmocks.NewMockSessionSecurity(t)
67	gate.EXPECT().Snapshot().Return(state)
68	c.opts.Security = gate
69	c.syncSecurity()
70	key := ports.KeyEvent{Keycode: 58, Keysym: "Control_L", Pressed: true, Mods: ports.ModCtrl, State: ports.ModState{Depressed: 1 << 19, Latched: 1 << 21, Locked: 1 << 23, Group: 2}}
71	ev, ok := c.admitInput(ports.SecurityInput{State: state, Event: key})
72	if !ok || ev.(ports.KeyEvent) != key {
73		t.Fatalf("producer state reinterpreted: %+v", ev)
74	}
75	if _, ok := c.admitInput(ports.SecurityInput{State: ports.SecurityState{Generation: 1}, Event: key}); ok {
76		t.Fatal("stale producer epoch admitted")
77	}
78}
79
80func TestSlotsPendingBackpressureSuppressesSpawnAndDefersSlots(t *testing.T) {
81	c, _, _ := hiddenCaptureCommands(t)
82	key := slotKey{workspace: "dev", index: 1}
83	c.slots[key] = &slotState{argv: []string{"terminal"}}
84	c.toSpawn = []slotKey{key}
85	var state atomic.Value
86	state.Store(ports.SecurityState{})
87	gate := portsmocks.NewMockSessionSecurity(t)
88	var calls atomic.Int32
89	gate.EXPECT().Snapshot().RunAndReturn(func() ports.SecurityState {
90		v := state.Load().(ports.SecurityState)
91		if calls.Add(1) == 2 {
92			// The gate locks right after the check that admits the slot.
93			state.Store(ports.SecurityState{Generation: 1, Protected: true})
94		}
95		return v
96	})
97	c.opts.Security = gate
98	commands := make(chan ports.ClientCommand, 8)
99	c.ch.Commands = commands
100	spawn := make(chan ports.SpawnRequest, 2)
101	c.ch.Spawn = spawn
102	err := c.spawnSlots(context.Background(), false)
103	cmd := reviewReceive(t, commands).(ports.SecurityCommand)
104	if _, ok := cmd.Command.(ports.SlotsPending); !ok {
105		t.Fatal(cmd)
106	}
107	if err != nil {
108		t.Fatal(err)
109	}
110	if len(spawn) != 0 || len(c.toSpawn) != 1 || c.placement.slotPending(key) {
111		t.Fatal("spawn leaked or deferred slot lost")
112	}
113	// Retry after unlock, with a real buffered command sink.
114	c.ch.Commands = make(chan ports.ClientCommand, 8)
115	state.Store(ports.SecurityState{Generation: 2})
116	c.syncSecurity()
117	if err := c.spawnSlots(context.Background(), false); err != nil {
118		t.Fatal(err)
119	}
120	if len(spawn) != 1 || len(c.toSpawn) != 0 {
121		t.Fatal("deferred slot did not retry")
122	}
123}
124
125func TestTerminalPendingBackpressureSuppressesSpawnAndRestoresRetry(t *testing.T) {
126	c, _, _ := hiddenCaptureCommands(t)
127	c.cur().mon.RemoveWindow(1)
128	c.cur().mon.RemoveWindow(2)
129	c.opts.Terminal = true
130	c.cfg.Terminal.AutoOpen = "always"
131	c.cfg.Terminal.Command = []string{"terminal"}
132	var state atomic.Value
133	state.Store(ports.SecurityState{})
134	gate := portsmocks.NewMockSessionSecurity(t)
135	var calls atomic.Int32
136	gate.EXPECT().Snapshot().RunAndReturn(func() ports.SecurityState {
137		v := state.Load().(ports.SecurityState)
138		if calls.Add(1) == 1 {
139			// The gate locks right after the check that admits the spawn.
140			state.Store(ports.SecurityState{Generation: 1, Protected: true})
141		}
142		return v
143	})
144	c.opts.Security = gate
145	commands := make(chan ports.ClientCommand, 8)
146	c.ch.Commands = commands
147	spawn := make(chan ports.SpawnRequest, 1)
148	c.ch.Spawn = spawn
149	err := c.spawnEmpty(context.Background())
150	reviewReceive(t, commands)
151	if err != nil {
152		t.Fatal(err)
153	}
154	if len(spawn) != 0 || c.placement.anyPending() {
155		t.Fatal("terminal spawn/claim crossed lock")
156	}
157}
158
159// The pre-existing slot test can observe a startup placeholder snapshot: a
160// scene send happens before the real-output workspace publication. Reproduce
161// the empty snapshot contract without intentionally panicking a test runner.
162func TestStartupWorkspaceSnapshotHasNoOutputBeforeOutputAdded(t *testing.T) {
163	var cfg ports.Config
164	cfg.Keyboard.CmdKey = "super"
165	cfg.Layout.MaxColumns = 2
166	workspaces := make(chan ports.Workspaces, 1)
167	c, err := New(cfg, Channels{Scenes: make(chan []ports.Scene, 1), Workspaces: workspaces}, Options{})
168	if err != nil {
169		t.Fatal(err)
170	}
171	c.publishWorkspaces()
172	initial := <-workspaces
173	if len(initial.Outputs) != 0 {
174		t.Fatal(initial)
175	}
176	c.addScreen(ports.OutputInfo{Name: "A", Width: 100, Height: 80})
177	c.publishWorkspaces()
178	if next := <-workspaces; len(next.Outputs) != 1 {
179		t.Fatal(next)
180	}
181}

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 24

Tags 10

↑↓ moveenter openesc close