code.gitea.io/gitea@v1.21.7/models/unittest/testdb.go (about)

     1  // Copyright 2021 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package unittest
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"log"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  	"testing"
    14  
    15  	"code.gitea.io/gitea/models/db"
    16  	"code.gitea.io/gitea/models/system"
    17  	"code.gitea.io/gitea/modules/auth/password/hash"
    18  	"code.gitea.io/gitea/modules/base"
    19  	"code.gitea.io/gitea/modules/git"
    20  	"code.gitea.io/gitea/modules/setting"
    21  	"code.gitea.io/gitea/modules/setting/config"
    22  	"code.gitea.io/gitea/modules/storage"
    23  	"code.gitea.io/gitea/modules/util"
    24  
    25  	"github.com/stretchr/testify/assert"
    26  	"xorm.io/xorm"
    27  	"xorm.io/xorm/names"
    28  )
    29  
    30  // giteaRoot a path to the gitea root
    31  var (
    32  	giteaRoot   string
    33  	fixturesDir string
    34  )
    35  
    36  // FixturesDir returns the fixture directory
    37  func FixturesDir() string {
    38  	return fixturesDir
    39  }
    40  
    41  func fatalTestError(fmtStr string, args ...any) {
    42  	_, _ = fmt.Fprintf(os.Stderr, fmtStr, args...)
    43  	os.Exit(1)
    44  }
    45  
    46  // InitSettings initializes config provider and load common settings for tests
    47  func InitSettings(extraConfigs ...string) {
    48  	if setting.CustomConf == "" {
    49  		setting.CustomConf = filepath.Join(setting.CustomPath, "conf/app-unittest-tmp.ini")
    50  		_ = os.Remove(setting.CustomConf)
    51  	}
    52  	setting.InitCfgProvider(setting.CustomConf, strings.Join(extraConfigs, "\n"))
    53  	setting.LoadCommonSettings()
    54  
    55  	if err := setting.PrepareAppDataPath(); err != nil {
    56  		log.Fatalf("Can not prepare APP_DATA_PATH: %v", err)
    57  	}
    58  	// register the dummy hash algorithm function used in the test fixtures
    59  	_ = hash.Register("dummy", hash.NewDummyHasher)
    60  
    61  	setting.PasswordHashAlgo, _ = hash.SetDefaultPasswordHashAlgorithm("dummy")
    62  }
    63  
    64  // TestOptions represents test options
    65  type TestOptions struct {
    66  	GiteaRootPath string
    67  	FixtureFiles  []string
    68  	SetUp         func() error // SetUp will be executed before all tests in this package
    69  	TearDown      func() error // TearDown will be executed after all tests in this package
    70  }
    71  
    72  // MainTest a reusable TestMain(..) function for unit tests that need to use a
    73  // test database. Creates the test database, and sets necessary settings.
    74  func MainTest(m *testing.M, testOpts *TestOptions) {
    75  	setting.CustomPath = filepath.Join(testOpts.GiteaRootPath, "custom")
    76  	InitSettings()
    77  
    78  	var err error
    79  
    80  	giteaRoot = testOpts.GiteaRootPath
    81  	fixturesDir = filepath.Join(testOpts.GiteaRootPath, "models", "fixtures")
    82  
    83  	var opts FixturesOptions
    84  	if len(testOpts.FixtureFiles) == 0 {
    85  		opts.Dir = fixturesDir
    86  	} else {
    87  		for _, f := range testOpts.FixtureFiles {
    88  			if len(f) != 0 {
    89  				opts.Files = append(opts.Files, filepath.Join(fixturesDir, f))
    90  			}
    91  		}
    92  	}
    93  
    94  	if err = CreateTestEngine(opts); err != nil {
    95  		fatalTestError("Error creating test engine: %v\n", err)
    96  	}
    97  
    98  	setting.AppURL = "https://try.gitea.io/"
    99  	setting.RunUser = "runuser"
   100  	setting.SSH.User = "sshuser"
   101  	setting.SSH.BuiltinServerUser = "builtinuser"
   102  	setting.SSH.Port = 3000
   103  	setting.SSH.Domain = "try.gitea.io"
   104  	setting.Database.Type = "sqlite3"
   105  	setting.Repository.DefaultBranch = "master" // many test code still assume that default branch is called "master"
   106  	repoRootPath, err := os.MkdirTemp(os.TempDir(), "repos")
   107  	if err != nil {
   108  		fatalTestError("TempDir: %v\n", err)
   109  	}
   110  	setting.RepoRootPath = repoRootPath
   111  	appDataPath, err := os.MkdirTemp(os.TempDir(), "appdata")
   112  	if err != nil {
   113  		fatalTestError("TempDir: %v\n", err)
   114  	}
   115  	setting.AppDataPath = appDataPath
   116  	setting.AppWorkPath = testOpts.GiteaRootPath
   117  	setting.StaticRootPath = testOpts.GiteaRootPath
   118  	setting.GravatarSource = "https://secure.gravatar.com/avatar/"
   119  
   120  	setting.Attachment.Storage.Path = filepath.Join(setting.AppDataPath, "attachments")
   121  
   122  	setting.LFS.Storage.Path = filepath.Join(setting.AppDataPath, "lfs")
   123  
   124  	setting.Avatar.Storage.Path = filepath.Join(setting.AppDataPath, "avatars")
   125  
   126  	setting.RepoAvatar.Storage.Path = filepath.Join(setting.AppDataPath, "repo-avatars")
   127  
   128  	setting.RepoArchive.Storage.Path = filepath.Join(setting.AppDataPath, "repo-archive")
   129  
   130  	setting.Packages.Storage.Path = filepath.Join(setting.AppDataPath, "packages")
   131  
   132  	setting.Actions.LogStorage.Path = filepath.Join(setting.AppDataPath, "actions_log")
   133  
   134  	setting.Git.HomePath = filepath.Join(setting.AppDataPath, "home")
   135  
   136  	setting.IncomingEmail.ReplyToAddress = "incoming+%{token}@localhost"
   137  
   138  	config.SetDynGetter(system.NewDatabaseDynKeyGetter())
   139  
   140  	if err = storage.Init(); err != nil {
   141  		fatalTestError("storage.Init: %v\n", err)
   142  	}
   143  	if err = util.RemoveAll(repoRootPath); err != nil {
   144  		fatalTestError("util.RemoveAll: %v\n", err)
   145  	}
   146  	if err = CopyDir(filepath.Join(testOpts.GiteaRootPath, "tests", "gitea-repositories-meta"), setting.RepoRootPath); err != nil {
   147  		fatalTestError("util.CopyDir: %v\n", err)
   148  	}
   149  
   150  	if err = git.InitFull(context.Background()); err != nil {
   151  		fatalTestError("git.Init: %v\n", err)
   152  	}
   153  	ownerDirs, err := os.ReadDir(setting.RepoRootPath)
   154  	if err != nil {
   155  		fatalTestError("unable to read the new repo root: %v\n", err)
   156  	}
   157  	for _, ownerDir := range ownerDirs {
   158  		if !ownerDir.Type().IsDir() {
   159  			continue
   160  		}
   161  		repoDirs, err := os.ReadDir(filepath.Join(setting.RepoRootPath, ownerDir.Name()))
   162  		if err != nil {
   163  			fatalTestError("unable to read the new repo root: %v\n", err)
   164  		}
   165  		for _, repoDir := range repoDirs {
   166  			_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "pack"), 0o755)
   167  			_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "info"), 0o755)
   168  			_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "heads"), 0o755)
   169  			_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "tag"), 0o755)
   170  		}
   171  	}
   172  
   173  	if testOpts.SetUp != nil {
   174  		if err := testOpts.SetUp(); err != nil {
   175  			fatalTestError("set up failed: %v\n", err)
   176  		}
   177  	}
   178  
   179  	exitStatus := m.Run()
   180  
   181  	if testOpts.TearDown != nil {
   182  		if err := testOpts.TearDown(); err != nil {
   183  			fatalTestError("tear down failed: %v\n", err)
   184  		}
   185  	}
   186  
   187  	if err = util.RemoveAll(repoRootPath); err != nil {
   188  		fatalTestError("util.RemoveAll: %v\n", err)
   189  	}
   190  	if err = util.RemoveAll(appDataPath); err != nil {
   191  		fatalTestError("util.RemoveAll: %v\n", err)
   192  	}
   193  	os.Exit(exitStatus)
   194  }
   195  
   196  // FixturesOptions fixtures needs to be loaded options
   197  type FixturesOptions struct {
   198  	Dir   string
   199  	Files []string
   200  }
   201  
   202  // CreateTestEngine creates a memory database and loads the fixture data from fixturesDir
   203  func CreateTestEngine(opts FixturesOptions) error {
   204  	x, err := xorm.NewEngine("sqlite3", "file::memory:?cache=shared&_txlock=immediate")
   205  	if err != nil {
   206  		if strings.Contains(err.Error(), "unknown driver") {
   207  			return fmt.Errorf(`sqlite3 requires: import _ "github.com/mattn/go-sqlite3" or -tags sqlite,sqlite_unlock_notify%s%w`, "\n", err)
   208  		}
   209  		return err
   210  	}
   211  	x.SetMapper(names.GonicMapper{})
   212  	db.SetDefaultEngine(context.Background(), x)
   213  
   214  	if err = db.SyncAllTables(); err != nil {
   215  		return err
   216  	}
   217  	switch os.Getenv("GITEA_UNIT_TESTS_LOG_SQL") {
   218  	case "true", "1":
   219  		x.ShowSQL(true)
   220  	}
   221  
   222  	return InitFixtures(opts)
   223  }
   224  
   225  // PrepareTestDatabase load test fixtures into test database
   226  func PrepareTestDatabase() error {
   227  	return LoadFixtures()
   228  }
   229  
   230  // PrepareTestEnv prepares the environment for unit tests. Can only be called
   231  // by tests that use the above MainTest(..) function.
   232  func PrepareTestEnv(t testing.TB) {
   233  	assert.NoError(t, PrepareTestDatabase())
   234  	assert.NoError(t, util.RemoveAll(setting.RepoRootPath))
   235  	metaPath := filepath.Join(giteaRoot, "tests", "gitea-repositories-meta")
   236  	assert.NoError(t, CopyDir(metaPath, setting.RepoRootPath))
   237  	ownerDirs, err := os.ReadDir(setting.RepoRootPath)
   238  	assert.NoError(t, err)
   239  	for _, ownerDir := range ownerDirs {
   240  		if !ownerDir.Type().IsDir() {
   241  			continue
   242  		}
   243  		repoDirs, err := os.ReadDir(filepath.Join(setting.RepoRootPath, ownerDir.Name()))
   244  		assert.NoError(t, err)
   245  		for _, repoDir := range repoDirs {
   246  			_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "pack"), 0o755)
   247  			_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "info"), 0o755)
   248  			_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "heads"), 0o755)
   249  			_ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "tag"), 0o755)
   250  		}
   251  	}
   252  
   253  	base.SetupGiteaRoot() // Makes sure GITEA_ROOT is set
   254  }