diff options
author | wxiaoguang <wxiaoguang@gmail.com> | 2021-11-12 22:36:47 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-12 22:36:47 +0800 |
commit | df64fa486555de6f403a795fd16c2e9e1d59e535 (patch) | |
tree | b899e9b9e5d57409b1bf0e3afbd606b6a3900235 /models/db | |
parent | 7f802631c54d2e91301158380b273b872d62bd80 (diff) | |
download | gitea-df64fa486555de6f403a795fd16c2e9e1d59e535.tar.gz gitea-df64fa486555de6f403a795fd16c2e9e1d59e535.zip |
Decouple unit test code from business code (#17623)
Diffstat (limited to 'models/db')
-rwxr-xr-x | models/db/engine.go | 5 | ||||
-rw-r--r-- | models/db/paginator/main_test.go (renamed from models/db/main_test.go) | 6 | ||||
-rw-r--r-- | models/db/paginator/paginator.go | 8 | ||||
-rw-r--r-- | models/db/paginator/paginator_test.go (renamed from models/db/list_options_test.go) | 13 | ||||
-rw-r--r-- | models/db/test_fixtures.go | 117 | ||||
-rw-r--r-- | models/db/unit_tests.go | 206 |
6 files changed, 61 insertions, 294 deletions
diff --git a/models/db/engine.go b/models/db/engine.go index d5f2470a7a..d1b279e016 100755 --- a/models/db/engine.go +++ b/models/db/engine.go @@ -124,7 +124,8 @@ func NewEngine() (*xorm.Engine, error) { return engine, nil } -func syncTables() error { +//SyncAllTables sync the schemas of all tables, is required by unit test code +func SyncAllTables() error { return x.StoreEngine("InnoDB").Sync2(tables...) } @@ -176,7 +177,7 @@ func InitEngineWithMigration(ctx context.Context, migrateFunc func(*xorm.Engine) return fmt.Errorf("migrate: %v", err) } - if err = syncTables(); err != nil { + if err = SyncAllTables(); err != nil { return fmt.Errorf("sync database struct error: %v", err) } diff --git a/models/db/main_test.go b/models/db/paginator/main_test.go index f34ff65813..601ed89710 100644 --- a/models/db/main_test.go +++ b/models/db/paginator/main_test.go @@ -2,13 +2,15 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package db +package paginator import ( "path/filepath" "testing" + + "code.gitea.io/gitea/models/unittest" ) func TestMain(m *testing.M) { - MainTest(m, filepath.Join("..", "..")) + unittest.MainTest(m, filepath.Join("..", "..", "..")) } diff --git a/models/db/paginator/paginator.go b/models/db/paginator/paginator.go new file mode 100644 index 0000000000..747539f30e --- /dev/null +++ b/models/db/paginator/paginator.go @@ -0,0 +1,8 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package paginator + +// dummy only. in the future, the models/db/list_options.go should be moved here to decouple from db package +// otherwise the unit test will cause cycle import diff --git a/models/db/list_options_test.go b/models/db/paginator/paginator_test.go index 2c860afdfb..fdb8eee441 100644 --- a/models/db/list_options_test.go +++ b/models/db/paginator/paginator_test.go @@ -2,11 +2,12 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package db +package paginator import ( "testing" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/setting" "github.com/stretchr/testify/assert" @@ -14,35 +15,35 @@ import ( func TestPaginator(t *testing.T) { cases := []struct { - Paginator + db.Paginator Skip int Take int Start int End int }{ { - Paginator: &ListOptions{Page: -1, PageSize: -1}, + Paginator: &db.ListOptions{Page: -1, PageSize: -1}, Skip: 0, Take: setting.API.DefaultPagingNum, Start: 0, End: setting.API.DefaultPagingNum, }, { - Paginator: &ListOptions{Page: 2, PageSize: 10}, + Paginator: &db.ListOptions{Page: 2, PageSize: 10}, Skip: 10, Take: 10, Start: 10, End: 20, }, { - Paginator: NewAbsoluteListOptions(-1, -1), + Paginator: db.NewAbsoluteListOptions(-1, -1), Skip: 0, Take: setting.API.DefaultPagingNum, Start: 0, End: setting.API.DefaultPagingNum, }, { - Paginator: NewAbsoluteListOptions(2, 10), + Paginator: db.NewAbsoluteListOptions(2, 10), Skip: 2, Take: 10, Start: 2, diff --git a/models/db/test_fixtures.go b/models/db/test_fixtures.go deleted file mode 100644 index 2715b688ea..0000000000 --- a/models/db/test_fixtures.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2017 The Gitea Authors. All rights reserved. -// Use of this source code is governed by a MIT-style -// license that can be found in the LICENSE file. - -package db - -import ( - "fmt" - "os" - "time" - - "github.com/go-testfixtures/testfixtures/v3" - "xorm.io/xorm" - "xorm.io/xorm/schemas" -) - -var fixtures *testfixtures.Loader - -// InitFixtures initialize test fixtures for a test database -func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) { - e := x - if len(engine) == 1 { - e = engine[0] - } - - var testfiles func(*testfixtures.Loader) error - if opts.Dir != "" { - testfiles = testfixtures.Directory(opts.Dir) - } else { - testfiles = testfixtures.Files(opts.Files...) - } - dialect := "unknown" - switch e.Dialect().URI().DBType { - case schemas.POSTGRES: - dialect = "postgres" - case schemas.MYSQL: - dialect = "mysql" - case schemas.MSSQL: - dialect = "mssql" - case schemas.SQLITE: - dialect = "sqlite3" - default: - fmt.Println("Unsupported RDBMS for integration tests") - os.Exit(1) - } - loaderOptions := []func(loader *testfixtures.Loader) error{ - testfixtures.Database(e.DB().DB), - testfixtures.Dialect(dialect), - testfixtures.DangerousSkipTestDatabaseCheck(), - testfiles, - } - - if e.Dialect().URI().DBType == schemas.POSTGRES { - loaderOptions = append(loaderOptions, testfixtures.SkipResetSequences()) - } - - fixtures, err = testfixtures.New(loaderOptions...) - if err != nil { - return err - } - - return err -} - -// LoadFixtures load fixtures for a test database -func LoadFixtures(engine ...*xorm.Engine) error { - e := x - if len(engine) == 1 { - e = engine[0] - } - var err error - // Database transaction conflicts could occur and result in ROLLBACK - // As a simple workaround, we just retry 20 times. - for i := 0; i < 20; i++ { - err = fixtures.Load() - if 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 - } - } - } - } - return err -} diff --git a/models/db/unit_tests.go b/models/db/unit_tests.go index 6f079c8676..2b1691726f 100644 --- a/models/db/unit_tests.go +++ b/models/db/unit_tests.go @@ -6,157 +6,26 @@ package db import ( "context" - "fmt" "math" - "net/url" - "os" - "path/filepath" - "testing" - "code.gitea.io/gitea/modules/base" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/storage" - "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/unittestbridge" - "github.com/stretchr/testify/assert" "xorm.io/xorm" - "xorm.io/xorm/names" ) +// Code in this file is mainly used by models.CheckConsistencyFor, which is not in the unit test for various reasons. +// In the future if we can decouple CheckConsistencyFor into separate unit test code, then this file can be moved into unittest package too. + // NonexistentID an ID that will never exist const NonexistentID = int64(math.MaxInt64) -// giteaRoot a path to the gitea root -var ( - giteaRoot string - fixturesDir string -) - -// FixturesDir returns the fixture directory -func FixturesDir() string { - return fixturesDir -} - -func fatalTestError(fmtStr string, args ...interface{}) { - fmt.Fprintf(os.Stderr, fmtStr, args...) - os.Exit(1) -} - -// MainTest a reusable TestMain(..) function for unit tests that need to use a -// test database. Creates the test database, and sets necessary settings. -func MainTest(m *testing.M, pathToGiteaRoot string, fixtureFiles ...string) { - var err error - giteaRoot = pathToGiteaRoot - fixturesDir = filepath.Join(pathToGiteaRoot, "models", "fixtures") - - var opts FixturesOptions - if len(fixtureFiles) == 0 { - opts.Dir = fixturesDir - } else { - for _, f := range fixtureFiles { - if len(f) != 0 { - opts.Files = append(opts.Files, filepath.Join(fixturesDir, f)) - } - } - } - - if err = CreateTestEngine(opts); err != nil { - fatalTestError("Error creating test engine: %v\n", err) - } - - setting.AppURL = "https://try.gitea.io/" - setting.RunUser = "runuser" - setting.SSH.Port = 3000 - setting.SSH.Domain = "try.gitea.io" - setting.Database.UseSQLite3 = true - setting.RepoRootPath, err = os.MkdirTemp(os.TempDir(), "repos") - if err != nil { - fatalTestError("TempDir: %v\n", err) - } - setting.AppDataPath, err = os.MkdirTemp(os.TempDir(), "appdata") - if err != nil { - fatalTestError("TempDir: %v\n", err) - } - setting.AppWorkPath = pathToGiteaRoot - setting.StaticRootPath = pathToGiteaRoot - setting.GravatarSourceURL, err = url.Parse("https://secure.gravatar.com/avatar/") - if err != nil { - fatalTestError("url.Parse: %v\n", err) - } - setting.Attachment.Storage.Path = filepath.Join(setting.AppDataPath, "attachments") - - setting.LFS.Storage.Path = filepath.Join(setting.AppDataPath, "lfs") - - setting.Avatar.Storage.Path = filepath.Join(setting.AppDataPath, "avatars") - - setting.RepoAvatar.Storage.Path = filepath.Join(setting.AppDataPath, "repo-avatars") - - setting.RepoArchive.Storage.Path = filepath.Join(setting.AppDataPath, "repo-archive") - - if err = storage.Init(); err != nil { - fatalTestError("storage.Init: %v\n", err) - } - - if err = util.RemoveAll(setting.RepoRootPath); err != nil { - fatalTestError("util.RemoveAll: %v\n", err) - } - if err = util.CopyDir(filepath.Join(pathToGiteaRoot, "integrations", "gitea-repositories-meta"), setting.RepoRootPath); err != nil { - fatalTestError("util.CopyDir: %v\n", err) - } - - exitStatus := m.Run() - if err = util.RemoveAll(setting.RepoRootPath); err != nil { - fatalTestError("util.RemoveAll: %v\n", err) - } - if err = util.RemoveAll(setting.AppDataPath); err != nil { - fatalTestError("util.RemoveAll: %v\n", err) - } - os.Exit(exitStatus) -} - -// FixturesOptions fixtures needs to be loaded options -type FixturesOptions struct { - Dir string - Files []string -} - -// CreateTestEngine creates a memory database and loads the fixture data from fixturesDir -func CreateTestEngine(opts FixturesOptions) error { - var err error - x, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared&_txlock=immediate") - if err != nil { - return err - } - x.SetMapper(names.GonicMapper{}) - if err = syncTables(); err != nil { - return err - } - switch os.Getenv("GITEA_UNIT_TESTS_VERBOSE") { - case "true", "1": - x.ShowSQL(true) - } - +//SetUnitTestEngine is used by unit test code +func SetUnitTestEngine(eng *xorm.Engine) { + x = eng DefaultContext = &Context{ Context: context.Background(), e: x, } - - return InitFixtures(opts) -} - -// PrepareTestDatabase load test fixtures into test database -func PrepareTestDatabase() error { - return LoadFixtures() -} - -// PrepareTestEnv prepares the environment for unit tests. Can only be called -// by tests that use the above MainTest(..) function. -func PrepareTestEnv(t testing.TB) { - assert.NoError(t, PrepareTestDatabase()) - assert.NoError(t, util.RemoveAll(setting.RepoRootPath)) - metaPath := filepath.Join(giteaRoot, "integrations", "gitea-repositories-meta") - assert.NoError(t, util.CopyDir(metaPath, setting.RepoRootPath)) - base.SetupGiteaRoot() // Makes sure GITEA_ROOT is set } type testCond struct { @@ -182,10 +51,6 @@ func whereConditions(sess *xorm.Session, conditions []interface{}) { // LoadBeanIfExists loads beans from fixture database if exist func LoadBeanIfExists(bean interface{}, conditions ...interface{}) (bool, error) { - return loadBeanIfExists(bean, conditions...) -} - -func loadBeanIfExists(bean interface{}, conditions ...interface{}) (bool, error) { sess := x.NewSession() defer sess.Close() whereConditions(sess, conditions) @@ -193,61 +58,68 @@ func loadBeanIfExists(bean interface{}, conditions ...interface{}) (bool, error) } // BeanExists for testing, check if a bean exists -func BeanExists(t testing.TB, bean interface{}, conditions ...interface{}) bool { - exists, err := loadBeanIfExists(bean, conditions...) - assert.NoError(t, err) +func BeanExists(t unittestbridge.Tester, bean interface{}, conditions ...interface{}) bool { + ta := unittestbridge.NewAsserter(t) + exists, err := LoadBeanIfExists(bean, conditions...) + ta.NoError(err) return exists } -// AssertExistsAndLoadBean assert that a bean exists and load it from the test -// database -func AssertExistsAndLoadBean(t testing.TB, bean interface{}, conditions ...interface{}) interface{} { - exists, err := loadBeanIfExists(bean, conditions...) - assert.NoError(t, err) - assert.True(t, exists, +// AssertExistsAndLoadBean assert that a bean exists and load it from the test database +func AssertExistsAndLoadBean(t unittestbridge.Tester, bean interface{}, conditions ...interface{}) interface{} { + ta := unittestbridge.NewAsserter(t) + exists, err := LoadBeanIfExists(bean, conditions...) + ta.NoError(err) + ta.True(exists, "Expected to find %+v (of type %T, with conditions %+v), but did not", bean, bean, conditions) return bean } // GetCount get the count of a bean -func GetCount(t testing.TB, bean interface{}, conditions ...interface{}) int { +func GetCount(t unittestbridge.Tester, bean interface{}, conditions ...interface{}) int { + ta := unittestbridge.NewAsserter(t) sess := x.NewSession() defer sess.Close() whereConditions(sess, conditions) count, err := sess.Count(bean) - assert.NoError(t, err) + ta.NoError(err) return int(count) } // AssertNotExistsBean assert that a bean does not exist in the test database -func AssertNotExistsBean(t testing.TB, bean interface{}, conditions ...interface{}) { - exists, err := loadBeanIfExists(bean, conditions...) - assert.NoError(t, err) - assert.False(t, exists) +func AssertNotExistsBean(t unittestbridge.Tester, bean interface{}, conditions ...interface{}) { + ta := unittestbridge.NewAsserter(t) + exists, err := LoadBeanIfExists(bean, conditions...) + ta.NoError(err) + ta.False(exists) } // AssertExistsIf asserts that a bean exists or does not exist, depending on // what is expected. -func AssertExistsIf(t *testing.T, expected bool, bean interface{}, conditions ...interface{}) { - exists, err := loadBeanIfExists(bean, conditions...) - assert.NoError(t, err) - assert.Equal(t, expected, exists) +func AssertExistsIf(t unittestbridge.Tester, expected bool, bean interface{}, conditions ...interface{}) { + ta := unittestbridge.NewAsserter(t) + exists, err := LoadBeanIfExists(bean, conditions...) + ta.NoError(err) + ta.Equal(expected, exists) } // AssertSuccessfulInsert assert that beans is successfully inserted -func AssertSuccessfulInsert(t testing.TB, beans ...interface{}) { +func AssertSuccessfulInsert(t unittestbridge.Tester, beans ...interface{}) { + ta := unittestbridge.NewAsserter(t) _, err := x.Insert(beans...) - assert.NoError(t, err) + ta.NoError(err) } // AssertCount assert the count of a bean -func AssertCount(t testing.TB, bean, expected interface{}) { - assert.EqualValues(t, expected, GetCount(t, bean)) +func AssertCount(t unittestbridge.Tester, bean, expected interface{}) { + ta := unittestbridge.NewAsserter(t) + ta.EqualValues(expected, GetCount(ta, bean)) } // AssertInt64InRange assert value is in range [low, high] -func AssertInt64InRange(t testing.TB, low, high, value int64) { - assert.True(t, value >= low && value <= high, +func AssertInt64InRange(t unittestbridge.Tester, low, high, value int64) { + ta := unittestbridge.NewAsserter(t) + ta.True(value >= low && value <= high, "Expected value in range [%d, %d], found %d", low, high, value) } |