perf(tests): copy a migrated template DB instead of migrating per test

Most tests opened a throwaway panel DB with database.InitDB, which runs the
full AutoMigrate + seed on an empty file every time: ~230ms, and ~850ms under
-race because GORM's reflection-heavy migration is what the detector slows
most. internal/web/service does this in ~550 of its 830 tests, so the CI race
job spent ~10 of its ~14.6 minutes re-migrating empty databases.

internal/database/dbtest.InitDB migrates once per test process, then hands
each test its own copy of that file (~130ms under -race) and registers the
CloseDB cleanup. The copy then goes through InitDB like a panel restart, so
every test still starts from the state a fresh install has. Tests that reopen
an existing file, migrate a hand-built legacy DB or target Postgres keep
calling database.InitDB.

Locally under -race: internal/web/service 626s (last CI run) -> 114s,
internal/sub 246s -> 35s.
This commit is contained in:
MHSanaei
2026-09-27 03:04:50 +02:00
parent 33a469315a
commit 12d51d7195
82 changed files with 289 additions and 451 deletions
+2 -6
View File
@@ -6,17 +6,13 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
func initSubDB(t *testing.T) {
t.Helper()
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
// Close the handle before t.TempDir cleanup so Windows doesn't refuse to
// remove the still-open sqlite file.
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
}
// The subscription page's Copy URL must be built from the same host the
+2 -5
View File
@@ -6,17 +6,14 @@ import (
"path/filepath"
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/web/service"
)
// #6559: the Master panel must send a stable X-HWID when fetching external
// subscriptions, otherwise an HWID-limited donor answers 404.
func TestFetchSendsStableHwid(t *testing.T) {
if err := database.InitDB(filepath.Join(t.TempDir(), "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "x-ui.db"))
var gotHwid string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+3 -8
View File
@@ -13,6 +13,7 @@ import (
"time"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
@@ -302,10 +303,7 @@ func TestExpandEntryCacheHitWritesNothing(t *testing.T) {
func TestFetchUsesConfiguredExternalSubUserAgent(t *testing.T) {
resetSubscriptionCache(t)
if err := database.InitDB(filepath.Join(t.TempDir(), "ua.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "ua.db"))
const customUA = "Happ/4.2.1"
if err := database.GetDB().Create(&model.Setting{
@@ -333,10 +331,7 @@ func TestFetchUsesConfiguredExternalSubUserAgent(t *testing.T) {
func TestFetchFallsBackToDefaultExternalSubUserAgent(t *testing.T) {
resetSubscriptionCache(t)
if err := database.InitDB(filepath.Join(t.TempDir(), "ua-default.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "ua-default.db"))
var gotUA string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+2 -4
View File
@@ -8,6 +8,7 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
@@ -15,10 +16,7 @@ func seedSubDB(t *testing.T) {
t.Helper()
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
}
// seedSubInbound creates a VLESS inbound with one client wired into the
+2 -4
View File
@@ -12,6 +12,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
@@ -27,10 +28,7 @@ func initHwidSubRouter(t *testing.T, limit int) (*gin.Engine, string) {
}
t.Setenv("XUI_DB_FOLDER", tmp)
if err := database.InitDB(filepath.Join(tmp, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(tmp, "x-ui.db"))
const subID = "sub-hwid-route"
const email = "route@example.com"
+2 -4
View File
@@ -9,6 +9,7 @@ import (
"time"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/xray"
)
@@ -19,10 +20,7 @@ func initMutDB(t *testing.T) {
t.Helper()
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
}
// --- json_service.go:40 — rules are merged into routing only when non-empty ---
+2 -4
View File
@@ -17,6 +17,7 @@ import (
yaml "github.com/goccy/go-yaml"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
)
func mergeRemoteClashRulesYAML(base map[string]any, raw string) error {
@@ -607,10 +608,7 @@ func TestRemoteRoutingHTTPClientRejectsLoopback(t *testing.T) {
func TestRemoteRoutingPersistedLoadRetriesAfterDatabaseBecomesReady(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "x-ui.db")
if err := database.InitDB(dbPath); err != nil {
t.Fatalf("init db: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, dbPath)
deeplink, err := normalizeHappRouting([]byte(`{"Name":"persisted-after-ready"}`))
if err != nil {
+3 -8
View File
@@ -7,6 +7,7 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
@@ -18,10 +19,7 @@ import (
func TestGetSubs_DuplicateSettingsClients_Deduped(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
const subId = "sub-dup"
const email = "dup@example.com"
@@ -76,10 +74,7 @@ func TestGetSubs_DuplicateSettingsClients_Deduped(t *testing.T) {
func TestMatchingClients_DedupsCaseInsensitiveEmail(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
const subId = "s1"
const uuid = "11111111-2222-4333-8444-555555555555"
+2 -6
View File
@@ -8,6 +8,7 @@ import (
"time"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/web/service"
"github.com/mhsanaei/3x-ui/v3/internal/xray"
@@ -15,12 +16,7 @@ import (
func setupInfoNodeTestDB(t *testing.T) {
t.Helper()
if err := database.InitDB(t.TempDir() + "/test_infonode.db"); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() {
_ = database.CloseDB()
})
dbtest.InitDB(t, t.TempDir()+"/test_infonode.db")
db := database.GetDB()
if err := db.AutoMigrate(
&model.Inbound{},
+2 -4
View File
@@ -5,6 +5,7 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/xray"
)
@@ -17,10 +18,7 @@ import (
func TestStatsForClient_OrphanedInboundIdFallback(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
const email = "old-client@example.com"
const total = int64(100) * gb
+2 -4
View File
@@ -6,6 +6,7 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)
@@ -16,10 +17,7 @@ import (
func TestGetSubs_OrdersBySubSortIndexThenId(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
const subId = "sub-sort"
db := database.GetDB()
+2 -4
View File
@@ -5,6 +5,7 @@ import (
"testing"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
"github.com/mhsanaei/3x-ui/v3/internal/xray"
)
@@ -12,10 +13,7 @@ import (
func TestAggregateTrafficByEmails_FallsBackToClientLimits(t *testing.T) {
dbDir := t.TempDir()
t.Setenv("XUI_DB_FOLDER", dbDir)
if err := database.InitDB(filepath.Join(dbDir, "x-ui.db")); err != nil {
t.Fatalf("InitDB: %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(dbDir, "x-ui.db"))
const email = "node-client@example.com"
const totalBytes = int64(300) * 1024 * 1024 * 1024
+2 -4
View File
@@ -16,6 +16,7 @@ import (
"github.com/mhsanaei/3x-ui/v3/internal/config"
"github.com/mhsanaei/3x-ui/v3/internal/database"
"github.com/mhsanaei/3x-ui/v3/internal/database/dbtest"
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
xuilogger "github.com/mhsanaei/3x-ui/v3/internal/logger"
"github.com/mhsanaei/3x-ui/v3/internal/xray"
@@ -38,10 +39,7 @@ func setupScaleSubDB(t *testing.T) {
}
switch strings.ToLower(strings.TrimSpace(os.Getenv("XUI_SCALE_TEST"))) {
case "1", "true", "yes":
if err := database.InitDB(filepath.Join(t.TempDir(), "scale.db")); err != nil {
t.Fatalf("InitDB(sqlite): %v", err)
}
t.Cleanup(func() { _ = database.CloseDB() })
dbtest.InitDB(t, filepath.Join(t.TempDir(), "scale.db"))
return
}
t.Skip("set XUI_SCALE_TEST=1 (sqlite) or XUI_DB_TYPE=postgres + XUI_DB_DSN (postgres) to run the scale benchmark")