site_benchmark_new_test.go - hugo - [fork] hugo port for 9front
(HTM) git clone https://git.drkhsh.at/hugo.git
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) Submodules
(DIR) README
(DIR) LICENSE
---
site_benchmark_new_test.go (14616B)
---
1 // Copyright 2019 The Hugo Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 package hugolib
15
16 import (
17 "fmt"
18 "math/rand"
19 "path"
20 "path/filepath"
21 "strconv"
22 "strings"
23 "testing"
24
25 "github.com/gohugoio/hugo/resources/page"
26
27 qt "github.com/frankban/quicktest"
28 )
29
30 type siteBenchmarkTestcase struct {
31 name string
32 create func(t testing.TB) *sitesBuilder
33 check func(s *sitesBuilder)
34 }
35
36 func getBenchmarkSiteDeepContent(b testing.TB) *sitesBuilder {
37 pageContent := func(size int) string {
38 return getBenchmarkTestDataPageContentForMarkdown(size, false, "", benchmarkMarkdownSnippets)
39 }
40
41 sb := newTestSitesBuilder(b).WithConfigFile("toml", `
42 baseURL = "https://example.com"
43
44 [languages]
45 [languages.en]
46 weight=1
47 contentDir="content/en"
48 [languages.fr]
49 weight=2
50 contentDir="content/fr"
51 [languages.no]
52 weight=3
53 contentDir="content/no"
54 [languages.sv]
55 weight=4
56 contentDir="content/sv"
57
58 `)
59
60 createContent := func(dir, name string) {
61 sb.WithContent(filepath.Join("content", dir, name), pageContent(1))
62 }
63
64 createBundledFiles := func(dir string) {
65 sb.WithContent(filepath.Join("content", dir, "data.json"), `{ "hello": "world" }`)
66 for i := 1; i <= 3; i++ {
67 sb.WithContent(filepath.Join("content", dir, fmt.Sprintf("page%d.md", i)), pageContent(1))
68 }
69 }
70
71 for _, lang := range []string{"en", "fr", "no", "sv"} {
72 for level := 1; level <= 5; level++ {
73 sectionDir := path.Join(lang, strings.Repeat("section/", level))
74 createContent(sectionDir, "_index.md")
75 createBundledFiles(sectionDir)
76 for i := 1; i <= 3; i++ {
77 leafBundleDir := path.Join(sectionDir, fmt.Sprintf("bundle%d", i))
78 createContent(leafBundleDir, "index.md")
79 createBundledFiles(path.Join(leafBundleDir, "assets1"))
80 createBundledFiles(path.Join(leafBundleDir, "assets1", "assets2"))
81 }
82 }
83 }
84
85 return sb
86 }
87
88 func getBenchmarkTestDataPageContentForMarkdown(size int, toml bool, category, markdown string) string {
89 base := `---
90 title: "My Page"
91 %s
92 ---
93
94 My page content.
95 `
96 if toml {
97 base = `+++
98 title="My Page"
99 %s
100 +++
101
102 My page content.
103 `
104 }
105
106 var categoryKey string
107 if category != "" {
108 categoryKey = fmt.Sprintf("categories: [%s]", category)
109 if toml {
110 categoryKey = fmt.Sprintf("categories=[%s]", category)
111 }
112 }
113 base = fmt.Sprintf(base, categoryKey)
114
115 return base + strings.Repeat(markdown, size)
116 }
117
118 const benchmarkMarkdownSnippets = `
119
120 ## Links
121
122
123 This is [an example](http://example.com/ "Title") inline link.
124
125 [This link](http://example.net/) has no title attribute.
126
127 This is [Relative](/all-is-relative).
128
129 See my [About](/about/) page for details.
130 `
131
132 func getBenchmarkSiteTestCases() []siteBenchmarkTestcase {
133 pageContentWithCategory := func(size int, category string) string {
134 return getBenchmarkTestDataPageContentForMarkdown(size, false, category, benchmarkMarkdownSnippets)
135 }
136
137 pageContent := func(size int) string {
138 return getBenchmarkTestDataPageContentForMarkdown(size, false, "", benchmarkMarkdownSnippets)
139 }
140
141 config := `
142 baseURL = "https://example.com"
143 `
144
145 benchmarks := []siteBenchmarkTestcase{
146 {
147 "Bundle with image", func(b testing.TB) *sitesBuilder {
148 sb := newTestSitesBuilder(b).WithConfigFile("toml", config)
149 sb.WithContent("content/blog/mybundle/index.md", pageContent(1))
150 sb.WithSunset("content/blog/mybundle/sunset1.jpg")
151
152 return sb
153 },
154 func(s *sitesBuilder) {
155 s.AssertFileContent("public/blog/mybundle/index.html", "/blog/mybundle/sunset1.jpg")
156 s.CheckExists("public/blog/mybundle/sunset1.jpg")
157 },
158 },
159 {
160 "Bundle with JSON file", func(b testing.TB) *sitesBuilder {
161 sb := newTestSitesBuilder(b).WithConfigFile("toml", config)
162 sb.WithContent("content/blog/mybundle/index.md", pageContent(1))
163 sb.WithContent("content/blog/mybundle/mydata.json", `{ "hello": "world" }`)
164
165 return sb
166 },
167 func(s *sitesBuilder) {
168 s.AssertFileContent("public/blog/mybundle/index.html", "Resources: application/json: /blog/mybundle/mydata.json")
169 s.CheckExists("public/blog/mybundle/mydata.json")
170 },
171 },
172 {
173 "Tags and categories", func(b testing.TB) *sitesBuilder {
174 sb := newTestSitesBuilder(b).WithConfigFile("toml", `
175 title = "Tags and Cats"
176 baseURL = "https://example.com"
177
178 `)
179
180 const pageTemplate = `
181 ---
182 title: "Some tags and cats"
183 categories: ["caGR", "cbGR"]
184 tags: ["taGR", "tbGR"]
185 ---
186
187 Some content.
188
189 `
190 for i := 1; i <= 100; i++ {
191 content := strings.Replace(pageTemplate, "GR", strconv.Itoa(i/3), -1)
192 sb.WithContent(fmt.Sprintf("content/page%d.md", i), content)
193 }
194
195 return sb
196 },
197 func(s *sitesBuilder) {
198 s.AssertFileContent("public/page3/index.html", "/page3/|Permalink: https://example.com/page3/")
199 s.AssertFileContent("public/tags/ta3/index.html", "a3")
200 },
201 },
202 {
203 "Canonify URLs", func(b testing.TB) *sitesBuilder {
204 sb := newTestSitesBuilder(b).WithConfigFile("toml", `
205 title = "Canon"
206 baseURL = "https://example.com"
207 canonifyURLs = true
208
209 `)
210 for i := 1; i <= 100; i++ {
211 sb.WithContent(fmt.Sprintf("content/page%d.md", i), pageContent(i))
212 }
213
214 return sb
215 },
216 func(s *sitesBuilder) {
217 s.AssertFileContent("public/page8/index.html", "https://example.com/about/")
218 },
219 },
220
221 {
222 "Deep content tree", func(b testing.TB) *sitesBuilder {
223 return getBenchmarkSiteDeepContent(b)
224 },
225 func(s *sitesBuilder) {
226 s.CheckExists("public/blog/mybundle/index.html")
227 s.Assert(len(s.H.Sites), qt.Equals, 4)
228 s.Assert(len(s.H.Sites[0].RegularPages()), qt.Equals, len(s.H.Sites[1].RegularPages()))
229 s.Assert(len(s.H.Sites[0].RegularPages()), qt.Equals, 30)
230 },
231 },
232 {
233 "TOML front matter", func(b testing.TB) *sitesBuilder {
234 sb := newTestSitesBuilder(b).WithConfigFile("toml", config)
235 for i := 1; i <= 200; i++ {
236 content := getBenchmarkTestDataPageContentForMarkdown(1, true, "\"a\", \"b\", \"c\"", benchmarkMarkdownSnippets)
237 sb.WithContent(fmt.Sprintf("content/p%d.md", i), content)
238 }
239
240 return sb
241 },
242 func(s *sitesBuilder) {
243 },
244 },
245 {
246 "Many HTML templates", func(b testing.TB) *sitesBuilder {
247 pageTemplateTemplate := `
248 <!DOCTYPE html>
249 <html>
250 <head>
251 <meta charset="utf-8">
252 <title>{{ if not .IsPage }}{{ .Title }}{{ else }}{{ printf "Site: %s" site.Title }}{{ end }}</title>
253 <style>
254 body {
255 margin: 3rem;
256 }
257 </style>
258 </head>
259 <body>
260 <div class="page">{{ .Content }}</div>
261 <ul>
262 {{ with .Pages }}
263 {{ range . }}
264 <li><a href="{{ .RelPermalink }}">{{ .LinkTitle }} {{ if not .IsNode }} (Page){{ end }}</a></li>
265 {{ end }}
266 {{ end }}
267 </ul>
268 </body>
269 </html>
270 `
271
272 sb := newTestSitesBuilder(b).WithConfigFile("toml", `
273 baseURL = "https://example.com"
274
275 [languages]
276 [languages.en]
277 weight=1
278 contentDir="content/en"
279 [languages.fr]
280 weight=2
281 contentDir="content/fr"
282 [languages.no]
283 weight=3
284 contentDir="content/no"
285 [languages.sv]
286 weight=4
287 contentDir="content/sv"
288
289 `)
290
291 createContent := func(dir, name string) {
292 sb.WithContent(filepath.Join("content", dir, name), pageContent(1))
293 }
294
295 for _, lang := range []string{"en", "fr", "no", "sv"} {
296 sb.WithTemplatesAdded(fmt.Sprintf("_default/single.%s.html", lang), pageTemplateTemplate)
297 sb.WithTemplatesAdded(fmt.Sprintf("_default/list.%s.html", lang), pageTemplateTemplate)
298
299 for level := 1; level <= 5; level++ {
300 sectionDir := path.Join(lang, strings.Repeat("section/", level))
301 createContent(sectionDir, "_index.md")
302 for i := 1; i <= 3; i++ {
303 leafBundleDir := path.Join(sectionDir, fmt.Sprintf("bundle%d", i))
304 createContent(leafBundleDir, "index.md")
305 }
306 }
307 }
308
309 return sb
310 },
311 func(s *sitesBuilder) {
312 s.CheckExists("public/blog/mybundle/index.html")
313 s.Assert(len(s.H.Sites), qt.Equals, 4)
314 s.Assert(len(s.H.Sites[0].RegularPages()), qt.Equals, len(s.H.Sites[1].RegularPages()))
315 s.Assert(len(s.H.Sites[0].RegularPages()), qt.Equals, 15)
316 },
317 },
318 {
319 "Page collections", func(b testing.TB) *sitesBuilder {
320 pageTemplateTemplate := `
321 {{ if .IsNode }}
322 {{ len .Paginator.Pages }}
323 {{ end }}
324 {{ len .Sections }}
325 {{ len .Pages }}
326 {{ len .RegularPages }}
327 {{ len .Resources }}
328 {{ len site.RegularPages }}
329 {{ len site.Pages }}
330 {{ with .NextInSection }}Next in section: {{ .RelPermalink }}{{ end }}
331 {{ with .PrevInSection }}Prev in section: {{ .RelPermalink }}{{ end }}
332 {{ with .Next }}Next: {{ .RelPermalink }}{{ end }}
333 {{ with .Prev }}Prev: {{ .RelPermalink }}{{ end }}
334 `
335
336 sb := newTestSitesBuilder(b).WithConfigFile("toml", `
337 baseURL = "https://example.com"
338
339 [languages]
340 [languages.en]
341 weight=1
342 contentDir="content/en"
343 [languages.fr]
344 weight=2
345 contentDir="content/fr"
346 [languages.no]
347 weight=3
348 contentDir="content/no"
349 [languages.sv]
350 weight=4
351 contentDir="content/sv"
352
353 `)
354
355 sb.WithTemplates("index.html", pageTemplateTemplate)
356 sb.WithTemplates("_default/single.html", pageTemplateTemplate)
357 sb.WithTemplates("_default/list.html", pageTemplateTemplate)
358
359 r := rand.New(rand.NewSource(99))
360
361 createContent := func(dir, name string) {
362 var content string
363 if strings.Contains(name, "_index") {
364 content = pageContent(1)
365 } else {
366 content = pageContentWithCategory(1, fmt.Sprintf("category%d", r.Intn(5)+1))
367 }
368
369 sb.WithContent(filepath.Join("content", dir, name), content)
370 }
371
372 createBundledFiles := func(dir string) {
373 sb.WithContent(filepath.Join("content", dir, "data.json"), `{ "hello": "world" }`)
374 for i := 1; i <= 3; i++ {
375 sb.WithContent(filepath.Join("content", dir, fmt.Sprintf("page%d.md", i)), pageContent(1))
376 }
377 }
378
379 for _, lang := range []string{"en", "fr", "no", "sv"} {
380 for level := 1; level <= r.Intn(5)+1; level++ {
381 sectionDir := path.Join(lang, strings.Repeat("section/", level))
382 createContent(sectionDir, "_index.md")
383 createBundledFiles(sectionDir)
384 for i := 1; i <= r.Intn(20)+1; i++ {
385 leafBundleDir := path.Join(sectionDir, fmt.Sprintf("bundle%d", i))
386 createContent(leafBundleDir, "index.md")
387 createBundledFiles(path.Join(leafBundleDir, "assets1"))
388 createBundledFiles(path.Join(leafBundleDir, "assets1", "assets2"))
389 }
390 }
391 }
392
393 return sb
394 },
395 func(s *sitesBuilder) {
396 s.CheckExists("public/blog/mybundle/index.html")
397 s.Assert(len(s.H.Sites), qt.Equals, 4)
398 s.Assert(len(s.H.Sites[0].RegularPages()), qt.Equals, 26)
399 },
400 },
401 {
402 "List terms", func(b testing.TB) *sitesBuilder {
403 pageTemplateTemplate := `
404 <ul>
405 {{ range (.GetTerms "categories") }}
406 <li><a href="{{ .Permalink }}">{{ .LinkTitle }}</a></li>
407 {{ end }}
408 </ul>
409 `
410
411 sb := newTestSitesBuilder(b).WithConfigFile("toml", `
412 baseURL = "https://example.com"
413 `)
414
415 sb.WithTemplates("_default/single.html", pageTemplateTemplate)
416 sb.WithTemplates("_default/list.html", "List")
417
418 r := rand.New(rand.NewSource(99))
419
420 createContent := func(dir, name string) {
421 var content string
422 if strings.Contains(name, "_index") {
423 // Empty
424 } else {
425 content = pageContentWithCategory(1, fmt.Sprintf("category%d", r.Intn(5)+1))
426 }
427 sb.WithContent(filepath.Join("content", dir, name), content)
428 }
429
430 for level := 1; level <= r.Intn(5)+1; level++ {
431 sectionDir := path.Join(strings.Repeat("section/", level))
432 createContent(sectionDir, "_index.md")
433 for i := 1; i <= r.Intn(33); i++ {
434 leafBundleDir := path.Join(sectionDir, fmt.Sprintf("bundle%d", i))
435 createContent(leafBundleDir, "index.md")
436 }
437 }
438
439 return sb
440 },
441 func(s *sitesBuilder) {
442 s.AssertFileContent("public/section/bundle8/index.html", `<a href="https://example.com/categories/category1/">`)
443 s.Assert(len(s.H.Sites), qt.Equals, 1)
444 s.Assert(len(s.H.Sites[0].RegularPages()), qt.Equals, 35)
445 },
446 },
447 }
448
449 return benchmarks
450 }
451
452 // Run the benchmarks below as tests. Mostly useful when adding new benchmark
453 // variants.
454 func TestBenchmarkSite(b *testing.T) {
455 benchmarks := getBenchmarkSiteTestCases()
456 for _, bm := range benchmarks {
457 if bm.name != "Deep content tree" {
458 continue
459 }
460 b.Run(bm.name, func(b *testing.T) {
461 s := bm.create(b)
462
463 err := s.BuildE(BuildCfg{})
464 if err != nil {
465 b.Fatal(err)
466 }
467 bm.check(s)
468 })
469 }
470 }
471
472 func TestBenchmarkSiteDeepContentEdit(t *testing.T) {
473 b := getBenchmarkSiteDeepContent(t).Running()
474 b.Build(BuildCfg{})
475
476 p := b.H.Sites[0].RegularPages()[12]
477
478 b.EditFiles(p.File().Filename(), fmt.Sprintf(`---
479 title: %s
480 ---
481
482 Edited!!`, p.Title()))
483
484 counters := &buildCounters{}
485
486 b.Build(BuildCfg{testCounters: counters})
487
488 // We currently rebuild all the language versions of the same content file.
489 // We could probably optimize that case, but it's not trivial.
490 b.Assert(int(counters.contentRenderCounter.Load()), qt.Equals, 4)
491 b.AssertFileContent("public"+p.RelPermalink()+"index.html", "Edited!!")
492 }
493
494 func BenchmarkSiteNew(b *testing.B) {
495 rnd := rand.New(rand.NewSource(32))
496 benchmarks := getBenchmarkSiteTestCases()
497 for _, edit := range []bool{true, false} {
498 for _, bm := range benchmarks {
499 name := bm.name
500 if edit {
501 name = "Edit_" + name
502 } else {
503 name = "Regular_" + name
504 }
505 b.Run(name, func(b *testing.B) {
506 sites := make([]*sitesBuilder, b.N)
507 for i := 0; i < b.N; i++ {
508 sites[i] = bm.create(b)
509 if edit {
510 sites[i].Running()
511 }
512 }
513
514 b.ResetTimer()
515 for i := 0; i < b.N; i++ {
516 if edit {
517 b.StopTimer()
518 }
519 s := sites[i]
520 err := s.BuildE(BuildCfg{})
521 if err != nil {
522 b.Fatal(err)
523 }
524 bm.check(s)
525
526 if edit {
527 if edit {
528 b.StartTimer()
529 }
530 // Edit a random page in a random language.
531 pages := s.H.Sites[rnd.Intn(len(s.H.Sites))].Pages()
532 var p page.Page
533 count := 0
534 for {
535 count++
536 if count > 100 {
537 panic("infinite loop")
538 }
539 p = pages[rnd.Intn(len(pages))]
540 if p.File() != nil {
541 break
542 }
543 }
544
545 s.EditFiles(p.File().Filename(), fmt.Sprintf(`---
546 title: %s
547 ---
548
549 Edited!!`, p.Title()))
550
551 err := s.BuildE(BuildCfg{})
552 if err != nil {
553 b.Fatal(err)
554 }
555 }
556 }
557 })
558 }
559 }
560 }