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 }