feat: each test package runs on its own database

agent, db, server and evidence serialized on one shared test database via an advisory lock, while evidence, llmrec and traffic-style packages could still run against it in parallel; under full-suite load that window produced a real SQLSTATE 40P01 deadlock in the blackboard direct-source test. TestMain in every PG-using package now rewrites ARTEX_PG_DSN to a per-package database (created automatically by ensureDatabase), so packages neither contend nor need to serialize; the advisory lock stays as an in-package safety net. llmrec gains the TestMain it lacked, which also gives its requires-isolated-DSN tests a truly isolated database inside ./... runs.
This commit is contained in:
BaaRai
2026-10-12 18:43:29 +02:00
parent ff2e0bf338
commit aaf966872a
6 changed files with 52 additions and 0 deletions
+4
View File
@@ -13,6 +13,10 @@ import (
// agent test suite so cross-package DELETE cleanup races with db/server
// packages are avoided when running `go test ./...`.
func TestMain(m *testing.M) {
// Own database per package (#80): with separate databases, packages stop
// contending on shared tables and the advisory lock below stops
// serializing go test ./... across packages.
db.SuffixTestDatabase("agent")
dsn, _, err := db.DSN()
if err != nil {
os.Exit(m.Run())
+21
View File
@@ -10,6 +10,7 @@ import (
"errors"
"fmt"
"net/url"
"os"
"strings"
"time"
@@ -96,6 +97,26 @@ func DSN() (dsn, source string, err error) {
return config.PostgresDSN()
}
// SuffixTestDatabase rewrites the ARTEX_PG_DSN environment variable so its
// database name carries the given suffix (artex -> artex_agent). Called from
// package TestMain functions so every go test package runs against its own
// database: parallel package processes then cannot contend on the same tables
// (the under-load deadlock family, #80). No-op when ARTEX_PG_DSN is unset —
// config-file and production paths are untouched. Open creates the suffixed
// database automatically (ensureDatabase).
func SuffixTestDatabase(suffix string) {
v := strings.TrimSpace(os.Getenv("ARTEX_PG_DSN"))
if v == "" {
return
}
u, err := url.Parse(v)
if err != nil || u.Path == "" || u.Path == "/" {
return
}
u.Path = "/" + strings.TrimPrefix(u.Path, "/") + "_" + suffix
_ = os.Setenv("ARTEX_PG_DSN", u.String())
}
// DB wraps the shared *sql.DB. PG handles its own connection pool + concurrency
// (MVCC), so unlike the old SQLite store there is no process-wide write mutex.
type DB struct{ *sql.DB }
+4
View File
@@ -14,6 +14,10 @@ import (
// avoid cross-package cleanup races (e.g. DELETE FROM assets WHERE id > X
// from one package deleting assets created by another).
func TestMain(m *testing.M) {
// Own database per package (#80): with separate databases, packages stop
// contending on shared tables and the advisory lock below stops
// serializing go test ./... across packages.
SuffixTestDatabase("db")
dsn, _, err := DSN()
if err != nil {
// No DB configured — tests that need PG will skip themselves.
+3
View File
@@ -11,6 +11,9 @@ import (
// Initialize an explicitly configured fresh database before taking the same
// suite lock as db, agent and server. Hold it on one pinned connection.
func TestMain(m *testing.M) {
// Own database per package (#80): stop contending on the shared tables of
// a single test database during parallel ./... runs.
db.SuffixTestDatabase("evidence")
if os.Getenv("ARTEX_PG_DSN") == "" {
os.Exit(m.Run())
}
+16
View File
@@ -0,0 +1,16 @@
package llmrec
import (
"os"
"testing"
"github.com/Autumn-27/artex/db"
)
// Run against this package's own database (db.SuffixTestDatabase, #80): the
// recorder tests require an isolated ARTEX_PG_DSN, and the suffix gives them
// one even inside a full parallel ./... run.
func TestMain(m *testing.M) {
db.SuffixTestDatabase("llmrec")
os.Exit(m.Run())
}
+4
View File
@@ -13,6 +13,10 @@ import (
// server test suite so cross-package DELETE cleanup races with db/agent
// packages are avoided when running `go test ./...`.
func TestMain(m *testing.M) {
// Own database per package (#80): with separate databases, packages stop
// contending on shared tables and the advisory lock below stops
// serializing go test ./... across packages.
db.SuffixTestDatabase("server")
dsn, _, err := db.DSN()
if err != nil {
os.Exit(m.Run())