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