github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/hugolib/site_benchmark_new_test.go (about)

     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  }