hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

site_benchmark_new_test.go (14563B)

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