From: Anderson A. Giacomolli Date: Wed, 11 Nov 2026 14:11:27 +0000 (-0300) Subject: Name private helpers for their owning feature X-Git-Url: https://git.giacomolli.uk/onechat/commitdiff_plain/b961d7cdc59213673c3e4f86d3dae99daf0a1935?hp=d8f3785bb69288663f4cc46bf4d609c6770452d5 Name private helpers for their owning feature Daemon log formatting and single-consumer test helpers identify their owning feature. Memory replay helpers identify the memory test owner. WhatsApp tests reuse the shared discard logger instead of a duplicate logger factory. The renamed functions keep their behavior. Issue: 947bddb --- diff --git a/daemon_log.go b/daemon_log.go index 771fef4..1ff6aa4 100644 --- a/daemon_log.go +++ b/daemon_log.go @@ -138,15 +138,15 @@ func daemonLogAttr(line *strings.Builder, group string, attr slog.Attr, cause *s line.WriteByte('=') if attr.Value.Kind() == slog.KindDuration { - line.WriteString(durationText(attr.Value.Duration())) + line.WriteString(daemonLogDuration(attr.Value.Duration())) } else { fmt.Fprint(line, attr.Value.Any()) } } -// durationText returns d for a log line: rounded to the second, or to the +// daemonLogDuration returns d for a log line: rounded to the second, or to the // millisecond when it is shorter than a second. -func durationText(d time.Duration) string { +func daemonLogDuration(d time.Duration) string { if d < time.Second { return d.Round(time.Millisecond).String() } diff --git a/daemon_start_helper_test.go b/daemon_start_helper_test.go index b09c11b..a27967e 100644 --- a/daemon_start_helper_test.go +++ b/daemon_start_helper_test.go @@ -36,7 +36,7 @@ func daemonStartTestWithWhatsApp(t *testing.T, server *ModelFake, bot *TelegramB func daemonStartTestWithSchedules(t *testing.T, server *ModelFake, bot *TelegramBot, whatsapp *WhatsApp, schedules *Schedules, clock func() time.Time) (string, *Memory) { t.Helper() - dir := socketTestDir(t) + dir := daemonTestSocketDir(t) agent, err := openAgent(filepath.Join(dir, daemonAgentDirName)) require.NoError(t, err) @@ -67,10 +67,10 @@ func daemonStartTestWithSchedules(t *testing.T, server *ModelFake, bot *Telegram return socket, memory } -// socketTestDir returns a new short directory under /tmp for data that holds a -// daemon socket. t.TempDir holds the test name and the TMPDIR of the shell, so -// its paths can pass the 107-byte limit of a Unix socket path. -func socketTestDir(t *testing.T) string { +// daemonTestSocketDir returns a new short directory under /tmp for data that +// holds a daemon socket. t.TempDir holds the test name and the TMPDIR of the +// shell, so its paths can pass the 107-byte limit of a Unix socket path. +func daemonTestSocketDir(t *testing.T) string { t.Helper() dir, err := os.MkdirTemp("/tmp", "onechat-") diff --git a/memory_test.go b/memory_test.go index d3fa612..6f76e5b 100644 --- a/memory_test.go +++ b/memory_test.go @@ -18,12 +18,12 @@ import ( ) const ( - // replayMessages is the length of the synthetic log. - replayMessages = 100000 + // memoryReplayMessages is the length of the synthetic log. + memoryReplayMessages = 100000 - // replayShortMessages is the length of the synthetic log with -short. - // It is long enough for batches of the chat view. - replayShortMessages = 5000 + // memoryReplayShortMessages is the length of the synthetic log with + // -short. It is long enough for batches of the chat view. + memoryReplayShortMessages = 5000 ) func TestMemoryRestartLoadsTheSameView(t *testing.T) { @@ -520,9 +520,9 @@ func TestQueuedAppendRefusesAnInvalidReceipt(t *testing.T) { // log is shorter, and the test still requires a batch of the chat view. func TestReplaySyntheticLog(t *testing.T) { start := time.Now() - messages := replayMessages + messages := memoryReplayMessages if testing.Short() { - messages = replayShortMessages + messages = memoryReplayShortMessages } root := t.TempDir() @@ -541,17 +541,17 @@ func TestReplaySyntheticLog(t *testing.T) { long := make([]bool, messages) for id := range messages { if id == messages/2 { - memory = replayRequireRestart(t, memory, root, clock) + memory = memoryReplayRequireRestart(t, memory, root, clock) } clock.now = clock.now.Add(time.Minute) - kind, text := replayMessage(random, id) + kind, text := memoryReplayMessage(random, id) long[id] = len(kind)+len(": ")+len(text) > nodeBytes - lines := replayViewLines(memory) + lines := memoryReplayViewLines(memory) _, err = memory.Append(kind, text) require.NoError(t, err) - size, merged := replayRequireViews(t, memory, lines, long, id%1000 == 0) + size, merged := memoryReplayRequireViews(t, memory, lines, long, id%1000 == 0) if merged { batches++ low = min(low, size) @@ -560,7 +560,7 @@ func TestReplaySyntheticLog(t *testing.T) { require.NoError(t, memory.Wait(t.Context())) } - memory = replayRequireRestart(t, memory, root, clock) + memory = memoryReplayRequireRestart(t, memory, root, clock) require.Equal(t, messages, memory.log.Len()) require.Positive(t, batches) @@ -582,9 +582,9 @@ func memoryLongText(n int) string { return fmt.Sprintf("long %d ", n) + strings.Repeat("x", nodeBytes) } -// replayRequireRestart closes memory, opens it again, and checks that the views -// and view.json stay the same byte for byte. -func replayRequireRestart(t *testing.T, memory *Memory, root string, clock *TestClock) *Memory { +// memoryReplayRequireRestart closes memory, opens it again, and checks that the +// views and view.json stay the same byte for byte. +func memoryReplayRequireRestart(t *testing.T, memory *Memory, root string, clock *TestClock) *Memory { t.Helper() require.NoError(t, memory.Wait(t.Context())) @@ -608,85 +608,85 @@ func replayRequireRestart(t *testing.T, memory *Memory, root string, clock *Test return memory } -// replayMessage returns the kind and the text of message id. Most messages are -// tool calls and their output. Users write short lines. One output in a hundred -// is over 30 KB. -func replayMessage(random *rand.Rand, id int) (string, string) { +// memoryReplayMessage returns the kind and the text of message id. Most +// messages are tool calls and their output. Users write short lines. One output +// in a hundred is over 30 KB. +func memoryReplayMessage(random *rand.Rand, id int) (string, string) { roll := random.IntN(100) switch { case roll < 5: - return "user", replayText(id, 10+random.IntN(200)) + return "user", memoryReplayText(id, 10+random.IntN(200)) case roll < 10: - return "reply", replayText(id, 100+random.IntN(1500)) + return "reply", memoryReplayText(id, 100+random.IntN(1500)) case roll < 12: - return "note", replayText(id, 50+random.IntN(600)) + return "note", memoryReplayText(id, 50+random.IntN(600)) case roll < 13: - return "work", replayText(id, 300+random.IntN(3000)) + return "work", memoryReplayText(id, 300+random.IntN(3000)) case roll < 56: - return "tool", replayText(id, 60+random.IntN(400)) + return "tool", memoryReplayText(id, 60+random.IntN(400)) case roll < 57: - return "echo", replayText(id, 30000+random.IntN(30000)) + return "echo", memoryReplayText(id, 30000+random.IntN(30000)) default: - return "echo", replayText(id, 20+random.IntN(4000)) + return "echo", memoryReplayText(id, 20+random.IntN(4000)) } } -// replayViewLines returns the number of lines in the chat view of memory. -func replayViewLines(memory *Memory) int { +// memoryReplayViewLines returns the number of lines in the chat view of memory. +func memoryReplayViewLines(memory *Memory) int { memory.mutex.Lock() defer memory.mutex.Unlock() return len(memory.chat.lines) } -// replayRequireViews checks the views after a new message, and with full +// memoryReplayRequireViews checks the views after a new message, and with full // compares them with the tree. Each view is at most its high limit, and a batch // merges the chat view to its low limit. The leaf of the new message can be // built after the append, so the sizes count it as the pending mark, as the // append did. It returns the size of the chat view and if a batch merged it, // from the number of lines before the message. -func replayRequireViews(t *testing.T, memory *Memory, lines int, long []bool, full bool) (int, bool) { +func memoryReplayRequireViews(t *testing.T, memory *Memory, lines int, long []bool, full bool) (int, bool) { t.Helper() memory.mutex.Lock() defer memory.mutex.Unlock() - size := replayAppendSize(memory.chat) + size := memoryReplayAppendSize(memory.chat) require.LessOrEqual(t, size, viewChatHigh) - require.LessOrEqual(t, replayAppendSize(memory.cview), viewCompactionHigh) + require.LessOrEqual(t, memoryReplayAppendSize(memory.cview), viewCompactionHigh) merged := len(memory.chat.lines) < lines if merged { require.LessOrEqual(t, size, viewChatLow) } - replayRequireOnlySummaries(t, memory, long, full) + memoryReplayRequireOnlySummaries(t, memory, long, full) return size, merged } -// replayText returns a text of about size bytes that starts with its message ID -// and holds some non-ASCII characters. -func replayText(id, size int) string { +// memoryReplayText returns a text of about size bytes that starts with its +// message ID and holds some non-ASCII characters. +func memoryReplayText(id, size int) string { head := fmt.Sprintf("m%d ", id) words := "ação data line café " return head + strings.Repeat(words, max(1, (size-len(head))/len(words))) } -// replayAppendSize returns the size of view with its last line as the pending -// mark. -func replayAppendSize(view *View) int { +// memoryReplayAppendSize returns the size of view with its last line as the +// pending mark. +func memoryReplayAppendSize(view *View) int { last := view.lines[len(view.lines)-1] return view.Size() - view.lineSize(last) + len(viewPending) } -// replayRequireOnlySummaries checks each line of the chat view as the model -// reads it. A built line holds its node, a long message has a summary of the -// fake summarizer and not its text, and an unbuilt line holds only the pending -// mark. With full, it also checks that the view keeps the node of each built -// line. The caller holds the lock of memory. -func replayRequireOnlySummaries(t *testing.T, memory *Memory, long []bool, full bool) { +// memoryReplayRequireOnlySummaries checks each line of the chat view as the +// model reads it. A built line holds its node, a long message has a summary of +// the fake summarizer and not its text, and an unbuilt line holds only the +// pending mark. With full, it also checks that the view keeps the node of each +// built line. The caller holds the lock of memory. +func memoryReplayRequireOnlySummaries(t *testing.T, memory *Memory, long []bool, full bool) { t.Helper() rendered := strings.Split(memory.chat.Render(), "\n") diff --git a/probe_log_helper_test.go b/probe_log_helper_test.go index 7df9f48..acf4c8f 100644 --- a/probe_log_helper_test.go +++ b/probe_log_helper_test.go @@ -130,8 +130,8 @@ func (g *ProbeWriter) outputLine() string { } // probeLog returns count messages of made-up work on a small bakery order -// system, with the mix of kinds and sizes of replayMessage. The same count -// always gives the same log. +// system, with the mix of kinds and sizes of memoryReplayMessage. The same +// count always gives the same log. func probeLog(count int) []DatedMessage { g := ProbeWriter{ random: rand.New(rand.NewPCG(7, 11)), diff --git a/turn_test.go b/turn_test.go index 27151a9..3c6bb06 100644 --- a/turn_test.go +++ b/turn_test.go @@ -18,7 +18,7 @@ func TestTurnRunsToolsUntilTheFinalAnswer(t *testing.T) { server.reply = func(w http.ResponseWriter, call ModelFakeCall) { if call.number == 0 { modelWriteEvents(w, modelCompletedEvent([]any{ - modelReasoningItem("rs_1", "opaque thought"), + turnReasoningItem("rs_1", "opaque thought"), modelFunctionCallItem("fc_1", "call_1", "shell", `{"command":"echo hi"}`), }, 100, 0, 5)) @@ -391,7 +391,7 @@ func (r *TurnEventRecorder) LastEvent(kind string) Event { return Event{} } -func modelReasoningItem(id, encrypted string) map[string]any { +func turnReasoningItem(id, encrypted string) map[string]any { return map[string]any{ "type": "reasoning", "id": id, diff --git a/whatsapp_test.go b/whatsapp_test.go index 9da1537..6475805 100644 --- a/whatsapp_test.go +++ b/whatsapp_test.go @@ -2,7 +2,6 @@ package main import ( "errors" - "log/slog" "os" "path/filepath" "strings" @@ -91,7 +90,7 @@ func TestWhatsAppRetriesTheFirstConnectionWithCappedBackoff(t *testing.T) { time.Sleep(10 * time.Minute) synctest.Wait() require.Len(t, test.fake.Connections(), 11) - require.Equal(t, "<4>first connect failed, retrying: network unavailable\n<6>connected again after="+durationText(outage)+"\n", test.logs.String()) + require.Equal(t, "<4>first connect failed, retrying: network unavailable\n<6>connected again after="+daemonLogDuration(outage)+"\n", test.logs.String()) }) } @@ -357,7 +356,7 @@ func TestWhatsAppRefusesASessionThatOthersCanRead(t *testing.T) { whatsappTestSession(t, dir, true) require.NoError(t, os.Chmod(filepath.Join(dir, whatsappDirName, whatsappSessionFileName), 0o644)) - _, err := openWhatsApp(t.Context(), dir, time.Now, whatsappDiscardLog(), whatsappNewClient) + _, err := openWhatsApp(t.Context(), dir, time.Now, discardLog(), whatsappNewClient) require.EqualError(t, err, "whatsapp/session.db holds the WhatsApp session, so its mode must be 0600") } @@ -758,8 +757,3 @@ func TestSubagentHasNoWhatsAppTools(t *testing.T) { require.Equal(t, `error: unknown tool "whatsapp_find"`, box.run(t.Context(), "whatsapp_find", `{"name":"ana"}`)) require.Equal(t, `error: unknown tool "whatsapp_send"`, box.run(t.Context(), "whatsapp_send", `{"id":"a","chat":"15550002222@s.whatsapp.net","text":"hi"}`)) } - -// whatsappDiscardLog is a logger for a WhatsApp whose log a test does not read. -func whatsappDiscardLog() *slog.Logger { - return slog.New(slog.DiscardHandler) -}