loading up the forgejo repo on tangled to test page performance
at forgejo 496 lines 15 kB view raw
1// Copyright 2018 The Gitea Authors. All rights reserved. 2// Copyright 2025 The Forgejo Authors. 3// SPDX-License-Identifier: MIT 4 5package markup 6 7import ( 8 "fmt" 9 "strconv" 10 "strings" 11 "testing" 12 13 "forgejo.org/modules/git" 14 "forgejo.org/modules/setting" 15 "forgejo.org/modules/util" 16 17 "github.com/stretchr/testify/assert" 18 "github.com/stretchr/testify/require" 19) 20 21const ( 22 TestAppURL = "http://localhost:3000/" 23 TestOrgRepo = "gogits/gogs" 24 TestRepoURL = TestAppURL + TestOrgRepo + "/" 25) 26 27// externalIssueLink an HTML link to an alphanumeric-style issue 28func externalIssueLink(baseURL, class, name string) string { 29 return link(util.URLJoin(baseURL, name), class, name) 30} 31 32// numericLink an HTML to a numeric-style issue 33func numericIssueLink(baseURL, class string, index int, marker string) string { 34 return link(util.URLJoin(baseURL, strconv.Itoa(index)), class, fmt.Sprintf("%s%d", marker, index)) 35} 36 37// link an HTML link 38func link(href, class, contents string) string { 39 if class != "" { 40 class = " class=\"" + class + "\"" 41 } 42 43 return fmt.Sprintf("<a href=\"%s\"%s>%s</a>", href, class, contents) 44} 45 46var numericMetas = map[string]string{ 47 "format": "https://someurl.com/{user}/{repo}/{index}", 48 "user": "someUser", 49 "repo": "someRepo", 50 "style": IssueNameStyleNumeric, 51} 52 53var alphanumericMetas = map[string]string{ 54 "format": "https://someurl.com/{user}/{repo}/{index}", 55 "user": "someUser", 56 "repo": "someRepo", 57 "style": IssueNameStyleAlphanumeric, 58} 59 60var regexpMetas = map[string]string{ 61 "format": "https://someurl.com/{user}/{repo}/{index}", 62 "user": "someUser", 63 "repo": "someRepo", 64 "style": IssueNameStyleRegexp, 65} 66 67// these values should match the TestOrgRepo const above 68var localMetas = map[string]string{ 69 "user": "gogits", 70 "repo": "gogs", 71} 72 73func TestRender_IssueIndexPattern(t *testing.T) { 74 // numeric: render inputs without valid mentions 75 test := func(s string) { 76 testRenderIssueIndexPattern(t, s, s, &RenderContext{ 77 Ctx: git.DefaultContext, 78 }) 79 testRenderIssueIndexPattern(t, s, s, &RenderContext{ 80 Ctx: git.DefaultContext, 81 Metas: numericMetas, 82 }) 83 } 84 85 // should not render anything when there are no mentions 86 test("") 87 test("this is a test") 88 test("test 123 123 1234") 89 test("#") 90 test("# # #") 91 test("# 123") 92 test("#abcd") 93 test("test#1234") 94 test("#1234test") 95 test("#abcd") 96 test("test!1234") 97 test("!1234test") 98 test(" test !1234test") 99 test("/home/gitea/#1234") 100 test("/home/gitea/!1234") 101 102 // should not render issue mention without leading space 103 test("test#54321 issue") 104 105 // should not render issue mention without trailing space 106 test("test #54321issue") 107} 108 109func TestRender_IssueIndexPattern2(t *testing.T) { 110 setting.AppURL = TestAppURL 111 112 // numeric: render inputs with valid mentions 113 test := func(s, expectedFmt, marker string, indices ...int) { 114 var path, prefix string 115 isExternal := false 116 if marker == "!" { 117 path = "pulls" 118 prefix = "http://localhost:3000/someUser/someRepo/pulls/" 119 } else { 120 path = "issues" 121 prefix = "https://someurl.com/someUser/someRepo/" 122 isExternal = true 123 } 124 125 links := make([]any, len(indices)) 126 for i, index := range indices { 127 links[i] = numericIssueLink(util.URLJoin(TestRepoURL, path), "ref-issue", index, marker) 128 } 129 expectedNil := fmt.Sprintf(expectedFmt, links...) 130 testRenderIssueIndexPattern(t, s, expectedNil, &RenderContext{ 131 Ctx: git.DefaultContext, 132 Metas: localMetas, 133 }) 134 135 class := "ref-issue" 136 if isExternal { 137 class += " ref-external-issue" 138 } 139 140 for i, index := range indices { 141 links[i] = numericIssueLink(prefix, class, index, marker) 142 } 143 expectedNum := fmt.Sprintf(expectedFmt, links...) 144 testRenderIssueIndexPattern(t, s, expectedNum, &RenderContext{ 145 Ctx: git.DefaultContext, 146 Metas: numericMetas, 147 }) 148 } 149 150 // should render freestanding mentions 151 test("#1234 test", "%s test", "#", 1234) 152 test("test #8 issue", "test %s issue", "#", 8) 153 test("!1234 test", "%s test", "!", 1234) 154 test("test !8 issue", "test %s issue", "!", 8) 155 test("test issue #1234", "test issue %s", "#", 1234) 156 test("fixes issue #1234.", "fixes issue %s.", "#", 1234) 157 158 // should render mentions in parentheses / brackets 159 test("(#54321 issue)", "(%s issue)", "#", 54321) 160 test("[#54321 issue]", "[%s issue]", "#", 54321) 161 test("test (#9801 extra) issue", "test (%s extra) issue", "#", 9801) 162 test("test (!9801 extra) issue", "test (%s extra) issue", "!", 9801) 163 test("test (#1)", "test (%s)", "#", 1) 164 165 // should render multiple issue mentions in the same line 166 test("#54321 #1243", "%s %s", "#", 54321, 1243) 167 test("wow (#54321 #1243)", "wow (%s %s)", "#", 54321, 1243) 168 test("(#4)(#5)", "(%s)(%s)", "#", 4, 5) 169 test("#1 (#4321) test", "%s (%s) test", "#", 1, 4321) 170 171 // should render with : 172 test("#1234: test", "%s: test", "#", 1234) 173 test("wow (#54321: test)", "wow (%s: test)", "#", 54321) 174} 175 176func TestRender_IssueIndexPattern3(t *testing.T) { 177 setting.AppURL = TestAppURL 178 179 // alphanumeric: render inputs without valid mentions 180 test := func(s string) { 181 testRenderIssueIndexPattern(t, s, s, &RenderContext{ 182 Ctx: git.DefaultContext, 183 Metas: alphanumericMetas, 184 }) 185 } 186 test("") 187 test("this is a test") 188 test("test 123 123 1234") 189 test("#") 190 test("# 123") 191 test("#abcd") 192 test("test #123") 193 test("abc-1234") // issue prefix must be capital 194 test("ABc-1234") // issue prefix must be _all_ capital 195 test("ABCDEFGHIJK-1234") // the limit is 10 characters in the prefix 196 test("ABC1234") // dash is required 197 test("test ABC- test") // number is required 198 test("test -1234 test") // prefix is required 199 test("testABC-123 test") // leading space is required 200 test("test ABC-123test") // trailing space is required 201 test("ABC-0123") // no leading zero 202} 203 204func TestRender_IssueIndexPattern4(t *testing.T) { 205 setting.AppURL = TestAppURL 206 207 // alphanumeric: render inputs with valid mentions 208 test := func(s, expectedFmt string, names ...string) { 209 links := make([]any, len(names)) 210 for i, name := range names { 211 links[i] = externalIssueLink("https://someurl.com/someUser/someRepo/", "ref-issue ref-external-issue", name) 212 } 213 expected := fmt.Sprintf(expectedFmt, links...) 214 testRenderIssueIndexPattern(t, s, expected, &RenderContext{ 215 Ctx: git.DefaultContext, 216 Metas: alphanumericMetas, 217 }) 218 } 219 test("OTT-1234 test", "%s test", "OTT-1234") 220 test("test T-12 issue", "test %s issue", "T-12") 221 test("test issue ABCDEFGHIJ-1234567890", "test issue %s", "ABCDEFGHIJ-1234567890") 222} 223 224func TestRender_IssueIndexPattern5(t *testing.T) { 225 setting.AppURL = TestAppURL 226 227 // regexp: render inputs without valid mentions 228 test := func(s, expectedFmt, pattern string, ids, names []string) { 229 metas := regexpMetas 230 metas["regexp"] = pattern 231 links := make([]any, len(ids)) 232 for i, id := range ids { 233 links[i] = link(util.URLJoin("https://someurl.com/someUser/someRepo/", id), "ref-issue ref-external-issue", names[i]) 234 } 235 236 expected := fmt.Sprintf(expectedFmt, links...) 237 testRenderIssueIndexPattern(t, s, expected, &RenderContext{ 238 Ctx: git.DefaultContext, 239 Metas: metas, 240 }) 241 } 242 243 test("abc ISSUE-123 def", "abc %s def", 244 "ISSUE-(\\d+)", 245 []string{"123"}, 246 []string{"ISSUE-123"}, 247 ) 248 249 test("abc (ISSUE 123) def", "abc %s def", 250 "\\(ISSUE (\\d+)\\)", 251 []string{"123"}, 252 []string{"(ISSUE 123)"}, 253 ) 254 255 test("abc ISSUE-123 def", "abc %s def", 256 "(ISSUE-(\\d+))", 257 []string{"ISSUE-123"}, 258 []string{"ISSUE-123"}, 259 ) 260 261 testRenderIssueIndexPattern(t, "will not match", "will not match", &RenderContext{ 262 Ctx: git.DefaultContext, 263 Metas: regexpMetas, 264 }) 265} 266 267func TestRender_IssueIndexPattern_Document(t *testing.T) { 268 setting.AppURL = TestAppURL 269 metas := map[string]string{ 270 "format": "https://someurl.com/{user}/{repo}/{index}", 271 "user": "someUser", 272 "repo": "someRepo", 273 "style": IssueNameStyleNumeric, 274 "mode": "document", 275 } 276 277 testRenderIssueIndexPattern(t, "#1", "#1", &RenderContext{ 278 Ctx: git.DefaultContext, 279 Metas: metas, 280 }) 281 testRenderIssueIndexPattern(t, "#1312", "#1312", &RenderContext{ 282 Ctx: git.DefaultContext, 283 Metas: metas, 284 }) 285 testRenderIssueIndexPattern(t, "!1", "!1", &RenderContext{ 286 Ctx: git.DefaultContext, 287 Metas: metas, 288 }) 289} 290 291func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *RenderContext) { 292 ctx.Links.AbsolutePrefix = true 293 if ctx.Links.Base == "" { 294 ctx.Links.Base = TestRepoURL 295 } 296 297 var buf strings.Builder 298 err := postProcess(ctx, []processor{issueIndexPatternProcessor}, strings.NewReader(input), &buf) 299 require.NoError(t, err) 300 assert.Equal(t, expected, buf.String(), "input=%q", input) 301} 302 303func TestRender_AutoLink(t *testing.T) { 304 setting.AppURL = TestAppURL 305 306 test := func(input, expected string) { 307 var buffer strings.Builder 308 err := PostProcess(&RenderContext{ 309 Ctx: git.DefaultContext, 310 Links: Links{ 311 Base: TestRepoURL, 312 }, 313 Metas: localMetas, 314 }, strings.NewReader(input), &buffer) 315 require.NoError(t, err, nil) 316 assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String())) 317 318 buffer.Reset() 319 err = PostProcess(&RenderContext{ 320 Ctx: git.DefaultContext, 321 Links: Links{ 322 Base: TestRepoURL, 323 }, 324 Metas: localMetas, 325 IsWiki: true, 326 }, strings.NewReader(input), &buffer) 327 require.NoError(t, err) 328 assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String())) 329 } 330 331 // render valid issue URLs 332 test(util.URLJoin(TestRepoURL, "issues", "3333"), 333 numericIssueLink(util.URLJoin(TestRepoURL, "issues"), "ref-issue", 3333, "#")) 334 335 // render valid commit URLs 336 tmp := util.URLJoin(TestRepoURL, "commit", "d8a994ef243349f321568f9e36d5c3f444b99cae") 337 test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">d8a994ef24</code></a>") 338 tmp += "#diff-2" 339 test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">d8a994ef24 (diff-2)</code></a>") 340 341 // render other commit URLs 342 tmp = "https://external-link.gitea.io/go-gitea/gitea/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2" 343 test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">d8a994ef24 (diff-2)</code></a>") 344} 345 346func TestRender_IssueIndexPatternRef(t *testing.T) { 347 setting.AppURL = TestAppURL 348 349 test := func(input, expected string) { 350 var buf strings.Builder 351 err := postProcess(&RenderContext{ 352 Ctx: git.DefaultContext, 353 Metas: numericMetas, 354 }, []processor{issueIndexPatternProcessor}, strings.NewReader(input), &buf) 355 require.NoError(t, err) 356 assert.Equal(t, expected, buf.String(), "input=%q", input) 357 } 358 359 test("alan-turin/Enigma-cryptanalysis#1", `<a href="/alan-turin/enigma-cryptanalysis/issues/1" class="ref-issue">alan-turin/Enigma-cryptanalysis#1</a>`) 360} 361 362func TestRender_FullIssueURLs(t *testing.T) { 363 setting.AppURL = TestAppURL 364 365 test := func(input, expected string) { 366 var result strings.Builder 367 err := postProcess(&RenderContext{ 368 Ctx: git.DefaultContext, 369 Links: Links{ 370 Base: TestRepoURL, 371 }, 372 Metas: localMetas, 373 }, []processor{fullIssuePatternProcessor}, strings.NewReader(input), &result) 374 require.NoError(t, err) 375 assert.Equal(t, expected, result.String()) 376 } 377 test("Here is a link https://git.osgeo.org/gogs/postgis/postgis/pulls/6", 378 "Here is a link https://git.osgeo.org/gogs/postgis/postgis/pulls/6") 379 test("Look here http://localhost:3000/person/repo/issues/4", 380 `Look here <a href="http://localhost:3000/person/repo/issues/4" class="ref-issue">person/repo#4</a>`) 381 test("http://localhost:3000/person/repo/issues/4#issuecomment-1234", 382 `<a href="http://localhost:3000/person/repo/issues/4#issuecomment-1234" class="ref-issue">person/repo#4 (comment)</a>`) 383 test("http://localhost:3000/gogits/gogs/issues/4", 384 `<a href="http://localhost:3000/gogits/gogs/issues/4" class="ref-issue">#4</a>`) 385 test("http://localhost:3000/gogits/gogs/issues/4 test", 386 `<a href="http://localhost:3000/gogits/gogs/issues/4" class="ref-issue">#4</a> test`) 387 test("http://localhost:3000/gogits/gogs/issues/4?a=1&b=2#comment-form test", 388 `<a href="http://localhost:3000/gogits/gogs/issues/4?a=1&amp;b=2#comment-form" class="ref-issue">#4</a> test`) 389 test("http://localhost:3000/testOrg/testOrgRepo/pulls/2/files#issuecomment-24", 390 `<a href="http://localhost:3000/testOrg/testOrgRepo/pulls/2/files#issuecomment-24" class="ref-issue">testOrg/testOrgRepo#2/files (comment)</a>`) 391 test("http://localhost:3000/testOrg/testOrgRepo/pulls/2/commits", 392 `<a href="http://localhost:3000/testOrg/testOrgRepo/pulls/2/commits" class="ref-issue">testOrg/testOrgRepo#2/commits</a>`) 393} 394 395func TestRegExp_hashCurrentPattern(t *testing.T) { 396 trueTestCases := []string{ 397 "d8a994ef243349f321568f9e36d5c3f444b99cae", 398 "abcdefabcdefabcdefabcdefabcdefabcdefabcd", 399 "(abcdefabcdefabcdefabcdefabcdefabcdefabcd)", 400 "[abcdefabcdefabcdefabcdefabcdefabcdefabcd]", 401 "abcdefabcdefabcdefabcdefabcdefabcdefabcd.", 402 "abcdefabcdefabcdefabcdefabcdefabcdefabcd:", 403 "d8a994ef243349f321568f9e36d5c3f444b99cae12424fa123391042fbae2319", 404 "abcdefd?", 405 "abcdefd!", 406 "!abcd3ef", 407 ":abcd3ef", 408 ".abcd3ef", 409 " (abcd3ef). ", 410 } 411 falseTestCases := []string{ 412 "test", 413 "abcdefg", 414 "e59ff077-2d03-4e6b-964d-63fbaea81f", 415 "abcdefghijklmnopqrstuvwxyzabcdefghijklmn", 416 "abcdefghijklmnopqrstuvwxyzabcdefghijklmO", 417 "commit/abcdefd", 418 "abcd3ef...defabcd", 419 } 420 421 for _, testCase := range trueTestCases { 422 assert.True(t, hashCurrentPattern.MatchString(testCase)) 423 } 424 for _, testCase := range falseTestCases { 425 assert.False(t, hashCurrentPattern.MatchString(testCase)) 426 } 427} 428 429func TestRegExp_anySHA1Pattern(t *testing.T) { 430 testCases := map[string][]string{ 431 "https://github.com/jquery/jquery/blob/a644101ed04d0beacea864ce805e0c4f86ba1cd1/test/unit/event.js#L2703": { 432 "a644101ed04d0beacea864ce805e0c4f86ba1cd1", 433 "/test/unit/event.js", 434 "", 435 "#L2703", 436 }, 437 "https://github.com/jquery/jquery/blob/a644101ed04d0beacea864ce805e0c4f86ba1cd1/test/unit/event.js": { 438 "a644101ed04d0beacea864ce805e0c4f86ba1cd1", 439 "/test/unit/event.js", 440 "", 441 "", 442 }, 443 "https://github.com/jquery/jquery/commit/0705be475092aede1eddae01319ec931fb9c65fc": { 444 "0705be475092aede1eddae01319ec931fb9c65fc", 445 "", 446 "", 447 "", 448 }, 449 "https://github.com/jquery/jquery/tree/0705be475092aede1eddae01319ec931fb9c65fc/src": { 450 "0705be475092aede1eddae01319ec931fb9c65fc", 451 "/src", 452 "", 453 "", 454 }, 455 "https://try.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2": { 456 "d8a994ef243349f321568f9e36d5c3f444b99cae", 457 "", 458 "", 459 "#diff-2", 460 }, 461 "https://codeberg.org/forgejo/forgejo/src/commit/949ab9a5c4cac742f84ae5a9fa186f8d6eb2cdc0/RELEASE-NOTES.md?display=source&w=1#L7-L9": { 462 "949ab9a5c4cac742f84ae5a9fa186f8d6eb2cdc0", 463 "/RELEASE-NOTES.md", 464 "?display=source&w=1", 465 "#L7-L9", 466 }, 467 } 468 469 for k, v := range testCases { 470 assert.Equal(t, anyHashPattern.FindStringSubmatch(k)[1:], v) 471 } 472} 473 474func TestRegExp_shortLinkPattern(t *testing.T) { 475 trueTestCases := []string{ 476 "[[stuff]]", 477 "[[]]", 478 "[[stuff|title=Difficult name with spaces*!]]", 479 } 480 falseTestCases := []string{ 481 "test", 482 "abcdefg", 483 "[[]", 484 "[[", 485 "[]", 486 "]]", 487 "abcdefghijklmnopqrstuvwxyz", 488 } 489 490 for _, testCase := range trueTestCases { 491 assert.True(t, shortLinkPattern.MatchString(testCase)) 492 } 493 for _, testCase := range falseTestCases { 494 assert.False(t, shortLinkPattern.MatchString(testCase)) 495 } 496}