mirror of
https://codeberg.org/forgejo/forgejo
synced 2024-11-25 19:26:09 +01:00
Create Proper Migration Tests (#15116)
* Create Proper Migration tests Unfortunately our testing regime has so far meant that migrations do not get proper testing. This PR begins the process of creating migration tests for this. * Add test for v176 * fix mssql drop db Signed-off-by: Andrew Thornton <art27@cantab.net>
This commit is contained in:
parent
750ac52db2
commit
39ef6f83d5
37
Makefile
37
Makefile
|
@ -89,7 +89,7 @@ LDFLAGS := $(LDFLAGS) -X "main.MakeVersion=$(MAKE_VERSION)" -X "main.Version=$(G
|
|||
|
||||
LINUX_ARCHS ?= linux/amd64,linux/386,linux/arm-5,linux/arm-6,linux/arm64
|
||||
|
||||
GO_PACKAGES ?= $(filter-out code.gitea.io/gitea/integrations/migration-test,$(filter-out code.gitea.io/gitea/integrations,$(shell $(GO) list -mod=vendor ./... | grep -v /vendor/)))
|
||||
GO_PACKAGES ?= $(filter-out code.gitea.io/gitea/models/migrations code.gitea.io/gitea/integrations/migration-test code.gitea.io/gitea/integrations,$(shell $(GO) list -mod=vendor ./... | grep -v /vendor/))
|
||||
|
||||
FOMANTIC_CONFIGS := semantic.json web_src/fomantic/theme.config.less web_src/fomantic/_site/globals/site.variables
|
||||
FOMANTIC_DEST := web_src/fomantic/build/semantic.js web_src/fomantic/build/semantic.css
|
||||
|
@ -399,8 +399,9 @@ test-sqlite\#%: integrations.sqlite.test generate-ini-sqlite
|
|||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./integrations.sqlite.test -test.run $(subst .,/,$*)
|
||||
|
||||
.PHONY: test-sqlite-migration
|
||||
test-sqlite-migration: migrations.sqlite.test generate-ini-sqlite
|
||||
test-sqlite-migration: migrations.sqlite.test migrations.individual.sqlite.test generate-ini-sqlite
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.sqlite.test
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/sqlite.ini ./migrations.individual.sqlite.test
|
||||
|
||||
generate-ini-mysql:
|
||||
sed -e 's|{{TEST_MYSQL_HOST}}|${TEST_MYSQL_HOST}|g' \
|
||||
|
@ -419,8 +420,9 @@ test-mysql\#%: integrations.mysql.test generate-ini-mysql
|
|||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql.ini ./integrations.mysql.test -test.run $(subst .,/,$*)
|
||||
|
||||
.PHONY: test-mysql-migration
|
||||
test-mysql-migration: migrations.mysql.test generate-ini-mysql
|
||||
test-mysql-migration: migrations.mysql.test migrations.individual.mysql.test generate-ini-mysql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql.ini ./migrations.mysql.test
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql.ini ./migrations.individual.mysql.test
|
||||
|
||||
generate-ini-mysql8:
|
||||
sed -e 's|{{TEST_MYSQL8_HOST}}|${TEST_MYSQL8_HOST}|g' \
|
||||
|
@ -439,8 +441,9 @@ test-mysql8\#%: integrations.mysql8.test generate-ini-mysql8
|
|||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql8.ini ./integrations.mysql8.test -test.run $(subst .,/,$*)
|
||||
|
||||
.PHONY: test-mysql8-migration
|
||||
test-mysql8-migration: migrations.mysql8.test generate-ini-mysql8
|
||||
test-mysql8-migration: migrations.mysql8.test migrations.individual.mysql8.test generate-ini-mysql8
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql8.ini ./migrations.mysql8.test
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mysql8.ini ./migrations.individual.mysql8.test
|
||||
|
||||
generate-ini-pgsql:
|
||||
sed -e 's|{{TEST_PGSQL_HOST}}|${TEST_PGSQL_HOST}|g' \
|
||||
|
@ -460,8 +463,9 @@ test-pgsql\#%: integrations.pgsql.test generate-ini-pgsql
|
|||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/pgsql.ini ./integrations.pgsql.test -test.run $(subst .,/,$*)
|
||||
|
||||
.PHONY: test-pgsql-migration
|
||||
test-pgsql-migration: migrations.pgsql.test generate-ini-pgsql
|
||||
test-pgsql-migration: migrations.pgsql.test migrations.individual.pgsql.test generate-ini-pgsql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/pgsql.ini ./migrations.pgsql.test
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/pgsql.ini ./migrations.individual.pgsql.test
|
||||
|
||||
generate-ini-mssql:
|
||||
sed -e 's|{{TEST_MSSQL_HOST}}|${TEST_MSSQL_HOST}|g' \
|
||||
|
@ -480,8 +484,9 @@ test-mssql\#%: integrations.mssql.test generate-ini-mssql
|
|||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mssql.ini ./integrations.mssql.test -test.run $(subst .,/,$*)
|
||||
|
||||
.PHONY: test-mssql-migration
|
||||
test-mssql-migration: migrations.mssql.test generate-ini-mssql
|
||||
test-mssql-migration: migrations.mssql.test migrations.individual.mssql.test generate-ini-mssql
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mssql.ini ./migrations.mssql.test -test.failfast
|
||||
GITEA_ROOT="$(CURDIR)" GITEA_CONF=integrations/mssql.ini ./migrations.individual.mssql.test -test.failfast
|
||||
|
||||
.PHONY: bench-sqlite
|
||||
bench-sqlite: integrations.sqlite.test generate-ini-sqlite
|
||||
|
@ -541,6 +546,26 @@ migrations.mssql.test: $(GO_SOURCES)
|
|||
migrations.sqlite.test: $(GO_SOURCES)
|
||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/integrations/migration-test -o migrations.sqlite.test -tags '$(TEST_TAGS)'
|
||||
|
||||
.PHONY: migrations.individual.mysql.test
|
||||
migrations.individual.mysql.test: $(GO_SOURCES)
|
||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/models/migrations -o migrations.individual.mysql.test
|
||||
|
||||
.PHONY: migrations.individual.mysql8.test
|
||||
migrations.individual.mysql8.test: $(GO_SOURCES)
|
||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/models/migrations -o migrations.individual.mysql8.test
|
||||
|
||||
.PHONY: migrations.individual.pgsql.test
|
||||
migrations.individual.pgsql.test: $(GO_SOURCES)
|
||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/models/migrations -o migrations.individual.pgsql.test
|
||||
|
||||
.PHONY: migrations.individual.mssql.test
|
||||
migrations.individual.mssql.test: $(GO_SOURCES)
|
||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/models/migrations -o migrations.individual.mssql.test
|
||||
|
||||
.PHONY: migrations.individual.sqlite.test
|
||||
migrations.individual.sqlite.test: $(GO_SOURCES)
|
||||
$(GO) test $(GOTESTFLAGS) -c code.gitea.io/gitea/models/migrations -o migrations.individual.sqlite.test -tags '$(TEST_TAGS)'
|
||||
|
||||
.PHONY: check
|
||||
check: test
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
# Issue_Label 1 should not be deleted
|
||||
-
|
||||
id: 1
|
||||
issue_id: 1
|
||||
label_id: 1
|
||||
|
||||
# Issue_label 2 should be deleted
|
||||
-
|
||||
id: 2
|
||||
issue_id: 5
|
||||
label_id: 99
|
||||
|
||||
# Issue_Label 3 should not be deleted
|
||||
-
|
||||
id: 3
|
||||
issue_id: 2
|
||||
label_id: 1
|
||||
|
||||
# Issue_Label 4 should not be deleted
|
||||
-
|
||||
id: 4
|
||||
issue_id: 2
|
||||
label_id: 4
|
||||
|
||||
-
|
||||
id: 5
|
||||
issue_id: 2
|
||||
label_id: 87
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
-
|
||||
id: 1
|
||||
repo_id: 1
|
||||
org_id: 0
|
||||
name: label1
|
||||
color: '#abcdef'
|
||||
num_issues: 2
|
||||
num_closed_issues: 0
|
||||
|
||||
-
|
||||
id: 2
|
||||
repo_id: 1
|
||||
org_id: 0
|
||||
name: label2
|
||||
color: '#000000'
|
||||
num_issues: 1
|
||||
num_closed_issues: 1
|
||||
-
|
||||
id: 3
|
||||
repo_id: 0
|
||||
org_id: 3
|
||||
name: orglabel3
|
||||
color: '#abcdef'
|
||||
num_issues: 0
|
||||
num_closed_issues: 0
|
||||
|
||||
-
|
||||
id: 4
|
||||
repo_id: 0
|
||||
org_id: 3
|
||||
name: orglabel4
|
||||
color: '#000000'
|
||||
num_issues: 1
|
||||
num_closed_issues: 0
|
||||
|
||||
-
|
||||
id: 5
|
||||
repo_id: 10
|
||||
org_id: 0
|
||||
name: pull-test-label
|
||||
color: '#000000'
|
||||
num_issues: 0
|
||||
num_closed_issues: 0
|
|
@ -0,0 +1,52 @@
|
|||
# type Comment struct {
|
||||
# ID int64 `xorm:"pk autoincr"`
|
||||
# Type int `xorm:"INDEX"`
|
||||
# IssueID int64 `xorm:"INDEX"`
|
||||
# LabelID int64
|
||||
# }
|
||||
#
|
||||
# we are only interested in type 7
|
||||
#
|
||||
|
||||
-
|
||||
id: 1 # Should remain
|
||||
type: 6
|
||||
issue_id: 1
|
||||
label_id: 0
|
||||
should_remain: true
|
||||
-
|
||||
id: 2 # Should remain
|
||||
type: 7
|
||||
issue_id: 1 # repo_id: 1
|
||||
label_id: 1 # repo_id: 1
|
||||
should_remain: true
|
||||
-
|
||||
id: 3 # Should remain
|
||||
type: 7
|
||||
issue_id: 2 # repo_id: 2 owner_id: 1
|
||||
label_id: 2 # org_id: 1
|
||||
should_remain: true
|
||||
-
|
||||
id: 4 # Should be DELETED
|
||||
type: 7
|
||||
issue_id: 1 # repo_id: 1
|
||||
label_id: 3 # repo_id: 2
|
||||
should_remain: false
|
||||
-
|
||||
id: 5 # Should remain
|
||||
type: 7
|
||||
issue_id: 3 # repo_id: 1
|
||||
label_id: 1 # repo_id: 1
|
||||
should_remain: true
|
||||
-
|
||||
id: 6 # Should be DELETED
|
||||
type: 7
|
||||
issue_id: 3 # repo_id: 1 owner_id: 2
|
||||
label_id: 2 # org_id: 1
|
||||
should_remain: false
|
||||
-
|
||||
id: 7 # Should be DELETED
|
||||
type: 7
|
||||
issue_id: 3 # repo_id: 1 owner_id: 2
|
||||
label_id: 5 # repo_id: 3
|
||||
should_remain: false
|
|
@ -0,0 +1,21 @@
|
|||
# type Issue struct {
|
||||
# ID int64 `xorm:"pk autoincr"`
|
||||
# RepoID int64 `xorm:"INDEX UNIQUE(repo_index)"`
|
||||
# Index int64 `xorm:"UNIQUE(repo_index)"` // Index in one repository.
|
||||
# }
|
||||
-
|
||||
id: 1
|
||||
repo_id: 1
|
||||
index: 1
|
||||
-
|
||||
id: 2
|
||||
repo_id: 2
|
||||
index: 1
|
||||
-
|
||||
id: 3
|
||||
repo_id: 1
|
||||
index: 2
|
||||
-
|
||||
id: 4
|
||||
repo_id: 3
|
||||
index: 1
|
|
@ -0,0 +1,35 @@
|
|||
# type IssueLabel struct {
|
||||
# ID int64 `xorm:"pk autoincr"`
|
||||
# IssueID int64 `xorm:"UNIQUE(s)"`
|
||||
# LabelID int64 `xorm:"UNIQUE(s)"`
|
||||
# }
|
||||
-
|
||||
id: 1 # Should remain - matches comment 2
|
||||
issue_id: 1
|
||||
label_id: 1
|
||||
should_remain: true
|
||||
-
|
||||
id: 2 # Should remain
|
||||
issue_id: 2
|
||||
label_id: 2
|
||||
should_remain: true
|
||||
-
|
||||
id: 3 # Should be deleted
|
||||
issue_id: 1
|
||||
label_id: 3
|
||||
should_remain: false
|
||||
-
|
||||
id: 4 # Should remain
|
||||
issue_id: 3
|
||||
label_id: 1
|
||||
should_remain: true
|
||||
-
|
||||
id: 5 # Should be deleted
|
||||
issue_id: 3
|
||||
label_id: 2
|
||||
should_remain: false
|
||||
-
|
||||
id: 6 # Should be deleted
|
||||
issue_id: 3
|
||||
label_id: 5
|
||||
should_remain: false
|
|
@ -0,0 +1,26 @@
|
|||
# type Label struct {
|
||||
# ID int64 `xorm:"pk autoincr"`
|
||||
# RepoID int64 `xorm:"INDEX"`
|
||||
# OrgID int64 `xorm:"INDEX"`
|
||||
# }
|
||||
-
|
||||
id: 1
|
||||
repo_id: 1
|
||||
org_id: 0
|
||||
-
|
||||
id: 2
|
||||
repo_id: 0
|
||||
org_id: 1
|
||||
-
|
||||
id: 3
|
||||
repo_id: 2
|
||||
org_id: 0
|
||||
-
|
||||
id: 4
|
||||
repo_id: 1
|
||||
org_id: 0
|
||||
-
|
||||
id: 5
|
||||
repo_id: 3
|
||||
org_id: 0
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# type Repository struct {
|
||||
# ID int64 `xorm:"pk autoincr"`
|
||||
# OwnerID int64 `xorm:"UNIQUE(s) index"`
|
||||
# LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
# }
|
||||
-
|
||||
id: 1
|
||||
owner_id: 2
|
||||
lower_name: "repo1"
|
||||
-
|
||||
id: 2
|
||||
owner_id: 1
|
||||
lower_name: "repo2"
|
||||
-
|
||||
id: 3
|
||||
owner_id: 2
|
||||
lower_name: "repo3"
|
|
@ -819,9 +819,24 @@ func dropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin
|
|||
}
|
||||
for _, constraint := range constraints {
|
||||
if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` DROP CONSTRAINT `%s`", tableName, constraint)); err != nil {
|
||||
return fmt.Errorf("Drop table `%s` constraint `%s`: %v", tableName, constraint, err)
|
||||
return fmt.Errorf("Drop table `%s` default constraint `%s`: %v", tableName, constraint, err)
|
||||
}
|
||||
}
|
||||
sql = fmt.Sprintf("SELECT DISTINCT Name FROM SYS.INDEXES INNER JOIN SYS.INDEX_COLUMNS ON INDEXES.INDEX_ID = INDEX_COLUMNS.INDEX_ID AND INDEXES.OBJECT_ID = INDEX_COLUMNS.OBJECT_ID WHERE INDEXES.OBJECT_ID = OBJECT_ID('%[1]s') AND INDEX_COLUMNS.COLUMN_ID IN (SELECT column_id FROM sys.columns WHERE lower(NAME) IN (%[2]s) AND object_id = OBJECT_ID('%[1]s'))",
|
||||
tableName, strings.ReplaceAll(cols, "`", "'"))
|
||||
constraints = make([]string, 0)
|
||||
if err := sess.SQL(sql).Find(&constraints); err != nil {
|
||||
return fmt.Errorf("Find constraints: %v", err)
|
||||
}
|
||||
for _, constraint := range constraints {
|
||||
if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` DROP CONSTRAINT IF EXISTS `%s`", tableName, constraint)); err != nil {
|
||||
return fmt.Errorf("Drop table `%s` index constraint `%s`: %v", tableName, constraint, err)
|
||||
}
|
||||
if _, err := sess.Exec(fmt.Sprintf("DROP INDEX IF EXISTS `%[2]s` ON `%[1]s`", tableName, constraint)); err != nil {
|
||||
return fmt.Errorf("Drop index `%[2]s` on `%[1]s`: %v", tableName, constraint, err)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` DROP COLUMN %s", tableName, cols)); err != nil {
|
||||
return fmt.Errorf("Drop table `%s` columns %v: %v", tableName, columnNames, err)
|
||||
}
|
||||
|
|
338
models/migrations/migrations_test.go
Normal file
338
models/migrations/migrations_test.go
Normal file
|
@ -0,0 +1,338 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/unknwon/com"
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/names"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
giteaRoot := base.SetupGiteaRoot()
|
||||
if giteaRoot == "" {
|
||||
fmt.Println("Environment variable $GITEA_ROOT not set")
|
||||
os.Exit(1)
|
||||
}
|
||||
giteaBinary := "gitea"
|
||||
if runtime.GOOS == "windows" {
|
||||
giteaBinary += ".exe"
|
||||
}
|
||||
setting.AppPath = path.Join(giteaRoot, giteaBinary)
|
||||
if _, err := os.Stat(setting.AppPath); err != nil {
|
||||
fmt.Printf("Could not find gitea binary at %s\n", setting.AppPath)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
giteaConf := os.Getenv("GITEA_CONF")
|
||||
if giteaConf == "" {
|
||||
giteaConf = path.Join(filepath.Dir(setting.AppPath), "integrations/sqlite.ini")
|
||||
fmt.Printf("Environment variable $GITEA_CONF not set - defaulting to %s\n", giteaConf)
|
||||
}
|
||||
|
||||
if !path.IsAbs(giteaConf) {
|
||||
setting.CustomConf = path.Join(giteaRoot, giteaConf)
|
||||
} else {
|
||||
setting.CustomConf = giteaConf
|
||||
}
|
||||
|
||||
setting.SetCustomPathAndConf("", "", "")
|
||||
setting.NewContext()
|
||||
setting.CheckLFSVersion()
|
||||
setting.InitDBConfig()
|
||||
setting.NewLogServices(true)
|
||||
|
||||
exitStatus := m.Run()
|
||||
|
||||
if err := removeAllWithRetry(setting.RepoRootPath); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "os.RemoveAll: %v\n", err)
|
||||
}
|
||||
if err := removeAllWithRetry(setting.AppDataPath); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "os.RemoveAll: %v\n", err)
|
||||
}
|
||||
os.Exit(exitStatus)
|
||||
}
|
||||
|
||||
func removeAllWithRetry(dir string) error {
|
||||
var err error
|
||||
for i := 0; i < 20; i++ {
|
||||
err = os.RemoveAll(dir)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// SetEngine sets the xorm.Engine
|
||||
func SetEngine() (*xorm.Engine, error) {
|
||||
x, err := models.GetNewEngine()
|
||||
if err != nil {
|
||||
return x, fmt.Errorf("Failed to connect to database: %v", err)
|
||||
}
|
||||
|
||||
x.SetMapper(names.GonicMapper{})
|
||||
// WARNING: for serv command, MUST remove the output to os.stdout,
|
||||
// so use log file to instead print to stdout.
|
||||
x.SetLogger(models.NewXORMLogger(setting.Database.LogSQL))
|
||||
x.ShowSQL(setting.Database.LogSQL)
|
||||
x.SetMaxOpenConns(setting.Database.MaxOpenConns)
|
||||
x.SetMaxIdleConns(setting.Database.MaxIdleConns)
|
||||
x.SetConnMaxLifetime(setting.Database.ConnMaxLifetime)
|
||||
return x, nil
|
||||
}
|
||||
|
||||
func deleteDB() error {
|
||||
switch {
|
||||
case setting.Database.UseSQLite3:
|
||||
if err := util.Remove(setting.Database.Path); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.MkdirAll(path.Dir(setting.Database.Path), os.ModePerm)
|
||||
|
||||
case setting.Database.UseMySQL:
|
||||
db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/",
|
||||
setting.Database.User, setting.Database.Passwd, setting.Database.Host))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
if _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", setting.Database.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", setting.Database.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case setting.Database.UsePostgreSQL:
|
||||
db, err := sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/?sslmode=%s",
|
||||
setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.SSLMode))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
if _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", setting.Database.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", setting.Database.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
db.Close()
|
||||
|
||||
// Check if we need to setup a specific schema
|
||||
if len(setting.Database.Schema) != 0 {
|
||||
db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s",
|
||||
setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.Name, setting.Database.SSLMode))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
schrows, err := db.Query(fmt.Sprintf("SELECT 1 FROM information_schema.schemata WHERE schema_name = '%s'", setting.Database.Schema))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer schrows.Close()
|
||||
|
||||
if !schrows.Next() {
|
||||
// Create and setup a DB schema
|
||||
_, err = db.Exec(fmt.Sprintf("CREATE SCHEMA %s", setting.Database.Schema))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Make the user's default search path the created schema; this will affect new connections
|
||||
_, err = db.Exec(fmt.Sprintf(`ALTER USER "%s" SET search_path = %s`, setting.Database.User, setting.Database.Schema))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
case setting.Database.UseMSSQL:
|
||||
host, port := setting.ParseMSSQLHostPort(setting.Database.Host)
|
||||
db, err := sql.Open("mssql", fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;",
|
||||
host, port, "master", setting.Database.User, setting.Database.Passwd))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
if _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS [%s]", setting.Database.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE [%s]", setting.Database.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// prepareTestEnv prepares the test environment and reset the database. The skip parameter should usually be 0.
|
||||
// Provide models to be sync'd with the database - in particular any models you expect fixtures to be loaded from.
|
||||
//
|
||||
// fixtures in `models/migrations/fixtures/<TestName>` will be loaded automatically
|
||||
func prepareTestEnv(t *testing.T, skip int, syncModels ...interface{}) (*xorm.Engine, func()) {
|
||||
t.Helper()
|
||||
ourSkip := 2
|
||||
ourSkip += skip
|
||||
deferFn := PrintCurrentTest(t, ourSkip)
|
||||
assert.NoError(t, os.RemoveAll(setting.RepoRootPath))
|
||||
|
||||
assert.NoError(t, com.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"),
|
||||
setting.RepoRootPath))
|
||||
|
||||
if err := deleteDB(); err != nil {
|
||||
t.Errorf("unable to reset database: %v", err)
|
||||
return nil, deferFn
|
||||
}
|
||||
|
||||
x, err := SetEngine()
|
||||
assert.NoError(t, err)
|
||||
if x != nil {
|
||||
oldDefer := deferFn
|
||||
deferFn = func() {
|
||||
oldDefer()
|
||||
if err := x.Close(); err != nil {
|
||||
t.Errorf("error during close: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return x, deferFn
|
||||
}
|
||||
|
||||
if len(syncModels) > 0 {
|
||||
if err := x.Sync2(syncModels...); err != nil {
|
||||
t.Errorf("error during sync: %v", err)
|
||||
return x, deferFn
|
||||
}
|
||||
}
|
||||
|
||||
fixturesDir := filepath.Join(filepath.Dir(setting.AppPath), "models", "migrations", "fixtures", t.Name())
|
||||
|
||||
if _, err := os.Stat(fixturesDir); err == nil {
|
||||
t.Logf("initializing fixtures from: %s", fixturesDir)
|
||||
if err := models.InitFixtures(fixturesDir, x); err != nil {
|
||||
t.Errorf("error whilst initializing fixtures from %s: %v", fixturesDir, err)
|
||||
return x, deferFn
|
||||
}
|
||||
if err := models.LoadFixtures(x); err != nil {
|
||||
t.Errorf("error whilst loading fixtures from %s: %v", fixturesDir, err)
|
||||
return x, deferFn
|
||||
}
|
||||
} else if !os.IsNotExist(err) {
|
||||
t.Errorf("unexpected error whilst checking for existence of fixtures: %v", err)
|
||||
} else {
|
||||
t.Logf("no fixtures found in: %s", fixturesDir)
|
||||
}
|
||||
|
||||
return x, deferFn
|
||||
}
|
||||
|
||||
func Test_dropTableColumns(t *testing.T) {
|
||||
x, deferable := prepareTestEnv(t, 0)
|
||||
if x == nil || t.Failed() {
|
||||
defer deferable()
|
||||
return
|
||||
}
|
||||
defer deferable()
|
||||
|
||||
type DropTest struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
FirstColumn string
|
||||
ToDropColumn string `xorm:"unique"`
|
||||
AnotherColumn int64
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||
}
|
||||
|
||||
columns := []string{
|
||||
"first_column",
|
||||
"to_drop_column",
|
||||
"another_column",
|
||||
"created_unix",
|
||||
"updated_unix",
|
||||
}
|
||||
|
||||
for i := range columns {
|
||||
x.SetMapper(names.GonicMapper{})
|
||||
if err := x.Sync2(new(DropTest)); err != nil {
|
||||
t.Errorf("unable to create DropTest table: %v", err)
|
||||
return
|
||||
}
|
||||
sess := x.NewSession()
|
||||
if err := sess.Begin(); err != nil {
|
||||
sess.Close()
|
||||
t.Errorf("unable to begin transaction: %v", err)
|
||||
return
|
||||
}
|
||||
if err := dropTableColumns(sess, "drop_test", columns[i:]...); err != nil {
|
||||
sess.Close()
|
||||
t.Errorf("Unable to drop columns[%d:]: %s from drop_test: %v", i, columns[i:], err)
|
||||
return
|
||||
}
|
||||
if err := sess.Commit(); err != nil {
|
||||
sess.Close()
|
||||
t.Errorf("unable to commit transaction: %v", err)
|
||||
return
|
||||
}
|
||||
sess.Close()
|
||||
if err := x.DropTables(new(DropTest)); err != nil {
|
||||
t.Errorf("unable to drop table: %v", err)
|
||||
return
|
||||
}
|
||||
for j := range columns[i+1:] {
|
||||
x.SetMapper(names.GonicMapper{})
|
||||
if err := x.Sync2(new(DropTest)); err != nil {
|
||||
t.Errorf("unable to create DropTest table: %v", err)
|
||||
return
|
||||
}
|
||||
dropcols := append([]string{columns[i]}, columns[j+i+1:]...)
|
||||
sess := x.NewSession()
|
||||
if err := sess.Begin(); err != nil {
|
||||
sess.Close()
|
||||
t.Errorf("unable to begin transaction: %v", err)
|
||||
return
|
||||
}
|
||||
if err := dropTableColumns(sess, "drop_test", dropcols...); err != nil {
|
||||
sess.Close()
|
||||
t.Errorf("Unable to drop columns: %s from drop_test: %v", dropcols, err)
|
||||
return
|
||||
}
|
||||
if err := sess.Commit(); err != nil {
|
||||
sess.Close()
|
||||
t.Errorf("unable to commit transaction: %v", err)
|
||||
return
|
||||
}
|
||||
sess.Close()
|
||||
if err := x.DropTables(new(DropTest)); err != nil {
|
||||
t.Errorf("unable to drop table: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
188
models/migrations/testlogger_test.go
Normal file
188
models/migrations/testlogger_test.go
Normal file
|
@ -0,0 +1,188 @@
|
|||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/queue"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
var (
|
||||
prefix string
|
||||
slowTest = 10 * time.Second
|
||||
slowFlush = 5 * time.Second
|
||||
)
|
||||
|
||||
// TestLogger is a logger which will write to the testing log
|
||||
type TestLogger struct {
|
||||
log.WriterLogger
|
||||
}
|
||||
|
||||
var writerCloser = &testLoggerWriterCloser{}
|
||||
|
||||
type testLoggerWriterCloser struct {
|
||||
sync.RWMutex
|
||||
t []*testing.TB
|
||||
}
|
||||
|
||||
func (w *testLoggerWriterCloser) setT(t *testing.TB) {
|
||||
w.Lock()
|
||||
w.t = append(w.t, t)
|
||||
w.Unlock()
|
||||
}
|
||||
|
||||
func (w *testLoggerWriterCloser) Write(p []byte) (int, error) {
|
||||
w.RLock()
|
||||
var t *testing.TB
|
||||
if len(w.t) > 0 {
|
||||
t = w.t[len(w.t)-1]
|
||||
}
|
||||
w.RUnlock()
|
||||
if t != nil && *t != nil {
|
||||
if len(p) > 0 && p[len(p)-1] == '\n' {
|
||||
p = p[:len(p)-1]
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
var errString string
|
||||
errErr, ok := err.(error)
|
||||
if ok {
|
||||
errString = errErr.Error()
|
||||
} else {
|
||||
errString, ok = err.(string)
|
||||
}
|
||||
if !ok {
|
||||
panic(err)
|
||||
}
|
||||
if !strings.HasPrefix(errString, "Log in goroutine after ") {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
(*t).Log(string(p))
|
||||
return len(p), nil
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (w *testLoggerWriterCloser) Close() error {
|
||||
w.Lock()
|
||||
if len(w.t) > 0 {
|
||||
w.t = w.t[:len(w.t)-1]
|
||||
}
|
||||
w.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// PrintCurrentTest prints the current test to os.Stdout
|
||||
func PrintCurrentTest(t testing.TB, skip ...int) func() {
|
||||
start := time.Now()
|
||||
actualSkip := 1
|
||||
if len(skip) > 0 {
|
||||
actualSkip = skip[0]
|
||||
}
|
||||
_, filename, line, _ := runtime.Caller(actualSkip)
|
||||
|
||||
if log.CanColorStdout {
|
||||
fmt.Fprintf(os.Stdout, "=== %s (%s:%d)\n", fmt.Formatter(log.NewColoredValue(t.Name())), strings.TrimPrefix(filename, prefix), line)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stdout, "=== %s (%s:%d)\n", t.Name(), strings.TrimPrefix(filename, prefix), line)
|
||||
}
|
||||
writerCloser.setT(&t)
|
||||
return func() {
|
||||
took := time.Since(start)
|
||||
if took > slowTest {
|
||||
if log.CanColorStdout {
|
||||
fmt.Fprintf(os.Stdout, "+++ %s is a slow test (took %v)\n", fmt.Formatter(log.NewColoredValue(t.Name(), log.Bold, log.FgYellow)), fmt.Formatter(log.NewColoredValue(took, log.Bold, log.FgYellow)))
|
||||
} else {
|
||||
fmt.Fprintf(os.Stdout, "+++ %s is a slow tets (took %v)\n", t.Name(), took)
|
||||
}
|
||||
}
|
||||
timer := time.AfterFunc(slowFlush, func() {
|
||||
if log.CanColorStdout {
|
||||
fmt.Fprintf(os.Stdout, "+++ %s ... still flushing after %v ...\n", fmt.Formatter(log.NewColoredValue(t.Name(), log.Bold, log.FgRed)), slowFlush)
|
||||
} else {
|
||||
fmt.Fprintf(os.Stdout, "+++ %s ... still flushing after %v ...\n", t.Name(), slowFlush)
|
||||
}
|
||||
})
|
||||
if err := queue.GetManager().FlushAll(context.Background(), -1); err != nil {
|
||||
t.Errorf("Flushing queues failed with error %v", err)
|
||||
}
|
||||
timer.Stop()
|
||||
flushTook := time.Since(start) - took
|
||||
if flushTook > slowFlush {
|
||||
if log.CanColorStdout {
|
||||
fmt.Fprintf(os.Stdout, "+++ %s had a slow clean-up flush (took %v)\n", fmt.Formatter(log.NewColoredValue(t.Name(), log.Bold, log.FgRed)), fmt.Formatter(log.NewColoredValue(flushTook, log.Bold, log.FgRed)))
|
||||
} else {
|
||||
fmt.Fprintf(os.Stdout, "+++ %s had a slow clean-up flush (took %v)\n", t.Name(), flushTook)
|
||||
}
|
||||
}
|
||||
_ = writerCloser.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Printf takes a format and args and prints the string to os.Stdout
|
||||
func Printf(format string, args ...interface{}) {
|
||||
if log.CanColorStdout {
|
||||
for i := 0; i < len(args); i++ {
|
||||
args[i] = log.NewColoredValue(args[i])
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(os.Stdout, "\t"+format, args...)
|
||||
}
|
||||
|
||||
// NewTestLogger creates a TestLogger as a log.LoggerProvider
|
||||
func NewTestLogger() log.LoggerProvider {
|
||||
logger := &TestLogger{}
|
||||
logger.Colorize = log.CanColorStdout
|
||||
logger.Level = log.TRACE
|
||||
return logger
|
||||
}
|
||||
|
||||
// Init inits connection writer with json config.
|
||||
// json config only need key "level".
|
||||
func (log *TestLogger) Init(config string) error {
|
||||
json := jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
err := json.Unmarshal([]byte(config), log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.NewWriterLogger(writerCloser)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Flush when log should be flushed
|
||||
func (log *TestLogger) Flush() {
|
||||
}
|
||||
|
||||
//ReleaseReopen does nothing
|
||||
func (log *TestLogger) ReleaseReopen() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetName returns the default name for this implementation
|
||||
func (log *TestLogger) GetName() string {
|
||||
return "test"
|
||||
}
|
||||
|
||||
func init() {
|
||||
log.Register("test", NewTestLogger)
|
||||
_, filename, _, _ := runtime.Caller(0)
|
||||
prefix = strings.TrimSuffix(filename, "integrations/testlogger.go")
|
||||
}
|
|
@ -8,6 +8,9 @@ import (
|
|||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// removeInvalidLabels looks through the database to look for comments and issue_labels
|
||||
// that refer to labels do not belong to the repository or organization that repository
|
||||
// that the issue is in
|
||||
func removeInvalidLabels(x *xorm.Engine) error {
|
||||
type Comment struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
|
|
128
models/migrations/v176_test.go
Normal file
128
models/migrations/v176_test.go
Normal file
|
@ -0,0 +1,128 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_removeInvalidLabels(t *testing.T) {
|
||||
// Models used by the migration
|
||||
type Comment struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Type int `xorm:"INDEX"`
|
||||
IssueID int64 `xorm:"INDEX"`
|
||||
LabelID int64
|
||||
ShouldRemain bool // <- Flag for testing the migration
|
||||
}
|
||||
|
||||
type Issue struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"INDEX UNIQUE(repo_index)"`
|
||||
Index int64 `xorm:"UNIQUE(repo_index)"` // Index in one repository.
|
||||
}
|
||||
|
||||
type Repository struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
OwnerID int64 `xorm:"UNIQUE(s) index"`
|
||||
LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
}
|
||||
|
||||
type Label struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"INDEX"`
|
||||
OrgID int64 `xorm:"INDEX"`
|
||||
}
|
||||
|
||||
type IssueLabel struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
IssueID int64 `xorm:"UNIQUE(s)"`
|
||||
LabelID int64 `xorm:"UNIQUE(s)"`
|
||||
ShouldRemain bool // <- Flag for testing the migration
|
||||
}
|
||||
|
||||
// load and prepare the test database
|
||||
x, deferable := prepareTestEnv(t, 0, new(Comment), new(Issue), new(Repository), new(IssueLabel), new(Label))
|
||||
if x == nil || t.Failed() {
|
||||
defer deferable()
|
||||
return
|
||||
}
|
||||
defer deferable()
|
||||
|
||||
var issueLabels []*IssueLabel
|
||||
ilPreMigration := map[int64]*IssueLabel{}
|
||||
ilPostMigration := map[int64]*IssueLabel{}
|
||||
|
||||
var comments []*Comment
|
||||
comPreMigration := map[int64]*Comment{}
|
||||
comPostMigration := map[int64]*Comment{}
|
||||
|
||||
// Get pre migration values
|
||||
if err := x.Find(&issueLabels); err != nil {
|
||||
t.Errorf("Unable to find issueLabels: %v", err)
|
||||
return
|
||||
}
|
||||
for _, issueLabel := range issueLabels {
|
||||
ilPreMigration[issueLabel.ID] = issueLabel
|
||||
}
|
||||
if err := x.Find(&comments); err != nil {
|
||||
t.Errorf("Unable to find comments: %v", err)
|
||||
return
|
||||
}
|
||||
for _, comment := range comments {
|
||||
comPreMigration[comment.ID] = comment
|
||||
}
|
||||
|
||||
// Run the migration
|
||||
if err := removeInvalidLabels(x); err != nil {
|
||||
t.Errorf("unable to RemoveInvalidLabels: %v", err)
|
||||
}
|
||||
|
||||
// Get the post migration values
|
||||
issueLabels = issueLabels[:0]
|
||||
if err := x.Find(&issueLabels); err != nil {
|
||||
t.Errorf("Unable to find issueLabels: %v", err)
|
||||
return
|
||||
}
|
||||
for _, issueLabel := range issueLabels {
|
||||
ilPostMigration[issueLabel.ID] = issueLabel
|
||||
}
|
||||
comments = comments[:0]
|
||||
if err := x.Find(&comments); err != nil {
|
||||
t.Errorf("Unable to find comments: %v", err)
|
||||
return
|
||||
}
|
||||
for _, comment := range comments {
|
||||
comPostMigration[comment.ID] = comment
|
||||
}
|
||||
|
||||
// Finally test results of the migration
|
||||
for id, comment := range comPreMigration {
|
||||
post, ok := comPostMigration[id]
|
||||
if ok {
|
||||
if !comment.ShouldRemain {
|
||||
t.Errorf("Comment[%d] remained but should have been deleted", id)
|
||||
}
|
||||
assert.Equal(t, comment, post)
|
||||
} else if comment.ShouldRemain {
|
||||
t.Errorf("Comment[%d] was deleted but should have remained", id)
|
||||
}
|
||||
}
|
||||
|
||||
for id, il := range ilPreMigration {
|
||||
post, ok := ilPostMigration[id]
|
||||
if ok {
|
||||
if !il.ShouldRemain {
|
||||
t.Errorf("IssueLabel[%d] remained but should have been deleted", id)
|
||||
}
|
||||
assert.Equal(t, il, post)
|
||||
} else if il.ShouldRemain {
|
||||
t.Errorf("IssueLabel[%d] was deleted but should have remained", id)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -10,6 +10,7 @@ import (
|
|||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// deleteOrphanedIssueLabels looks through the database for issue_labels where the label no longer exists and deletes them.
|
||||
func deleteOrphanedIssueLabels(x *xorm.Engine) error {
|
||||
type IssueLabel struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
|
|
88
models/migrations/v177_test.go
Normal file
88
models/migrations/v177_test.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_deleteOrphanedIssueLabels(t *testing.T) {
|
||||
// Create the models used in the migration
|
||||
type IssueLabel struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
IssueID int64 `xorm:"UNIQUE(s)"`
|
||||
LabelID int64 `xorm:"UNIQUE(s)"`
|
||||
}
|
||||
|
||||
type Label struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"INDEX"`
|
||||
OrgID int64 `xorm:"INDEX"`
|
||||
Name string
|
||||
Description string
|
||||
Color string `xorm:"VARCHAR(7)"`
|
||||
NumIssues int
|
||||
NumClosedIssues int
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||
}
|
||||
|
||||
// Prepare and load the testing database
|
||||
x, deferable := prepareTestEnv(t, 0, new(IssueLabel), new(Label))
|
||||
if x == nil || t.Failed() {
|
||||
defer deferable()
|
||||
return
|
||||
}
|
||||
defer deferable()
|
||||
|
||||
var issueLabels []*IssueLabel
|
||||
preMigration := map[int64]*IssueLabel{}
|
||||
postMigration := map[int64]*IssueLabel{}
|
||||
|
||||
// Load issue labels that exist in the database pre-migration
|
||||
if err := x.Find(&issueLabels); err != nil {
|
||||
assert.NoError(t, err)
|
||||
return
|
||||
}
|
||||
for _, issueLabel := range issueLabels {
|
||||
preMigration[issueLabel.ID] = issueLabel
|
||||
}
|
||||
|
||||
// Run the migration
|
||||
if err := deleteOrphanedIssueLabels(x); err != nil {
|
||||
assert.NoError(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Load the remaining issue-labels
|
||||
issueLabels = issueLabels[:0]
|
||||
if err := x.Find(&issueLabels); err != nil {
|
||||
assert.NoError(t, err)
|
||||
return
|
||||
}
|
||||
for _, issueLabel := range issueLabels {
|
||||
postMigration[issueLabel.ID] = issueLabel
|
||||
}
|
||||
|
||||
// Now test what is left
|
||||
if _, ok := postMigration[2]; ok {
|
||||
t.Errorf("Orphaned Label[2] survived the migration")
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok := postMigration[5]; ok {
|
||||
t.Errorf("Orphaned Label[5] survived the migration")
|
||||
return
|
||||
}
|
||||
|
||||
for id, post := range postMigration {
|
||||
pre := preMigration[id]
|
||||
assert.Equal(t, pre, post, "migration changed issueLabel %d", id)
|
||||
}
|
||||
|
||||
}
|
|
@ -142,7 +142,8 @@ func init() {
|
|||
}
|
||||
}
|
||||
|
||||
func getEngine() (*xorm.Engine, error) {
|
||||
// GetNewEngine returns a new xorm engine from the configuration
|
||||
func GetNewEngine() (*xorm.Engine, error) {
|
||||
connStr, err := setting.DBConnStr()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -172,7 +173,7 @@ func getEngine() (*xorm.Engine, error) {
|
|||
|
||||
// NewTestEngine sets a new test xorm.Engine
|
||||
func NewTestEngine() (err error) {
|
||||
x, err = getEngine()
|
||||
x, err = GetNewEngine()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Connect to database: %v", err)
|
||||
}
|
||||
|
@ -185,7 +186,7 @@ func NewTestEngine() (err error) {
|
|||
|
||||
// SetEngine sets the xorm.Engine
|
||||
func SetEngine() (err error) {
|
||||
x, err = getEngine()
|
||||
x, err = GetNewEngine()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to connect to database: %v", err)
|
||||
}
|
||||
|
|
|
@ -10,16 +10,22 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/go-testfixtures/testfixtures/v3"
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
var fixtures *testfixtures.Loader
|
||||
|
||||
// InitFixtures initialize test fixtures for a test database
|
||||
func InitFixtures(dir string) (err error) {
|
||||
func InitFixtures(dir string, engine ...*xorm.Engine) (err error) {
|
||||
e := x
|
||||
if len(engine) == 1 {
|
||||
e = engine[0]
|
||||
}
|
||||
|
||||
testfiles := testfixtures.Directory(dir)
|
||||
dialect := "unknown"
|
||||
switch x.Dialect().URI().DBType {
|
||||
switch e.Dialect().URI().DBType {
|
||||
case schemas.POSTGRES:
|
||||
dialect = "postgres"
|
||||
case schemas.MYSQL:
|
||||
|
@ -33,13 +39,13 @@ func InitFixtures(dir string) (err error) {
|
|||
os.Exit(1)
|
||||
}
|
||||
loaderOptions := []func(loader *testfixtures.Loader) error{
|
||||
testfixtures.Database(x.DB().DB),
|
||||
testfixtures.Database(e.DB().DB),
|
||||
testfixtures.Dialect(dialect),
|
||||
testfixtures.DangerousSkipTestDatabaseCheck(),
|
||||
testfiles,
|
||||
}
|
||||
|
||||
if x.Dialect().URI().DBType == schemas.POSTGRES {
|
||||
if e.Dialect().URI().DBType == schemas.POSTGRES {
|
||||
loaderOptions = append(loaderOptions, testfixtures.SkipResetSequences())
|
||||
}
|
||||
|
||||
|
@ -52,7 +58,11 @@ func InitFixtures(dir string) (err error) {
|
|||
}
|
||||
|
||||
// LoadFixtures load fixtures for a test database
|
||||
func LoadFixtures() error {
|
||||
func LoadFixtures(engine ...*xorm.Engine) error {
|
||||
e := x
|
||||
if len(engine) == 1 {
|
||||
e = engine[0]
|
||||
}
|
||||
var err error
|
||||
// Database transaction conflicts could occur and result in ROLLBACK
|
||||
// As a simple workaround, we just retry 20 times.
|
||||
|
@ -67,8 +77,8 @@ func LoadFixtures() error {
|
|||
fmt.Printf("LoadFixtures failed after retries: %v\n", err)
|
||||
}
|
||||
// Now if we're running postgres we need to tell it to update the sequences
|
||||
if x.Dialect().URI().DBType == schemas.POSTGRES {
|
||||
results, err := x.QueryString(`SELECT 'SELECT SETVAL(' ||
|
||||
if e.Dialect().URI().DBType == schemas.POSTGRES {
|
||||
results, err := e.QueryString(`SELECT 'SELECT SETVAL(' ||
|
||||
quote_literal(quote_ident(PGT.schemaname) || '.' || quote_ident(S.relname)) ||
|
||||
', COALESCE(MAX(' ||quote_ident(C.attname)|| '), 1) ) FROM ' ||
|
||||
quote_ident(PGT.schemaname)|| '.'||quote_ident(T.relname)|| ';'
|
||||
|
@ -90,7 +100,7 @@ func LoadFixtures() error {
|
|||
}
|
||||
for _, r := range results {
|
||||
for _, value := range r {
|
||||
_, err = x.Exec(value)
|
||||
_, err = e.Exec(value)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to update sequence: %s Error: %v\n", value, err)
|
||||
return err
|
||||
|
|
Loading…
Reference in a new issue