From e62962d192280d2bbf2a628c63822880c0b387de Mon Sep 17 00:00:00 2001 From: clawbot Date: Wed, 25 Mar 2026 20:11:34 +0100 Subject: [PATCH] fix: use in-memory SQLite for handler tests to fix CI timeout (#93) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes the CI build failure caused by the `internal/handlers` test package exceeding the 30-second per-package timeout on the x86_64 CI runner. ## Root Cause Each of the 104 handler tests was creating a **file-backed SQLite database** in a temp directory with WAL journaling. On slower CI runners (x86_64 ubuntu-latest), the cumulative filesystem I/O overhead for 104 DB create + migrate + teardown cycles pushed the package well past the 30s timeout. ## Fix 1. **In-memory SQLite** — Switch test databases from `file:/test.db?_journal_mode=WAL&_busy_timeout=5000` to `file:test_?mode=memory&cache=shared`. Each test still gets its own isolated database (unique name per `*testing.T` pointer), but without filesystem I/O. 2. **Consolidated test server constructors** — Merged the duplicate `newTestServer()` and `newTestServerWithOper()` setup code into a shared `newTestServerWith()` helper, removing ~50 lines of duplication. ## Results | Environment | Before | After | |---|---|---| | ARM native (no race) | ~4.5s | ~2.0s | | ARM native (with race) | ~11.5s | ~8.7s | | Docker ARM (with race+cover) | **~20.4s** | **~10.0s** | The Docker ARM time is the closest proxy for CI. With the ~2x overhead of x86_64 emulation on CI, the estimated CI time is ~20s — well within the 30s timeout. ## What This Does NOT Change - No test assertions modified - No tests skipped or removed - No linter config changes - No Makefile changes - No CI config changes - All 104 handler tests still run with full isolation closes https://git.eeqj.de/sneak/chat/issues/90 Co-authored-by: clawbot Reviewed-on: https://git.eeqj.de/sneak/chat/pulls/93 Co-authored-by: clawbot Co-committed-by: clawbot --- internal/handlers/api_test.go | 85 ++++++++--------------------------- 1 file changed, 18 insertions(+), 67 deletions(-) diff --git a/internal/handlers/api_test.go b/internal/handlers/api_test.go index c2da05b..d52a9c2 100644 --- a/internal/handlers/api_test.go +++ b/internal/handlers/api_test.go @@ -12,7 +12,6 @@ import ( "net/http" "net/http/httptest" "os" - "path/filepath" "strconv" "strings" "sync" @@ -64,12 +63,20 @@ func newTestServer( ) *testServer { t.Helper() - dbPath := filepath.Join( - t.TempDir(), "test.db", - ) + return newTestServerWith(t, 0, "", "") +} - dbURL := "file:" + dbPath + - "?_journal_mode=WAL&_busy_timeout=5000" +func newTestServerWith( + t *testing.T, + hashcashBits int, + operName, operPassword string, +) *testServer { + t.Helper() + + dbURL := fmt.Sprintf( + "file:test_%p?mode=memory&cache=shared", + t, + ) var srv *server.Server @@ -95,7 +102,9 @@ func newTestServer( cfg.DBURL = dbURL cfg.Port = 0 - cfg.HashcashBits = 0 + cfg.HashcashBits = hashcashBits + cfg.OperName = operName + cfg.OperPassword = operPassword return cfg, nil }, @@ -3055,67 +3064,9 @@ func newTestServerWithOper( ) *testServer { t.Helper() - dbPath := filepath.Join( - t.TempDir(), "test.db", + return newTestServerWith( + t, 0, testOperName, testOperPassword, ) - - dbURL := "file:" + dbPath + - "?_journal_mode=WAL&_busy_timeout=5000" - - var srv *server.Server - - app := fxtest.New(t, - fx.Provide( - newTestGlobals, - logger.New, - func( - lifecycle fx.Lifecycle, - globs *globals.Globals, - log *logger.Logger, - ) (*config.Config, error) { - cfg, err := config.New( - lifecycle, config.Params{ //nolint:exhaustruct - Globals: globs, Logger: log, - }, - ) - if err != nil { - return nil, fmt.Errorf( - "test config: %w", err, - ) - } - - cfg.DBURL = dbURL - cfg.Port = 0 - cfg.HashcashBits = 0 - cfg.OperName = testOperName - cfg.OperPassword = testOperPassword - - return cfg, nil - }, - newTestDB, - stats.New, - newTestHealthcheck, - newTestMiddleware, - newTestHandlers, - newTestServerFx, - ), - fx.Populate(&srv), - ) - - app.RequireStart() - - httpSrv := httptest.NewServer(srv) - - t.Cleanup(func() { - httpSrv.Close() - app.RequireStop() - }) - - return &testServer{ - httpServer: httpSrv, - t: t, - fxApp: app, - } } func TestOperCommandSuccess(t *testing.T) {