readiness_test.go RawHistoryFolder

1package sessionlock
2
3import (
4	"fmt"
5	"testing"
6
7	"github.com/bnema/neferwl/internal/ports"
8)
9
10func lockProof(generation ports.LockGeneration, instance ports.OutputInstance) ports.OutputProtection {
11	return ports.OutputProtection{Generation: generation, Instance: instance, Kind: ports.ProtectionProtectedFrame}
12}
13
14func requireLockAccepted(t *testing.T, accepted bool) {
15	t.Helper()
16	if !accepted {
17		t.Fatal("valid operation rejected")
18	}
19}
20
21func requireLockReady(t *testing.T, r *Readiness, want bool) {
22	t.Helper()
23	if got := r.Ready(); got != want {
24		t.Fatalf("Ready() = %v, want %v; ledger %+v", got, want, r)
25	}
26}
27
28func TestLockReadinessZeroValue(t *testing.T) {
29	var r Readiness
30	requireLockReady(t, &r, false)
31	if r.Begin(0) || r.End(0) || r.Add(0) || r.Remove(0) || r.Remove(99) ||
32		r.Record(ports.OutputProtection{}) || r.Record(lockProof(1, 1)) ||
33		r.BackendBarrier(0) || r.BackendBarrier(1) || r.CaptureBarrier(0) || r.CaptureBarrier(1) {
34		t.Fatal("zero/disengaged ledger accepted invalid input")
35	}
36	requireLockReady(t, &r, false)
37}
38
39func TestLockReadinessEmptyInventoryNeedsBothBarriers(t *testing.T) {
40	for _, backendFirst := range []bool{false, true} {
41		t.Run(fmt.Sprintf("backend-first=%v", backendFirst), func(t *testing.T) {
42			var r Readiness
43			requireLockAccepted(t, r.Begin(7))
44			requireLockReady(t, &r, false)
45			if backendFirst {
46				requireLockAccepted(t, r.BackendBarrier(7))
47			} else {
48				requireLockAccepted(t, r.CaptureBarrier(7))
49			}
50			requireLockReady(t, &r, false)
51			if backendFirst {
52				requireLockAccepted(t, r.CaptureBarrier(7))
53			} else {
54				requireLockAccepted(t, r.BackendBarrier(7))
55			}
56			requireLockReady(t, &r, true)
57		})
58	}
59}
60
61func TestLockReadinessEveryPrerequisiteInAnyOrder(t *testing.T) {
62	// Two output proofs and two independent barriers: all 24 arrival orders.
63	for a := range 4 {
64		for b := range 4 {
65			for c := range 4 {
66				for d := range 4 {
67					order := [4]int{a, b, c, d}
68					if a == b || a == c || a == d || b == c || b == d || c == d {
69						continue
70					}
71					t.Run(fmt.Sprint(order), func(t *testing.T) {
72						var r Readiness
73						requireLockAccepted(t, r.Add(11))
74						requireLockAccepted(t, r.Add(12))
75						requireLockAccepted(t, r.Begin(5))
76						requireLockReady(t, &r, false)
77						for i, step := range order {
78							var accepted bool
79							switch step {
80							case 0:
81								accepted = r.Record(lockProof(5, 11))
82							case 1:
83								accepted = r.Record(ports.OutputProtection{Generation: 5, Instance: 12, Kind: ports.ProtectionInactiveOutput})
84							case 2:
85								accepted = r.BackendBarrier(5)
86							case 3:
87								accepted = r.CaptureBarrier(5)
88							}
89							requireLockAccepted(t, accepted)
90							requireLockReady(t, &r, i == 3)
91						}
92						// Duplicate evidence is idempotent.
93						requireLockAccepted(t, r.Record(lockProof(5, 11)))
94						requireLockAccepted(t, r.BackendBarrier(5))
95						requireLockAccepted(t, r.CaptureBarrier(5))
96						requireLockReady(t, &r, true)
97					})
98				}
99			}
100		}
101	}
102}
103
104func TestLockReadinessGenerationFencing(t *testing.T) {
105	var r Readiness
106	requireLockAccepted(t, r.Add(1))
107	requireLockAccepted(t, r.Begin(10))
108	requireLockAccepted(t, r.Record(lockProof(10, 1)))
109	requireLockAccepted(t, r.BackendBarrier(10))
110	requireLockAccepted(t, r.CaptureBarrier(10))
111	requireLockReady(t, &r, true)
112	for _, generation := range []ports.LockGeneration{0, 9, 10} {
113		if r.Begin(generation) {
114			t.Fatalf("accepted non-increasing generation %d", generation)
115		}
116		requireLockReady(t, &r, true)
117	}
118
119	// A newer Begin while engaged clears both barriers and every proof.
120	requireLockAccepted(t, r.Begin(11))
121	requireLockReady(t, &r, false)
122	for _, generation := range []ports.LockGeneration{0, 9, 10, 12} {
123		if r.Record(lockProof(generation, 1)) || r.BackendBarrier(generation) || r.CaptureBarrier(generation) || r.End(generation) {
124			t.Fatalf("accepted wrong-generation evidence/End for %d", generation)
125		}
126	}
127	requireLockAccepted(t, r.BackendBarrier(11))
128	requireLockAccepted(t, r.CaptureBarrier(11))
129	requireLockReady(t, &r, false) // old proof was cleared
130	requireLockAccepted(t, r.Record(lockProof(11, 1)))
131	requireLockReady(t, &r, true)
132	requireLockAccepted(t, r.End(11))
133	requireLockReady(t, &r, false)
134	if r.Record(lockProof(11, 1)) || r.BackendBarrier(11) || r.CaptureBarrier(11) || r.End(11) || r.Begin(11) || r.Begin(10) {
135		t.Fatal("ended round accepted late evidence or reused generation")
136	}
137
138	requireLockAccepted(t, r.Begin(13)) // intervening gate release may consume 12
139	if r.End(11) || r.Record(lockProof(11, 1)) || r.BackendBarrier(11) || r.CaptureBarrier(11) {
140		t.Fatal("late old round affected newer round")
141	}
142	requireLockAccepted(t, r.Record(lockProof(13, 1)))
143	requireLockReady(t, &r, false) // neither old barrier survived End/Begin
144	requireLockAccepted(t, r.BackendBarrier(13))
145	requireLockReady(t, &r, false)
146	requireLockAccepted(t, r.CaptureBarrier(13))
147	requireLockReady(t, &r, true)
148}
149
150func TestLockReadinessGenerationExhaustionDoesNotWrap(t *testing.T) {
151	var r Readiness
152	maxGeneration := ports.LockGeneration(^uint64(0))
153	requireLockAccepted(t, r.Begin(maxGeneration))
154	requireLockAccepted(t, r.End(maxGeneration))
155	for _, generation := range []ports.LockGeneration{0, 1, maxGeneration} {
156		if r.Begin(generation) {
157			t.Fatalf("accepted wrapped/reused generation %d", generation)
158		}
159	}
160	requireLockReady(t, &r, false)
161}
162
163func TestLockReadinessInvalidProofsDoNotCountOrEraseEvidence(t *testing.T) {
164	invalid := []ports.OutputProtection{
165		{},
166		{Generation: 1, Instance: 1},
167		{Generation: 1, Instance: 1, Kind: "submitted-frame"},
168		{Generation: 1, Instance: 1, Kind: "error-gone"},
169		{Generation: 1, Instance: 0, Kind: ports.ProtectionProtectedFrame},
170		{Generation: 1, Instance: 99, Kind: ports.ProtectionProtectedFrame},
171		{Generation: 0, Instance: 1, Kind: ports.ProtectionProtectedFrame},
172		{Generation: 2, Instance: 1, Kind: ports.ProtectionProtectedFrame},
173	}
174	var r Readiness
175	requireLockAccepted(t, r.Add(1))
176	requireLockAccepted(t, r.Begin(1))
177	requireLockAccepted(t, r.BackendBarrier(1))
178	requireLockAccepted(t, r.CaptureBarrier(1))
179	for _, proof := range invalid {
180		if r.Record(proof) {
181			t.Fatalf("accepted invalid proof %+v", proof)
182		}
183		requireLockReady(t, &r, false)
184	}
185	requireLockAccepted(t, r.Record(lockProof(1, 1)))
186	for _, proof := range invalid {
187		if r.Record(proof) {
188			t.Fatalf("accepted invalid proof %+v", proof)
189		}
190		requireLockReady(t, &r, true)
191	}
192}
193
194func TestLockReadinessOutputInventoryChanges(t *testing.T) {
195	var r Readiness
196	requireLockAccepted(t, r.Add(1))
197	requireLockAccepted(t, r.Add(2))
198	requireLockAccepted(t, r.Remove(2)) // registration and retirement before Begin
199	if r.Add(2) || r.Remove(2) || r.Add(1) {
200		t.Fatal("accepted duplicate or retired lifetime")
201	}
202	requireLockAccepted(t, r.Begin(1))
203	requireLockAccepted(t, r.Add(3)) // new output before Ready
204	requireLockAccepted(t, r.Record(lockProof(1, 1)))
205	requireLockAccepted(t, r.BackendBarrier(1))
206	requireLockAccepted(t, r.CaptureBarrier(1))
207	requireLockReady(t, &r, false)
208	requireLockAccepted(t, r.Record(lockProof(1, 3)))
209	requireLockReady(t, &r, true)
210	if r.Add(0) || r.Add(3) || r.Remove(99) {
211		t.Fatal("accepted invalid inventory operation")
212	}
213	requireLockReady(t, &r, true)    // duplicate registration did not erase proof
214	requireLockAccepted(t, r.Add(4)) // hotplug after Ready must revoke it
215	requireLockReady(t, &r, false)
216	requireLockAccepted(t, r.Remove(4)) // caller-confirmed retired lifetime
217	requireLockReady(t, &r, true)
218
219	// The same output name can return, but the new lifetime is instance 5.
220	requireLockAccepted(t, r.Remove(3))
221	requireLockAccepted(t, r.Add(5))
222	if r.Record(lockProof(1, 3)) || r.Add(3) {
223		t.Fatal("replacement accepted old-lifetime evidence")
224	}
225	requireLockReady(t, &r, false)
226	requireLockAccepted(t, r.Record(lockProof(1, 5)))
227	requireLockReady(t, &r, true)
228
229	requireLockAccepted(t, r.End(1))
230	requireLockAccepted(t, r.Add(6)) // changes after End join next snapshot
231	requireLockAccepted(t, r.Remove(1))
232	requireLockAccepted(t, r.Begin(2))
233	requireLockAccepted(t, r.BackendBarrier(2))
234	requireLockAccepted(t, r.CaptureBarrier(2))
235	requireLockAccepted(t, r.Record(lockProof(2, 5)))
236	requireLockReady(t, &r, false)
237	requireLockAccepted(t, r.Record(lockProof(2, 6)))
238	requireLockReady(t, &r, true)
239	if r.Add(1) || r.Add(2) || r.Add(3) || r.Add(4) {
240		t.Fatal("retired lifetime reused across lock rounds")
241	}
242}
243
244func TestLockReadinessInvalidationRequiresFreshProof(t *testing.T) {
245	var r Readiness
246	requireLockAccepted(t, r.Add(1))
247	requireLockAccepted(t, r.Begin(7))
248	requireLockAccepted(t, r.Record(lockProof(7, 1)))
249	requireLockAccepted(t, r.BackendBarrier(7))
250	requireLockAccepted(t, r.CaptureBarrier(7))
251	requireLockReady(t, &r, true)
252	if r.Invalidate(6, 1) || r.Invalidate(7, 2) {
253		t.Fatal("invalid invalidation accepted")
254	}
255	requireLockReady(t, &r, true)
256	requireLockAccepted(t, r.Invalidate(7, 1))
257	requireLockReady(t, &r, false)
258	requireLockAccepted(t, r.Record(lockProof(7, 1)))
259	requireLockReady(t, &r, true)
260	requireLockAccepted(t, r.End(7))
261	if r.Invalidate(7, 1) {
262		t.Fatal("ended invalidation accepted")
263	}
264}
265
266func TestLockReadinessInvalidationIsolatedAndRetired(t *testing.T) {
267	var r Readiness
268	requireLockAccepted(t, r.Add(1))
269	requireLockAccepted(t, r.Add(2))
270	requireLockAccepted(t, r.Add(3))
271	requireLockAccepted(t, r.Remove(3))
272	requireLockAccepted(t, r.Begin(7))
273	requireLockAccepted(t, r.Record(lockProof(7, 1)))
274	requireLockAccepted(t, r.Record(lockProof(7, 2)))
275	requireLockAccepted(t, r.BackendBarrier(7))
276	requireLockAccepted(t, r.CaptureBarrier(7))
277	if r.Invalidate(8, 1) || r.Invalidate(7, 3) {
278		t.Fatal("future or retired invalidation accepted")
279	}
280	requireLockReady(t, &r, true)
281	requireLockAccepted(t, r.Invalidate(7, 1))
282	requireLockReady(t, &r, false)
283	requireLockAccepted(t, r.Record(lockProof(7, 1)))
284	requireLockReady(t, &r, true) // output 2 and both barriers retained
285}
286
287func TestLockReadinessRemovingLastOutputCannotReplaceBackendACK(t *testing.T) {
288	var r Readiness
289	requireLockAccepted(t, r.Add(1))
290	requireLockAccepted(t, r.Begin(1))
291	requireLockAccepted(t, r.CaptureBarrier(1))
292	requireLockAccepted(t, r.Remove(1))
293	requireLockReady(t, &r, false)
294	requireLockAccepted(t, r.BackendBarrier(1))
295	requireLockReady(t, &r, true)
296}

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