Follow-up from #6, which was closed with this explicitly left open.
internal/dbtest is the lowest-coverage package (~66%). Every remaining uncovered line in SetupDB is a t.Fatalf branch that fires only when MySQL breaks mid-call: CREATE DATABASE fails, the fresh pool cannot ping, migrations fail against a database created a moment earlier.
Those cannot be reached today, because triggering them means the helper fails its own test.
The approach
Introduce a narrow interface over the testing.T methods SetupDB actually uses:
type testingT interface {
Helper()
Skipf(format string, args ...any)
Fatalf(format string, args ...any)
Logf(format string, args ...any)
Cleanup(func())
}
Keep SetupDB(t *testing.T) *sql.DB as the public signature so no caller changes, and have it delegate to setupDB(t testingT). Tests then pass a fake that records calls instead of aborting.
The one subtlety: real Fatalf calls runtime.Goexit(), so a fake has to stop execution somehow — panic with a sentinel and recover in the test is the usual approach. Get that right or the tests will pass for the wrong reason.
To actually trigger a CREATE DATABASE failure you will likely want a privilege-restricted MySQL user (GRANT SELECT ON *.* only). That is why this was not done inline: it couples the suite to environment setup, including CI. Worth doing if you can keep the coupling contained — e.g. skip those specific tests when the restricted user is absent, rather than requiring it everywhere.
Why it matters
SetupDB is called by every database-backed test in the repo. Its failure paths are the ones that decide whether a broken environment is reported or silently skipped — and there is prior art for getting that wrong here: SetupDB used to skip under CI, which meant a wrong DSN produced a green run that exercised no SQL at all (fixed in 6d82e77).
Follow-up from #6, which was closed with this explicitly left open.
internal/dbtestis the lowest-coverage package (~66%). Every remaining uncovered line inSetupDBis at.Fatalfbranch that fires only when MySQL breaks mid-call:CREATE DATABASEfails, the fresh pool cannot ping, migrations fail against a database created a moment earlier.Those cannot be reached today, because triggering them means the helper fails its own test.
The approach
Introduce a narrow interface over the
testing.TmethodsSetupDBactually uses:Keep
SetupDB(t *testing.T) *sql.DBas the public signature so no caller changes, and have it delegate tosetupDB(t testingT). Tests then pass a fake that records calls instead of aborting.The one subtlety: real
Fatalfcallsruntime.Goexit(), so a fake has to stop execution somehow — panic with a sentinel and recover in the test is the usual approach. Get that right or the tests will pass for the wrong reason.To actually trigger a
CREATE DATABASEfailure you will likely want a privilege-restricted MySQL user (GRANT SELECT ON *.*only). That is why this was not done inline: it couples the suite to environment setup, including CI. Worth doing if you can keep the coupling contained — e.g. skip those specific tests when the restricted user is absent, rather than requiring it everywhere.Why it matters
SetupDBis called by every database-backed test in the repo. Its failure paths are the ones that decide whether a broken environment is reported or silently skipped — and there is prior art for getting that wrong here:SetupDBused to skip under CI, which meant a wrong DSN produced a green run that exercised no SQL at all (fixed in 6d82e77).