2021-03-24 21:27:22 +03:00
// 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 (
2021-12-01 10:50:01 +03:00
"context"
2021-03-24 21:27:22 +03:00
"database/sql"
"fmt"
"os"
"path"
"path/filepath"
"runtime"
"testing"
"time"
2021-09-19 14:49:59 +03:00
"code.gitea.io/gitea/models/db"
2021-11-12 17:36:47 +03:00
"code.gitea.io/gitea/models/unittest"
2021-03-24 21:27:22 +03:00
"code.gitea.io/gitea/modules/base"
2021-06-26 14:28:55 +03:00
"code.gitea.io/gitea/modules/git"
2021-03-24 21:27:22 +03:00
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
2021-09-19 14:49:59 +03:00
2021-03-24 21:27:22 +03:00
"github.com/stretchr/testify/assert"
"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 == "" {
2022-09-02 22:18:23 +03:00
giteaConf = path . Join ( filepath . Dir ( setting . AppPath ) , "tests/sqlite.ini" )
2021-03-24 21:27:22 +03:00
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
}
2022-01-10 04:48:13 +03:00
tmpDataPath , err := os . MkdirTemp ( "" , "data" )
2021-12-10 22:43:28 +03:00
if err != nil {
fmt . Printf ( "Unable to create temporary data path %v\n" , err )
os . Exit ( 1 )
}
setting . AppDataPath = tmpDataPath
2021-03-24 21:27:22 +03:00
setting . SetCustomPathAndConf ( "" , "" , "" )
2021-12-01 10:50:01 +03:00
setting . LoadForTest ( )
2022-08-09 06:22:24 +03:00
if err = git . InitFull ( context . Background ( ) ) ; err != nil {
fmt . Printf ( "Unable to InitFull: %v\n" , err )
2022-06-16 18:47:44 +03:00
os . Exit ( 1 )
}
2021-03-24 21:27:22 +03:00
setting . InitDBConfig ( )
setting . NewLogServices ( true )
exitStatus := m . Run ( )
if err := removeAllWithRetry ( setting . RepoRootPath ) ; err != nil {
fmt . Fprintf ( os . Stderr , "os.RemoveAll: %v\n" , err )
}
2021-12-10 22:43:28 +03:00
if err := removeAllWithRetry ( tmpDataPath ) ; err != nil {
2021-03-24 21:27:22 +03:00
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
}
2021-12-01 10:50:01 +03:00
func newXORMEngine ( ) ( * xorm . Engine , error ) {
if err := db . InitEngine ( context . Background ( ) ) ; err != nil {
return nil , err
2021-03-24 21:27:22 +03:00
}
2021-12-01 10:50:01 +03:00
x := unittest . GetXORMEngine ( )
2021-03-24 21:27:22 +03:00
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 ) )
2022-09-02 22:18:23 +03:00
assert . NoError ( t , unittest . CopyDir ( path . Join ( filepath . Dir ( setting . AppPath ) , "tests/gitea-repositories-meta" ) , setting . RepoRootPath ) )
2021-12-16 22:01:14 +03:00
ownerDirs , err := os . ReadDir ( setting . RepoRootPath )
if err != nil {
assert . NoError ( t , err , "unable to read the new repo root: %v\n" , err )
}
for _ , ownerDir := range ownerDirs {
if ! ownerDir . Type ( ) . IsDir ( ) {
continue
}
repoDirs , err := os . ReadDir ( filepath . Join ( setting . RepoRootPath , ownerDir . Name ( ) ) )
if err != nil {
assert . NoError ( t , err , "unable to read the new repo root: %v\n" , err )
}
for _ , repoDir := range repoDirs {
2022-01-20 20:46:10 +03:00
_ = os . MkdirAll ( filepath . Join ( setting . RepoRootPath , ownerDir . Name ( ) , repoDir . Name ( ) , "objects" , "pack" ) , 0 o755 )
_ = os . MkdirAll ( filepath . Join ( setting . RepoRootPath , ownerDir . Name ( ) , repoDir . Name ( ) , "objects" , "info" ) , 0 o755 )
_ = os . MkdirAll ( filepath . Join ( setting . RepoRootPath , ownerDir . Name ( ) , repoDir . Name ( ) , "refs" , "heads" ) , 0 o755 )
_ = os . MkdirAll ( filepath . Join ( setting . RepoRootPath , ownerDir . Name ( ) , repoDir . Name ( ) , "refs" , "tag" ) , 0 o755 )
2021-12-16 22:01:14 +03:00
}
}
2021-03-24 21:27:22 +03:00
if err := deleteDB ( ) ; err != nil {
t . Errorf ( "unable to reset database: %v" , err )
return nil , deferFn
}
2021-12-01 10:50:01 +03:00
x , err := newXORMEngine ( )
2021-03-24 21:27:22 +03:00
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 )
}
2021-07-24 13:16:34 +03:00
if err := deleteDB ( ) ; err != nil {
t . Errorf ( "unable to reset database: %v" , err )
}
2021-03-24 21:27:22 +03:00
}
}
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 )
2021-11-12 17:36:47 +03:00
if err := unittest . InitFixtures (
unittest . FixturesOptions {
2021-09-24 14:32:56 +03:00
Dir : fixturesDir ,
} , x ) ; err != nil {
2021-03-24 21:27:22 +03:00
t . Errorf ( "error whilst initializing fixtures from %s: %v" , fixturesDir , err )
return x , deferFn
}
2021-11-12 17:36:47 +03:00
if err := unittest . LoadFixtures ( x ) ; err != nil {
2021-03-24 21:27:22 +03:00
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
}
}
}
}