From c506eaa8df1d00bee2a025ca2d01c44e6414330e Mon Sep 17 00:00:00 2001 From: "nekomimi.pet" Date: Sat, 31 Oct 2026 18:33:01 -0400 Subject: [PATCH] cli: keep real startup budgets when the daemon's timers are scaled; agents.md: a flake means something slowed down --- agents.md | 2 +- cli/internal/localdaemon/lifecycle.go | 13 ++++++------- cli/internal/localdaemon/startup_test.go | 14 ++++++++++++-- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/agents.md b/agents.md index 3eba7b3c..859b4b8f 100644 --- a/agents.md +++ b/agents.md @@ -39,7 +39,7 @@ Python test files are loaded through `test/python_test_runner.py`; a selected fi The e2e suites compile the daemon once per run with `test/snapshot-daemon.sh` (test.sh does it once for both and passes `ALBEDO_TEST_DAEMON`) and boot every test daemon from that copy through `ALBEDO_DAEMON`, never through `gleam run`, so their daemons neither queue on gleam's build lock nor see a rebuild mid-run. The one compile still takes the lock, which `gleam test` holds for its whole run: an e2e suite started from inside `gleam test` deadlocks, and one started beside it waits for it to finish. The Python e2e suite drives the prebuilt `cli/bin/albedo` binary; the Go e2e suite builds its own CLI and runs with `-count=1`, because it boots a hermetic daemon and must never reuse a cached run or a stale binary. -Test placement (expanded rules in `.agents/skills/writing-tests/SKILL.md`): the default is an end-to-end scenario through the real daemon on the shared harness in `test/e2e/` — a scripted fake model provider, one shared daemon for the tests that isolate themselves by session, workspace and provider profile, and a fresh daemon for each `@exclusive` test that changes global state or restarts it. Unit tests are the exception; each module docstring must say which bug it catches that E2E cannot. `test/harness/api_docs_test.py` is the invariant test to imitate: it builds the model's real Python namespace and fails whenever a public binding exists that the model is never told about. `test/manual/` is opt-in (needs a provider, a PTY, or benchmark artifacts) and is never part of the gate. A test that fails only sometimes is a product bug until shown otherwise: reproduce it under load with `python3 test/manual/stress.py ` before fixing it, and never answer it with a retry or a longer timeout. +Test placement (expanded rules in `.agents/skills/writing-tests/SKILL.md`): the default is an end-to-end scenario through the real daemon on the shared harness in `test/e2e/` — a scripted fake model provider, one shared daemon for the tests that isolate themselves by session, workspace and provider profile, and a fresh daemon for each `@exclusive` test that changes global state or restarts it. Unit tests are the exception; each module docstring must say which bug it catches that E2E cannot. `test/harness/api_docs_test.py` is the invariant test to imitate: it builds the model's real Python namespace and fails whenever a public binding exists that the model is never told about. `test/manual/` is opt-in (needs a provider, a PTY, or benchmark artifacts) and is never part of the gate. A test that fails only sometimes is a product bug until shown otherwise: a flake usually means something gets slow under load, if it doesn't break outright, so reproduce it under load with `python3 test/manual/stress.py `, measure what slowed down, and fix that before concluding the test is wrong. Never answer a flake with a retry or a longer timeout. ### Private daemons diff --git a/cli/internal/localdaemon/lifecycle.go b/cli/internal/localdaemon/lifecycle.go index 97a0895f..493f8af2 100644 --- a/cli/internal/localdaemon/lifecycle.go +++ b/cli/internal/localdaemon/lifecycle.go @@ -12,7 +12,6 @@ import ( "os" "os/exec" "path/filepath" - "strconv" "strings" "time" ) @@ -270,13 +269,13 @@ type startupProgress struct { logSize int64 } -// startupDelay matches the daemon's opt-in test timer scale. +// startupScale divides the startup budgets. Only this package's tests raise it: +// a CLI under test must keep its real budgets even when the daemon's timers are +// scaled, or a slow boot on a busy machine reads as a stall. +var startupScale time.Duration = 1 + func startupDelay(delay time.Duration) time.Duration { - scale, err := strconv.ParseInt(os.Getenv("ALBEDO_TEST_TIME_SCALE"), 10, 64) - if err != nil || scale <= 1 { - return delay - } - return max(time.Millisecond, delay/time.Duration(scale)) + return max(time.Millisecond, delay/startupScale) } func watchStartup(home string) startupProgress { diff --git a/cli/internal/localdaemon/startup_test.go b/cli/internal/localdaemon/startup_test.go index 459af52d..5c0bba00 100644 --- a/cli/internal/localdaemon/startup_test.go +++ b/cli/internal/localdaemon/startup_test.go @@ -22,6 +22,9 @@ func TestStartupHelperProcess(t *testing.T) { if mode == "" { return } + if scale, err := strconv.Atoi(os.Getenv("ALBEDO_STARTUP_TEST_SCALE")); err == nil { + startupScale = time.Duration(scale) + } home := os.Getenv("ALBEDO_HOME") if err := os.WriteFile(filepath.Join(home, "helper.pid"), []byte(strconv.Itoa(os.Getpid())), 0600); err != nil { panic(err) @@ -59,6 +62,13 @@ func TestStartupHelperProcess(t *testing.T) { } } +// scaleStartup runs this test's startup budgets, and its helper's, ten times faster. +func scaleStartup(t *testing.T) { + startupScale = 10 + t.Cleanup(func() { startupScale = 1 }) + t.Setenv("ALBEDO_STARTUP_TEST_SCALE", "10") +} + func startupHelper(t *testing.T, mode string) string { t.Helper() if runtime.GOOS == "windows" { @@ -89,7 +99,7 @@ func startupHelper(t *testing.T, mode string) string { } func TestLaunchWaitsForProgressBeyondThirtySeconds(t *testing.T) { - t.Setenv("ALBEDO_TEST_TIME_SCALE", "10") + scaleStartup(t) home := startupHelper(t, "progress") ctx, cancel := context.WithTimeout(t.Context(), 75*time.Second) defer cancel() @@ -115,7 +125,7 @@ func TestLaunchWaitsForProgressBeyondThirtySeconds(t *testing.T) { } func TestLaunchReportsStalledStartup(t *testing.T) { - t.Setenv("ALBEDO_TEST_TIME_SCALE", "10") + scaleStartup(t) home := startupHelper(t, "stall") _, err := Launch(t.Context(), LocalOptions{HomeDir: home}) var local *LocalError -- knotmirror 0.0.1