You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

migrations_test.go 9.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. // Copyright 2021 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package migrations
  5. import (
  6. "database/sql"
  7. "fmt"
  8. "os"
  9. "path"
  10. "path/filepath"
  11. "runtime"
  12. "testing"
  13. "time"
  14. "code.gitea.io/gitea/models/db"
  15. "code.gitea.io/gitea/models/unittest"
  16. "code.gitea.io/gitea/modules/base"
  17. "code.gitea.io/gitea/modules/git"
  18. "code.gitea.io/gitea/modules/setting"
  19. "code.gitea.io/gitea/modules/timeutil"
  20. "code.gitea.io/gitea/modules/util"
  21. "github.com/stretchr/testify/assert"
  22. "github.com/unknwon/com"
  23. "xorm.io/xorm"
  24. "xorm.io/xorm/names"
  25. )
  26. func TestMain(m *testing.M) {
  27. giteaRoot := base.SetupGiteaRoot()
  28. if giteaRoot == "" {
  29. fmt.Println("Environment variable $GITEA_ROOT not set")
  30. os.Exit(1)
  31. }
  32. giteaBinary := "gitea"
  33. if runtime.GOOS == "windows" {
  34. giteaBinary += ".exe"
  35. }
  36. setting.AppPath = path.Join(giteaRoot, giteaBinary)
  37. if _, err := os.Stat(setting.AppPath); err != nil {
  38. fmt.Printf("Could not find gitea binary at %s\n", setting.AppPath)
  39. os.Exit(1)
  40. }
  41. giteaConf := os.Getenv("GITEA_CONF")
  42. if giteaConf == "" {
  43. giteaConf = path.Join(filepath.Dir(setting.AppPath), "integrations/sqlite.ini")
  44. fmt.Printf("Environment variable $GITEA_CONF not set - defaulting to %s\n", giteaConf)
  45. }
  46. if !path.IsAbs(giteaConf) {
  47. setting.CustomConf = path.Join(giteaRoot, giteaConf)
  48. } else {
  49. setting.CustomConf = giteaConf
  50. }
  51. setting.SetCustomPathAndConf("", "", "")
  52. setting.NewContext()
  53. git.CheckLFSVersion()
  54. setting.InitDBConfig()
  55. setting.NewLogServices(true)
  56. exitStatus := m.Run()
  57. if err := removeAllWithRetry(setting.RepoRootPath); err != nil {
  58. fmt.Fprintf(os.Stderr, "os.RemoveAll: %v\n", err)
  59. }
  60. if err := removeAllWithRetry(setting.AppDataPath); err != nil {
  61. fmt.Fprintf(os.Stderr, "os.RemoveAll: %v\n", err)
  62. }
  63. os.Exit(exitStatus)
  64. }
  65. func removeAllWithRetry(dir string) error {
  66. var err error
  67. for i := 0; i < 20; i++ {
  68. err = os.RemoveAll(dir)
  69. if err == nil {
  70. break
  71. }
  72. time.Sleep(100 * time.Millisecond)
  73. }
  74. return err
  75. }
  76. // newEngine sets the xorm.Engine
  77. func newEngine() (*xorm.Engine, error) {
  78. x, err := db.NewEngine()
  79. if err != nil {
  80. return x, fmt.Errorf("Failed to connect to database: %v", err)
  81. }
  82. x.SetMapper(names.GonicMapper{})
  83. // WARNING: for serv command, MUST remove the output to os.stdout,
  84. // so use log file to instead print to stdout.
  85. x.SetLogger(db.NewXORMLogger(setting.Database.LogSQL))
  86. x.ShowSQL(setting.Database.LogSQL)
  87. x.SetMaxOpenConns(setting.Database.MaxOpenConns)
  88. x.SetMaxIdleConns(setting.Database.MaxIdleConns)
  89. x.SetConnMaxLifetime(setting.Database.ConnMaxLifetime)
  90. return x, nil
  91. }
  92. func deleteDB() error {
  93. switch {
  94. case setting.Database.UseSQLite3:
  95. if err := util.Remove(setting.Database.Path); err != nil {
  96. return err
  97. }
  98. return os.MkdirAll(path.Dir(setting.Database.Path), os.ModePerm)
  99. case setting.Database.UseMySQL:
  100. db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/",
  101. setting.Database.User, setting.Database.Passwd, setting.Database.Host))
  102. if err != nil {
  103. return err
  104. }
  105. defer db.Close()
  106. if _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", setting.Database.Name)); err != nil {
  107. return err
  108. }
  109. if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS %s", setting.Database.Name)); err != nil {
  110. return err
  111. }
  112. return nil
  113. case setting.Database.UsePostgreSQL:
  114. db, err := sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/?sslmode=%s",
  115. setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.SSLMode))
  116. if err != nil {
  117. return err
  118. }
  119. defer db.Close()
  120. if _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", setting.Database.Name)); err != nil {
  121. return err
  122. }
  123. if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", setting.Database.Name)); err != nil {
  124. return err
  125. }
  126. db.Close()
  127. // Check if we need to setup a specific schema
  128. if len(setting.Database.Schema) != 0 {
  129. db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s",
  130. setting.Database.User, setting.Database.Passwd, setting.Database.Host, setting.Database.Name, setting.Database.SSLMode))
  131. if err != nil {
  132. return err
  133. }
  134. defer db.Close()
  135. schrows, err := db.Query(fmt.Sprintf("SELECT 1 FROM information_schema.schemata WHERE schema_name = '%s'", setting.Database.Schema))
  136. if err != nil {
  137. return err
  138. }
  139. defer schrows.Close()
  140. if !schrows.Next() {
  141. // Create and setup a DB schema
  142. _, err = db.Exec(fmt.Sprintf("CREATE SCHEMA %s", setting.Database.Schema))
  143. if err != nil {
  144. return err
  145. }
  146. }
  147. // Make the user's default search path the created schema; this will affect new connections
  148. _, err = db.Exec(fmt.Sprintf(`ALTER USER "%s" SET search_path = %s`, setting.Database.User, setting.Database.Schema))
  149. if err != nil {
  150. return err
  151. }
  152. return nil
  153. }
  154. case setting.Database.UseMSSQL:
  155. host, port := setting.ParseMSSQLHostPort(setting.Database.Host)
  156. db, err := sql.Open("mssql", fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;",
  157. host, port, "master", setting.Database.User, setting.Database.Passwd))
  158. if err != nil {
  159. return err
  160. }
  161. defer db.Close()
  162. if _, err = db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS [%s]", setting.Database.Name)); err != nil {
  163. return err
  164. }
  165. if _, err = db.Exec(fmt.Sprintf("CREATE DATABASE [%s]", setting.Database.Name)); err != nil {
  166. return err
  167. }
  168. }
  169. return nil
  170. }
  171. // prepareTestEnv prepares the test environment and reset the database. The skip parameter should usually be 0.
  172. // Provide models to be sync'd with the database - in particular any models you expect fixtures to be loaded from.
  173. //
  174. // fixtures in `models/migrations/fixtures/<TestName>` will be loaded automatically
  175. func prepareTestEnv(t *testing.T, skip int, syncModels ...interface{}) (*xorm.Engine, func()) {
  176. t.Helper()
  177. ourSkip := 2
  178. ourSkip += skip
  179. deferFn := PrintCurrentTest(t, ourSkip)
  180. assert.NoError(t, os.RemoveAll(setting.RepoRootPath))
  181. assert.NoError(t, com.CopyDir(path.Join(filepath.Dir(setting.AppPath), "integrations/gitea-repositories-meta"),
  182. setting.RepoRootPath))
  183. if err := deleteDB(); err != nil {
  184. t.Errorf("unable to reset database: %v", err)
  185. return nil, deferFn
  186. }
  187. x, err := newEngine()
  188. assert.NoError(t, err)
  189. if x != nil {
  190. oldDefer := deferFn
  191. deferFn = func() {
  192. oldDefer()
  193. if err := x.Close(); err != nil {
  194. t.Errorf("error during close: %v", err)
  195. }
  196. if err := deleteDB(); err != nil {
  197. t.Errorf("unable to reset database: %v", err)
  198. }
  199. }
  200. }
  201. if err != nil {
  202. return x, deferFn
  203. }
  204. if len(syncModels) > 0 {
  205. if err := x.Sync2(syncModels...); err != nil {
  206. t.Errorf("error during sync: %v", err)
  207. return x, deferFn
  208. }
  209. }
  210. fixturesDir := filepath.Join(filepath.Dir(setting.AppPath), "models", "migrations", "fixtures", t.Name())
  211. if _, err := os.Stat(fixturesDir); err == nil {
  212. t.Logf("initializing fixtures from: %s", fixturesDir)
  213. if err := unittest.InitFixtures(
  214. unittest.FixturesOptions{
  215. Dir: fixturesDir,
  216. }, x); err != nil {
  217. t.Errorf("error whilst initializing fixtures from %s: %v", fixturesDir, err)
  218. return x, deferFn
  219. }
  220. if err := unittest.LoadFixtures(x); err != nil {
  221. t.Errorf("error whilst loading fixtures from %s: %v", fixturesDir, err)
  222. return x, deferFn
  223. }
  224. } else if !os.IsNotExist(err) {
  225. t.Errorf("unexpected error whilst checking for existence of fixtures: %v", err)
  226. } else {
  227. t.Logf("no fixtures found in: %s", fixturesDir)
  228. }
  229. return x, deferFn
  230. }
  231. func Test_dropTableColumns(t *testing.T) {
  232. x, deferable := prepareTestEnv(t, 0)
  233. if x == nil || t.Failed() {
  234. defer deferable()
  235. return
  236. }
  237. defer deferable()
  238. type DropTest struct {
  239. ID int64 `xorm:"pk autoincr"`
  240. FirstColumn string
  241. ToDropColumn string `xorm:"unique"`
  242. AnotherColumn int64
  243. CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
  244. UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
  245. }
  246. columns := []string{
  247. "first_column",
  248. "to_drop_column",
  249. "another_column",
  250. "created_unix",
  251. "updated_unix",
  252. }
  253. for i := range columns {
  254. x.SetMapper(names.GonicMapper{})
  255. if err := x.Sync2(new(DropTest)); err != nil {
  256. t.Errorf("unable to create DropTest table: %v", err)
  257. return
  258. }
  259. sess := x.NewSession()
  260. if err := sess.Begin(); err != nil {
  261. sess.Close()
  262. t.Errorf("unable to begin transaction: %v", err)
  263. return
  264. }
  265. if err := dropTableColumns(sess, "drop_test", columns[i:]...); err != nil {
  266. sess.Close()
  267. t.Errorf("Unable to drop columns[%d:]: %s from drop_test: %v", i, columns[i:], err)
  268. return
  269. }
  270. if err := sess.Commit(); err != nil {
  271. sess.Close()
  272. t.Errorf("unable to commit transaction: %v", err)
  273. return
  274. }
  275. sess.Close()
  276. if err := x.DropTables(new(DropTest)); err != nil {
  277. t.Errorf("unable to drop table: %v", err)
  278. return
  279. }
  280. for j := range columns[i+1:] {
  281. x.SetMapper(names.GonicMapper{})
  282. if err := x.Sync2(new(DropTest)); err != nil {
  283. t.Errorf("unable to create DropTest table: %v", err)
  284. return
  285. }
  286. dropcols := append([]string{columns[i]}, columns[j+i+1:]...)
  287. sess := x.NewSession()
  288. if err := sess.Begin(); err != nil {
  289. sess.Close()
  290. t.Errorf("unable to begin transaction: %v", err)
  291. return
  292. }
  293. if err := dropTableColumns(sess, "drop_test", dropcols...); err != nil {
  294. sess.Close()
  295. t.Errorf("Unable to drop columns: %s from drop_test: %v", dropcols, err)
  296. return
  297. }
  298. if err := sess.Commit(); err != nil {
  299. sess.Close()
  300. t.Errorf("unable to commit transaction: %v", err)
  301. return
  302. }
  303. sess.Close()
  304. if err := x.DropTables(new(DropTest)); err != nil {
  305. t.Errorf("unable to drop table: %v", err)
  306. return
  307. }
  308. }
  309. }
  310. }