1package daemon
2
3import (
4 "bytes"
5 "testing"
6
7 "github.com/stretchr/testify/require"
8
9 vt "github.com/bnema/vev-vt"
10 renderer "github.com/bnema/vev-vt/ansi"
11)
12
13func TestSnapshotChunkCacheIsScopedToNamedSession(t *testing.T) {
14 d := New(nil, nil, nil)
15 a := newSnapshotTestSession(t, "a", false, "/a")
16 b := newSnapshotTestSession(t, "b", false, "/b")
17 a.snapshotChunkCache = newSnapshotChunkCache(snapshotChunkCacheLimit)
18 b.snapshotChunkCache = newSnapshotChunkCache(snapshotChunkCacheLimit)
19
20 // Eleven maximum-size chunks exceed the per-session 16 MiB cache budget.
21 // The cache keeps its warmed working set rather than admitting every
22 // oldest-to-newest scan miss.
23 aHistory := snapshotCacheHistory(t, 11, 'a')
24 aCapture := snapshotCacheCapture(a, aHistory, 1)
25 aFirst, err := d.incrementalPublication(aCapture)
26 require.NoError(t, err)
27 require.NotEmpty(t, aFirst.Objects)
28 require.LessOrEqual(t, a.snapshotChunkCache.used, a.snapshotChunkCache.limit)
29 require.LessOrEqual(t, a.snapshotChunkCache.limit, snapshotChunkCacheLimit)
30 require.Contains(t, a.snapshotChunkCache.byPtr, aHistory.Chunk(0), "session A should retain its warmed entry")
31
32 bHistory := snapshotCacheHistory(t, 1, 'b')
33 bCapture := snapshotCacheCapture(b, bHistory, 1)
34 _, err = d.incrementalPublication(bCapture)
35 require.NoError(t, err)
36 require.Contains(t, b.snapshotChunkCache.byPtr, bHistory.Chunk(0))
37
38 inFlightBytes := append([]byte(nil), aFirst.Objects[0].Data...)
39 aSecond := snapshotCacheCapture(a, snapshotCacheHistory(t, 1, 'z'), 2)
40 _, err = d.incrementalPublication(aSecond)
41 require.NoError(t, err)
42 require.True(t, bytes.Equal(inFlightBytes, aFirst.Objects[0].Data), "cache pruning must not mutate queued publication bytes")
43 require.LessOrEqual(t, a.snapshotChunkCache.used, snapshotChunkCacheLimit)
44
45 _, err = d.incrementalPublication(snapshotCacheCapture(b, bHistory, 2))
46 require.NoError(t, err)
47 require.Contains(t, b.snapshotChunkCache.byPtr, bHistory.Chunk(0), "session A eviction must not affect session B")
48 require.LessOrEqual(t, b.snapshotChunkCache.used, snapshotChunkCacheLimit)
49}
50
51func snapshotCacheHistory(t *testing.T, chunks int, first rune) vt.HistorySnapshotView {
52 t.Helper()
53 history := vt.NewHistory(vt.HistoryConfig{MaxRows: chunks * 256, ChunkRows: 256})
54 for row := 0; row < chunks*256; row++ {
55 cells := make([]renderer.Cell, 160)
56 for col := range cells {
57 cells[col] = renderer.Cell{Rune: first + rune((row+col)%26)}
58 }
59 appendHistoryRow(t, history, cells)
60 }
61 return history.SnapshotView()
62}
63
64func snapshotCacheCapture(sess *session, history vt.HistorySnapshotView, generation uint64) *snapshotCapture {
65 return &snapshotCapture{
66 session: sess,
67 name: sess.name,
68 incarnation: sess.incarnation,
69 generation: generation,
70 tabs: []snapshotCaptureTab{{
71 stableID: "tab",
72 cols: 1,
73 rows: 1,
74 panes: []snapshotCapturePane{{
75 id: "pane",
76 stableID: "pane",
77 sealed: history,
78 tail: history.Tail(),
79 transcript: vt.NewScreen(1, 1).RecoveryTranscriptSnapshot(),
80 }},
81 }},
82 }
83}