1 Commits
Author SHA1 Message Date
sneak 1233f44c8b Make the internal/handlers tests fast (closes #113)
check / check (push) Failing after 5s
Nearly all of the package's test time was SQLite work, and much of it
was setup every test repeated: each test migrated a fresh schema, and
each new session wrote the seven-line default MOTD to the database.
TestMain now migrates one in-memory template database once, and each
test starts from its own copy of it, made with SQLite's backup API.
The test config's MOTD is one line. Every test still gets an empty
database of its own, and every check runs as before.

Model: opus-5-5
2026-10-06 21:16:32 +00:00
2 changed files with 77 additions and 6 deletions
+2 -5
View File
@@ -32,12 +32,9 @@ RUN go mod download
COPY . . COPY . .
RUN mkdir -p web/dist && touch web/dist/index.html web/dist/style.css web/dist/app.js RUN mkdir -p web/dist && touch web/dist/index.html web/dist/style.css web/dist/app.js
# -p 4 because test runs on a shared build host cap their parallelism. # -p 4 because test runs on a shared build host cap their parallelism.
# -race also turns on Go's unsafe-pointer checks (checkptr) in every RUN go test -p 4 -timeout 90s -race -cover ./... || \
# package. In modernc.org, SQLite machine-translated from C, they doubled
# the test time, so they are off there and stay on everywhere else.
RUN go test -p 4 -timeout 90s -race -gcflags='modernc.org/...=-d=checkptr=0' -cover ./... || \
{ echo "--- Rerunning with -v for details ---"; \ { echo "--- Rerunning with -v for details ---"; \
go test -p 4 -timeout 90s -race -gcflags='modernc.org/...=-d=checkptr=0' -v ./...; exit 1; } go test -p 4 -timeout 90s -race -v ./...; exit 1; }
# Build stage. Nothing is wanted from either phase above; the copies # Build stage. Nothing is wanted from either phase above; the copies
# are what make BuildKit build them first, so this stage cannot run # are what make BuildKit build them first, so this stage cannot run
+75 -1
View File
@@ -6,7 +6,10 @@ package handlers_test
import ( import (
"bytes" "bytes"
"context"
"database/sql"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@@ -22,6 +25,7 @@ import (
"go.uber.org/fx" "go.uber.org/fx"
"go.uber.org/fx/fxtest" "go.uber.org/fx/fxtest"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"modernc.org/sqlite"
"sneak.berlin/go/neoirc/internal/broker" "sneak.berlin/go/neoirc/internal/broker"
"sneak.berlin/go/neoirc/internal/config" "sneak.berlin/go/neoirc/internal/config"
"sneak.berlin/go/neoirc/internal/db" "sneak.berlin/go/neoirc/internal/db"
@@ -37,9 +41,76 @@ import (
"sneak.berlin/go/neoirc/pkg/irc" "sneak.berlin/go/neoirc/pkg/irc"
) )
// templateDBURL names the in-memory database TestMain migrates once
// and every test copies; it lasts while TestMain holds it open.
const templateDBURL = "file:template?mode=memory&cache=shared"
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
db.SetBcryptCost(bcrypt.MinCost) db.SetBcryptCost(bcrypt.MinCost)
os.Exit(m.Run())
template, err := sql.Open("sqlite", templateDBURL)
if err != nil {
panic(err)
}
err = db.NewTestDatabaseFromConn(template).
RunMigrations(context.Background())
if err != nil {
panic(err)
}
code := m.Run()
_ = template.Close()
os.Exit(code)
}
// copyTemplateDB creates the in-memory database dbURL as a copy of
// the migrated template, so a test does not migrate the schema
// again. The copy lasts until the test ends.
func copyTemplateDB(t *testing.T, dbURL string) {
t.Helper()
database, err := sql.Open("sqlite", dbURL)
if err != nil {
t.Fatalf("open test db: %v", err)
}
t.Cleanup(func() { _ = database.Close() })
conn, err := database.Conn(t.Context())
if err != nil {
t.Fatalf("connect test db: %v", err)
}
defer func() { _ = conn.Close() }()
err = conn.Raw(func(driverConn any) error {
restorer, ok := driverConn.(interface {
NewRestore(srcURI string) (*sqlite.Backup, error)
})
if !ok {
return errors.ErrUnsupported
}
restore, restoreErr := restorer.NewRestore(templateDBURL)
if restoreErr != nil {
return fmt.Errorf("start restore: %w", restoreErr)
}
_, restoreErr = restore.Step(-1)
if restoreErr != nil {
_ = restore.Finish()
return fmt.Errorf("restore: %w", restoreErr)
}
return restore.Finish()
})
if err != nil {
t.Fatalf("copy template db: %v", err)
}
} }
const ( const (
@@ -84,6 +155,7 @@ func newTestServerWith(
"file:test_%p?mode=memory&cache=shared", "file:test_%p?mode=memory&cache=shared",
t, t,
) )
copyTemplateDB(t, dbURL)
var srv *server.Server var srv *server.Server
@@ -109,6 +181,8 @@ func newTestServerWith(
cfg.DBURL = dbURL cfg.DBURL = dbURL
cfg.Port = 0 cfg.Port = 0
// One line: each line is a database write per new session.
cfg.MOTD = "neoirc test server"
cfg.HashcashBits = hashcashBits cfg.HashcashBits = hashcashBits
cfg.OperName = operName cfg.OperName = operName
cfg.OperPassword = operPassword cfg.OperPassword = operPassword