mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-05-31 11:52:10 +00:00
Decouple unit test, remove intermediate unittestbridge
package (#17662)
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
parent
23bd7b1211
commit
81926d61db
151 changed files with 1719 additions and 1781 deletions
|
@ -1,54 +0,0 @@
|
|||
// 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 unittest
|
||||
|
||||
import (
|
||||
"code.gitea.io/gitea/modules/unittestbridge"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// For legacy code only, please refer to the `unittestbridge` package.
|
||||
|
||||
// TestifyAsserter uses "stretchr/testify/assert" to do assert
|
||||
type TestifyAsserter struct {
|
||||
t unittestbridge.Tester
|
||||
}
|
||||
|
||||
// Errorf assert Errorf
|
||||
func (ta TestifyAsserter) Errorf(format string, args ...interface{}) {
|
||||
ta.t.Errorf(format, args)
|
||||
}
|
||||
|
||||
// NoError assert NoError
|
||||
func (ta TestifyAsserter) NoError(err error, msgAndArgs ...interface{}) bool {
|
||||
return assert.NoError(ta, err, msgAndArgs...)
|
||||
}
|
||||
|
||||
// EqualValues assert EqualValues
|
||||
func (ta TestifyAsserter) EqualValues(expected, actual interface{}, msgAndArgs ...interface{}) bool {
|
||||
return assert.EqualValues(ta, expected, actual, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Equal assert Equal
|
||||
func (ta TestifyAsserter) Equal(expected, actual interface{}, msgAndArgs ...interface{}) bool {
|
||||
return assert.Equal(ta, expected, actual, msgAndArgs...)
|
||||
}
|
||||
|
||||
// True assert True
|
||||
func (ta TestifyAsserter) True(value bool, msgAndArgs ...interface{}) bool {
|
||||
return assert.True(ta, value, msgAndArgs...)
|
||||
}
|
||||
|
||||
// False assert False
|
||||
func (ta TestifyAsserter) False(value bool, msgAndArgs ...interface{}) bool {
|
||||
return assert.False(ta, value, msgAndArgs...)
|
||||
}
|
||||
|
||||
// InitUnitTestBridge init the unit test bridge. eg: models.CheckConsistencyFor can use testing and assert frameworks
|
||||
func InitUnitTestBridge() {
|
||||
unittestbridge.SetNewAsserterFunc(func(t unittestbridge.Tester) unittestbridge.Asserter {
|
||||
return &TestifyAsserter{t: t}
|
||||
})
|
||||
}
|
190
models/unittest/consistency.go
Normal file
190
models/unittest/consistency.go
Normal file
|
@ -0,0 +1,190 @@
|
|||
// 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 unittest
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
const (
|
||||
// these const values are copied from `models` package to prevent from cycle-import
|
||||
modelsUserTypeOrganization = 1
|
||||
modelsRepoWatchModeDont = 2
|
||||
modelsCommentTypeComment = 0
|
||||
)
|
||||
|
||||
var consistencyCheckMap = make(map[string]func(t assert.TestingT, bean interface{}))
|
||||
|
||||
// CheckConsistencyFor test that all matching database entries are consistent
|
||||
func CheckConsistencyFor(t assert.TestingT, beansToCheck ...interface{}) {
|
||||
for _, bean := range beansToCheck {
|
||||
sliceType := reflect.SliceOf(reflect.TypeOf(bean))
|
||||
sliceValue := reflect.MakeSlice(sliceType, 0, 10)
|
||||
|
||||
ptrToSliceValue := reflect.New(sliceType)
|
||||
ptrToSliceValue.Elem().Set(sliceValue)
|
||||
|
||||
assert.NoError(t, db.GetEngine(db.DefaultContext).Table(bean).Find(ptrToSliceValue.Interface()))
|
||||
sliceValue = ptrToSliceValue.Elem()
|
||||
|
||||
for i := 0; i < sliceValue.Len(); i++ {
|
||||
entity := sliceValue.Index(i).Interface()
|
||||
checkForConsistency(t, entity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkForConsistency(t assert.TestingT, bean interface{}) {
|
||||
tb, err := db.TableInfo(bean)
|
||||
assert.NoError(t, err)
|
||||
f := consistencyCheckMap[tb.Name]
|
||||
if f == nil {
|
||||
assert.Fail(t, "unknown bean type: %#v", bean)
|
||||
return
|
||||
}
|
||||
f(t, bean)
|
||||
}
|
||||
|
||||
func init() {
|
||||
parseBool := func(v string) bool {
|
||||
b, _ := strconv.ParseBool(v)
|
||||
return b
|
||||
}
|
||||
parseInt := func(v string) int {
|
||||
i, _ := strconv.Atoi(v)
|
||||
return i
|
||||
}
|
||||
|
||||
checkForUserConsistency := func(t assert.TestingT, bean interface{}) {
|
||||
user := reflectionWrap(bean)
|
||||
AssertCountByCond(t, "repository", builder.Eq{"owner_id": user.int("ID")}, user.int("NumRepos"))
|
||||
AssertCountByCond(t, "star", builder.Eq{"uid": user.int("ID")}, user.int("NumStars"))
|
||||
AssertCountByCond(t, "org_user", builder.Eq{"org_id": user.int("ID")}, user.int("NumMembers"))
|
||||
AssertCountByCond(t, "team", builder.Eq{"org_id": user.int("ID")}, user.int("NumTeams"))
|
||||
AssertCountByCond(t, "follow", builder.Eq{"user_id": user.int("ID")}, user.int("NumFollowing"))
|
||||
AssertCountByCond(t, "follow", builder.Eq{"follow_id": user.int("ID")}, user.int("NumFollowers"))
|
||||
if user.int("Type") != modelsUserTypeOrganization {
|
||||
assert.EqualValues(t, 0, user.int("NumMembers"))
|
||||
assert.EqualValues(t, 0, user.int("NumTeams"))
|
||||
}
|
||||
}
|
||||
|
||||
checkForRepoConsistency := func(t assert.TestingT, bean interface{}) {
|
||||
repo := reflectionWrap(bean)
|
||||
assert.Equal(t, repo.str("LowerName"), strings.ToLower(repo.str("Name")), "repo: %+v", repo)
|
||||
AssertCountByCond(t, "star", builder.Eq{"repo_id": repo.int("ID")}, repo.int("NumStars"))
|
||||
AssertCountByCond(t, "milestone", builder.Eq{"repo_id": repo.int("ID")}, repo.int("NumMilestones"))
|
||||
AssertCountByCond(t, "repository", builder.Eq{"fork_id": repo.int("ID")}, repo.int("NumForks"))
|
||||
if repo.bool("IsFork") {
|
||||
AssertExistsAndLoadMap(t, "repository", builder.Eq{"id": repo.int("ForkID")})
|
||||
}
|
||||
|
||||
actual := GetCountByCond(t, "watch", builder.Eq{"repo_id": repo.int("ID")}.
|
||||
And(builder.Neq{"mode": modelsRepoWatchModeDont}))
|
||||
assert.EqualValues(t, repo.int("NumWatches"), actual,
|
||||
"Unexpected number of watches for repo %+v", repo)
|
||||
|
||||
actual = GetCountByCond(t, "issue", builder.Eq{"is_pull": false, "repo_id": repo.int("ID")})
|
||||
assert.EqualValues(t, repo.int("NumIssues"), actual,
|
||||
"Unexpected number of issues for repo %+v", repo)
|
||||
|
||||
actual = GetCountByCond(t, "issue", builder.Eq{"is_pull": false, "is_closed": true, "repo_id": repo.int("ID")})
|
||||
assert.EqualValues(t, repo.int("NumClosedIssues"), actual,
|
||||
"Unexpected number of closed issues for repo %+v", repo)
|
||||
|
||||
actual = GetCountByCond(t, "issue", builder.Eq{"is_pull": true, "repo_id": repo.int("ID")})
|
||||
assert.EqualValues(t, repo.int("NumPulls"), actual,
|
||||
"Unexpected number of pulls for repo %+v", repo)
|
||||
|
||||
actual = GetCountByCond(t, "issue", builder.Eq{"is_pull": true, "is_closed": true, "repo_id": repo.int("ID")})
|
||||
assert.EqualValues(t, repo.int("NumClosedPulls"), actual,
|
||||
"Unexpected number of closed pulls for repo %+v", repo)
|
||||
|
||||
actual = GetCountByCond(t, "milestone", builder.Eq{"is_closed": true, "repo_id": repo.int("ID")})
|
||||
assert.EqualValues(t, repo.int("NumClosedMilestones"), actual,
|
||||
"Unexpected number of closed milestones for repo %+v", repo)
|
||||
}
|
||||
|
||||
checkForIssueConsistency := func(t assert.TestingT, bean interface{}) {
|
||||
issue := reflectionWrap(bean)
|
||||
typeComment := modelsCommentTypeComment
|
||||
actual := GetCountByCond(t, "comment", builder.Eq{"`type`": typeComment, "issue_id": issue.int("ID")})
|
||||
assert.EqualValues(t, issue.int("NumComments"), actual, "Unexpected number of comments for issue %+v", issue)
|
||||
if issue.bool("IsPull") {
|
||||
prRow := AssertExistsAndLoadMap(t, "pull_request", builder.Eq{"issue_id": issue.int("ID")})
|
||||
assert.EqualValues(t, parseInt(prRow["index"]), issue.int("Index"))
|
||||
}
|
||||
}
|
||||
|
||||
checkForPullRequestConsistency := func(t assert.TestingT, bean interface{}) {
|
||||
pr := reflectionWrap(bean)
|
||||
issueRow := AssertExistsAndLoadMap(t, "issue", builder.Eq{"id": pr.int("IssueID")})
|
||||
assert.True(t, parseBool(issueRow["is_pull"]))
|
||||
assert.EqualValues(t, parseInt(issueRow["index"]), pr.int("Index"))
|
||||
}
|
||||
|
||||
checkForMilestoneConsistency := func(t assert.TestingT, bean interface{}) {
|
||||
milestone := reflectionWrap(bean)
|
||||
AssertCountByCond(t, "issue", builder.Eq{"milestone_id": milestone.int("ID")}, milestone.int("NumIssues"))
|
||||
|
||||
actual := GetCountByCond(t, "issue", builder.Eq{"is_closed": true, "milestone_id": milestone.int("ID")})
|
||||
assert.EqualValues(t, milestone.int("NumClosedIssues"), actual, "Unexpected number of closed issues for milestone %+v", milestone)
|
||||
|
||||
completeness := 0
|
||||
if milestone.int("NumIssues") > 0 {
|
||||
completeness = milestone.int("NumClosedIssues") * 100 / milestone.int("NumIssues")
|
||||
}
|
||||
assert.Equal(t, completeness, milestone.int("Completeness"))
|
||||
}
|
||||
|
||||
checkForLabelConsistency := func(t assert.TestingT, bean interface{}) {
|
||||
label := reflectionWrap(bean)
|
||||
issueLabels, err := db.GetEngine(db.DefaultContext).Table("issue_label").
|
||||
Where(builder.Eq{"label_id": label.int("ID")}).
|
||||
Query()
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, label.int("NumIssues"), len(issueLabels), "Unexpected number of issue for label %+v", label)
|
||||
|
||||
issueIDs := make([]int, len(issueLabels))
|
||||
for i, issueLabel := range issueLabels {
|
||||
issueIDs[i], _ = strconv.Atoi(string(issueLabel["issue_id"]))
|
||||
}
|
||||
|
||||
expected := int64(0)
|
||||
if len(issueIDs) > 0 {
|
||||
expected = GetCountByCond(t, "issue", builder.In("id", issueIDs).And(builder.Eq{"is_closed": true}))
|
||||
}
|
||||
assert.EqualValues(t, expected, label.int("NumClosedIssues"), "Unexpected number of closed issues for label %+v", label)
|
||||
}
|
||||
|
||||
checkForTeamConsistency := func(t assert.TestingT, bean interface{}) {
|
||||
team := reflectionWrap(bean)
|
||||
AssertCountByCond(t, "team_user", builder.Eq{"team_id": team.int("ID")}, team.int("NumMembers"))
|
||||
AssertCountByCond(t, "team_repo", builder.Eq{"team_id": team.int("ID")}, team.int("NumRepos"))
|
||||
}
|
||||
|
||||
checkForActionConsistency := func(t assert.TestingT, bean interface{}) {
|
||||
action := reflectionWrap(bean)
|
||||
repoRow := AssertExistsAndLoadMap(t, "repository", builder.Eq{"id": action.int("RepoID")})
|
||||
assert.Equal(t, parseBool(repoRow["is_private"]), action.bool("IsPrivate"), "action: %+v", action)
|
||||
}
|
||||
|
||||
consistencyCheckMap["user"] = checkForUserConsistency
|
||||
consistencyCheckMap["repository"] = checkForRepoConsistency
|
||||
consistencyCheckMap["issue"] = checkForIssueConsistency
|
||||
consistencyCheckMap["pull_request"] = checkForPullRequestConsistency
|
||||
consistencyCheckMap["milestone"] = checkForMilestoneConsistency
|
||||
consistencyCheckMap["label"] = checkForLabelConsistency
|
||||
consistencyCheckMap["team"] = checkForTeamConsistency
|
||||
consistencyCheckMap["action"] = checkForActionConsistency
|
||||
}
|
41
models/unittest/reflection.go
Normal file
41
models/unittest/reflection.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
// 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 unittest
|
||||
|
||||
import (
|
||||
"log"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func fieldByName(v reflect.Value, field string) reflect.Value {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
f := v.FieldByName(field)
|
||||
if !f.IsValid() {
|
||||
log.Panicf("can not read %s for %v", field, v)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
type reflectionValue struct {
|
||||
v reflect.Value
|
||||
}
|
||||
|
||||
func reflectionWrap(v interface{}) *reflectionValue {
|
||||
return &reflectionValue{v: reflect.ValueOf(v)}
|
||||
}
|
||||
|
||||
func (rv *reflectionValue) int(field string) int {
|
||||
return int(fieldByName(rv.v, field).Int())
|
||||
}
|
||||
|
||||
func (rv *reflectionValue) str(field string) string {
|
||||
return fieldByName(rv.v, field).String()
|
||||
}
|
||||
|
||||
func (rv *reflectionValue) bool(field string) bool {
|
||||
return fieldByName(rv.v, field).Bool()
|
||||
}
|
|
@ -18,7 +18,6 @@ import (
|
|||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/names"
|
||||
)
|
||||
|
@ -43,7 +42,7 @@ func fatalTestError(fmtStr string, args ...interface{}) {
|
|||
// test database. Creates the test database, and sets necessary settings.
|
||||
func MainTest(m *testing.M, pathToGiteaRoot string, fixtureFiles ...string) {
|
||||
var err error
|
||||
InitUnitTestBridge()
|
||||
|
||||
giteaRoot = pathToGiteaRoot
|
||||
fixturesDir = filepath.Join(pathToGiteaRoot, "models", "fixtures")
|
||||
|
||||
|
@ -125,7 +124,7 @@ func CreateTestEngine(opts FixturesOptions) error {
|
|||
return err
|
||||
}
|
||||
x.SetMapper(names.GonicMapper{})
|
||||
db.SetUnitTestEngine(x)
|
||||
db.SetEngine(x)
|
||||
|
||||
if err = db.SyncAllTables(); err != nil {
|
||||
return err
|
||||
|
|
139
models/unittest/unit_tests.go
Normal file
139
models/unittest/unit_tests.go
Normal file
|
@ -0,0 +1,139 @@
|
|||
// Copyright 2016 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 unittest
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// Code in this file is mainly used by unittest.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)
|
||||
|
||||
type testCond struct {
|
||||
query interface{}
|
||||
args []interface{}
|
||||
}
|
||||
|
||||
// Cond create a condition with arguments for a test
|
||||
func Cond(query interface{}, args ...interface{}) interface{} {
|
||||
return &testCond{query: query, args: args}
|
||||
}
|
||||
|
||||
func whereConditions(e db.Engine, conditions []interface{}) db.Engine {
|
||||
for _, condition := range conditions {
|
||||
switch cond := condition.(type) {
|
||||
case *testCond:
|
||||
e = e.Where(cond.query, cond.args...)
|
||||
default:
|
||||
e = e.Where(cond)
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// LoadBeanIfExists loads beans from fixture database if exist
|
||||
func LoadBeanIfExists(bean interface{}, conditions ...interface{}) (bool, error) {
|
||||
e := db.GetEngine(db.DefaultContext)
|
||||
return whereConditions(e, conditions).Get(bean)
|
||||
}
|
||||
|
||||
// BeanExists for testing, check if a bean exists
|
||||
func BeanExists(t assert.TestingT, bean interface{}, conditions ...interface{}) bool {
|
||||
exists, err := LoadBeanIfExists(bean, conditions...)
|
||||
assert.NoError(t, err)
|
||||
return exists
|
||||
}
|
||||
|
||||
// AssertExistsAndLoadBean assert that a bean exists and load it from the test database
|
||||
func AssertExistsAndLoadBean(t assert.TestingT, bean interface{}, conditions ...interface{}) interface{} {
|
||||
exists, err := LoadBeanIfExists(bean, conditions...)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exists,
|
||||
"Expected to find %+v (of type %T, with conditions %+v), but did not",
|
||||
bean, bean, conditions)
|
||||
return bean
|
||||
}
|
||||
|
||||
// AssertExistsAndLoadMap assert that a row exists and load it from the test database
|
||||
func AssertExistsAndLoadMap(t assert.TestingT, table string, conditions ...interface{}) map[string]string {
|
||||
e := db.GetEngine(db.DefaultContext).Table(table)
|
||||
res, err := whereConditions(e, conditions).Query()
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, len(res) == 1,
|
||||
"Expected to find one row in %s (with conditions %+v), but found %d",
|
||||
table, conditions, len(res),
|
||||
)
|
||||
|
||||
if len(res) == 1 {
|
||||
rec := map[string]string{}
|
||||
for k, v := range res[0] {
|
||||
rec[k] = string(v)
|
||||
}
|
||||
return rec
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCount get the count of a bean
|
||||
func GetCount(t assert.TestingT, bean interface{}, conditions ...interface{}) int {
|
||||
e := db.GetEngine(db.DefaultContext)
|
||||
count, err := whereConditions(e, conditions).Count(bean)
|
||||
assert.NoError(t, err)
|
||||
return int(count)
|
||||
}
|
||||
|
||||
// AssertNotExistsBean assert that a bean does not exist in the test database
|
||||
func AssertNotExistsBean(t assert.TestingT, bean interface{}, conditions ...interface{}) {
|
||||
exists, err := LoadBeanIfExists(bean, conditions...)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, exists)
|
||||
}
|
||||
|
||||
// AssertExistsIf asserts that a bean exists or does not exist, depending on
|
||||
// what is expected.
|
||||
func AssertExistsIf(t assert.TestingT, expected bool, bean interface{}, conditions ...interface{}) {
|
||||
exists, err := LoadBeanIfExists(bean, conditions...)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, exists)
|
||||
}
|
||||
|
||||
// AssertSuccessfulInsert assert that beans is successfully inserted
|
||||
func AssertSuccessfulInsert(t assert.TestingT, beans ...interface{}) {
|
||||
err := db.Insert(db.DefaultContext, beans...)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// AssertCount assert the count of a bean
|
||||
func AssertCount(t assert.TestingT, bean, expected interface{}) {
|
||||
assert.EqualValues(t, expected, GetCount(t, bean))
|
||||
}
|
||||
|
||||
// AssertInt64InRange assert value is in range [low, high]
|
||||
func AssertInt64InRange(t assert.TestingT, low, high, value int64) {
|
||||
assert.True(t, value >= low && value <= high,
|
||||
"Expected value in range [%d, %d], found %d", low, high, value)
|
||||
}
|
||||
|
||||
// GetCountByCond get the count of database entries matching bean
|
||||
func GetCountByCond(t assert.TestingT, tableName string, cond builder.Cond) int64 {
|
||||
e := db.GetEngine(db.DefaultContext)
|
||||
count, err := e.Table(tableName).Where(cond).Count()
|
||||
assert.NoError(t, err)
|
||||
return count
|
||||
}
|
||||
|
||||
// AssertCountByCond test the count of database entries matching bean
|
||||
func AssertCountByCond(t assert.TestingT, tableName string, cond builder.Cond, expected int) {
|
||||
assert.EqualValues(t, expected, GetCountByCond(t, tableName, cond),
|
||||
"Failed consistency test, the counted bean (of table %s) was %+v", tableName, cond)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue