diff options
Diffstat (limited to '')
-rw-r--r-- | models/migrations/test/tests.go | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/models/migrations/test/tests.go b/models/migrations/test/tests.go new file mode 100644 index 0000000..0e37233 --- /dev/null +++ b/models/migrations/test/tests.go @@ -0,0 +1,274 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +//nolint:forbidigo +package test + +import ( + "context" + "database/sql" + "fmt" + "os" + "path" + "path/filepath" + "runtime" + "strings" + "testing" + "time" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/testlogger" + "code.gitea.io/gitea/modules/util" + + "github.com/stretchr/testify/require" + "xorm.io/xorm" +) + +// 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 ...any) (*xorm.Engine, func()) { + t.Helper() + ourSkip := 2 + ourSkip += skip + deferFn := testlogger.PrintCurrentTest(t, ourSkip) + require.NoError(t, os.RemoveAll(setting.RepoRootPath)) + require.NoError(t, unittest.CopyDir(path.Join(filepath.Dir(setting.AppPath), "tests/gitea-repositories-meta"), setting.RepoRootPath)) + ownerDirs, err := os.ReadDir(setting.RepoRootPath) + if err != nil { + require.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 { + require.NoError(t, err, "unable to read the new repo root: %v\n", err) + } + for _, repoDir := range repoDirs { + _ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "pack"), 0o755) + _ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "info"), 0o755) + _ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "heads"), 0o755) + _ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "tag"), 0o755) + } + } + + if err := deleteDB(); err != nil { + t.Errorf("unable to reset database: %v", err) + return nil, deferFn + } + + x, err := newXORMEngine() + require.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 := deleteDB(); err != nil { + t.Errorf("unable to reset database: %v", err) + } + } + } + if err != nil { + return x, deferFn + } + + if len(syncModels) > 0 { + if err := x.Sync(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 := unittest.InitFixtures( + unittest.FixturesOptions{ + Dir: fixturesDir, + }, x); err != nil { + t.Errorf("error whilst initializing fixtures from %s: %v", fixturesDir, err) + return x, deferFn + } + if err := unittest.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 MainTest(m *testing.M) { + log.RegisterEventWriter("test", testlogger.NewTestLoggerWriter) + + 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), "tests/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 + } + + tmpDataPath, err := os.MkdirTemp("", "data") + if err != nil { + fmt.Printf("Unable to create temporary data path %v\n", err) + os.Exit(1) + } + + setting.CustomPath = filepath.Join(setting.AppWorkPath, "custom") + setting.AppDataPath = tmpDataPath + + unittest.InitSettings() + if err = git.InitFull(context.Background()); err != nil { + fmt.Printf("Unable to InitFull: %v\n", err) + os.Exit(1) + } + setting.LoadDBSetting() + setting.InitLoggersForTest() + + exitStatus := m.Run() + + if err := testlogger.WriterCloser.Reset(); err != nil && exitStatus == 0 { + fmt.Printf("testlogger.WriterCloser.Reset: error ignored: %v\n", err) + } + if err := removeAllWithRetry(setting.RepoRootPath); err != nil { + fmt.Fprintf(os.Stderr, "os.RemoveAll: %v\n", err) + } + if err := removeAllWithRetry(tmpDataPath); err != nil { + fmt.Fprintf(os.Stderr, "os.RemoveAll: %v\n", err) + } + os.Exit(exitStatus) +} + +func newXORMEngine() (*xorm.Engine, error) { + if err := db.InitEngine(context.Background()); err != nil { + return nil, err + } + x := unittest.GetXORMEngine() + return x, nil +} + +func deleteDB() error { + switch { + case setting.Database.Type.IsSQLite3(): + if err := util.Remove(setting.Database.Path); err != nil { + return err + } + return os.MkdirAll(path.Dir(setting.Database.Path), os.ModePerm) + + case setting.Database.Type.IsMySQL(): + 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() + + databaseName := strings.SplitN(setting.Database.Name, "?", 2)[0] + + if _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", databaseName)); err != nil { + return err + } + + if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", databaseName)); err != nil { + return err + } + return nil + case setting.Database.Type.IsPostgreSQL(): + 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 + } + } + + return nil +} + +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 +} |