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>tokarchuk/v1.17
parent
750ac52db2
commit
39ef6f83d5
@ -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" |
@ -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 |
||||
} |
||||
} |
||||
} |
||||
} |
@ -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") |
||||
} |
@ -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) |
||||
} |
||||
} |
||||
|
||||
} |
@ -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) |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue