1package presented
2
3import (
4 "reflect"
5 "testing"
6
7 portsmocks "github.com/bnema/neferwl/internal/mocks/ports"
8 "github.com/bnema/neferwl/internal/ports"
9 "github.com/stretchr/testify/mock"
10 "github.com/stretchr/testify/require"
11)
12
13func ptr(m map[ports.WindowID]uint64) uintptr { return reflect.ValueOf(m).Pointer() }
14
15// An unchanged publication reuses its immutable snapshots; a change clones,
16// and earlier reports keep their maps.
17func TestQueueSnapshotsCloneOnlyOnChange(t *testing.T) {
18 var q Queue
19 seen := map[ports.WindowID]uint64{1: 4, 5: 7}
20 reads := map[ports.WindowID]uint64{5: 2}
21 q.Push(ports.OutputPresented{Output: "A", Seen: seen, ChildReads: reads})
22 first := q.Unsent()[0]
23 require.Equal(t, seen, first.Seen)
24 require.Equal(t, reads, first.ChildReads)
25 seen[1] = 99 // the caller's scratch changes: the report does not
26 require.Equal(t, uint64(4), first.Seen[1])
27
28 seen[1] = 4
29 q.Push(ports.OutputPresented{Output: "A", Seen: seen, ChildReads: reads})
30 again := q.Unsent()[0]
31 require.Equal(t, ptr(first.Seen), ptr(again.Seen), "unchanged seen is shared")
32 require.Equal(t, ptr(first.ChildReads), ptr(again.ChildReads), "unchanged reads are shared")
33 allocs := testing.AllocsPerRun(100, func() { q.Push(ports.OutputPresented{Output: "A", Seen: seen, ChildReads: reads}) })
34 require.Zero(t, allocs, "unchanged publication allocates nothing")
35
36 reads[5] = 3
37 q.Push(ports.OutputPresented{Output: "A", Seen: seen, ChildReads: reads})
38 changed := q.Unsent()[0]
39 require.Equal(t, map[ports.WindowID]uint64{5: 3}, changed.ChildReads)
40 require.Equal(t, map[ports.WindowID]uint64{5: 2}, first.ChildReads, "sent snapshot stays immutable")
41 require.Equal(t, ptr(first.Seen), ptr(changed.Seen), "seen still unchanged")
42}
43
44// Flip-less reports fold into the newest one and keep its flip; flips queue.
45func TestQueueKeepsEveryFlip(t *testing.T) {
46 var q Queue
47 seen := map[ports.WindowID]uint64{1: 1}
48 q.Push(ports.OutputPresented{Flip: &ports.FlipInfo{Seq: 1}, Seen: seen})
49 q.Push(ports.OutputPresented{Seen: seen})
50 q.Push(ports.OutputPresented{Flip: &ports.FlipInfo{Seq: 2}, Seen: seen})
51 q.Push(ports.OutputPresented{Seen: map[ports.WindowID]uint64{1: 3}})
52 u := q.Unsent()
53 require.Len(t, u, 2)
54 require.Equal(t, uint64(1), u[0].Flip.Seq)
55 require.Equal(t, uint64(2), u[1].Flip.Seq, "a flip-less report keeps the queued flip")
56 require.Equal(t, uint64(3), u[1].Seen[1])
57}
58
59// Merge folds a flip into the next: counted as merged and its shown windows
60// carried, without mutating a shared Shows snapshot.
61func TestMergeCarriesShowsWithoutMutatingSharedSnapshot(t *testing.T) {
62 shared := map[ports.WindowID]uint64{2: 5}
63 old := &ports.FlipInfo{Seq: 1, Merged: 2, Shows: map[ports.WindowID]uint64{1: 4}}
64 next := &ports.FlipInfo{Seq: 2, Shows: shared}
65 Merge(old, next)
66 require.Equal(t, 3, next.Merged)
67 require.Equal(t, map[ports.WindowID]uint64{1: 4, 2: 5}, next.Shows)
68 require.Equal(t, map[ports.WindowID]uint64{2: 5}, shared, "shared snapshot untouched")
69}
70
71// The queue never merges flips: every flip of a turn is pushed to the sink.
72func TestQueueDoesNotBoundFlips(t *testing.T) {
73 var q Queue
74 for i := range 40 {
75 q.Push(ports.OutputPresented{Flip: &ports.FlipInfo{Seq: uint64(i + 1)}})
76 }
77 u := q.Unsent()
78 require.Len(t, u, 40)
79 require.Zero(t, u[0].Flip.Merged)
80 require.Equal(t, uint64(1), u[0].Flip.Seq)
81}
82
83func TestQueueFlushPushesInOrderAndEmpties(t *testing.T) {
84 var q Queue
85 q.Push(ports.OutputPresented{Flip: &ports.FlipInfo{Seq: 1}})
86 q.Push(ports.OutputPresented{Flip: &ports.FlipInfo{Seq: 2}})
87 var got []ports.OutputPresented
88 sink := portsmocks.NewMockPresentationSink(t)
89 sink.EXPECT().Push(mock.Anything).RunAndReturn(func(r ports.OutputPresented) { got = append(got, r) })
90 q.Flush(sink)
91 require.Zero(t, q.Len())
92 require.Len(t, got, 2)
93 require.Equal(t, uint64(1), got[0].Flip.Seq)
94 require.Equal(t, uint64(2), got[1].Flip.Seq)
95 q.Flush(sink) // empty: nothing more is pushed
96 require.Len(t, got, 2)
97
98 q.Push(ports.OutputPresented{Flip: &ports.FlipInfo{Seq: 3}})
99 q.Reset()
100 require.Zero(t, q.Len())
101}