Compare commits

..
Author SHA1 Message Date
anandh8x 145a604396 fix(tui): reset quiet clock for new runs
New turns inherited lastStreamActivity from the prior turn, so a fresh request could immediately look stalled. Initialize stream activity from the same timestamp as turnStartedAt and cover the stale-clock case with a deterministic regression test.
2026-07-24 10:38:08 +05:30
3 changed files with 22 additions and 23 deletions

No files matched your search

+7 -23
View File
@@ -1,7 +1,6 @@
package config
import (
"errors"
"os"
"path/filepath"
"runtime"
@@ -88,7 +87,7 @@ func TestLoadProviderCommandTimeout(t *testing.T) {
if elapsed > maxElapsed {
t.Fatalf("timeout returned after %s, want roughly 5s", elapsed)
}
assertProcessTerminatedIfStarted(t, pidFile)
assertProcessTerminated(t, pidFile)
}
// TestLoadProviderCommandTerminatesBackgroundChild covers a command that
@@ -98,10 +97,9 @@ func TestLoadProviderCommandTimeout(t *testing.T) {
// leftover child instead of just returning and leaking it.
func TestLoadProviderCommandTerminatesBackgroundChild(t *testing.T) {
pidFile := filepath.Join(t.TempDir(), "bg.pid")
childLifetime := 10 * time.Second
command := writeCommand(t, commandScript{
Stdout: `{"name":"cmd","provider":"openai","apiKey":"sk-command","model":"gpt-command"}`,
BackgroundSleepSeconds: int(childLifetime / time.Second),
BackgroundSleepSeconds: 10,
BackgroundPidFile: pidFile,
})
@@ -114,8 +112,8 @@ func TestLoadProviderCommandTerminatesBackgroundChild(t *testing.T) {
if !strings.Contains(err.Error(), "timed out after 5s") {
t.Fatalf("error = %q, want timeout", err.Error())
}
if elapsed >= childLifetime {
t.Fatalf("returned after %s, want before the background child finishes naturally after %s", elapsed, childLifetime)
if elapsed > 4*time.Second {
t.Fatalf("returned after %s, want well under the 5s provider-command timeout since WaitDelay (1s) should trigger termination", elapsed)
}
assertProcessTerminated(t, pidFile)
}
@@ -128,11 +126,10 @@ func TestLoadProviderCommandTerminatesBackgroundChild(t *testing.T) {
// be terminated on that path.
func TestLoadProviderCommandTerminatesBackgroundChildOnFailure(t *testing.T) {
pidFile := filepath.Join(t.TempDir(), "bg-fail.pid")
childLifetime := 10 * time.Second
command := writeCommand(t, commandScript{
Stderr: "boom",
ExitCode: 7,
BackgroundSleepSeconds: int(childLifetime / time.Second),
BackgroundSleepSeconds: 10,
BackgroundPidFile: pidFile,
})
@@ -145,21 +142,8 @@ func TestLoadProviderCommandTerminatesBackgroundChildOnFailure(t *testing.T) {
if !strings.Contains(err.Error(), "exit status") {
t.Fatalf("error = %q, want exit status failure", err.Error())
}
if elapsed >= childLifetime {
t.Fatalf("returned after %s, want before the background child finishes naturally after %s", elapsed, childLifetime)
}
assertProcessTerminated(t, pidFile)
}
func assertProcessTerminatedIfStarted(t *testing.T, pidFile string) {
t.Helper()
// The basic timeout fixture is intentionally unsynchronized. On a loaded
// Windows runner, the cold PowerShell child can be terminated before it
// records its PID; the synchronized background-child tests below retain
// the strict PID and liveness checks.
if _, err := os.Stat(pidFile); errors.Is(err, os.ErrNotExist) {
return
if elapsed > 4*time.Second {
t.Fatalf("returned after %s, want well under the 5s provider-command timeout since WaitDelay (1s) should trigger termination", elapsed)
}
assertProcessTerminated(t, pidFile)
}
+1
View File
@@ -4807,6 +4807,7 @@ func (m model) beginRun(cancel context.CancelFunc) model {
// suppressed by a stale preference.
m.sidebarHidden = false
m.turnStartedAt = m.now()
m.lastStreamActivity = m.turnStartedAt
m.turnStreamedRunes = 0
m.spinnerTicking = true
return m
+14
View File
@@ -106,6 +106,20 @@ func TestQuietGenerationHint(t *testing.T) {
}
}
func TestBeginRunResetsQuietGenerationClock(t *testing.T) {
now := time.Date(2026, 7, 24, 12, 0, 0, 0, time.UTC)
m := model{
now: func() time.Time { return now },
lastStreamActivity: now.Add(-time.Minute),
}
m = m.beginRun(nil)
if got := m.quietGenerationHint(); got != "" {
t.Fatalf("new run inherited a stale quiet-generation warning: %q", got)
}
}
// TestQuietGenerationHintEscalatesPastHalfIdleTimeout: a heartbeating-but-
// silent stream (chatgpt/gpt-5.x, ollama reasoning models — see
// providerio.ErrStreamStalled) and a genuinely healthy-but-slow one look