1package daemon
2
3import (
4 "bytes"
5 "sync"
6 "testing"
7
8 "github.com/stretchr/testify/require"
9
10 "github.com/bnema/vev/internal/domain"
11 "github.com/bnema/vev/internal/ports"
12 recoveryusecase "github.com/bnema/vev/internal/usecase/recovery"
13)
14
15type syncCountingCatalogue struct {
16 *durableRecoveryCatalogue
17
18 muSync sync.Mutex
19 syncs int
20}
21
22func (c *syncCountingCatalogue) Sync() error {
23 c.muSync.Lock()
24 defer c.muSync.Unlock()
25 c.syncs++
26 return nil
27}
28
29func (c *syncCountingCatalogue) syncCount() int {
30 c.muSync.Lock()
31 defer c.muSync.Unlock()
32 return c.syncs
33}
34
35func (c *syncCountingCatalogue) Create(record domain.CatalogueRecord) error {
36 c.mu.Lock()
37 if _, exists := c.records[record.Name]; exists {
38 c.mu.Unlock()
39 return errSessionNameInUse
40 }
41 c.records[record.Name] = record
42 c.mu.Unlock()
43 return c.Sync()
44}
45
46func TestMetadataWritesAreDeferred(t *testing.T) {
47 t.Parallel()
48 sess := newSnapshotTestSession(t, "work", false, "/work")
49 sess.mu.Lock()
50 record := sess.persistRecordLocked(sess.createdAt)
51 sess.mu.Unlock()
52 cat := &syncCountingCatalogue{durableRecoveryCatalogue: newDurableRecoveryCatalogue([]domain.CatalogueRecord{record})}
53 d := newTestDaemon(t, nil, stubClock{})
54 d.catalogue = cat
55 d.persistEnabled = true
56 d.sessions[sess.id] = sess
57
58 syncsBefore := cat.syncCount()
59 require.NoError(t, d.renameTab(sess, sess.tabs[0], "build"))
60 d.touchMRU(sess)
61 require.Equal(t, syncsBefore, cat.syncCount(), "metadata updates must not force a durable write")
62
63 require.NoError(t, d.flushCatalogue())
64 require.Greater(t, cat.syncCount(), syncsBefore)
65
66 got, ok, err := cat.Record("work")
67 require.NoError(t, err)
68 require.True(t, ok)
69 require.Equal(t, []string{"build"}, got.TabNames)
70}
71
72func TestIdentityWritesAreSynchronous(t *testing.T) {
73 t.Parallel()
74 sess := newSnapshotTestSession(t, "0", true, "/work")
75 cat := &syncCountingCatalogue{durableRecoveryCatalogue: newDurableRecoveryCatalogue(nil)}
76 d := newTestDaemon(t, nil, stubClock{})
77 d.catalogue = cat
78 d.persistEnabled = true
79 d.recovery = recoveryusecase.NewCoordinator(cat, nil, bytes.NewReader(bytes.Repeat([]byte{1}, 16)))
80 d.sessions[sess.id] = sess
81
82 syncsBefore := cat.syncCount()
83 require.NoError(t, d.renameSession(sess, "work"))
84 require.Greater(t, cat.syncCount(), syncsBefore, "creating a named session must be durable before it is exposed")
85
86 d.mu.Lock()
87 published := d.findByNameLocked("work")
88 d.mu.Unlock()
89 require.Same(t, sess, published)
90 sess.mu.Lock()
91 require.False(t, sess.ephemeral)
92 sess.mu.Unlock()
93}
94
95var _ ports.Catalogue = (*syncCountingCatalogue)(nil)