Files
gitea/tests/integration/migration-test/migration_test.go
wxiaoguang deb31d3f30 Refactor database connection (#37496)
Clean up legacy copied&pasted code, introduce the unique "database
connection" function. Move migration testing helper function
PrepareTestEnv to a separate package.

By the way, remove "shadow connection secrets" tricks: showing
connection string on UI is useless

---------

Co-authored-by: Nicolas <bircni@icloud.com>
2026-05-01 15:38:38 +00:00

199 lines
5.4 KiB
Go

// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package migrations
import (
"bytes"
"compress/gzip"
"context"
"database/sql"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"testing"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/migrations"
migrate_base "code.gitea.io/gitea/models/migrations/base"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/testlogger"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"xorm.io/xorm"
)
var currentEngine *xorm.Engine
func initMigrationTest(t *testing.T) func() {
testlogger.Init()
require.NoError(t, setting.PrepareIntegrationTestConfig())
setting.SetupGiteaTestEnv()
assert.NotEmpty(t, setting.RepoRootPath)
assert.NoError(t, unittest.SyncDirs(filepath.Join(setting.GetGiteaTestSourceRoot(), "tests/gitea-repositories-meta"), setting.RepoRootPath))
assert.NoError(t, git.InitFull())
setting.LoadDBSetting()
setting.InitLoggersForTest()
return testlogger.PrintCurrentTest(t, 2)
}
func availableVersions() ([]string, error) {
migrationsDir, err := os.Open(filepath.Join(setting.GetGiteaTestSourceRoot(), "tests/integration/migration-test"))
if err != nil {
return nil, err
}
defer migrationsDir.Close()
versionRE, err := regexp.Compile("gitea-v(?P<version>.+)" + regexp.QuoteMeta("."+string(setting.Database.Type)+".sql.gz"))
if err != nil {
return nil, err
}
filenames, err := migrationsDir.Readdirnames(-1)
if err != nil {
return nil, err
}
var versions []string
for _, filename := range filenames {
if versionRE.MatchString(filename) {
substrings := versionRE.FindStringSubmatch(filename)
versions = append(versions, substrings[1])
}
}
sort.Strings(versions)
return versions, nil
}
func readSQLFromFile(version string) (string, error) {
filename := fmt.Sprintf("tests/integration/migration-test/gitea-v%s.%s.sql.gz", version, setting.Database.Type)
filename = filepath.Join(setting.GetGiteaTestSourceRoot(), filename)
file, err := os.Open(filename)
if err != nil {
return "", err
}
defer file.Close()
gr, err := gzip.NewReader(file)
if err != nil {
return "", err
}
defer gr.Close()
buf, err := io.ReadAll(gr)
if err != nil {
return "", err
}
return string(bytes.TrimPrefix(buf, []byte("\xef\xbb\xbf"))), nil
}
func restoreOldDB(t *testing.T, version string) {
data, err := readSQLFromFile(version)
require.NoError(t, err)
require.NotEmpty(t, data, "No data found for %s version: %s", setting.Database.Type, version)
cleanup, err := unittest.ResetTestDatabase()
require.NoError(t, err)
_ = cleanup // no clean up yet (not needed at the moment)
connOpts := db.GlobalConnOptions()
if !connOpts.Type.IsMSSQL() {
if connOpts.Type.IsMySQL() {
connOpts.Database += "?multiStatements=true"
}
driver, connStr, err := db.ConnStr(connOpts)
require.NoError(t, err)
sqlDB, err := sql.Open(driver, connStr)
require.NoError(t, err)
defer sqlDB.Close()
_, err = sqlDB.Exec(data)
require.NoError(t, err)
return
}
// MSSQL is special. the test fixture will create the [testgitea] database again, so drop it ahead if it exists
driver, connStr, err := db.ConnStrDefaultDatabase(connOpts)
require.NoError(t, err)
sqlDB, err := sql.Open(driver, connStr)
require.NoError(t, err)
_, err = sqlDB.Exec("DROP DATABASE IF EXISTS [testgitea]")
require.NoError(t, err, "drop existing database testgitea")
for statement := range strings.SplitSeq(data, "\nGO\n") {
if useStmtAfter, ok := strings.CutPrefix(statement, "USE ["); ok {
_ = sqlDB.Close()
dbname := strings.TrimSuffix(useStmtAfter, "]") // extract the database name from "USE [dbname]"
connOpts.Database = dbname
driver, connStr, err := db.ConnStr(connOpts)
require.NoError(t, err)
sqlDB, err = sql.Open(driver, connStr)
require.NoError(t, err)
}
_, err = sqlDB.Exec(statement)
require.NoError(t, err, "SQL Exec failed when running: %s\nError: %v", statement, err)
}
_ = sqlDB.Close()
}
func wrappedMigrate(ctx context.Context, x *xorm.Engine) error {
currentEngine = x
return migrations.Migrate(ctx, x)
}
func doMigrationTest(t *testing.T, version string) {
defer testlogger.PrintCurrentTest(t)()
restoreOldDB(t, version)
setting.InitSQLLoggersForCli(log.INFO)
err := db.InitEngineWithMigration(t.Context(), wrappedMigrate)
assert.NoError(t, err)
currentEngine.Close()
beans, _ := db.NamesToBean()
err = db.InitEngineWithMigration(t.Context(), func(ctx context.Context, x *xorm.Engine) error {
currentEngine = x
return migrate_base.RecreateTables(beans...)(x)
})
assert.NoError(t, err)
currentEngine.Close()
// We do this a second time to ensure that there is not a problem with retained indices
err = db.InitEngineWithMigration(t.Context(), func(ctx context.Context, x *xorm.Engine) error {
currentEngine = x
return migrate_base.RecreateTables(beans...)(x)
})
assert.NoError(t, err)
currentEngine.Close()
}
func TestMigrations(t *testing.T) {
defer initMigrationTest(t)()
dialect := setting.Database.Type
versions, err := availableVersions()
require.NoError(t, err)
require.NotEmpty(t, versions, "No old database versions available to migration test for %s", dialect)
for _, version := range versions {
t.Run(fmt.Sprintf("Migrate-%s-%s", dialect, version), func(t *testing.T) {
doMigrationTest(t, version)
})
}
}