Browse Source

Consistency checks for unit tests (#853)

Ethan Koenig 3 years ago
parent
commit
ceae143e78
6 changed files with 182 additions and 12 deletions
  1. 149 0
      models/consistency_test.go
  2. 1 0
      models/models.go
  3. 1 0
      models/notification_test.go
  4. 16 0
      models/org_test.go
  5. 8 11
      models/pull_test.go
  6. 7 1
      models/setup_for_test.go

+ 149 - 0
models/consistency_test.go

@@ -0,0 +1,149 @@
1
+// Copyright 2017 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
+
5
+package models
6
+
7
+import (
8
+	"reflect"
9
+	"testing"
10
+
11
+	"github.com/stretchr/testify/assert"
12
+)
13
+
14
+// ConsistencyCheckable a type that can be tested for database consistency
15
+type ConsistencyCheckable interface {
16
+	CheckForConsistency(t *testing.T)
17
+}
18
+
19
+// CheckConsistencyForAll test that the entire database is consistent
20
+func CheckConsistencyForAll(t *testing.T) {
21
+	CheckConsistencyFor(t,
22
+		&User{},
23
+		&Repository{},
24
+		&Issue{},
25
+		&PullRequest{},
26
+		&Milestone{},
27
+		&Label{},
28
+		&Team{})
29
+}
30
+
31
+// CheckConsistencyFor test that all matching database entries are consistent
32
+func CheckConsistencyFor(t *testing.T, beansToCheck ...interface{}) {
33
+	for _, bean := range beansToCheck {
34
+		sliceType := reflect.SliceOf(reflect.TypeOf(bean))
35
+		sliceValue := reflect.MakeSlice(sliceType, 0, 10)
36
+
37
+		ptrToSliceValue := reflect.New(sliceType)
38
+		ptrToSliceValue.Elem().Set(sliceValue)
39
+
40
+		assert.NoError(t, x.Find(ptrToSliceValue.Interface()))
41
+		sliceValue = ptrToSliceValue.Elem()
42
+
43
+		for i := 0; i < sliceValue.Len(); i++ {
44
+			entity := sliceValue.Index(i).Interface()
45
+			checkable, ok := entity.(ConsistencyCheckable)
46
+			if !ok {
47
+				t.Errorf("Expected %+v (of type %T) to be checkable for consistency",
48
+					entity, entity)
49
+			} else {
50
+				checkable.CheckForConsistency(t)
51
+			}
52
+		}
53
+	}
54
+}
55
+
56
+// getCount get the count of database entries matching bean
57
+func getCount(t *testing.T, e Engine, bean interface{}) int64 {
58
+	count, err := e.Count(bean)
59
+	assert.NoError(t, err)
60
+	return count
61
+}
62
+
63
+// assertCount test the count of database entries matching bean
64
+func assertCount(t *testing.T, bean interface{}, expected int) {
65
+	assert.EqualValues(t, expected, getCount(t, x, bean),
66
+		"Failed consistency test, the counted bean (of type %T) was %+v", bean, bean)
67
+}
68
+
69
+func (user *User) CheckForConsistency(t *testing.T) {
70
+	assertCount(t, &Repository{OwnerID: user.ID}, user.NumRepos)
71
+	assertCount(t, &Star{UID: user.ID}, user.NumStars)
72
+	assertCount(t, &OrgUser{OrgID: user.ID}, user.NumMembers)
73
+	assertCount(t, &Team{OrgID: user.ID}, user.NumTeams)
74
+	assertCount(t, &Follow{UserID: user.ID}, user.NumFollowing)
75
+	assertCount(t, &Follow{FollowID: user.ID}, user.NumFollowers)
76
+}
77
+
78
+func (repo *Repository) CheckForConsistency(t *testing.T) {
79
+	assertCount(t, &Star{RepoID: repo.ID}, repo.NumStars)
80
+	assertCount(t, &Watch{RepoID: repo.ID}, repo.NumWatches)
81
+	assertCount(t, &Issue{RepoID: repo.ID}, repo.NumIssues)
82
+	assertCount(t, &Milestone{RepoID: repo.ID}, repo.NumMilestones)
83
+	assertCount(t, &Repository{ForkID: repo.ID}, repo.NumForks)
84
+	if repo.IsFork {
85
+		AssertExistsAndLoadBean(t, &Repository{ID: repo.ForkID})
86
+	}
87
+
88
+	actual := getCount(t, x.Where("is_closed=1"), &Issue{RepoID: repo.ID})
89
+	assert.EqualValues(t, repo.NumClosedIssues, actual,
90
+		"Unexpected number of closed issues for repo %+v", repo)
91
+
92
+	actual = getCount(t, x.Where("is_pull=1"), &Issue{RepoID: repo.ID})
93
+	assert.EqualValues(t, repo.NumPulls, actual,
94
+		"Unexpected number of pulls for repo %+v", repo)
95
+
96
+	actual = getCount(t, x.Where("is_pull=1 AND is_closed=1"), &Issue{RepoID: repo.ID})
97
+	assert.EqualValues(t, repo.NumClosedPulls, actual,
98
+		"Unexpected number of closed pulls for repo %+v", repo)
99
+
100
+	actual = getCount(t, x.Where("is_closed=1"), &Milestone{RepoID: repo.ID})
101
+	assert.EqualValues(t, repo.NumClosedMilestones, actual,
102
+		"Unexpected number of closed milestones for repo %+v", repo)
103
+}
104
+
105
+func (issue *Issue) CheckForConsistency(t *testing.T) {
106
+	assertCount(t, &Comment{IssueID: issue.ID}, issue.NumComments)
107
+	if issue.IsPull {
108
+		pr := AssertExistsAndLoadBean(t, &PullRequest{IssueID: issue.ID}).(*PullRequest)
109
+		assert.EqualValues(t, pr.Index, issue.Index)
110
+	}
111
+}
112
+
113
+func (pr *PullRequest) CheckForConsistency(t *testing.T) {
114
+	issue := AssertExistsAndLoadBean(t, &Issue{ID: pr.IssueID}).(*Issue)
115
+	assert.True(t, issue.IsPull)
116
+	assert.EqualValues(t, issue.Index, pr.Index)
117
+}
118
+
119
+func (milestone *Milestone) CheckForConsistency(t *testing.T) {
120
+	assertCount(t, &Issue{MilestoneID: milestone.ID}, milestone.NumIssues)
121
+
122
+	actual := getCount(t, x.Where("is_closed=1"), &Issue{MilestoneID: milestone.ID})
123
+	assert.EqualValues(t, milestone.NumClosedIssues, actual,
124
+		"Unexpected number of closed issues for milestone %+v", milestone)
125
+}
126
+
127
+func (label *Label) CheckForConsistency(t *testing.T) {
128
+	issueLabels := make([]*IssueLabel, 0, 10)
129
+	assert.NoError(t, x.Find(&issueLabels, &IssueLabel{LabelID: label.ID}))
130
+	assert.EqualValues(t, label.NumIssues, len(issueLabels),
131
+		"Unexpected number of issue for label %+v", label)
132
+
133
+	issueIDs := make([]int64, len(issueLabels))
134
+	for i, issueLabel := range issueLabels {
135
+		issueIDs[i] = issueLabel.IssueID
136
+	}
137
+
138
+	expected := int64(0)
139
+	if len(issueIDs) > 0 {
140
+		expected = getCount(t, x.In("id", issueIDs).Where("is_closed=1"), &Issue{})
141
+	}
142
+	assert.EqualValues(t, expected, label.NumClosedIssues,
143
+		"Unexpected number of closed issues for label %+v", label)
144
+}
145
+
146
+func (team *Team) CheckForConsistency(t *testing.T) {
147
+	assertCount(t, &TeamUser{TeamID: team.ID}, team.NumMembers)
148
+	assertCount(t, &TeamRepo{TeamID: team.ID}, team.NumRepos)
149
+}

+ 1 - 0
models/models.go

@@ -30,6 +30,7 @@ import (
30 30
 
31 31
 // Engine represents a xorm engine or session.
32 32
 type Engine interface {
33
+	Count(interface{}) (int64, error)
33 34
 	Decr(column string, arg ...interface{}) *xorm.Session
34 35
 	Delete(interface{}) (int64, error)
35 36
 	Exec(string, ...interface{}) (sql.Result, error)

+ 1 - 0
models/notification_test.go

@@ -20,6 +20,7 @@ func TestCreateOrUpdateIssueNotifications(t *testing.T) {
20 20
 	assert.Equal(t, NotificationStatusUnread, notf.Status)
21 21
 	notf = AssertExistsAndLoadBean(t, &Notification{UserID: 4, IssueID: issue.ID}).(*Notification)
22 22
 	assert.Equal(t, NotificationStatusUnread, notf.Status)
23
+	CheckConsistencyFor(t, &Issue{ID: issue.ID})
23 24
 }
24 25
 
25 26
 func TestNotificationsForUser(t *testing.T) {

+ 16 - 0
models/org_test.go

@@ -101,6 +101,8 @@ func TestUser_AddMember(t *testing.T) {
101 101
 	AssertExistsAndLoadBean(t, &OrgUser{UID: 4, OrgID: 3})
102 102
 	org = AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
103 103
 	assert.Equal(t, prevNumMembers, org.NumMembers)
104
+
105
+	CheckConsistencyFor(t, &User{})
104 106
 }
105 107
 
106 108
 func TestUser_RemoveMember(t *testing.T) {
@@ -122,6 +124,8 @@ func TestUser_RemoveMember(t *testing.T) {
122 124
 	AssertNotExistsBean(t, &OrgUser{UID: 5, OrgID: 3})
123 125
 	org = AssertExistsAndLoadBean(t, &User{ID: 3}).(*User)
124 126
 	assert.Equal(t, prevNumMembers, org.NumMembers)
127
+
128
+	CheckConsistencyFor(t, &User{}, &Team{})
125 129
 }
126 130
 
127 131
 func TestUser_RemoveOrgRepo(t *testing.T) {
@@ -140,6 +144,11 @@ func TestUser_RemoveOrgRepo(t *testing.T) {
140 144
 	AssertNotExistsBean(t, &TeamRepo{RepoID: repo.ID, OrgID: org.ID})
141 145
 
142 146
 	assert.NoError(t, org.RemoveOrgRepo(NonexistentID))
147
+
148
+	CheckConsistencyFor(t,
149
+		&User{ID: org.ID},
150
+		&Team{OrgID: org.ID},
151
+		&Repository{ID: repo.ID})
143 152
 }
144 153
 
145 154
 func TestCreateOrganization(t *testing.T) {
@@ -159,6 +168,7 @@ func TestCreateOrganization(t *testing.T) {
159 168
 	ownerTeam := AssertExistsAndLoadBean(t,
160 169
 		&Team{Name: ownerTeamName, OrgID: org.ID}).(*Team)
161 170
 	AssertExistsAndLoadBean(t, &TeamUser{UID: owner.ID, TeamID: ownerTeam.ID})
171
+	CheckConsistencyFor(t, &User{}, &Team{})
162 172
 }
163 173
 
164 174
 func TestCreateOrganization2(t *testing.T) {
@@ -176,6 +186,7 @@ func TestCreateOrganization2(t *testing.T) {
176 186
 	assert.Error(t, err)
177 187
 	assert.True(t, IsErrUserNotAllowedCreateOrg(err))
178 188
 	AssertNotExistsBean(t, &User{Name: newOrgName, Type: UserTypeOrganization})
189
+	CheckConsistencyFor(t, &User{}, &Team{})
179 190
 }
180 191
 
181 192
 func TestCreateOrganization3(t *testing.T) {
@@ -188,6 +199,7 @@ func TestCreateOrganization3(t *testing.T) {
188 199
 	err := CreateOrganization(org, owner)
189 200
 	assert.Error(t, err)
190 201
 	assert.True(t, IsErrUserAlreadyExist(err))
202
+	CheckConsistencyFor(t, &User{}, &Team{})
191 203
 }
192 204
 
193 205
 func TestCreateOrganization4(t *testing.T) {
@@ -198,6 +210,7 @@ func TestCreateOrganization4(t *testing.T) {
198 210
 	err := CreateOrganization(&User{Name: "assets"}, owner)
199 211
 	assert.Error(t, err)
200 212
 	assert.True(t, IsErrNameReserved(err))
213
+	CheckConsistencyFor(t, &User{}, &Team{})
201 214
 }
202 215
 
203 216
 func TestGetOrgByName(t *testing.T) {
@@ -257,6 +270,7 @@ func TestDeleteOrganization(t *testing.T) {
257 270
 
258 271
 	nonOrg := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User)
259 272
 	assert.Error(t, DeleteOrganization(nonOrg))
273
+	CheckConsistencyFor(t, &User{}, &Team{})
260 274
 }
261 275
 
262 276
 func TestIsOrganizationOwner(t *testing.T) {
@@ -416,6 +430,7 @@ func TestAddOrgUser(t *testing.T) {
416 430
 	testSuccess(3, 5)
417 431
 	testSuccess(3, 5)
418 432
 	testSuccess(6, 2)
433
+	CheckConsistencyFor(t, &User{}, &Team{})
419 434
 }
420 435
 
421 436
 func TestRemoveOrgUser(t *testing.T) {
@@ -438,6 +453,7 @@ func TestRemoveOrgUser(t *testing.T) {
438 453
 	assert.Error(t, err)
439 454
 	assert.True(t, IsErrLastOrgOwner(err))
440 455
 	AssertExistsAndLoadBean(t, &OrgUser{OrgID: 7, UID: 5})
456
+	CheckConsistencyFor(t, &User{}, &Team{})
441 457
 }
442 458
 
443 459
 func TestUser_GetUserTeamIDs(t *testing.T) {

+ 8 - 11
models/pull_test.go

@@ -155,34 +155,30 @@ func TestGetPullRequestByIssueID(t *testing.T) {
155 155
 
156 156
 func TestPullRequest_Update(t *testing.T) {
157 157
 	assert.NoError(t, PrepareTestDatabase())
158
-	pr := &PullRequest{
159
-		ID:         1,
160
-		IssueID:    100,
161
-		BaseBranch: "baseBranch",
162
-		HeadBranch: "headBranch",
163
-	}
158
+	pr := AssertExistsAndLoadBean(t, &PullRequest{ID: 1}).(*PullRequest)
159
+	pr.BaseBranch = "baseBranch"
160
+	pr.HeadBranch = "headBranch"
164 161
 	pr.Update()
165 162
 
166
-	pr = AssertExistsAndLoadBean(t, &PullRequest{ID: 1}).(*PullRequest)
167
-	assert.Equal(t, int64(100), pr.IssueID)
163
+	pr = AssertExistsAndLoadBean(t, &PullRequest{ID: pr.ID}).(*PullRequest)
168 164
 	assert.Equal(t, "baseBranch", pr.BaseBranch)
169 165
 	assert.Equal(t, "headBranch", pr.HeadBranch)
166
+	CheckConsistencyFor(t, pr)
170 167
 }
171 168
 
172 169
 func TestPullRequest_UpdateCols(t *testing.T) {
173 170
 	assert.NoError(t, PrepareTestDatabase())
174 171
 	pr := &PullRequest{
175 172
 		ID:         1,
176
-		IssueID:    int64(100),
177 173
 		BaseBranch: "baseBranch",
178 174
 		HeadBranch: "headBranch",
179 175
 	}
180
-	pr.UpdateCols("issue_id", "head_branch")
176
+	pr.UpdateCols("head_branch")
181 177
 
182 178
 	pr = AssertExistsAndLoadBean(t, &PullRequest{ID: 1}).(*PullRequest)
183
-	assert.Equal(t, int64(100), pr.IssueID)
184 179
 	assert.Equal(t, "master", pr.BaseBranch)
185 180
 	assert.Equal(t, "headBranch", pr.HeadBranch)
181
+	CheckConsistencyFor(t, pr)
186 182
 }
187 183
 
188 184
 // TODO TestPullRequest_UpdatePatch
@@ -232,4 +228,5 @@ func TestChangeUsernameInPullRequests(t *testing.T) {
232 228
 	for _, pr := range prs {
233 229
 		assert.Equal(t, newUsername, pr.HeadUserName)
234 230
 	}
231
+	CheckConsistencyFor(t, &PullRequest{})
235 232
 }

+ 7 - 1
models/setup_for_test.go

@@ -56,6 +56,11 @@ func CreateTestEngine() error {
56 56
 	return err
57 57
 }
58 58
 
59
+func TestFixturesAreConsistent(t *testing.T) {
60
+	assert.NoError(t, PrepareTestDatabase())
61
+	CheckConsistencyForAll(t)
62
+}
63
+
59 64
 // PrepareTestDatabase load test fixtures into test database
60 65
 func PrepareTestDatabase() error {
61 66
 	return fixtures.Load()
@@ -84,7 +89,8 @@ func AssertExistsAndLoadBean(t *testing.T, bean interface{}, conditions ...inter
84 89
 	exists, err := loadBeanIfExists(bean, conditions...)
85 90
 	assert.NoError(t, err)
86 91
 	assert.True(t, exists,
87
-		"Expected to find %+v (with conditions %+v), but did not", bean, conditions)
92
+		"Expected to find %+v (of type %T, with conditions %+v), but did not",
93
+		bean, bean, conditions)
88 94
 	return bean
89 95
 }
90 96