X-Git-Url: https://git.giacomolli.uk/onechat/blobdiff_plain/2532718d5288e1a2586fe5716cc41bb2a0210e16..59e1537e1969f7c109084497287d729edbedba04:/syncer_test.go diff --git a/syncer_test.go b/syncer_test.go index 28a2597..16f3345 100644 --- a/syncer_test.go +++ b/syncer_test.go @@ -15,12 +15,12 @@ import ( "github.com/stretchr/testify/require" ) -// childDirVariable names the chat directory of the writer child process of -// TestKilledWriterLosesNoLoggedMessage. -const childDirVariable = "ONECHAT_TEST_CHILD_DIR" +// syncerChildDirVariable names the chat directory of the writer child process +// of TestKilledWriterLosesNoLoggedMessage. +const syncerChildDirVariable = "ONECHAT_TEST_CHILD_DIR" func TestFlushWaitsForASyncThatStartsAfterTheWrite(t *testing.T) { - gate := newGatedSync() + gate := newSyncerGatedSync() s := newSyncer(gate.Sync) s.Written() gate.WaitStarted(t, 1) @@ -28,6 +28,7 @@ func TestFlushWaitsForASyncThatStartsAfterTheWrite(t *testing.T) { // This write comes during the first sync, so that sync does not cover // it. s.Written() + flushed := make(chan error) go func() { flushed <- s.Flush(t.Context()) @@ -35,6 +36,7 @@ func TestFlushWaitsForASyncThatStartsAfterTheWrite(t *testing.T) { gate.Release() gate.WaitStarted(t, 2) + select { case err := <-flushed: t.Fatalf("flush returned before the second sync: %v", err) @@ -48,10 +50,11 @@ func TestFlushWaitsForASyncThatStartsAfterTheWrite(t *testing.T) { } func TestOneSyncCoversTheWritesDuringTheSyncBeforeIt(t *testing.T) { - gate := newGatedSync() + gate := newSyncerGatedSync() s := newSyncer(gate.Sync) s.Written() gate.WaitStarted(t, 1) + for range 10 { s.Written() } @@ -65,7 +68,7 @@ func TestOneSyncCoversTheWritesDuringTheSyncBeforeIt(t *testing.T) { } func TestFlushWithoutWritesNeedsNoSync(t *testing.T) { - gate := newGatedSync() + gate := newSyncerGatedSync() s := newSyncer(gate.Sync) require.NoError(t, s.Flush(t.Context())) require.NoError(t, s.Close()) @@ -86,7 +89,7 @@ func TestAFailedSyncFailsEachLaterFlush(t *testing.T) { } func TestMemoryFlushSyncsTheChat(t *testing.T) { - memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, newFakeSummarizer()) + memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, newFakeSummarizer(), discardLog()) require.NoError(t, err) _, err = memory.Append("tool", "shell: ls") @@ -98,16 +101,17 @@ func TestMemoryFlushSyncsTheChat(t *testing.T) { func TestKilledWriterLosesNoLoggedMessage(t *testing.T) { root := t.TempDir() for round := range 3 { - acknowledged := runKilledWriter(t, root, 200+100*round) + acknowledged := syncerRunKilledWriter(t, root, 200+100*round) - memory, err := openMemory(t.Context(), root, time.Now, newFakeSummarizer()) + memory, err := openMemory(t.Context(), root, time.Now, newFakeSummarizer(), discardLog()) require.NoError(t, err) require.GreaterOrEqual(t, memory.log.Len(), acknowledged) + for id := range memory.log.Len() { message, err := memory.log.Message(id) require.NoError(t, err) - require.Equal(t, writerKind(id), message.Kind) - require.Equal(t, writerText(id), message.Text) + require.Equal(t, syncerWriterKind(id), message.Kind) + require.Equal(t, syncerWriterText(id), message.Text) } require.NoError(t, memory.Close()) @@ -115,19 +119,20 @@ func TestKilledWriterLosesNoLoggedMessage(t *testing.T) { } // TestWriterChild is the child process of TestKilledWriterLosesNoLoggedMessage. -// It appends messages to the chat in the directory of childDirVariable and -// prints the ID of each message after its Append returns, until it is killed. +// It appends messages to the chat in the directory of syncerChildDirVariable +// and prints the ID of each message after its Append returns, until it is +// killed. func TestWriterChild(t *testing.T) { - root := os.Getenv(childDirVariable) + root := os.Getenv(syncerChildDirVariable) if root == "" { t.Skip("runs only as a child process") } - memory, err := openMemory(t.Context(), root, time.Now, newFakeSummarizer()) + memory, err := openMemory(t.Context(), root, time.Now, newFakeSummarizer(), discardLog()) require.NoError(t, err) for id := memory.log.Len(); ; id++ { - _, err = memory.Append(writerKind(id), writerText(id)) + _, err = memory.Append(syncerWriterKind(id), syncerWriterText(id)) require.NoError(t, err) _, err = fmt.Println(id) @@ -135,34 +140,29 @@ func TestWriterChild(t *testing.T) { } } -// gatedSync is a sync function that blocks until the test releases it. -type gatedSync struct { +// SyncerGatedSync is a sync function that blocks until the test releases it. +type SyncerGatedSync struct { mutex sync.Mutex count int started chan struct{} release chan struct{} } -func newGatedSync() *gatedSync { - return &gatedSync{ - started: make(chan struct{}, 100), - release: make(chan struct{}), - } -} - -func (g *gatedSync) Sync() error { +func (g *SyncerGatedSync) Sync() error { g.mutex.Lock() + g.count++ g.mutex.Unlock() g.started <- struct{}{} + <-g.release return nil } // WaitStarted waits until sync number n has started. -func (g *gatedSync) WaitStarted(t *testing.T, n int) { +func (g *SyncerGatedSync) WaitStarted(t *testing.T, n int) { t.Helper() require.Eventually(t, func() bool { @@ -171,26 +171,34 @@ func (g *gatedSync) WaitStarted(t *testing.T, n int) { } // Release lets the running sync end. -func (g *gatedSync) Release() { +func (g *SyncerGatedSync) Release() { <-g.started + g.release <- struct{}{} } -func (g *gatedSync) Count() int { +func (g *SyncerGatedSync) Count() int { g.mutex.Lock() defer g.mutex.Unlock() return g.count } -// runKilledWriter starts the writer child on the chat in root, kills it with -// SIGKILL after it acknowledged count messages, and returns the number of +func newSyncerGatedSync() *SyncerGatedSync { + return &SyncerGatedSync{ + started: make(chan struct{}, 100), + release: make(chan struct{}), + } +} + +// syncerRunKilledWriter starts the writer child on the chat in root, kills it +// with SIGKILL after it acknowledged count messages, and returns the number of // messages that it acknowledged. -func runKilledWriter(t *testing.T, root string, count int) int { +func syncerRunKilledWriter(t *testing.T, root string, count int) int { t.Helper() command := exec.Command(os.Args[0], "-test.run=^TestWriterChild$") - command.Env = append(os.Environ(), childDirVariable+"="+root) + command.Env = append(os.Environ(), syncerChildDirVariable+"="+root) stdout, err := command.StdoutPipe() require.NoError(t, err) require.NoError(t, command.Start()) @@ -205,6 +213,7 @@ func runKilledWriter(t *testing.T, root string, count int) int { } require.NoError(t, command.Process.Kill()) + for scanner.Scan() { _, err = strconv.Atoi(strings.TrimSpace(scanner.Text())) if err == nil { @@ -218,14 +227,14 @@ func runKilledWriter(t *testing.T, root string, count int) int { return acknowledged } -// writerKind returns the kind of message id of the writer child. -func writerKind(id int) string { +// syncerWriterKind returns the kind of message id of the writer child. +func syncerWriterKind(id int) string { return []string{"user", "tool", "echo", "reply"}[id%4] } -// writerText returns the text of message id of the writer child. Some texts -// are large, so a kill can come during the write of one line. -func writerText(id int) string { +// syncerWriterText returns the text of message id of the writer child. Some +// texts are large, so a kill can come during the write of one line. +func syncerWriterText(id int) string { size := 40 + id%7*30 if id%5 == 4 { size = 64*1024 + id