mirror of
				https://github.com/gohugoio/hugo.git
				synced 2024-05-11 05:54:58 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			538 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			538 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2019 The Hugo Authors. All rights reserved.
 | |
| //
 | |
| // Licensed under the Apache License, Version 2.0 (the "License");
 | |
| // you may not use this file except in compliance with the License.
 | |
| // You may obtain a copy of the License at
 | |
| // http://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing, software
 | |
| // distributed under the License is distributed on an "AS IS" BASIS,
 | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| // See the License for the specific language governing permissions and
 | |
| // limitations under the License.
 | |
| 
 | |
| package hugolib
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"math/rand"
 | |
| 	"path"
 | |
| 	"path/filepath"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"testing"
 | |
| 
 | |
| 	"github.com/gohugoio/hugo/resources/page"
 | |
| 
 | |
| 	qt "github.com/frankban/quicktest"
 | |
| )
 | |
| 
 | |
| type siteBenchmarkTestcase struct {
 | |
| 	name   string
 | |
| 	create func(t testing.TB) *sitesBuilder
 | |
| 	check  func(s *sitesBuilder)
 | |
| }
 | |
| 
 | |
| func getBenchmarkSiteDeepContent(b testing.TB) *sitesBuilder {
 | |
| 	pageContent := func(size int) string {
 | |
| 		return getBenchmarkTestDataPageContentForMarkdown(size, "", benchmarkMarkdownSnippets)
 | |
| 	}
 | |
| 
 | |
| 	sb := newTestSitesBuilder(b).WithConfigFile("toml", `
 | |
| baseURL = "https://example.com"
 | |
| 
 | |
| [languages]
 | |
| [languages.en]
 | |
| weight=1
 | |
| contentDir="content/en"
 | |
| [languages.fr]
 | |
| weight=2
 | |
| contentDir="content/fr"
 | |
| [languages.no]
 | |
| weight=3
 | |
| contentDir="content/no"
 | |
| [languages.sv]
 | |
| weight=4
 | |
| contentDir="content/sv"
 | |
| 			
 | |
| `)
 | |
| 
 | |
| 	createContent := func(dir, name string) {
 | |
| 		sb.WithContent(filepath.Join("content", dir, name), pageContent(1))
 | |
| 	}
 | |
| 
 | |
| 	createBundledFiles := func(dir string) {
 | |
| 		sb.WithContent(filepath.Join("content", dir, "data.json"), `{ "hello": "world" }`)
 | |
| 		for i := 1; i <= 3; i++ {
 | |
| 			sb.WithContent(filepath.Join("content", dir, fmt.Sprintf("page%d.md", i)), pageContent(1))
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	for _, lang := range []string{"en", "fr", "no", "sv"} {
 | |
| 		for level := 1; level <= 5; level++ {
 | |
| 			sectionDir := path.Join(lang, strings.Repeat("section/", level))
 | |
| 			createContent(sectionDir, "_index.md")
 | |
| 			createBundledFiles(sectionDir)
 | |
| 			for i := 1; i <= 3; i++ {
 | |
| 				leafBundleDir := path.Join(sectionDir, fmt.Sprintf("bundle%d", i))
 | |
| 				createContent(leafBundleDir, "index.md")
 | |
| 				createBundledFiles(path.Join(leafBundleDir, "assets1"))
 | |
| 				createBundledFiles(path.Join(leafBundleDir, "assets1", "assets2"))
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return sb
 | |
| }
 | |
| 
 | |
| func getBenchmarkTestDataPageContentForMarkdown(size int, category, markdown string) string {
 | |
| 	base := `---
 | |
| title: "My Page"
 | |
| %s
 | |
| ---
 | |
| 
 | |
| My page content.
 | |
| `
 | |
| 
 | |
| 	var categoryKey string
 | |
| 	if category != "" {
 | |
| 		categoryKey = fmt.Sprintf("categories: [%s]", category)
 | |
| 	}
 | |
| 	base = fmt.Sprintf(base, categoryKey)
 | |
| 
 | |
| 	return base + strings.Repeat(markdown, size)
 | |
| }
 | |
| 
 | |
| const benchmarkMarkdownSnippets = `
 | |
| 
 | |
| ## Links
 | |
| 
 | |
| 
 | |
| This is [an example](http://example.com/ "Title") inline link.
 | |
| 
 | |
| [This link](http://example.net/) has no title attribute.
 | |
| 
 | |
| This is [Relative](/all-is-relative).
 | |
| 
 | |
| See my [About](/about/) page for details. 
 | |
| `
 | |
| 
 | |
| func getBenchmarkSiteNewTestCases() []siteBenchmarkTestcase {
 | |
| 
 | |
| 	pageContentWithCategory := func(size int, category string) string {
 | |
| 		return getBenchmarkTestDataPageContentForMarkdown(size, category, benchmarkMarkdownSnippets)
 | |
| 	}
 | |
| 
 | |
| 	pageContent := func(size int) string {
 | |
| 		return getBenchmarkTestDataPageContentForMarkdown(size, "", benchmarkMarkdownSnippets)
 | |
| 	}
 | |
| 
 | |
| 	config := `
 | |
| baseURL = "https://example.com"
 | |
| `
 | |
| 
 | |
| 	benchmarks := []siteBenchmarkTestcase{
 | |
| 		{"Bundle with image", func(b testing.TB) *sitesBuilder {
 | |
| 			sb := newTestSitesBuilder(b).WithConfigFile("toml", config)
 | |
| 			sb.WithContent("content/blog/mybundle/index.md", pageContent(1))
 | |
| 			sb.WithSunset("content/blog/mybundle/sunset1.jpg")
 | |
| 
 | |
| 			return sb
 | |
| 		},
 | |
| 			func(s *sitesBuilder) {
 | |
| 				s.AssertFileContent("public/blog/mybundle/index.html", "/blog/mybundle/sunset1.jpg")
 | |
| 				s.CheckExists("public/blog/mybundle/sunset1.jpg")
 | |
| 
 | |
| 			},
 | |
| 		},
 | |
| 		{"Bundle with JSON file", func(b testing.TB) *sitesBuilder {
 | |
| 			sb := newTestSitesBuilder(b).WithConfigFile("toml", config)
 | |
| 			sb.WithContent("content/blog/mybundle/index.md", pageContent(1))
 | |
| 			sb.WithContent("content/blog/mybundle/mydata.json", `{ "hello": "world" }`)
 | |
| 
 | |
| 			return sb
 | |
| 		},
 | |
| 			func(s *sitesBuilder) {
 | |
| 				s.AssertFileContent("public/blog/mybundle/index.html", "Resources: application/json: /blog/mybundle/mydata.json")
 | |
| 				s.CheckExists("public/blog/mybundle/mydata.json")
 | |
| 
 | |
| 			},
 | |
| 		},
 | |
| 		{"Tags and categories", func(b testing.TB) *sitesBuilder {
 | |
| 			sb := newTestSitesBuilder(b).WithConfigFile("toml", `
 | |
| title = "Tags and Cats"
 | |
| baseURL = "https://example.com"
 | |
| 
 | |
| `)
 | |
| 
 | |
| 			const pageTemplate = `
 | |
| ---
 | |
| title: "Some tags and cats"
 | |
| categories: ["caGR", "cbGR"]
 | |
| tags: ["taGR", "tbGR"]
 | |
| ---
 | |
| 
 | |
| Some content.
 | |
| 			
 | |
| `
 | |
| 			for i := 1; i <= 100; i++ {
 | |
| 				content := strings.Replace(pageTemplate, "GR", strconv.Itoa(i/3), -1)
 | |
| 				sb.WithContent(fmt.Sprintf("content/page%d.md", i), content)
 | |
| 			}
 | |
| 
 | |
| 			return sb
 | |
| 		},
 | |
| 			func(s *sitesBuilder) {
 | |
| 				s.AssertFileContent("public/page3/index.html", "/page3/|Permalink: https://example.com/page3/")
 | |
| 				s.AssertFileContent("public/tags/ta3/index.html", "|ta3|")
 | |
| 			},
 | |
| 		},
 | |
| 		{"Canonify URLs", func(b testing.TB) *sitesBuilder {
 | |
| 			sb := newTestSitesBuilder(b).WithConfigFile("toml", `
 | |
| title = "Canon"
 | |
| baseURL = "https://example.com"
 | |
| canonifyURLs = true
 | |
| 
 | |
| `)
 | |
| 			for i := 1; i <= 100; i++ {
 | |
| 				sb.WithContent(fmt.Sprintf("content/page%d.md", i), pageContent(i))
 | |
| 			}
 | |
| 
 | |
| 			return sb
 | |
| 		},
 | |
| 			func(s *sitesBuilder) {
 | |
| 				s.AssertFileContent("public/page8/index.html", "https://example.com/about/")
 | |
| 			},
 | |
| 		},
 | |
| 
 | |
| 		{"Deep content tree", func(b testing.TB) *sitesBuilder {
 | |
| 			return getBenchmarkSiteDeepContent(b)
 | |
| 		},
 | |
| 			func(s *sitesBuilder) {
 | |
| 				s.CheckExists("public/blog/mybundle/index.html")
 | |
| 				s.Assert(len(s.H.Sites), qt.Equals, 4)
 | |
| 				s.Assert(len(s.H.Sites[0].RegularPages()), qt.Equals, len(s.H.Sites[1].RegularPages()))
 | |
| 				s.Assert(len(s.H.Sites[0].RegularPages()), qt.Equals, 30)
 | |
| 
 | |
| 			},
 | |
| 		},
 | |
| 		{"Many HTML templates", func(b testing.TB) *sitesBuilder {
 | |
| 
 | |
| 			pageTemplateTemplate := `
 | |
| <!DOCTYPE html>
 | |
| <html>
 | |
|   <head>
 | |
|     <meta charset="utf-8">
 | |
|     <title>{{ if not .IsPage }}{{ .Title }}{{ else }}{{ printf "Site: %s" site.Title }}{{ end }}</title>
 | |
|     <style>
 | |
|      body {
 | |
|        margin: 3rem;
 | |
|      }
 | |
|     </style>
 | |
|   </head>
 | |
|   <body>
 | |
|     <div class="page">{{ .Content }}</div>
 | |
|     <ul>
 | |
|     {{ with .Pages }}
 | |
|     {{ range . }}
 | |
|     <li><a href="{{ .RelPermalink }}">{{ .LinkTitle }} {{ if not .IsNode }} (Page){{ end }}</a></li>
 | |
|     {{ end }}
 | |
|     {{ end }}
 | |
|     </ul>
 | |
|   </body>
 | |
| </html>
 | |
| `
 | |
| 
 | |
| 			sb := newTestSitesBuilder(b).WithConfigFile("toml", `
 | |
| baseURL = "https://example.com"
 | |
| 
 | |
| [languages]
 | |
| [languages.en]
 | |
| weight=1
 | |
| contentDir="content/en"
 | |
| [languages.fr]
 | |
| weight=2
 | |
| contentDir="content/fr"
 | |
| [languages.no]
 | |
| weight=3
 | |
| contentDir="content/no"
 | |
| [languages.sv]
 | |
| weight=4
 | |
| contentDir="content/sv"
 | |
| 			
 | |
| `)
 | |
| 
 | |
| 			createContent := func(dir, name string) {
 | |
| 				sb.WithContent(filepath.Join("content", dir, name), pageContent(1))
 | |
| 			}
 | |
| 
 | |
| 			for _, lang := range []string{"en", "fr", "no", "sv"} {
 | |
| 				sb.WithTemplatesAdded(fmt.Sprintf("_default/single.%s.html", lang), pageTemplateTemplate)
 | |
| 				sb.WithTemplatesAdded(fmt.Sprintf("_default/list.%s.html", lang), pageTemplateTemplate)
 | |
| 
 | |
| 				for level := 1; level <= 5; level++ {
 | |
| 					sectionDir := path.Join(lang, strings.Repeat("section/", level))
 | |
| 					createContent(sectionDir, "_index.md")
 | |
| 					for i := 1; i <= 3; i++ {
 | |
| 						leafBundleDir := path.Join(sectionDir, fmt.Sprintf("bundle%d", i))
 | |
| 						createContent(leafBundleDir, "index.md")
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			return sb
 | |
| 		},
 | |
| 			func(s *sitesBuilder) {
 | |
| 				s.CheckExists("public/blog/mybundle/index.html")
 | |
| 				s.Assert(len(s.H.Sites), qt.Equals, 4)
 | |
| 				s.Assert(len(s.H.Sites[0].RegularPages()), qt.Equals, len(s.H.Sites[1].RegularPages()))
 | |
| 				s.Assert(len(s.H.Sites[0].RegularPages()), qt.Equals, 15)
 | |
| 
 | |
| 			},
 | |
| 		},
 | |
| 		{"Page collections", func(b testing.TB) *sitesBuilder {
 | |
| 
 | |
| 			pageTemplateTemplate := `
 | |
| {{ if .IsNode }}
 | |
| {{ len .Paginator.Pages }}
 | |
| {{ end }}
 | |
| {{ len .Sections }}
 | |
| {{ len .Pages }}
 | |
| {{ len .RegularPages }}
 | |
| {{ len .Resources }}
 | |
| {{ len site.RegularPages }}
 | |
| {{ len site.Pages }}
 | |
| {{ with .NextInSection }}Next in section: {{ .RelPermalink }}{{ end }}
 | |
| {{ with .PrevInSection }}Prev in section: {{ .RelPermalink }}{{ end }}
 | |
| {{ with .Next }}Next: {{ .RelPermalink }}{{ end }}
 | |
| {{ with .Prev }}Prev: {{ .RelPermalink }}{{ end }}
 | |
| `
 | |
| 
 | |
| 			sb := newTestSitesBuilder(b).WithConfigFile("toml", `
 | |
| baseURL = "https://example.com"
 | |
| 
 | |
| [languages]
 | |
| [languages.en]
 | |
| weight=1
 | |
| contentDir="content/en"
 | |
| [languages.fr]
 | |
| weight=2
 | |
| contentDir="content/fr"
 | |
| [languages.no]
 | |
| weight=3
 | |
| contentDir="content/no"
 | |
| [languages.sv]
 | |
| weight=4
 | |
| contentDir="content/sv"
 | |
| 			
 | |
| `)
 | |
| 
 | |
| 			sb.WithTemplates("index.html", pageTemplateTemplate)
 | |
| 			sb.WithTemplates("_default/single.html", pageTemplateTemplate)
 | |
| 			sb.WithTemplates("_default/list.html", pageTemplateTemplate)
 | |
| 
 | |
| 			r := rand.New(rand.NewSource(99))
 | |
| 
 | |
| 			createContent := func(dir, name string) {
 | |
| 				var content string
 | |
| 				if strings.Contains(name, "_index") {
 | |
| 					content = pageContent(1)
 | |
| 
 | |
| 				} else {
 | |
| 					content = pageContentWithCategory(1, fmt.Sprintf("category%d", r.Intn(5)+1))
 | |
| 				}
 | |
| 
 | |
| 				sb.WithContent(filepath.Join("content", dir, name), content)
 | |
| 			}
 | |
| 
 | |
| 			createBundledFiles := func(dir string) {
 | |
| 				sb.WithContent(filepath.Join("content", dir, "data.json"), `{ "hello": "world" }`)
 | |
| 				for i := 1; i <= 3; i++ {
 | |
| 					sb.WithContent(filepath.Join("content", dir, fmt.Sprintf("page%d.md", i)), pageContent(1))
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			for _, lang := range []string{"en", "fr", "no", "sv"} {
 | |
| 				for level := 1; level <= r.Intn(5)+1; level++ {
 | |
| 					sectionDir := path.Join(lang, strings.Repeat("section/", level))
 | |
| 					createContent(sectionDir, "_index.md")
 | |
| 					createBundledFiles(sectionDir)
 | |
| 					for i := 1; i <= r.Intn(20)+1; i++ {
 | |
| 						leafBundleDir := path.Join(sectionDir, fmt.Sprintf("bundle%d", i))
 | |
| 						createContent(leafBundleDir, "index.md")
 | |
| 						createBundledFiles(path.Join(leafBundleDir, "assets1"))
 | |
| 						createBundledFiles(path.Join(leafBundleDir, "assets1", "assets2"))
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			return sb
 | |
| 		},
 | |
| 			func(s *sitesBuilder) {
 | |
| 				s.CheckExists("public/blog/mybundle/index.html")
 | |
| 				s.Assert(len(s.H.Sites), qt.Equals, 4)
 | |
| 				s.Assert(len(s.H.Sites[0].RegularPages()), qt.Equals, 26)
 | |
| 
 | |
| 			},
 | |
| 		},
 | |
| 		{"List terms", func(b testing.TB) *sitesBuilder {
 | |
| 
 | |
| 			pageTemplateTemplate := `
 | |
| <ul>
 | |
|     {{ range (.GetTerms "categories") }}
 | |
|         <li><a href="{{ .Permalink }}">{{ .LinkTitle }}</a></li>
 | |
|    {{ end }}
 | |
| </ul>
 | |
| `
 | |
| 
 | |
| 			sb := newTestSitesBuilder(b).WithConfigFile("toml", `
 | |
| baseURL = "https://example.com"
 | |
| `)
 | |
| 
 | |
| 			sb.WithTemplates("_default/single.html", pageTemplateTemplate)
 | |
| 
 | |
| 			r := rand.New(rand.NewSource(99))
 | |
| 
 | |
| 			createContent := func(dir, name string) {
 | |
| 				var content string
 | |
| 				if strings.Contains(name, "_index") {
 | |
| 					content = pageContent(1)
 | |
| 				} else {
 | |
| 					content = pageContentWithCategory(1, fmt.Sprintf("category%d", r.Intn(5)+1))
 | |
| 					sb.WithContent(filepath.Join("content", dir, name), content)
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			for level := 1; level <= r.Intn(5)+1; level++ {
 | |
| 				sectionDir := path.Join(strings.Repeat("section/", level))
 | |
| 				createContent(sectionDir, "_index.md")
 | |
| 				for i := 1; i <= r.Intn(33); i++ {
 | |
| 					leafBundleDir := path.Join(sectionDir, fmt.Sprintf("bundle%d", i))
 | |
| 					createContent(leafBundleDir, "index.md")
 | |
| 				}
 | |
| 			}
 | |
| 
 | |
| 			return sb
 | |
| 		},
 | |
| 			func(s *sitesBuilder) {
 | |
| 				s.AssertFileContent("public/section/bundle8/index.html", ` <li><a href="https://example.com/categories/category1/">category1</a></li>`)
 | |
| 				s.Assert(len(s.H.Sites), qt.Equals, 1)
 | |
| 				s.Assert(len(s.H.Sites[0].RegularPages()), qt.Equals, 35)
 | |
| 
 | |
| 			},
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	return benchmarks
 | |
| 
 | |
| }
 | |
| 
 | |
| // Run the benchmarks below as tests. Mostly useful when adding new benchmark
 | |
| // variants.
 | |
| func TestBenchmarkSiteNew(b *testing.T) {
 | |
| 	benchmarks := getBenchmarkSiteNewTestCases()
 | |
| 	for _, bm := range benchmarks {
 | |
| 		b.Run(bm.name, func(b *testing.T) {
 | |
| 			s := bm.create(b)
 | |
| 
 | |
| 			err := s.BuildE(BuildCfg{})
 | |
| 			if err != nil {
 | |
| 				b.Fatal(err)
 | |
| 			}
 | |
| 			bm.check(s)
 | |
| 
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func TestBenchmarkSiteDeepContentEdit(t *testing.T) {
 | |
| 	b := getBenchmarkSiteDeepContent(t).Running()
 | |
| 	b.Build(BuildCfg{})
 | |
| 
 | |
| 	p := b.H.Sites[0].RegularPages()[12]
 | |
| 
 | |
| 	b.EditFiles(p.File().Filename(), fmt.Sprintf(`---
 | |
| title: %s
 | |
| ---
 | |
| 
 | |
| Edited!!`, p.Title()))
 | |
| 
 | |
| 	counters := &testCounters{}
 | |
| 
 | |
| 	b.Build(BuildCfg{testCounters: counters})
 | |
| 
 | |
| 	// We currently rebuild all the language versions of the same content file.
 | |
| 	// We could probably optimize that case, but it's not trivial.
 | |
| 	b.Assert(int(counters.contentRenderCounter), qt.Equals, 4)
 | |
| 	b.AssertFileContent("public"+p.RelPermalink()+"index.html", "Edited!!")
 | |
| 
 | |
| }
 | |
| 
 | |
| func BenchmarkSiteNew(b *testing.B) {
 | |
| 	rnd := rand.New(rand.NewSource(32))
 | |
| 	benchmarks := getBenchmarkSiteNewTestCases()
 | |
| 	for _, edit := range []bool{true, false} {
 | |
| 		for _, bm := range benchmarks {
 | |
| 			name := bm.name
 | |
| 			if edit {
 | |
| 				name = "Edit_" + name
 | |
| 			} else {
 | |
| 				name = "Regular_" + name
 | |
| 			}
 | |
| 			b.Run(name, func(b *testing.B) {
 | |
| 				sites := make([]*sitesBuilder, b.N)
 | |
| 				for i := 0; i < b.N; i++ {
 | |
| 					sites[i] = bm.create(b)
 | |
| 					if edit {
 | |
| 						sites[i].Running()
 | |
| 					}
 | |
| 				}
 | |
| 
 | |
| 				b.ResetTimer()
 | |
| 				for i := 0; i < b.N; i++ {
 | |
| 					if edit {
 | |
| 						b.StopTimer()
 | |
| 					}
 | |
| 					s := sites[i]
 | |
| 					err := s.BuildE(BuildCfg{})
 | |
| 					if err != nil {
 | |
| 						b.Fatal(err)
 | |
| 					}
 | |
| 					bm.check(s)
 | |
| 
 | |
| 					if edit {
 | |
| 						if edit {
 | |
| 							b.StartTimer()
 | |
| 						}
 | |
| 						// Edit a random page in a random language.
 | |
| 						pages := s.H.Sites[rnd.Intn(len(s.H.Sites))].Pages()
 | |
| 						var p page.Page
 | |
| 						count := 0
 | |
| 						for {
 | |
| 							count++
 | |
| 							if count > 100 {
 | |
| 								panic("infinite loop")
 | |
| 							}
 | |
| 							p = pages[rnd.Intn(len(pages))]
 | |
| 							if !p.File().IsZero() {
 | |
| 								break
 | |
| 							}
 | |
| 						}
 | |
| 
 | |
| 						s.EditFiles(p.File().Filename(), fmt.Sprintf(`---
 | |
| title: %s
 | |
| ---
 | |
| 
 | |
| Edited!!`, p.Title()))
 | |
| 
 | |
| 						err := s.BuildE(BuildCfg{})
 | |
| 						if err != nil {
 | |
| 							b.Fatal(err)
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 			})
 | |
| 		}
 | |
| 	}
 | |
| }
 |