loading up the forgejo repo on tangled to test page performance
at forgejo 325 lines 9.7 kB view raw
1// Copyright 2022 The Gitea Authors. All rights reserved. 2// SPDX-License-Identifier: MIT 3 4package integration 5 6import ( 7 "errors" 8 "fmt" 9 "net/url" 10 "os" 11 "path/filepath" 12 "reflect" 13 "strings" 14 "testing" 15 16 auth_model "forgejo.org/models/auth" 17 repo_model "forgejo.org/models/repo" 18 "forgejo.org/models/unittest" 19 user_model "forgejo.org/models/user" 20 base "forgejo.org/modules/migration" 21 "forgejo.org/modules/setting" 22 "forgejo.org/modules/structs" 23 "forgejo.org/services/migrations" 24 25 "github.com/stretchr/testify/assert" 26 "github.com/stretchr/testify/require" 27 "gopkg.in/yaml.v3" 28) 29 30func TestDumpRestore(t *testing.T) { 31 onGiteaRun(t, func(t *testing.T, u *url.URL) { 32 AllowLocalNetworks := setting.Migrations.AllowLocalNetworks 33 setting.Migrations.AllowLocalNetworks = true 34 AppVer := setting.AppVer 35 // Gitea SDK (go-sdk) need to parse the AppVer from server response, so we must set it to a valid version string. 36 setting.AppVer = "1.16.0" 37 defer func() { 38 setting.Migrations.AllowLocalNetworks = AllowLocalNetworks 39 setting.AppVer = AppVer 40 }() 41 42 require.NoError(t, migrations.Init()) 43 44 reponame := "repo1" 45 46 basePath := t.TempDir() 47 48 repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}) 49 repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) 50 session := loginUser(t, repoOwner.Name) 51 token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteIssue, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeReadMisc) 52 53 // 54 // Phase 1: dump repo1 from the Gitea instance to the filesystem 55 // 56 57 ctx := t.Context() 58 opts := migrations.MigrateOptions{ 59 GitServiceType: structs.GiteaService, 60 Issues: true, 61 PullRequests: true, 62 Labels: true, 63 Milestones: true, 64 Comments: true, 65 AuthToken: token, 66 CloneAddr: repo.CloneLink().HTTPS, 67 RepoName: reponame, 68 } 69 err := migrations.DumpRepository(ctx, basePath, repoOwner.Name, opts) 70 require.NoError(t, err) 71 72 // 73 // Verify desired side effects of the dump 74 // 75 d := filepath.Join(basePath, repo.OwnerName, repo.Name) 76 for _, f := range []string{"repo.yml", "topic.yml", "label.yml", "milestone.yml", "issue.yml"} { 77 assert.FileExists(t, filepath.Join(d, f)) 78 } 79 80 // 81 // Phase 2: restore from the filesystem to the Gitea instance in restoredrepo 82 // 83 84 newreponame := "restored" 85 err = migrations.RestoreRepository(ctx, d, repo.OwnerName, newreponame, []string{ 86 "labels", "issues", "comments", "milestones", "pull_requests", 87 }, false) 88 require.NoError(t, err) 89 90 newrepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: newreponame}) 91 92 // 93 // Phase 3: dump restored from the Gitea instance to the filesystem 94 // 95 opts.RepoName = newreponame 96 opts.CloneAddr = newrepo.CloneLink().HTTPS 97 err = migrations.DumpRepository(ctx, basePath, repoOwner.Name, opts) 98 require.NoError(t, err) 99 100 // 101 // Verify the dump of restored is the same as the dump of repo1 102 // 103 comparator := &compareDump{ 104 t: t, 105 basePath: basePath, 106 } 107 comparator.assertEquals(repo, newrepo) 108 }) 109} 110 111type compareDump struct { 112 t *testing.T 113 basePath string 114 repoBefore *repo_model.Repository 115 dirBefore string 116 repoAfter *repo_model.Repository 117 dirAfter string 118} 119 120type compareField struct { 121 before any 122 after any 123 ignore bool 124 transform func(string) string 125 nested *compareFields 126} 127 128type compareFields map[string]compareField 129 130func (c *compareDump) replaceRepoName(original string) string { 131 return strings.ReplaceAll(original, c.repoBefore.Name, c.repoAfter.Name) 132} 133 134func (c *compareDump) assertEquals(repoBefore, repoAfter *repo_model.Repository) { 135 c.repoBefore = repoBefore 136 c.dirBefore = filepath.Join(c.basePath, repoBefore.OwnerName, repoBefore.Name) 137 c.repoAfter = repoAfter 138 c.dirAfter = filepath.Join(c.basePath, repoAfter.OwnerName, repoAfter.Name) 139 140 // 141 // base.Repository 142 // 143 _ = c.assertEqual("repo.yml", base.Repository{}, compareFields{ 144 "Name": { 145 before: c.repoBefore.Name, 146 after: c.repoAfter.Name, 147 }, 148 "CloneURL": {transform: c.replaceRepoName}, 149 "OriginalURL": {transform: c.replaceRepoName}, 150 }) 151 152 // 153 // base.Label 154 // 155 labels, ok := c.assertEqual("label.yml", []base.Label{}, compareFields{}).([]*base.Label) 156 assert.True(c.t, ok) 157 assert.GreaterOrEqual(c.t, len(labels), 1) 158 159 // 160 // base.Milestone 161 // 162 milestones, ok := c.assertEqual("milestone.yml", []base.Milestone{}, compareFields{ 163 "Updated": {ignore: true}, // the database updates that field independently 164 }).([]*base.Milestone) 165 assert.True(c.t, ok) 166 assert.GreaterOrEqual(c.t, len(milestones), 1) 167 168 // 169 // base.Issue and the associated comments 170 // 171 issues, ok := c.assertEqual("issue.yml", []base.Issue{}, compareFields{ 172 "Assignees": {ignore: true}, // not implemented yet 173 }).([]*base.Issue) 174 assert.True(c.t, ok) 175 assert.GreaterOrEqual(c.t, len(issues), 1) 176 for _, issue := range issues { 177 filename := filepath.Join("comments", fmt.Sprintf("%d.yml", issue.Number)) 178 comments, ok := c.assertEqual(filename, []base.Comment{}, compareFields{ 179 "Index": {ignore: true}, 180 }).([]*base.Comment) 181 assert.True(c.t, ok) 182 for _, comment := range comments { 183 assert.Equal(c.t, issue.Number, comment.IssueIndex) 184 } 185 } 186 187 // 188 // base.PullRequest and the associated comments 189 // 190 comparePullRequestBranch := &compareFields{ 191 "RepoName": { 192 before: c.repoBefore.Name, 193 after: c.repoAfter.Name, 194 }, 195 "CloneURL": {transform: c.replaceRepoName}, 196 } 197 prs, ok := c.assertEqual("pull_request.yml", []base.PullRequest{}, compareFields{ 198 "Assignees": {ignore: true}, // not implemented yet 199 "Head": {nested: comparePullRequestBranch}, 200 "Base": {nested: comparePullRequestBranch}, 201 "Labels": {ignore: true}, // because org labels are not handled properly 202 }).([]*base.PullRequest) 203 assert.True(c.t, ok) 204 assert.GreaterOrEqual(c.t, len(prs), 1) 205 for _, pr := range prs { 206 filename := filepath.Join("comments", fmt.Sprintf("%d.yml", pr.Number)) 207 comments, ok := c.assertEqual(filename, []base.Comment{}, compareFields{}).([]*base.Comment) 208 assert.True(c.t, ok) 209 for _, comment := range comments { 210 assert.Equal(c.t, pr.Number, comment.IssueIndex) 211 } 212 } 213} 214 215func (c *compareDump) assertLoadYAMLFiles(beforeFilename, afterFilename string, before, after any) { 216 _, beforeErr := os.Stat(beforeFilename) 217 _, afterErr := os.Stat(afterFilename) 218 assert.Equal(c.t, errors.Is(beforeErr, os.ErrNotExist), errors.Is(afterErr, os.ErrNotExist)) 219 if errors.Is(beforeErr, os.ErrNotExist) { 220 return 221 } 222 223 beforeBytes, err := os.ReadFile(beforeFilename) 224 require.NoError(c.t, err) 225 require.NoError(c.t, yaml.Unmarshal(beforeBytes, before)) 226 afterBytes, err := os.ReadFile(afterFilename) 227 require.NoError(c.t, err) 228 require.NoError(c.t, yaml.Unmarshal(afterBytes, after)) 229} 230 231func (c *compareDump) assertLoadFiles(beforeFilename, afterFilename string, t reflect.Type) (before, after reflect.Value) { 232 var beforePtr, afterPtr reflect.Value 233 if t.Kind() == reflect.Slice { 234 // 235 // Given []Something{} create afterPtr, beforePtr []*Something{} 236 // 237 sliceType := reflect.SliceOf(reflect.PointerTo(t.Elem())) 238 beforeSlice := reflect.MakeSlice(sliceType, 0, 10) 239 beforePtr = reflect.New(beforeSlice.Type()) 240 beforePtr.Elem().Set(beforeSlice) 241 afterSlice := reflect.MakeSlice(sliceType, 0, 10) 242 afterPtr = reflect.New(afterSlice.Type()) 243 afterPtr.Elem().Set(afterSlice) 244 } else { 245 // 246 // Given Something{} create afterPtr, beforePtr *Something{} 247 // 248 beforePtr = reflect.New(t) 249 afterPtr = reflect.New(t) 250 } 251 c.assertLoadYAMLFiles(beforeFilename, afterFilename, beforePtr.Interface(), afterPtr.Interface()) 252 return beforePtr.Elem(), afterPtr.Elem() 253} 254 255func (c *compareDump) assertEqual(filename string, kind any, fields compareFields) (i any) { 256 beforeFilename := filepath.Join(c.dirBefore, filename) 257 afterFilename := filepath.Join(c.dirAfter, filename) 258 259 typeOf := reflect.TypeOf(kind) 260 before, after := c.assertLoadFiles(beforeFilename, afterFilename, typeOf) 261 if typeOf.Kind() == reflect.Slice { 262 i = c.assertEqualSlices(before, after, fields) 263 } else { 264 i = c.assertEqualValues(before, after, fields) 265 } 266 return i 267} 268 269func (c *compareDump) assertEqualSlices(before, after reflect.Value, fields compareFields) any { 270 assert.Equal(c.t, before.Len(), after.Len()) 271 if before.Len() == after.Len() { 272 for i := 0; i < before.Len(); i++ { 273 _ = c.assertEqualValues( 274 reflect.Indirect(before.Index(i).Elem()), 275 reflect.Indirect(after.Index(i).Elem()), 276 fields) 277 } 278 } 279 return after.Interface() 280} 281 282func (c *compareDump) assertEqualValues(before, after reflect.Value, fields compareFields) any { 283 for _, field := range reflect.VisibleFields(before.Type()) { 284 bf := before.FieldByName(field.Name) 285 bi := bf.Interface() 286 af := after.FieldByName(field.Name) 287 ai := af.Interface() 288 if compare, ok := fields[field.Name]; ok { 289 if compare.ignore == true { 290 // 291 // Ignore 292 // 293 continue 294 } 295 if compare.transform != nil { 296 // 297 // Transform these strings before comparing them 298 // 299 bs, ok := bi.(string) 300 assert.True(c.t, ok) 301 as, ok := ai.(string) 302 assert.True(c.t, ok) 303 assert.Equal(c.t, compare.transform(bs), compare.transform(as)) 304 continue 305 } 306 if compare.before != nil && compare.after != nil { 307 // 308 // The fields are expected to have different values 309 // 310 assert.Equal(c.t, compare.before, bi) 311 assert.Equal(c.t, compare.after, ai) 312 continue 313 } 314 if compare.nested != nil { 315 // 316 // The fields are a struct, recurse 317 // 318 c.assertEqualValues(bf, af, *compare.nested) 319 continue 320 } 321 } 322 assert.Equal(c.t, bi, ai) 323 } 324 return after.Interface() 325}