232268e6

By: Michael Lynch <git@mtlynch.io>

Return startup failures through testable command runners

Both binaries previously mixed global flag parsing, dependency construction, serving, and process termination. SQLite migrations and handler construction called log.Fatal below main, which prevented callers from adding context, running deferred cleanup, or directly testing malformed flags and startup failures.

Litestream v0.5.14 in files cmd/litestream/main.go and cmd/litestream/restore.go keeps main as a thin exit-code boundary and exposes command Run methods that accept context and arguments, use private flag.FlagSet values, and return errors. That structure makes command behavior testable in-process while preserving one clear place that decides whether an error terminates the program.

Introduce Main.Run(ctx, args) for the app and Tinybeans importer, migrate both to local flag sets, and return setup and serving failures. Make SQLite, migrations, SMTP setup, and handler construction return errors. Constructors close partially initialized resources, commands own every resource they successfully create, and errors.Join preserves a cleanup failure alongside the operation that triggered it. Direct runner and constructor tests now cover help, invalid arguments, database-open failure, and startup session-cleanup failure.

The most delicate issue was the existing Litestream shutdown contract. Ordinary failures must return errors, but an intentional signal shutdown must still drain HTTP, close SQLite, and return successfully so the Litestream parent performs its final sync. The runner tracks store ownership to avoid a deferred double close while retaining that zero-exit signal path. Migrating handlers.New also required updating route-level test helpers without exporting implementation seams solely for tests.

No external blockers were encountered. Focused tests, the full Go checks, and every flake target passed before the amendment.

Suite timing

Time to Start Worker time Duration Time to finish Idle
Config 31s 22s 22s 54s 31s
Eval 55s 1m38s 1m38s 2m33s 1s
Build 2m19s 2m46s - - 2m24s
Suite 31s 4m47s - - 2m57s

Timeline

0s40s1m1m20s1m40s2m2m20s5m5m20s