mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-07-22 02:59:39 +02:00
- A mistake that is often made is to not put a empty fixture for tables that gets populated in a test and should be cleaned up between runs. The CI does not detect such issues as these never arise on the first run of the test suite and are only noticed when developers run a test for a second time, unless you are aware of this behavior (which very few do as this is not documented and pure folk knowledge) can end up in chasing a bug that does not exist for hours. forgejo/forgejo#7041, forgejo/forgejo#7419, meissa/forgejo#21, forgejo/forgejo#5771 - Because we now own the fixture loader (forgejo/forgejo#7715), its rather simple to ensure that all tables that did not receive fixtures should be cleaned between runs and removes the need to place empty fixture files. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/8353 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Reviewed-by: Otto <otto@codeberg.org> Co-authored-by: Gusted <postmaster@gusted.xyz> Co-committed-by: Gusted <postmaster@gusted.xyz>
152 lines
3.7 KiB
Go
152 lines
3.7 KiB
Go
// Copyright 2021 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
//nolint:forbidigo
|
|
package unittest
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"sync"
|
|
"time"
|
|
|
|
"forgejo.org/models/db"
|
|
"forgejo.org/modules/auth/password/hash"
|
|
"forgejo.org/modules/container"
|
|
"forgejo.org/modules/setting"
|
|
|
|
"xorm.io/xorm"
|
|
"xorm.io/xorm/schemas"
|
|
)
|
|
|
|
var fixturesLoader *loader
|
|
|
|
// GetXORMEngine gets the XORM engine
|
|
func GetXORMEngine(engine ...*xorm.Engine) (x *xorm.Engine, err error) {
|
|
if len(engine) == 1 {
|
|
return engine[0], nil
|
|
}
|
|
return db.GetMasterEngine(db.DefaultContext.(*db.Context).Engine())
|
|
}
|
|
|
|
func OverrideFixtures(dir string) func() {
|
|
old := fixturesLoader
|
|
|
|
opts := FixturesOptions{
|
|
Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"),
|
|
Base: setting.AppWorkPath,
|
|
Dirs: []string{dir},
|
|
}
|
|
if err := InitFixtures(opts); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return func() {
|
|
fixturesLoader = old
|
|
}
|
|
}
|
|
|
|
var allTableNames = sync.OnceValue(db.GetTableNames)
|
|
|
|
// InitFixtures initialize test fixtures for a test database
|
|
func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) {
|
|
e, err := GetXORMEngine(engine...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fixturePaths := []string{}
|
|
if opts.Dir != "" {
|
|
fixturePaths = append(fixturePaths, opts.Dir)
|
|
} else {
|
|
fixturePaths = append(fixturePaths, opts.Files...)
|
|
}
|
|
if opts.Dirs != nil {
|
|
for _, dir := range opts.Dirs {
|
|
fixturePaths = append(fixturePaths, filepath.Join(opts.Base, dir))
|
|
}
|
|
}
|
|
|
|
var dialect string
|
|
switch e.Dialect().URI().DBType {
|
|
case schemas.POSTGRES:
|
|
dialect = "postgres"
|
|
case schemas.MYSQL:
|
|
dialect = "mysql"
|
|
case schemas.SQLITE:
|
|
dialect = "sqlite3"
|
|
default:
|
|
panic("Unsupported RDBMS for test")
|
|
}
|
|
|
|
var allTables container.Set[string]
|
|
if !opts.SkipCleanRegistedModels {
|
|
allTables = allTableNames().Clone()
|
|
}
|
|
|
|
fixturesLoader, err = newFixtureLoader(e.DB().DB, dialect, fixturePaths, allTables)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// register the dummy hash algorithm function used in the test fixtures
|
|
_ = hash.Register("dummy", hash.NewDummyHasher)
|
|
|
|
setting.PasswordHashAlgo, _ = hash.SetDefaultPasswordHashAlgorithm("dummy")
|
|
|
|
return err
|
|
}
|
|
|
|
// LoadFixtures load fixtures for a test database
|
|
func LoadFixtures(engine ...*xorm.Engine) error {
|
|
e, err := GetXORMEngine(engine...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// (doubt) database transaction conflicts could occur and result in ROLLBACK? just try for a few times.
|
|
for range 5 {
|
|
if err = fixturesLoader.Load(); err == nil {
|
|
break
|
|
}
|
|
time.Sleep(200 * time.Millisecond)
|
|
}
|
|
if err != nil {
|
|
fmt.Printf("LoadFixtures failed after retries: %v\n", err)
|
|
}
|
|
// Now if we're running postgres we need to tell it to update the sequences
|
|
if e.Dialect().URI().DBType == schemas.POSTGRES {
|
|
results, err := e.QueryString(`SELECT 'SELECT SETVAL(' ||
|
|
quote_literal(quote_ident(PGT.schemaname) || '.' || quote_ident(S.relname)) ||
|
|
', COALESCE(MAX(' ||quote_ident(C.attname)|| '), 1) ) FROM ' ||
|
|
quote_ident(PGT.schemaname)|| '.'||quote_ident(T.relname)|| ';'
|
|
FROM pg_class AS S,
|
|
pg_depend AS D,
|
|
pg_class AS T,
|
|
pg_attribute AS C,
|
|
pg_tables AS PGT
|
|
WHERE S.relkind = 'S'
|
|
AND S.oid = D.objid
|
|
AND D.refobjid = T.oid
|
|
AND D.refobjid = C.attrelid
|
|
AND D.refobjsubid = C.attnum
|
|
AND T.relname = PGT.tablename
|
|
ORDER BY S.relname;`)
|
|
if err != nil {
|
|
fmt.Printf("Failed to generate sequence update: %v\n", err)
|
|
return err
|
|
}
|
|
for _, r := range results {
|
|
for _, value := range r {
|
|
_, err = e.Exec(value)
|
|
if err != nil {
|
|
fmt.Printf("Failed to update sequence: %s Error: %v\n", value, err)
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_ = hash.Register("dummy", hash.NewDummyHasher)
|
|
setting.PasswordHashAlgo, _ = hash.SetDefaultPasswordHashAlgorithm("dummy")
|
|
|
|
return err
|
|
}
|